1/** 2 * The offer form's inline validation. 3 * 4 * The rules mirror findmy_offer_validate() so a farmer sees the same message 5 * the server would have sent back, without the round trip. The server still 6 * validates every submission â this only stops one that is certain to fail, and 7 * it never reports success on its own. 8 */ 9( function () { 10 'use strict'; 11 12 var EMAIL = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; 13 14 function init( form ) { 15 var messages = {}; 16 17 // The server rendered its own messages into these; reuse the wording. 18 form.querySelectorAll( '[data-fml-error]' ).forEach( function ( el ) { 19 messages[ el.getAttribute( 'data-fml-error' ) ] = el.dataset.fmlMessage || el.textContent.trim(); 20 } ); 21 22 function show( field, message ) { 23 var el = form.querySelector( '[data-fml-error="' + field + '"]' ); 24 25 if ( ! el ) { 26 return; 27 } 28 29 if ( message ) { 30 el.textContent = message; 31 el.hidden = false; 32 } else { 33 el.hidden = true; 34 } 35 } 36 37 function value( name ) { 38 var nodes = form.querySelectorAll( '[name="' + name + '"]' ); 39 40 if ( ! nodes.length ) { 41 return ''; 42 } 43 44 if ( 'radio' === nodes[ 0 ].type ) { 45 var on = form.querySelector( '[name="' + name + '"]:checked' ); 46 47 return on ? on.value : ''; 48 } 49 50 return nodes[ 0 ].value; 51 } 52 53 function check() { 54 var errors = {}; 55 56 if ( ! value( 'dyr' ) ) { 57 errors.dyr = messages.dyr; 58 } 59 60 if ( ! value( 'antall' ) || Number( value( 'antall' ) ) < 1 ) { 61 errors.antall = messages.antall; 62 } 63 64 if ( ! value( 'cov' ) ) { 65 errors.cov = messages.cov; 66 } 67 68 if ( ! value( 'navn' ).trim() ) { 69 errors.navn = messages.navn; 70 } 71 72 if ( ! EMAIL.test( value( 'epost' ).trim() ) ) { 73 errors.epost = messages.epost; 74 } 75 76 if ( ! value( 'land' ) ) { 77 errors.land = messages.land; 78 } 79 80 return errors; 81 } 82 83 form.addEventListener( 'submit', function ( event ) { 84 var errors = check(); 85 var fields = Object.keys( errors ); 86 87 [ 'dyr', 'antall', 'cov', 'navn', 'epost', 'land' ].forEach( function ( field ) { 88 show( field, errors[ field ] ); 89 } ); 90 91 if ( ! fields.length ) { 92 return; 93 } 94 95 event.preventDefault(); 96 97 var first = form.querySelector( '[name="' + fields[ 0 ] + '"]' ); 98 99 if ( first ) { 100 first.focus(); 101 first.scrollIntoView( { block: 'center', behavior: 'smooth' } ); 102 } 103 } ); 104 105 // Clear a message as soon as the farmer fixes the field. 106 form.addEventListener( 'input', function ( event ) { 107 var name = event.target.name; 108 109 if ( name && messages.hasOwnProperty( name ) ) { 110 show( name, '' ); 111 } 112 } ); 113 114 form.addEventListener( 'change', function ( event ) { 115 var name = event.target.name; 116 117 if ( name && messages.hasOwnProperty( name ) ) { 118 show( name, '' ); 119 } 120 } ); 121 } 122 123 /* 124 * The CF7 version of the form. CF7 validates on the server and drops a tip 125 * into each field's wrapper; this names those fields under the button, takes 126 * the farmer to the first one, and clears a tip as soon as it is fixed â 127 * CF7 does not do that for the theme's own tiles and cards. 128 */ 129 130 // A field's label as the farmer saw it, without the * or "(valgfritt)". 131 function cf7Label( wrap ) { 132 var field = wrap.closest( '.fml-field' ); 133 var group = wrap.closest( '.fml-offerform__group' ); 134 var el = field ? field.querySelector( ':scope > span' ) 135 : group ? group.querySelector( '.fml-offerform__labels span' ) 136 : null; 137 138 if ( ! el ) { 139 return ''; 140 } 141 142 el = el.cloneNode( true ); 143 el.querySelectorAll( 'abbr, em' ).forEach( function ( node ) { 144 node.remove(); 145 } ); 146 147 return el.textContent.trim(); 148 } 149 150 function cf7Invalid( form ) { 151 return Array.prototype.map.call( form.querySelectorAll( '.wpcf7-not-valid-tip' ), function ( tip ) { 152 return tip.closest( '.wpcf7-form-control-wrap' ); 153 } ).filter( Boolean ); 154 } 155 156 function cf7Summary( form ) { 157 var output = form.querySelector( '.wpcf7-response-output' ); 158 159 if ( ! output ) { 160 return; 161 } 162 163 var list = output.querySelector( '.fml-offerform__missing' ); 164 var names = cf7Invalid( form ).map( cf7Label ).filter( function ( name, i, all ) { 165 return name && all.indexOf( name ) === i; 166 } ); 167 168 if ( ! names.length ) { 169 if ( list ) { 170 list.remove(); 171 } 172 return; 173 } 174 175 if ( ! list ) { 176 list = document.createElement( 'span' ); 177 list.className = 'fml-offerform__missing'; 178 output.appendChild( list ); 179 } 180 181 list.textContent = names.join( ', ' ); 182 } 183 184 function cf7Fixed( control ) { 185 var wrap = control.closest( '.wpcf7-form-control-wrap' ); 186 187 if ( 'radio' === control.type ) { 188 return !! wrap.querySelector( 'input:checked' ); 189 } 190 191 if ( 'email' === control.type ) { 192 return EMAIL.test( control.value.trim() ); 193 } 194 195 if ( 'number' === control.type ) { 196 return Number( control.value ) >= 1; 197 } 198 199 return '' !== control.value.trim(); 200 } 201 202 function cf7Clear( event ) { 203 var control = event.target; 204 var form = control.form; 205 var wrap = control.closest( '.wpcf7-form-control-wrap' ); 206 207 if ( ! form || ! wrap || ! wrap.querySelector( '.wpcf7-not-valid-tip' ) || ! cf7Fixed( control ) ) { 208 return; 209 } 210 211 wrap.querySelectorAll( '.wpcf7-not-valid-tip' ).forEach( function ( tip ) { 212 tip.remove(); 213 } ); 214 wrap.querySelectorAll( '.wpcf7-not-valid' ).forEach( function ( el ) { 215 el.classList.remove( 'wpcf7-not-valid' ); 216 el.setAttribute( 'aria-invalid', 'false' ); 217 } ); 218 219 cf7Summary( form ); 220 } 221 222 function cf7Init( form ) { 223 form.addEventListener( 'input', cf7Clear ); 224 form.addEventListener( 'change', cf7Clear ); 225 226 form.addEventListener( 'wpcf7invalid', function () { 227 // CF7 writes its own message into the response line after this event 228 // fires, so wait for it before adding the list. 229 setTimeout( function () { 230 var wrap = cf7Invalid( form )[ 0 ]; 231 232 cf7Summary( form ); 233 234 if ( ! wrap ) { 235 return; 236 } 237 238 var control = wrap.querySelector( 'input:checked, input, textarea, select' ); 239 var target = wrap.closest( '.fml-field' ) || wrap.closest( '.fml-offerform__group' ) || wrap; 240 241 target.scrollIntoView( { block: 'center', behavior: 'smooth' } ); 242 243 if ( control ) { 244 control.focus( { preventScroll: true } ); 245 } 246 }, 0 ); 247 } ); 248 } 249 250 function boot() { 251 document.querySelectorAll( '[data-fml-offer]' ).forEach( init ); 252 document.querySelectorAll( '.fml-offerform__cf7 form' ).forEach( cf7Init ); 253 } 254 255 if ( 'loading' === document.readyState ) { 256 document.addEventListener( 'DOMContentLoaded', boot ); 257 } else { 258 boot(); 259 } 260}() );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.