| [ 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, sidebarArgs, 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 // Build containers from the registered sidebar, since partial params can be declared in post content. 424 sidebarArgs = _.findWhere( self.registeredSidebars, { id: sidebarPartial.sidebarId } ); 425 426 // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder. 427 _.each( sidebarPartial.placements(), function( sidebarPlacement ) { 428 var foundWidgetPlacement, widgetContainerElement; 429 430 if ( ! sidebarArgs ) { 431 return; 432 } 433 434 foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) { 435 return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber ); 436 } ); 437 if ( foundWidgetPlacement ) { 438 return; 439 } 440 441 widgetContainerElement = $( 442 sidebarArgs.before_widget.replace( /%1\$s/g, widgetId ).replace( /%2\$s/g, 'widget' ) + 443 sidebarArgs.after_widget 444 ); 445 446 // Handle rare case where before_widget and after_widget are empty. 447 if ( ! widgetContainerElement[0] ) { 448 return; 449 } 450 451 widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id ); 452 widgetContainerElement.attr( 'data-customize-partial-type', 'widget' ); 453 widgetContainerElement.attr( 'data-customize-widget-id', widgetId ); 454 455 /* 456 * Make sure the widget container element has the customize-container context data. 457 * The sidebar_instance_number is used to disambiguate multiple instances of the 458 * same sidebar are rendered onto the template, and so the same widget is embedded 459 * multiple times. 460 */ 461 widgetContainerElement.data( 'customize-partial-placement-context', { 462 'sidebar_id': sidebarPartial.sidebarId, 463 'sidebar_instance_number': sidebarPlacement.context.instanceNumber 464 } ); 465 466 sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode ); 467 wasInserted = true; 468 } ); 469 470 api.selectiveRefresh.partial.add( widgetPartial ); 471 472 if ( wasInserted ) { 473 sidebarPartial.reflowWidgets(); 474 } 475 476 return widgetPartial; 477 }, 478 479 /** 480 * Handles changes to the sidebars_widgets[] setting. 481 * 482 * @since 4.5.0 483 * 484 * @param {string[]} newWidgetIds New widget IDs. 485 * @param {string[]} oldWidgetIds Old widget IDs. 486 * 487 * @return {void} 488 */ 489 handleSettingChange: function( newWidgetIds, oldWidgetIds ) { 490 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; 491 492 needsRefresh = ( 493 ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) || 494 ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length ) 495 ); 496 if ( needsRefresh ) { 497 sidebarPartial.fallback(); 498 return; 499 } 500 501 // Handle removal of widgets. 502 widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds ); 503 _.each( widgetsRemoved, function( removedWidgetId ) { 504 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' ); 505 if ( widgetPartial ) { 506 _.each( widgetPartial.placements(), function( placement ) { 507 var isRemoved = ( 508 placement.context.sidebar_id === sidebarPartial.sidebarId || 509 ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId ) 510 ); 511 if ( isRemoved ) { 512 placement.container.remove(); 513 } 514 } ); 515 } 516 delete self.renderedWidgets[ removedWidgetId ]; 517 } ); 518 519 // Handle insertion of widgets. 520 widgetsAdded = _.difference( newWidgetIds, oldWidgetIds ); 521 _.each( widgetsAdded, function( addedWidgetId ) { 522 var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId ); 523 addedWidgetPartials.push( widgetPartial ); 524 self.renderedWidgets[ addedWidgetId ] = true; 525 } ); 526 527 _.each( addedWidgetPartials, function( widgetPartial ) { 528 widgetPartial.refresh(); 529 } ); 530 531 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 532 }, 533 534 /** 535 * Refreshes the sidebar partial. 536 * 537 * Note that the meat is handled in handleSettingChange because it has the 538 * context of which widgets were removed. 539 * 540 * @since 4.5.0 541 * 542 * @return {JQuery.Promise<*>} Promise resolved once the sidebar's widgets have been reflowed, or rejected when the sidebar has no placements. 543 */ 544 refresh: function() { 545 var partial = this, deferred = $.Deferred(); 546 547 deferred.fail( function() { 548 partial.fallback(); 549 } ); 550 551 if ( 0 === partial.placements().length ) { 552 deferred.reject(); 553 } else { 554 _.each( partial.reflowWidgets(), function( sidebarPlacement ) { 555 api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement ); 556 } ); 557 deferred.resolve(); 558 } 559 560 return deferred.promise(); 561 } 562 }); 563 564 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial; 565 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial; 566 567 /** 568 * Adds partials for the registered widget areas (sidebars). 569 * 570 * @since 4.5.0 571 * 572 * @return {void} 573 */ 574 self.addPartials = function() { 575 _.each( self.registeredSidebars, function( registeredSidebar ) { 576 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; 577 partial = api.selectiveRefresh.partial( partialId ); 578 if ( ! partial ) { 579 partial = new self.SidebarPartial( partialId, { 580 params: { 581 sidebarArgs: registeredSidebar 582 } 583 } ); 584 api.selectiveRefresh.partial.add( partial ); 585 } 586 } ); 587 }; 588 589 /** 590 * Calculates the selector for the sidebar's widgets based on the registered 591 * sidebar's info. 592 * 593 * @memberOf wp.customize.widgetsPreview 594 * 595 * @since 3.9.0 596 * 597 * @return {void} 598 */ 599 self.buildWidgetSelectors = function() { 600 var self = this; 601 602 $.each( self.registeredSidebars, function( i, sidebar ) { 603 var widgetTpl = [ 604 sidebar.before_widget, 605 sidebar.before_title, 606 sidebar.after_title, 607 sidebar.after_widget 608 ].join( '' ), 609 emptyWidget, 610 widgetSelector, 611 widgetClasses; 612 613 emptyWidget = $( widgetTpl ); 614 widgetSelector = emptyWidget.prop( 'tagName' ) || ''; 615 widgetClasses = emptyWidget.prop( 'className' ) || ''; 616 617 // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty. 618 if ( ! widgetClasses ) { 619 return; 620 } 621 622 // Remove class names that incorporate the string formatting placeholders %1$s and %2$s. 623 widgetClasses = widgetClasses.replace( /\S*%[12]\$s\S*/g, '' ); 624 widgetClasses = widgetClasses.replace( /^\s+|\s+$/g, '' ); 625 if ( widgetClasses ) { 626 widgetSelector += '.' + widgetClasses.split( /\s+/ ).join( '.' ); 627 } 628 self.widgetSelectors.push( widgetSelector ); 629 }); 630 }; 631 632 /** 633 * Highlights the widget on widget updates or widget control mouse overs. 634 * 635 * @memberOf wp.customize.widgetsPreview 636 * 637 * @since 3.9.0 638 * @param {string} widgetId ID of the widget. 639 * 640 * @return {void} 641 */ 642 self.highlightWidget = function( widgetId ) { 643 var $body = $( document.body ), 644 $widget = $( '#' + widgetId ); 645 646 $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' ); 647 648 $widget.addClass( 'widget-customizer-highlighted-widget' ); 649 setTimeout( function() { 650 $widget.removeClass( 'widget-customizer-highlighted-widget' ); 651 }, 500 ); 652 }; 653 654 /** 655 * Shows a title and highlights widgets on hover. On shift+clicking focuses the 656 * widget control. 657 * 658 * @memberOf wp.customize.widgetsPreview 659 * 660 * @since 3.9.0 661 * 662 * @return {void} 663 */ 664 self.highlightControls = function() { 665 var self = this, 666 selector = this.widgetSelectors.join( ',' ); 667 668 // Skip adding highlights if not in the customizer preview iframe. 669 if ( ! api.settings.channel ) { 670 return; 671 } 672 673 $( selector ).attr( 'title', this.l10n.widgetTooltip ); 674 // Highlights widget when entering the widget editor. 675 $( document ).on( 'mouseenter', selector, function() { 676 self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) ); 677 }); 678 679 // Open expand the widget control when shift+clicking the widget element. 680 $( document ).on( 'click', selector, function( e ) { 681 if ( ! e.shiftKey ) { 682 return; 683 } 684 e.preventDefault(); 685 686 self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) ); 687 }); 688 }; 689 690 /** 691 * Parses a widget ID. 692 * 693 * @memberOf wp.customize.widgetsPreview 694 * 695 * @since 4.5.0 696 * 697 * @param {string} widgetId The widget ID. 698 * 699 * @return {{idBase: string, number: number|null}} An object containing the idBase 700 * and number of the parsed widget ID. 701 */ 702 self.parseWidgetId = function( widgetId ) { 703 var matches, parsed = { 704 idBase: '', 705 number: null 706 }; 707 708 matches = widgetId.match( /^(.+)-(\d+)$/ ); 709 if ( matches ) { 710 parsed.idBase = matches[1]; 711 parsed.number = parseInt( matches[2], 10 ); 712 } else { 713 parsed.idBase = widgetId; // Likely an old single widget. 714 } 715 716 return parsed; 717 }; 718 719 /** 720 * Parses a widget setting ID. 721 * 722 * @memberOf wp.customize.widgetsPreview 723 * 724 * @since 4.5.0 725 * 726 * @param {string} settingId Widget setting ID. 727 * 728 * @return {{idBase: string, number: number|null}|null} Either an object containing the idBase 729 * and number of the parsed widget setting ID, 730 * or null. 731 */ 732 self.parseWidgetSettingId = function( settingId ) { 733 var matches, parsed = { 734 idBase: '', 735 number: null 736 }; 737 738 matches = settingId.match( /^widget_([^\[]+?)(?:\[(\d+)])?$/ ); 739 if ( ! matches ) { 740 return null; 741 } 742 parsed.idBase = matches[1]; 743 if ( matches[2] ) { 744 parsed.number = parseInt( matches[2], 10 ); 745 } 746 return parsed; 747 }; 748 749 /** 750 * Converts a widget ID into a Customizer setting ID. 751 * 752 * @memberOf wp.customize.widgetsPreview 753 * 754 * @since 4.5.0 755 * 756 * @param {string} widgetId The widget ID. 757 * 758 * @return {string} The setting ID. 759 */ 760 self.getWidgetSettingId = function( widgetId ) { 761 var parsed = this.parseWidgetId( widgetId ), settingId; 762 763 settingId = 'widget_' + parsed.idBase; 764 if ( parsed.number ) { 765 settingId += '[' + String( parsed.number ) + ']'; 766 } 767 768 return settingId; 769 }; 770 771 api.bind( 'preview-ready', function() { 772 $.extend( self, _wpWidgetCustomizerPreviewSettings ); 773 self.init(); 774 }); 775 776 return self; 777 })( jQuery, _, wp, wp.customize );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Mon Nov 9 08:20:01 2026 | Cross-referenced by PHPXref |