PluginProbe
Booking Calendar / 10.15.6
Booking Calendar v10.15.6
11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 All 204 releases
booking / includes / save-load-option / _src / save-load-option.js

save-load-option.js in Booking Calendar 10.15.6, at includes/save-load-option/_src/save-load-option.js

382 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * General Option Loader/Saver (client)
3 *
4 * - Provides:
5 * window.wpbc_save_option_from_element(el)
6 * window.wpbc_load_option_from_element(el)
7 * - Busy UI (spinner + disabled)
8 * - JSON path: send raw JSON string untouched.
9 * - RAW scalar path: send as-is.
10 * - Fields path: serialize to query-string via jQuery.param.
11 *
12 * IMPORTANT:
13 * - jQuery .data() is cached. If some code updates data-* attributes via setAttribute(),
14 * reading via $el.data(...) can return stale values.
15 * - Therefore, this module prefers reading via $el.attr('data-...') for dynamic keys
16 * (value/value-json), and falls back to $el.data(...) when attribute is missing.
17 *
18 * file: ../includes/save-load-option/_out/save-load-option.js
19 *
20 * Events:
21 * $(document).on('wpbc:option:beforeSave', (e, $el, payload) => {})
22 * $(document).on('wpbc:option:afterSave', (e, response) => {})
23 * $(document).on('wpbc:option:beforeLoad', (e, $el, name) => {})
24 * $(document).on('wpbc:option:afterLoad', (e, response) => {})
25 */
26 (function (w, $) {
27 'use strict';
28
29 /**
30 * Escape for safe HTML injection (small helper).
31 *
32 * @param {string} s
33 * @returns {string}
34 */
35 function wpbc_uix_escape_html(s) {
36 return String(s)
37 .replace(/&/g, '&')
38 .replace(/</g, '&lt;')
39 .replace(/>/g, '&gt;')
40 .replace(/"/g, '&quot;')
41 .replace(/'/g, '&#039;');
42 }
43
44 /**
45 * Read a value from data-* attribute first (fresh), then fallback to jQuery .data() cache.
46 *
47 * @param {jQuery} $el
48 * @param {string} attr_name Example: 'data-wpbc-u-save-value'
49 * @param {string} data_key Example: 'wpbc-u-save-value'
50 * @returns {*}
51 */
52 function wpbc_uix_read_attr_or_data($el, attr_name, data_key) {
53 var v = $el.attr(attr_name);
54 if (typeof v !== 'undefined') {
55 return v;
56 }
57 return $el.data(data_key);
58 }
59
60 /**
61 * Turn "On"/"Off" like values into consistent "On"/"Off".
62 * (Used for checkbox/toggle serialization.)
63 *
64 * @param {*} v
65 * @returns {string}
66 */
67 function wpbc_uix_to_on_off(v) {
68 if (v === true) { return 'On'; }
69 if (v === false) { return 'Off'; }
70 var s = String(v || '').toLowerCase();
71 if (s === 'on' || s === '1' || s === 'true' || s === 'yes') { return 'On'; }
72 return 'Off';
73 }
74
75 /**
76 * Get a useful value from an input/select/textarea element.
77 * - checkbox => 'On'/'Off'
78 * - radio => value of checked in group (if possible), else ''
79 * - others => .val()
80 *
81 * @param {jQuery} $control
82 * @returns {string}
83 */
84 function wpbc_uix_get_control_value($control) {
85
86 if (!$control || !$control.length) {
87 return '';
88 }
89
90 // checkbox/toggle.
91 if ($control.is(':checkbox')) {
92 return $control.is(':checked') ? 'On' : 'Off';
93 }
94
95 // radio group.
96 if ($control.is(':radio')) {
97 var name = $control.attr('name');
98 if (name) {
99 var $checked = $('input[type="radio"][name="' + name + '"]:checked');
100 return $checked.length ? String($checked.val()) : '';
101 }
102 return $control.is(':checked') ? String($control.val()) : '';
103 }
104
105 // select/text/textarea/etc.
106 return String($control.val() == null ? '' : $control.val());
107 }
108
109 /**
110 * Busy ON UI for a clickable element.
111 *
112 * @param {jQuery} $el
113 * @returns {void}
114 */
115 function wpbc_uix_busy_on($el) {
116 if (!$el || !$el.length || $el.data('wpbc-uix-busy')) {
117 return;
118 }
119
120 $el.data('wpbc-uix-busy', 1);
121 $el.data('wpbc-uix-original-html', $el.html());
122
123 var busy_text = $el.data('wpbc-u-busy-text');
124 var spinner = '<span class="wpbc_icn_rotate_right wpbc_spin wpbc_ajax_icon wpbc_processing wpbc_icn_autorenew" aria-hidden="true"></span>';
125
126 if (typeof busy_text === 'string' && busy_text.length) {
127 $el.html(wpbc_uix_escape_html(busy_text) + ' ' + spinner);
128 } else {
129 $el.append(spinner);
130 }
131
132 $el.addClass('wpbc-is-busy')
133 .attr('aria-disabled', 'true')
134 .prop('disabled', true);
135 }
136
137 /**
138 * Busy OFF UI for a clickable element.
139 *
140 * @param {jQuery} $el
141 * @returns {void}
142 */
143 function wpbc_uix_busy_off($el) {
144 if (!$el || !$el.length || !$el.data('wpbc-uix-busy')) {
145 return;
146 }
147
148 var original = $el.data('wpbc-uix-original-html');
149 if (typeof original === 'string') {
150 $el.html(original);
151 }
152
153 $el.removeClass('wpbc-is-busy')
154 .removeAttr('aria-disabled')
155 .prop('disabled', false);
156
157 $el.removeData('wpbc-uix-busy')
158 .removeData('wpbc-uix-original-html');
159 }
160
161 /**
162 * Save Option - send ajax request to save data.
163 *
164 * Data attributes:
165 * data-wpbc-u-save-name — option key (required)
166 * data-wpbc-u-save-nonce — nonce value (required)
167 * data-wpbc-u-save-action — nonce action (required)
168 * data-wpbc-u-save-value — RAW scalar to save (optional) (dynamic: read via attr first)
169 * data-wpbc-u-save-value-json — JSON string to save (optional) (dynamic: read via attr first)
170 * data-wpbc-u-save-fields — CSV selectors; values serialized with jQuery.param (optional). Optional allowlist of keys for split mode (CSV).
171 * data-wpbc-u-save-mode - Optional.: 'split' | '' -- Optional: split JSON object into separate options server-side.
172 * data-wpbc-u-save-value-from — OPTIONAL selector to read scalar from (checkbox => On/Off)
173 * data-wpbc-u-busy-text — custom text during AJAX (optional)
174 * data-wpbc-u-save-callback — window function name to call on success (optional)
175 *
176 * @param {HTMLElement} el element with data attributes.
177 * @returns {void}
178 */
179 w.wpbc_save_option_from_element = function (el) {
180
181 if (!w.wpbc_option_saver_loader_config) {
182 console.error('WPBC | config missing');
183 return;
184 }
185
186 var $el = $(el);
187
188 // Static values can be read from .data().
189 var nonce = $el.data('wpbc-u-save-nonce');
190 var nonce_action = $el.data('wpbc-u-save-action');
191 var data_name = $el.data('wpbc-u-save-name');
192
193 // Dynamic values MUST prefer attribute read (fresh), fallback to .data().
194 var fields_raw = $el.data('wpbc-u-save-fields') || '';
195 var inline_value = wpbc_uix_read_attr_or_data($el, 'data-wpbc-u-save-value', 'wpbc-u-save-value');
196 var json = wpbc_uix_read_attr_or_data($el, 'data-wpbc-u-save-value-json', 'wpbc-u-save-value-json');
197 var save_mode = $el.data('wpbc-u-save-mode') || $el.attr('data-wpbc-u-save-mode') || '';
198
199 // Optional: compute scalar from another control selector at click time.
200 var value_from_selector = $el.data('wpbc-u-save-value-from') || $el.attr('data-wpbc-u-save-value-from');
201
202 var cb_id = $el.data('wpbc-u-save-callback');
203 var cb_fn = (cb_id && typeof w[cb_id] === 'function') ? w[cb_id] : null;
204
205 if (!nonce || !nonce_action || !data_name) {
206 console.error('WPBC | missing nonce/action/name');
207 return;
208 }
209
210 var payload = '';
211
212 // 1) JSON path.
213 if (typeof json === 'string' && json.trim() !== '') {
214 payload = json.trim();
215 }
216 // 2) Scalar computed from selector (checkbox => On/Off).
217 else if (value_from_selector) {
218 var $src = $(value_from_selector);
219 var $control = $src.is('input,select,textarea') ? $src : $src.find('input,select,textarea').first();
220 payload = wpbc_uix_get_control_value($control);
221 }
222 // 3) RAW scalar path.
223 else if (typeof inline_value !== 'undefined' && inline_value !== null) {
224 payload = String(inline_value);
225 }
226 // 4) Fields path (query-string).
227 else if (fields_raw) {
228
229 var fields = String(fields_raw).split(',')
230 .map(function (s) { return String(s || '').trim(); })
231 .filter(Boolean);
232
233 var data = {};
234
235 fields.forEach(function (sel) {
236 var $f = $(sel);
237 if (!$f.length) { return; }
238
239 // If selector points to a wrapper, try to locate a real control inside.
240 var $control = $f.is('input,select,textarea') ? $f : $f.find('input,select,textarea').first();
241 if (!$control.length) { return; }
242
243 var key = $control.attr('name') || $control.attr('id');
244 if (!key) { return; }
245
246 data[key] = wpbc_uix_get_control_value($control);
247 });
248
249 payload = $.param(data);
250 }
251 else {
252 console.error('WPBC | provide value, value-from selector, json, or fields');
253 return;
254 }
255
256 // Sync jQuery cache for the scalar value (helps other code that still reads .data()).
257 // If payload looks like a simple scalar (not JSON, not query-string), keep it aligned.
258 if (typeof payload === 'string' && payload.indexOf('=') === -1 && payload.indexOf('&') === -1) {
259 try {
260 $el.data('wpbc-u-save-value', payload);
261 } catch (e) {}
262 }
263
264 $(document).trigger('wpbc:option:beforeSave', [ $el, payload ]);
265 wpbc_uix_busy_on($el);
266
267 $.ajax({
268 url: w.wpbc_option_saver_loader_config.ajax_url,
269 type: 'POST',
270 data: {
271 action: w.wpbc_option_saver_loader_config.action_save,
272 nonce: nonce,
273 nonce_action: nonce_action,
274 data_name: data_name,
275 data_value: payload,
276
277 // Optional: split JSON object into separate options server-side.
278 data_mode: save_mode,
279
280 // Optional allowlist of keys for split mode (CSV).
281 data_fields: (save_mode === 'split' ? String(fields_raw || '') : '')
282 }
283 })
284 .done(function (resp) {
285
286 // NOTE: previously the code always showed "success" even on error.
287 // Fixed: show success only when resp.success is true.
288
289 if (resp && resp.success) {
290
291 if (cb_fn) {
292 try { cb_fn(resp); } catch (e) { console.error(e); }
293 }
294
295 var ok_message = (resp && resp.data && resp.data.message) ? resp.data.message : 'Saved';
296 if (typeof w.wpbc_admin_show_message === 'function') {
297 w.wpbc_admin_show_message(ok_message, 'success', 1000, false);
298 }
299
300 } else {
301
302 var err_message = (resp && resp.data && resp.data.message) ? resp.data.message : 'Save error';
303 console.error('WPBC | ' + err_message);
304
305 if (typeof w.wpbc_admin_show_message === 'function') {
306 w.wpbc_admin_show_message(err_message, 'error', 30000);
307 }
308 }
309
310 $(document).trigger('wpbc:option:afterSave', [ resp ]);
311 })
312 .fail(function (xhr) {
313 var feedback_message = 'WPBC | AJAX ' + xhr.status + ' ' + xhr.statusText;
314 console.error(feedback_message);
315
316 if (typeof w.wpbc_admin_show_message === 'function') {
317 w.wpbc_admin_show_message(feedback_message, 'error', 30000);
318 }
319
320 $(document).trigger('wpbc:option:afterSave', [ { success: false, data: { message: xhr.statusText } } ]);
321 })
322 .always(function () {
323 wpbc_uix_busy_off($el);
324 });
325 };
326
327 /**
328 * Load option value via AJAX.
329 *
330 * @param {HTMLElement} el element with data attributes.
331 * @returns {void}
332 */
333 w.wpbc_load_option_from_element = function (el) {
334
335 if (!w.wpbc_option_saver_loader_config) {
336 console.error('WPBC | config missing');
337 return;
338 }
339
340 var $el = $(el);
341 var name = $el.data('wpbc-u-load-name') || $el.data('wpbc-u-save-name');
342
343 var cb_id = $el.data('wpbc-u-load-callback');
344 var cb_fn = (cb_id && typeof w[cb_id] === 'function') ? w[cb_id] : null;
345
346 if (!name) {
347 console.error('WPBC | missing data-wpbc-u-load-name');
348 return;
349 }
350
351 $(document).trigger('wpbc:option:beforeLoad', [ $el, name ]);
352 wpbc_uix_busy_on($el);
353
354 $.ajax({
355 url: w.wpbc_option_saver_loader_config.ajax_url,
356 type: 'GET',
357 data: {
358 action: w.wpbc_option_saver_loader_config.action_load,
359 data_name: name
360 }
361 })
362 .done(function (resp) {
363 if (resp && resp.success) {
364 if (cb_fn) {
365 try { cb_fn(resp.data && resp.data.value); } catch (e) { console.error(e); }
366 }
367 } else {
368 console.error('WPBC | ' + (resp && resp.data && resp.data.message ? resp.data.message : 'Load error'));
369 }
370 $(document).trigger('wpbc:option:afterLoad', [ resp ]);
371 })
372 .fail(function (xhr) {
373 console.error('WPBC | AJAX ' + xhr.status + ' ' + xhr.statusText);
374 $(document).trigger('wpbc:option:afterLoad', [ { success: false, data: { message: xhr.statusText } } ]);
375 })
376 .always(function () {
377 wpbc_uix_busy_off($el);
378 });
379 };
380
381 }(window, jQuery));
382