| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-includes/js/customize-preview-widgets.js 3 */ 4 5 /* global _wpWidgetCustomizerPreviewSettings */ 6 7 /** 8 * Handles the initialization, refreshing and rendering of widget partials and sidebar widgets. 9 * 10 * @since 4.5.0 11 * 12 * @namespace wp.customize.widgetsPreview 13 * 14 * @param {JQueryStatic} $ The jQuery object. 15 * @param {_.UnderscoreStatic} _ The Underscore.js object. 16 * @param {Object} wp The WordPress global object. 17 * @param {Object} api The Customizer API. 18 * 19 * @return {Object} Widget-related variables. 20 */ 21 wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) { 22 23 var self; 24 25 self = { 26 renderedSidebars: {}, 27 renderedWidgets: {}, 28 registeredSidebars: [], 29 registeredWidgets: {}, 30 widgetSelectors: [], 31 preview: null, 32 l10n: { 33 widgetTooltip: '' 34 }, 35 selectiveRefreshableWidgets: {} 36 }; 37 38 /** 39 * Initializes the widgets preview. 40 * 41 * @since 4.5.0 42 * 43 * @memberOf wp.customize.widgetsPreview 44 * 45 * @return {void} 46 */ 47 self.init = function() { 48 var self = this; 49 50 self.preview = api.preview; 51 if ( ! _.isEmpty( self.selectiveRefreshableWidgets ) ) { 52 self.addPartials(); 53 } 54 55 self.buildWidgetSelectors(); 56 self.highlightControls(); 57 58 self.preview.bind( 'highlight-widget', self.highlightWidget ); 59 60 api.preview.bind( 'active', function() { 61 self.highlightControls(); 62 } ); 63 64 /* 65 * Refresh a partial when the controls pane requests it. This is used currently just by the 66 * Gallery widget so that when an attachment's caption is updated in the media modal, 67 * the widget in the preview will then be refreshed to show the change. Normally doing this 68 * would not be necessary because all of the state should be contained inside the changeset, 69 * as everything done in the Customizer should not make a change to the site unless the 70 * changeset itself is published. Attachments are a current exception to this rule. 71 * For a proposal to include attachments in the customized state, see #37887. 72 */ 73 api.preview.bind( 'refresh-widget-partial', function( widgetId ) { 74 var partialId = 'widget[' + widgetId + ']'; 75 if ( api.selectiveRefresh.partial.has( partialId ) ) { 76 api.selectiveRefresh.partial( partialId ).refresh(); 77 } else if ( self.renderedWidgets[ widgetId ] ) { 78 api.preview.send( 'refresh' ); // Fallback in case theme does not support 'customize-selective-refresh-widgets'. 79 } 80 } ); 81 }; 82 83 self.WidgetPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.WidgetPartial.prototype */{ 84 85 /** 86 * Represents a partial widget instance. 87 * 88 * @since 4.5.0 89 * 90 * @constructs 91 * @augments wp.customize.selectiveRefresh.Partial 92 * 93 * @alias wp.customize.widgetsPreview.WidgetPartial 94 * @memberOf wp.customize.widgetsPreview 95 * 96 * @param {string} id The partial's ID. 97 * @param {Object} [options] Options used to initialize the partial's instance. 98 * @param {Object} [options.params] The options parameters. 99 */ 100 initialize: function( id, options ) { 101 var partial = this, matches; 102 matches = id.match( /^widget\[(.+)]$/ ); 103 if ( ! matches ) { 104 throw new Error( 'Illegal id for widget partial.' ); 105 } 106 107 partial.widgetId = matches[1]; 108 partial.widgetIdParts = self.parseWidgetId( partial.widgetId ); 109 options = options || {}; 110 options.params = _.extend( 111 { 112 settings: [ self.getWidgetSettingId( partial.widgetId ) ], 113 containerInclusive: true 114 }, 115 options.params || {} 116 ); 117 118 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); 119 }, 120 121 /** 122 * Refreshes the widget partial. 123 * 124 * @since 4.5.0 125 * 126 * @return {JQuery.Promise<*>} Promise for the request to render the partial. 127 */ 128 refresh: function() { 129 var partial = this, refreshDeferred; 130 if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) { 131 refreshDeferred = $.Deferred(); 132 refreshDeferred.reject(); 133 partial.fallback(); 134 return refreshDeferred.promise(); 135 } else { 136 return api.selectiveRefresh.Partial.prototype.refresh.call( partial ); 137 } 138 }, 139 140 /** 141 * Sends the widget-updated message to the parent so the spinner will get 142 * removed from the widget control. 143 * 144 * @inheritDoc 145 * @param {wp.customize.selectiveRefresh.Placement} placement The placement to render into. 146 * 147 * @return {void} 148 */ 149 renderContent: function( placement ) { 150 var partial = this; 151 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { 152 api.preview.send( 'widget-updated', partial.widgetId ); 153 api.selectiveRefresh.trigger( 'widget-updated', partial ); 154 } 155 } 156 }); 157 158 self.SidebarPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.SidebarPartial.prototype */{ 159 160 /** 161 * Represents a partial widget area. 162 * 163 * @since 4.5.0 164 * 165 * @constructs 166 * @augments wp.customize.selectiveRefresh.Partial 167 * 168 * @memberOf wp.customize.widgetsPreview 169 * @alias wp.customize.widgetsPreview.SidebarPartial 170 * 171 * @param {string} id The partial's ID. 172 * @param {Object} [options] Options used to initialize the partial's instance. 173 * @param {Object} [options.params] The options parameters. 174 */ 175 initialize: function( id, options ) { 176 var partial = this, matches; 177 matches = id.match( /^sidebar\[(.+)]$/ ); 178 if ( ! matches ) { 179 throw new Error( 'Illegal id for sidebar partial.' ); 180 } 181 partial.sidebarId = matches[1]; 182 183 options = options || {}; 184 options.params = _.extend( 185 { 186 settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ] 187 }, 188 options.params || {} 189 ); 190 191 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); 192 193 if ( ! partial.params.sidebarArgs ) { 194 throw new Error( 'The sidebarArgs param was not provided.' ); 195 } 196 if ( partial.params.settings.length > 1 ) { 197 throw new Error( 'Expected SidebarPartial to only have one associated setting' ); 198 } 199 }, 200 201 /** 202 * Sets up the partial. 203 * 204 * @since 4.5.0 205 * 206 * @return {void} 207 */ 208 ready: function() { 209 var sidebarPartial = this; 210 211 // Watch for changes to the sidebar_widgets setting. 212 _.each( sidebarPartial.settings(), function( settingId ) { 213 api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) ); 214 } ); 215 216 // Trigger an event for this sidebar being updated whenever a widget inside is rendered. 217 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { 218 var isAssignedWidgetPartial = ( 219 placement.partial.extended( self.WidgetPartial ) && 220 ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) ) 221 ); 222 if ( isAssignedWidgetPartial ) { 223 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 224 } 225 } ); 226 227 // Make sure that a widget partial has a container in the DOM prior to a refresh. 228 api.bind( 'change', function( widgetSetting ) { 229 var widgetId, parsedId; 230 parsedId = self.parseWidgetSettingId( widgetSetting.id ); 231 if ( ! parsedId ) { 232 return; 233 } 234 widgetId = parsedId.idBase; 235 if ( parsedId.number ) { 236 widgetId += '-' + String( parsedId.number ); 237 } 238 if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) { 239 sidebarPartial.ensureWidgetPlacementContainers( widgetId ); 240 } 241 } ); 242 }, 243 244 /** 245 * Gets the before/after boundary nodes for all instances of this sidebar 246 * (usually one). 247 * 248 * Note that TreeWalker is not implemented in IE8. 249 * 250 * @since 4.5.0 251 * 252 * @return {Array.<{before: Comment, after: Comment, instanceNumber: number}>} 253 * An array with an object for each sidebar instance, containing the 254 * node before and after the sidebar instance and its instance number. 255 */ 256 findDynamicSidebarBoundaryNodes: function() { 257 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal; 258 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/; 259 recursiveCommentTraversal = function( childNodes ) { 260 _.each( childNodes, function( node ) { 261 var matches; 262 if ( 8 === node.nodeType ) { 263 matches = node.nodeValue.match( regExp ); 264 if ( ! matches || matches[2] !== partial.sidebarId ) { 265 return; 266 } 267 if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) { 268 boundaryNodes[ matches[3] ] = { 269 before: null, 270 after: null, 271 instanceNumber: parseInt( matches[3], 10 ) 272 }; 273 } 274 if ( 'dynamic_sidebar_before' === matches[1] ) { 275 boundaryNodes[ matches[3] ].before = node; 276 } else { 277 boundaryNodes[ matches[3] ].after = node; 278 } 279 } else if ( 1 === node.nodeType ) { 280 recursiveCommentTraversal( node.childNodes ); 281 } 282 } ); 283 }; 284 285 recursiveCommentTraversal( document.body.childNodes ); 286 return _.values( boundaryNodes ); 287 }, 288 289 /** 290 * Gets the placements for this partial. 291 * 292 * @since 4.5.0 293 * 294 * @return {wp.customize.selectiveRefresh.Placement[]} A placement for each of the dynamic 295 * sidebar boundary nodes. 296 */ 297 placements: function() { 298 var partial = this; 299 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) { 300 return new api.selectiveRefresh.Placement( { 301 partial: partial, 302 container: null, 303 startNode: boundaryNodes.before, 304 endNode: boundaryNodes.after, 305 context: { 306 instanceNumber: boundaryNodes.instanceNumber 307 } 308 } ); 309 } ); 310 }, 311 312 /** 313 * Get the list of widget IDs associated with this widget area. 314 * 315 * @since 4.5.0 316 * 317 * @throws {Error} If there's no settingId. 318 * @throws {Error} If the setting doesn't exist in the API. 319 * @throws {Error} If the API doesn't pass an array of widget IDs. 320 * 321 * @return {string[]} A shallow copy of the array containing widget IDs. 322 */ 323 getWidgetIds: function() { 324 var sidebarPartial = this, settingId, widgetIds; 325 settingId = sidebarPartial.settings()[0]; 326 if ( ! settingId ) { 327 throw new Error( 'Missing associated setting.' ); 328 } 329 if ( ! api.has( settingId ) ) { 330 throw new Error( 'Setting does not exist.' ); 331 } 332 widgetIds = api( settingId ).get(); 333 if ( ! _.isArray( widgetIds ) ) { 334 throw new Error( 'Expected setting to be array of widget IDs' ); 335 } 336 return widgetIds.slice( 0 ); 337 }, 338 339 /** 340 * Reflows widgets in the sidebar, ensuring they have the proper position in the 341 * DOM. 342 * 343 * @since 4.5.0 344 * 345 * @return {wp.customize.selectiveRefresh.Placement[]} List of placements that were reflowed. 346 */ 347 reflowWidgets: function() { 348 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = []; 349 widgetIds = sidebarPartial.getWidgetIds(); 350 sidebarPlacements = sidebarPartial.placements(); 351 352 widgetPartials = {}; 353 _.each( widgetIds, function( widgetId ) { 354 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' ); 355 if ( widgetPartial ) { 356 widgetPartials[ widgetId ] = widgetPartial; 357 } 358 } ); 359 360 _.each( sidebarPlacements, function( sidebarPlacement ) { 361 var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1; 362 363 // Gather list of widget partial containers in this sidebar, and determine if a sort is needed. 364 _.each( widgetPartials, function( widgetPartial ) { 365 _.each( widgetPartial.placements(), function( widgetPlacement ) { 366 367 if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) { 368 thisPosition = widgetPlacement.container.index(); 369 sidebarWidgets.push( { 370 partial: widgetPartial, 371 placement: widgetPlacement, 372 position: thisPosition 373 } ); 374 if ( thisPosition < lastPosition ) { 375 needsSort = true; 376 } 377 lastPosition = thisPosition; 378 } 379 } ); 380 } ); 381 382 if ( needsSort ) { 383 _.each( sidebarWidgets, function( sidebarWidget ) { 384 sidebarPlacement.endNode.parentNode.insertBefore( 385 sidebarWidget.placement.container[0], 386 sidebarPlacement.endNode 387 ); 388 389 // @todo Rename partial-placement-moved? 390 api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement ); 391 } ); 392 393 sortedSidebarContainers.push( sidebarPlacement ); 394 } 395 } ); 396 397 if ( sortedSidebarContainers.length > 0 ) { 398 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 399 } 400 401 return sortedSidebarContainers; 402 }, 403 404 /** 405 * Makes sure there is a widget instance container in this sidebar for the given 406 * widget ID. 407 * 408 * @since 4.5.0 409 * 410 * @param {string} widgetId The widget ID. 411 * 412 * @return {wp.customize.selectiveRefresh.Partial} The widget instance partial. 413 */ 414 ensureWidgetPlacementContainers: function( widgetId ) { 415 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']'; 416 widgetPartial = api.selectiveRefresh.partial( partialId ); 417 if ( ! widgetPartial ) { 418 widgetPartial = new self.WidgetPartial( partialId, { 419 params: {} 420 } ); 421 } 422 423 // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder. 424 _.each( sidebarPartial.placements(), function( sidebarPlacement ) { 425 var foundWidgetPlacement, widgetContainerElement; 426 427 foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) { 428 return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber ); 429 } ); 430 if ( foundWidgetPlacement ) { 431 return; 432 } 433 434 widgetContainerElement = $( 435 sidebarPartial.params.sidebarArgs.before_widget.replace( /%1\$s/g, widgetId ).replace( /%2\$s/g, 'widget' ) + 436 sidebarPartial.params.sidebarArgs.after_widget 437 ); 438 439 // Handle rare case where before_widget and after_widget are empty. 440 if ( ! widgetContainerElement[0] ) { 441 return; 442 } 443 444 widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id ); 445 widgetContainerElement.attr( 'data-customize-partial-type', 'widget' ); 446 widgetContainerElement.attr( 'data-customize-widget-id', widgetId ); 447 448 /* 449 * Make sure the widget container element has the customize-container context data. 450 * The sidebar_instance_number is used to disambiguate multiple instances of the 451 * same sidebar are rendered onto the template, and so the same widget is embedded 452 * multiple times. 453 */ 454 widgetContainerElement.data( 'customize-partial-placement-context', { 455 'sidebar_id': sidebarPartial.sidebarId, 456 'sidebar_instance_number': sidebarPlacement.context.instanceNumber 457 } ); 458 459 sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode ); 460 wasInserted = true; 461 } ); 462 463 api.selectiveRefresh.partial.add( widgetPartial ); 464 465 if ( wasInserted ) { 466 sidebarPartial.reflowWidgets(); 467 } 468 469 return widgetPartial; 470 }, 471 472 /** 473 * Handles changes to the sidebars_widgets[] setting. 474 * 475 * @since 4.5.0 476 * 477 * @param {string[]} newWidgetIds New widget IDs. 478 * @param {string[]} oldWidgetIds Old widget IDs. 479 * 480 * @return {void} 481 */ 482 handleSettingChange: function( newWidgetIds, oldWidgetIds ) { 483 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; 484 485 needsRefresh = ( 486 ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) || 487 ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length ) 488 ); 489 if ( needsRefresh ) { 490 sidebarPartial.fallback(); 491 return; 492 } 493 494 // Handle removal of widgets. 495 widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds ); 496 _.each( widgetsRemoved, function( removedWidgetId ) { 497 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' ); 498 if ( widgetPartial ) { 499 _.each( widgetPartial.placements(), function( placement ) { 500 var isRemoved = ( 501 placement.context.sidebar_id === sidebarPartial.sidebarId || 502 ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId ) 503 ); 504 if ( isRemoved ) { 505 placement.container.remove(); 506 } 507 } ); 508 } 509 delete self.renderedWidgets[ removedWidgetId ]; 510 } ); 511 512 // Handle insertion of widgets. 513 widgetsAdded = _.difference( newWidgetIds, oldWidgetIds ); 514 _.each( widgetsAdded, function( addedWidgetId ) { 515 var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId ); 516 addedWidgetPartials.push( widgetPartial ); 517 self.renderedWidgets[ addedWidgetId ] = true; 518 } ); 519 520 _.each( addedWidgetPartials, function( widgetPartial ) { 521 widgetPartial.refresh(); 522 } ); 523 524 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 525 }, 526 527 /** 528 * Refreshes the sidebar partial. 529 * 530 * Note that the meat is handled in handleSettingChange because it has the 531 * context of which widgets were removed. 532 * 533 * @since 4.5.0 534 * 535 * @return {JQuery.Promise<*>} Promise resolved once the sidebar's widgets have been reflowed, or rejected when the sidebar has no placements. 536 */ 537 refresh: function() { 538 var partial = this, deferred = $.Deferred(); 539 540 deferred.fail( function() { 541 partial.fallback(); 542 } ); 543 544 if ( 0 === partial.placements().length ) { 545 deferred.reject(); 546 } else { 547 _.each( partial.reflowWidgets(), function( sidebarPlacement ) { 548 api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement ); 549 } ); 550 deferred.resolve(); 551 } 552 553 return deferred.promise(); 554 } 555 }); 556 557 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial; 558 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial; 559 560 /** 561 * Adds partials for the registered widget areas (sidebars). 562 * 563 * @since 4.5.0 564 * 565 * @return {void} 566 */ 567 self.addPartials = function() { 568 _.each( self.registeredSidebars, function( registeredSidebar ) { 569 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; 570 partial = api.selectiveRefresh.partial( partialId ); 571 if ( ! partial ) { 572 partial = new self.SidebarPartial( partialId, { 573 params: { 574 sidebarArgs: registeredSidebar 575 } 576 } ); 577 api.selectiveRefresh.partial.add( partial ); 578 } 579 } ); 580 }; 581 582 /** 583 * Calculates the selector for the sidebar's widgets based on the registered 584 * sidebar's info. 585 * 586 * @memberOf wp.customize.widgetsPreview 587 * 588 * @since 3.9.0 589 * 590 * @return {void} 591 */ 592 self.buildWidgetSelectors = function() { 593 var self = this; 594 595 $.each( self.registeredSidebars, function( i, sidebar ) { 596 var widgetTpl = [ 597 sidebar.before_widget, 598 sidebar.before_title, 599 sidebar.after_title, 600 sidebar.after_widget 601 ].join( '' ), 602 emptyWidget, 603 widgetSelector, 604 widgetClasses; 605 606 emptyWidget = $( widgetTpl ); 607 widgetSelector = emptyWidget.prop( 'tagName' ) || ''; 608 widgetClasses = emptyWidget.prop( 'className' ) || ''; 609 610 // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty. 611 if ( ! widgetClasses ) { 612 return; 613 } 614 615 // Remove class names that incorporate the string formatting placeholders %1$s and %2$s. 616 widgetClasses = widgetClasses.replace( /\S*%[12]\$s\S*/g, '' ); 617 widgetClasses = widgetClasses.replace( /^\s+|\s+$/g, '' ); 618 if ( widgetClasses ) { 619 widgetSelector += '.' + widgetClasses.split( /\s+/ ).join( '.' ); 620 } 621 self.widgetSelectors.push( widgetSelector ); 622 }); 623 }; 624 625 /** 626 * Highlights the widget on widget updates or widget control mouse overs. 627 * 628 * @memberOf wp.customize.widgetsPreview 629 * 630 * @since 3.9.0 631 * @param {string} widgetId ID of the widget. 632 * 633 * @return {void} 634 */ 635 self.highlightWidget = function( widgetId ) { 636 var $body = $( document.body ), 637 $widget = $( '#' + widgetId ); 638 639 $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' ); 640 641 $widget.addClass( 'widget-customizer-highlighted-widget' ); 642 setTimeout( function() { 643 $widget.removeClass( 'widget-customizer-highlighted-widget' ); 644 }, 500 ); 645 }; 646 647 /** 648 * Shows a title and highlights widgets on hover. On shift+clicking focuses the 649 * widget control. 650 * 651 * @memberOf wp.customize.widgetsPreview 652 * 653 * @since 3.9.0 654 * 655 * @return {void} 656 */ 657 self.highlightControls = function() { 658 var self = this, 659 selector = this.widgetSelectors.join( ',' ); 660 661 // Skip adding highlights if not in the customizer preview iframe. 662 if ( ! api.settings.channel ) { 663 return; 664 } 665 666 $( selector ).attr( 'title', this.l10n.widgetTooltip ); 667 // Highlights widget when entering the widget editor. 668 $( document ).on( 'mouseenter', selector, function() { 669 self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) ); 670 }); 671 672 // Open expand the widget control when shift+clicking the widget element. 673 $( document ).on( 'click', selector, function( e ) { 674 if ( ! e.shiftKey ) { 675 return; 676 } 677 e.preventDefault(); 678 679 self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) ); 680 }); 681 }; 682 683 /** 684 * Parses a widget ID. 685 * 686 * @memberOf wp.customize.widgetsPreview 687 * 688 * @since 4.5.0 689 * 690 * @param {string} widgetId The widget ID. 691 * 692 * @return {{idBase: string, number: number|null}} An object containing the idBase 693 * and number of the parsed widget ID. 694 */ 695 self.parseWidgetId = function( widgetId ) { 696 var matches, parsed = { 697 idBase: '', 698 number: null 699 }; 700 701 matches = widgetId.match( /^(.+)-(\d+)$/ ); 702 if ( matches ) { 703 parsed.idBase = matches[1]; 704 parsed.number = parseInt( matches[2], 10 ); 705 } else { 706 parsed.idBase = widgetId; // Likely an old single widget. 707 } 708 709 return parsed; 710 }; 711 712 /** 713 * Parses a widget setting ID. 714 * 715 * @memberOf wp.customize.widgetsPreview 716 * 717 * @since 4.5.0 718 * 719 * @param {string} settingId Widget setting ID. 720 * 721 * @return {{idBase: string, number: number|null}|null} Either an object containing the idBase 722 * and number of the parsed widget setting ID, 723 * or null. 724 */ 725 self.parseWidgetSettingId = function( settingId ) { 726 var matches, parsed = { 727 idBase: '', 728 number: null 729 }; 730 731 matches = settingId.match( /^widget_([^\[]+?)(?:\[(\d+)])?$/ ); 732 if ( ! matches ) { 733 return null; 734 } 735 parsed.idBase = matches[1]; 736 if ( matches[2] ) { 737 parsed.number = parseInt( matches[2], 10 ); 738 } 739 return parsed; 740 }; 741 742 /** 743 * Converts a widget ID into a Customizer setting ID. 744 * 745 * @memberOf wp.customize.widgetsPreview 746 * 747 * @since 4.5.0 748 * 749 * @param {string} widgetId The widget ID. 750 * 751 * @return {string} The setting ID. 752 */ 753 self.getWidgetSettingId = function( widgetId ) { 754 var parsed = this.parseWidgetId( widgetId ), settingId; 755 756 settingId = 'widget_' + parsed.idBase; 757 if ( parsed.number ) { 758 settingId += '[' + String( parsed.number ) + ']'; 759 } 760 761 return settingId; 762 }; 763 764 api.bind( 'preview-ready', function() { 765 $.extend( self, _wpWidgetCustomizerPreviewSettings ); 766 self.init(); 767 }); 768 769 return self; 770 })( jQuery, _, wp, wp.customize );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Sep 9 08:20:27 2026 | Cross-referenced by PHPXref |