PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.2.7
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.2.7
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / assets / js / admin / admin.js

admin.js in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.2.7, at assets/js/admin/admin.js

2,624 lines 108.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2
3 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4 ;
5 (function ($, window, document, undefined) {
6 'use strict';
7
8 var merchant = merchant || {};
9 var params = new URLSearchParams(window.location.search);
10 var currentModule = params.get('module');
11 $(document).ready(function () {
12 // AjaxSave
13 var $ajaxForm = $('.merchant-module-page-ajax-form');
14 var $ajaxHeader = $('.merchant-module-page-ajax-header');
15 var $ajaxSaveBtn = $('.merchant-module-save-button');
16 $('.merchant-module-page-content').on('change keypress change.merchant', function (e) {
17 if ($(e.target).hasClass('merchant-backup-file') || $(e.target).hasClass('merchant-search-field')) {
18 return;
19 }
20 if (!$(this).is('.merchant-module-question-answer-textarea, .merchant-license-code-input')) {
21 if (!merchant.show_save) {
22 $ajaxHeader.addClass('merchant-show');
23 $ajaxHeader.removeClass('merchant-saving');
24 merchant.show_save = true;
25 }
26 }
27 GroubField.initFlag();
28 });
29
30 // Warn the user before leaving the page with unsaved changes.
31 window.addEventListener('beforeunload', function (e) {
32 if (merchant.show_save) {
33 e.preventDefault();
34 }
35 });
36 $ajaxForm.ajaxForm({
37 beforeSubmit: function beforeSubmit(arr) {
38 $ajaxHeader.addClass('merchant-saving');
39
40 // Serialize merchant[] fields into a single JSON payload
41 // to bypass PHP's max_input_vars limit (Issue #608).
42 var merchantData = {};
43 var keepIndexes = [];
44 arr.forEach(function (field, index) {
45 if (field.name === 'merchant-search-field') {
46 keepIndexes.push(index);
47 return;
48 }
49 if (field.name && field.name.indexOf('merchant[') === 0) {
50 var keys = field.name.match(/\[([^\]]*)\]/g);
51 if (!keys) {
52 return;
53 }
54 var obj = merchantData;
55 for (var i = 0; i < keys.length - 1; i++) {
56 var key = keys[i].slice(1, -1);
57 if (obj[key] === undefined) {
58 var nextKey = keys[i + 1] ? keys[i + 1].slice(1, -1) : '';
59 obj[key] = /^\d+$/.test(nextKey) ? [] : {};
60 }
61 obj = obj[key];
62 }
63 var lastKey = keys[keys.length - 1].slice(1, -1);
64 if (lastKey === '') {
65 // Array notation (e.g. name="...[show_pages][]") — push into parent array.
66 var parentKey = keys[keys.length - 2].slice(1, -1);
67 var parentObj = merchantData;
68 for (var j = 0; j < keys.length - 2; j++) {
69 parentObj = parentObj[keys[j].slice(1, -1)];
70 }
71 if (!Array.isArray(parentObj[parentKey])) {
72 parentObj[parentKey] = [];
73 }
74 parentObj[parentKey].push(field.value);
75 } else {
76 obj[lastKey] = field.value;
77 }
78 } else {
79 keepIndexes.push(index);
80 }
81 });
82
83 // Ensure unchecked checkboxes inside hydrated FC rows are saved as 0.
84 // serializeArray() omits unchecked checkboxes entirely, which would
85 // silently drop the field from the JSON payload and lose the "off" state.
86 $('.merchant-flexible-content .layout:not([data-deferred])').each(function () {
87 $(this).find('.layout-body input[type="checkbox"]:not(:checked)').each(function () {
88 var name = $(this).attr('name') || '';
89 if (name.indexOf('merchant[') !== 0) {
90 return;
91 }
92
93 // Skip checkbox_multiple (name ends with []) — absence = empty array.
94 if (/\[\]$/.test(name)) {
95 return;
96 }
97 var keys = name.match(/\[([^\]]*)\]/g);
98 if (!keys) {
99 return;
100 }
101 var obj = merchantData;
102 for (var i = 0; i < keys.length - 1; i++) {
103 var key = keys[i].slice(1, -1);
104 if (obj[key] === undefined) {
105 obj[key] = {};
106 }
107 obj = obj[key];
108 }
109 var lastKey = keys[keys.length - 1].slice(1, -1);
110 if (obj[lastKey] === undefined) {
111 obj[lastKey] = 0;
112 }
113 });
114 });
115
116 // Merge deferred (non-hydrated) layout row data into merchantData.
117 // These rows have no form inputs, only a data-fields-json attribute.
118 $('.merchant-flexible-content .layout[data-deferred="1"]').each(function () {
119 var jsonStr = $(this).attr('data-fields-json');
120 if (!jsonStr) {
121 return;
122 }
123 var deferredData;
124 try {
125 deferredData = JSON.parse(jsonStr);
126 } catch (e) {
127 return;
128 }
129 var $control = $(this).closest('.merchant-flexible-content-control');
130 var fcFieldId = $control.attr('data-id');
131 var rowIndex = parseInt($(this).find('.layout-count').text(), 10) - 1;
132 if (!merchantData[fcFieldId]) {
133 merchantData[fcFieldId] = {};
134 }
135
136 // Build the row data from deferred values + layout/flexible_id from hidden inputs.
137 var rowData = $.extend({}, deferredData.values || {});
138 rowData.layout = $(this).attr('data-type') || '';
139 rowData.flexible_id = $(this).find('.flexible-id').val() || '';
140 merchantData[fcFieldId][rowIndex] = rowData;
141 });
142 var kept = keepIndexes.map(function (i) {
143 return arr[i];
144 });
145 kept.push({
146 name: 'merchant_json_payload',
147 value: JSON.stringify(merchantData)
148 });
149 arr.length = 0;
150 kept.forEach(function (item) {
151 arr.push(item);
152 });
153 },
154 success: function success() {
155 $ajaxHeader.removeClass('merchant-show');
156 merchant.show_save = false;
157
158 // Module Alert after Ajax Save
159 if (!$('.merchant-module-action').hasClass('merchant-enabled')) {
160 var $moduleAlert = $('.merchant-module-alert');
161 $moduleAlert.addClass('merchant-show');
162 $(document).off('click.merchant-alert-close');
163 $(document).on('click.merchant-alert-close', function (e) {
164 if (!$(e.target).closest('.merchant-module-alert-wrapper').length) {
165 $moduleAlert.removeClass('merchant-show');
166 $(document).off('click.merchant-alert-close');
167 }
168 });
169 }
170 $(document).trigger('save.merchant', [currentModule]);
171 }
172 });
173 var $disableModuleSubmitBtn = $('.merchant-module-question-answer-button');
174 var $disableModuleTextField = $('.merchant-module-question-answer-textarea');
175 $disableModuleTextField.on('input', function () {
176 $disableModuleSubmitBtn.prop('disabled', $(this).val().trim() === '');
177 });
178 $disableModuleSubmitBtn.on('click', function (e) {
179 e.preventDefault();
180 var message = $disableModuleTextField.val();
181 if (!message.trim()) {
182 alert('Please provide the required information.');
183 return;
184 }
185 var $button = $(this);
186 $('.merchant-module-question-answer-dropdown').removeClass('merchant-show');
187 $('.merchant-module-question-thank-you-dropdown').addClass('merchant-show');
188 window.wp.ajax.post('merchant_module_feedback', {
189 subject: $disableModuleTextField.attr('data-subject'),
190 message: message,
191 module: $button.closest('.merchant-module-action').find('.merchant-module-page-button-action-activate').data('module'),
192 nonce: window.merchant.nonce
193 });
194 });
195 $('.merchant-module-page-button-action-activate').on('click', function (e) {
196 e.preventDefault();
197 if ($(this).hasClass('merchant-module-deactivated-by-bp')) {
198 return false;
199 }
200 $('.merchant-module-question-list-dropdown').removeClass('merchant-show');
201 $('.merchant-module-question-answer-dropdown').removeClass('merchant-show');
202 $('.merchant-module-question-answer-form').removeClass('merchant-show');
203 $('.merchant-module-question-answer-title').removeClass('merchant-show');
204 $('.merchant-module-question-thank-you-dropdown').removeClass('merchant-show');
205 $('.merchant-module-question-answer-textarea').val('');
206 window.wp.ajax.post('merchant_module_activate', {
207 module: $(this).data('module'),
208 nonce: window.merchant.nonce
209 }).done(function () {
210 $('body').removeClass('merchant-module-disabled').addClass('merchant-module-enabled');
211 $('.merchant-module-action').addClass('merchant-enabled');
212 });
213 });
214 $('.merchant-module-page-button-action-deactivate').on('click', function (e) {
215 e.preventDefault();
216 window.wp.ajax.post('merchant_module_deactivate', {
217 module: $(this).data('module'),
218 nonce: window.merchant.nonce
219 }).done(function () {
220 $('body').removeClass('merchant-module-enabled').addClass('merchant-module-disabled');
221 $('.merchant-module-action').removeClass('merchant-enabled');
222 $('.merchant-module-question-list-dropdown').addClass('merchant-show');
223 });
224 });
225 $('.merchant-module-question-list-dropdown li').on('click', function (e) {
226 $disableModuleSubmitBtn.prop('disabled', $disableModuleTextField.val().trim() === '');
227 var $question = $(this);
228 var target = $question.data('answer-target');
229 var $answer = $('[data-answer-title="' + target + '"]');
230 if ($answer.length) {
231 $answer.addClass('merchant-show').siblings().removeClass('merchant-show');
232 $('.merchant-module-question-answer-dropdown').addClass('merchant-show');
233 $('.merchant-module-question-answer-textarea').attr('data-subject', $question.text().trim());
234 } else {
235 $('.merchant-module-question-thank-you-dropdown').addClass('merchant-show');
236 $('.merchant-module-question-answer-dropdown').removeClass('merchant-show');
237 }
238 $('.merchant-module-question-answer-textarea').val('');
239 $('.merchant-module-question-list-dropdown').removeClass('merchant-show');
240 });
241 $('.merchant-module-dropdown-close').on('click', function (e) {
242 e.preventDefault();
243 $(this).closest('.merchant-module-dropdown').removeClass('merchant-show');
244 });
245 $('.merchant-module-page-button-deactivate').on('click', function (e) {
246 e.preventDefault();
247 var $button = $(this);
248 var $dropdown = $('.merchant-module-deactivate-dropdown');
249 $dropdown.toggleClass('merchant-show');
250 $(document).off('click.merchant-close');
251 $(document).on('click.merchant-close', function (e) {
252 if (!$(e.target).closest('.merchant-module-deactivate').length) {
253 $dropdown.removeClass('merchant-show');
254 $(document).off('click.merchant-close');
255 }
256 });
257 });
258
259 // Create a function that initializes a single range or all ranges in a context for range field
260 function initMerchantRange() {
261 var rangeFields = $(document).find('.merchant-range');
262 if (rangeFields.length === 0) {
263 return;
264 }
265 rangeFields.each(function () {
266 var $range = $(this);
267 var $rangeInput = $range.find('.merchant-range-input');
268 var $numberInput = $range.find('.merchant-range-number-input');
269 $rangeInput.on('change input merchant.range merchant-init.range', function (e) {
270 var $range = $(this);
271 var value = (e.type === 'merchant' ? $numberInput.val() : $range.val()) || 0;
272 var min = $range.attr('min') || 0;
273 var max = $range.attr('max') || 1;
274 var percentage = (value - min) / (max - min) * 100;
275 if ($('body').hasClass('rtl')) {
276 $range.css({
277 'background': 'linear-gradient(to left, #3858E9 0%, #3858E9 ' + percentage + '%, #ddd ' + percentage + '%, #ddd 100%)'
278 });
279 } else {
280 $range.css({
281 'background': 'linear-gradient(to right, #3858E9 0%, #3858E9 ' + percentage + '%, #ddd ' + percentage + '%, #ddd 100%)'
282 });
283 }
284 $rangeInput.val(value);
285 $numberInput.val(value);
286 }).trigger('merchant-init.range');
287 $numberInput.on('change input blur', function () {
288 if ($rangeInput.hasClass('merchant-range-input')) {
289 $rangeInput.val($(this).val()).trigger('merchant.range');
290 }
291 });
292 });
293 }
294
295 // 1. Initialize on DOM ready (existing fields)
296 initMerchantRange();
297 $(document).on('click', '.merchant-module-page-setting-field-hidden-desc-trigger', function () {
298 var $trigger = $(this);
299 $trigger.toggleClass('expanded');
300 var showText = $trigger.attr('data-show-text');
301 var hiddenText = $trigger.attr('data-hidden-text');
302 $(this).find('span:first').text($trigger.text() === showText ? hiddenText : showText);
303 $(this).closest('.merchant-module-page-setting-field').find('.merchant-module-page-setting-field-hidden-desc').stop(true, true).slideToggle('fast');
304 });
305 var moduleBackup = {
306 init: function init() {
307 this.events();
308 },
309 events: function events() {
310 var self = this;
311 $(document).on('click', '#download-backup-button', this.download.bind(this));
312
313 // Browse button opens file picker.
314 $(document).on('click', '.merchant-dropzone__browse', function (e) {
315 e.preventDefault();
316 e.stopPropagation();
317 $(this).closest('.merchant-dropzone').find('.merchant-backup-file').trigger('click');
318 });
319
320 // Auto-restore when a file is selected (via browse or drop).
321 $(document).on('change', '#merchant-backup-file', function () {
322 var file = this.files[0];
323 if (file) {
324 self.restoreFile(file);
325 }
326 });
327
328 // Drag-and-drop visual feedback.
329 var $dropzone = $('#merchant-restore-dropzone');
330 $dropzone.on('dragover dragenter', function (e) {
331 e.preventDefault();
332 e.stopPropagation();
333 $(this).addClass('drag-over');
334 });
335 $dropzone.on('dragleave drop', function (e) {
336 e.preventDefault();
337 e.stopPropagation();
338 $(this).removeClass('drag-over');
339 });
340 $dropzone.on('drop', function (e) {
341 var file = e.originalEvent.dataTransfer.files[0];
342 if (file) {
343 self.restoreFile(file);
344 }
345 });
346 },
347 download: function download(e) {
348 var self = this;
349 e.preventDefault();
350 var container = $('.merchant-module-page-setting-fields .backup-section');
351 var $status = container.find('.merchant-backup-status');
352 $status.removeAttr('class').addClass('merchant-backup-status').text('');
353 var module_id = $(e.target).attr('data-module-id');
354 $.ajax({
355 url: merchant_admin_options.ajaxurl,
356 type: 'GET',
357 data: {
358 action: 'merchant_get_module_settings',
359 nonce: merchant_admin_options.ajaxnonce,
360 module_id: module_id
361 },
362 beforeSend: function beforeSend() {
363 self.showLoadingIndicator(container);
364 },
365 success: function success(response) {
366 self.hideLoadingIndicator(container);
367 if (response.success) {
368 var moduleSettings = response.data;
369 var fileName = 'merchant-' + module_id + '-backup-' + new Date().toISOString().slice(0, 10) + '-' + new Date().getHours() + '-' + new Date().getMinutes() + '-' + new Date().getSeconds() + '.json';
370 self.downloadJson(moduleSettings, fileName);
371 $status.addClass('is-success').text(merchant_admin_options.backup_success || 'Downloaded!');
372 } else {
373 $status.addClass('is-error').text(response.data.message);
374 }
375 setTimeout(function () {
376 $status.removeAttr('class').addClass('merchant-backup-status').text('');
377 }, 3000);
378 },
379 error: function error(xhr, status, _error) {
380 self.hideLoadingIndicator(container);
381 $status.addClass('is-error').text(_error);
382 setTimeout(function () {
383 $status.removeAttr('class').addClass('merchant-backup-status').text('');
384 }, 3000);
385 }
386 });
387 },
388 restoreFile: function restoreFile(file) {
389 var self = this;
390 var container = $('.merchant-module-page-setting-fields .restore-section');
391 var module_id = $('#merchant-restore-dropzone').data('module-id');
392 self.hideError();
393 if (!file || file.type !== 'application/json') {
394 self.displayError(merchant_admin_options.invalid_file);
395 return;
396 }
397 var reader = new FileReader();
398 reader.onload = function (e) {
399 var moduleSettings = e.target.result;
400 $.ajax({
401 url: merchant_admin_options.ajaxurl,
402 type: 'POST',
403 data: {
404 action: 'merchant_restore_module_settings',
405 nonce: merchant_admin_options.ajaxnonce,
406 module_id: module_id,
407 module_settings: moduleSettings
408 },
409 beforeSend: function beforeSend() {
410 $('#merchant-restore-dropzone').addClass('is-loading');
411 self.showLoadingIndicator(container);
412 },
413 success: function success(response) {
414 if (response.success) {
415 merchant.show_save = false;
416 var $dz = $('#merchant-restore-dropzone');
417 $dz.removeClass('is-loading').addClass('is-success');
418 self.hideLoadingIndicator(container);
419 setTimeout(function () {
420 window.location.href = response.data.redirect_url;
421 }, 1500);
422 } else {
423 self.displayError(response.data.message);
424 }
425 },
426 error: function error(xhr, status, _error2) {
427 self.displayError(_error2);
428 }
429 });
430 };
431 reader.readAsText(file);
432 },
433 downloadJson: function downloadJson(data, filename) {
434 if (_typeof(data) === 'object') {
435 data = JSON.stringify(data);
436 }
437 var blob = new Blob([data], {
438 type: "application/json"
439 });
440 var url = URL.createObjectURL(blob);
441 var a = document.createElement('a');
442 a.href = url;
443 a.download = filename;
444 a.click();
445 URL.revokeObjectURL(url);
446 },
447 displayError: function displayError(errorMsg) {
448 var $dz = $('#merchant-restore-dropzone');
449 $dz.find('.merchant-dropzone__error p').text(errorMsg);
450 $dz.removeClass('is-loading').addClass('is-error');
451 this.hideLoadingIndicator($('.merchant-module-page-setting-fields .restore-section'));
452 setTimeout(function () {
453 $dz.removeClass('is-error');
454 // Reset file input so same file can be re-selected.
455 $dz.find('.merchant-backup-file').val('');
456 }, 3000);
457 },
458 hideError: function hideError() {
459 $('#merchant-restore-dropzone').removeClass('is-error');
460 },
461 showLoadingIndicator: function showLoadingIndicator(container) {
462 container.find('.merchant-loading-spinner').addClass('show');
463 },
464 hideLoadingIndicator: function hideLoadingIndicator(container) {
465 container.find('.merchant-loading-spinner').removeClass('show');
466 }
467 };
468 moduleBackup.init();
469
470 // Add support for toggle field inside flexible content.
471 var flexibleToggleField = {
472 init: function init(field) {
473 this.events();
474 },
475 events: function events() {
476 $(document).on('click', '.merchant-flexible-content .merchant-toggle-switch .toggle-switch-label span', function () {
477 var checkBox = $(this).closest('.merchant-toggle-switch').find('.toggle-switch-checkbox');
478 checkBox.prop('checked', !checkBox.prop('checked'));
479 }).trigger('merchant.change');
480 }
481 };
482 var dateTimePickerField = {
483 init: function init() {
484 this.initiate_datepicker();
485 this.events();
486 },
487 initiate_datepicker: function initiate_datepicker() {
488 var elements = $('.merchant-module-page-setting-field .merchant-datetime-field');
489 if (elements.length === 0) {
490 return;
491 }
492 elements.each(function (index) {
493 var input = $(this).find('input'),
494 options = {
495 locale: JSON.parse(merchant_datepicker_locale),
496 selectedDates: input.val() ? [new Date(input.val())] : [],
497 onSelect: function onSelect(_ref) {
498 var date = _ref.date,
499 formattedDate = _ref.formattedDate,
500 datepicker = _ref.datepicker;
501 if (typeof formattedDate === "undefined") {
502 // allow removing date
503 // input.val('');
504 datepicker.$el.value = '';
505 }
506 input.trigger('change.merchant');
507 input.trigger('change.merchant-datepicker', [formattedDate, input, options, index]);
508 }
509 },
510 fieldOptions = $(this).data('options');
511 // add buttons to fieldOptions
512 fieldOptions.buttons = ['clear'];
513 if (fieldOptions) {
514 if (fieldOptions.minDate !== undefined && fieldOptions.minDate === 'today') {
515 fieldOptions.minDate = new Date();
516 if (fieldOptions.timeZone !== undefined && fieldOptions.timeZone !== '') {
517 fieldOptions.minDate = new Date(fieldOptions.minDate.toLocaleString('en-US', {
518 timeZone: fieldOptions.timeZone
519 }));
520 }
521 }
522 options = Object.assign(options, fieldOptions);
523 }
524 var datepickerObj = new AirDatepicker(input.getPath(), options);
525 input.attr('readonly', true);
526 $(document).trigger('initiated.merchant-datepicker', [datepickerObj, input, options, index]);
527 });
528 },
529 events: function events() {
530 var self = this;
531 $(document).on('merchant-flexible-content-added', function () {
532 self.initiate_datepicker();
533 });
534 }
535 };
536 dateTimePickerField.init();
537
538 // Sortable.
539 var SortableField = {
540 init: function init(field) {
541 this.events();
542 },
543 events: function events() {
544 var self = this;
545 $('.merchant-sortable').each(function () {
546 var field = $(this),
547 input = field.find('.merchant-sortable-input');
548
549 // Init sortable.
550 $(field.find('ul.merchant-sortable-list').first()).sortable({
551 // Update value when we stop sorting.
552 update: function update() {
553 input.val(self.sortableGetNewVal(field)).trigger('change.merchant');
554 }
555 }).disableSelection().find('li').each(function () {
556 // Enable/disable options when we click on the eye of Thundera.
557 $(this).find('i.visibility').click(function () {
558 $(this).toggleClass('dashicons-visibility-faint').parents('li:eq(0)').toggleClass('invisible');
559 });
560 }).click(function () {
561 // Update value when click in the eye.
562 if ($(event.target).hasClass('dashicons-visibility')) {
563 input.val(self.sortableGetNewVal(field)).trigger('change.merchant');
564 }
565 });
566 });
567 },
568 sortableGetNewVal: function sortableGetNewVal(field) {
569 var items = $(field.find('li'));
570 var newVal = [];
571 _.each(items, function (item) {
572 if (!$(item).hasClass('invisible')) {
573 newVal.push($(item).data('value'));
574 }
575 });
576 return JSON.stringify(newVal);
577 }
578 };
579
580 // Initialize Sortable.
581 SortableField.init();
582 flexibleToggleField.init();
583
584 // When adding/duplicating new item, refresh sorting
585 $(document).on('merchant-flexible-content-added', function (e, $layout) {
586 var $sortableWrapper = $layout.find('.merchant-sortable-repeater-control');
587 var $sortableElement = $sortableWrapper.find('.merchant-sortable-repeater.sortable');
588 SortableRepeaterField.makeFieldsSortable($sortableElement);
589 });
590
591 // Sortable Repeater.
592 var SortableRepeaterField = {
593 init: function init(field) {
594 var self = this;
595
596 // Update the values for all our input fields and initialise the sortable repeater.
597 $('.merchant-sortable-repeater-control').each(function () {
598 // If there is an existing customizer value, populate our rows
599 var defaultValuesArray = JSON.parse($(this).find('.merchant-sortable-repeater-input').val());
600 var numRepeaterItems = defaultValuesArray.length;
601 if (numRepeaterItems > 0) {
602 // Add the first item to our existing input field
603 $(this).find('.repeater-input').val(defaultValuesArray[0]);
604
605 // Create a new row for each new value
606 if (numRepeaterItems > 1) {
607 // var i;
608 for (var i = 1; i < numRepeaterItems; ++i) {
609 self.appendRow($(this), defaultValuesArray[i]);
610 }
611 }
612 }
613
614 // Todo: remove
615 // Make our Repeater fields sortable. Doesn't work with flexible content
616 // if (!$(this).hasClass('disable-sorting')) {
617 // $(this).find('.merchant-sortable-repeater.sortable').sortable({
618 // update: function (event, ui) {
619 // self.getAllInputs($(this).parent());
620 // }
621 // });
622 // }
623 });
624
625 // Events.
626 this.events();
627 },
628 events: function events() {
629 var self = this;
630
631 // Remove item starting from its parent element
632 $(document).on('click', '.merchant-sortable-repeater.sortable .customize-control-sortable-repeater-delete', function (event) {
633 event.preventDefault();
634 $(this).parent().slideUp('fast', function () {
635 var parentContainer = $(this).parent().parent();
636 $(this).remove();
637 self.getAllInputs(parentContainer);
638 });
639 $(document).trigger('merchant-sortable-repeater-item-deleted');
640 });
641
642 // Add new item
643 $(document).on('click', '.customize-control-sortable-repeater-add', function (event) {
644 event.preventDefault();
645 self.appendRow($(this).parent());
646 self.getAllInputs($(this).parent());
647 });
648
649 // Refresh our hidden field if any fields change
650 $(document).on('change', '.merchant-sortable-repeater.sortable', function () {
651 self.getAllInputs($(this).parent());
652 });
653 $(document).on('focusout', '.merchant-sortable-repeater.sortable .repeater-input', function () {
654 self.getAllInputs($(this).parent());
655 });
656 },
657 /**
658 * Append a new row to our list of elements.
659 *
660 */
661 appendRow: function appendRow($element) {
662 var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
663 var newRow = '<div class="repeater" style="display:none"><input type="text" value="' + defaultValue + '" class="repeater-input" /><span class="dashicons dashicons-menu"></span><a class="customize-control-sortable-repeater-delete" href="#"><span class="dashicons dashicons-no-alt"></span></a></div>';
664 $element.find('.sortable').append(newRow);
665 var $newItem = $element.find('.sortable').find('.repeater:last');
666 $newItem.slideDown('slow', function () {
667 $(this).find('input').focus();
668 });
669
670 // Make Repeater fields sortable; Putting here works better with flexible content
671 this.makeFieldsSortable($element.find('.sortable'));
672 $(document).trigger('merchant-sortable-repeater-item-added', [$newItem, $element.find('.sortable')]);
673 },
674 makeFieldsSortable: function makeFieldsSortable($sortableElement) {
675 if (!$sortableElement.hasClass('disable-sorting')) {
676 $sortableElement.sortable({
677 update: function update(event, ui) {
678 SortableRepeaterField.getAllInputs($sortableElement.parent());
679 }
680 });
681 }
682 },
683 /**
684 * Get the values from the repeater input fields and add to our hidden field.
685 *
686 */
687 getAllInputs: function getAllInputs($element) {
688 var inputValues = $element.find('.repeater-input').map(function () {
689 return $(this).val();
690 }).toArray();
691
692 // Keep one empty item if all deleted.
693 if (!inputValues.length) {
694 inputValues.push('');
695 }
696
697 // Add all the values from our repeater fields to the hidden field (which is the one that actually gets saved)
698 $element.find('.merchant-sortable-repeater-input').val(JSON.stringify(inputValues));
699 // Important! Make sure to trigger change event so Customizer knows it has to save the field
700 $element.find('.merchant-sortable-repeater-input').trigger('change');
701 $element.find('.merchant-sortable-repeater-input').trigger('sortable.repeater.change');
702 }
703 };
704
705 // Initialize Sortable Repeater.
706 SortableRepeaterField.init();
707 var GroubField = {
708 init: function init() {
709 var self = this;
710 self.initAccordion();
711 self.initFlag();
712 },
713 initAccordion: function initAccordion() {
714 var self = this;
715 $('.merchant-group-field.has-accordion').each(function () {
716 var element = $(this);
717 element.accordion({
718 collapsible: true,
719 header: "> .title-area",
720 heightStyle: "content",
721 active: element.hasClass('open') ? 0 : false
722 });
723 });
724 },
725 initFlag: function initFlag() {
726 $('.merchant-group-field.has-flag').each(function () {
727 var element = $(this);
728 var field_id = element.data('id');
729 var status_field = element.find(".merchant-field-".concat(field_id, "_status select"));
730 var selected_value = status_field.val();
731 var selected_label = status_field.find('option:selected').text();
732 var status_element = element.find('.field-status');
733 status_element.removeClass('hidden active inactive').text(selected_label).addClass(selected_value);
734 });
735 }
736 };
737 var ReviewsSelector = {
738 accordion: null,
739 activePopupContainer: null,
740 // Initialize the Reviews Selector
741 init: function init() {
742 var self = this;
743 self.initAccordion();
744 self.events();
745 // Skip the flexible content hidden elements that will be cloned when initialize the sortable reviews functionality
746 self.makeSelectedReviewsSortable($('.merchant-reviews-selector').not('.layouts .merchant-reviews-selector'));
747 },
748 // Set up event listeners
749 events: function events() {
750 var self = this;
751 $(document).on('input', '.merchant-reviews-selector .products-search', self.ajaxSearch.bind(self));
752 $(document).on('change', '.merchant-reviews-selector .product-review input[type="checkbox"]', self.toggleReview.bind(self));
753 $(document).on('click', '.merchant-reviews-selector .review-photo img', self.requestReviewImages.bind(self));
754 $(document).on('click', '.review-photos-popup .overlay', self.dismissImagesGallery.bind(self));
755 $(document).on('click', '.merchant-reviews-selector .product-reviews-load-more button', self.loadMoreReviews.bind(self));
756 $(document).on('click', '.merchant-reviews-selector .popup-trigger', self.initPopUp.bind(self));
757 $(document).on('click', '.merchant-reviews-selector .popup-header .close, .merchant-reviews-selector > .overlay', self.dismissPopUp.bind(self));
758 $(document).on('click', '.merchant-reviews-selector .product-review-delete', self.deleteSelectedReview.bind(self));
759 // Listen to escape key
760 $(document).on('keyup', function (e) {
761 if (e.key === "Escape") {
762 var isGallery = self.dismissImagesGallery();
763 if (isGallery) {
764 return;
765 }
766 self.dismissPopUp(self);
767 }
768 });
769 $(document).on('merchant-flexible-content-added', function (e, layout) {
770 self.makeSelectedReviewsSortable();
771 self.initAccordion();
772 });
773 },
774 // Make selected reviews sortable
775 makeSelectedReviewsSortable: function makeSelectedReviewsSortable() {
776 var container = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $(document).find('.merchant-reviews-selector').not('.layouts .merchant-reviews-selector');
777 var self = this;
778 var selectedReviews = container.find('.selected-reviews .product-reviews');
779 selectedReviews.sortable({
780 axis: 'y',
781 cursor: 'move',
782 helper: 'original',
783 handle: '.product-review-move',
784 cancel: '',
785 placeholder: 'placeholder',
786 // Add a class for styling
787 update: function update(event, ui) {
788 var container = ui.item.closest('.merchant-reviews-selector');
789 self.saveModifications(container);
790 },
791 start: function start(event, ui) {
792 ui.placeholder.height(ui.item.height()); // Match height
793 }
794 });
795 // $('.product-reviews').sortable('refresh');
796 // console.log('selectedReviews',selectedReviews.sortable('instance'))
797 },
798 // Delete selected review
799 deleteSelectedReview: function deleteSelectedReview(e) {
800 var self = this;
801 var review = $(e.target).closest('.product-review');
802 var container = review.closest('.merchant-reviews-selector');
803 review.remove();
804 self.saveModifications(container);
805 self.updateSelectedReviewsCheckboxState(container);
806 self.countSelectedReviewsInProduct(container);
807 },
808 // Handle AJAX search for reviews
809 ajaxSearch: function ajaxSearch(e) {
810 var self = this;
811 var searchField = $(e.target);
812 var container = searchField.closest('.merchant-reviews-selector');
813 var header = container.find('.popup-header');
814
815 // check value length > 3
816 if (searchField.val().length === 0 || searchField.val().length >= 3) {
817 $.ajax({
818 url: merchant_admin_options.ajaxurl,
819 type: 'POST',
820 data: {
821 action: 'merchant_search_reviews',
822 search: searchField.val(),
823 nonce: merchant_admin_options.ajaxnonce
824 },
825 beforeSend: function beforeSend() {
826 header.addClass('loading');
827 self.destroyAccordion(container);
828 },
829 success: function success(response) {
830 if (response.success) {
831 header.removeClass('popup-error');
832 container.find('.products-search-results').html(response.data);
833 self.initAccordion(container);
834 } else {
835 header.addClass('popup-error');
836 }
837 },
838 complete: function complete() {
839 header.removeClass('loading');
840 },
841 error: function error() {
842 header.addClass('popup-error');
843 }
844 });
845 }
846 },
847 // Load more reviews
848 loadMoreReviews: function loadMoreReviews(e) {
849 var self = this;
850 var product = $(e.target).closest('.product-item');
851 var offset = product.find('.product-review').length;
852 $.ajax({
853 url: merchant_admin_options.ajaxurl,
854 type: 'POST',
855 data: {
856 action: 'merchant_load_more_reviews',
857 product_id: product.attr('data-id'),
858 offset: offset,
859 nonce: merchant_admin_options.ajaxnonce
860 },
861 beforeSend: function beforeSend() {
862 product.addClass('loading');
863 },
864 success: function success(response) {
865 if (response.success) {
866 product.find('.product-reviews .reviews-wrapper').append(response.data.reviews);
867 if (response.data.load_more === '') {
868 self.hideLoadMoreButton(product);
869 }
870 self.updateSelectedReviewsCheckboxState(product.closest('.merchant-reviews-selector'));
871 self.countSelectedReviewsInProduct(product.closest('.merchant-reviews-selector'));
872 }
873 },
874 complete: function complete() {
875 product.removeClass('loading');
876 }
877 });
878 },
879 // Hide load more button
880 hideLoadMoreButton: function hideLoadMoreButton(product) {
881 product.find('.product-reviews-load-more').remove();
882 },
883 // Initialize the popup
884 initPopUp: function initPopUp(e) {
885 var self = this;
886 var container = $(e.target).closest('.merchant-reviews-selector');
887 var popup = container.find('.selector-popup');
888 var overlay = container.find('.overlay');
889 popup.addClass('active');
890 overlay.addClass('active');
891 self.activePopupContainer = container;
892 },
893 // Dismiss the popup
894 dismissPopUp: function dismissPopUp() {
895 var self = this;
896 var container = self.activePopupContainer;
897 if (container === null) {
898 return;
899 }
900 var popup = container.find('.selector-popup');
901 var overlay = container.find('.overlay');
902 popup.removeClass('active');
903 overlay.removeClass('active');
904 self.activePopupContainer = null;
905 },
906 // Initialize the accordion
907 initAccordion: function initAccordion() {
908 var container = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $(document).find('.merchant-reviews-selector');
909 var self = this;
910 container.each(function () {
911 var elements = $(this).find('.popup-content .product-item.product-item-has-reviews');
912 elements.each(function () {
913 elements.accordion({
914 collapsible: true,
915 heightStyle: "content",
916 icons: false,
917 // Disable icons
918 active: elements.hasClass('opened') ? 0 : false,
919 activate: function activate(event, ui) {
920 // Check if a new panel is being activated
921 if (ui.newPanel.length) {
922 // Add 'opened' class to the parent product item
923 ui.newPanel.parent().addClass('opened');
924 } else {
925 // Remove 'opened' class from all product items
926 elements.removeClass('opened');
927 }
928 }
929 });
930 });
931 elements.find('a').on('click', function (e) {
932 e.stopPropagation(); // Prevent event from bubbling up to the accordion header
933 });
934 self.updateSelectedReviewsCheckboxState($(this));
935 self.countSelectedReviewsInProduct($(this));
936 });
937 },
938 // Destroy the accordion
939 destroyAccordion: function destroyAccordion(container) {
940 var elements = container.find('.selector-popup .product-item.product-item-has-reviews');
941 elements.each(function () {
942 if ($(this).hasClass('ui-accordion')) {
943 // Check if it has the accordion class
944 $(this).accordion('destroy');
945 }
946 });
947 },
948 // Toggle review selection
949 toggleReview: function toggleReview(e) {
950 var self = this;
951 var checked = $(e.target).prop('checked');
952 var reviewsFieldContainer = $(e.target).closest('.merchant-reviews-selector');
953 var review = $(e.target).closest('.product-review');
954 var reviewId = review.attr('data-id');
955 var selectedReviews = reviewsFieldContainer.find('.selected-reviews .product-reviews');
956 var selectedReview = selectedReviews.find(".product-review[data-id=\"".concat(reviewId, "\"]"));
957 if (checked) {
958 if (selectedReview.length === 0) {
959 var newReview = review.clone();
960 selectedReviews.append(newReview);
961 self.makeSelectedReviewsSortable();
962 }
963 } else {
964 selectedReview.remove();
965 }
966 setTimeout(function () {
967 self.saveModifications(reviewsFieldContainer);
968 }, 150);
969 },
970 // Save modifications to the selected reviews
971 saveModifications: function saveModifications(container) {
972 var self = this;
973 var reviewsSavedIdsField = container.find('.review-saved-ids');
974 var selectedReviews = container.find('.selected-reviews .product-reviews');
975 var reviews = selectedReviews.find('.product-review');
976 var reviewsIds = [];
977 reviews.each(function () {
978 var id = $(this).attr('data-id');
979 reviewsIds.push(id);
980 });
981 reviewsSavedIdsField.val(reviewsIds.join(','));
982 reviewsSavedIdsField.trigger('change');
983 // reviewsSavedIds;
984 self.updateSelectedReviewsCheckboxState(container);
985 self.countSelectedReviewsInProduct(container);
986 },
987 // Mark selected reviews checkbox
988 updateSelectedReviewsCheckboxState: function updateSelectedReviewsCheckboxState(container) {
989 var popupContent = container.find('.selector-popup');
990 var reviewsSavedIds = container.find('.review-saved-ids');
991 var reviews = popupContent.find('.product-review');
992 var reviewsIds = reviewsSavedIds.val().split(',');
993 reviews.each(function () {
994 var reviewId = $(this).attr('data-id');
995 var checkbox = container.find(".product-review[data-id=\"".concat(reviewId, "\"] input[type=\"checkbox\"]"));
996 if (reviewsIds.includes(reviewId)) {
997 checkbox.prop('checked', true);
998 } else {
999 checkbox.prop('checked', false);
1000 }
1001 });
1002 },
1003 // Count selected reviews in product
1004 countSelectedReviewsInProduct: function countSelectedReviewsInProduct(container) {
1005 var self = this;
1006 var products = container.find('.selector-popup .product-item');
1007 var reviewsSavedIds = container.find('.review-saved-ids');
1008 var reviewsIds = reviewsSavedIds.val().split(',');
1009 products.each(function () {
1010 var selectedReviews = 0;
1011 var product = $(this);
1012 var productReviews = product.find('.product-review');
1013 productReviews.each(function () {
1014 var review = $(this);
1015 var reviewId = review.attr('data-id');
1016 if (reviewsIds.includes(reviewId)) {
1017 selectedReviews++;
1018 }
1019 });
1020 product.find('.selected-reviews-count .counter').text(selectedReviews);
1021 });
1022 },
1023 // Request review images
1024 requestReviewImages: function requestReviewImages(e) {
1025 var reviewContainer = $(e.target).closest('.product-review');
1026 var image = $(e.target);
1027 var reviewId = reviewContainer.attr('data-id');
1028 $.ajax({
1029 url: merchant_admin_options.ajaxurl,
1030 type: 'POST',
1031 data: {
1032 action: 'merchant_get_review_images',
1033 review_id: reviewId,
1034 nonce: merchant_admin_options.ajaxnonce
1035 },
1036 beforeSend: function beforeSend() {
1037 // Show loading spinner
1038 image.addClass('loading');
1039 },
1040 success: function success(response) {
1041 if (response.success) {
1042 $('body').append(response.data);
1043 setTimeout(function () {
1044 $('body').find('.review-photos-popup').addClass('active');
1045 }, 300);
1046 }
1047 },
1048 complete: function complete() {
1049 // Hide loading spinner
1050 image.removeClass('loading');
1051 }
1052 });
1053 },
1054 // Dismiss the images gallery
1055 dismissImagesGallery: function dismissImagesGallery() {
1056 var gallery = $('body').find('.review-photos-popup');
1057 if (gallery.length) {
1058 gallery.removeClass('active');
1059 setTimeout(function () {
1060 gallery.remove();
1061 }, 300);
1062 return true;
1063 }
1064 return false;
1065 }
1066 };
1067
1068 // Flexible Content.
1069 var FlexibleContentField = {
1070 init: function init(field) {
1071 var self = this;
1072
1073 // Update the values for all our input fields and initialise the sortable repeater.
1074 $('.merchant-flexible-content-control').each(function () {
1075 var hasAccordion = $(this).hasClass('has-accordion'),
1076 $content = $(this).find('.merchant-flexible-content');
1077 var campaignId = params.get('campaign_id');
1078 var campaignIndex = 0;
1079
1080 // Find the campaignIndex based on campaignId
1081 if (campaignId) {
1082 $content.find('input.flexible-id').each(function (index) {
1083 if ($(this).val() === campaignId) {
1084 campaignIndex = index;
1085 return false;
1086 }
1087 });
1088 }
1089 if (hasAccordion) {
1090 $content.accordion({
1091 active: campaignIndex,
1092 // Open the campaign with campaign index
1093 collapsible: true,
1094 //header: "> div > .layout-header",
1095 header: function header(elem) {
1096 return elem.find('.layout__inner > .layout-header');
1097 },
1098 heightStyle: "content",
1099 beforeActivate: function beforeActivate(event, ui) {
1100 // Prevent accordion toggle when clicking the status badge.
1101 if (event.originalEvent && $(event.originalEvent.target).hasClass('layout-status')) {
1102 event.preventDefault();
1103 return;
1104 }
1105
1106 // Hydrate deferred layout before the accordion animation
1107 // so the panel opens with content already rendered.
1108 if (ui.newPanel.length) {
1109 var $layout = ui.newPanel.closest('.layout');
1110 if ($layout.attr('data-deferred') === '1') {
1111 self.hydrateLayout($layout);
1112 }
1113 }
1114 }
1115 }).sortable({
1116 axis: 'y',
1117 cursor: 'move',
1118 helper: 'original',
1119 handle: '.customize-control-flexible-content-move',
1120 stop: function stop(event, ui) {
1121 $content.trigger('merchant.sorted');
1122 self.refreshNumbers($content);
1123 $content.accordion("refresh");
1124 }
1125 });
1126 } else {
1127 $content.sortable({
1128 axis: 'y',
1129 cursor: 'move',
1130 helper: 'original',
1131 handle: '.customize-control-flexible-content-move',
1132 stop: function stop(event, ui) {
1133 $content.trigger('merchant.sorted');
1134 self.refreshNumbers($content);
1135 $content.accordion("refresh");
1136 }
1137 });
1138 }
1139 });
1140 this.updateLayoutTitle();
1141 this.updateLayoutStatus();
1142 this.updateDiscountPercentMaxVal();
1143 // Events.
1144 this.events();
1145 },
1146 updateLayoutTitle: function updateLayoutTitle() {
1147 // Update the title for all layout header.
1148 $('.merchant-flexible-content .layout').each(function () {
1149 var title = $(this).find('.layout-title[data-title-field]');
1150 if (title.length) {
1151 var input = $(this).find('.layout-body .merchant-field-' + title.data('title-field') + ' input');
1152 input.on('change keyup', function () {
1153 title.text($(this).val());
1154 });
1155 title.text(input.val());
1156 }
1157 });
1158 },
1159 /**
1160 * Resolve a status field inside a layout, returning a unified adapter
1161 * that works with select, radio, and switcher field types.
1162 *
1163 * @param {jQuery} $layout The .layout element.
1164 * @param {string} fieldId The field identifier (e.g. 'campaign_status').
1165 * @returns {object|null} Adapter with getValue, getLabel, getOptions, setNext, onChange — or null.
1166 */
1167 resolveStatusField: function resolveStatusField($layout, fieldId) {
1168 var $wrapper = $layout.find('.layout-body .merchant-field-' + fieldId);
1169 if (!$wrapper.length) return null;
1170 var $select = $wrapper.find('select');
1171 var $radios = $wrapper.find('input[type="radio"]');
1172 var $checkbox = $wrapper.find('input[type="checkbox"]');
1173
1174 // Select field adapter.
1175 if ($select.length) {
1176 return {
1177 getValue: function getValue() {
1178 return $select.val() || 'active';
1179 },
1180 getLabel: function getLabel() {
1181 return $select.find('option:selected').text() || this.getValue();
1182 },
1183 getOptions: function getOptions() {
1184 return $select.find('option').map(function () {
1185 return $(this).val();
1186 }).get();
1187 },
1188 setNext: function setNext() {
1189 var opts = $select.find('option');
1190 var idx = opts.index(opts.filter(':selected'));
1191 var next = (idx + 1) % opts.length;
1192 $select.val(opts.eq(next).val()).trigger('change');
1193 },
1194 onChange: function onChange(fn) {
1195 $select.off('change.statusBadge').on('change.statusBadge', fn);
1196 }
1197 };
1198 }
1199
1200 // Radio field adapter.
1201 if ($radios.length) {
1202 return {
1203 getValue: function getValue() {
1204 return $radios.filter(':checked').val() || 'active';
1205 },
1206 getLabel: function getLabel() {
1207 var $checked = $radios.filter(':checked');
1208 var $label = $wrapper.find('label[for="' + $checked.attr('id') + '"]');
1209 return $label.length ? $label.text() : this.getValue();
1210 },
1211 getOptions: function getOptions() {
1212 return $radios.map(function () {
1213 return $(this).val();
1214 }).get();
1215 },
1216 setNext: function setNext() {
1217 var vals = this.getOptions();
1218 var idx = vals.indexOf(this.getValue());
1219 var next = (idx + 1) % vals.length;
1220 $radios.filter('[value="' + vals[next] + '"]').prop('checked', true).trigger('change');
1221 },
1222 onChange: function onChange(fn) {
1223 $radios.off('change.statusBadge').on('change.statusBadge', fn);
1224 }
1225 };
1226 }
1227
1228 // Switcher (checkbox) field adapter — on/off.
1229 if ($checkbox.length) {
1230 return {
1231 getValue: function getValue() {
1232 return $checkbox.is(':checked') ? 'active' : 'inactive';
1233 },
1234 getLabel: function getLabel() {
1235 return $checkbox.is(':checked') ? 'Active' : 'Inactive';
1236 },
1237 getOptions: function getOptions() {
1238 return ['active', 'inactive'];
1239 },
1240 setNext: function setNext() {
1241 $checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change');
1242 },
1243 onChange: function onChange(fn) {
1244 $checkbox.off('change.statusBadge').on('change.statusBadge', fn);
1245 }
1246 };
1247 }
1248 return null;
1249 },
1250 updateLayoutStatus: function updateLayoutStatus() {
1251 var self = this;
1252
1253 // Sync status badge on each layout header with the field value.
1254 $('.merchant-flexible-content .layout').each(function () {
1255 var $badge = $(this).find('.layout-header .layout-status[data-status-field]');
1256 if (!$badge.length) return;
1257 var fieldId = $badge.data('status-field');
1258 var adapter = self.resolveStatusField($(this), fieldId);
1259 if (!adapter) return;
1260 function syncBadge() {
1261 var val = adapter.getValue();
1262 var label = adapter.getLabel();
1263 $badge.removeClass(function (i, cls) {
1264 return (cls.match(/layout-status--\S+/g) || []).join(' ');
1265 }).addClass('layout-status--' + val).text(label.trim());
1266 }
1267 adapter.onChange(syncBadge);
1268 syncBadge();
1269 });
1270
1271 // Delegated click handler for badge toggle — works on all rows including deferred.
1272 $(document).off('click.statusToggle').on('click.statusToggle', '.layout-status[data-status-field]', function (e) {
1273 e.stopPropagation();
1274 e.preventDefault();
1275 var $badge = $(this);
1276 var fieldId = $badge.data('status-field');
1277 var $layout = $badge.closest('.layout');
1278 var adapter = self.resolveStatusField($layout, fieldId);
1279 if (adapter) {
1280 // Hydrated row: toggle via the field adapter.
1281 adapter.setNext();
1282 } else {
1283 // Deferred row: cycle through options stored in data-status-options,
1284 // falling back to active/inactive.
1285 var optionsAttr = $badge.attr('data-status-options');
1286 var options = optionsAttr ? optionsAttr.split(',') : ['active', 'inactive'];
1287 var currentVal = $badge.attr('class').match(/layout-status--(\S+)/);
1288 currentVal = currentVal ? currentVal[1] : options[0];
1289 var idx = options.indexOf(currentVal);
1290 var nextIdx = (idx + 1) % options.length;
1291 var newVal = options[nextIdx];
1292 var newLabel = newVal.charAt(0).toUpperCase() + newVal.slice(1);
1293 $badge.removeClass('layout-status--' + currentVal).addClass('layout-status--' + newVal).text(newLabel);
1294
1295 // Update data-fields-json so the value persists when hydrated.
1296 var jsonAttr = $layout.attr('data-fields-json');
1297 if (jsonAttr) {
1298 try {
1299 var data = JSON.parse(jsonAttr);
1300 if (!data.values) data.values = {};
1301 data.values[fieldId] = newVal;
1302 $layout.attr('data-fields-json', JSON.stringify(data));
1303 } catch (ex) {/* ignore parse errors */}
1304 }
1305
1306 // Mark the form as changed.
1307 $('.merchant-module-page-content').trigger('change.merchant');
1308 }
1309 });
1310 },
1311 updateDiscountPercentMaxVal: function updateDiscountPercentMaxVal() {
1312 $('.merchant-flexible-content .layout').each(function () {
1313 var $layout = $(this);
1314 var $discountType = $layout.find('.merchant-module-page-setting-field[data-id="discount_type"]');
1315 var $discountVal = $layout.find('.merchant-module-page-setting-field[data-id="discount_value"]');
1316 $discountVal = $discountVal.length ? $discountVal : $layout.find('.merchant-module-page-setting-field[data-id="discount"]');
1317 $discountVal = $discountVal.length ? $discountVal : $layout.find('.merchant-module-page-setting-field[data-id="discount_amount"]');
1318 var checkedValue = $discountType.find('input:checked').val();
1319
1320 // Set/Remove max value based on the discount type.
1321 checkedValue === 'percentage_discount' || checkedValue === 'percentage' ? $discountVal.find('input').attr('max', 100) : $discountVal.find('input').removeAttr('max');
1322 });
1323 $('.merchant-module-page-setting-fields');
1324 },
1325 generateUUID: function generateUUID() {
1326 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
1327 var r = Math.random() * 16 | 0;
1328 var v = c === 'x' ? r : r & 0x3 | 0x8;
1329 return v.toString(16);
1330 });
1331 },
1332 events: function events() {
1333 var self = this;
1334 $(document).on('change', '.merchant-module-page-setting-field[data-id="discount_type"] input', function () {
1335 self.updateDiscountPercentMaxVal();
1336 });
1337
1338 // Update "selected" attribute on select. It's Required for duplicating layouts.
1339 $(document).on('change', 'select', function () {
1340 var selectedValue = $(this).val();
1341 var isMultiple = $(this).prop('multiple');
1342 $(this).find('option').each(function () {
1343 var currentValue = $(this).val();
1344 var isSelected = isMultiple ? selectedValue.includes(currentValue) : currentValue === selectedValue;
1345 $(this).attr('selected', isSelected);
1346 });
1347 });
1348 $('.customize-control-flexible-content-add-button').on('click', function (event) {
1349 event.preventDefault();
1350 event.stopImmediatePropagation();
1351 if ($(this).parent().find('.customize-control-flexible-content-add-list a').length === 1) {
1352 // If there is only one layout, trigger click on it.
1353 $(this).parent().find('.customize-control-flexible-content-add-list a').trigger('click');
1354 return;
1355 }
1356 $(this).parent().find('.customize-control-flexible-content-add-list').toggleClass('active');
1357 });
1358
1359 // Add new item
1360 $(document).on('click', '.customize-control-flexible-content-add', function (event) {
1361 event.preventDefault();
1362 event.stopImmediatePropagation();
1363 var $field = $('.merchant-flexible-content-control[data-id=' + $(this).data('id') + ']');
1364 var $layouts = $field.find('.layouts');
1365 var $selected = $(this).data('layout');
1366 var $layout = $layouts.find('.layout[data-type=' + $selected + ']').clone(true);
1367 var $content = $field.find('.merchant-flexible-content');
1368 var $items = $content.find('.layout');
1369 var uuid = self.generateUUID();
1370 $layout.find('input, select, textarea').each(function () {
1371 if ($(this).data('name')) {
1372 $(this).attr('name', $(this).data('name').replace('0', $items.length));
1373 }
1374 if ($(this).is(':checkbox, :radio') && $(this).attr('checked')) {
1375 $(this).prop('checked', true);
1376 }
1377 });
1378 $layout.attr('data-layout-id', uuid);
1379 $layout.find('.layout-count').text($items.length + 1);
1380 $layout.find('.flexible-id').val(uuid);
1381 $content.append($layout);
1382 $content.removeClass('empty');
1383 $(this).parent().removeClass('active');
1384 if ($layout.find('.merchant-module-page-setting-field-upload').length) {
1385 initUploadField($layout.find('.merchant-module-page-setting-field-upload'));
1386 }
1387 if ($layout.find('.merchant-module-page-setting-field-select_ajax').length) {
1388 initSelectAjax($layout.find('.merchant-module-page-setting-field-select_ajax'));
1389 }
1390 var parentDiv = $(this).closest('.merchant-flexible-content-control'),
1391 hasAccordion = parentDiv.hasClass('has-accordion');
1392 if (hasAccordion) {
1393 parentDiv.find('.merchant-flexible-content').accordion("refresh");
1394 // Expand the accordion last added
1395 parentDiv.find('.merchant-flexible-content').accordion("option", "active", -1);
1396 }
1397 GroubField.init();
1398 $(document).trigger('merchant-flexible-content-added', [$layout]);
1399 initMerchantRange();
1400 self.updateLayoutTitle();
1401 self.updateLayoutStatus();
1402 self.updateDiscountPercentMaxVal();
1403 $('.merchant-module-page-content').trigger('change.merchant');
1404 });
1405
1406 // Duplicate item
1407 $(document).on('click', '.customize-control-flexible-content-duplicate', function (event) {
1408 event.preventDefault();
1409 event.stopImmediatePropagation();
1410 var $duplicateBtn = $(this);
1411 var $flexibleContentWrapper = $duplicateBtn.closest('.merchant-flexible-content-control[data-id=' + $duplicateBtn.data('id') + ']');
1412 var $flexibleContent = $flexibleContentWrapper === null || $flexibleContentWrapper === void 0 ? void 0 : $flexibleContentWrapper.find('.merchant-flexible-content');
1413 if (!$flexibleContentWrapper.length || !$flexibleContent.length) {
1414 return;
1415 }
1416 var $sourceLayout = $duplicateBtn.closest('.layout');
1417 if (!$sourceLayout.length) {
1418 return;
1419 }
1420 $sourceLayout.find('.layout-actions__inner').hide();
1421
1422 // Clone the layout without data & events.
1423 var $clonedLayout = $sourceLayout.clone();
1424 var $items = $flexibleContent.find('.layout');
1425 var index = $sourceLayout.find('.layout-count').text();
1426 var uuid = self.generateUUID();
1427 $clonedLayout.attr('data-layout-id', uuid);
1428 $clonedLayout.find('.flexible-id').val(uuid);
1429 $clonedLayout.find('input, select, textarea').each(function () {
1430 var $input = $(this);
1431 var inputName = $input.attr('name');
1432 if (inputName) {
1433 var prefix = inputName.split('[')[0];
1434 var indexPart = inputName.match(/\[(.*?)\]/g);
1435 if (indexPart && indexPart.length > 1) {
1436 indexPart[1] = '[' + index + ']';
1437 var newName = "".concat(prefix).concat(indexPart.join(''));
1438 $input.attr('name', newName);
1439 }
1440 }
1441 });
1442
1443 // Find select2, Remove and Re-init again. select.select2('destroy') doesn't work.
1444 $clonedLayout.find('select').each(function () {
1445 if ($(this).hasClass('select2-hidden-accessible')) {
1446 $(this).removeClass('select2-hidden-accessible').removeAttr('data-live-search').removeAttr('data-select2-id').removeAttr('aria-hidden').removeAttr('tabindex');
1447
1448 // Remove the existing dropdown
1449 $(this).nextAll('.select2-container').remove();
1450
1451 // Re-init
1452 $(this).select2();
1453 }
1454 });
1455
1456 // Removing copied style to make the accordion work properly.
1457 $clonedLayout.find('.layout-body').removeAttr('style');
1458
1459 // Append the cloned layout right after the source one with fadeIn effect.
1460 $clonedLayout.hide();
1461 $clonedLayout.insertAfter($sourceLayout);
1462 $clonedLayout.fadeIn();
1463 if ($clonedLayout.find('.merchant-module-page-setting-field-upload').length) {
1464 initUploadField($clonedLayout.find('.merchant-module-page-setting-field-upload'));
1465 }
1466 if ($clonedLayout.find('.merchant-module-page-setting-field-select_ajax').length) {
1467 initSelectAjax($clonedLayout.find('.merchant-module-page-setting-field-select_ajax'));
1468 }
1469 self.refreshNumbers($flexibleContent);
1470 $(document).trigger('merchant-flexible-content-added', [$clonedLayout]);
1471 if ($flexibleContentWrapper.hasClass('has-accordion')) {
1472 $flexibleContent.accordion('refresh');
1473 }
1474 self.updateLayoutTitle();
1475 self.updateLayoutStatus();
1476 GroubField.init();
1477 $('.merchant-module-page-content').trigger('change.merchant');
1478 });
1479
1480 // Delete item
1481 $(document).on('click', '.customize-control-flexible-content-delete', function (event) {
1482 event.preventDefault();
1483 var $item = $(this).closest('.layout');
1484 var $content = $item.parent();
1485 $item.remove();
1486 if ($content.find('.layout').length === 0) {
1487 $content.addClass('empty');
1488 }
1489 self.refreshNumbers($content);
1490 $(document).trigger('merchant-flexible-content-deleted', [$item]);
1491 var parentDiv = $(this).closest('.merchant-flexible-content-control'),
1492 hasAccordion = parentDiv.hasClass('has-accordion');
1493 if (hasAccordion) {
1494 parentDiv.find('.merchant-flexible-content').accordion("refresh");
1495 }
1496 $('.merchant-module-page-content').trigger('change.merchant');
1497 });
1498
1499 // Toggle Actions(delete/duplicate)
1500 $(document).on('click', '.layout-actions__toggle', function (e) {
1501 e.preventDefault();
1502
1503 // Hide other opened elements
1504 hideOtherActions($(this).closest('.layout'));
1505
1506 // Toggle the current element
1507 $(this).closest('.layout-actions').find('.layout-actions__inner').stop().slideToggle(300);
1508 });
1509
1510 // Hide Actions when collapse/open
1511 $(document).on('click', '.layout-header', function () {
1512 hideOtherActions($(this).closest('.layout'));
1513 });
1514 $(document).on('merchant-flexible-content-added', function (e, $layout) {
1515 hideOtherActions($layout);
1516 });
1517 function hideOtherActions($layout) {
1518 if ($layout && $layout.length) {
1519 $layout.siblings().find('.layout-actions__inner').slideUp(300);
1520 }
1521 }
1522
1523 // Dismiss actions menu on click outside or Escape.
1524 $(document).on('click', function (e) {
1525 if (!$(e.target).closest('.layout-actions').length) {
1526 $('.layout-actions__inner').slideUp(300);
1527 }
1528 });
1529 $(document).on('keydown', function (e) {
1530 if (e.key === 'Escape') {
1531 $('.layout-actions__inner').slideUp(300);
1532 }
1533 });
1534 },
1535 /**
1536 * Hydrate a deferred layout row.
1537 *
1538 * Clones the hidden template for the layout type, populates field values
1539 * from the data-fields-json attribute, initialises all widgets, and
1540 * replaces the empty layout-body content with the fully rendered fields.
1541 *
1542 * @param {jQuery} $layout The .layout element with data-deferred="1".
1543 */
1544 hydrateLayout: function hydrateLayout($layout) {
1545 var jsonStr = $layout.attr('data-fields-json');
1546 if (!jsonStr) {
1547 return;
1548 }
1549 var data;
1550 try {
1551 data = JSON.parse(jsonStr);
1552 } catch (e) {
1553 return;
1554 }
1555 var layoutType = $layout.attr('data-type');
1556 var $control = $layout.closest('.merchant-flexible-content-control');
1557 var $template = $control.find('.layouts .layout[data-type="' + layoutType + '"]');
1558 if (!$template.length) {
1559 return;
1560 }
1561
1562 // Clone the template's layout-body content (the rendered fields).
1563 var $clonedBody = $template.find('.layout-body').clone();
1564
1565 // Determine the correct row index from layout-count.
1566 var rowIndex = parseInt($layout.find('.layout-count').text(), 10) - 1;
1567 var fieldId = $control.attr('data-id');
1568
1569 // Fix name attributes: replace data-name with name, and index [0] with [rowIndex].
1570 $clonedBody.find('input, select, textarea').each(function () {
1571 var dataName = $(this).attr('data-name');
1572 if (dataName) {
1573 $(this).attr('name', dataName.replace('[0]', '[' + rowIndex + ']'));
1574 $(this).removeAttr('data-name');
1575 }
1576 });
1577
1578 // Populate field values from the resolved data.
1579 this.populateFieldValues($clonedBody, data, fieldId, rowIndex);
1580
1581 // Sync color picker preview boxes to reflect populated values.
1582 $clonedBody.find('.merchant-color').each(function () {
1583 var colorVal = $(this).find('.merchant-color-input').val();
1584 if (colorVal) {
1585 $(this).find('.merchant-color-picker').css('background-color', colorVal);
1586 }
1587 });
1588
1589 // Sync range slider values from their sibling number inputs.
1590 // populateFieldValues only sets the number input (it has the name attribute);
1591 // the range slider (no name) still holds the template default.
1592 $clonedBody.find('.merchant-range').each(function () {
1593 var numVal = $(this).find('.merchant-range-number-input').val();
1594 if (numVal !== undefined && numVal !== '') {
1595 $(this).find('.merchant-range-input').val(numVal);
1596 }
1597 });
1598
1599 // Swap the content of the layout-body (not the element itself)
1600 // to preserve jQuery UI accordion's internal panel reference.
1601 var $existingBody = $layout.find('.layout-body');
1602 $existingBody.empty().append($clonedBody.children());
1603
1604 // Initialise widgets within the hydrated layout.
1605 if ($existingBody.find('.merchant-module-page-setting-field-upload').length) {
1606 initUploadField($existingBody.find('.merchant-module-page-setting-field-upload'));
1607 }
1608 if ($existingBody.find('.merchant-module-page-setting-field-select_ajax').length) {
1609 initSelectAjax($existingBody.find('.merchant-module-page-setting-field-select_ajax'));
1610 }
1611
1612 // Trigger events to re-initialise fields_group, color pickers, conditions, etc.
1613 $(document).trigger('merchant-flexible-content-added', [$layout]);
1614 GroubField.init();
1615 initMerchantRange();
1616
1617 // Init layout title binding.
1618 var $title = $layout.find('.layout-title[data-title-field]');
1619 if ($title.length) {
1620 var titleFieldId = $title.attr('data-title-field');
1621 var $titleInput = $layout.find('.layout-body .merchant-field-' + titleFieldId + ' input');
1622 $titleInput.on('change keyup', function () {
1623 $title.text($(this).val());
1624 });
1625 }
1626
1627 // Init status badge binding for hydrated row.
1628 var $badge = $layout.find('.layout-header .layout-status[data-status-field]');
1629 if ($badge.length) {
1630 var statusFieldId = $badge.data('status-field');
1631 var statusAdapter = this.resolveStatusField($layout, statusFieldId);
1632 if (statusAdapter) {
1633 var syncHydratedBadge = function syncHydratedBadge() {
1634 var val = statusAdapter.getValue();
1635 var label = statusAdapter.getLabel();
1636 $badge.removeClass(function (i, cls) {
1637 return (cls.match(/layout-status--\S+/g) || []).join(' ');
1638 }).addClass('layout-status--' + val).text(label.trim());
1639 };
1640 statusAdapter.onChange(syncHydratedBadge);
1641 syncHydratedBadge();
1642 }
1643 }
1644
1645 // Init discount max val.
1646 this.updateDiscountPercentMaxVal();
1647
1648 // Remove the deferred flag and clean up.
1649 $layout.removeAttr('data-deferred');
1650 $layout.removeAttr('data-fields-json');
1651
1652 // Refresh accordion to re-sync panel references after DOM change.
1653 var $content = $control.find('.merchant-flexible-content');
1654 if ($content.data('ui-accordion')) {
1655 $content.accordion('refresh');
1656 }
1657
1658 // Trigger condition checks for the newly hydrated panel.
1659 $(document).trigger('merchant-admin-check-fields');
1660 $(document).trigger('merchant-admin-check-color-fields');
1661 },
1662 /**
1663 * Populate field values from deferred data into a cloned template body.
1664 *
1665 * @param {jQuery} $body The cloned .layout-body element.
1666 * @param {Object} data The parsed data-fields-json object.
1667 * @param {string} fieldId The flexible content field ID.
1668 * @param {number} rowIndex The row index for name attribute construction.
1669 */
1670 populateFieldValues: function populateFieldValues($body, data, fieldId, rowIndex) {
1671 var values = data.values || {};
1672 var selectOptions = data.select_options || {};
1673 var productOptions = data.product_options || {};
1674 var reviewOptions = data.review_options || {};
1675
1676 // Populate simple field values.
1677 $body.find('input, select, textarea').each(function () {
1678 var $el = $(this);
1679 var name = $el.attr('name') || '';
1680
1681 // Handle checkbox_multiple: name ends with [] (e.g. merchant[fc][0][show_pages][]).
1682 var arrayMatch = name.match(/\[([^\]]+)\]\[\]$/);
1683 if (arrayMatch) {
1684 var arrayKey = arrayMatch[1];
1685 var arrVal = values[arrayKey];
1686 if (Array.isArray(arrVal) && $el.is(':checkbox')) {
1687 $el.prop('checked', arrVal.indexOf($el.val()) !== -1);
1688 }
1689 return;
1690 }
1691
1692 // Extract the field key from name="merchant[fieldId][rowIndex][fieldKey]".
1693 var match = name.match(/\[([^\]]+)\]$/);
1694 if (!match) {
1695 return;
1696 }
1697 var fieldKey = match[1];
1698 var val = values[fieldKey];
1699 if (val === undefined || val === null) {
1700 return;
1701 }
1702 if ($el.is(':checkbox') || $el.is(':radio')) {
1703 // For checkboxes/radios, check if this input's value matches.
1704 if ($el.val() === String(val) || val === '1' && $el.is(':checkbox')) {
1705 $el.prop('checked', true);
1706 }
1707 } else if ($el.is('select')) {
1708 $el.val(val);
1709 } else {
1710 $el.val(val);
1711 }
1712 });
1713
1714 // Populate select_ajax pre-selected options.
1715 $.each(selectOptions, function (selectFieldId, options) {
1716 var $select = $body.find('[data-id="' + selectFieldId + '"] select');
1717 if (!$select.length) {
1718 return;
1719 }
1720
1721 // Clear any existing options and add resolved ones.
1722 $select.empty();
1723 $.each(options, function (_, opt) {
1724 $select.append($('<option>').val(opt.id).text(opt.text).prop('selected', true));
1725 });
1726 });
1727
1728 // Populate products_selector pre-selected products.
1729 $.each(productOptions, function (prodFieldId, products) {
1730 var $container = $body.find('[data-id="' + prodFieldId + '"] .merchant-products-search-container');
1731 if (!$container.length) {
1732 return;
1733 }
1734 var $preview = $container.find('.merchant-selected-products-preview ul');
1735 var ids = [];
1736 $preview.empty();
1737 $.each(products, function (_, product) {
1738 ids.push(product.id);
1739
1740 // Build <li> matching the structure from Merchant_Admin_Ajax::product_data_li().
1741 var $li = $('<li>').addClass('product-item').attr('data-id', product.id).attr('data-name', product.title);
1742 if (product.image) {
1743 $li.append($('<span>').addClass('img').append($('<img>').attr('src', product.image).attr('alt', product.title).attr('width', 30).attr('height', 30)));
1744 }
1745 $li.append($('<span>').addClass('data').append($('<span>').addClass('name').text(product.title), $('<span>').addClass('info').html(product.price)));
1746 if (product.type || product.id) {
1747 var typeHtml = (product.type || '') + '<br>#' + product.id;
1748 var $typeSpan = $('<span>').addClass('type');
1749 if (product.edit_url) {
1750 $typeSpan.append($('<a>').attr('href', product.edit_url).attr('target', '_blank').html(typeHtml));
1751 } else {
1752 $typeSpan.html(typeHtml);
1753 }
1754 $li.append($typeSpan);
1755 }
1756 $li.append($('<span>').addClass('remove hint--left').attr('aria-label', 'Remove').html('&times;'));
1757 $preview.append($li);
1758 });
1759 $container.find('.merchant-selected-products').val(ids.join(','));
1760 });
1761
1762 // Populate reviews_selector pre-selected reviews.
1763 $.each(reviewOptions, function (reviewFieldId, reviews) {
1764 var $container = $body.find('[data-id="' + reviewFieldId + '"] .merchant-reviews-selector');
1765 if (!$container.length) {
1766 return;
1767 }
1768 var $selectedList = $container.find('.selected-reviews .product-reviews');
1769 var ids = [];
1770 $selectedList.empty();
1771 $.each(reviews, function (_, review) {
1772 ids.push(review.id);
1773 var $reviewEl = $('<div>').addClass('product-review').attr('data-review-id', review.id);
1774 $reviewEl.append($('<span>').addClass('review-author').text(review.author));
1775 $reviewEl.append($('<span>').addClass('review-content').text(review.content));
1776 $reviewEl.append($('<span>').addClass('review-rating').text('�
1777 '.repeat(parseInt(review.rating) || 0)));
1778 $reviewEl.append($('<span>').addClass('review-product').text(review.product_name));
1779 $reviewEl.append($('<span>').addClass('product-review-delete').html('&times;'));
1780 $reviewEl.append($('<span>').addClass('product-review-move').html('⋮⋮'));
1781 $selectedList.append($reviewEl);
1782 });
1783 $container.find('.merchant-selected-reviews').val(ids.join(','));
1784 });
1785
1786 // Handle fields_group and compound (e.g. hook_select) nested values.
1787 // Recursively walks the value tree to find and set inputs
1788 // at any nesting depth (e.g. fields_group → hook_select → hook_name).
1789 var _setNestedValues = function setNestedValues(obj, namePrefix) {
1790 $.each(obj, function (key, val) {
1791 if (_typeof(val) === 'object' && val !== null && !Array.isArray(val)) {
1792 // Recurse into nested objects.
1793 _setNestedValues(val, namePrefix + '[' + key + ']');
1794 } else if (Array.isArray(val)) {
1795 // checkbox_multiple inside a nested object: check boxes whose value is in the array.
1796 $body.find('[name="' + namePrefix + '[' + key + '][]"]').each(function () {
1797 if ($(this).is(':checkbox')) {
1798 $(this).prop('checked', val.indexOf($(this).val()) !== -1);
1799 }
1800 });
1801 } else {
1802 var $field = $body.find('[name="' + namePrefix + '[' + key + ']"]');
1803 if ($field.length) {
1804 if ($field.is(':checkbox') || $field.is(':radio')) {
1805 if ($field.val() === String(val) || val === '1' && $field.is(':checkbox')) {
1806 $field.prop('checked', true);
1807 }
1808 } else {
1809 $field.val(val);
1810 }
1811 }
1812 }
1813 });
1814 };
1815 $.each(values, function (key, val) {
1816 if (_typeof(val) === 'object' && val !== null && !Array.isArray(val)) {
1817 _setNestedValues(val, 'merchant[' + fieldId + '][' + rowIndex + '][' + key + ']');
1818 }
1819 });
1820 },
1821 refreshNumbers: function refreshNumbers($content) {
1822 $content.find('.layout').each(function (index) {
1823 var $count = $(this).find('.layout-count').text();
1824 var $inputIndex = parseInt($count) - 1;
1825 $(this).find('.layout-count').text(index + 1);
1826 $(this).find('input, select, textarea').each(function () {
1827 if ($(this).attr('name')) {
1828 $(this).attr('name', $(this).attr('name').replace('[' + $inputIndex + ']', '[*refreshed*' + index + ']'));
1829 }
1830 });
1831 });
1832 $content.find('.layout').each(function (index) {
1833 $(this).find('input, select, textarea').each(function () {
1834 // We've added *refreshed* to the attribute name in the prior loop as refreshing the numbers in the attribute can cause
1835 // checked boxes to be unchecked due to similar attribute names during the change while sorting, within this loop we remove them
1836 var nameAttr = $(this).attr('name');
1837 if (nameAttr) {
1838 // Check if name attribute exists
1839 $(this).attr('name', nameAttr.replace('*refreshed*', ''));
1840 }
1841 });
1842 });
1843 $content.parent().find('input').trigger('change.merchant');
1844 }
1845 };
1846
1847 // Initialize Flexible Content.
1848 FlexibleContentField.init();
1849 GroubField.init();
1850 ReviewsSelector.init();
1851
1852 // Products selector.
1853 // Handle keyup event for the search input
1854 var debounceTimer;
1855 $(document).on('keyup', '.merchant-module-page-setting-field-products_selector .merchant-search-field', function () {
1856 clearTimeout(debounceTimer);
1857 var categories = [];
1858 var $excluded = $(this).closest('[data-id="excluded_products"]');
1859 if ($excluded.length) {
1860 var $layout = $(this).closest('.layout');
1861 var rules = $layout.find('.merchant-field-rules_to_apply select').val() || $layout.find('.merchant-field-rules_to_display select').val() || $layout.find('.merchant-field-display_rules select').val();
1862 if (rules === 'categories' || rules === 'by_category') {
1863 categories = $layout.find('.merchant-field-category_slugs select').val() || $layout.find('.merchant-field-product_cats select').val();
1864 }
1865 }
1866 var parent = $(this).closest('.merchant-products-search-container');
1867 if ($(this).val() !== '') {
1868 parent.find('.merchant-searching').addClass('active');
1869 var data = {
1870 action: 'merchant_admin_products_search',
1871 nonce: merchant_admin_options.ajaxnonce,
1872 keyword: $(this).val(),
1873 product_types: $(this).data('allowed-types'),
1874 ids: parent.find('.merchant-selected-products').val(),
1875 categories: categories
1876 };
1877 debounceTimer = setTimeout(function () {
1878 $.post(merchant_admin_options.ajaxurl, data, function (response) {
1879 var results = parent.find('.merchant-selections-products-preview');
1880 results.show();
1881 results.html(response);
1882 parent.find('.merchant-searching').removeClass('active');
1883 });
1884 }, 250);
1885 } else {
1886 parent.find('.merchant-selections-products-preview').html('').hide();
1887 }
1888 });
1889
1890 // Products selector: dismiss search results on click outside or Escape.
1891 $(document).on('click touch', function (e) {
1892 if (!$(e.target).closest('.merchant-products-search-container').length) {
1893 $('.merchant-selections-products-preview').html('').hide();
1894 $('.merchant-search-field').val('');
1895 }
1896 });
1897 $(document).on('keydown', '.merchant-search-field', function (e) {
1898 if (e.key === 'Escape') {
1899 var parent = $(this).closest('.merchant-products-search-container');
1900 parent.find('.merchant-selections-products-preview').html('').hide();
1901 $(this).val('').blur();
1902 }
1903 });
1904
1905 // Products selector.
1906 // Handle click/touch event for the search results
1907 $(document).on('click touch', '.merchant-module-page-setting-field-products_selector .merchant-selections-products-preview li', function () {
1908 var parent = $(this).closest('.merchant-products-search-container'),
1909 valueField = parent.find('.merchant-selected-products'),
1910 oldValue = valueField.val(),
1911 multiple = parent.data('multiple') === 'multiple';
1912 if (parent.find('.merchant-selected-products-preview ul li').length > 0 && !multiple) {
1913 // replace the first item
1914 parent.find('.merchant-selected-products-preview ul li').remove();
1915 valueField.val('').change();
1916 }
1917 $(this).children('.remove').attr('aria-label', 'Remove').html('×');
1918 parent.find('.merchant-selected-products-preview ul').append($(this));
1919 parent.find('.merchant-selections-products-preview').html('').hide();
1920 parent.find('.merchant-search-field').val('').change();
1921 if (oldValue === '') {
1922 valueField.val($(this).data('id'));
1923 } else {
1924 if (!multiple) {
1925 valueField.val($(this).data('id')).change();
1926 } else {
1927 var newValue = oldValue.split(',');
1928 newValue.push($(this).data('id'));
1929 valueField.val(newValue.join(',')).change();
1930 }
1931 }
1932 });
1933
1934 // Products selector.
1935 // Handle click/touch event for the remove button.
1936 $(document).on('click touch', '.merchant-selected-products-preview .remove', function () {
1937 var removeButton = $(this);
1938 var parent = removeButton.closest('.merchant-products-search-container'),
1939 valueField = parent.find('.merchant-selected-products'),
1940 id = removeButton.parent().data('id');
1941 removeButton.parent().remove();
1942 var currentValue = valueField.val().split(',');
1943 if (currentValue.length > 0) {
1944 for (var key in currentValue) {
1945 if (parseInt(currentValue[key]) === parseInt(id)) {
1946 currentValue.splice(key, 1);
1947 }
1948 }
1949 }
1950 valueField.val(currentValue.join(',')).change();
1951 valueField.trigger('change.merchant');
1952 });
1953 $(document).on('merchant-admin-check-fields merchant-flexible-content-added', function () {
1954 $('.merchant-module-page-setting-field').each(function () {
1955 var $field = $(this);
1956 if ($field.data('condition') && $field.data('condition').length) {
1957 var condition = $field.data('condition');
1958 var $target = $(this).closest('.layout-body').find('input[name*="' + condition[0] + '"],select[name*="' + condition[0] + '"]');
1959 if (!$target.length) {
1960 $target = $('input[name="merchant[' + condition[0] + ']"],select[name="merchant[' + condition[0] + ']"]');
1961 }
1962 if ($target.length) {
1963 var passed = false;
1964 switch (condition[1]) {
1965 case '==':
1966 if ($target.attr('type') === 'radio' || $target.attr('type') === 'checkbox') {
1967 var checked = $target.parent().find('input:checked');
1968 if (checked.length && checked.val() === condition[2]) {
1969 passed = true;
1970 }
1971 }
1972 if ($target.is('select') && $target.val() == condition[2]) {
1973 passed = true;
1974 }
1975 break;
1976 case 'any':
1977 if ($target.attr('type') === 'radio' || $target.attr('type') === 'checkbox') {
1978 var _checked = $target.parent().find('input:checked');
1979 if (_checked.length && condition[2].split('|').includes(_checked.val())) {
1980 passed = true;
1981 }
1982 }
1983 if ($target.is('select') && condition[2].split('|').includes($target.val())) {
1984 passed = true;
1985 }
1986 break;
1987 }
1988 if (passed) {
1989 $field.removeClass('merchant-hide').addClass('merchant-show');
1990 } else {
1991 $field.removeClass('merchant-show').addClass('merchant-hide');
1992 }
1993 }
1994 }
1995 });
1996 }).trigger('merchant.change');
1997 $(document).on('merchant-admin-check-fields merchant-flexible-content-added change keyup', function () {
1998 $(document).find('.merchant-module-page-setting-field').each(function () {
1999 var $field = $(this);
2000 if ($field.data('conditions')) {
2001 var conditions = $field.data('conditions'),
2002 passed = evaluateConditions(conditions, $field);
2003 if (passed) {
2004 $field.removeClass('merchant-hide').addClass('merchant-show');
2005 } else {
2006 $field.removeClass('merchant-show').addClass('merchant-hide');
2007 }
2008 }
2009 });
2010 }).trigger('merchant.change');
2011 $(document).on('change', '.merchant-module-page-setting-field', function () {
2012 $(document).trigger('merchant-admin-check-fields');
2013 }).trigger('merchant.change');
2014 $(document).trigger('merchant-admin-check-fields');
2015 $(document).on('merchant-admin-check-color-fields merchant-flexible-content-added', function () {
2016 $('.merchant-color').each(function () {
2017 var $color = $(this);
2018 var $picker = $color.find('.merchant-color-picker');
2019 var $input = $color.find('.merchant-color-input');
2020 var inited = false;
2021 var pickr;
2022 $picker.off('click').on('click', function (e) {
2023 e.preventDefault();
2024 e.stopPropagation();
2025 var $bodyHTML = $('body,html');
2026 $bodyHTML.addClass('merchant-height-auto');
2027 if (!inited) {
2028 pickr = new Pickr({
2029 el: $picker.get(0),
2030 container: 'body',
2031 theme: 'merchant',
2032 appClass: 'merchant-pcr-app',
2033 default: $input.val() || $picker.data('default-color') || '#212121',
2034 swatches: ['#000000', '#F44336', '#E91E63', '#673AB7', '#03A9F4', '#8BC34A', '#FFEB3B', '#FFC107', '#FFFFFF'],
2035 sliders: 'h',
2036 useAsButton: true,
2037 components: {
2038 hue: true,
2039 preview: true,
2040 opacity: true,
2041 interaction: {
2042 input: true,
2043 clear: true
2044 }
2045 },
2046 i18n: {
2047 'btn:clear': 'Default'
2048 }
2049 });
2050 pickr.on('change', function (color) {
2051 var colorCode;
2052 if (color.a === 1) {
2053 pickr.setColorRepresentation('HEX');
2054 colorCode = color.toHEXA().toString(0);
2055 } else {
2056 pickr.setColorRepresentation('RGBA');
2057 colorCode = color.toRGBA().toString(0);
2058 }
2059 $picker.css({
2060 'background-color': colorCode
2061 });
2062 if ($input.val() !== colorCode) {
2063 $input.val(colorCode).trigger('change.merchant');
2064 }
2065 $(document).trigger('merchant-color-picker-updated', [colorCode, $input]);
2066 });
2067 pickr.on('clear', function () {
2068 var defaultColor = $picker.data('default-color');
2069 if (defaultColor) {
2070 pickr.setColor(defaultColor);
2071 } else {
2072 $picker.css({
2073 'background-color': 'white'
2074 });
2075 $input.val('');
2076 }
2077 });
2078 pickr.on('hide', function () {
2079 $bodyHTML.removeClass('merchant-height-auto');
2080 });
2081 $picker.data('pickr', pickr);
2082 setTimeout(function () {
2083 pickr.show();
2084 }, 200);
2085 inited = true;
2086 } else {
2087 pickr.setColor($input.val());
2088 }
2089 });
2090 $input.on('change keyup', function () {
2091 var colorCode = $(this).val();
2092 $picker.css({
2093 'background-color': colorCode
2094 });
2095 });
2096 });
2097 });
2098 $(document).trigger('merchant-admin-check-color-fields');
2099
2100 // Create Page Control.
2101 var CreatePageControl = {
2102 init: function init() {
2103 this.events();
2104 },
2105 events: function events() {
2106 var self = this;
2107 $(document).on('click', '.merchant-create-page-control-button', function (e) {
2108 e.preventDefault();
2109 var $this = $(this),
2110 $create_message = $this.parent().find('.merchant-create-page-control-create-message'),
2111 $success_message = $this.parent().find('.merchant-create-page-control-success-message'),
2112 initial_text = $this.text(),
2113 creating_text = $this.data('creating-text'),
2114 created_text = $this.data('created-text'),
2115 page_title = $this.data('page-title'),
2116 page_meta_key = $this.data('page-meta-key'),
2117 page_meta_value = $this.data('page-meta-value'),
2118 option_name = $this.data('option-name'),
2119 nonce = $this.data('nonce');
2120 if (!page_title) {
2121 return false;
2122 }
2123 $(this).text(creating_text);
2124 $(this).attr('disabled', true);
2125 $.ajax({
2126 type: 'post',
2127 url: ajaxurl,
2128 data: {
2129 action: 'merchant_create_page_control',
2130 page_title: page_title,
2131 page_meta_key: page_meta_key,
2132 page_meta_value: page_meta_value,
2133 option_name: option_name,
2134 nonce: nonce
2135 },
2136 success: function success(response) {
2137 self.ajaxResponseHandler(response, $this, $success_message, $create_message);
2138 }
2139 });
2140 });
2141 },
2142 ajaxResponseHandler: function ajaxResponseHandler(response, $this, $success_message, $create_message) {
2143 if ('success' === response.status) {
2144 var $editLink = $success_message.find('a').first(),
2145 href = $editLink.attr('href');
2146 if (href) {
2147 $editLink.attr('href', href.replace('?post=&', '?post=' + response.page_id + '&'));
2148 }
2149 $success_message.css('display', 'block');
2150 $create_message.remove();
2151 $this.remove();
2152 }
2153 }
2154 };
2155
2156 // Initialize Create Page Control.
2157 CreatePageControl.init();
2158 $('.merchant-module-page-setting-field-gallery').each(function () {
2159 var $this = $(this);
2160 var $button = $this.find('.merchant-gallery-button');
2161 var $input = $this.find('.merchant-gallery-input');
2162 var $images = $this.find('.merchant-gallery-images');
2163 var $remove = $this.find('.merchant-gallery-remove');
2164 var wpMediaFrame;
2165 var sortable = $images.sortable({
2166 helper: 'original',
2167 update: function update(event, ui) {
2168 var selectedIds = [];
2169 $images.find('.merchant-gallery-image').each(function () {
2170 selectedIds.push($(this).data('item-id'));
2171 });
2172 $input.val(selectedIds.join(',')).trigger('change');
2173 }
2174 });
2175 $remove.on('click', function (e) {
2176 e.preventDefault();
2177 $(this).parent().remove();
2178 var selectedIds = [];
2179 $images.find('.merchant-gallery-image').each(function () {
2180 selectedIds.push($(this).data('item-id'));
2181 });
2182 $input.val(selectedIds.join(',')).trigger('change');
2183 });
2184 $button.on('click', function (e) {
2185 var $btn = $(this);
2186 var ids = $input.val();
2187 var mode = ids ? 'edit' : 'add';
2188 e.preventDefault();
2189 if (typeof window.wp === 'undefined' || !window.wp.media || !window.wp.media.gallery) {
2190 return;
2191 }
2192 if (mode === 'add') {
2193 wpMediaFrame = window.wp.media({
2194 library: {
2195 type: 'image'
2196 },
2197 frame: 'post',
2198 state: 'gallery',
2199 multiple: true
2200 });
2201 wpMediaFrame.open();
2202 } else {
2203 wpMediaFrame = window.wp.media.gallery.edit('[gallery ids="' + ids + '"]');
2204 }
2205 wpMediaFrame.on('update', function (selection) {
2206 $images.empty();
2207 var selectedIds = selection.models.map(function (attachment) {
2208 var item = attachment.toJSON();
2209 var thumb = item.sizes && item.sizes.thumbnail && item.sizes.thumbnail.url ? item.sizes.thumbnail.url : item.url;
2210 $images.append('<div class="merchant-gallery-image" data-item-id="' + item.id + '"><i class="merchant-gallery-remove dashicons dashicons-no-alt"></i><img src="' + thumb + '" /></div>');
2211 return item.id;
2212 });
2213 $input.val(selectedIds.join(',')).trigger('change');
2214 $this.find('.merchant-gallery-remove').on('click', function (e) {
2215 e.preventDefault();
2216 $(this).parent().remove();
2217 var selectedIds = [];
2218 $images.find('.merchant-gallery-image').each(function () {
2219 selectedIds.push($(this).data('item-id'));
2220 });
2221 $input.val(selectedIds.join(',')).trigger('change');
2222 });
2223 });
2224 });
2225 });
2226 var initUploadField = function initUploadField(element) {
2227 var $this = element;
2228 var $button = $this.find('.merchant-upload-button');
2229 var $input = $this.find('.merchant-upload-input');
2230 var $wrapper = $this.find('.merchant-upload-wrapper');
2231 var $remove = $this.find('.merchant-upload-remove');
2232 var wpMediaFrame;
2233 $remove.on('click', function (e) {
2234 e.preventDefault();
2235 $(this).parent().remove();
2236 $input.val('').trigger('change');
2237 });
2238 $button.on('click', function (e) {
2239 e.preventDefault();
2240 if (typeof window.wp === 'undefined' || !window.wp.media) {
2241 return;
2242 }
2243 if (!wpMediaFrame) {
2244 wpMediaFrame = window.wp.media({
2245 library: {
2246 type: 'image'
2247 }
2248 });
2249 }
2250 wpMediaFrame.open();
2251 wpMediaFrame.on('select', function () {
2252 $wrapper.empty();
2253 var item = wpMediaFrame.state().get('selection').first().attributes;
2254 var thumb = item.sizes && item.sizes.thumbnail && item.sizes.thumbnail.url ? item.sizes.thumbnail.url : item.url;
2255 var sizes = item.sizes ? JSON.stringify(item.sizes) : '';
2256 $wrapper.append('<div class="merchant-upload-image" data-sizes=\'' + sizes + '\'><i class="merchant-upload-remove dashicons dashicons-no-alt"></i><img src="' + thumb + '" /></div>');
2257 $input.val(item.id).trigger('change');
2258 $this.find('.merchant-upload-button-drag-drop').hide();
2259 $this.find('.merchant-upload-remove').on('click', function (e) {
2260 e.preventDefault();
2261 $(this).parent().remove();
2262 $input.val('').trigger('change');
2263 $this.find('.merchant-upload-button-drag-drop').show();
2264 });
2265 });
2266 });
2267 };
2268 $('.merchant-module-page-setting-field-upload:not(.template)').each(function () {
2269 initUploadField($(this));
2270 });
2271
2272 // Drag & Drag
2273 var events = ['dragenter', 'dragover', 'dragleave', 'drop'];
2274 jQuery.each(events, function (index, eventName) {
2275 $(document).on(eventName, '.merchant-upload-button-drag-drop', function (e) {
2276 e.preventDefault();
2277 e.stopPropagation();
2278 });
2279 });
2280 $(document).on('dragenter', '.merchant-upload-button-drag-drop', function (e) {
2281 $(this).closest('.merchant-module-page-setting-field-upload').find('.merchant-upload-button').click();
2282 });
2283 var initSelectAjax = function initSelectAjax($selectAjax) {
2284 var $select = $selectAjax.find('select');
2285 var $source = $select.data('source');
2286 var $config = window.merchant_admin_options;
2287 var $object = {
2288 width: '100%',
2289 templateSelection: function templateSelection(category) {
2290 return category.text.replace(/&nbsp;-*\s*/g, '').trim();
2291 }
2292 };
2293 if ($source === 'post' || $source === 'product' || $source === 'user') {
2294 $object.minimumInputLength = 1;
2295 $object.ajax = {
2296 url: $config.ajaxurl,
2297 dataType: 'json',
2298 delay: 250,
2299 cache: true,
2300 data: function data(params) {
2301 return {
2302 action: 'merchant_admin_options_select_ajax',
2303 nonce: $config.ajaxnonce,
2304 term: params.term,
2305 source: $source
2306 };
2307 },
2308 processResults: function processResults(response, params) {
2309 if (response.success) {
2310 return {
2311 results: response.data
2312 };
2313 }
2314 return {};
2315 }
2316 };
2317 }
2318 $select.select2($object);
2319 $selectAjax.find('.select2-selection--multiple').append('<span class="merchant-select2-clear"></span>');
2320 };
2321 $('.merchant-module-page-setting-field-select_ajax:not(.template)').each(function () {
2322 initSelectAjax($(this));
2323 });
2324 $('.merchant-module-page-settings-responsive').each(function () {
2325 var $this = $(this);
2326 var $button = $this.find('.merchant-module-page-settings-devices button');
2327 var $container = $this.find('.merchant-module-page-settings-device-container');
2328 $button.on('click', function (e) {
2329 e.preventDefault();
2330 var $device = $(this).data('device');
2331 $button.removeClass('active');
2332 $container.removeClass('active');
2333 $(this).addClass('active');
2334 $container.each(function () {
2335 if ($(this).data('device') === $device) {
2336 $(this).addClass('active');
2337 }
2338 });
2339 });
2340 });
2341 $('.merchant-animated-buttons').each(function () {
2342 var $button = $(this).find('label');
2343 var $demo = $('.merchant-animation-demo');
2344 var animation;
2345 var animationHover;
2346 $button.on('click', function () {
2347 $demo.removeClass('merchant-animation-' + animation);
2348 $demo.removeClass('merchant-animation-' + animationHover);
2349 animation = $(this).find('input').attr('value');
2350 setTimeout(function () {
2351 $demo.addClass('merchant-animation-' + animation);
2352 }, 100);
2353 setTimeout(function () {
2354 $demo.removeClass('merchant-animation-' + animation);
2355 }, 1000);
2356 });
2357 $button.mouseover(function () {
2358 $demo.removeClass('merchant-animation-' + animation);
2359 animationHover = $(this).find('input').attr('value');
2360 $demo.addClass('merchant-animation-' + animationHover);
2361 }).mouseout(function () {
2362 $demo.removeClass('merchant-animation-' + animationHover);
2363 });
2364 });
2365
2366 // Notifications Sidebar
2367 var $notificationsSidebar = $('.merchant-notifications-sidebar');
2368 if ($notificationsSidebar.length) {
2369 var $notifications = $('.merchant-notifications');
2370 $notifications.on('click', function (e) {
2371 e.preventDefault();
2372 var $notification = $(this);
2373 var latestNotificationDate = $notificationsSidebar.find('.merchant-notification:first-child .merchant-notification-date').data('raw-date');
2374 $notificationsSidebar.toggleClass('opened');
2375 if (!$notification.hasClass('read')) {
2376 $.post(window.merchant.ajax_url, {
2377 action: 'merchant_notifications_read',
2378 nonce: window.merchant.nonce,
2379 latest_notification_date: latestNotificationDate
2380 }, function (response) {
2381 if (response.success) {
2382 setTimeout(function () {
2383 $notification.addClass('read');
2384 }, 2000);
2385 }
2386 });
2387 }
2388 });
2389
2390 // Hide fixed changelog items
2391 var merchantNotificationsContent = $('.merchant-notification-content');
2392 merchantNotificationsContent.each(function () {
2393 var notificationContentItem = $(this);
2394 // search for all spans that contains class "changelog-fixed" and then go to the parent li of that span and add class hidden
2395 var fixedItems = notificationContentItem.find('span.changelog-fixed');
2396 fixedItems.each(function () {
2397 var fixedItem = $(this);
2398 fixedItem.closest('li').remove();
2399 });
2400 // now check if all li inside this notificationContentItem are hidden, so we need to look for the parent .merchant-notification and remove it
2401 var allItems = notificationContentItem.find('li');
2402 var hiddenItems = notificationContentItem.find('li.hidden');
2403 if (allItems.length === hiddenItems.length) {
2404 notificationContentItem.closest('.merchant-notification').remove();
2405 }
2406 });
2407 $(window).on('scroll', function () {
2408 if (window.pageYOffset > 60) {
2409 $notificationsSidebar.addClass('closing');
2410 setTimeout(function () {
2411 $notificationsSidebar.removeClass('opened');
2412 $notificationsSidebar.removeClass('closing');
2413 }, 300);
2414 }
2415 });
2416
2417 // Close Sidebar
2418 $('.merchant-notifications-sidebar-close').on('click', function (e) {
2419 e.preventDefault();
2420 $notificationsSidebar.addClass('closing');
2421 setTimeout(function () {
2422 $notificationsSidebar.removeClass('opened');
2423 $notificationsSidebar.removeClass('closing');
2424 }, 300);
2425 });
2426 }
2427
2428 // Tabs Navigation.
2429 var tabs = $('.merchant-tabs-nav');
2430 if (tabs.length) {
2431 tabs.each(function () {
2432 var tabWrapperId = $(this).data('tab-wrapper-id');
2433 $(this).find('.merchant-tabs-nav-link').on('click', function (e) {
2434 e.preventDefault();
2435 var tabsNavLink = $(this).closest('.merchant-tabs-nav').find('.merchant-tabs-nav-link'),
2436 to = $(this).data('tab-to');
2437
2438 // Tab Nav Item
2439 tabsNavLink.each(function () {
2440 $(this).closest('.merchant-tabs-nav-item').removeClass('active');
2441 });
2442 $(this).closest('.merchant-tabs-nav-item').addClass('active');
2443
2444 // Tab Content
2445 var tabContentWrapper = $('.merchant-tab-content-wrapper[data-tab-wrapper-id="' + tabWrapperId + '"]');
2446 tabContentWrapper.find('> .merchant-tab-content').removeClass('active');
2447 tabContentWrapper.find('> .merchant-tab-content[data-tab-content-id="' + to + '"]').addClass('active');
2448 });
2449 });
2450 }
2451
2452 // Module Alert
2453 var $moduleAlert = $('.merchant-module-alert');
2454 if ($moduleAlert.length) {
2455 $moduleAlert.find('.merchant-module-alert-close').on('click', function (e) {
2456 e.preventDefault();
2457 $moduleAlert.removeClass('merchant-show');
2458 $(document).off('click.merchant-alert-close');
2459 });
2460 }
2461 });
2462
2463 /**
2464 * Check if a string is numeric.
2465 *
2466 * @param str the string to check
2467 *
2468 * @returns {boolean} true if numeric, false otherwise
2469 */
2470 function isNumeric(str) {
2471 if (typeof str != "string") return false; // we only process strings!
2472 return !isNaN(str) &&
2473 // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
2474 !isNaN(parseFloat(str)); // ...and ensure strings of whitespace fail
2475 }
2476
2477 /**
2478 * Evaluate conditional fields.
2479 *
2480 * @param conditions the array of conditions
2481 * @param field the field that is being evaluated
2482 *
2483 * @returns {boolean}
2484 */
2485 function evaluateConditions(conditions) {
2486 var field = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2487 var passed = false;
2488 if ('relation' in conditions) {
2489 //loop through terms
2490 var relation = conditions.relation.toUpperCase();
2491 // lowercase relation
2492 if (relation === 'OR') {
2493 for (var i = 0; i < conditions.terms.length; i++) {
2494 var term = conditions.terms[i];
2495 passed = evaluateConditions(term, field);
2496 if (passed) {
2497 return true;
2498 }
2499 }
2500 } else if (relation === 'AND') {
2501 var n = 0;
2502 for (var _i = 0; _i < conditions.terms.length; _i++) {
2503 // check if inner terms are passed
2504 var _term = conditions.terms[_i];
2505 if (evaluateConditions(_term, field)) {
2506 n++;
2507 }
2508 }
2509 if (n === conditions.terms.length) {
2510 passed = true;
2511 }
2512 }
2513 } else {
2514 var condition = '';
2515 if ('terms' in conditions) {
2516 condition = conditions.terms[0];
2517 } else {
2518 condition = conditions;
2519 }
2520 var $target = $('input[name="merchant[' + condition.field + ']"],select[name="merchant[' + condition.field + ']"]');
2521 if (!$target.length) {
2522 // check if inside flexible content
2523 var flexibleContentParent = field.closest('.layout-body');
2524 if (flexibleContentParent.length > 0) {
2525 $target = flexibleContentParent.find('.merchant-field-' + condition.field).find('input, select');
2526 }
2527 }
2528 if (!$target.length) {
2529 // Maybe the field is a multiple field
2530 $target = $('input[name="merchant[' + condition.field + '][]"],select[name="merchant[' + condition.field + '][]"]');
2531 }
2532 if (!$target.length) {
2533 // Maybe the field is inside fields group
2534 $target = $('.merchant-group-fields-container').find('.merchant-field-' + condition.field + ' input[name*="' + condition.field + '"],.merchant-field-' + condition.field + ' select[name*="' + condition.field + '"]');
2535 }
2536 var value = $target.val();
2537 if ($target.attr('type') === 'checkbox') {
2538 value = $target.is(':checked');
2539 }
2540 if ($target.attr('type') === 'radio') {
2541 value = $target.filter(':checked').val();
2542 }
2543
2544 // check if the field is multiple checkbox
2545 if ($target.attr('type') === 'checkbox' && $target.length > 1) {
2546 value = [];
2547 $target.each(function () {
2548 if ($(this).is(':checked')) {
2549 value.push($(this).val());
2550 }
2551 });
2552 }
2553
2554 // cast value as string if numeric
2555 if (isNumeric(value)) {
2556 value = Number(value);
2557 }
2558
2559 // check if is array condition.value
2560 if (Array.isArray(condition.value)) {
2561 condition.value = condition.value.map(function (item) {
2562 if (isNumeric(item)) {
2563 return Number(item);
2564 }
2565 return item;
2566 });
2567 }
2568 if (condition.operator === '===' && value === condition.value) {
2569 passed = true;
2570 } else if (condition.operator === '!==' && value !== condition.value) {
2571 passed = true;
2572 } else if (condition.operator === '>' && value > condition.value) {
2573 passed = true;
2574 } else if (condition.operator === '<' && value < condition.value) {
2575 passed = true;
2576 } else if (condition.operator === '>=' && value >= condition.value) {
2577 passed = true;
2578 } else if (condition.operator === '<=' && value <= condition.value) {
2579 passed = true;
2580 } else if (condition.operator === 'in' && condition.value.includes(value)) {
2581 passed = true;
2582 } else if (condition.operator === '!in' && !condition.value.includes(value)) {
2583 passed = true;
2584 } else if (condition.operator === 'contains' && Array.isArray(value) && value.includes(condition.value)) {
2585 passed = true;
2586 } else if (condition.operator === '!contains' && Array.isArray(value) && !value.includes(condition.value)) {
2587 passed = true;
2588 }
2589 }
2590 return passed;
2591 }
2592 })(jQuery, window, document);
2593
2594 // Extend jQuery to add getPath func to get accurate dynamic selector to an element.
2595 jQuery.fn.extend({
2596 getPath: function getPath() {
2597 var pathes = [];
2598 this.each(function (index, element) {
2599 var path,
2600 $node = jQuery(element);
2601 while ($node.length) {
2602 var realNode = $node.get(0),
2603 name = realNode.localName;
2604 if (!name) {
2605 break;
2606 }
2607 name = name.toLowerCase();
2608 var parent = $node.parent();
2609 var sameTagSiblings = parent.children(name);
2610 if (sameTagSiblings.length > 1) {
2611 var allSiblings = parent.children();
2612 var index = allSiblings.index(realNode) + 1;
2613 if (index > 0) {
2614 name += ':nth-child(' + index + ')';
2615 }
2616 }
2617 path = name + (path ? ' > ' + path : '');
2618 $node = parent;
2619 }
2620 pathes.push(path);
2621 });
2622 return pathes.join(',');
2623 }
2624 });