|
1 /** |
|
2 * @output wp-includes/js/customize-preview-widgets.js |
|
3 */ |
|
4 |
1 /* global _wpWidgetCustomizerPreviewSettings */ |
5 /* global _wpWidgetCustomizerPreviewSettings */ |
2 |
6 |
3 /** @namespace wp.customize.widgetsPreview */ |
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 * @returns {Object} Widget-related variables. |
|
20 */ |
4 wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) { |
21 wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) { |
5 |
22 |
6 var self; |
23 var self; |
7 |
24 |
8 self = { |
25 self = { |
57 api.preview.send( 'refresh' ); // Fallback in case theme does not support 'customize-selective-refresh-widgets'. |
78 api.preview.send( 'refresh' ); // Fallback in case theme does not support 'customize-selective-refresh-widgets'. |
58 } |
79 } |
59 } ); |
80 } ); |
60 }; |
81 }; |
61 |
82 |
62 /** |
|
63 * Partial representing a widget instance. |
|
64 * |
|
65 * @memberOf wp.customize.widgetsPreview |
|
66 * @alias wp.customize.widgetsPreview.WidgetPartial |
|
67 * |
|
68 * @class |
|
69 * @augments wp.customize.selectiveRefresh.Partial |
|
70 * @since 4.5.0 |
|
71 */ |
|
72 self.WidgetPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.WidgetPartial.prototype */{ |
83 self.WidgetPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.WidgetPartial.prototype */{ |
73 |
84 |
74 /** |
85 /** |
75 * Constructor. |
86 * Represents a partial widget instance. |
76 * |
87 * |
77 * @since 4.5.0 |
88 * @since 4.5.0 |
78 * @param {string} id - Partial ID. |
89 * |
79 * @param {Object} options |
90 * @constructs |
80 * @param {Object} options.params |
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. |
81 */ |
100 */ |
82 initialize: function( id, options ) { |
101 initialize: function( id, options ) { |
83 var partial = this, matches; |
102 var partial = this, matches; |
84 matches = id.match( /^widget\[(.+)]$/ ); |
103 matches = id.match( /^widget\[(.+)]$/ ); |
85 if ( ! matches ) { |
104 if ( ! matches ) { |
99 |
118 |
100 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); |
119 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); |
101 }, |
120 }, |
102 |
121 |
103 /** |
122 /** |
104 * Refresh widget partial. |
123 * Refreshes the widget partial. |
105 * |
124 * |
106 * @returns {Promise} |
125 * @since 4.5.0 |
|
126 * |
|
127 * @returns {Promise|void} Either a promise postponing the refresh, or void. |
107 */ |
128 */ |
108 refresh: function() { |
129 refresh: function() { |
109 var partial = this, refreshDeferred; |
130 var partial = this, refreshDeferred; |
110 if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) { |
131 if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) { |
111 refreshDeferred = $.Deferred(); |
132 refreshDeferred = $.Deferred(); |
116 return api.selectiveRefresh.Partial.prototype.refresh.call( partial ); |
137 return api.selectiveRefresh.Partial.prototype.refresh.call( partial ); |
117 } |
138 } |
118 }, |
139 }, |
119 |
140 |
120 /** |
141 /** |
121 * Send widget-updated message to parent so spinner will get removed from widget control. |
142 * Sends the widget-updated message to the parent so the spinner will get |
122 * |
143 * removed from the widget control. |
123 * @inheritdoc |
144 * |
124 * @param {wp.customize.selectiveRefresh.Placement} placement |
145 * @inheritDoc |
|
146 * @param {wp.customize.selectiveRefresh.Placement} placement The placement |
|
147 * function. |
|
148 * |
|
149 * @returns {void} |
125 */ |
150 */ |
126 renderContent: function( placement ) { |
151 renderContent: function( placement ) { |
127 var partial = this; |
152 var partial = this; |
128 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { |
153 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { |
129 api.preview.send( 'widget-updated', partial.widgetId ); |
154 api.preview.send( 'widget-updated', partial.widgetId ); |
130 api.selectiveRefresh.trigger( 'widget-updated', partial ); |
155 api.selectiveRefresh.trigger( 'widget-updated', partial ); |
131 } |
156 } |
132 } |
157 } |
133 }); |
158 }); |
134 |
159 |
135 /** |
|
136 * Partial representing a widget area. |
|
137 * |
|
138 * @memberOf wp.customize.widgetsPreview |
|
139 * @alias wp.customize.widgetsPreview.SidebarPartial |
|
140 * |
|
141 * @class |
|
142 * @augments wp.customize.selectiveRefresh.Partial |
|
143 * @since 4.5.0 |
|
144 */ |
|
145 self.SidebarPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.SidebarPartial.prototype */{ |
160 self.SidebarPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.SidebarPartial.prototype */{ |
146 |
161 |
147 /** |
162 /** |
148 * Constructor. |
163 * Represents a partial widget area. |
149 * |
164 * |
150 * @since 4.5.0 |
165 * @since 4.5.0 |
151 * @param {string} id - Partial ID. |
166 * |
152 * @param {Object} options |
167 * @class |
153 * @param {Object} options.params |
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. |
154 */ |
176 */ |
155 initialize: function( id, options ) { |
177 initialize: function( id, options ) { |
156 var partial = this, matches; |
178 var partial = this, matches; |
157 matches = id.match( /^sidebar\[(.+)]$/ ); |
179 matches = id.match( /^sidebar\[(.+)]$/ ); |
158 if ( ! matches ) { |
180 if ( ! matches ) { |
218 } |
242 } |
219 } ); |
243 } ); |
220 }, |
244 }, |
221 |
245 |
222 /** |
246 /** |
223 * Get the before/after boundary nodes for all instances of this sidebar (usually one). |
247 * Gets the before/after boundary nodes for all instances of this sidebar |
|
248 * (usually one). |
224 * |
249 * |
225 * Note that TreeWalker is not implemented in IE8. |
250 * Note that TreeWalker is not implemented in IE8. |
226 * |
251 * |
227 * @since 4.5.0 |
252 * @since 4.5.0 |
|
253 * |
228 * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>} |
254 * @returns {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. |
229 */ |
257 */ |
230 findDynamicSidebarBoundaryNodes: function() { |
258 findDynamicSidebarBoundaryNodes: function() { |
231 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal; |
259 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal; |
232 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/; |
260 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/; |
233 recursiveCommentTraversal = function( childNodes ) { |
261 recursiveCommentTraversal = function( childNodes ) { |
259 recursiveCommentTraversal( document.body.childNodes ); |
287 recursiveCommentTraversal( document.body.childNodes ); |
260 return _.values( boundaryNodes ); |
288 return _.values( boundaryNodes ); |
261 }, |
289 }, |
262 |
290 |
263 /** |
291 /** |
264 * Get the placements for this partial. |
292 * Gets the placements for this partial. |
265 * |
293 * |
266 * @since 4.5.0 |
294 * @since 4.5.0 |
267 * @returns {Array} |
295 * |
|
296 * @returns {Array} An array containing placement objects for each of the |
|
297 * dynamic sidebar boundary nodes. |
268 */ |
298 */ |
269 placements: function() { |
299 placements: function() { |
270 var partial = this; |
300 var partial = this; |
271 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) { |
301 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) { |
272 return new api.selectiveRefresh.Placement( { |
302 return new api.selectiveRefresh.Placement( { |
284 /** |
314 /** |
285 * Get the list of widget IDs associated with this widget area. |
315 * Get the list of widget IDs associated with this widget area. |
286 * |
316 * |
287 * @since 4.5.0 |
317 * @since 4.5.0 |
288 * |
318 * |
289 * @returns {Array} |
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 * @returns {Array} A shallow copy of the array containing widget IDs. |
290 */ |
324 */ |
291 getWidgetIds: function() { |
325 getWidgetIds: function() { |
292 var sidebarPartial = this, settingId, widgetIds; |
326 var sidebarPartial = this, settingId, widgetIds; |
293 settingId = sidebarPartial.settings()[0]; |
327 settingId = sidebarPartial.settings()[0]; |
294 if ( ! settingId ) { |
328 if ( ! settingId ) { |
303 } |
337 } |
304 return widgetIds.slice( 0 ); |
338 return widgetIds.slice( 0 ); |
305 }, |
339 }, |
306 |
340 |
307 /** |
341 /** |
308 * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM. |
342 * Reflows widgets in the sidebar, ensuring they have the proper position in the |
309 * |
343 * DOM. |
310 * @since 4.5.0 |
344 * |
311 * |
345 * @since 4.5.0 |
312 * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed. |
346 * |
|
347 * @returns {Array.<wp.customize.selectiveRefresh.Placement>} List of placements |
|
348 * that were reflowed. |
313 */ |
349 */ |
314 reflowWidgets: function() { |
350 reflowWidgets: function() { |
315 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = []; |
351 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = []; |
316 widgetIds = sidebarPartial.getWidgetIds(); |
352 widgetIds = sidebarPartial.getWidgetIds(); |
317 sidebarPlacements = sidebarPartial.placements(); |
353 sidebarPlacements = sidebarPartial.placements(); |
367 |
403 |
368 return sortedSidebarContainers; |
404 return sortedSidebarContainers; |
369 }, |
405 }, |
370 |
406 |
371 /** |
407 /** |
372 * Make sure there is a widget instance container in this sidebar for the given widget ID. |
408 * Makes sure there is a widget instance container in this sidebar for the given |
373 * |
409 * widget ID. |
374 * @since 4.5.0 |
410 * |
375 * |
411 * @since 4.5.0 |
376 * @param {string} widgetId |
412 * |
377 * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial. |
413 * @param {string} widgetId The widget ID. |
|
414 * |
|
415 * @returns {wp.customize.selectiveRefresh.Partial} The widget instance partial. |
378 */ |
416 */ |
379 ensureWidgetPlacementContainers: function( widgetId ) { |
417 ensureWidgetPlacementContainers: function( widgetId ) { |
380 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']'; |
418 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']'; |
381 widgetPartial = api.selectiveRefresh.partial( partialId ); |
419 widgetPartial = api.selectiveRefresh.partial( partialId ); |
382 if ( ! widgetPartial ) { |
420 if ( ! widgetPartial ) { |
433 |
471 |
434 return widgetPartial; |
472 return widgetPartial; |
435 }, |
473 }, |
436 |
474 |
437 /** |
475 /** |
438 * Handle change to the sidebars_widgets[] setting. |
476 * Handles changes to the sidebars_widgets[] setting. |
439 * |
477 * |
440 * @since 4.5.0 |
478 * @since 4.5.0 |
441 * |
479 * |
442 * @param {Array} newWidgetIds New widget ids. |
480 * @param {Array} newWidgetIds New widget IDs. |
443 * @param {Array} oldWidgetIds Old widget ids. |
481 * @param {Array} oldWidgetIds Old widget IDs. |
|
482 * |
|
483 * @returns {void} |
444 */ |
484 */ |
445 handleSettingChange: function( newWidgetIds, oldWidgetIds ) { |
485 handleSettingChange: function( newWidgetIds, oldWidgetIds ) { |
446 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; |
486 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; |
447 |
487 |
448 needsRefresh = ( |
488 needsRefresh = ( |
486 |
526 |
487 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); |
527 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); |
488 }, |
528 }, |
489 |
529 |
490 /** |
530 /** |
491 * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed. |
531 * Refreshes the sidebar partial. |
492 * |
532 * |
493 * @since 4.5.0 |
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 * @returns {Promise} A promise postponing the refresh. |
494 */ |
539 */ |
495 refresh: function() { |
540 refresh: function() { |
496 var partial = this, deferred = $.Deferred(); |
541 var partial = this, deferred = $.Deferred(); |
497 |
542 |
498 deferred.fail( function() { |
543 deferred.fail( function() { |
514 |
559 |
515 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial; |
560 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial; |
516 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial; |
561 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial; |
517 |
562 |
518 /** |
563 /** |
519 * Add partials for the registered widget areas (sidebars). |
564 * Adds partials for the registered widget areas (sidebars). |
520 * |
565 * |
521 * @since 4.5.0 |
566 * @since 4.5.0 |
|
567 * |
|
568 * @returns {void} |
522 */ |
569 */ |
523 self.addPartials = function() { |
570 self.addPartials = function() { |
524 _.each( self.registeredSidebars, function( registeredSidebar ) { |
571 _.each( self.registeredSidebars, function( registeredSidebar ) { |
525 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; |
572 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; |
526 partial = api.selectiveRefresh.partial( partialId ); |
573 partial = api.selectiveRefresh.partial( partialId ); |
534 } |
581 } |
535 } ); |
582 } ); |
536 }; |
583 }; |
537 |
584 |
538 /** |
585 /** |
539 * Calculate the selector for the sidebar's widgets based on the registered sidebar's info. |
586 * Calculates the selector for the sidebar's widgets based on the registered |
|
587 * sidebar's info. |
540 * |
588 * |
541 * @memberOf wp.customize.widgetsPreview |
589 * @memberOf wp.customize.widgetsPreview |
542 * |
590 * |
543 * @since 3.9.0 |
591 * @since 3.9.0 |
|
592 * |
|
593 * @returns {void} |
544 */ |
594 */ |
545 self.buildWidgetSelectors = function() { |
595 self.buildWidgetSelectors = function() { |
546 var self = this; |
596 var self = this; |
547 |
597 |
548 $.each( self.registeredSidebars, function( i, sidebar ) { |
598 $.each( self.registeredSidebars, function( i, sidebar ) { |
574 self.widgetSelectors.push( widgetSelector ); |
624 self.widgetSelectors.push( widgetSelector ); |
575 }); |
625 }); |
576 }; |
626 }; |
577 |
627 |
578 /** |
628 /** |
579 * Highlight the widget on widget updates or widget control mouse overs. |
629 * Highlights the widget on widget updates or widget control mouse overs. |
580 * |
630 * |
581 * @memberOf wp.customize.widgetsPreview |
631 * @memberOf wp.customize.widgetsPreview |
582 * |
632 * |
583 * @since 3.9.0 |
633 * @since 3.9.0 |
584 * @param {string} widgetId ID of the widget. |
634 * @param {string} widgetId ID of the widget. |
|
635 * |
|
636 * @returns {void} |
585 */ |
637 */ |
586 self.highlightWidget = function( widgetId ) { |
638 self.highlightWidget = function( widgetId ) { |
587 var $body = $( document.body ), |
639 var $body = $( document.body ), |
588 $widget = $( '#' + widgetId ); |
640 $widget = $( '#' + widgetId ); |
589 |
641 |
594 $widget.removeClass( 'widget-customizer-highlighted-widget' ); |
646 $widget.removeClass( 'widget-customizer-highlighted-widget' ); |
595 }, 500 ); |
647 }, 500 ); |
596 }; |
648 }; |
597 |
649 |
598 /** |
650 /** |
599 * Show a title and highlight widgets on hover. On shift+clicking |
651 * Shows a title and highlights widgets on hover. On shift+clicking focuses the |
600 * focus the widget control. |
652 * widget control. |
601 * |
653 * |
602 * @memberOf wp.customize.widgetsPreview |
654 * @memberOf wp.customize.widgetsPreview |
603 * |
655 * |
604 * @since 3.9.0 |
656 * @since 3.9.0 |
|
657 * |
|
658 * @returns {void} |
605 */ |
659 */ |
606 self.highlightControls = function() { |
660 self.highlightControls = function() { |
607 var self = this, |
661 var self = this, |
608 selector = this.widgetSelectors.join( ',' ); |
662 selector = this.widgetSelectors.join( ',' ); |
609 |
663 |
611 if ( ! api.settings.channel ) { |
665 if ( ! api.settings.channel ) { |
612 return; |
666 return; |
613 } |
667 } |
614 |
668 |
615 $( selector ).attr( 'title', this.l10n.widgetTooltip ); |
669 $( selector ).attr( 'title', this.l10n.widgetTooltip ); |
616 |
670 // Highlights widget when entering the widget editor. |
617 $( document ).on( 'mouseenter', selector, function() { |
671 $( document ).on( 'mouseenter', selector, function() { |
618 self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) ); |
672 self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) ); |
619 }); |
673 }); |
620 |
674 |
621 // Open expand the widget control when shift+clicking the widget element |
675 // Open expand the widget control when shift+clicking the widget element |
628 self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) ); |
682 self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) ); |
629 }); |
683 }); |
630 }; |
684 }; |
631 |
685 |
632 /** |
686 /** |
633 * Parse a widget ID. |
687 * Parses a widget ID. |
634 * |
688 * |
635 * @memberOf wp.customize.widgetsPreview |
689 * @memberOf wp.customize.widgetsPreview |
636 * |
690 * |
637 * @since 4.5.0 |
691 * @since 4.5.0 |
638 * |
692 * |
639 * @param {string} widgetId Widget ID. |
693 * @param {string} widgetId The widget ID. |
640 * @returns {{idBase: string, number: number|null}} |
694 * |
|
695 * @returns {{idBase: string, number: number|null}} An object containing the |
|
696 * idBase and number of the parsed widget ID. |
641 */ |
697 */ |
642 self.parseWidgetId = function( widgetId ) { |
698 self.parseWidgetId = function( widgetId ) { |
643 var matches, parsed = { |
699 var matches, parsed = { |
644 idBase: '', |
700 idBase: '', |
645 number: null |
701 number: null |
655 |
711 |
656 return parsed; |
712 return parsed; |
657 }; |
713 }; |
658 |
714 |
659 /** |
715 /** |
660 * Parse a widget setting ID. |
716 * Parses a widget setting ID. |
661 * |
717 * |
662 * @memberOf wp.customize.widgetsPreview |
718 * @memberOf wp.customize.widgetsPreview |
663 * |
719 * |
664 * @since 4.5.0 |
720 * @since 4.5.0 |
665 * |
721 * |
666 * @param {string} settingId Widget setting ID. |
722 * @param {string} settingId Widget setting ID. |
667 * @returns {{idBase: string, number: number|null}|null} |
723 * |
|
724 * @returns {{idBase: string, number: number|null}|null} Either an object |
|
725 * containing the idBase and number of the parsed widget setting ID, or |
|
726 * null. |
668 */ |
727 */ |
669 self.parseWidgetSettingId = function( settingId ) { |
728 self.parseWidgetSettingId = function( settingId ) { |
670 var matches, parsed = { |
729 var matches, parsed = { |
671 idBase: '', |
730 idBase: '', |
672 number: null |
731 number: null |
682 } |
741 } |
683 return parsed; |
742 return parsed; |
684 }; |
743 }; |
685 |
744 |
686 /** |
745 /** |
687 * Convert a widget ID into a Customizer setting ID. |
746 * Converts a widget ID into a Customizer setting ID. |
688 * |
747 * |
689 * @memberOf wp.customize.widgetsPreview |
748 * @memberOf wp.customize.widgetsPreview |
690 * |
749 * |
691 * @since 4.5.0 |
750 * @since 4.5.0 |
692 * |
751 * |
693 * @param {string} widgetId Widget ID. |
752 * @param {string} widgetId The widget ID. |
694 * @returns {string} settingId Setting ID. |
753 * |
|
754 * @returns {string} The setting ID. |
695 */ |
755 */ |
696 self.getWidgetSettingId = function( widgetId ) { |
756 self.getWidgetSettingId = function( widgetId ) { |
697 var parsed = this.parseWidgetId( widgetId ), settingId; |
757 var parsed = this.parseWidgetId( widgetId ), settingId; |
698 |
758 |
699 settingId = 'widget_' + parsed.idBase; |
759 settingId = 'widget_' + parsed.idBase; |