No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

customize-preview-widgets.js 19 KiB

hace 3 años
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. /* global _wpWidgetCustomizerPreviewSettings */
  2. wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) {
  3. var self;
  4. self = {
  5. renderedSidebars: {},
  6. renderedWidgets: {},
  7. registeredSidebars: [],
  8. registeredWidgets: {},
  9. widgetSelectors: [],
  10. preview: null,
  11. l10n: {
  12. widgetTooltip: ''
  13. },
  14. selectiveRefreshableWidgets: {}
  15. };
  16. /**
  17. * Init widgets preview.
  18. *
  19. * @since 4.5.0
  20. */
  21. self.init = function() {
  22. var self = this;
  23. self.preview = api.preview;
  24. if ( ! _.isEmpty( self.selectiveRefreshableWidgets ) ) {
  25. self.addPartials();
  26. }
  27. self.buildWidgetSelectors();
  28. self.highlightControls();
  29. self.preview.bind( 'highlight-widget', self.highlightWidget );
  30. api.preview.bind( 'active', function() {
  31. self.highlightControls();
  32. } );
  33. };
  34. /**
  35. * Partial representing a widget instance.
  36. *
  37. * @class
  38. * @augments wp.customize.selectiveRefresh.Partial
  39. * @since 4.5.0
  40. */
  41. self.WidgetPartial = api.selectiveRefresh.Partial.extend({
  42. /**
  43. * Constructor.
  44. *
  45. * @since 4.5.0
  46. * @param {string} id - Partial ID.
  47. * @param {Object} options
  48. * @param {Object} options.params
  49. */
  50. initialize: function( id, options ) {
  51. var partial = this, matches;
  52. matches = id.match( /^widget\[(.+)]$/ );
  53. if ( ! matches ) {
  54. throw new Error( 'Illegal id for widget partial.' );
  55. }
  56. partial.widgetId = matches[1];
  57. partial.widgetIdParts = self.parseWidgetId( partial.widgetId );
  58. options = options || {};
  59. options.params = _.extend(
  60. {
  61. settings: [ self.getWidgetSettingId( partial.widgetId ) ],
  62. containerInclusive: true
  63. },
  64. options.params || {}
  65. );
  66. api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
  67. },
  68. /**
  69. * Refresh widget partial.
  70. *
  71. * @returns {Promise}
  72. */
  73. refresh: function() {
  74. var partial = this, refreshDeferred;
  75. if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) {
  76. refreshDeferred = $.Deferred();
  77. refreshDeferred.reject();
  78. partial.fallback();
  79. return refreshDeferred.promise();
  80. } else {
  81. return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
  82. }
  83. },
  84. /**
  85. * Send widget-updated message to parent so spinner will get removed from widget control.
  86. *
  87. * @inheritdoc
  88. * @param {wp.customize.selectiveRefresh.Placement} placement
  89. */
  90. renderContent: function( placement ) {
  91. var partial = this;
  92. if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
  93. api.preview.send( 'widget-updated', partial.widgetId );
  94. api.selectiveRefresh.trigger( 'widget-updated', partial );
  95. }
  96. }
  97. });
  98. /**
  99. * Partial representing a widget area.
  100. *
  101. * @class
  102. * @augments wp.customize.selectiveRefresh.Partial
  103. * @since 4.5.0
  104. */
  105. self.SidebarPartial = api.selectiveRefresh.Partial.extend({
  106. /**
  107. * Constructor.
  108. *
  109. * @since 4.5.0
  110. * @param {string} id - Partial ID.
  111. * @param {Object} options
  112. * @param {Object} options.params
  113. */
  114. initialize: function( id, options ) {
  115. var partial = this, matches;
  116. matches = id.match( /^sidebar\[(.+)]$/ );
  117. if ( ! matches ) {
  118. throw new Error( 'Illegal id for sidebar partial.' );
  119. }
  120. partial.sidebarId = matches[1];
  121. options = options || {};
  122. options.params = _.extend(
  123. {
  124. settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
  125. },
  126. options.params || {}
  127. );
  128. api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
  129. if ( ! partial.params.sidebarArgs ) {
  130. throw new Error( 'The sidebarArgs param was not provided.' );
  131. }
  132. if ( partial.params.settings.length > 1 ) {
  133. throw new Error( 'Expected SidebarPartial to only have one associated setting' );
  134. }
  135. },
  136. /**
  137. * Set up the partial.
  138. *
  139. * @since 4.5.0
  140. */
  141. ready: function() {
  142. var sidebarPartial = this;
  143. // Watch for changes to the sidebar_widgets setting.
  144. _.each( sidebarPartial.settings(), function( settingId ) {
  145. api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
  146. } );
  147. // Trigger an event for this sidebar being updated whenever a widget inside is rendered.
  148. api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
  149. var isAssignedWidgetPartial = (
  150. placement.partial.extended( self.WidgetPartial ) &&
  151. ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
  152. );
  153. if ( isAssignedWidgetPartial ) {
  154. api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
  155. }
  156. } );
  157. // Make sure that a widget partial has a container in the DOM prior to a refresh.
  158. api.bind( 'change', function( widgetSetting ) {
  159. var widgetId, parsedId;
  160. parsedId = self.parseWidgetSettingId( widgetSetting.id );
  161. if ( ! parsedId ) {
  162. return;
  163. }
  164. widgetId = parsedId.idBase;
  165. if ( parsedId.number ) {
  166. widgetId += '-' + String( parsedId.number );
  167. }
  168. if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
  169. sidebarPartial.ensureWidgetPlacementContainers( widgetId );
  170. }
  171. } );
  172. },
  173. /**
  174. * Get the before/after boundary nodes for all instances of this sidebar (usually one).
  175. *
  176. * Note that TreeWalker is not implemented in IE8.
  177. *
  178. * @since 4.5.0
  179. * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
  180. */
  181. findDynamicSidebarBoundaryNodes: function() {
  182. var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
  183. regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
  184. recursiveCommentTraversal = function( childNodes ) {
  185. _.each( childNodes, function( node ) {
  186. var matches;
  187. if ( 8 === node.nodeType ) {
  188. matches = node.nodeValue.match( regExp );
  189. if ( ! matches || matches[2] !== partial.sidebarId ) {
  190. return;
  191. }
  192. if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
  193. boundaryNodes[ matches[3] ] = {
  194. before: null,
  195. after: null,
  196. instanceNumber: parseInt( matches[3], 10 )
  197. };
  198. }
  199. if ( 'dynamic_sidebar_before' === matches[1] ) {
  200. boundaryNodes[ matches[3] ].before = node;
  201. } else {
  202. boundaryNodes[ matches[3] ].after = node;
  203. }
  204. } else if ( 1 === node.nodeType ) {
  205. recursiveCommentTraversal( node.childNodes );
  206. }
  207. } );
  208. };
  209. recursiveCommentTraversal( document.body.childNodes );
  210. return _.values( boundaryNodes );
  211. },
  212. /**
  213. * Get the placements for this partial.
  214. *
  215. * @since 4.5.0
  216. * @returns {Array}
  217. */
  218. placements: function() {
  219. var partial = this;
  220. return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
  221. return new api.selectiveRefresh.Placement( {
  222. partial: partial,
  223. container: null,
  224. startNode: boundaryNodes.before,
  225. endNode: boundaryNodes.after,
  226. context: {
  227. instanceNumber: boundaryNodes.instanceNumber
  228. }
  229. } );
  230. } );
  231. },
  232. /**
  233. * Get the list of widget IDs associated with this widget area.
  234. *
  235. * @since 4.5.0
  236. *
  237. * @returns {Array}
  238. */
  239. getWidgetIds: function() {
  240. var sidebarPartial = this, settingId, widgetIds;
  241. settingId = sidebarPartial.settings()[0];
  242. if ( ! settingId ) {
  243. throw new Error( 'Missing associated setting.' );
  244. }
  245. if ( ! api.has( settingId ) ) {
  246. throw new Error( 'Setting does not exist.' );
  247. }
  248. widgetIds = api( settingId ).get();
  249. if ( ! _.isArray( widgetIds ) ) {
  250. throw new Error( 'Expected setting to be array of widget IDs' );
  251. }
  252. return widgetIds.slice( 0 );
  253. },
  254. /**
  255. * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM.
  256. *
  257. * @since 4.5.0
  258. *
  259. * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed.
  260. */
  261. reflowWidgets: function() {
  262. var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
  263. widgetIds = sidebarPartial.getWidgetIds();
  264. sidebarPlacements = sidebarPartial.placements();
  265. widgetPartials = {};
  266. _.each( widgetIds, function( widgetId ) {
  267. var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
  268. if ( widgetPartial ) {
  269. widgetPartials[ widgetId ] = widgetPartial;
  270. }
  271. } );
  272. _.each( sidebarPlacements, function( sidebarPlacement ) {
  273. var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
  274. // Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
  275. _.each( widgetPartials, function( widgetPartial ) {
  276. _.each( widgetPartial.placements(), function( widgetPlacement ) {
  277. if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
  278. thisPosition = widgetPlacement.container.index();
  279. sidebarWidgets.push( {
  280. partial: widgetPartial,
  281. placement: widgetPlacement,
  282. position: thisPosition
  283. } );
  284. if ( thisPosition < lastPosition ) {
  285. needsSort = true;
  286. }
  287. lastPosition = thisPosition;
  288. }
  289. } );
  290. } );
  291. if ( needsSort ) {
  292. _.each( sidebarWidgets, function( sidebarWidget ) {
  293. sidebarPlacement.endNode.parentNode.insertBefore(
  294. sidebarWidget.placement.container[0],
  295. sidebarPlacement.endNode
  296. );
  297. // @todo Rename partial-placement-moved?
  298. api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
  299. } );
  300. sortedSidebarContainers.push( sidebarPlacement );
  301. }
  302. } );
  303. if ( sortedSidebarContainers.length > 0 ) {
  304. api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
  305. }
  306. return sortedSidebarContainers;
  307. },
  308. /**
  309. * Make sure there is a widget instance container in this sidebar for the given widget ID.
  310. *
  311. * @since 4.5.0
  312. *
  313. * @param {string} widgetId
  314. * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial.
  315. */
  316. ensureWidgetPlacementContainers: function( widgetId ) {
  317. var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
  318. widgetPartial = api.selectiveRefresh.partial( partialId );
  319. if ( ! widgetPartial ) {
  320. widgetPartial = new self.WidgetPartial( partialId, {
  321. params: {}
  322. } );
  323. }
  324. // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
  325. _.each( sidebarPartial.placements(), function( sidebarPlacement ) {
  326. var foundWidgetPlacement, widgetContainerElement;
  327. foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
  328. return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
  329. } );
  330. if ( foundWidgetPlacement ) {
  331. return;
  332. }
  333. widgetContainerElement = $(
  334. sidebarPartial.params.sidebarArgs.before_widget.replace( /%1\$s/g, widgetId ).replace( /%2\$s/g, 'widget' ) +
  335. sidebarPartial.params.sidebarArgs.after_widget
  336. );
  337. // Handle rare case where before_widget and after_widget are empty.
  338. if ( ! widgetContainerElement[0] ) {
  339. return;
  340. }
  341. widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
  342. widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
  343. widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
  344. /*
  345. * Make sure the widget container element has the customize-container context data.
  346. * The sidebar_instance_number is used to disambiguate multiple instances of the
  347. * same sidebar are rendered onto the template, and so the same widget is embedded
  348. * multiple times.
  349. */
  350. widgetContainerElement.data( 'customize-partial-placement-context', {
  351. 'sidebar_id': sidebarPartial.sidebarId,
  352. 'sidebar_instance_number': sidebarPlacement.context.instanceNumber
  353. } );
  354. sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
  355. wasInserted = true;
  356. } );
  357. api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
  358. if ( wasInserted ) {
  359. sidebarPartial.reflowWidgets();
  360. }
  361. return widgetPartial;
  362. },
  363. /**
  364. * Handle change to the sidebars_widgets[] setting.
  365. *
  366. * @since 4.5.0
  367. *
  368. * @param {Array} newWidgetIds New widget ids.
  369. * @param {Array} oldWidgetIds Old widget ids.
  370. */
  371. handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
  372. var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
  373. needsRefresh = (
  374. ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
  375. ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
  376. );
  377. if ( needsRefresh ) {
  378. sidebarPartial.fallback();
  379. return;
  380. }
  381. // Handle removal of widgets.
  382. widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
  383. _.each( widgetsRemoved, function( removedWidgetId ) {
  384. var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
  385. if ( widgetPartial ) {
  386. _.each( widgetPartial.placements(), function( placement ) {
  387. var isRemoved = (
  388. placement.context.sidebar_id === sidebarPartial.sidebarId ||
  389. ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
  390. );
  391. if ( isRemoved ) {
  392. placement.container.remove();
  393. }
  394. } );
  395. }
  396. } );
  397. // Handle insertion of widgets.
  398. widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
  399. _.each( widgetsAdded, function( addedWidgetId ) {
  400. var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
  401. addedWidgetPartials.push( widgetPartial );
  402. } );
  403. _.each( addedWidgetPartials, function( widgetPartial ) {
  404. widgetPartial.refresh();
  405. } );
  406. api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
  407. },
  408. /**
  409. * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed.
  410. *
  411. * @since 4.5.0
  412. */
  413. refresh: function() {
  414. var partial = this, deferred = $.Deferred();
  415. deferred.fail( function() {
  416. partial.fallback();
  417. } );
  418. if ( 0 === partial.placements().length ) {
  419. deferred.reject();
  420. } else {
  421. _.each( partial.reflowWidgets(), function( sidebarPlacement ) {
  422. api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
  423. } );
  424. deferred.resolve();
  425. }
  426. return deferred.promise();
  427. }
  428. });
  429. api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
  430. api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
  431. /**
  432. * Add partials for the registered widget areas (sidebars).
  433. *
  434. * @since 4.5.0
  435. */
  436. self.addPartials = function() {
  437. _.each( self.registeredSidebars, function( registeredSidebar ) {
  438. var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
  439. partial = api.selectiveRefresh.partial( partialId );
  440. if ( ! partial ) {
  441. partial = new self.SidebarPartial( partialId, {
  442. params: {
  443. sidebarArgs: registeredSidebar
  444. }
  445. } );
  446. api.selectiveRefresh.partial.add( partial.id, partial );
  447. }
  448. } );
  449. };
  450. /**
  451. * Calculate the selector for the sidebar's widgets based on the registered sidebar's info.
  452. *
  453. * @since 3.9.0
  454. */
  455. self.buildWidgetSelectors = function() {
  456. var self = this;
  457. $.each( self.registeredSidebars, function( i, sidebar ) {
  458. var widgetTpl = [
  459. sidebar.before_widget,
  460. sidebar.before_title,
  461. sidebar.after_title,
  462. sidebar.after_widget
  463. ].join( '' ),
  464. emptyWidget,
  465. widgetSelector,
  466. widgetClasses;
  467. emptyWidget = $( widgetTpl );
  468. widgetSelector = emptyWidget.prop( 'tagName' ) || '';
  469. widgetClasses = emptyWidget.prop( 'className' ) || '';
  470. // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty.
  471. if ( ! widgetClasses ) {
  472. return;
  473. }
  474. // Remove class names that incorporate the string formatting placeholders %1$s and %2$s.
  475. widgetClasses = widgetClasses.replace( /\S*%[12]\$s\S*/g, '' );
  476. widgetClasses = widgetClasses.replace( /^\s+|\s+$/g, '' );
  477. if ( widgetClasses ) {
  478. widgetSelector += '.' + widgetClasses.split( /\s+/ ).join( '.' );
  479. }
  480. self.widgetSelectors.push( widgetSelector );
  481. });
  482. };
  483. /**
  484. * Highlight the widget on widget updates or widget control mouse overs.
  485. *
  486. * @since 3.9.0
  487. * @param {string} widgetId ID of the widget.
  488. */
  489. self.highlightWidget = function( widgetId ) {
  490. var $body = $( document.body ),
  491. $widget = $( '#' + widgetId );
  492. $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' );
  493. $widget.addClass( 'widget-customizer-highlighted-widget' );
  494. setTimeout( function() {
  495. $widget.removeClass( 'widget-customizer-highlighted-widget' );
  496. }, 500 );
  497. };
  498. /**
  499. * Show a title and highlight widgets on hover. On shift+clicking
  500. * focus the widget control.
  501. *
  502. * @since 3.9.0
  503. */
  504. self.highlightControls = function() {
  505. var self = this,
  506. selector = this.widgetSelectors.join( ',' );
  507. // Skip adding highlights if not in the customizer preview iframe.
  508. if ( ! api.settings.channel ) {
  509. return;
  510. }
  511. $( selector ).attr( 'title', this.l10n.widgetTooltip );
  512. $( document ).on( 'mouseenter', selector, function() {
  513. self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) );
  514. });
  515. // Open expand the widget control when shift+clicking the widget element
  516. $( document ).on( 'click', selector, function( e ) {
  517. if ( ! e.shiftKey ) {
  518. return;
  519. }
  520. e.preventDefault();
  521. self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) );
  522. });
  523. };
  524. /**
  525. * Parse a widget ID.
  526. *
  527. * @since 4.5.0
  528. *
  529. * @param {string} widgetId Widget ID.
  530. * @returns {{idBase: string, number: number|null}}
  531. */
  532. self.parseWidgetId = function( widgetId ) {
  533. var matches, parsed = {
  534. idBase: '',
  535. number: null
  536. };
  537. matches = widgetId.match( /^(.+)-(\d+)$/ );
  538. if ( matches ) {
  539. parsed.idBase = matches[1];
  540. parsed.number = parseInt( matches[2], 10 );
  541. } else {
  542. parsed.idBase = widgetId; // Likely an old single widget.
  543. }
  544. return parsed;
  545. };
  546. /**
  547. * Parse a widget setting ID.
  548. *
  549. * @since 4.5.0
  550. *
  551. * @param {string} settingId Widget setting ID.
  552. * @returns {{idBase: string, number: number|null}|null}
  553. */
  554. self.parseWidgetSettingId = function( settingId ) {
  555. var matches, parsed = {
  556. idBase: '',
  557. number: null
  558. };
  559. matches = settingId.match( /^widget_([^\[]+?)(?:\[(\d+)])?$/ );
  560. if ( ! matches ) {
  561. return null;
  562. }
  563. parsed.idBase = matches[1];
  564. if ( matches[2] ) {
  565. parsed.number = parseInt( matches[2], 10 );
  566. }
  567. return parsed;
  568. };
  569. /**
  570. * Convert a widget ID into a Customizer setting ID.
  571. *
  572. * @since 4.5.0
  573. *
  574. * @param {string} widgetId Widget ID.
  575. * @returns {string} settingId Setting ID.
  576. */
  577. self.getWidgetSettingId = function( widgetId ) {
  578. var parsed = this.parseWidgetId( widgetId ), settingId;
  579. settingId = 'widget_' + parsed.idBase;
  580. if ( parsed.number ) {
  581. settingId += '[' + String( parsed.number ) + ']';
  582. }
  583. return settingId;
  584. };
  585. api.bind( 'preview-ready', function() {
  586. $.extend( self, _wpWidgetCustomizerPreviewSettings );
  587. self.init();
  588. });
  589. return self;
  590. })( jQuery, _, wp, wp.customize );