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