PluginProbe
Repeater Fields for Gravity Forms / 3.2.2
Repeater Fields for Gravity Forms v3.2.2
3.2.2 3.2.1 3.2.0 3.1.1 3.1.0 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 2.5.1 2.5.0 2.4.6 trunk 2.0.5 2.0.9 2.1.0 2.3.2 2.3.7 2.4.1 2.4.3 2.4.4 2.4.5
← All changes | libs/wp_repeater.js +203 -45 2.4.5 → 3.2.2 View file →
@@ -153,9 +153,17 @@
153 153 //jQuery(".gform-datepicker").datepicker('destroy');
154 154 get_repeater_data_name();
155 155 });
156 156 gform.addFilter('gform_file_upload_markup', function (html, file, up, strings, imagesUrl, response) {
157 - var repeater = $('#' + file.id).closest(".container-repeater-field").find(".gform_multifile_upload")
157 + var formId = up.settings.multipart_params.form_id;
158 + var fieldId = up.settings.multipart_params.field_id;
159 + var container = $('#' + file.id).closest(".container-repeater-field");
160 + var id_rand = container.data("id");
161 + var nameAttr = 'gform_multifile_upload_' + formId + '_' + fieldId + '__' + id_rand;
162 + var repeater = container.find('input[name="' + nameAttr + '"]');
163 + if (repeater.length === 0) {
164 + repeater = container.find(".gform_multifile_upload");
165 + }
158 166 var val_repeater = repeater.val();
159 167 var link = response.data.uploaded_filename;
160 168 if (val_repeater == "") {
161 169 repeater.val(link)
@@ -163,8 +171,17 @@
163 171 repeater.val(val_repeater + ", " + link);
164 172 }
165 173 return html.replace("onclick", "data-onclick");;
166 174 });
175 + if (window.gform && typeof window.gform.addFilter === 'function') {
176 + window.gform.addFilter('gform_datepicker_options_pre_init', function (options, formId, fieldId) {
177 + if (typeof fieldId === 'string' && fieldId.indexOf('-') > -1) {
178 + var originalFieldId = fieldId.split('-')[0];
179 + return window.gform.applyFilters('gform_datepicker_options_pre_init', options, formId, originalFieldId);
180 + }
181 + return options;
182 + }, 9);
183 + }
167 184 var repeater_fields_htmls = {};
168 185 //condition out repeater out
169 186 gform.addAction('gform_post_conditional_logic_field_action1', function (formId, action, targetId, defaultValues, isInit) {
170 187 if (targetId != "") {
@@ -205,9 +222,12 @@
205 222 function gformDeleteUploadedFileRepeater(formId, fieldId, deleteButton) {
206 223 if (deleteButton.closest(".repeater-field-warp-item-data").length > 0) {
207 224 var rand_id = deleteButton.closest(".container-repeater-field").data("id");
208 225 var fileIndex = jQuery(deleteButton).parent().index();
209 - var parent = jQuery("#field_" + formId + "_" + fieldId);
226 + var parent = deleteButton.closest(".container-repeater-field").find("#field_" + formId + "_" + fieldId + "-" + rand_id);
227 + if (parent.length === 0) {
228 + parent = jQuery("#field_" + formId + "_" + fieldId);
229 + }
210 230 parent.find('input[type="file"],.validation_message,#extensions_message_' + formId + '_' + fieldId).removeClass("gform_hidden");
211 231 parent.find(".ginput_post_image_file").show();
212 232 //parent.find("input[type=\"text\"]").val('');
213 233 var filesJson = jQuery('#gform_uploaded_files_' + formId).val();
@@ -214,9 +234,9 @@
214 234 if (filesJson) {
215 235 var files = jQuery.secureEvalJSON(filesJson);
216 236 if (files) {
217 237 var inputName = "input_" + fieldId;
218 - var full_name = deleteButton.closest(".repeater-field-warp-item-data").find('input[name="gform_multifile_upload_' + formId + '_' + fieldId + '__' + rand_id + '"]').val();
238 + var full_name = deleteButton.closest(".container-repeater-field").find('input[name="gform_multifile_upload_' + formId + '_' + fieldId + '__' + rand_id + '"]').val();
219 239 if (full_name == "") {
220 240 full_name = [];
221 241 } else {
222 242 full_name = full_name.split(",");
@@ -226,9 +246,9 @@
226 246 var index1 = full_name.indexOf(remove_name);
227 247 if (index1 !== -1) {
228 248 full_name.splice(index1, 1);
229 249 }
230 - deleteButton.closest(".repeater-field-warp-item-data").find('input[name="gform_multifile_upload_' + formId + '_' + fieldId + '__' + rand_id + '"]').val(full_name.join(","));
250 + deleteButton.closest(".container-repeater-field").find('input[name="gform_multifile_upload_' + formId + '_' + fieldId + '__' + rand_id + '"]').val(full_name.join(","));
231 251 deleteButton.closest(".ginput_preview").remove();
232 252 if ($multfile.length > 0) {
233 253 files[inputName].splice(fileIndex, 1);
234 254 var settings = $multfile.data('settings');
@@ -274,9 +294,9 @@
274 294 }
275 295 }
276 296 }
277 297 var input_ids = [];
278 - function change_name_and_ids(item, field_end = null, key = null) {
298 + function change_name_and_ids(item, field_end = null, key = null, row_data = null) {
279 299 if (key == null) {
280 300 var id_rand = Math.floor(Math.random() * 10000);
281 301 } else {
282 302 var id_rand = key;
@@ -287,8 +307,29 @@
287 307 datas.id = datas_ids;
288 308 field_end.find(".gf-field-repeater-data").val(JSON.stringify(datas));
289 309 item = $(item);
290 310 item.attr("data-id", id_rand);
311 +
312 + function get_saved_field_val(fieldName) {
313 + if (!row_data || typeof row_data !== 'object') {
314 + return null;
315 + }
316 + var clean = fieldName.replace(/\[\]$/, '');
317 + if (row_data[clean] !== undefined) {
318 + return row_data[clean];
319 + }
320 + var withUnderscore = clean.replace(/\./g, '_');
321 + if (row_data[withUnderscore] !== undefined) {
322 + return row_data[withUnderscore];
323 + }
324 + for (var rk in row_data) {
325 + if (rk === clean || rk === withUnderscore || rk.replace(/\./g, '_') === withUnderscore) {
326 + return row_data[rk];
327 + }
328 + }
329 + return null;
330 + }
331 +
291 332 $(".gfield_visibility_visible", item).each(function () {
292 333 var id = $(this).attr("id");
293 334 $(this).attr("id", id + "-" + id_rand);
294 335 $(this).attr("data-js-reload", id + "-" + id_rand);
@@ -324,8 +365,9 @@
324 365 }
325 366 var type = $(this).attr("type");
326 367 var id = $(this).attr("id");
327 368 input_ids.push(id);
369 + var original_name = name;
328 370 if (name != "" && name.endsWith('[]')) {
329 371 name = name.replace(/\[\]$/, '');
330 372 $(this).attr("name", name + "__" + id_rand + "[]");
331 373 } else {
@@ -331,31 +373,89 @@
331 373 } else {
332 374 $(this).attr("name", name + "__" + id_rand);
333 375 }
334 376 $(this).attr("id", id + "-" + id_rand);
377 + if ($(this).hasClass('gform-datepicker') || $(this).hasClass('datepicker')) {
378 + $(this).removeAttr('data-initialized').removeData('initialized').removeClass('hasDatepicker');
379 + if (this.dataset) {
380 + delete this.dataset.initialized;
381 + delete this.dataset.toggleClickedBeforeInit;
382 + }
383 + var $toggleBtn = $(this).siblings('.gform-datepicker-toggle');
384 + if (!$toggleBtn.length) {
385 + $toggleBtn = $(this).closest('.ginput_container_date').find('.gform-datepicker-toggle');
386 + }
387 + if ($toggleBtn.length) {
388 + $toggleBtn.attr('id', 'datepicker_toggle_' + id + '-' + id_rand);
389 + $toggleBtn.attr('aria-controls', id + '-' + id_rand);
390 + }
391 + var $kbd = $(this).siblings('#keyboardHint_' + id);
392 + if (!$kbd.length) {
393 + $kbd = $(this).closest('.ginput_container_date').find('#keyboardHint_' + id);
394 + }
395 + if ($kbd.length) {
396 + $kbd.attr('id', 'keyboardHint_' + id + '-' + id_rand);
397 + }
398 + }
335 399 var value_check = localStorage.getItem(id + "-" + id_rand);
400 + var saved_val = get_saved_field_val(name + "__" + id_rand);
401 + if (saved_val === null) {
402 + saved_val = get_saved_field_val(original_name + "__" + id_rand);
403 + }
336 404 if (type == "checkbox") {
337 405 $(this).closest("div").find("label").attr("for", id + "-" + id_rand);
338 - if (value_check != null) {
339 - $(this).attr("checked", "checked");
406 + if (saved_val !== null) {
407 + var is_checked = false;
408 + if (Array.isArray(saved_val)) {
409 + is_checked = saved_val.indexOf($(this).val()) !== -1;
410 + } else {
411 + is_checked = ($(this).val() == saved_val);
412 + }
413 + if (is_checked) {
414 + $(this).prop("checked", true).attr("checked", "checked");
415 + } else {
416 + $(this).prop("checked", false).removeAttr("checked");
417 + }
418 + } else if (value_check != null) {
419 + $(this).prop("checked", true).attr("checked", "checked");
340 420 }
341 421 } else if (type == "file") {
342 422 $(this).attr("id", id + "-" + id_rand);
423 + if (saved_val && typeof saved_val === 'string' && saved_val.indexOf('http') === 0) {
424 + var $fileContainer = $(this).closest('.ginput_container_fileupload');
425 + if ($fileContainer.length && !$fileContainer.find('.gf-repeater-saved-file').length) {
426 + var fileName = saved_val.split('/').pop();
427 + $fileContainer.append('<div class="gf-repeater-saved-file" style="margin-top:5px;font-size:13px;">' +
428 + '<a href="' + encodeURI(saved_val) + '" target="_blank" rel="noopener">' + (fileName || saved_val) + '</a>' +
429 + '<input type="hidden" name="' + name + '__' + id_rand + '" value="' + saved_val + '" />' +
430 + '</div>');
431 + }
432 + }
343 433 } else if (type == "radio") {
344 434 $(this).attr("id", id + "-" + id_rand);
345 435 $(this).closest("div, li").find("label").attr("for", id + "-" + id_rand);
346 - var value_check = localStorage.getItem(name + "__" + id_rand);
347 - if (value_check != null) {
348 - var old_check = $(this).val();
349 - if (old_check == value_check) {
350 - $(this).attr("checked", true);
436 + if (saved_val !== null) {
437 + if ($(this).val() == saved_val) {
438 + $(this).prop("checked", true).attr("checked", "checked");
439 + } else {
440 + $(this).prop("checked", false).removeAttr("checked");
351 441 }
442 + } else {
443 + var value_check = localStorage.getItem(name + "__" + id_rand);
444 + if (value_check != null) {
445 + var old_check = $(this).val();
446 + if (old_check == value_check) {
447 + $(this).prop("checked", true).attr("checked", "checked");
448 + }
449 + }
352 450 }
353 451 }
354 452 else {
355 453 // ensure the field label's "for" attribute corresponds with the field's ID
356 454 $(this).closest("div").parent().find("label").attr("for", id + "-" + id_rand);
357 - if (value_check != null) {
455 + if (saved_val !== null) {
456 + $(this).val(saved_val);
457 + } else if (value_check != null) {
358 458 $(this).val(value_check);
359 459 }
360 460 }
361 461 })
@@ -365,11 +465,16 @@
365 465 $(this).attr("name", name + "__" + id_rand);
366 466 $(this).attr("id", id + "-" + id_rand);
367 467 // ensure the field label's "for" attribute corresponds with the field's ID
368 468 $(this).closest("div").parent().find("label").attr("for", id + "-" + id_rand);
369 - var value_check = localStorage.getItem(id + "-" + id_rand);
370 - if (value_check != null) {
371 - $(this).val(value_check);
469 + var saved_val = get_saved_field_val(name + "__" + id_rand);
470 + if (saved_val !== null) {
471 + $(this).val(saved_val);
472 + } else {
473 + var value_check = localStorage.getItem(id + "-" + id_rand);
474 + if (value_check != null) {
475 + $(this).val(value_check);
476 + }
372 477 }
373 478 })
374 479 $("select", item).each(function () {
375 480 var name = $(this).attr("name");
@@ -382,16 +487,21 @@
382 487 }
383 488 $(this).attr("id", id + "-" + id_rand);
384 489 // ensure the field label's "for" attribute corresponds with the field's ID
385 490 $(this).closest("div").parent().find("label").attr("for", id + "-" + id_rand);
386 - var value_check = localStorage.getItem(id + "-" + id_rand);
387 - if (value_check != null) {
388 - $(this).val(value_check);
491 + var saved_val = get_saved_field_val(name + "__" + id_rand);
492 + if (saved_val !== null) {
493 + $(this).val(saved_val);
494 + } else {
495 + var value_check = localStorage.getItem(id + "-" + id_rand);
496 + if (value_check != null) {
497 + $(this).val(value_check);
498 + }
389 499 }
390 500 })
391 501 return item;
392 502 }
393 - function add_repeater_data(button, key = null) {
503 + function add_repeater_data(button, key = null, row_data = null) {
394 504 var start_field;
395 505 if (key == null) {
396 506 var key = Math.floor(Math.random() * 10000);
397 507 }
@@ -402,13 +512,14 @@
402 512 start_field = $(this);
403 513 return false;
404 514 }
405 515 })
406 - var html_field = get_repeater_data(button, key);
516 + var html_field = get_repeater_data(button, key, row_data);
407 517 var header = get_repeater_data_header(start_field);
408 518 item.find(".repeater-field-header").append(header);
409 519 item.find(".repeater-field-content").append(html_field);
410 520 button.find(".repeater-field-warp-item").append(item);
521 + item.find("input[type=radio][checked], input[type=checkbox][checked]").prop("checked", true);
411 522 update_repeater_count_header();
412 523 $("input").trigger("done_load_repeater");
413 524 var form_ids = button.attr("id").split("_");
414 525 if (yeeaddons_gf_repeater_data.pro == "ok") {
@@ -459,14 +570,15 @@
459 570 }
460 571 }
461 572 }
462 573 //end logic
463 - var j = 0;
574 +
464 575 $.each(names_upload, function (key_1, value) {
465 - gfMultiFileUploader.setup("#" + value);
466 - names_upload.splice(j, 1);
467 - j++;
576 + if ($("#" + value).length > 0) {
577 + gfMultiFileUploader.setup("#" + value);
578 + }
468 579 });
580 + names_upload = [];
469 581 var input_mask = yeeaddons_gf_repeater_data.input_mask;
470 582 $.each(input_ids, function (key_1, value_1) {
471 583 if (value_1 in input_mask) {
472 584 $('#' + value_1 + "-" + key).mask(input_mask[value_1]).bind('keypress', function (e) { if (e.which == 13) { jQuery(this).blur(); } })
@@ -475,15 +587,48 @@
475 587 conditional_logic_custom(key);
476 588 input_ids = [];
477 589 $('.gform-datepicker').each(function () {
478 590 var $element = $(this);
479 - initSingleDatepicker($element);
480 - $element.addClass('initialized');
591 + if (typeof $.fn.datepicker === 'function') {
592 + // Legacy GF < 3.0: jQuery UI datepicker available
593 + if (!$element.hasClass('hasDatepicker')) {
594 + initSingleDatepicker($element);
595 + $element.addClass('initialized');
596 + }
597 + }
481 598 });
599 + // GF 3.0+: Re-trigger datepicker initialization for the form
600 + // so GF's theme script loads the datepicker chunk and sets up delegation
601 + reinitGF3Datepicker(button);
482 602 }
603 + /**
604 + * Re-trigger GF 3.0 datepicker initialization.
605 + * GF 3.0 uses lazy-loading: the datepicker chunk is only loaded when
606 + * gform/post_render fires and .gform-datepicker elements exist in the DOM.
607 + * Since the repeater removes original fields before GF checks for them,
608 + * we need to re-dispatch the event after creating new rows.
609 + */
610 + var _gf3DatepickerTriggered = {};
611 + function reinitGF3Datepicker(el) {
612 + if (typeof $.fn.datepicker === 'function') {
613 + return; // Legacy jQuery UI datepicker is available, no need for GF 3.0 re-init
614 + }
615 + var $wrapper = el.closest('.gform_wrapper');
616 + if (!$wrapper.length) return;
617 + var formId = parseInt($wrapper.attr('id').replace('gform_wrapper_', ''));
618 + if (!formId || isNaN(formId)) return;
619 + // Only trigger once per form per page load to avoid duplicate event listeners
620 + if (_gf3DatepickerTriggered[formId]) return;
621 + _gf3DatepickerTriggered[formId] = true;
622 + // Dispatch gform/post_render so GF's theme script detects datepicker fields
623 + // and lazy-loads the accessible datepicker chunk
624 + document.dispatchEvent(new CustomEvent('gform/post_render', {
625 + detail: { formId: formId, currentPage: 1 }
626 + }));
627 + }
483 628 function yeeaddons_change_id_logic(value, key) {
484 629 var field_rules_inner = [];
485 - if (value && Array.isArray(value.field.rules)) { // added this line to fix js issue in add more to autofill multiple data
630 + if (value && value.field && Array.isArray(value.field.rules)) { // added this line to fix js issue in add more to autofill multiple data
486 631 $.each(value.field.rules, function (key_2, value_2) {
487 632 if (value_2.fieldId.search("-") < 0) {
488 633 var field_id_1 = value_2.fieldId + "-" + key;
489 634 }
@@ -511,14 +656,14 @@
511 656 if (datas.length > 1) {
512 657 conditional_logic_custom(datas[1]);
513 658 }
514 659 }, 10);
515 - function get_repeater_data(step_field, key = null) {
660 + function get_repeater_data(step_field, key = null, row_data = null) {
516 661 var data_html = step_field.find(".gf-field-repeater-data-html").val();
517 662 if (data_html == "") {
518 663 data_html = step_field.find(".gf-field-repeater-data-html").attr('value');
519 664 }
520 - var html_step = change_name_and_ids(data_html, step_field, key);
665 + var html_step = change_name_and_ids(data_html, step_field, key, row_data);
521 666 return html_step;
522 667 }
523 668 function get_repeater_data_name() {
524 669 var i = 1;
@@ -597,16 +742,27 @@
597 742 if (initial_rows === undefined || initial_rows === "") {
598 743 initial_rows = 1;
599 744 }
600 745 if (value != "") {
601 - value = JSON.parse(value);
602 - initial_rows = value.count;
603 - var data_arr_ids = value.id;
604 - setTimeout(function () {
605 - for (var j = 0; j < initial_rows; j++) {
606 - add_repeater_data(step_field.closest(".gfield--type-repeater_end"), data_arr_ids[j]);
607 - }
608 - }, 100);
746 + try {
747 + var parsed_val = typeof value === "string" ? JSON.parse(value) : value;
748 + initial_rows = parsed_val.count !== undefined ? parsed_val.count : (parsed_val.id ? parsed_val.id.length : 1);
749 + var data_arr_ids = parsed_val.id || [];
750 + var saved_values = parsed_val.saved_values || null;
751 + setTimeout(function () {
752 + for (var j = 0; j < initial_rows; j++) {
753 + var row_id = data_arr_ids[j] !== undefined ? data_arr_ids[j] : null;
754 + var row_data = (saved_values && row_id && saved_values[row_id]) ? saved_values[row_id] : null;
755 + add_repeater_data(step_field.closest(".gfield--type-repeater_end"), row_id, row_data);
756 + }
757 + }, 100);
758 + } catch (e) {
759 + setTimeout(function () {
760 + for (var j = 0; j < initial_rows; j++) {
761 + add_repeater_data(step_field.closest(".gfield--type-repeater_end"));
762 + }
763 + }, 100);
764 + }
609 765 } else {
610 766 setTimeout(function () {
611 767 for (var j = 0; j < initial_rows; j++) {
612 768 add_repeater_data(step_field.closest(".gfield--type-repeater_end"));
@@ -865,15 +1021,17 @@
865 1021 }
866 1022 inputId = inputId.split('_');
867 1023 // allow the user to override the datepicker options object
868 1024 optionsObj = gform.applyFilters('gform_datepicker_options_pre_init', optionsObj, inputId[1], inputId[2], $element);
869 - $element.datepicker(optionsObj);
870 - // We give the input focus after selecting a date which differs from default Datepicker behavior; this prevents
871 - // users from clicking on the input again to open the datepicker. Let's add a manual click event to handle this.
872 - if ($element.is(':input')) {
873 - $element.click(function () {
874 - $element.datepicker('show');
875 - });
1025 + if (typeof $element.datepicker === 'function') {
1026 + $element.datepicker(optionsObj);
1027 + // We give the input focus after selecting a date which differs from default Datepicker behavior; this prevents
1028 + // users from clicking on the input again to open the datepicker. Let's add a manual click event to handle this.
1029 + if ($element.is(':input')) {
1030 + $element.click(function () {
1031 + $element.datepicker('show');
1032 + });
1033 + }
876 1034 }
877 1035 }
878 1036 })
879 1037 $(document).on("change", ".gfield--type-repeater_end input,.gfield--type-repeater_end textarea,.gfield--type-repeater_end select", function (event) {