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