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