Source: scripts.js

  1. "use strict";
  2. // disable client side validation introduced in CF7 5.6 for now
  3. if (typeof wpcf7 !== 'undefined') {
  4. wpcf7.validate = (a,b) => null;
  5. }
  6. let cf7signature_resized = 0; // for compatibility with contact-form-7-signature-addon
  7. let wpcf7cf_timeout;
  8. let wpcf7cf_change_time_ms = 100; // the timeout after a change in the form is detected
  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. if ( defaultStatuses.has( status ) ) {
  25. status = defaultStatuses.get( status );
  26. }
  27. if ( ! Array.from( defaultStatuses.values() ).includes( status ) ) {
  28. status = status.replace( /[^0-9a-z]+/i, ' ' ).trim();
  29. status = status.replace( /\s+/, '-' );
  30. status = `custom-${ status }`;
  31. }
  32. const prevStatus = form.getAttribute( 'data-status' );
  33. form.wpcf7.status = status;
  34. form.setAttribute( 'data-status', status );
  35. form.classList.add( status );
  36. if ( prevStatus && prevStatus !== status ) {
  37. form.classList.remove( prevStatus );
  38. }
  39. return status;
  40. };
  41. }
  42. if (window.wpcf7cf_running_tests) {
  43. jQuery('input[name="_wpcf7cf_options"]').each(function(e) {
  44. const $input = jQuery(this);
  45. const opt = JSON.parse($input.val());
  46. opt.settings.animation_intime = 0;
  47. opt.settings.animation_outtime = 0;
  48. $input.val(JSON.stringify(opt));
  49. });
  50. wpcf7cf_change_time_ms = 0;
  51. }
  52. const wpcf7cf_show_animation = { "height": "show", "marginTop": "show", "marginBottom": "show", "paddingTop": "show", "paddingBottom": "show" };
  53. const wpcf7cf_hide_animation = { "height": "hide", "marginTop": "hide", "marginBottom": "hide", "paddingTop": "hide", "paddingBottom": "hide" };
  54. const wpcf7cf_show_step_animation = { "opacity": "show" };
  55. const wpcf7cf_hide_step_animation = { "opacity": "hide" };
  56. const wpcf7cf_change_events = 'input.wpcf7cf paste.wpcf7cf change.wpcf7cf click.wpcf7cf propertychange.wpcf7cf changedisabledprop.wpcf7cf';
  57. const wpcf7cf_forms = [];
  58. const Wpcf7cfForm = function($form) {
  59. const options_element = $form.find('input[name="_wpcf7cf_options"]').eq(0);
  60. if (!options_element.length || !options_element.val()) {
  61. // doesn't look like a CF7 form created with conditional fields plugin enabled.
  62. return false;
  63. }
  64. const form = this;
  65. const form_options = JSON.parse(options_element.val());
  66. form.$form = $form;
  67. form.$input_hidden_group_fields = $form.find('[name="_wpcf7cf_hidden_group_fields"]');
  68. form.$input_hidden_groups = $form.find('[name="_wpcf7cf_hidden_groups"]');
  69. form.$input_visible_groups = $form.find('[name="_wpcf7cf_visible_groups"]');
  70. form.$input_repeaters = $form.find('[name="_wpcf7cf_repeaters"]');
  71. form.$input_steps = $form.find('[name="_wpcf7cf_steps"]');
  72. form.unit_tag = $form.closest('.wpcf7').attr('id');
  73. form.conditions = form_options['conditions'];
  74. form.simpleDom = null;
  75. form.reloadSimpleDom = function() {
  76. form.simpleDom = wpcf7cf.get_simplified_dom_model(form.$form[0]);
  77. }
  78. // quicker than reloading the simpleDom completely with reloadSimpleDom
  79. form.updateSimpleDom = function() {
  80. if (!form.simpleDom) {
  81. form.reloadSimpleDom();
  82. }
  83. const inputs = Object.values(form.simpleDom).filter(item => item.type === 'input');
  84. const formdata = new FormData(form.$form[0]);
  85. let formdataEntries = [... formdata.entries()].map(entry => [ entry[0], entry[1].name ?? entry[1] ]);
  86. const buttonEntries = [ ... jQuery('button', form.$form) ].map(entry => [entry.name, entry.value]);
  87. formdataEntries = formdataEntries.concat(buttonEntries);
  88. inputs.forEach(simpleDomItem => {
  89. const newValue = form.getNewDomValueIfChanged(simpleDomItem, formdataEntries);
  90. if (newValue !== null) {
  91. form.simpleDom[simpleDomItem.name].val = newValue;
  92. }
  93. });
  94. }
  95. form.isDomMatch = function(simpleDomItem, formDataEntries) {
  96. const simpleDomItemName = simpleDomItem.name;
  97. const simpleDomItemValues = simpleDomItem.val;
  98. const currentValues = formDataEntries.filter(entry => entry[0] === simpleDomItemName).map(entry => entry[1]);
  99. return currentValues.join('|') === simpleDomItemValues.join('|');
  100. }
  101. /**
  102. *
  103. * @param {*} simpleDomItem
  104. * @param {*} formDataEntries
  105. * @returns the new value, or NULL if no change
  106. */
  107. form.getNewDomValueIfChanged = function(simpleDomItem, formDataEntries) {
  108. const simpleDomItemName = simpleDomItem.name;
  109. const simpleDomItemValues = simpleDomItem.val;
  110. const currentValues = formDataEntries.filter(entry => entry[0] === simpleDomItemName).map(entry => entry[1]);
  111. return currentValues.join('|') === simpleDomItemValues.join('|') ? null : currentValues;
  112. }
  113. // Wrapper around jQuery(selector, form.$form)
  114. form.get = function (selector) {
  115. // TODO: implement some caching here.
  116. return jQuery(selector, form.$form);
  117. }
  118. form.getFieldByName = function(name) {
  119. return form.simpleDom[name] || form.simpleDom[name+'[]'];
  120. }
  121. // compatibility with conditional forms created with older versions of the plugin ( < 1.4 )
  122. for (let i=0; i < form.conditions.length; i++) {
  123. const condition = form.conditions[i];
  124. if (!('and_rules' in condition)) {
  125. condition.and_rules = [{'if_field':condition.if_field,'if_value':condition.if_value,'operator':condition.operator}];
  126. }
  127. }
  128. form.initial_conditions = form.conditions;
  129. form.settings = form_options['settings'];
  130. form.$groups = jQuery(); // empty jQuery set
  131. form.repeaters = [];
  132. form.multistep = null;
  133. form.fields = [];
  134. form.settings.animation_intime = parseInt(form.settings.animation_intime);
  135. form.settings.animation_outtime = parseInt(form.settings.animation_outtime);
  136. if (form.settings.animation === 'no') {
  137. form.settings.animation_intime = 0;
  138. form.settings.animation_outtime = 0;
  139. }
  140. form.updateGroups();
  141. form.updateEventListeners();
  142. form.displayFields();
  143. // bring form in initial state if the reset event is fired on it.
  144. // (CF7 triggers the 'reset' event by default on each successfully submitted form)
  145. form.$form.on('reset.wpcf7cf', form, function(e) {
  146. const form = e.data;
  147. setTimeout(function(){
  148. form.reloadSimpleDom();
  149. form.displayFields();
  150. form.resetRepeaters();
  151. if (form.multistep != null) {
  152. form.multistep.moveToStep(1, false);
  153. }
  154. setTimeout(function(){
  155. if (form.$form.hasClass('sent')) {
  156. jQuery('.wpcf7-response-output', form.$form)[0].scrollIntoView({behavior: "smooth", block:"nearest", inline:"nearest"});
  157. }
  158. }, 400);
  159. },200);
  160. });
  161. }
  162. /**
  163. * reset initial number of subs for each repeater.
  164. * (does not clear values)
  165. */
  166. Wpcf7cfForm.prototype.resetRepeaters = function() {
  167. const form = this;
  168. form.repeaters.forEach(repeater => {
  169. repeater.updateSubs( repeater.params.$repeater.initial_subs );
  170. });
  171. }
  172. Wpcf7cfForm.prototype.displayFields = function() {
  173. const form = this;
  174. const wpcf7cf_conditions = this.conditions;
  175. const wpcf7cf_settings = this.settings;
  176. //for compatibility with contact-form-7-signature-addon
  177. if (cf7signature_resized === 0 && typeof signatures !== 'undefined' && signatures.constructor === Array && signatures.length > 0 ) {
  178. for (let i = 0; i < signatures.length; i++) {
  179. if (signatures[i].canvas.width === 0) {
  180. const $sig_canvas = jQuery(".wpcf7-form-control-signature-body>canvas");
  181. const $sig_wrap = jQuery(".wpcf7-form-control-signature-wrap");
  182. $sig_canvas.eq(i).attr('width', $sig_wrap.width());
  183. $sig_canvas.eq(i).attr('height', $sig_wrap.height());
  184. cf7signature_resized = 1;
  185. }
  186. }
  187. }
  188. form.$groups.addClass('wpcf7cf-hidden');
  189. for (let i=0; i < wpcf7cf_conditions.length; i++) {
  190. const condition = wpcf7cf_conditions[i];
  191. const show_group = window.wpcf7cf.should_group_be_shown(condition, form);
  192. if (show_group) {
  193. form.get('[data-id="'+condition.then_field+'"]').removeClass('wpcf7cf-hidden');
  194. }
  195. }
  196. const animation_intime = wpcf7cf_settings.animation_intime;
  197. const animation_outtime = wpcf7cf_settings.animation_outtime;
  198. form.$groups.each(function (index) {
  199. const $group = jQuery(this);
  200. if ($group.is(':animated')) {
  201. $group.finish(); // stop any current animations on the group
  202. }
  203. if ($group.css('display') === 'none' && !$group.hasClass('wpcf7cf-hidden')) {
  204. if ($group.prop('tagName') === 'SPAN') {
  205. $group.show().trigger('wpcf7cf_show_group'); // show instantly
  206. } else {
  207. $group.animate(wpcf7cf_show_animation, animation_intime).trigger('wpcf7cf_show_group'); // show with animation
  208. }
  209. if($group.attr('data-disable_on_hide') !== undefined) {
  210. $group.find(':input').prop('disabled', false).trigger('changedisabledprop.wpcf7cf');
  211. $group.find('.wpcf7-form-control-wrap').removeClass('wpcf7cf-disabled');
  212. }
  213. } else if ($group.css('display') !== 'none' && $group.hasClass('wpcf7cf-hidden')) {
  214. if ($group.attr('data-clear_on_hide') !== undefined) {
  215. const $inputs = jQuery(':input', $group).not(':button, :submit, :reset, :hidden');
  216. $inputs.each(function(){
  217. const $this = jQuery(this);
  218. $this.val(this.defaultValue);
  219. $this.prop('checked', this.defaultChecked);
  220. });
  221. jQuery('option', $group).each(function() {
  222. this.selected = this.defaultSelected;
  223. });
  224. jQuery('select', $group).each(function() {
  225. const $select = jQuery(this);
  226. if ($select.val() === null) {
  227. $select.val(jQuery("option:first",$select).val());
  228. }
  229. });
  230. $inputs.each(function(){this.dispatchEvent(new Event("change",{"bubbles":true}))});
  231. }
  232. if ($group.prop('tagName') === 'SPAN') {
  233. $group.hide().trigger('wpcf7cf_hide_group');
  234. } else {
  235. $group.animate(wpcf7cf_hide_animation, animation_outtime).trigger('wpcf7cf_hide_group'); // hide
  236. }
  237. }
  238. });
  239. form.updateHiddenFields();
  240. form.updateSummaryFields();
  241. };
  242. Wpcf7cfForm.prototype.updateSummaryFields = function() {
  243. const form = this;
  244. const $summary = form.get('.wpcf7cf-summary');
  245. if ($summary.length == 0 || !$summary.is(':visible')) {
  246. return;
  247. }
  248. const fd = new FormData();
  249. const formdata = form.$form.serializeArray();
  250. jQuery.each(formdata,function(key, input){
  251. fd.append(input.name, input.value);
  252. });
  253. // Make sure to add file fields to FormData
  254. jQuery.each(form.$form.find('input[type="file"]'), function(index, el) {
  255. if (! el.files.length) return true; // continue
  256. const fieldName = el.name;
  257. fd.append(fieldName, new Blob() , Array.from(el.files).map(file => file.name).join(', '));
  258. });
  259. // add file fields to form-data
  260. jQuery.ajax({
  261. url: wpcf7cf_global_settings.ajaxurl + '?action=wpcf7cf_get_summary',
  262. type: 'POST',
  263. data: fd,
  264. processData: false,
  265. contentType: false,
  266. dataType: 'json',
  267. success: function(json) {
  268. $summary.html(json.summaryHtml);
  269. }
  270. });
  271. };
  272. Wpcf7cfForm.prototype.updateHiddenFields = function() {
  273. const form = this;
  274. const hidden_fields = [];
  275. const hidden_groups = [];
  276. const visible_groups = [];
  277. form.$groups.each(function () {
  278. const $group = jQuery(this);
  279. if ($group.hasClass('wpcf7cf-hidden')) {
  280. hidden_groups.push($group.attr('data-id'));
  281. if($group.attr('data-disable_on_hide') !== undefined) {
  282. // fields inside hidden disable_on_hide group
  283. $group.find('input,select,textarea').each(function(){
  284. const $this = jQuery(this);
  285. if (!$this.prop('disabled')) {
  286. $this.prop('disabled', true).trigger('changedisabledprop.wpcf7cf');
  287. }
  288. // if there's no other field with the same name visible in the form
  289. // then push this field to hidden_fields
  290. if (form.$form.find(`[data-class="wpcf7cf_group"]:not(.wpcf7cf-hidden) [name='${$this.attr('name')}']`).length === 0) {
  291. hidden_fields.push($this.attr('name'));
  292. }
  293. })
  294. $group.find('.wpcf7-form-control-wrap').addClass('wpcf7cf-disabled');
  295. } else {
  296. // fields inside regular hidden group are all pushed to hidden_fields
  297. $group.find('input,select,textarea').each(function () {
  298. hidden_fields.push(jQuery(this).attr('name'));
  299. });
  300. }
  301. } else {
  302. visible_groups.push($group.attr('data-id'));
  303. }
  304. });
  305. form.hidden_fields = hidden_fields;
  306. form.hidden_groups = hidden_groups;
  307. form.visible_groups = visible_groups;
  308. form.$input_hidden_group_fields.val(JSON.stringify(hidden_fields));
  309. form.$input_hidden_groups.val(JSON.stringify(hidden_groups));
  310. form.$input_visible_groups.val(JSON.stringify(visible_groups));
  311. return true;
  312. };
  313. Wpcf7cfForm.prototype.updateGroups = function() {
  314. const form = this;
  315. form.$groups = form.$form.find('[data-class="wpcf7cf_group"]');
  316. form.$groups.height('auto');
  317. form.conditions = window.wpcf7cf.get_nested_conditions(form);
  318. };
  319. Wpcf7cfForm.prototype.updateEventListeners = function() {
  320. const form = this;
  321. // monitor input changes, and call displayFields() if something has changed
  322. form.get('input, select, textarea, button').not('.wpcf7cf_add, .wpcf7cf_remove').off(wpcf7cf_change_events).on(wpcf7cf_change_events,form, function(e) {
  323. const form = e.data;
  324. clearTimeout(wpcf7cf_timeout);
  325. wpcf7cf_timeout = setTimeout(function() {
  326. window.wpcf7cf.updateMultistepState(form.multistep);
  327. form.updateSimpleDom();
  328. form.displayFields();
  329. }, wpcf7cf_change_time_ms);
  330. });
  331. };
  332. /**
  333. * @global
  334. * @namespace wpcf7cf
  335. */
  336. window.wpcf7cf = {
  337. hideGroup : function($group, animate) {
  338. },
  339. showGroup : function($group, animate) {
  340. },
  341. updateRepeaterSubHTML : function(html, oldSuffix, newSuffix, parentRepeaters) {
  342. const oldIndexes = oldSuffix.split('__');
  343. oldIndexes.shift(); // remove first empty element
  344. const newIndexes = newSuffix.split('__');
  345. newIndexes.shift(); // remove first empty element
  346. let returnHtml = html;
  347. if (
  348. oldIndexes && newIndexes &&
  349. oldIndexes.length === parentRepeaters.length &&
  350. newIndexes.length === parentRepeaters.length
  351. ) {
  352. const parentRepeatersInfo = parentRepeaters.map((repeaterId, i) => {
  353. return {[repeaterId.split('__')[0]]: [oldIndexes[i], newIndexes[i]]};
  354. });
  355. const length = parentRepeatersInfo.length;
  356. let replacements = oldIndexes.map( (oldIndex, i) => {
  357. return [
  358. '__'+oldIndexes.slice(0,length-i).join('__'),
  359. '__'+newIndexes.slice(0,length-i).join('__'),
  360. ];
  361. });
  362. for (let i=0; i<length ; i++) {
  363. const id = Object.keys(parentRepeatersInfo[i])[0];
  364. const find = parentRepeatersInfo[i][id][0];
  365. const repl = parentRepeatersInfo[i][id][1];
  366. replacements.push([
  367. `<span class="wpcf7cf-index wpcf7cf__${id}">${find}<\\/span>`,
  368. `<span class="wpcf7cf-index wpcf7cf__${id}">${repl}</span>`
  369. ]);
  370. }
  371. replacements.forEach( ([oldSuffix, newSuffix]) => {
  372. returnHtml = returnHtml.replace(new RegExp(oldSuffix,'g'), newSuffix);
  373. });
  374. }
  375. return returnHtml ;
  376. },
  377. // keep this for backwards compatibility
  378. initForm : function($forms) {
  379. $forms.each(function(){
  380. const $form = jQuery(this);
  381. // only add form is its class is "wpcf7-form" and if the form was not previously added
  382. if (
  383. $form.hasClass('wpcf7-form') &&
  384. !wpcf7cf_forms.some((form)=>{ return form.$form.get(0) === $form.get(0); })
  385. ) {
  386. wpcf7cf_forms.push(new Wpcf7cfForm($form));
  387. }
  388. });
  389. },
  390. getWpcf7cfForm : function ($form) {
  391. const matched_forms = wpcf7cf_forms.filter((form)=>{
  392. return form.$form.get(0) === $form.get(0);
  393. });
  394. if (matched_forms.length) {
  395. return matched_forms[0];
  396. }
  397. return false;
  398. },
  399. get_nested_conditions : function(form) {
  400. const conditions = form.initial_conditions;
  401. //loop trough conditions. Then loop trough the dom, and each repeater we pass we should update all sub_values we encounter with __index
  402. form.reloadSimpleDom();
  403. const groups = Object.values(form.simpleDom).filter(function(item, i) {
  404. return item.type==='group';
  405. });
  406. let sub_conditions = [];
  407. for(let i = 0; i < groups.length; i++) {
  408. const g = groups[i];
  409. let relevant_conditions = conditions.filter(function(condition, i) {
  410. return condition.then_field === g.original_name;
  411. });
  412. relevant_conditions = relevant_conditions.map(function(item,i) {
  413. return {
  414. then_field : g.name,
  415. and_rules : item.and_rules.map(function(and_rule, i) {
  416. return {
  417. if_field : and_rule.if_field+g.suffix,
  418. if_value : and_rule.if_value,
  419. operator : and_rule.operator
  420. };
  421. })
  422. }
  423. });
  424. sub_conditions = sub_conditions.concat(relevant_conditions);
  425. }
  426. return sub_conditions;
  427. },
  428. get_simplified_dom_model : function(currentNode, simplified_dom = {}, parentGroups = [], parentRepeaters = []) {
  429. const type = currentNode.classList && currentNode.classList.contains('wpcf7cf_repeater') ? 'repeater' :
  430. currentNode.dataset.class == 'wpcf7cf_group' ? 'group' :
  431. currentNode.className == 'wpcf7cf_step' ? 'step' :
  432. currentNode.hasAttribute('name') ? 'input' : false;
  433. let newParentRepeaters = [...parentRepeaters];
  434. let newParentGroups = [...parentGroups];
  435. if (type) {
  436. const name = type === 'input' ? currentNode.getAttribute('name') : currentNode.dataset.id;
  437. if (type === 'repeater') {
  438. newParentRepeaters.push(name);
  439. }
  440. if (type === 'group') {
  441. newParentGroups.push(name);
  442. }
  443. // skip _wpcf7 hidden fields
  444. if (name.substring(0,6) === '_wpcf7') return {};
  445. const original_name = type === 'repeater' || type === 'group' ? currentNode.dataset.orig_data_id
  446. : type === 'input' ? (currentNode.getAttribute('data-orig_name') || name)
  447. : name;
  448. const nameWithoutBrackets = name.replace('[]','');
  449. const originalNameWithoutBrackets = original_name.replace('[]','');
  450. const val = type === 'step' ? [currentNode.dataset.id.substring(5)] : [];
  451. const suffix = nameWithoutBrackets.replace(originalNameWithoutBrackets, '');
  452. if (!simplified_dom[name]) {
  453. // init entry
  454. simplified_dom[name] = {name, type, original_name, suffix, val, parentGroups, parentRepeaters}
  455. }
  456. if (type === 'input') {
  457. // skip unchecked checkboxes and radiobuttons
  458. if ( (currentNode.type === 'checkbox' || currentNode.type === 'radio') && !currentNode.checked ) return {};
  459. // if multiselect, make sure to add all the values
  460. if ( currentNode.multiple && currentNode.options ) {
  461. simplified_dom[name].val = Object.values(currentNode.options).filter(o => o.selected).map(o => o.value)
  462. } else {
  463. simplified_dom[name].val.push(currentNode.value);
  464. }
  465. }
  466. }
  467. // can't use currentNode.children (because then field name cannot be "children")
  468. const getter = Object.getOwnPropertyDescriptor(Element.prototype, "children").get;
  469. const children = getter.call(currentNode);
  470. Array.from(children).forEach(childNode => {
  471. const dom = wpcf7cf.get_simplified_dom_model(childNode, simplified_dom, newParentGroups, newParentRepeaters);
  472. simplified_dom = {...dom, ...simplified_dom} ;
  473. });
  474. return simplified_dom;
  475. },
  476. updateMultistepState: function (multistep) {
  477. if (multistep == null) return;
  478. if (multistep.form.$form.hasClass('submitting')) return;
  479. // update hidden input field
  480. const stepsData = {
  481. currentStep : multistep.currentStep,
  482. numSteps : multistep.numSteps,
  483. fieldsInCurrentStep : multistep.getFieldsInStep(multistep.currentStep)
  484. };
  485. multistep.form.$input_steps.val(JSON.stringify(stepsData));
  486. // update Buttons
  487. multistep.$btn_prev.removeClass('disabled').attr('disabled', false);
  488. multistep.$btn_next.removeClass('disabled').attr('disabled', false);
  489. if (multistep.currentStep == multistep.numSteps) {
  490. multistep.$btn_next.addClass('disabled').attr('disabled', true);
  491. }
  492. if (multistep.currentStep == 1) {
  493. multistep.$btn_prev.addClass('disabled').attr('disabled', true);
  494. }
  495. // replace next button with submit button on last step.
  496. // TODO: make this depend on a setting
  497. const $submit_button = multistep.form.$form.find('input[type="submit"]:last').eq(0);
  498. const $ajax_loader = multistep.form.$form.find('.wpcf7-spinner').eq(0);
  499. $submit_button.detach().prependTo(multistep.$btn_next.parent());
  500. $ajax_loader.detach().prependTo(multistep.$btn_next.parent());
  501. if (multistep.currentStep == multistep.numSteps) {
  502. multistep.$btn_next.hide();
  503. $submit_button.show();
  504. } else {
  505. $submit_button.hide();
  506. multistep.$btn_next.show();
  507. }
  508. // update dots
  509. const $dots = multistep.$dots.find('.dot');
  510. $dots.removeClass('active').removeClass('completed');
  511. for(let step = 1; step <= multistep.numSteps; step++) {
  512. if (step < multistep.currentStep) {
  513. $dots.eq(step-1).addClass('completed');
  514. } else if (step == multistep.currentStep) {
  515. $dots.eq(step-1).addClass('active');
  516. }
  517. }
  518. },
  519. should_group_be_shown : function(condition, form) {
  520. let show_group = true;
  521. let atLeastOneFieldFound = false;
  522. for (let and_rule_i = 0; and_rule_i < condition.and_rules.length; and_rule_i++) {
  523. let condition_ok = false;
  524. const condition_and_rule = condition.and_rules[and_rule_i];
  525. const inputField = form.getFieldByName(condition_and_rule.if_field);
  526. if (!inputField) continue; // field not found
  527. atLeastOneFieldFound = true;
  528. const if_val = condition_and_rule.if_value;
  529. let operator = condition_and_rule.operator;
  530. //backwards compat
  531. operator = operator === '≤' ? 'less than or equals' : operator;
  532. operator = operator === '≥' ? 'greater than or equals' : operator;
  533. operator = operator === '>' ? 'greater than' : operator;
  534. operator = operator === '<' ? 'less than' : operator;
  535. const $field = operator === 'function' && jQuery(`[name="${inputField.name}"]`).eq(0);
  536. condition_ok = this.isConditionTrue(inputField.val,operator,if_val, $field);
  537. show_group = show_group && condition_ok;
  538. }
  539. return show_group && atLeastOneFieldFound;
  540. },
  541. isConditionTrue(values, operator, testValue='', $field=jQuery()) {
  542. if (!Array.isArray(values)) {
  543. values = [values];
  544. }
  545. let condition_ok = false; // start by assuming that the condition is not met
  546. // Considered EMPTY: [] [''] [null] ['',null] [,,'']
  547. // Considered NOT EMPTY: [0] ['ab','c'] ['',0,null]
  548. const valuesAreEmpty = values.length === 0 || values.every((v) => !v&&v!==0); // 0 is not considered empty
  549. // special cases: [] equals '' => TRUE; [] not equals '' => FALSE
  550. if (operator === 'equals' && testValue === '' && valuesAreEmpty) {
  551. return true;
  552. }
  553. if (operator === 'not equals' && testValue === '' && valuesAreEmpty) {
  554. return false;
  555. }
  556. if (valuesAreEmpty) {
  557. if (operator === 'is empty') {
  558. condition_ok = true;
  559. }
  560. } else {
  561. if (operator === 'not empty') {
  562. condition_ok = true;
  563. }
  564. }
  565. const testValueNumber = isFinite(parseFloat(testValue)) ? parseFloat(testValue) : NaN;
  566. if (operator === 'not equals' || operator === 'not equals (regex)') {
  567. // start by assuming that the condition is met
  568. condition_ok = true;
  569. }
  570. if (
  571. operator === 'function'
  572. && typeof window[testValue] == 'function'
  573. && window[testValue]($field) // here we call the actual user defined function
  574. ) {
  575. condition_ok = true;
  576. }
  577. let regex_patt = /.*/i; // fallback regex pattern
  578. let isValidRegex = true;
  579. if (operator === 'equals (regex)' || operator === 'not equals (regex)') {
  580. try {
  581. regex_patt = new RegExp(testValue, 'i');
  582. } catch(e) {
  583. isValidRegex = false;
  584. }
  585. }
  586. for(let i = 0; i < values.length; i++) {
  587. const value = values[i];
  588. const valueNumber = isFinite(parseFloat(value)) ? parseFloat(value) : NaN;
  589. const valsAreNumbers = !isNaN(valueNumber) && !isNaN(testValueNumber);
  590. if (
  591. operator === 'equals' && value === testValue ||
  592. operator === 'equals (regex)' && regex_patt.test(value) ||
  593. operator === 'greater than' && valsAreNumbers && valueNumber > testValueNumber ||
  594. operator === 'less than' && valsAreNumbers && valueNumber < testValueNumber ||
  595. operator === 'greater than or equals' && valsAreNumbers && valueNumber >= testValueNumber ||
  596. operator === 'less than or equals' && valsAreNumbers && valueNumber <= testValueNumber
  597. ) {
  598. condition_ok = true;
  599. break;
  600. } else if (
  601. operator === 'not equals' && value === testValue ||
  602. operator === 'not equals (regex)' && regex_patt.test(value)
  603. ) {
  604. condition_ok = false;
  605. break;
  606. }
  607. }
  608. return condition_ok;
  609. },
  610. getFormObj($form) {
  611. if (typeof $form === 'string') {
  612. $form = jQuery($form).eq(0);
  613. }
  614. return wpcf7cf.getWpcf7cfForm($form);
  615. },
  616. getRepeaterObj($form, repeaterDataId) {
  617. const form = wpcf7cf.getFormObj($form);
  618. const repeater = form.repeaters.find( repeater => repeater.params.$repeater.attr('data-id') === repeaterDataId );
  619. return repeater;
  620. },
  621. getMultiStepObj($form){
  622. const form = wpcf7cf.getFormObj($form);
  623. return form.multistep;
  624. },
  625. /**
  626. * Append a new sub-entry to the repeater with the name `repeaterDataId` inside the form `$form`
  627. * @memberof wpcf7cf
  628. * @function wpcf7cf.repeaterAddSub
  629. * @link
  630. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  631. * @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)
  632. */
  633. repeaterAddSub($form,repeaterDataId) {
  634. const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
  635. repeater.updateSubs(repeater.params.$repeater.num_subs+1);
  636. },
  637. /**
  638. * Insert a new sub-entry at the given `index` of the repeater with the name `repeaterDataId` inside the form `$form`
  639. * @memberof wpcf7cf
  640. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  641. * @param {String} repeaterDataId - *data-id* attribute of the repeater.
  642. * @param {Number} index - position where to insert the new sub-entry within the repeater
  643. */
  644. repeaterAddSubAtIndex($form,repeaterDataId,index) {
  645. const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
  646. repeater.addSubs(1, index);
  647. },
  648. /**
  649. * Remove the sub-entry at the given `index` of the repeater with the *data-id* attribute of `repeaterDataId` inside the form `$form`
  650. * @memberof wpcf7cf
  651. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  652. * @param {String} repeaterDataId - *data-id* attribute of the repeater.
  653. * @param {Number} index - position where to insert the new sub-entry within the repeater
  654. */
  655. repeaterRemoveSubAtIndex($form,repeaterDataId,index) {
  656. const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
  657. repeater.removeSubs(1, index);
  658. },
  659. /**
  660. * Remove the last sub-entry from the repeater with the *data-id* attribute of `repeaterDataId` inside the form `$form`
  661. * @memberof wpcf7cf
  662. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  663. * @param {String} repeaterDataId - *data-id* attribute of the repeater.
  664. * @param {Number} index - position where to insert the new sub-entry within the repeater
  665. */
  666. repeaterRemoveSub($form,repeaterDataId) {
  667. const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
  668. repeater.updateSubs(repeater.params.$repeater.num_subs-1);
  669. },
  670. /**
  671. * Set the number of subs for the repeater with the *data-id* attribute of `repeaterDataId` inside the form `$form`.
  672. * Subs are either appended to or removed from the end of the repeater.
  673. * @memberof wpcf7cf
  674. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  675. * @param {String} repeaterDataId - *data-id* attribute of the repeater.
  676. * @param {Number} numberOfSubs - position where to insert the new sub-entry within the repeater
  677. */
  678. repeaterSetNumberOfSubs($form, repeaterDataId, numberOfSubs) {
  679. const repeater = wpcf7cf.getRepeaterObj($form, repeaterDataId);
  680. repeater.updateSubs(numberOfSubs);
  681. },
  682. /**
  683. * Move to step number `step`, ignoring any validation.
  684. * @memberof wpcf7cf
  685. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  686. * @param {*} step
  687. */
  688. multistepMoveToStep($form, step) {
  689. const multistep = wpcf7cf.getMultiStepObj($form);
  690. multistep.moveToStep(step);
  691. },
  692. /**
  693. * Validate the current step, and move to step number `step` if validation passes.
  694. * @memberof wpcf7cf
  695. * @param {String|JQuery} $form - JQuery object or css-selector representing the form
  696. * @param {Number} step
  697. */
  698. async multistepMoveToStepWithValidation($form, step) {
  699. const multistep = wpcf7cf.getMultiStepObj($form);
  700. const result = await multistep.validateStep(multistep.currentStep);
  701. if (result === 'success') {
  702. multistep.moveToStep(step);
  703. }
  704. },
  705. };
  706. jQuery('.wpcf7-form').each(function(){
  707. wpcf7cf_forms.push(new Wpcf7cfForm(jQuery(this)));
  708. });
  709. // Call displayFields again on all forms
  710. // Necessary in case some theme or plugin changed a form value by the time the entire page is fully loaded.
  711. jQuery('document').ready( function() {
  712. wpcf7cf_forms.forEach(function(f){
  713. f.displayFields();
  714. });
  715. });
  716. // fix for exclusive checkboxes in IE (this will call the change-event again after all other checkboxes are unchecked, triggering the display_fields() function)
  717. const old_wpcf7ExclusiveCheckbox = jQuery.fn.wpcf7ExclusiveCheckbox;
  718. jQuery.fn.wpcf7ExclusiveCheckbox = function() {
  719. return this.find('input:checkbox').on('click', function() {
  720. const name = jQuery(this).attr('name');
  721. jQuery(this).closest('form').find('input:checkbox[name="' + name + '"]').not(this).prop('checked', false).eq(0).change();
  722. });
  723. };