[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/js/ -> customize-preview-widgets.js (source)

   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 );


Generated: Thu Nov 21 01:00:03 2024 Cross-referenced by PHPXref 0.7.1