Diff: STRATO-apps/wordpress_03/app/wp-includes/js/customize-selective-refresh.js
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
/**
2
+
* @output wp-includes/js/customize-selective-refresh.js
3
+
*/
4
+
5
+
/* global jQuery, JSON, _customizePartialRefreshExports, console */
6
+
7
+
/** @namespace wp.customize.selectiveRefresh */
8
+
wp.customize.selectiveRefresh = ( function( $, api ) {
9
+
'use strict';
10
+
var self, Partial, Placement;
11
+
12
+
self = {
13
+
ready: $.Deferred(),
14
+
editShortcutVisibility: new api.Value(),
15
+
data: {
16
+
partials: {},
17
+
renderQueryVar: '',
18
+
l10n: {
19
+
shiftClickToEdit: ''
20
+
}
21
+
},
22
+
currentRequest: null
23
+
};
24
+
25
+
_.extend( self, api.Events );
26
+
27
+
/**
28
+
* A Customizer Partial.
29
+
*
30
+
* A partial provides a rendering of one or more settings according to a template.
31
+
*
32
+
* @memberOf wp.customize.selectiveRefresh
33
+
*
34
+
* @see PHP class WP_Customize_Partial.
35
+
*
36
+
* @class
37
+
* @augments wp.customize.Class
38
+
* @since 4.5.0
39
+
*/
40
+
Partial = self.Partial = api.Class.extend(/** @lends wp.customize.SelectiveRefresh.Partial.prototype */{
41
+
42
+
id: null,
43
+
44
+
/**
45
+
* Default params.
46
+
*
47
+
* @since 4.9.0
48
+
* @var {object}
49
+
*/
50
+
defaults: {
51
+
selector: null,
52
+
primarySetting: null,
53
+
containerInclusive: false,
54
+
fallbackRefresh: true // Note this needs to be false in a front-end editing context.
55
+
},
56
+
57
+
/**
58
+
* Constructor.
59
+
*
60
+
* @since 4.5.0
61
+
*
62
+
* @param {string} id - Unique identifier for the partial instance.
63
+
* @param {Object} options - Options hash for the partial instance.
64
+
* @param {string} options.type - Type of partial (e.g. nav_menu, widget, etc)
65
+
* @param {string} options.selector - jQuery selector to find the container element in the page.
66
+
* @param {Array} options.settings - The IDs for the settings the partial relates to.
67
+
* @param {string} options.primarySetting - The ID for the primary setting the partial renders.
68
+
* @param {boolean} options.fallbackRefresh - Whether to refresh the entire preview in case of a partial refresh failure.
69
+
* @param {Object} [options.params] - Deprecated wrapper for the above properties.
70
+
*/
71
+
initialize: function( id, options ) {
72
+
var partial = this;
73
+
options = options || {};
74
+
partial.id = id;
75
+
76
+
partial.params = _.extend(
77
+
{
78
+
settings: []
79
+
},
80
+
partial.defaults,
81
+
options.params || options
82
+
);
83
+
84
+
partial.deferred = {};
85
+
partial.deferred.ready = $.Deferred();
86
+
87
+
partial.deferred.ready.done( function() {
88
+
partial.ready();
89
+
} );
90
+
},
91
+
92
+
/**
93
+
* Set up the partial.
94
+
*
95
+
* @since 4.5.0
96
+
*/
97
+
ready: function() {
98
+
var partial = this;
99
+
_.each( partial.placements(), function( placement ) {
100
+
$( placement.container ).attr( 'title', self.data.l10n.shiftClickToEdit );
101
+
partial.createEditShortcutForPlacement( placement );
102
+
} );
103
+
$( document ).on( 'click', partial.params.selector, function( e ) {
104
+
if ( ! e.shiftKey ) {
105
+
return;
106
+
}
107
+
e.preventDefault();
108
+
_.each( partial.placements(), function( placement ) {
109
+
if ( $( placement.container ).is( e.currentTarget ) ) {
110
+
partial.showControl();
111
+
}
112
+
} );
113
+
} );
114
+
},
115
+
116
+
/**
117
+
* Create and show the edit shortcut for a given partial placement container.
118
+
*
119
+
* @since 4.7.0
120
+
* @access public
121
+
*
122
+
* @param {Placement} placement The placement container element.
123
+
* @return {void}
124
+
*/
125
+
createEditShortcutForPlacement: function( placement ) {
126
+
var partial = this, $shortcut, $placementContainer, illegalAncestorSelector, illegalContainerSelector;
127
+
if ( ! placement.container ) {
128
+
return;
129
+
}
130
+
$placementContainer = $( placement.container );
131
+
illegalAncestorSelector = 'head';
132
+
illegalContainerSelector = 'area, audio, base, bdi, bdo, br, button, canvas, col, colgroup, command, datalist, embed, head, hr, html, iframe, img, input, keygen, label, link, map, math, menu, meta, noscript, object, optgroup, option, param, progress, rp, rt, ruby, script, select, source, style, svg, table, tbody, textarea, tfoot, thead, title, tr, track, video, wbr';
133
+
if ( ! $placementContainer.length || $placementContainer.is( illegalContainerSelector ) || $placementContainer.closest( illegalAncestorSelector ).length ) {
134
+
return;
135
+
}
136
+
$shortcut = partial.createEditShortcut();
137
+
$shortcut.on( 'click', function( event ) {
138
+
event.preventDefault();
139
+
event.stopPropagation();
140
+
partial.showControl();
141
+
} );
142
+
partial.addEditShortcutToPlacement( placement, $shortcut );
143
+
},
144
+
145
+
/**
146
+
* Add an edit shortcut to the placement container.
147
+
*
148
+
* @since 4.7.0
149
+
* @access public
150
+
*
151
+
* @param {Placement} placement The placement for the partial.
152
+
* @param {jQuery} $editShortcut The shortcut element as a jQuery object.
153
+
* @return {void}
154
+
*/
155
+
addEditShortcutToPlacement: function( placement, $editShortcut ) {
156
+
var $placementContainer = $( placement.container );
157
+
$placementContainer.prepend( $editShortcut );
158
+
if ( ! $placementContainer.is( ':visible' ) || 'none' === $placementContainer.css( 'display' ) ) {
159
+
$editShortcut.addClass( 'customize-partial-edit-shortcut-hidden' );
160
+
}
161
+
},
162
+
163
+
/**
164
+
* Return the unique class name for the edit shortcut button for this partial.
165
+
*
166
+
* @since 4.7.0
167
+
* @access public
168
+
*
169
+
* @return {string} Partial ID converted into a class name for use in shortcut.
170
+
*/
171
+
getEditShortcutClassName: function() {
172
+
var partial = this, cleanId;
173
+
cleanId = partial.id.replace( /]/g, '' ).replace( /\[/g, '-' );
174
+
return 'customize-partial-edit-shortcut-' + cleanId;
175
+
},
176
+
177
+
/**
178
+
* Return the appropriate translated string for the edit shortcut button.
179
+
*
180
+
* @since 4.7.0
181
+
* @access public
182
+
*
183
+
* @return {string} Tooltip for edit shortcut.
184
+
*/
185
+
getEditShortcutTitle: function() {
186
+
var partial = this, l10n = self.data.l10n;
187
+
switch ( partial.getType() ) {
188
+
case 'widget':
189
+
return l10n.clickEditWidget;
190
+
case 'blogname':
191
+
return l10n.clickEditTitle;
192
+
case 'blogdescription':
193
+
return l10n.clickEditTitle;
194
+
case 'nav_menu':
195
+
return l10n.clickEditMenu;
196
+
default:
197
+
return l10n.clickEditMisc;
198
+
}
199
+
},
200
+
201
+
/**
202
+
* Return the type of this partial
203
+
*
204
+
* Will use `params.type` if set, but otherwise will try to infer type from settingId.
205
+
*
206
+
* @since 4.7.0
207
+
* @access public
208
+
*
209
+
* @return {string} Type of partial derived from type param or the related setting ID.
210
+
*/
211
+
getType: function() {
212
+
var partial = this, settingId;
213
+
settingId = partial.params.primarySetting || _.first( partial.settings() ) || 'unknown';
214
+
if ( partial.params.type ) {
215
+
return partial.params.type;
216
+
}
217
+
if ( settingId.match( /^nav_menu_instance\[/ ) ) {
218
+
return 'nav_menu';
219
+
}
220
+
if ( settingId.match( /^widget_.+\[\d+]$/ ) ) {
221
+
return 'widget';
222
+
}
223
+
return settingId;
224
+
},
225
+
226
+
/**
227
+
* Create an edit shortcut button for this partial.
228
+
*
229
+
* @since 4.7.0
230
+
* @access public
231
+
*
232
+
* @return {jQuery} The edit shortcut button element.
233
+
*/
234
+
createEditShortcut: function() {
235
+
var partial = this, shortcutTitle, $buttonContainer, $button, $image;
236
+
shortcutTitle = partial.getEditShortcutTitle();
237
+
$buttonContainer = $( '<span>', {
238
+
'class': 'customize-partial-edit-shortcut ' + partial.getEditShortcutClassName()
239
+
} );
240
+
$button = $( '<button>', {
241
+
'aria-label': shortcutTitle,
242
+
'title': shortcutTitle,
243
+
'class': 'customize-partial-edit-shortcut-button'
244
+
} );
245
+
$image = $( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13.89 3.39l2.71 2.72c.46.46.42 1.24.03 1.64l-8.01 8.02-5.56 1.16 1.16-5.58s7.6-7.63 7.99-8.03c.39-.39 1.22-.39 1.68.07zm-2.73 2.79l-5.59 5.61 1.11 1.11 5.54-5.65zm-2.97 8.23l5.58-5.6-1.07-1.08-5.59 5.6z"/></svg>' );
246
+
$button.append( $image );
247
+
$buttonContainer.append( $button );
248
+
return $buttonContainer;
249
+
},
250
+
251
+
/**
252
+
* Find all placements for this partial in the document.
253
+
*
254
+
* @since 4.5.0
255
+
*
256
+
* @return {Array.<Placement>}
257
+
*/
258
+
placements: function() {
259
+
var partial = this, selector;
260
+
261
+
selector = partial.params.selector || '';
262
+
if ( selector ) {
263
+
selector += ', ';
264
+
}
265
+
selector += '[data-customize-partial-id="' + partial.id + '"]'; // @todo Consider injecting customize-partial-id-${id} classnames instead.
266
+
267
+
return $( selector ).map( function() {
268
+
var container = $( this ), context;
269
+
270
+
context = container.data( 'customize-partial-placement-context' );
271
+
if ( _.isString( context ) && '{' === context.substr( 0, 1 ) ) {
272
+
throw new Error( 'context JSON parse error' );
273
+
}
274
+
275
+
return new Placement( {
276
+
partial: partial,
277
+
container: container,
278
+
context: context
279
+
} );
280
+
} ).get();
281
+
},
282
+
283
+
/**
284
+
* Get list of setting IDs related to this partial.
285
+
*
286
+
* @since 4.5.0
287
+
*
288
+
* @return {string[]}
289
+
*/
290
+
settings: function() {
291
+
var partial = this;
292
+
if ( partial.params.settings && 0 !== partial.params.settings.length ) {
293
+
return partial.params.settings;
294
+
} else if ( partial.params.primarySetting ) {
295
+
return [ partial.params.primarySetting ];
296
+
} else {
297
+
return [ partial.id ];
298
+
}
299
+
},
300
+
301
+
/**
302
+
* Return whether the setting is related to the partial.
303
+
*
304
+
* @since 4.5.0
305
+
*
306
+
* @param {wp.customize.Value|string} setting ID or object for setting.
307
+
* @return {boolean} Whether the setting is related to the partial.
308
+
*/
309
+
isRelatedSetting: function( setting /*... newValue, oldValue */ ) {
310
+
var partial = this;
311
+
if ( _.isString( setting ) ) {
312
+
setting = api( setting );
313
+
}
314
+
if ( ! setting ) {
315
+
return false;
316
+
}
317
+
return -1 !== _.indexOf( partial.settings(), setting.id );
318
+
},
319
+
320
+
/**
321
+
* Show the control to modify this partial's setting(s).
322
+
*
323
+
* This may be overridden for inline editing.
324
+
*
325
+
* @since 4.5.0
326
+
*/
327
+
showControl: function() {
328
+
var partial = this, settingId = partial.params.primarySetting;
329
+
if ( ! settingId ) {
330
+
settingId = _.first( partial.settings() );
331
+
}
332
+
if ( partial.getType() === 'nav_menu' ) {
333
+
if ( partial.params.navMenuArgs.theme_location ) {
334
+
settingId = 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']';
335
+
} else if ( partial.params.navMenuArgs.menu ) {
336
+
settingId = 'nav_menu[' + String( partial.params.navMenuArgs.menu ) + ']';
337
+
}
338
+
}
339
+
api.preview.send( 'focus-control-for-setting', settingId );
340
+
},
341
+
342
+
/**
343
+
* Prepare container for selective refresh.
344
+
*
345
+
* @since 4.5.0
346
+
*
347
+
* @param {Placement} placement
348
+
*/
349
+
preparePlacement: function( placement ) {
350
+
$( placement.container ).addClass( 'customize-partial-refreshing' );
351
+
},
352
+
353
+
/**
354
+
* Reference to the pending promise returned from self.requestPartial().
355
+
*
356
+
* @since 4.5.0
357
+
* @private
358
+
*/
359
+
_pendingRefreshPromise: null,
360
+
361
+
/**
362
+
* Request the new partial and render it into the placements.
363
+
*
364
+
* @since 4.5.0
365
+
*
366
+
* @this {wp.customize.selectiveRefresh.Partial}
367
+
* @return {jQuery.Promise}
368
+
*/
369
+
refresh: function() {
370
+
var partial = this, refreshPromise;
371
+
372
+
refreshPromise = self.requestPartial( partial );
373
+
374
+
if ( ! partial._pendingRefreshPromise ) {
375
+
_.each( partial.placements(), function( placement ) {
376
+
partial.preparePlacement( placement );
377
+
} );
378
+
379
+
refreshPromise.done( function( placements ) {
380
+
_.each( placements, function( placement ) {
381
+
partial.renderContent( placement );
382
+
} );
383
+
} );
384
+
385
+
refreshPromise.fail( function( data, placements ) {
386
+
partial.fallback( data, placements );
387
+
} );
388
+
389
+
// Allow new request when this one finishes.
390
+
partial._pendingRefreshPromise = refreshPromise;
391
+
refreshPromise.always( function() {
392
+
partial._pendingRefreshPromise = null;
393
+
} );
394
+
}
395
+
396
+
return refreshPromise;
397
+
},
398
+
399
+
/**
400
+
* Apply the addedContent in the placement to the document.
401
+
*
402
+
* Note the placement object will have its container and removedNodes
403
+
* properties updated.
404
+
*
405
+
* @since 4.5.0
406
+
*
407
+
* @param {Placement} placement
408
+
* @param {Element|jQuery} [placement.container] - This param will be empty if there was no element matching the selector.
409
+
* @param {string|Object|boolean} placement.addedContent - Rendered HTML content, a data object for JS templates to render, or false if no render.
410
+
* @param {Object} [placement.context] - Optional context information about the container.
411
+
* @return {boolean} Whether the rendering was successful and the fallback was not invoked.
412
+
*/
413
+
renderContent: function( placement ) {
414
+
var partial = this, content, newContainerElement;
415
+
if ( ! placement.container ) {
416
+
partial.fallback( new Error( 'no_container' ), [ placement ] );
417
+
return false;
418
+
}
419
+
placement.container = $( placement.container );
420
+
if ( false === placement.addedContent ) {
421
+
partial.fallback( new Error( 'missing_render' ), [ placement ] );
422
+
return false;
423
+
}
424
+
425
+
// Currently a subclass needs to override renderContent to handle partials returning data object.
426
+
if ( ! _.isString( placement.addedContent ) ) {
427
+
partial.fallback( new Error( 'non_string_content' ), [ placement ] );
428
+
return false;
429
+
}
430
+
431
+
/* jshint ignore:start */
432
+
self.originalDocumentWrite = document.write;
433
+
document.write = function() {
434
+
throw new Error( self.data.l10n.badDocumentWrite );
435
+
};
436
+
/* jshint ignore:end */
437
+
try {
438
+
content = placement.addedContent;
439
+
if ( wp.emoji && wp.emoji.parse && ! $.contains( document.head, placement.container[0] ) ) {
440
+
content = wp.emoji.parse( content );
441
+
}
442
+
443
+
if ( partial.params.containerInclusive ) {
444
+
445
+
// Note that content may be an empty string, and in this case jQuery will just remove the oldContainer.
446
+
newContainerElement = $( content );
447
+
448
+
// Merge the new context on top of the old context.
449
+
placement.context = _.extend(
450
+
placement.context,
451
+
newContainerElement.data( 'customize-partial-placement-context' ) || {}
452
+
);
453
+
newContainerElement.data( 'customize-partial-placement-context', placement.context );
454
+
455
+
placement.removedNodes = placement.container;
456
+
placement.container = newContainerElement;
457
+
placement.removedNodes.replaceWith( placement.container );
458
+
placement.container.attr( 'title', self.data.l10n.shiftClickToEdit );
459
+
} else {
460
+
placement.removedNodes = document.createDocumentFragment();
461
+
while ( placement.container[0].firstChild ) {
462
+
placement.removedNodes.appendChild( placement.container[0].firstChild );
463
+
}
464
+
465
+
placement.container.html( content );
466
+
}
467
+
468
+
placement.container.removeClass( 'customize-render-content-error' );
469
+
} catch ( error ) {
470
+
if ( 'undefined' !== typeof console && console.error ) {
471
+
console.error( partial.id, error );
472
+
}
473
+
partial.fallback( error, [ placement ] );
474
+
}
475
+
/* jshint ignore:start */
476
+
document.write = self.originalDocumentWrite;
477
+
self.originalDocumentWrite = null;
478
+
/* jshint ignore:end */
479
+
480
+
partial.createEditShortcutForPlacement( placement );
481
+
placement.container.removeClass( 'customize-partial-refreshing' );
482
+
483
+
// Prevent placement container from being re-triggered as being rendered among nested partials.
484
+
placement.container.data( 'customize-partial-content-rendered', true );
485
+
486
+
/*
487
+
* Note that the 'wp_audio_shortcode_library' and 'wp_video_shortcode_library' filters
488
+
* will determine whether or not wp.mediaelement is loaded and whether it will
489
+
* initialize audio and video respectively. See also https://core.trac.wordpress.org/ticket/40144
490
+
*/
491
+
if ( wp.mediaelement ) {
492
+
wp.mediaelement.initialize();
493
+
}
494
+
495
+
if ( wp.playlist ) {
496
+
wp.playlist.initialize();
497
+
}
498
+
499
+
/**
500
+
* Announce when a partial's placement has been rendered so that dynamic elements can be re-built.
501
+
*/
502
+
self.trigger( 'partial-content-rendered', placement );
503
+
return true;
504
+
},
505
+
506
+
/**
507
+
* Handle fail to render partial.
508
+
*
509
+
* The first argument is either the failing jqXHR or an Error object, and the second argument is the array of containers.
510
+
*
511
+
* @since 4.5.0
512
+
*/
513
+
fallback: function() {
514
+
var partial = this;
515
+
if ( partial.params.fallbackRefresh ) {
516
+
self.requestFullRefresh();
517
+
}
518
+
}
519
+
} );
520
+
521
+
/**
522
+
* A Placement for a Partial.
523
+
*
524
+
* A partial placement is the actual physical representation of a partial for a given context.
525
+
* It also may have information in relation to how a placement may have just changed.
526
+
* The placement is conceptually similar to a DOM Range or MutationRecord.
527
+
*
528
+
* @memberOf wp.customize.selectiveRefresh
529
+
*
530
+
* @class Placement
531
+
* @augments wp.customize.Class
532
+
* @since 4.5.0
533
+
*/
534
+
self.Placement = Placement = api.Class.extend(/** @lends wp.customize.selectiveRefresh.prototype */{
535
+
536
+
/**
537
+
* The partial with which the container is associated.
538
+
*
539
+
* @param {wp.customize.selectiveRefresh.Partial}
540
+
*/
541
+
partial: null,
542
+
543
+
/**
544
+
* DOM element which contains the placement's contents.
545
+
*
546
+
* This will be null if the startNode and endNode do not point to the same
547
+
* DOM element, such as in the case of a sidebar partial.
548
+
* This container element itself will be replaced for partials that
549
+
* have containerInclusive param defined as true.
550
+
*/
551
+
container: null,
552
+
553
+
/**
554
+
* DOM node for the initial boundary of the placement.
555
+
*
556
+
* This will normally be the same as endNode since most placements appear as elements.
557
+
* This is primarily useful for widget sidebars which do not have intrinsic containers, but
558
+
* for which an HTML comment is output before to mark the starting position.
559
+
*/
560
+
startNode: null,
561
+
562
+
/**
563
+
* DOM node for the terminal boundary of the placement.
564
+
*
565
+
* This will normally be the same as startNode since most placements appear as elements.
566
+
* This is primarily useful for widget sidebars which do not have intrinsic containers, but
567
+
* for which an HTML comment is output before to mark the ending position.
568
+
*/
569
+
endNode: null,
570
+
571
+
/**
572
+
* Context data.
573
+
*
574
+
* This provides information about the placement which is included in the request
575
+
* in order to render the partial properly.
576
+
*
577
+
* @param {object}
578
+
*/
579
+
context: null,
580
+
581
+
/**
582
+
* The content for the partial when refreshed.
583
+
*
584
+
* @param {string}
585
+
*/
586
+
addedContent: null,
587
+
588
+
/**
589
+
* DOM node(s) removed when the partial is refreshed.
590
+
*
591
+
* If the partial is containerInclusive, then the removedNodes will be
592
+
* the single Element that was the partial's former placement. If the
593
+
* partial is not containerInclusive, then the removedNodes will be a
594
+
* documentFragment containing the nodes removed.
595
+
*
596
+
* @param {Element|DocumentFragment}
597
+
*/
598
+
removedNodes: null,
599
+
600
+
/**
601
+
* Constructor.
602
+
*
603
+
* @since 4.5.0
604
+
*
605
+
* @param {Object} args
606
+
* @param {Partial} args.partial
607
+
* @param {jQuery|Element} [args.container]
608
+
* @param {Node} [args.startNode]
609
+
* @param {Node} [args.endNode]
610
+
* @param {Object} [args.context]
611
+
* @param {string} [args.addedContent]
612
+
* @param {jQuery|DocumentFragment} [args.removedNodes]
613
+
*/
614
+
initialize: function( args ) {
615
+
var placement = this;
616
+
617
+
args = _.extend( {}, args || {} );
618
+
if ( ! args.partial || ! args.partial.extended( Partial ) ) {
619
+
throw new Error( 'Missing partial' );
620
+
}
621
+
args.context = args.context || {};
622
+
if ( args.container ) {
623
+
args.container = $( args.container );
624
+
}
625
+
626
+
_.extend( placement, args );
627
+
}
628
+
629
+
});
630
+
631
+
/**
632
+
* Mapping of type names to Partial constructor subclasses.
633
+
*
634
+
* @since 4.5.0
635
+
*
636
+
* @type {Object.<string, wp.customize.selectiveRefresh.Partial>}
637
+
*/
638
+
self.partialConstructor = {};
639
+
640
+
self.partial = new api.Values({ defaultConstructor: Partial });
641
+
642
+
/**
643
+
* Get the POST vars for a Customizer preview request.
644
+
*
645
+
* @since 4.5.0
646
+
* @see wp.customize.previewer.query()
647
+
*
648
+
* @return {Object}
649
+
*/
650
+
self.getCustomizeQuery = function() {
651
+
var dirtyCustomized = {};
652
+
api.each( function( value, key ) {
653
+
if ( value._dirty ) {
654
+
dirtyCustomized[ key ] = value();
655
+
}
656
+
} );
657
+
658
+
return {
659
+
wp_customize: 'on',
660
+
nonce: api.settings.nonce.preview,
661
+
customize_theme: api.settings.theme.stylesheet,
662
+
customized: JSON.stringify( dirtyCustomized ),
663
+
customize_changeset_uuid: api.settings.changeset.uuid
664
+
};
665
+
};
666
+
667
+
/**
668
+
* Currently-requested partials and their associated deferreds.
669
+
*
670
+
* @since 4.5.0
671
+
* @type {Object<string, { deferred: jQuery.Promise, partial: wp.customize.selectiveRefresh.Partial }>}
672
+
*/
673
+
self._pendingPartialRequests = {};
674
+
675
+
/**
676
+
* Timeout ID for the current request, or null if no request is current.
677
+
*
678
+
* @since 4.5.0
679
+
* @type {number|null}
680
+
* @private
681
+
*/
682
+
self._debouncedTimeoutId = null;
683
+
684
+
/**
685
+
* Current jqXHR for the request to the partials.
686
+
*
687
+
* @since 4.5.0
688
+
* @type {jQuery.jqXHR|null}
689
+
* @private
690
+
*/
691
+
self._currentRequest = null;
692
+
693
+
/**
694
+
* Request full page refresh.
695
+
*
696
+
* When selective refresh is embedded in the context of front-end editing, this request
697
+
* must fail or else changes will be lost, unless transactions are implemented.
698
+
*
699
+
* @since 4.5.0
700
+
*/
701
+
self.requestFullRefresh = function() {
702
+
api.preview.send( 'refresh' );
703
+
};
704
+
705
+
/**
706
+
* Request a re-rendering of a partial.
707
+
*
708
+
* @since 4.5.0
709
+
*
710
+
* @param {wp.customize.selectiveRefresh.Partial} partial
711
+
* @return {jQuery.Promise}
712
+
*/
713
+
self.requestPartial = function( partial ) {
714
+
var partialRequest;
715
+
716
+
if ( self._debouncedTimeoutId ) {
717
+
clearTimeout( self._debouncedTimeoutId );
718
+
self._debouncedTimeoutId = null;
719
+
}
720
+
if ( self._currentRequest ) {
721
+
self._currentRequest.abort();
722
+
self._currentRequest = null;
723
+
}
724
+
725
+
partialRequest = self._pendingPartialRequests[ partial.id ];
726
+
if ( ! partialRequest || 'pending' !== partialRequest.deferred.state() ) {
727
+
partialRequest = {
728
+
deferred: $.Deferred(),
729
+
partial: partial
730
+
};
731
+
self._pendingPartialRequests[ partial.id ] = partialRequest;
732
+
}
733
+
734
+
// Prevent leaking partial into debounced timeout callback.
735
+
partial = null;
736
+
737
+
self._debouncedTimeoutId = setTimeout(
738
+
function() {
739
+
var data, partialPlacementContexts, partialsPlacements, request;
740
+
741
+
self._debouncedTimeoutId = null;
742
+
data = self.getCustomizeQuery();
743
+
744
+
/*
745
+
* It is key that the containers be fetched exactly at the point of the request being
746
+
* made, because the containers need to be mapped to responses by array indices.
747
+
*/
748
+
partialsPlacements = {};
749
+
750
+
partialPlacementContexts = {};
751
+
752
+
_.each( self._pendingPartialRequests, function( pending, partialId ) {
753
+
partialsPlacements[ partialId ] = pending.partial.placements();
754
+
if ( ! self.partial.has( partialId ) ) {
755
+
pending.deferred.rejectWith( pending.partial, [ new Error( 'partial_removed' ), partialsPlacements[ partialId ] ] );
756
+
} else {
757
+
/*
758
+
* Note that this may in fact be an empty array. In that case, it is the responsibility
759
+
* of the Partial subclass instance to know where to inject the response, or else to
760
+
* just issue a refresh (default behavior). The data being returned with each container
761
+
* is the context information that may be needed to render certain partials, such as
762
+
* the contained sidebar for rendering widgets or what the nav menu args are for a menu.
763
+
*/
764
+
partialPlacementContexts[ partialId ] = _.map( partialsPlacements[ partialId ], function( placement ) {
765
+
return placement.context || {};
766
+
} );
767
+
}
768
+
} );
769
+
770
+
data.partials = JSON.stringify( partialPlacementContexts );
771
+
data[ self.data.renderQueryVar ] = '1';
772
+
773
+
request = self._currentRequest = wp.ajax.send( null, {
774
+
data: data,
775
+
url: api.settings.url.self
776
+
} );
777
+
778
+
request.done( function( data ) {
779
+
780
+
/**
781
+
* Announce the data returned from a request to render partials.
782
+
*
783
+
* The data is filtered on the server via customize_render_partials_response
784
+
* so plugins can inject data from the server to be utilized
785
+
* on the client via this event. Plugins may use this filter
786
+
* to communicate script and style dependencies that need to get
787
+
* injected into the page to support the rendered partials.
788
+
* This is similar to the 'saved' event.
789
+
*/
790
+
self.trigger( 'render-partials-response', data );
791
+
792
+
// Relay errors (warnings) captured during rendering and relay to console.
793
+
if ( data.errors && 'undefined' !== typeof console && console.warn ) {
794
+
_.each( data.errors, function( error ) {
795
+
console.warn( error );
796
+
} );
797
+
}
798
+
799
+
/*
800
+
* Note that data is an array of items that correspond to the array of
801
+
* containers that were submitted in the request. So we zip up the
802
+
* array of containers with the array of contents for those containers,
803
+
* and send them into .
804
+
*/
805
+
_.each( self._pendingPartialRequests, function( pending, partialId ) {
806
+
var placementsContents;
807
+
if ( ! _.isArray( data.contents[ partialId ] ) ) {
808
+
pending.deferred.rejectWith( pending.partial, [ new Error( 'unrecognized_partial' ), partialsPlacements[ partialId ] ] );
809
+
} else {
810
+
placementsContents = _.map( data.contents[ partialId ], function( content, i ) {
811
+
var partialPlacement = partialsPlacements[ partialId ][ i ];
812
+
if ( partialPlacement ) {
813
+
partialPlacement.addedContent = content;
814
+
} else {
815
+
partialPlacement = new Placement( {
816
+
partial: pending.partial,
817
+
addedContent: content
818
+
} );
819
+
}
820
+
return partialPlacement;
821
+
} );
822
+
pending.deferred.resolveWith( pending.partial, [ placementsContents ] );
823
+
}
824
+
} );
825
+
self._pendingPartialRequests = {};
826
+
} );
827
+
828
+
request.fail( function( data, statusText ) {
829
+
830
+
/*
831
+
* Ignore failures caused by partial.currentRequest.abort()
832
+
* The pending deferreds will remain in self._pendingPartialRequests
833
+
* for re-use with the next request.
834
+
*/
835
+
if ( 'abort' === statusText ) {
836
+
return;
837
+
}
838
+
839
+
_.each( self._pendingPartialRequests, function( pending, partialId ) {
840
+
pending.deferred.rejectWith( pending.partial, [ data, partialsPlacements[ partialId ] ] );
841
+
} );
842
+
self._pendingPartialRequests = {};
843
+
} );
844
+
},
845
+
api.settings.timeouts.selectiveRefresh
846
+
);
847
+
848
+
return partialRequest.deferred.promise();
849
+
};
850
+
851
+
/**
852
+
* Add partials for any nav menu container elements in the document.
853
+
*
854
+
* This method may be called multiple times. Containers that already have been
855
+
* seen will be skipped.
856
+
*
857
+
* @since 4.5.0
858
+
*
859
+
* @param {jQuery|HTMLElement} [rootElement]
860
+
* @param {object} [options]
861
+
* @param {boolean=true} [options.triggerRendered]
862
+
*/
863
+
self.addPartials = function( rootElement, options ) {
864
+
var containerElements;
865
+
if ( ! rootElement ) {
866
+
rootElement = document.documentElement;
867
+
}
868
+
rootElement = $( rootElement );
869
+
options = _.extend(
870
+
{
871
+
triggerRendered: true
872
+
},
873
+
options || {}
874
+
);
875
+
876
+
containerElements = rootElement.find( '[data-customize-partial-id]' );
877
+
if ( rootElement.is( '[data-customize-partial-id]' ) ) {
878
+
containerElements = containerElements.add( rootElement );
879
+
}
880
+
containerElements.each( function() {
881
+
var containerElement = $( this ), partial, placement, id, Constructor, partialOptions, containerContext;
882
+
id = containerElement.data( 'customize-partial-id' );
883
+
if ( ! id ) {
884
+
return;
885
+
}
886
+
containerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
887
+
888
+
partial = self.partial( id );
889
+
if ( ! partial ) {
890
+
partialOptions = containerElement.data( 'customize-partial-options' ) || {};
891
+
partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
892
+
Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
893
+
partial = new Constructor( id, partialOptions );
894
+
self.partial.add( partial );
895
+
}
896
+
897
+
/*
898
+
* Only trigger renders on (nested) partials that have been not been
899
+
* handled yet. An example where this would apply is a nav menu
900
+
* embedded inside of a navigation menu widget. When the widget's title
901
+
* is updated, the entire widget will re-render and then the event
902
+
* will be triggered for the nested nav menu to do any initialization.
903
+
*/
904
+
if ( options.triggerRendered && ! containerElement.data( 'customize-partial-content-rendered' ) ) {
905
+
906
+
placement = new Placement( {
907
+
partial: partial,
908
+
context: containerContext,
909
+
container: containerElement
910
+
} );
911
+
912
+
$( placement.container ).attr( 'title', self.data.l10n.shiftClickToEdit );
913
+
partial.createEditShortcutForPlacement( placement );
914
+
915
+
/**
916
+
* Announce when a partial's nested placement has been re-rendered.
917
+
*/
918
+
self.trigger( 'partial-content-rendered', placement );
919
+
}
920
+
containerElement.data( 'customize-partial-content-rendered', true );
921
+
} );
922
+
};
923
+
924
+
api.bind( 'preview-ready', function() {
925
+
var handleSettingChange, watchSettingChange, unwatchSettingChange;
926
+
927
+
_.extend( self.data, _customizePartialRefreshExports );
928
+
929
+
// Create the partial JS models.
930
+
_.each( self.data.partials, function( data, id ) {
931
+
var Constructor, partial = self.partial( id );
932
+
if ( ! partial ) {
933
+
Constructor = self.partialConstructor[ data.type ] || self.Partial;
934
+
partial = new Constructor(
935
+
id,
936
+
_.extend( { params: data }, data ) // Inclusion of params alias is for back-compat for custom partials that expect to augment this property.
937
+
);
938
+
self.partial.add( partial );
939
+
} else {
940
+
_.extend( partial.params, data );
941
+
}
942
+
} );
943
+
944
+
/**
945
+
* Handle change to a setting.
946
+
*
947
+
* Note this is largely needed because adding a 'change' event handler to wp.customize
948
+
* will only include the changed setting object as an argument, not including the
949
+
* new value or the old value.
950
+
*
951
+
* @since 4.5.0
952
+
* @this {wp.customize.Setting}
953
+
*
954
+
* @param {*|null} newValue New value, or null if the setting was just removed.
955
+
* @param {*|null} oldValue Old value, or null if the setting was just added.
956
+
*/
957
+
handleSettingChange = function( newValue, oldValue ) {
958
+
var setting = this;
959
+
self.partial.each( function( partial ) {
960
+
if ( partial.isRelatedSetting( setting, newValue, oldValue ) ) {
961
+
partial.refresh();
962
+
}
963
+
} );
964
+
};
965
+
966
+
/**
967
+
* Trigger the initial change for the added setting, and watch for changes.
968
+
*
969
+
* @since 4.5.0
970
+
* @this {wp.customize.Values}
971
+
*
972
+
* @param {wp.customize.Setting} setting
973
+
*/
974
+
watchSettingChange = function( setting ) {
975
+
handleSettingChange.call( setting, setting(), null );
976
+
setting.bind( handleSettingChange );
977
+
};
978
+
979
+
/**
980
+
* Trigger the final change for the removed setting, and unwatch for changes.
981
+
*
982
+
* @since 4.5.0
983
+
* @this {wp.customize.Values}
984
+
*
985
+
* @param {wp.customize.Setting} setting
986
+
*/
987
+
unwatchSettingChange = function( setting ) {
988
+
handleSettingChange.call( setting, null, setting() );
989
+
setting.unbind( handleSettingChange );
990
+
};
991
+
992
+
api.bind( 'add', watchSettingChange );
993
+
api.bind( 'remove', unwatchSettingChange );
994
+
api.each( function( setting ) {
995
+
setting.bind( handleSettingChange );
996
+
} );
997
+
998
+
// Add (dynamic) initial partials that are declared via data-* attributes.
999
+
self.addPartials( document.documentElement, {
1000
+
triggerRendered: false
1001
+
} );
1002
+
1003
+
// Add new dynamic partials when the document changes.
1004
+
if ( 'undefined' !== typeof MutationObserver ) {
1005
+
self.mutationObserver = new MutationObserver( function( mutations ) {
1006
+
_.each( mutations, function( mutation ) {
1007
+
self.addPartials( $( mutation.target ) );
1008
+
} );
1009
+
} );
1010
+
self.mutationObserver.observe( document.documentElement, {
1011
+
childList: true,
1012
+
subtree: true
1013
+
} );
1014
+
}
1015
+
1016
+
/**
1017
+
* Handle rendering of partials.
1018
+
*
1019
+
* @param {api.selectiveRefresh.Placement} placement
1020
+
*/
1021
+
api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
1022
+
if ( placement.container ) {
1023
+
self.addPartials( placement.container );
1024
+
}
1025
+
} );
1026
+
1027
+
/**
1028
+
* Handle setting validities in partial refresh response.
1029
+
*
1030
+
* @param {object} data Response data.
1031
+
* @param {object} data.setting_validities Setting validities.
1032
+
*/
1033
+
api.selectiveRefresh.bind( 'render-partials-response', function handleSettingValiditiesResponse( data ) {
1034
+
if ( data.setting_validities ) {
1035
+
api.preview.send( 'selective-refresh-setting-validities', data.setting_validities );
1036
+
}
1037
+
} );
1038
+
1039
+
api.preview.bind( 'edit-shortcut-visibility', function( visibility ) {
1040
+
api.selectiveRefresh.editShortcutVisibility.set( visibility );
1041
+
} );
1042
+
api.selectiveRefresh.editShortcutVisibility.bind( function( visibility ) {
1043
+
var body = $( document.body ), shouldAnimateHide;
1044
+
1045
+
shouldAnimateHide = ( 'hidden' === visibility && body.hasClass( 'customize-partial-edit-shortcuts-shown' ) && ! body.hasClass( 'customize-partial-edit-shortcuts-hidden' ) );
1046
+
body.toggleClass( 'customize-partial-edit-shortcuts-hidden', shouldAnimateHide );
1047
+
body.toggleClass( 'customize-partial-edit-shortcuts-shown', 'visible' === visibility );
1048
+
} );
1049
+
1050
+
api.preview.bind( 'active', function() {
1051
+
1052
+
// Make all partials ready.
1053
+
self.partial.each( function( partial ) {
1054
+
partial.deferred.ready.resolve();
1055
+
} );
1056
+
1057
+
// Make all partials added henceforth as ready upon add.
1058
+
self.partial.bind( 'add', function( partial ) {
1059
+
partial.deferred.ready.resolve();
1060
+
} );
1061
+
} );
1062
+
1063
+
} );
1064
+
1065
+
return self;
1066
+
}( jQuery, wp.customize ) );
1067
+