PluginProbe ʕ •ᴥ•ʔ
Conditional Fields for Contact Form 7 / 2.5.13
Conditional Fields for Contact Form 7 v2.5.13
2.7.8 2.7.7 2.7.6 2.7.5 2.7.4 2.7.3 2.7.2 0.2.4 0.2.5 0.2.6 0.2.7 0.2.8 0.2.9 1.0 1.1 1.2 1.2.1 1.2.2 1.2.3 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.4 1.4.1 1.4.2 1.4.3 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6.1 1.6.2 1.6.3 1.6.5 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.8 1.7.9 1.8 1.8.1 1.8.2 1.8.3 1.8.5 1.8.6 1.8.7 1.9 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2 2.2.1 2.2.10 2.2.11 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3 2.3.1 2.3.10 2.3.11 2.3.12 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.14 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.7 2.7.1 trunk 0.1 0.1.1 0.1.2 0.1.3 0.1.4 0.1.5 0.1.6 0.1.7 0.2 0.2.1 0.2.2 0.2.3
cf7-conditional-fields / js / scripts.js
cf7-conditional-fields / js Last commit date
polyfill.js 3 years ago scripts.js 1 year ago scripts.js.map 3 years ago scripts_admin copy.js 4 years ago scripts_admin.js 1 year ago scripts_admin_all_pages.js 2 years ago scripts_es6.js 3 years ago temp.js 4 years ago
scripts.js
919 lines
1 "use strict";
2
3 // disable client side validation introduced in CF7 5.6 for now
4 if (typeof wpcf7 !== 'undefined') {
5 wpcf7.validate = (a,b) => null;
6 }
7
8 let cf7signature_resized = 0; // for compatibility with contact-form-7-signature-addon
9
10 let wpcf7cf_timeout;
11 let wpcf7cf_change_time_ms = 100; // the timeout after a change in the form is detected
12
13 if (window.wpcf7 && !wpcf7.setStatus) {
14 wpcf7.setStatus = ( form, status ) => {
15 form = form.length ? form[0] : form; // if form is a jQuery object, only grab te html-element
16 const defaultStatuses = new Map( [
17 // 0: Status in API response, 1: Status in HTML class
18 [ 'init', 'init' ],
19 [ 'validation_failed', 'invalid' ],
20 [ 'acceptance_missing', 'unaccepted' ],
21 [ 'spam', 'spam' ],
22 [ 'aborted', 'aborted' ],
23 [ 'mail_sent', 'sent' ],
24 [ 'mail_failed', 'failed' ],
25 [ 'submitting', 'submitting' ],
26 [ 'resetting', 'resetting' ],
27 ] );
28
29 if ( defaultStatuses.has( status ) ) {
30 status = defaultStatuses.get( status );
31 }
32
33 if ( ! Array.from( defaultStatuses.values() ).includes( status ) ) {
34 status = status.replace( /[^0-9a-z]+/i, ' ' ).trim();
35 status = status.replace( /\s+/, '-' );
36 status = `custom-${ status }`;
37 }
38
39 const prevStatus = form.getAttribute( 'data-status' );
40
41 form.wpcf7.status = status;
42 form.setAttribute( 'data-status', status );
43 form.classList.add( status );
44
45 if ( prevStatus && prevStatus !== status ) {
46 form.classList.remove( prevStatus );
47 }
48
49 return status;
50 };
51 }
52
53 if (window.wpcf7cf_running_tests) {
54 jQuery('input[name="_wpcf7cf_options"]').each(function(e) {
55 const $input = jQuery(this);
56 const opt = JSON.parse($input.val());
57 opt.settings.animation_intime = 0;
58 opt.settings.animation_outtime = 0;
59 $input.val(JSON.stringify(opt));
60 });
61 wpcf7cf_change_time_ms = 0;
62 }
63
64 const wpcf7cf_show_animation = { "height": "show", "marginTop": "show", "marginBottom": "show", "paddingTop": "show", "paddingBottom": "show" };
65 const wpcf7cf_hide_animation = { "height": "hide", "marginTop": "hide", "marginBottom": "hide", "paddingTop": "hide", "paddingBottom": "hide" };
66
67 const wpcf7cf_show_step_animation = { "opacity": "show" };
68 const wpcf7cf_hide_step_animation = { "opacity": "hide" };
69
70 const wpcf7cf_change_events = 'input.wpcf7cf paste.wpcf7cf change.wpcf7cf click.wpcf7cf propertychange.wpcf7cf changedisabledprop.wpcf7cf';
71
72 const wpcf7cf_forms = [];
73
74 const Wpcf7cfForm = function($form) {
75
76 const options_element = $form.find('input[name="_wpcf7cf_options"]').eq(0);
77 if (!options_element.length || !options_element.val()) {
78 // doesn't look like a CF7 form created with conditional fields plugin enabled.
79 return false;
80 }
81
82 const form = this;
83
84 const form_options = JSON.parse(options_element.val());
85
86 form.$form = $form;
87 form.$input_hidden_group_fields = $form.find('[name="_wpcf7cf_hidden_group_fields"]');
88 form.$input_hidden_groups = $form.find('[name="_wpcf7cf_hidden_groups"]');
89 form.$input_visible_groups = $form.find('[name="_wpcf7cf_visible_groups"]');
90 form.$input_repeaters = $form.find('[name="_wpcf7cf_repeaters"]');
91 form.$input_steps = $form.find('[name="_wpcf7cf_steps"]');
92
93 form.unit_tag = $form.closest('.wpcf7').attr('id');
94 form.conditions = form_options['conditions'];
95
96 form.simpleDom = null;
97
98 form.reloadSimpleDom = function() {
99 form.simpleDom = wpcf7cf.get_simplified_dom_model(form.$form[0]);
100 }
101
102 // quicker than reloading the simpleDom completely with reloadSimpleDom
103 form.updateSimpleDom = function() {
104 if (!form.simpleDom) {
105 form.reloadSimpleDom();
106 }
107 const inputs = Object.values(form.simpleDom).filter(item => item.type === 'input');
108 const formdata = new FormData(form.$form[0]);
109
110 let formdataEntries = [... formdata.entries()].map(entry => [ entry[0], entry[1].name ?? entry[1] ]);
111 const buttonEntries = [ ... jQuery('button', form.$form) ].map(entry => [entry.name, entry.value]);
112 formdataEntries = formdataEntries.concat(buttonEntries);
113
114 inputs.forEach(simpleDomItem => {
115 const newValue = form.getNewDomValueIfChanged(simpleDomItem, formdataEntries);
116 if (newValue !== null) {
117 form.simpleDom[simpleDomItem.name].val = newValue;
118 }
119 });
120
121 }
122
123 form.isDomMatch = function(simpleDomItem, formDataEntries) {
124 const simpleDomItemName = simpleDomItem.name;
125 const simpleDomItemValues = simpleDomItem.val;
126 const currentValues = formDataEntries.filter(entry => entry[0] === simpleDomItemName).map(entry => entry[1]);
127 return currentValues.join('|') === simpleDomItemValues.join('|');
128 }
129
130 /**
131 *
132 * @param {*} simpleDomItem
133 * @param {*} formDataEntries
134 * @returns the new value, or NULL if no change
135 */
136 form.getNewDomValueIfChanged = function(simpleDomItem, formDataEntries) {
137 const simpleDomItemName = simpleDomItem.name;
138 const simpleDomItemValues = simpleDomItem.val;
139 const currentValues = formDataEntries.filter(entry => entry[0] === simpleDomItemName).map(entry => entry[1]);
140 return currentValues.join('|') === simpleDomItemValues.join('|') ? null : currentValues;
141 }
142
143 // Wrapper around jQuery(selector, form.$form)
144 form.get = function (selector) {
145 // TODO: implement some caching here.
146 return jQuery(selector, form.$form);
147 }
148
149 form.getFieldByName = function(name) {
150 return form.simpleDom[name] || form.simpleDom[name+'[]'];
151 }
152
153 // compatibility with conditional forms created with older versions of the plugin ( < 1.4 )
154 for (let i=0; i < form.conditions.length; i++) {
155 const condition = form.conditions[i];
156 if (!('and_rules' in condition)) {
157 condition.and_rules = [{'if_field':condition.if_field,'if_value':condition.if_value,'operator':condition.operator}];
158 }
159 }
160
161 form.initial_conditions = form.conditions;
162 form.settings = form_options['settings'];
163
164 form.$groups = jQuery(); // empty jQuery set
165 form.repeaters = [];
166 form.multistep = null;
167 form.fields = [];
168
169 form.settings.animation_intime = parseInt(form.settings.animation_intime);
170 form.settings.animation_outtime = parseInt(form.settings.animation_outtime);
171
172 if (form.settings.animation === 'no') {
173 form.settings.animation_intime = 0;
174 form.settings.animation_outtime = 0;
175 }
176
177 form.updateGroups();
178 form.updateEventListeners();
179 form.displayFields();
180
181 // bring form in initial state if the reset event is fired on it.
182 // (CF7 triggers the 'reset' event by default on each successfully submitted form)
183 form.$form.on('reset.wpcf7cf', form, function(e) {
184 const form = e.data;
185 setTimeout(function(){
186 form.reloadSimpleDom();
187 form.displayFields();
188 form.resetRepeaters();
189 if (form.multistep != null) {
190 form.multistep.moveToStep(1, false);
191 }
192 setTimeout(function(){
193 if (form.$form.hasClass('sent')) {
194 jQuery('.wpcf7-response-output', form.$form)[0].scrollIntoView({behavior: "smooth", block:"nearest", inline:"nearest"});
195 }
196 }, 400);
197 },200);
198 });
199
200
201
202 }
203
204 /**
205 * reset initial number of subs for each repeater.
206 * (does not clear values)
207 */
208 Wpcf7cfForm.prototype.resetRepeaters = function() {
209 const form = this;
210 form.repeaters.forEach(repeater => {
211 repeater.updateSubs( repeater.params.$repeater.initial_subs );
212 });
213 }
214
215 Wpcf7cfForm.prototype.displayFields = function() {
216
217 const form = this;
218
219 const wpcf7cf_conditions = this.conditions;
220 const wpcf7cf_settings = this.settings;
221
222 //for compatibility with contact-form-7-signature-addon
223 if (cf7signature_resized === 0 && typeof signatures !== 'undefined' && signatures.constructor === Array && signatures.length > 0 ) {
224 for (let i = 0; i < signatures.length; i++) {
225 if (signatures[i].canvas.width === 0) {
226
227 const $sig_canvas = jQuery(".wpcf7-form-control-signature-body>canvas");
228 const $sig_wrap = jQuery(".wpcf7-form-control-signature-wrap");
229 $sig_canvas.eq(i).attr('width', $sig_wrap.width());
230 $sig_canvas.eq(i).attr('height', $sig_wrap.height());
231
232 cf7signature_resized = 1;
233 }
234 }
235 }
236
237 form.$groups.addClass('wpcf7cf-hidden');
238
239 for (let i=0; i < wpcf7cf_conditions.length; i++) {
240
241 const condition = wpcf7cf_conditions[i];
242
243 const show_group = window.wpcf7cf.should_group_be_shown(condition, form);
244
245 if (show_group) {
246 form.get('[data-id="'+condition.then_field+'"]').removeClass('wpcf7cf-hidden');
247 }
248 }
249
250
251 const animation_intime = wpcf7cf_settings.animation_intime;
252 const animation_outtime = wpcf7cf_settings.animation_outtime;
253
254 form.$groups.each(function (index) {
255 const $group = jQuery(this);
256 if ($group.is(':animated')) {
257 $group.finish(); // stop any current animations on the group
258 }
259 if ($group.css('display') === 'none' && !$group.hasClass('wpcf7cf-hidden')) {
260 if ($group.prop('tagName') === 'SPAN') {
261 $group.show().trigger('wpcf7cf_show_group'); // show instantly
262 } else {
263 $group.animate(wpcf7cf_show_animation, animation_intime).trigger('wpcf7cf_show_group'); // show with animation
264 }
265
266 if($group.attr('data-disable_on_hide') !== undefined) {
267 $group.find(':input').prop('disabled', false).trigger('changedisabledprop.wpcf7cf');
268 $group.find('.wpcf7-form-control-wrap').removeClass('wpcf7cf-disabled');
269 }
270
271 } else if ($group.css('display') !== 'none' && $group.hasClass('wpcf7cf-hidden')) {
272
273 if ($group.attr('data-clear_on_hide') !== undefined) {
274 const $inputs = jQuery(':input', $group).not(':button, :submit, :reset, :hidden');
275
276 $inputs.each(function(){
277 const $this = jQuery(this);
278 $this.val(this.defaultValue);
279 $this.prop('checked', this.defaultChecked);
280 });
281
282 jQuery('option', $group).each(function() {
283 this.selected = this.defaultSelected;
284 });
285
286 jQuery('select', $group).each(function() {
287 const $select = jQuery(this);
288 if ($select.val() === null) {
289 $select.val(jQuery("option:first",$select).val());
290 }
291 });
292
293 $inputs.each(function(){this.dispatchEvent(new Event("change",{"bubbles":true}))});
294 }
295
296 if ($group.prop('tagName') === 'SPAN') {
297 $group.hide().trigger('wpcf7cf_hide_group');
298 } else {
299 $group.animate(wpcf7cf_hide_animation, animation_outtime).trigger('wpcf7cf_hide_group'); // hide
300 }
301 }
302 });
303
304 form.updateHiddenFields();
305 form.updateSummaryFields();
306 };
307
308 Wpcf7cfForm.prototype.updateSummaryFields = function() {
309 const form = this;
310 const $summary = form.get('.wpcf7cf-summary');
311
312 if ($summary.length == 0 || !$summary.is(':visible')) {
313 return;
314 }
315
316 const fd = new FormData();
317
318 const formdata = form.$form.serializeArray();
319 jQuery.each(formdata,function(key, input){
320 fd.append(input.name, input.value);
321 });
322
323 // Make sure to add file fields to FormData
324 jQuery.each(form.$form.find('input[type="file"]'), function(index, el) {
325 if (! el.files.length) return true; // continue
326 const fieldName = el.name;
327 fd.append(fieldName, new Blob() , Array.from(el.files).map(file => file.name).join(', '));
328 });
329
330 // add file fields to form-data
331
332 jQuery.ajax({
333 url: wpcf7cf_global_settings.ajaxurl + '?action=wpcf7cf_get_summary',
334 type: 'POST',
335 data: fd,
336 processData: false,
337 contentType: false,
338 dataType: 'json',
339 success: function(json) {
340 $summary.html(json.summaryHtml);
341 }
342 });
343 };
344
345 Wpcf7cfForm.prototype.updateHiddenFields = function() {
346
347 const form = this;
348
349 const hidden_fields = [];
350 const hidden_groups = [];
351 const visible_groups = [];
352
353 form.$groups.each(function () {
354 const $group = jQuery(this);
355 if ($group.hasClass('wpcf7cf-hidden')) {
356 hidden_groups.push($group.attr('data-id'));
357 if($group.attr('data-disable_on_hide') !== undefined) {
358 // fields inside hidden disable_on_hide group
359 $group.find('input,select,textarea').each(function(){
360 const $this = jQuery(this);
361 if (!$this.prop('disabled')) {
362 $this.prop('disabled', true).trigger('changedisabledprop.wpcf7cf');
363 }
364
365 // if there's no other field with the same name visible in the form
366 // then push this field to hidden_fields
367 if (form.$form.find(`[data-class="wpcf7cf_group"]:not(.wpcf7cf-hidden) [name='${$this.attr('name')}']`).length === 0) {
368 hidden_fields.push($this.attr('name'));
369 }
370 })
371 $group.find('.wpcf7-form-control-wrap').addClass('wpcf7cf-disabled');
372 } else {
373 // fields inside regular hidden group are all pushed to hidden_fields
374 $group.find('input,select,textarea').each(function () {
375 hidden_fields.push(jQuery(this).attr('name'));
376 });
377 }
378 } else {
379 visible_groups.push($group.attr('data-id'));
380 }
381 });
382
383 form.hidden_fields = hidden_fields;
384 form.hidden_groups = hidden_groups;
385 form.visible_groups = visible_groups;
386
387 form.$input_hidden_group_fields.val(JSON.stringify(hidden_fields));
388 form.$input_hidden_groups.val(JSON.stringify(hidden_groups));
389 form.$input_visible_groups.val(JSON.stringify(visible_groups));
390
391 return true;
392 };
393 Wpcf7cfForm.prototype.updateGroups = function() {
394 const form = this;
395 form.$groups = form.$form.find('[data-class="wpcf7cf_group"]');
396 form.$groups.height('auto');
397 form.conditions = window.wpcf7cf.get_nested_conditions(form);
398
399 };
400 Wpcf7cfForm.prototype.updateEventListeners = function() {
401
402 const form = this;
403
404 // monitor input changes, and call displayFields() if something has changed
405 form.get('input, select, textarea, button').not('.wpcf7cf_add, .wpcf7cf_remove').off(wpcf7cf_change_events).on(wpcf7cf_change_events,form, function(e) {
406 const form = e.data;
407 clearTimeout(wpcf7cf_timeout);
408 wpcf7cf_timeout = setTimeout(function() {
409 window.wpcf7cf.updateMultistepState(form.multistep);
410 form.updateSimpleDom();
411 form.displayFields();
412 }, wpcf7cf_change_time_ms);
413 });
414
415
416 };
417
418
419
420 /**
421 * @global
422 * @namespace wpcf7cf
423 */
424 window.wpcf7cf = {
425
426 hideGroup : function($group, animate) {
427
428 },
429
430 showGroup : function($group, animate) {
431
432 },
433
434 updateRepeaterSubHTML : function(html, oldSuffix, newSuffix, parentRepeaters) {
435 const oldIndexes = oldSuffix.split('__');
436 oldIndexes.shift(); // remove first empty element
437 const newIndexes = newSuffix.split('__');
438 newIndexes.shift(); // remove first empty element
439
440 let returnHtml = html;
441
442 if (
443 oldIndexes && newIndexes &&
444 oldIndexes.length === parentRepeaters.length &&
445 newIndexes.length === parentRepeaters.length
446 ) {
447
448 const parentRepeatersInfo = parentRepeaters.map((repeaterId, i) => {
449 return {[repeaterId.split('__')[0]]: [oldIndexes[i], newIndexes[i]]};
450 });
451
452 const length = parentRepeatersInfo.length;
453
454 let replacements = oldIndexes.map( (oldIndex, i) => {
455 return [
456 '__'+oldIndexes.slice(0,length-i).join('__'),
457 '__'+newIndexes.slice(0,length-i).join('__'),
458 ];
459 });
460
461
462 for (let i=0; i<length ; i++) {
463 const id = Object.keys(parentRepeatersInfo[i])[0];
464 const find = parentRepeatersInfo[i][id][0];
465 const repl = parentRepeatersInfo[i][id][1];
466 replacements.push([
467 `<span class="wpcf7cf-index wpcf7cf__${id}">${find}<\\/span>`,
468 `<span class="wpcf7cf-index wpcf7cf__${id}">${repl}</span>`
469 ]);
470 }
471
472 replacements.forEach( ([oldSuffix, newSuffix]) => {
473 returnHtml = returnHtml.replace(new RegExp(oldSuffix,'g'), newSuffix);
474 });
475
476 }
477
478 return returnHtml ;
479 },
480
481 // keep this for backwards compatibility
482 initForm : function($forms) {
483 $forms.each(function(){
484 const $form = jQuery(this);
485 // only add form is its class is "wpcf7-form" and if the form was not previously added
486 if (
487 $form.hasClass('wpcf7-form') &&
488 !wpcf7cf_forms.some((form)=>{ return form.$form.get(0) === $form.get(0); })
489 ) {
490 wpcf7cf_forms.push(new Wpcf7cfForm($form));
491 }
492 });
493 },
494
495 getWpcf7cfForm : function ($form) {
496 const matched_forms = wpcf7cf_forms.filter((form)=>{
497 return form.$form.get(0) === $form.get(0);
498 });
499 if (matched_forms.length) {
500 return matched_forms[0];
501 }
502 return false;
503 },
504
505 get_nested_conditions : function(form) {
506 const conditions = form.initial_conditions;
507 //loop trough conditions. Then loop trough the dom, and each repeater we pass we should update all sub_values we encounter with __index
508 form.reloadSimpleDom();
509 const groups = Object.values(form.simpleDom).filter(function(item, i) {
510 return item.type==='group';
511 });
512
513 let sub_conditions = [];
514
515 for(let i = 0; i < groups.length; i++) {
516 const g = groups[i];
517 let relevant_conditions = conditions.filter(function(condition, i) {
518 return condition.then_field === g.original_name;
519 });
520
521 relevant_conditions = relevant_conditions.map(function(item,i) {
522 return {
523 then_field : g.name,
524 and_rules : item.and_rules.map(function(and_rule, i) {
525 return {
526 if_field : and_rule.if_field+g.suffix,
527 if_value : and_rule.if_value,
528 operator : and_rule.operator
529 };
530 })
531 }
532 });
533
534 sub_conditions = sub_conditions.concat(relevant_conditions);
535 }
536 return sub_conditions;
537 },
538
539 get_simplified_dom_model : function(currentNode, simplified_dom = {}, parentGroups = [], parentRepeaters = []) {
540
541 const type = currentNode.classList && currentNode.classList.contains('wpcf7cf_repeater') ? 'repeater' :
542 currentNode.dataset.class == 'wpcf7cf_group' ? 'group' :
543 currentNode.className == 'wpcf7cf_step' ? 'step' :
544 currentNode.hasAttribute('name') ? 'input' : false;
545
546 let newParentRepeaters = [...parentRepeaters];
547 let newParentGroups = [...parentGroups];
548
549 if (type) {
550
551 const name = type === 'input' ? currentNode.getAttribute('name') : currentNode.dataset.id;
552
553 if (type === 'repeater') {
554 newParentRepeaters.push(name);
555 }
556 if (type === 'group') {
557 newParentGroups.push(name);
558 }
559
560 // skip _wpcf7 hidden fields
561 if (name.substring(0,6) === '_wpcf7') return {};
562
563 const original_name = type === 'repeater' || type === 'group' ? currentNode.dataset.orig_data_id
564 : type === 'input' ? (currentNode.getAttribute('data-orig_name') || name)
565 : name;
566
567 const nameWithoutBrackets = name.replace('[]','');
568 const originalNameWithoutBrackets = original_name.replace('[]','');
569
570 const val = type === 'step' ? [currentNode.dataset.id.substring(5)] : [];
571
572 const suffix = nameWithoutBrackets.replace(originalNameWithoutBrackets, '');
573
574 if (!simplified_dom[name]) {
575 // init entry
576 simplified_dom[name] = {name, type, original_name, suffix, val, parentGroups, parentRepeaters}
577 }
578
579 if (type === 'input') {
580
581 // skip unchecked checkboxes and radiobuttons
582 if ( (currentNode.type === 'checkbox' || currentNode.type === 'radio') && !currentNode.checked ) return {};
583
584 // if multiselect, make sure to add all the values
585 if ( currentNode.multiple && currentNode.options ) {
586 simplified_dom[name].val = Object.values(currentNode.options).filter(o => o.selected).map(o => o.value)
587 } else {
588 simplified_dom[name].val.push(currentNode.value);
589 }
590 }
591 }
592
593 // can't use currentNode.children (because then field name cannot be "children")
594 const getter = Object.getOwnPropertyDescriptor(Element.prototype, "children").get;
595 const children = getter.call(currentNode);
596
597 Array.from(children).forEach(childNode => {
598 const dom = wpcf7cf.get_simplified_dom_model(childNode, simplified_dom, newParentGroups, newParentRepeaters);
599 simplified_dom = {...dom, ...simplified_dom} ;
600 });
601
602 return simplified_dom;
603 },
604
605 updateMultistepState: function (multistep) {
606 if (multistep == null) return;
607 if (multistep.form.$form.hasClass('submitting')) return;
608
609 // update hidden input field
610
611 const stepsData = {
612 currentStep : multistep.currentStep,
613 numSteps : multistep.numSteps,
614 fieldsInCurrentStep : multistep.getFieldsInStep(multistep.currentStep)
615 };
616 multistep.form.$input_steps.val(JSON.stringify(stepsData));
617
618 // update Buttons
619 multistep.$btn_prev.removeClass('disabled').attr('disabled', false);
620 multistep.$btn_next.removeClass('disabled').attr('disabled', false);
621 if (multistep.currentStep == multistep.numSteps) {
622 multistep.$btn_next.addClass('disabled').attr('disabled', true);
623 }
624 if (multistep.currentStep == 1) {
625 multistep.$btn_prev.addClass('disabled').attr('disabled', true);
626 }
627
628 // replace next button with submit button on last step.
629 // TODO: make this depend on a setting
630 const $submit_button = multistep.form.$form.find('input[type="submit"]:last').eq(0);
631 const $ajax_loader = multistep.form.$form.find('.wpcf7-spinner').eq(0);
632
633 $submit_button.detach().prependTo(multistep.$btn_next.parent());
634 $ajax_loader.detach().prependTo(multistep.$btn_next.parent());
635
636 if (multistep.currentStep == multistep.numSteps) {
637 multistep.$btn_next.hide();
638 $submit_button.show();
639 } else {
640 $submit_button.hide();
641 multistep.$btn_next.show();
642 }
643
644 // update dots
645 const $dots = multistep.$dots.find('.dot');
646 $dots.removeClass('active').removeClass('completed');
647 for(let step = 1; step <= multistep.numSteps; step++) {
648 if (step < multistep.currentStep) {
649 $dots.eq(step-1).addClass('completed');
650 } else if (step == multistep.currentStep) {
651 $dots.eq(step-1).addClass('active');
652 }
653 }
654
655 },
656
657 should_group_be_shown : function(condition, form) {
658
659 let show_group = true;
660 let atLeastOneFieldFound = false;
661
662 for (let and_rule_i = 0; and_rule_i < condition.and_rules.length; and_rule_i++) {
663
664 let condition_ok = false;
665
666 const condition_and_rule = condition.and_rules[and_rule_i];
667
668 const inputField = form.getFieldByName(condition_and_rule.if_field);
669
670 if (!inputField) continue; // field not found
671
672 atLeastOneFieldFound = true;
673
674 const if_val = condition_and_rule.if_value;
675 let operator = condition_and_rule.operator;
676
677 //backwards compat
678 operator = operator === '' ? 'less than or equals' : operator;
679 operator = operator === '' ? 'greater than or equals' : operator;
680 operator = operator === '>' ? 'greater than' : operator;
681 operator = operator === '<' ? 'less than' : operator;
682
683 const $field = operator === 'function' && jQuery(`[name="${inputField.name}"]`).eq(0);
684
685 condition_ok = this.isConditionTrue(inputField.val,operator,if_val, $field);
686
687 show_group = show_group && condition_ok;
688 }
689
690 return show_group && atLeastOneFieldFound;
691
692 },
693
694 isConditionTrue(values, operator, testValue='', $field=jQuery()) {
695
696 if (!Array.isArray(values)) {
697 values = [values];
698 }
699
700 let condition_ok = false; // start by assuming that the condition is not met
701
702 // Considered EMPTY: [] [''] [null] ['',null] [,,'']
703 // Considered NOT EMPTY: [0] ['ab','c'] ['',0,null]
704 const valuesAreEmpty = values.length === 0 || values.every((v) => !v&&v!==0); // 0 is not considered empty
705
706 // special cases: [] equals '' => TRUE; [] not equals '' => FALSE
707 if (operator === 'equals' && testValue === '' && valuesAreEmpty) {
708 return true;
709 }
710 if (operator === 'not equals' && testValue === '' && valuesAreEmpty) {
711 return false;
712 }
713
714 if (valuesAreEmpty) {
715 if (operator === 'is empty') {
716 condition_ok = true;
717 }
718 } else {
719 if (operator === 'not empty') {
720 condition_ok = true;
721 }
722 }
723
724 const testValueNumber = isFinite(parseFloat(testValue)) ? parseFloat(testValue) : NaN;
725
726
727 if (operator === 'not equals' || operator === 'not equals (regex)') {
728 // start by assuming that the condition is met
729 condition_ok = true;
730 }
731
732 if (
733 operator === 'function'
734 && typeof window[testValue] == 'function'
735 && window[testValue]($field) // here we call the actual user defined function
736 ) {
737 condition_ok = true;
738 }
739
740 let regex_patt = /.*/i; // fallback regex pattern
741 let isValidRegex = true;
742 if (operator === 'equals (regex)' || operator === 'not equals (regex)') {
743 try {
744 regex_patt = new RegExp(testValue, 'i');
745 } catch(e) {
746 isValidRegex = false;
747 }
748 }
749
750
751 for(let i = 0; i < values.length; i++) {
752
753 const value = values[i];
754
755 const valueNumber = isFinite(parseFloat(value)) ? parseFloat(value) : NaN;
756 const valsAreNumbers = !isNaN(valueNumber) && !isNaN(testValueNumber);
757
758 if (
759
760 operator === 'equals' && value === testValue ||
761 operator === 'equals (regex)' && regex_patt.test(value) ||
762 operator === 'greater than' && valsAreNumbers && valueNumber > testValueNumber ||
763 operator === 'less than' && valsAreNumbers && valueNumber < testValueNumber ||
764 operator === 'greater than or equals' && valsAreNumbers && valueNumber >= testValueNumber ||
765 operator === 'less than or equals' && valsAreNumbers && valueNumber <= testValueNumber
766
767 ) {
768
769 condition_ok = true;
770 break;
771
772 } else if (
773
774 operator === 'not equals' && value === testValue ||
775 operator === 'not equals (regex)' && regex_patt.test(value)
776
777 ) {
778
779 condition_ok = false;
780 break;
781
782 }
783 }
784
785 return condition_ok;
786
787 },
788
789 getFormObj($form) {
790 if (typeof $form === 'string') {
791 $form = jQuery($form).eq(0);
792 }
793 return wpcf7cf.getWpcf7cfForm($form);
794 },
795
796 getRepeaterObj($form, repeaterDataId) {
797 const form = wpcf7cf.getFormObj($form);
798 const repeater = form.repeaters.find( repeater => repeater.params.$repeater.attr('data-id') === repeaterDataId );
799
800 return repeater;
801
802 },
803
804 getMultiStepObj($form){
805 const form = wpcf7cf.getFormObj($form);
806 return form.multistep;
807 },
808
809 /**
810 * Append a new sub-entry to the repeater with the name `repeaterDataId` inside the form `$form`
811 * @memberof wpcf7cf
812 * @function wpcf7cf.repeaterAddSub
813 * @link
814 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
815 * @param {String} repeaterDataId - *data-id* attribute of the repeater. Normally this is simply the name of the repeater. However, in case of a nested repeater you need to append the name with the correct suffix. For example `my-nested-repeater__1__3`. Hint (check the `data-id` attribute in the HTML code to find the correct suffix)
816 */
817 repeaterAddSub($form,repeaterDataId) {
818 const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
819 repeater.updateSubs(repeater.params.$repeater.num_subs+1);
820 },
821
822 /**
823 * Insert a new sub-entry at the given `index` of the repeater with the name `repeaterDataId` inside the form `$form`
824 * @memberof wpcf7cf
825 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
826 * @param {String} repeaterDataId - *data-id* attribute of the repeater.
827 * @param {Number} index - position where to insert the new sub-entry within the repeater
828 */
829 repeaterAddSubAtIndex($form,repeaterDataId,index) {
830 const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
831 repeater.addSubs(1, index);
832 },
833
834 /**
835 * Remove the sub-entry at the given `index` of the repeater with the *data-id* attribute of `repeaterDataId` inside the form `$form`
836 * @memberof wpcf7cf
837 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
838 * @param {String} repeaterDataId - *data-id* attribute of the repeater.
839 * @param {Number} index - position where to insert the new sub-entry within the repeater
840 */
841 repeaterRemoveSubAtIndex($form,repeaterDataId,index) {
842 const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
843 repeater.removeSubs(1, index);
844 },
845
846 /**
847 * Remove the last sub-entry from the repeater with the *data-id* attribute of `repeaterDataId` inside the form `$form`
848 * @memberof wpcf7cf
849 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
850 * @param {String} repeaterDataId - *data-id* attribute of the repeater.
851 * @param {Number} index - position where to insert the new sub-entry within the repeater
852 */
853 repeaterRemoveSub($form,repeaterDataId) {
854 const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
855 repeater.updateSubs(repeater.params.$repeater.num_subs-1);
856 },
857
858 /**
859 * Set the number of subs for the repeater with the *data-id* attribute of `repeaterDataId` inside the form `$form`.
860 * Subs are either appended to or removed from the end of the repeater.
861 * @memberof wpcf7cf
862 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
863 * @param {String} repeaterDataId - *data-id* attribute of the repeater.
864 * @param {Number} numberOfSubs - position where to insert the new sub-entry within the repeater
865 */
866 repeaterSetNumberOfSubs($form, repeaterDataId, numberOfSubs) {
867 const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
868 repeater.updateSubs(numberOfSubs);
869 },
870
871 /**
872 * Move to step number `step`, ignoring any validation.
873 * @memberof wpcf7cf
874 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
875 * @param {*} step
876 */
877 multistepMoveToStep($form, step) {
878 const multistep = wpcf7cf.getMultiStepObj($form);
879 multistep.moveToStep(step);
880 },
881
882 /**
883 * Validate the current step, and move to step number `step` if validation passes.
884 * @memberof wpcf7cf
885 * @param {String|JQuery} $form - JQuery object or css-selector representing the form
886 * @param {Number} step
887 */
888 async multistepMoveToStepWithValidation($form, step) {
889 const multistep = wpcf7cf.getMultiStepObj($form);
890
891 const result = await multistep.validateStep(multistep.currentStep);
892 if (result === 'success') {
893 multistep.moveToStep(step);
894 }
895 },
896
897
898 };
899
900 jQuery('.wpcf7-form').each(function(){
901 wpcf7cf_forms.push(new Wpcf7cfForm(jQuery(this)));
902 });
903
904 // Call displayFields again on all forms
905 // Necessary in case some theme or plugin changed a form value by the time the entire page is fully loaded.
906 jQuery('document').ready( function() {
907 wpcf7cf_forms.forEach(function(f){
908 f.displayFields();
909 });
910 });
911
912 // fix for exclusive checkboxes in IE (this will call the change-event again after all other checkboxes are unchecked, triggering the display_fields() function)
913 const old_wpcf7ExclusiveCheckbox = jQuery.fn.wpcf7ExclusiveCheckbox;
914 jQuery.fn.wpcf7ExclusiveCheckbox = function() {
915 return this.find('input:checkbox').on('click', function() {
916 const name = jQuery(this).attr('name');
917 jQuery(this).closest('form').find('input:checkbox[name="' + name + '"]').not(this).prop('checked', false).eq(0).change();
918 });
919 };