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