]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/customize-selective-refresh.js
Wordpress 4.6
[autoinstalls/wordpress.git] / wp-includes / js / customize-selective-refresh.js
1 /* global jQuery, JSON, _customizePartialRefreshExports, console */
2
3 wp.customize.selectiveRefresh = ( function( $, api ) {
4         'use strict';
5         var self, Partial, Placement;
6
7         self = {
8                 ready: $.Deferred(),
9                 data: {
10                         partials: {},
11                         renderQueryVar: '',
12                         l10n: {
13                                 shiftClickToEdit: ''
14                         },
15                         refreshBuffer: 250
16                 },
17                 currentRequest: null
18         };
19
20         _.extend( self, api.Events );
21
22         /**
23          * A Customizer Partial.
24          *
25          * A partial provides a rendering of one or more settings according to a template.
26          *
27          * @see PHP class WP_Customize_Partial.
28          *
29          * @class
30          * @augments wp.customize.Class
31          * @since 4.5.0
32          *
33          * @param {string} id                              Unique identifier for the control instance.
34          * @param {object} options                         Options hash for the control instance.
35          * @param {object} options.params
36          * @param {string} options.params.type             Type of partial (e.g. nav_menu, widget, etc)
37          * @param {string} options.params.selector         jQuery selector to find the container element in the page.
38          * @param {array}  options.params.settings         The IDs for the settings the partial relates to.
39          * @param {string} options.params.primarySetting   The ID for the primary setting the partial renders.
40          * @param {bool}   options.params.fallbackRefresh  Whether to refresh the entire preview in case of a partial refresh failure.
41          */
42         Partial = self.Partial = api.Class.extend({
43
44                 id: null,
45
46                  /**
47                  * Constructor.
48                  *
49                  * @since 4.5.0
50                  *
51                  * @param {string} id - Partial ID.
52                  * @param {Object} options
53                  * @param {Object} options.params
54                  */
55                 initialize: function( id, options ) {
56                         var partial = this;
57                         options = options || {};
58                         partial.id = id;
59
60                         partial.params = _.extend(
61                                 {
62                                         selector: null,
63                                         settings: [],
64                                         primarySetting: null,
65                                         containerInclusive: false,
66                                         fallbackRefresh: true // Note this needs to be false in a front-end editing context.
67                                 },
68                                 options.params || {}
69                         );
70
71                         partial.deferred = {};
72                         partial.deferred.ready = $.Deferred();
73
74                         partial.deferred.ready.done( function() {
75                                 partial.ready();
76                         } );
77                 },
78
79                 /**
80                  * Set up the partial.
81                  *
82                  * @since 4.5.0
83                  */
84                 ready: function() {
85                         var partial = this;
86                         _.each( _.pluck( partial.placements(), 'container' ), function( container ) {
87                                 $( container ).attr( 'title', self.data.l10n.shiftClickToEdit );
88                         } );
89                         $( document ).on( 'click', partial.params.selector, function( e ) {
90                                 if ( ! e.shiftKey ) {
91                                         return;
92                                 }
93                                 e.preventDefault();
94                                 _.each( partial.placements(), function( placement ) {
95                                         if ( $( placement.container ).is( e.currentTarget ) ) {
96                                                 partial.showControl();
97                                         }
98                                 } );
99                         } );
100                 },
101
102                 /**
103                  * Find all placements for this partial int he document.
104                  *
105                  * @since 4.5.0
106                  *
107                  * @return {Array.<Placement>}
108                  */
109                 placements: function() {
110                         var partial = this, selector;
111
112                         selector = partial.params.selector || '';
113                         if ( selector ) {
114                                 selector += ', ';
115                         }
116                         selector += '[data-customize-partial-id="' + partial.id + '"]'; // @todo Consider injecting customize-partial-id-${id} classnames instead.
117
118                         return $( selector ).map( function() {
119                                 var container = $( this ), context;
120
121                                 context = container.data( 'customize-partial-placement-context' );
122                                 if ( _.isString( context ) && '{' === context.substr( 0, 1 ) ) {
123                                         throw new Error( 'context JSON parse error' );
124                                 }
125
126                                 return new Placement( {
127                                         partial: partial,
128                                         container: container,
129                                         context: context
130                                 } );
131                         } ).get();
132                 },
133
134                 /**
135                  * Get list of setting IDs related to this partial.
136                  *
137                  * @since 4.5.0
138                  *
139                  * @return {String[]}
140                  */
141                 settings: function() {
142                         var partial = this;
143                         if ( partial.params.settings && 0 !== partial.params.settings.length ) {
144                                 return partial.params.settings;
145                         } else if ( partial.params.primarySetting ) {
146                                 return [ partial.params.primarySetting ];
147                         } else {
148                                 return [ partial.id ];
149                         }
150                 },
151
152                 /**
153                  * Return whether the setting is related to the partial.
154                  *
155                  * @since 4.5.0
156                  *
157                  * @param {wp.customize.Value|string} setting  ID or object for setting.
158                  * @return {boolean} Whether the setting is related to the partial.
159                  */
160                 isRelatedSetting: function( setting /*... newValue, oldValue */ ) {
161                         var partial = this;
162                         if ( _.isString( setting ) ) {
163                                 setting = api( setting );
164                         }
165                         if ( ! setting ) {
166                                 return false;
167                         }
168                         return -1 !== _.indexOf( partial.settings(), setting.id );
169                 },
170
171                 /**
172                  * Show the control to modify this partial's setting(s).
173                  *
174                  * This may be overridden for inline editing.
175                  *
176                  * @since 4.5.0
177                  */
178                 showControl: function() {
179                         var partial = this, settingId = partial.params.primarySetting;
180                         if ( ! settingId ) {
181                                 settingId = _.first( partial.settings() );
182                         }
183                         api.preview.send( 'focus-control-for-setting', settingId );
184                 },
185
186                 /**
187                  * Prepare container for selective refresh.
188                  *
189                  * @since 4.5.0
190                  *
191                  * @param {Placement} placement
192                  */
193                 preparePlacement: function( placement ) {
194                         $( placement.container ).addClass( 'customize-partial-refreshing' );
195                 },
196
197                 /**
198                  * Reference to the pending promise returned from self.requestPartial().
199                  *
200                  * @since 4.5.0
201                  * @private
202                  */
203                 _pendingRefreshPromise: null,
204
205                 /**
206                  * Request the new partial and render it into the placements.
207                  *
208                  * @since 4.5.0
209                  *
210                  * @this {wp.customize.selectiveRefresh.Partial}
211                  * @return {jQuery.Promise}
212                  */
213                 refresh: function() {
214                         var partial = this, refreshPromise;
215
216                         refreshPromise = self.requestPartial( partial );
217
218                         if ( ! partial._pendingRefreshPromise ) {
219                                 _.each( partial.placements(), function( placement ) {
220                                         partial.preparePlacement( placement );
221                                 } );
222
223                                 refreshPromise.done( function( placements ) {
224                                         _.each( placements, function( placement ) {
225                                                 partial.renderContent( placement );
226                                         } );
227                                 } );
228
229                                 refreshPromise.fail( function( data, placements ) {
230                                         partial.fallback( data, placements );
231                                 } );
232
233                                 // Allow new request when this one finishes.
234                                 partial._pendingRefreshPromise = refreshPromise;
235                                 refreshPromise.always( function() {
236                                         partial._pendingRefreshPromise = null;
237                                 } );
238                         }
239
240                         return refreshPromise;
241                 },
242
243                 /**
244                  * Apply the addedContent in the placement to the document.
245                  *
246                  * Note the placement object will have its container and removedNodes
247                  * properties updated.
248                  *
249                  * @since 4.5.0
250                  *
251                  * @param {Placement}             placement
252                  * @param {Element|jQuery}        [placement.container]  - This param will be empty if there was no element matching the selector.
253                  * @param {string|object|boolean} placement.addedContent - Rendered HTML content, a data object for JS templates to render, or false if no render.
254                  * @param {object}                [placement.context]    - Optional context information about the container.
255                  * @returns {boolean} Whether the rendering was successful and the fallback was not invoked.
256                  */
257                 renderContent: function( placement ) {
258                         var partial = this, content, newContainerElement;
259                         if ( ! placement.container ) {
260                                 partial.fallback( new Error( 'no_container' ), [ placement ] );
261                                 return false;
262                         }
263                         placement.container = $( placement.container );
264                         if ( false === placement.addedContent ) {
265                                 partial.fallback( new Error( 'missing_render' ), [ placement ] );
266                                 return false;
267                         }
268
269                         // Currently a subclass needs to override renderContent to handle partials returning data object.
270                         if ( ! _.isString( placement.addedContent ) ) {
271                                 partial.fallback( new Error( 'non_string_content' ), [ placement ] );
272                                 return false;
273                         }
274
275                         /* jshint ignore:start */
276                         self.orginalDocumentWrite = document.write;
277                         document.write = function() {
278                                 throw new Error( self.data.l10n.badDocumentWrite );
279                         };
280                         /* jshint ignore:end */
281                         try {
282                                 content = placement.addedContent;
283                                 if ( wp.emoji && wp.emoji.parse && ! $.contains( document.head, placement.container[0] ) ) {
284                                         content = wp.emoji.parse( content );
285                                 }
286
287                                 if ( partial.params.containerInclusive ) {
288
289                                         // Note that content may be an empty string, and in this case jQuery will just remove the oldContainer
290                                         newContainerElement = $( content );
291
292                                         // Merge the new context on top of the old context.
293                                         placement.context = _.extend(
294                                                 placement.context,
295                                                 newContainerElement.data( 'customize-partial-placement-context' ) || {}
296                                         );
297                                         newContainerElement.data( 'customize-partial-placement-context', placement.context );
298
299                                         placement.removedNodes = placement.container;
300                                         placement.container = newContainerElement;
301                                         placement.removedNodes.replaceWith( placement.container );
302                                         placement.container.attr( 'title', self.data.l10n.shiftClickToEdit );
303                                 } else {
304                                         placement.removedNodes = document.createDocumentFragment();
305                                         while ( placement.container[0].firstChild ) {
306                                                 placement.removedNodes.appendChild( placement.container[0].firstChild );
307                                         }
308
309                                         placement.container.html( content );
310                                 }
311
312                                 placement.container.removeClass( 'customize-render-content-error' );
313                         } catch ( error ) {
314                                 if ( 'undefined' !== typeof console && console.error ) {
315                                         console.error( partial.id, error );
316                                 }
317                         }
318                         /* jshint ignore:start */
319                         document.write = self.orginalDocumentWrite;
320                         self.orginalDocumentWrite = null;
321                         /* jshint ignore:end */
322
323                         placement.container.removeClass( 'customize-partial-refreshing' );
324
325                         // Prevent placement container from being being re-triggered as being rendered among nested partials.
326                         placement.container.data( 'customize-partial-content-rendered', true );
327
328                         /**
329                          * Announce when a partial's placement has been rendered so that dynamic elements can be re-built.
330                          */
331                         self.trigger( 'partial-content-rendered', placement );
332                         return true;
333                 },
334
335                 /**
336                  * Handle fail to render partial.
337                  *
338                  * The first argument is either the failing jqXHR or an Error object, and the second argument is the array of containers.
339                  *
340                  * @since 4.5.0
341                  */
342                 fallback: function() {
343                         var partial = this;
344                         if ( partial.params.fallbackRefresh ) {
345                                 self.requestFullRefresh();
346                         }
347                 }
348         } );
349
350         /**
351          * A Placement for a Partial.
352          *
353          * A partial placement is the actual physical representation of a partial for a given context.
354          * It also may have information in relation to how a placement may have just changed.
355          * The placement is conceptually similar to a DOM Range or MutationRecord.
356          *
357          * @class
358          * @augments wp.customize.Class
359          * @since 4.5.0
360          */
361         self.Placement = Placement = api.Class.extend({
362
363                 /**
364                  * The partial with which the container is associated.
365                  *
366                  * @param {wp.customize.selectiveRefresh.Partial}
367                  */
368                 partial: null,
369
370                 /**
371                  * DOM element which contains the placement's contents.
372                  *
373                  * This will be null if the startNode and endNode do not point to the same
374                  * DOM element, such as in the case of a sidebar partial.
375                  * This container element itself will be replaced for partials that
376                  * have containerInclusive param defined as true.
377                  */
378                 container: null,
379
380                 /**
381                  * DOM node for the initial boundary of the placement.
382                  *
383                  * This will normally be the same as endNode since most placements appear as elements.
384                  * This is primarily useful for widget sidebars which do not have intrinsic containers, but
385                  * for which an HTML comment is output before to mark the starting position.
386                  */
387                 startNode: null,
388
389                 /**
390                  * DOM node for the terminal boundary of the placement.
391                  *
392                  * This will normally be the same as startNode since most placements appear as elements.
393                  * This is primarily useful for widget sidebars which do not have intrinsic containers, but
394                  * for which an HTML comment is output before to mark the ending position.
395                  */
396                 endNode: null,
397
398                 /**
399                  * Context data.
400                  *
401                  * This provides information about the placement which is included in the request
402                  * in order to render the partial properly.
403                  *
404                  * @param {object}
405                  */
406                 context: null,
407
408                 /**
409                  * The content for the partial when refreshed.
410                  *
411                  * @param {string}
412                  */
413                 addedContent: null,
414
415                 /**
416                  * DOM node(s) removed when the partial is refreshed.
417                  *
418                  * If the partial is containerInclusive, then the removedNodes will be
419                  * the single Element that was the partial's former placement. If the
420                  * partial is not containerInclusive, then the removedNodes will be a
421                  * documentFragment containing the nodes removed.
422                  *
423                  * @param {Element|DocumentFragment}
424                  */
425                 removedNodes: null,
426
427                 /**
428                  * Constructor.
429                  *
430                  * @since 4.5.0
431                  *
432                  * @param {object}                   args
433                  * @param {Partial}                  args.partial
434                  * @param {jQuery|Element}           [args.container]
435                  * @param {Node}                     [args.startNode]
436                  * @param {Node}                     [args.endNode]
437                  * @param {object}                   [args.context]
438                  * @param {string}                   [args.addedContent]
439                  * @param {jQuery|DocumentFragment}  [args.removedNodes]
440                  */
441                 initialize: function( args ) {
442                         var placement = this;
443
444                         args = _.extend( {}, args || {} );
445                         if ( ! args.partial || ! args.partial.extended( Partial ) ) {
446                                 throw new Error( 'Missing partial' );
447                         }
448                         args.context = args.context || {};
449                         if ( args.container ) {
450                                 args.container = $( args.container );
451                         }
452
453                         _.extend( placement, args );
454                 }
455
456         });
457
458         /**
459          * Mapping of type names to Partial constructor subclasses.
460          *
461          * @since 4.5.0
462          *
463          * @type {Object.<string, wp.customize.selectiveRefresh.Partial>}
464          */
465         self.partialConstructor = {};
466
467         self.partial = new api.Values({ defaultConstructor: Partial });
468
469         /**
470          * Get the POST vars for a Customizer preview request.
471          *
472          * @since 4.5.0
473          * @see wp.customize.previewer.query()
474          *
475          * @return {object}
476          */
477         self.getCustomizeQuery = function() {
478                 var dirtyCustomized = {};
479                 api.each( function( value, key ) {
480                         if ( value._dirty ) {
481                                 dirtyCustomized[ key ] = value();
482                         }
483                 } );
484
485                 return {
486                         wp_customize: 'on',
487                         nonce: api.settings.nonce.preview,
488                         theme: api.settings.theme.stylesheet,
489                         customized: JSON.stringify( dirtyCustomized )
490                 };
491         };
492
493         /**
494          * Currently-requested partials and their associated deferreds.
495          *
496          * @since 4.5.0
497          * @type {Object<string, { deferred: jQuery.Promise, partial: wp.customize.selectiveRefresh.Partial }>}
498          */
499         self._pendingPartialRequests = {};
500
501         /**
502          * Timeout ID for the current requesr, or null if no request is current.
503          *
504          * @since 4.5.0
505          * @type {number|null}
506          * @private
507          */
508         self._debouncedTimeoutId = null;
509
510         /**
511          * Current jqXHR for the request to the partials.
512          *
513          * @since 4.5.0
514          * @type {jQuery.jqXHR|null}
515          * @private
516          */
517         self._currentRequest = null;
518
519         /**
520          * Request full page refresh.
521          *
522          * When selective refresh is embedded in the context of front-end editing, this request
523          * must fail or else changes will be lost, unless transactions are implemented.
524          *
525          * @since 4.5.0
526          */
527         self.requestFullRefresh = function() {
528                 api.preview.send( 'refresh' );
529         };
530
531         /**
532          * Request a re-rendering of a partial.
533          *
534          * @since 4.5.0
535          *
536          * @param {wp.customize.selectiveRefresh.Partial} partial
537          * @return {jQuery.Promise}
538          */
539         self.requestPartial = function( partial ) {
540                 var partialRequest;
541
542                 if ( self._debouncedTimeoutId ) {
543                         clearTimeout( self._debouncedTimeoutId );
544                         self._debouncedTimeoutId = null;
545                 }
546                 if ( self._currentRequest ) {
547                         self._currentRequest.abort();
548                         self._currentRequest = null;
549                 }
550
551                 partialRequest = self._pendingPartialRequests[ partial.id ];
552                 if ( ! partialRequest || 'pending' !== partialRequest.deferred.state() ) {
553                         partialRequest = {
554                                 deferred: $.Deferred(),
555                                 partial: partial
556                         };
557                         self._pendingPartialRequests[ partial.id ] = partialRequest;
558                 }
559
560                 // Prevent leaking partial into debounced timeout callback.
561                 partial = null;
562
563                 self._debouncedTimeoutId = setTimeout(
564                         function() {
565                                 var data, partialPlacementContexts, partialsPlacements, request;
566
567                                 self._debouncedTimeoutId = null;
568                                 data = self.getCustomizeQuery();
569
570                                 /*
571                                  * It is key that the containers be fetched exactly at the point of the request being
572                                  * made, because the containers need to be mapped to responses by array indices.
573                                  */
574                                 partialsPlacements = {};
575
576                                 partialPlacementContexts = {};
577
578                                 _.each( self._pendingPartialRequests, function( pending, partialId ) {
579                                         partialsPlacements[ partialId ] = pending.partial.placements();
580                                         if ( ! self.partial.has( partialId ) ) {
581                                                 pending.deferred.rejectWith( pending.partial, [ new Error( 'partial_removed' ), partialsPlacements[ partialId ] ] );
582                                         } else {
583                                                 /*
584                                                  * Note that this may in fact be an empty array. In that case, it is the responsibility
585                                                  * of the Partial subclass instance to know where to inject the response, or else to
586                                                  * just issue a refresh (default behavior). The data being returned with each container
587                                                  * is the context information that may be needed to render certain partials, such as
588                                                  * the contained sidebar for rendering widgets or what the nav menu args are for a menu.
589                                                  */
590                                                 partialPlacementContexts[ partialId ] = _.map( partialsPlacements[ partialId ], function( placement ) {
591                                                         return placement.context || {};
592                                                 } );
593                                         }
594                                 } );
595
596                                 data.partials = JSON.stringify( partialPlacementContexts );
597                                 data[ self.data.renderQueryVar ] = '1';
598
599                                 request = self._currentRequest = wp.ajax.send( null, {
600                                         data: data,
601                                         url: api.settings.url.self
602                                 } );
603
604                                 request.done( function( data ) {
605
606                                         /**
607                                          * Announce the data returned from a request to render partials.
608                                          *
609                                          * The data is filtered on the server via customize_render_partials_response
610                                          * so plugins can inject data from the server to be utilized
611                                          * on the client via this event. Plugins may use this filter
612                                          * to communicate script and style dependencies that need to get
613                                          * injected into the page to support the rendered partials.
614                                          * This is similar to the 'saved' event.
615                                          */
616                                         self.trigger( 'render-partials-response', data );
617
618                                         // Relay errors (warnings) captured during rendering and relay to console.
619                                         if ( data.errors && 'undefined' !== typeof console && console.warn ) {
620                                                 _.each( data.errors, function( error ) {
621                                                         console.warn( error );
622                                                 } );
623                                         }
624
625                                         /*
626                                          * Note that data is an array of items that correspond to the array of
627                                          * containers that were submitted in the request. So we zip up the
628                                          * array of containers with the array of contents for those containers,
629                                          * and send them into .
630                                          */
631                                         _.each( self._pendingPartialRequests, function( pending, partialId ) {
632                                                 var placementsContents;
633                                                 if ( ! _.isArray( data.contents[ partialId ] ) ) {
634                                                         pending.deferred.rejectWith( pending.partial, [ new Error( 'unrecognized_partial' ), partialsPlacements[ partialId ] ] );
635                                                 } else {
636                                                         placementsContents = _.map( data.contents[ partialId ], function( content, i ) {
637                                                                 var partialPlacement = partialsPlacements[ partialId ][ i ];
638                                                                 if ( partialPlacement ) {
639                                                                         partialPlacement.addedContent = content;
640                                                                 } else {
641                                                                         partialPlacement = new Placement( {
642                                                                                 partial: pending.partial,
643                                                                                 addedContent: content
644                                                                         } );
645                                                                 }
646                                                                 return partialPlacement;
647                                                         } );
648                                                         pending.deferred.resolveWith( pending.partial, [ placementsContents ] );
649                                                 }
650                                         } );
651                                         self._pendingPartialRequests = {};
652                                 } );
653
654                                 request.fail( function( data, statusText ) {
655
656                                         /*
657                                          * Ignore failures caused by partial.currentRequest.abort()
658                                          * The pending deferreds will remain in self._pendingPartialRequests
659                                          * for re-use with the next request.
660                                          */
661                                         if ( 'abort' === statusText ) {
662                                                 return;
663                                         }
664
665                                         _.each( self._pendingPartialRequests, function( pending, partialId ) {
666                                                 pending.deferred.rejectWith( pending.partial, [ data, partialsPlacements[ partialId ] ] );
667                                         } );
668                                         self._pendingPartialRequests = {};
669                                 } );
670                         },
671                         self.data.refreshBuffer
672                 );
673
674                 return partialRequest.deferred.promise();
675         };
676
677         /**
678          * Add partials for any nav menu container elements in the document.
679          *
680          * This method may be called multiple times. Containers that already have been
681          * seen will be skipped.
682          *
683          * @since 4.5.0
684          *
685          * @param {jQuery|HTMLElement} [rootElement]
686          * @param {object}             [options]
687          * @param {boolean=true}       [options.triggerRendered]
688          */
689         self.addPartials = function( rootElement, options ) {
690                 var containerElements;
691                 if ( ! rootElement ) {
692                         rootElement = document.documentElement;
693                 }
694                 rootElement = $( rootElement );
695                 options = _.extend(
696                         {
697                                 triggerRendered: true
698                         },
699                         options || {}
700                 );
701
702                 containerElements = rootElement.find( '[data-customize-partial-id]' );
703                 if ( rootElement.is( '[data-customize-partial-id]' ) ) {
704                         containerElements = containerElements.add( rootElement );
705                 }
706                 containerElements.each( function() {
707                         var containerElement = $( this ), partial, id, Constructor, partialOptions, containerContext;
708                         id = containerElement.data( 'customize-partial-id' );
709                         if ( ! id ) {
710                                 return;
711                         }
712                         containerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
713
714                         partial = self.partial( id );
715                         if ( ! partial ) {
716                                 partialOptions = containerElement.data( 'customize-partial-options' ) || {};
717                                 partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
718                                 Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
719                                 partial = new Constructor( id, partialOptions );
720                                 self.partial.add( partial.id, partial );
721                         }
722
723                         /*
724                          * Only trigger renders on (nested) partials that have been not been
725                          * handled yet. An example where this would apply is a nav menu
726                          * embedded inside of a custom menu widget. When the widget's title
727                          * is updated, the entire widget will re-render and then the event
728                          * will be triggered for the nested nav menu to do any initialization.
729                          */
730                         if ( options.triggerRendered && ! containerElement.data( 'customize-partial-content-rendered' ) ) {
731
732                                 /**
733                                  * Announce when a partial's nested placement has been re-rendered.
734                                  */
735                                 self.trigger( 'partial-content-rendered', new Placement( {
736                                         partial: partial,
737                                         context: containerContext,
738                                         container: containerElement
739                                 } ) );
740                         }
741                         containerElement.data( 'customize-partial-content-rendered', true );
742                 } );
743         };
744
745         api.bind( 'preview-ready', function() {
746                 var handleSettingChange, watchSettingChange, unwatchSettingChange;
747
748                 // Polyfill for IE8 to support the document.head attribute.
749                 if ( ! document.head ) {
750                         document.head = $( 'head:first' )[0];
751                 }
752
753                 _.extend( self.data, _customizePartialRefreshExports );
754
755                 // Create the partial JS models.
756                 _.each( self.data.partials, function( data, id ) {
757                         var Constructor, partial = self.partial( id );
758                         if ( ! partial ) {
759                                 Constructor = self.partialConstructor[ data.type ] || self.Partial;
760                                 partial = new Constructor( id, { params: data } );
761                                 self.partial.add( id, partial );
762                         } else {
763                                 _.extend( partial.params, data );
764                         }
765                 } );
766
767                 /**
768                  * Handle change to a setting.
769                  *
770                  * Note this is largely needed because adding a 'change' event handler to wp.customize
771                  * will only include the changed setting object as an argument, not including the
772                  * new value or the old value.
773                  *
774                  * @since 4.5.0
775                  * @this {wp.customize.Setting}
776                  *
777                  * @param {*|null} newValue New value, or null if the setting was just removed.
778                  * @param {*|null} oldValue Old value, or null if the setting was just added.
779                  */
780                 handleSettingChange = function( newValue, oldValue ) {
781                         var setting = this;
782                         self.partial.each( function( partial ) {
783                                 if ( partial.isRelatedSetting( setting, newValue, oldValue ) ) {
784                                         partial.refresh();
785                                 }
786                         } );
787                 };
788
789                 /**
790                  * Trigger the initial change for the added setting, and watch for changes.
791                  *
792                  * @since 4.5.0
793                  * @this {wp.customize.Values}
794                  *
795                  * @param {wp.customize.Setting} setting
796                  */
797                 watchSettingChange = function( setting ) {
798                         handleSettingChange.call( setting, setting(), null );
799                         setting.bind( handleSettingChange );
800                 };
801
802                 /**
803                  * Trigger the final change for the removed setting, and unwatch for changes.
804                  *
805                  * @since 4.5.0
806                  * @this {wp.customize.Values}
807                  *
808                  * @param {wp.customize.Setting} setting
809                  */
810                 unwatchSettingChange = function( setting ) {
811                         handleSettingChange.call( setting, null, setting() );
812                         setting.unbind( handleSettingChange );
813                 };
814
815                 api.bind( 'add', watchSettingChange );
816                 api.bind( 'remove', unwatchSettingChange );
817                 api.each( function( setting ) {
818                         setting.bind( handleSettingChange );
819                 } );
820
821                 // Add (dynamic) initial partials that are declared via data-* attributes.
822                 self.addPartials( document.documentElement, {
823                         triggerRendered: false
824                 } );
825
826                 // Add new dynamic partials when the document changes.
827                 if ( 'undefined' !== typeof MutationObserver ) {
828                         self.mutationObserver = new MutationObserver( function( mutations ) {
829                                 _.each( mutations, function( mutation ) {
830                                         self.addPartials( $( mutation.target ) );
831                                 } );
832                         } );
833                         self.mutationObserver.observe( document.documentElement, {
834                                 childList: true,
835                                 subtree: true
836                         } );
837                 }
838
839                 /**
840                  * Handle rendering of partials.
841                  *
842                  * @param {api.selectiveRefresh.Placement} placement
843                  */
844                 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
845                         if ( placement.container ) {
846                                 self.addPartials( placement.container );
847                         }
848                 } );
849
850                 /**
851                  * Handle setting validities in partial refresh response.
852                  *
853                  * @param {object} data Response data.
854                  * @param {object} data.setting_validities Setting validities.
855                  */
856                 api.selectiveRefresh.bind( 'render-partials-response', function handleSettingValiditiesResponse( data ) {
857                         if ( data.setting_validities ) {
858                                 api.preview.send( 'selective-refresh-setting-validities', data.setting_validities );
859                         }
860                 } );
861
862                 api.preview.bind( 'active', function() {
863
864                         // Make all partials ready.
865                         self.partial.each( function( partial ) {
866                                 partial.deferred.ready.resolve();
867                         } );
868
869                         // Make all partials added henceforth as ready upon add.
870                         self.partial.bind( 'add', function( partial ) {
871                                 partial.deferred.ready.resolve();
872                         } );
873                 } );
874
875         } );
876
877         return self;
878 }( jQuery, wp.customize ) );