← All changes
|
includes/save-load-option/_src/save-load-option.js
+187
-28
10.15.6
→
11.9
View file →
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | /** |
| 2 | 2 | * General Option Loader/Saver (client) |
| 3 | 3 | * |
| 4 | 4 | * - Provides: |
| @@ -157,19 +157,128 @@ | ||
| 157 | 157 | $el.removeData('wpbc-uix-busy') |
| 158 | 158 | .removeData('wpbc-uix-original-html'); |
| 159 | 159 | } |
| 160 | 160 | |
| 161 | + var wpbc_uix_autosave_registry = {}; | |
| 162 | + | |
| 163 | + function wpbc_uix_get_option_save_config($el) { | |
| 164 | + return { | |
| 165 | + data_name : $el.data('wpbc-u-save-name'), | |
| 166 | + fields_raw : $el.data('wpbc-u-save-fields') || '', | |
| 167 | + inline_value : wpbc_uix_read_attr_or_data($el, 'data-wpbc-u-save-value', 'wpbc-u-save-value'), | |
| 168 | + json : wpbc_uix_read_attr_or_data($el, 'data-wpbc-u-save-value-json', 'wpbc-u-save-value-json'), | |
| 169 | + value_from_selector: $el.data('wpbc-u-save-value-from') || $el.attr('data-wpbc-u-save-value-from') | |
| 170 | + }; | |
| 171 | + } | |
| 172 | + | |
| 173 | + function wpbc_uix_build_option_save_payload($el, cfg) { | |
| 174 | + cfg = cfg || wpbc_uix_get_option_save_config($el); | |
| 175 | + | |
| 176 | + if (typeof cfg.json === 'string' && cfg.json.trim() !== '') { | |
| 177 | + return cfg.json.trim(); | |
| 178 | + } | |
| 179 | + | |
| 180 | + if (cfg.value_from_selector) { | |
| 181 | + var $src = $(cfg.value_from_selector); | |
| 182 | + var $control = $src.is('input,select,textarea') ? $src : $src.find('input,select,textarea').first(); | |
| 183 | + return wpbc_uix_get_control_value($control); | |
| 184 | + } | |
| 185 | + | |
| 186 | + if (typeof cfg.inline_value !== 'undefined' && cfg.inline_value !== null) { | |
| 187 | + return String(cfg.inline_value); | |
| 188 | + } | |
| 189 | + | |
| 190 | + if (cfg.fields_raw) { | |
| 191 | + var fields = String(cfg.fields_raw).split(',') | |
| 192 | + .map(function (s) { return String(s || '').trim(); }) | |
| 193 | + .filter(Boolean); | |
| 194 | + | |
| 195 | + var data = {}; | |
| 196 | + | |
| 197 | + fields.forEach(function (sel) { | |
| 198 | + var $f = $(sel); | |
| 199 | + if (!$f.length) { return; } | |
| 200 | + | |
| 201 | + var $control = $f.is('input,select,textarea') ? $f : $f.find('input,select,textarea').first(); | |
| 202 | + if (!$control.length) { return; } | |
| 203 | + | |
| 204 | + var key = $control.attr('name') || $control.attr('id'); | |
| 205 | + if (!key) { return; } | |
| 206 | + | |
| 207 | + data[key] = wpbc_uix_get_control_value($control); | |
| 208 | + }); | |
| 209 | + | |
| 210 | + return $.param(data); | |
| 211 | + } | |
| 212 | + | |
| 213 | + return null; | |
| 214 | + } | |
| 215 | + | |
| 216 | + function wpbc_uix_update_autosave_registry_from_element(el, is_dirty) { | |
| 217 | + var $el = $(el); | |
| 218 | + var cfg = wpbc_uix_get_option_save_config($el); | |
| 219 | + | |
| 220 | + if (!cfg.data_name) { | |
| 221 | + return; | |
| 222 | + } | |
| 223 | + | |
| 224 | + wpbc_uix_autosave_registry[cfg.data_name] = { | |
| 225 | + data_name : cfg.data_name, | |
| 226 | + payload : wpbc_uix_build_option_save_payload($el, cfg), | |
| 227 | + dirty : !!is_dirty, | |
| 228 | + el : el | |
| 229 | + }; | |
| 230 | + } | |
| 231 | + | |
| 232 | + function wpbc_uix_save_autosave_registry_entry(entry) { | |
| 233 | + if (!entry || !entry.dirty || !entry.data_name || entry.payload === null || !w.wpbc_option_saver_loader_config || !w.wpbc_option_saver_loader_config.save_nonce) { | |
| 234 | + return; | |
| 235 | + } | |
| 236 | + | |
| 237 | + $(document).trigger('wpbc:option:beforeSave', [ $(), entry.payload ]); | |
| 238 | + | |
| 239 | + $.ajax({ | |
| 240 | + url: w.wpbc_option_saver_loader_config.ajax_url, | |
| 241 | + type: 'POST', | |
| 242 | + data: { | |
| 243 | + action: w.wpbc_option_saver_loader_config.action_save, | |
| 244 | + nonce: w.wpbc_option_saver_loader_config.save_nonce, | |
| 245 | + data_name: entry.data_name, | |
| 246 | + data_value: entry.payload | |
| 247 | + } | |
| 248 | + }) | |
| 249 | + .done(function (resp) { | |
| 250 | + if (resp && resp.success) { | |
| 251 | + entry.dirty = false; | |
| 252 | + if (entry.el) { | |
| 253 | + $(entry.el).attr('data-wpbc-u-autosave-dirty', '0'); | |
| 254 | + } | |
| 255 | + if (typeof w.wpbc_admin_show_message === 'function') { | |
| 256 | + w.wpbc_admin_show_message((resp.data && resp.data.message) ? resp.data.message : 'Saved', 'success', 1000, false); | |
| 257 | + } | |
| 258 | + } else if (typeof w.wpbc_admin_show_message === 'function') { | |
| 259 | + w.wpbc_admin_show_message((resp && resp.data && resp.data.message) ? resp.data.message : 'Save error', 'error', 30000); | |
| 260 | + } | |
| 261 | + | |
| 262 | + $(document).trigger('wpbc:option:afterSave', [ resp ]); | |
| 263 | + }) | |
| 264 | + .fail(function (xhr) { | |
| 265 | + if (typeof w.wpbc_admin_show_message === 'function') { | |
| 266 | + w.wpbc_admin_show_message('WPBC | AJAX ' + xhr.status + ' ' + xhr.statusText, 'error', 30000); | |
| 267 | + } | |
| 268 | + $(document).trigger('wpbc:option:afterSave', [ { success: false, data: { message: xhr.statusText } } ]); | |
| 269 | + }); | |
| 270 | + } | |
| 271 | + | |
| 161 | 272 | /** |
| 162 | 273 | * Save Option - send ajax request to save data. |
| 163 | 274 | * |
| 164 | 275 | * Data attributes: |
| 165 | 276 | * 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) | |
| 277 | + * The fixed save nonce is supplied by wpbc_option_saver_loader_config. | |
| 168 | 278 | * data-wpbc-u-save-value — RAW scalar to save (optional) (dynamic: read via attr first) |
| 169 | 279 | * 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. | |
| 280 | + * data-wpbc-u-save-fields — CSV selectors serialized with jQuery.param (optional). Server policy owns the writable-key allowlist. | |
| 172 | 281 | * data-wpbc-u-save-value-from — OPTIONAL selector to read scalar from (checkbox => On/Off) |
| 173 | 282 | * data-wpbc-u-busy-text — custom text during AJAX (optional) |
| 174 | 283 | * data-wpbc-u-save-callback — window function name to call on success (optional) |
| 175 | 284 | * |
| @@ -184,18 +293,15 @@ | ||
| 184 | 293 | } |
| 185 | 294 | |
| 186 | 295 | var $el = $(el); |
| 187 | 296 | |
| 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'); | |
| 297 | + var nonce = w.wpbc_option_saver_loader_config.save_nonce; | |
| 298 | + var data_name = $el.data('wpbc-u-save-name'); | |
| 192 | 299 | |
| 193 | 300 | // Dynamic values MUST prefer attribute read (fresh), fallback to .data(). |
| 194 | 301 | var fields_raw = $el.data('wpbc-u-save-fields') || ''; |
| 195 | 302 | var inline_value = wpbc_uix_read_attr_or_data($el, 'data-wpbc-u-save-value', 'wpbc-u-save-value'); |
| 196 | 303 | 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 | 304 | |
| 199 | 305 | // Optional: compute scalar from another control selector at click time. |
| 200 | 306 | var value_from_selector = $el.data('wpbc-u-save-value-from') || $el.attr('data-wpbc-u-save-value-from'); |
| 201 | 307 | |
| @@ -201,10 +307,10 @@ | ||
| 201 | 307 | |
| 202 | 308 | var cb_id = $el.data('wpbc-u-save-callback'); |
| 203 | 309 | var cb_fn = (cb_id && typeof w[cb_id] === 'function') ? w[cb_id] : null; |
| 204 | 310 | |
| 205 | - if (!nonce || !nonce_action || !data_name) { | |
| 206 | - console.error('WPBC | missing nonce/action/name'); | |
| 311 | + if (!nonce || !data_name) { | |
| 312 | + console.error('WPBC | missing nonce/name'); | |
| 207 | 313 | return; |
| 208 | 314 | } |
| 209 | 315 | |
| 210 | 316 | var payload = ''; |
| @@ -266,20 +372,13 @@ | ||
| 266 | 372 | |
| 267 | 373 | $.ajax({ |
| 268 | 374 | url: w.wpbc_option_saver_loader_config.ajax_url, |
| 269 | 375 | 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 || '') : '') | |
| 376 | + data: { | |
| 377 | + action: w.wpbc_option_saver_loader_config.action_save, | |
| 378 | + nonce: nonce, | |
| 379 | + data_name: data_name, | |
| 380 | + data_value: payload | |
| 282 | 381 | } |
| 283 | 382 | }) |
| 284 | 383 | .done(function (resp) { |
| 285 | 384 | |
| @@ -287,8 +386,13 @@ | ||
| 287 | 386 | // Fixed: show success only when resp.success is true. |
| 288 | 387 | |
| 289 | 388 | if (resp && resp.success) { |
| 290 | 389 | |
| 390 | + $el.attr('data-wpbc-u-autosave-dirty', '0'); | |
| 391 | + if (data_name && wpbc_uix_autosave_registry[data_name]) { | |
| 392 | + wpbc_uix_autosave_registry[data_name].dirty = false; | |
| 393 | + } | |
| 394 | + | |
| 291 | 395 | if (cb_fn) { |
| 292 | 396 | try { cb_fn(resp); } catch (e) { console.error(e); } |
| 293 | 397 | } |
| 294 | 398 | |
| @@ -324,8 +428,60 @@ | ||
| 324 | 428 | }); |
| 325 | 429 | }; |
| 326 | 430 | |
| 327 | 431 | /** |
| 432 | + * Wire opt-in autosave of global options after successful BFB form save. | |
| 433 | + * | |
| 434 | + * Add data-wpbc-u-autosave-on-form-save="1" to a save control to participate. | |
| 435 | + * Dirty state is tracked from data-wpbc-u-save-value-from, or data-wpbc-u-autosave-watch when present. | |
| 436 | + * | |
| 437 | + * @returns {void} | |
| 438 | + */ | |
| 439 | + function wpbc_uix_bind_autosave_on_form_save() { | |
| 440 | + var autosave_selector = '[data-wpbc-u-autosave-on-form-save="1"]'; | |
| 441 | + | |
| 442 | + $(document).on('change input', 'input,select,textarea', function () { | |
| 443 | + var changed_el = this; | |
| 444 | + | |
| 445 | + $(autosave_selector).each(function () { | |
| 446 | + var $btn = $(this); | |
| 447 | + var watch_selector = $btn.attr('data-wpbc-u-autosave-watch') || $btn.attr('data-wpbc-u-save-value-from') || ''; | |
| 448 | + | |
| 449 | + if (!watch_selector) { | |
| 450 | + return; | |
| 451 | + } | |
| 452 | + | |
| 453 | + var $watched = $(watch_selector); | |
| 454 | + if ($watched.filter(changed_el).length || $watched.has(changed_el).length) { | |
| 455 | + $btn.attr('data-wpbc-u-autosave-dirty', '1'); | |
| 456 | + wpbc_uix_update_autosave_registry_from_element(this, true); | |
| 457 | + } | |
| 458 | + }); | |
| 459 | + }); | |
| 460 | + | |
| 461 | + document.addEventListener('wpbc:bfb:form:ajax_saved', function () { | |
| 462 | + $(autosave_selector).each(function () { | |
| 463 | + if (this.getAttribute('data-wpbc-u-autosave-dirty') === '1') { | |
| 464 | + wpbc_uix_update_autosave_registry_from_element(this, true); | |
| 465 | + } | |
| 466 | + }); | |
| 467 | + | |
| 468 | + Object.keys(wpbc_uix_autosave_registry).forEach(function (option_name) { | |
| 469 | + var entry = wpbc_uix_autosave_registry[option_name]; | |
| 470 | + if (!entry || !entry.dirty) { | |
| 471 | + return; | |
| 472 | + } | |
| 473 | + | |
| 474 | + if (entry.el && document.documentElement.contains(entry.el)) { | |
| 475 | + w.wpbc_save_option_from_element(entry.el); | |
| 476 | + } else { | |
| 477 | + wpbc_uix_save_autosave_registry_entry(entry); | |
| 478 | + } | |
| 479 | + }); | |
| 480 | + }); | |
| 481 | + } | |
| 482 | + | |
| 483 | + /** | |
| 328 | 484 | * Load option value via AJAX. |
| 329 | 485 | * |
| 330 | 486 | * @param {HTMLElement} el element with data attributes. |
| 331 | 487 | * @returns {void} |
| @@ -342,10 +498,10 @@ | ||
| 342 | 498 | |
| 343 | 499 | var cb_id = $el.data('wpbc-u-load-callback'); |
| 344 | 500 | var cb_fn = (cb_id && typeof w[cb_id] === 'function') ? w[cb_id] : null; |
| 345 | 501 | |
| 346 | - if (!name) { | |
| 347 | - console.error('WPBC | missing data-wpbc-u-load-name'); | |
| 502 | + if (!name || !w.wpbc_option_saver_loader_config.load_nonce) { | |
| 503 | + console.error('WPBC | missing load nonce/name'); | |
| 348 | 504 | return; |
| 349 | 505 | } |
| 350 | 506 | |
| 351 | 507 | $(document).trigger('wpbc:option:beforeLoad', [ $el, name ]); |
| @@ -353,11 +509,12 @@ | ||
| 353 | 509 | |
| 354 | 510 | $.ajax({ |
| 355 | 511 | url: w.wpbc_option_saver_loader_config.ajax_url, |
| 356 | 512 | type: 'GET', |
| 357 | - data: { | |
| 358 | - action: w.wpbc_option_saver_loader_config.action_load, | |
| 359 | - data_name: name | |
| 513 | + data: { | |
| 514 | + action: w.wpbc_option_saver_loader_config.action_load, | |
| 515 | + nonce: w.wpbc_option_saver_loader_config.load_nonce, | |
| 516 | + data_name: name | |
| 360 | 517 | } |
| 361 | 518 | }) |
| 362 | 519 | .done(function (resp) { |
| 363 | 520 | if (resp && resp.success) { |
| @@ -376,6 +533,8 @@ | ||
| 376 | 533 | .always(function () { |
| 377 | 534 | wpbc_uix_busy_off($el); |
| 378 | 535 | }); |
| 379 | 536 | }; |
| 537 | + | |
| 538 | + wpbc_uix_bind_autosave_on_form_save(); | |
| 380 | 539 | |
| 381 | 540 | }(window, jQuery)); |