146 el: '#available-widgets', |
145 el: '#available-widgets', |
147 |
146 |
148 events: { |
147 events: { |
149 'input #widgets-search': 'search', |
148 'input #widgets-search': 'search', |
150 'keyup #widgets-search': 'search', |
149 'keyup #widgets-search': 'search', |
151 'change #widgets-search': 'search', |
|
152 'search #widgets-search': 'search', |
|
153 'focus .widget-tpl' : 'focus', |
150 'focus .widget-tpl' : 'focus', |
154 'click .widget-tpl' : '_submit', |
151 'click .widget-tpl' : '_submit', |
155 'keypress .widget-tpl' : '_submit', |
152 'keypress .widget-tpl' : '_submit', |
156 'keydown' : 'keyboardAccessible' |
153 'keydown' : 'keyboardAccessible' |
157 }, |
154 }, |
160 selected: null, |
157 selected: null, |
161 |
158 |
162 // Cache sidebar control which has opened panel |
159 // Cache sidebar control which has opened panel |
163 currentSidebarControl: null, |
160 currentSidebarControl: null, |
164 $search: null, |
161 $search: null, |
|
162 $clearResults: null, |
|
163 searchMatchesCount: null, |
165 |
164 |
166 initialize: function() { |
165 initialize: function() { |
167 var self = this; |
166 var self = this; |
168 |
167 |
169 this.$search = $( '#widgets-search' ); |
168 this.$search = $( '#widgets-search' ); |
170 |
169 |
|
170 this.$clearResults = this.$el.find( '.clear-results' ); |
|
171 |
171 _.bindAll( this, 'close' ); |
172 _.bindAll( this, 'close' ); |
172 |
173 |
173 this.listenTo( this.collection, 'change', this.updateList ); |
174 this.listenTo( this.collection, 'change', this.updateList ); |
174 |
175 |
175 this.updateList(); |
176 this.updateList(); |
|
177 |
|
178 // Set the initial search count to the number of available widgets. |
|
179 this.searchMatchesCount = this.collection.length; |
176 |
180 |
177 // If the available widgets panel is open and the customize controls are |
181 // If the available widgets panel is open and the customize controls are |
178 // interacted with (i.e. available widgets panel is blurred) then close the |
182 // interacted with (i.e. available widgets panel is blurred) then close the |
179 // available widgets panel. |
183 // available widgets panel. Also close on back button click. |
180 $( '#customize-controls, .customize-overlay-close' ).on( 'click keydown', function( e ) { |
184 $( '#customize-controls, #available-widgets .customize-section-title' ).on( 'click keydown', function( e ) { |
181 var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' ); |
185 var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' ); |
182 if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) { |
186 if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) { |
183 self.close(); |
187 self.close(); |
184 } |
188 } |
185 } ); |
189 } ); |
186 |
190 |
|
191 // Clear the search results and trigger a `keyup` event to fire a new search. |
|
192 this.$clearResults.on( 'click', function() { |
|
193 self.$search.val( '' ).focus().trigger( 'keyup' ); |
|
194 } ); |
|
195 |
187 // Close the panel if the URL in the preview changes |
196 // Close the panel if the URL in the preview changes |
188 api.previewer.bind( 'url', this.close ); |
197 api.previewer.bind( 'url', this.close ); |
189 }, |
198 }, |
190 |
199 |
191 // Performs a search and handles selected widget |
200 // Performs a search and handles selected widget |
192 search: function( event ) { |
201 search: function( event ) { |
193 var firstVisible; |
202 var firstVisible; |
194 |
203 |
195 this.collection.doSearch( event.target.value ); |
204 this.collection.doSearch( event.target.value ); |
|
205 // Update the search matches count. |
|
206 this.updateSearchMatchesCount(); |
|
207 // Announce how many search results. |
|
208 this.announceSearchMatches(); |
196 |
209 |
197 // Remove a widget from being selected if it is no longer visible |
210 // Remove a widget from being selected if it is no longer visible |
198 if ( this.selected && ! this.selected.is( ':visible' ) ) { |
211 if ( this.selected && ! this.selected.is( ':visible' ) ) { |
199 this.selected.removeClass( 'selected' ); |
212 this.selected.removeClass( 'selected' ); |
200 this.selected = null; |
213 this.selected = null; |
211 firstVisible = this.$el.find( '> .widget-tpl:visible:first' ); |
224 firstVisible = this.$el.find( '> .widget-tpl:visible:first' ); |
212 if ( firstVisible.length ) { |
225 if ( firstVisible.length ) { |
213 this.select( firstVisible ); |
226 this.select( firstVisible ); |
214 } |
227 } |
215 } |
228 } |
216 }, |
229 |
|
230 // Toggle the clear search results button. |
|
231 if ( '' !== event.target.value ) { |
|
232 this.$clearResults.addClass( 'is-visible' ); |
|
233 } else if ( '' === event.target.value ) { |
|
234 this.$clearResults.removeClass( 'is-visible' ); |
|
235 } |
|
236 |
|
237 // Set a CSS class on the search container when there are no search results. |
|
238 if ( ! this.searchMatchesCount ) { |
|
239 this.$el.addClass( 'no-widgets-found' ); |
|
240 } else { |
|
241 this.$el.removeClass( 'no-widgets-found' ); |
|
242 } |
|
243 }, |
|
244 |
|
245 // Update the count of the available widgets that have the `search_matched` attribute. |
|
246 updateSearchMatchesCount: function() { |
|
247 this.searchMatchesCount = this.collection.where({ search_matched: true }).length; |
|
248 }, |
|
249 |
|
250 // Send a message to the aria-live region to announce how many search results. |
|
251 announceSearchMatches: _.debounce( function() { |
|
252 var message = l10n.widgetsFound.replace( '%d', this.searchMatchesCount ) ; |
|
253 |
|
254 if ( ! this.searchMatchesCount ) { |
|
255 message = l10n.noWidgetsFound; |
|
256 } |
|
257 |
|
258 wp.a11y.speak( message ); |
|
259 }, 500 ), |
217 |
260 |
218 // Changes visibility of available widgets |
261 // Changes visibility of available widgets |
219 updateList: function() { |
262 updateList: function() { |
220 this.collection.each( function( widget ) { |
263 this.collection.each( function( widget ) { |
221 var widgetTpl = $( '#widget-tpl-' + widget.id ); |
264 var widgetTpl = $( '#widget-tpl-' + widget.id ); |
415 }, |
462 }, |
416 |
463 |
417 /** |
464 /** |
418 * @since 4.1.0 |
465 * @since 4.1.0 |
419 */ |
466 */ |
420 initialize: function ( id, options ) { |
467 initialize: function( id, options ) { |
421 var control = this; |
468 var control = this; |
422 api.Control.prototype.initialize.call( control, id, options ); |
469 |
423 control.expanded = new api.Value(); |
470 control.widgetControlEmbedded = false; |
|
471 control.widgetContentEmbedded = false; |
|
472 control.expanded = new api.Value( false ); |
424 control.expandedArgumentsQueue = []; |
473 control.expandedArgumentsQueue = []; |
425 control.expanded.bind( function ( expanded ) { |
474 control.expanded.bind( function( expanded ) { |
426 var args = control.expandedArgumentsQueue.shift(); |
475 var args = control.expandedArgumentsQueue.shift(); |
427 args = $.extend( {}, control.defaultExpandedArguments, args ); |
476 args = $.extend( {}, control.defaultExpandedArguments, args ); |
428 control.onChangeExpanded( expanded, args ); |
477 control.onChangeExpanded( expanded, args ); |
429 }); |
478 }); |
430 control.expanded.set( false ); |
479 control.altNotice = true; |
431 }, |
480 |
432 |
481 api.Control.prototype.initialize.call( control, id, options ); |
433 /** |
482 }, |
434 * Set up the control |
483 |
|
484 /** |
|
485 * Set up the control. |
|
486 * |
|
487 * @since 3.9.0 |
435 */ |
488 */ |
436 ready: function() { |
489 ready: function() { |
437 this._setupModel(); |
490 var control = this; |
438 this._setupWideWidget(); |
491 |
439 this._setupControlToggle(); |
492 /* |
440 this._setupWidgetTitle(); |
493 * Embed a placeholder once the section is expanded. The full widget |
441 this._setupReorderUI(); |
494 * form content will be embedded once the control itself is expanded, |
442 this._setupHighlightEffects(); |
495 * and at this point the widget-added event will be triggered. |
443 this._setupUpdateUI(); |
496 */ |
444 this._setupRemoveUI(); |
497 if ( ! control.section() ) { |
|
498 control.embedWidgetControl(); |
|
499 } else { |
|
500 api.section( control.section(), function( section ) { |
|
501 var onExpanded = function( isExpanded ) { |
|
502 if ( isExpanded ) { |
|
503 control.embedWidgetControl(); |
|
504 section.expanded.unbind( onExpanded ); |
|
505 } |
|
506 }; |
|
507 if ( section.expanded() ) { |
|
508 onExpanded( true ); |
|
509 } else { |
|
510 section.expanded.bind( onExpanded ); |
|
511 } |
|
512 } ); |
|
513 } |
|
514 }, |
|
515 |
|
516 /** |
|
517 * Embed the .widget element inside the li container. |
|
518 * |
|
519 * @since 4.4.0 |
|
520 */ |
|
521 embedWidgetControl: function() { |
|
522 var control = this, widgetControl; |
|
523 |
|
524 if ( control.widgetControlEmbedded ) { |
|
525 return; |
|
526 } |
|
527 control.widgetControlEmbedded = true; |
|
528 |
|
529 widgetControl = $( control.params.widget_control ); |
|
530 control.container.append( widgetControl ); |
|
531 |
|
532 control._setupModel(); |
|
533 control._setupWideWidget(); |
|
534 control._setupControlToggle(); |
|
535 |
|
536 control._setupWidgetTitle(); |
|
537 control._setupReorderUI(); |
|
538 control._setupHighlightEffects(); |
|
539 control._setupUpdateUI(); |
|
540 control._setupRemoveUI(); |
|
541 }, |
|
542 |
|
543 /** |
|
544 * Embed the actual widget form inside of .widget-content and finally trigger the widget-added event. |
|
545 * |
|
546 * @since 4.4.0 |
|
547 */ |
|
548 embedWidgetContent: function() { |
|
549 var control = this, widgetContent; |
|
550 |
|
551 control.embedWidgetControl(); |
|
552 if ( control.widgetContentEmbedded ) { |
|
553 return; |
|
554 } |
|
555 control.widgetContentEmbedded = true; |
|
556 |
|
557 // Update the notification container element now that the widget content has been embedded. |
|
558 control.notifications.container = control.getNotificationsContainerElement(); |
|
559 control.notifications.render(); |
|
560 |
|
561 widgetContent = $( control.params.widget_content ); |
|
562 control.container.find( '.widget-content:first' ).append( widgetContent ); |
445 |
563 |
446 /* |
564 /* |
447 * Trigger widget-added event so that plugins can attach any event |
565 * Trigger widget-added event so that plugins can attach any event |
448 * listeners and dynamic UI elements. |
566 * listeners and dynamic UI elements. |
449 */ |
567 */ |
450 $( document ).trigger( 'widget-added', [ this.container.find( '.widget:first' ) ] ); |
568 $( document ).trigger( 'widget-added', [ control.container.find( '.widget:first' ) ] ); |
|
569 |
451 }, |
570 }, |
452 |
571 |
453 /** |
572 /** |
454 * Handle changes to the setting |
573 * Handle changes to the setting |
455 */ |
574 */ |
456 _setupModel: function() { |
575 _setupModel: function() { |
457 var self = this, rememberSavedWidgetId; |
576 var self = this, rememberSavedWidgetId; |
458 |
|
459 api.Widgets.savedWidgetIds = api.Widgets.savedWidgetIds || []; |
|
460 |
577 |
461 // Remember saved widgets so we know which to trash (move to inactive widgets sidebar) |
578 // Remember saved widgets so we know which to trash (move to inactive widgets sidebar) |
462 rememberSavedWidgetId = function() { |
579 rememberSavedWidgetId = function() { |
463 api.Widgets.savedWidgetIds[self.params.widget_id] = true; |
580 api.Widgets.savedWidgetIds[self.params.widget_id] = true; |
464 }; |
581 }; |
482 */ |
599 */ |
483 _setupWideWidget: function() { |
600 _setupWideWidget: function() { |
484 var self = this, $widgetInside, $widgetForm, $customizeSidebar, |
601 var self = this, $widgetInside, $widgetForm, $customizeSidebar, |
485 $themeControlsContainer, positionWidget; |
602 $themeControlsContainer, positionWidget; |
486 |
603 |
487 if ( ! this.params.is_wide ) { |
604 if ( ! this.params.is_wide || $( window ).width() <= 640 /* max-width breakpoint in customize-controls.css */ ) { |
488 return; |
605 return; |
489 } |
606 } |
490 |
607 |
491 $widgetInside = this.container.find( '.widget-inside' ); |
608 $widgetInside = this.container.find( '.widget-inside' ); |
492 $widgetForm = $widgetInside.find( '> .form' ); |
609 $widgetForm = $widgetInside.find( '> .form' ); |
493 $customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); |
610 $customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); |
494 this.container.addClass( 'wide-widget-control' ); |
611 this.container.addClass( 'wide-widget-control' ); |
495 |
612 |
496 this.container.find( '.widget-content:first' ).css( { |
613 this.container.find( '.form:first' ).css( { |
497 'max-width': this.params.width, |
614 'max-width': this.params.width, |
498 'min-height': this.params.height |
615 'min-height': this.params.height |
499 } ); |
616 } ); |
500 |
617 |
501 /** |
618 /** |
1007 */ |
1125 */ |
1008 updateWidget: function( args ) { |
1126 updateWidget: function( args ) { |
1009 var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent, |
1127 var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent, |
1010 updateNumber, params, data, $inputs, processing, jqxhr, isChanged; |
1128 updateNumber, params, data, $inputs, processing, jqxhr, isChanged; |
1011 |
1129 |
|
1130 // The updateWidget logic requires that the form fields to be fully present. |
|
1131 self.embedWidgetContent(); |
|
1132 |
1012 args = $.extend( { |
1133 args = $.extend( { |
1013 instance: null, |
1134 instance: null, |
1014 complete: null, |
1135 complete: null, |
1015 ignoreActiveElement: false |
1136 ignoreActiveElement: false |
1016 }, args ); |
1137 }, args ); |
1251 * |
1373 * |
1252 * @param {Boolean} expanded |
1374 * @param {Boolean} expanded |
1253 * @param {Object} args merged on top of this.defaultActiveArguments |
1375 * @param {Object} args merged on top of this.defaultActiveArguments |
1254 */ |
1376 */ |
1255 onChangeExpanded: function ( expanded, args ) { |
1377 onChangeExpanded: function ( expanded, args ) { |
1256 var self = this, $widget, $inside, complete, prevComplete; |
1378 var self = this, $widget, $inside, complete, prevComplete, expandControl, $toggleBtn; |
|
1379 |
|
1380 self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI. |
|
1381 if ( expanded ) { |
|
1382 self.embedWidgetContent(); |
|
1383 } |
1257 |
1384 |
1258 // If the expanded state is unchanged only manipulate container expanded states |
1385 // If the expanded state is unchanged only manipulate container expanded states |
1259 if ( args.unchanged ) { |
1386 if ( args.unchanged ) { |
1260 if ( expanded ) { |
1387 if ( expanded ) { |
1261 api.Control.prototype.expand.call( self, { |
1388 api.Control.prototype.expand.call( self, { |
1298 $inside.slideDown( args.duration, complete ); |
1426 $inside.slideDown( args.duration, complete ); |
1299 } |
1427 } |
1300 |
1428 |
1301 self.container.trigger( 'expand' ); |
1429 self.container.trigger( 'expand' ); |
1302 self.container.addClass( 'expanding' ); |
1430 self.container.addClass( 'expanding' ); |
|
1431 }; |
|
1432 |
|
1433 if ( expanded ) { |
|
1434 if ( api.section.has( self.section() ) ) { |
|
1435 api.section( self.section() ).expand( { |
|
1436 completeCallback: expandControl |
|
1437 } ); |
|
1438 } else { |
|
1439 expandControl(); |
|
1440 } |
1303 } else { |
1441 } else { |
1304 |
|
1305 complete = function() { |
1442 complete = function() { |
1306 self.container.removeClass( 'collapsing' ); |
1443 self.container.removeClass( 'collapsing' ); |
1307 self.container.removeClass( 'expanded' ); |
1444 self.container.removeClass( 'expanded' ); |
|
1445 $widget.removeClass( 'open' ); |
|
1446 $toggleBtn.attr( 'aria-expanded', 'false' ); |
1308 self.container.trigger( 'collapsed' ); |
1447 self.container.trigger( 'collapsed' ); |
1309 }; |
1448 }; |
1310 if ( args.completeCallback ) { |
1449 if ( args.completeCallback ) { |
1311 prevComplete = complete; |
1450 prevComplete = complete; |
1312 complete = function () { |
1451 complete = function () { |
1426 setTimeout( function() { |
1565 setTimeout( function() { |
1427 $target.removeClass( 'highlighted' ); |
1566 $target.removeClass( 'highlighted' ); |
1428 }, 500 ); |
1567 }, 500 ); |
1429 } |
1568 } |
1430 } ); |
1569 } ); |
|
1570 |
|
1571 /** |
|
1572 * wp.customize.Widgets.WidgetsPanel |
|
1573 * |
|
1574 * Customizer panel containing the widget area sections. |
|
1575 * |
|
1576 * @since 4.4.0 |
|
1577 */ |
|
1578 api.Widgets.WidgetsPanel = api.Panel.extend({ |
|
1579 |
|
1580 /** |
|
1581 * Add and manage the display of the no-rendered-areas notice. |
|
1582 * |
|
1583 * @since 4.4.0 |
|
1584 */ |
|
1585 ready: function () { |
|
1586 var panel = this; |
|
1587 |
|
1588 api.Panel.prototype.ready.call( panel ); |
|
1589 |
|
1590 panel.deferred.embedded.done(function() { |
|
1591 var panelMetaContainer, noticeContainer, updateNotice, getActiveSectionCount, shouldShowNotice; |
|
1592 panelMetaContainer = panel.container.find( '.panel-meta' ); |
|
1593 |
|
1594 // @todo This should use the Notifications API introduced to panels. See <https://core.trac.wordpress.org/ticket/38794>. |
|
1595 noticeContainer = $( '<div></div>', { |
|
1596 'class': 'no-widget-areas-rendered-notice' |
|
1597 }); |
|
1598 panelMetaContainer.append( noticeContainer ); |
|
1599 |
|
1600 /** |
|
1601 * Get the number of active sections in the panel. |
|
1602 * |
|
1603 * @return {number} Number of active sidebar sections. |
|
1604 */ |
|
1605 getActiveSectionCount = function() { |
|
1606 return _.filter( panel.sections(), function( section ) { |
|
1607 return section.active(); |
|
1608 } ).length; |
|
1609 }; |
|
1610 |
|
1611 /** |
|
1612 * Determine whether or not the notice should be displayed. |
|
1613 * |
|
1614 * @return {boolean} |
|
1615 */ |
|
1616 shouldShowNotice = function() { |
|
1617 var activeSectionCount = getActiveSectionCount(); |
|
1618 if ( 0 === activeSectionCount ) { |
|
1619 return true; |
|
1620 } else { |
|
1621 return activeSectionCount !== api.Widgets.data.registeredSidebars.length; |
|
1622 } |
|
1623 }; |
|
1624 |
|
1625 /** |
|
1626 * Update the notice. |
|
1627 * |
|
1628 * @returns {void} |
|
1629 */ |
|
1630 updateNotice = function() { |
|
1631 var activeSectionCount = getActiveSectionCount(), someRenderedMessage, nonRenderedAreaCount, registeredAreaCount; |
|
1632 noticeContainer.empty(); |
|
1633 |
|
1634 registeredAreaCount = api.Widgets.data.registeredSidebars.length; |
|
1635 if ( activeSectionCount !== registeredAreaCount ) { |
|
1636 |
|
1637 if ( 0 !== activeSectionCount ) { |
|
1638 nonRenderedAreaCount = registeredAreaCount - activeSectionCount; |
|
1639 someRenderedMessage = l10n.someAreasShown[ nonRenderedAreaCount ]; |
|
1640 } else { |
|
1641 someRenderedMessage = l10n.noAreasShown; |
|
1642 } |
|
1643 if ( someRenderedMessage ) { |
|
1644 noticeContainer.append( $( '<p></p>', { |
|
1645 text: someRenderedMessage |
|
1646 } ) ); |
|
1647 } |
|
1648 |
|
1649 noticeContainer.append( $( '<p></p>', { |
|
1650 text: l10n.navigatePreview |
|
1651 } ) ); |
|
1652 } |
|
1653 }; |
|
1654 updateNotice(); |
|
1655 |
|
1656 /* |
|
1657 * Set the initial visibility state for rendered notice. |
|
1658 * Update the visibility of the notice whenever a reflow happens. |
|
1659 */ |
|
1660 noticeContainer.toggle( shouldShowNotice() ); |
|
1661 api.previewer.deferred.active.done( function () { |
|
1662 noticeContainer.toggle( shouldShowNotice() ); |
|
1663 }); |
|
1664 api.bind( 'pane-contents-reflowed', function() { |
|
1665 var duration = ( 'resolved' === api.previewer.deferred.active.state() ) ? 'fast' : 0; |
|
1666 updateNotice(); |
|
1667 if ( shouldShowNotice() ) { |
|
1668 noticeContainer.slideDown( duration ); |
|
1669 } else { |
|
1670 noticeContainer.slideUp( duration ); |
|
1671 } |
|
1672 }); |
|
1673 }); |
|
1674 }, |
|
1675 |
|
1676 /** |
|
1677 * Allow an active widgets panel to be contextually active even when it has no active sections (widget areas). |
|
1678 * |
|
1679 * This ensures that the widgets panel appears even when there are no |
|
1680 * sidebars displayed on the URL currently being previewed. |
|
1681 * |
|
1682 * @since 4.4.0 |
|
1683 * |
|
1684 * @returns {boolean} |
|
1685 */ |
|
1686 isContextuallyActive: function() { |
|
1687 var panel = this; |
|
1688 return panel.active(); |
|
1689 } |
|
1690 }); |
1431 |
1691 |
1432 /** |
1692 /** |
1433 * wp.customize.Widgets.SidebarSection |
1693 * wp.customize.Widgets.SidebarSection |
1434 * |
1694 * |
1435 * Customizer section representing a widget area widget |
1695 * Customizer section representing a widget area widget |
1602 */ |
1862 */ |
1603 this.$sectionContent.sortable( { |
1863 this.$sectionContent.sortable( { |
1604 items: '> .customize-control-widget_form', |
1864 items: '> .customize-control-widget_form', |
1605 handle: '.widget-top', |
1865 handle: '.widget-top', |
1606 axis: 'y', |
1866 axis: 'y', |
|
1867 tolerance: 'pointer', |
1607 connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)', |
1868 connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)', |
1608 update: function() { |
1869 update: function() { |
1609 var widgetContainerIds = self.$sectionContent.sortable( 'toArray' ), widgetIds; |
1870 var widgetContainerIds = self.$sectionContent.sortable( 'toArray' ), widgetIds; |
1610 |
1871 |
1611 widgetIds = $.map( widgetContainerIds, function( widgetContainerId ) { |
1872 widgetIds = $.map( widgetContainerIds, function( widgetContainerId ) { |
1639 }); |
1900 }); |
1640 |
1901 |
1641 /** |
1902 /** |
1642 * Keyboard-accessible reordering |
1903 * Keyboard-accessible reordering |
1643 */ |
1904 */ |
1644 this.container.find( '.reorder-toggle' ).on( 'click keydown', function( event ) { |
1905 this.container.find( '.reorder-toggle' ).on( 'click', function() { |
1645 if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar |
|
1646 return; |
|
1647 } |
|
1648 |
|
1649 self.toggleReordering( ! self.isReordering ); |
1906 self.toggleReordering( ! self.isReordering ); |
1650 } ); |
1907 } ); |
1651 }, |
1908 }, |
1652 |
1909 |
1653 /** |
1910 /** |
1654 * Set up UI for adding a new widget |
1911 * Set up UI for adding a new widget |
1655 */ |
1912 */ |
1656 _setupAddition: function() { |
1913 _setupAddition: function() { |
1657 var self = this; |
1914 var self = this; |
1658 |
1915 |
1659 this.container.find( '.add-new-widget' ).on( 'click keydown', function( event ) { |
1916 this.container.find( '.add-new-widget' ).on( 'click', function() { |
1660 if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar |
1917 var addNewWidgetBtn = $( this ); |
1661 return; |
|
1662 } |
|
1663 |
1918 |
1664 if ( self.$sectionContent.hasClass( 'reordering' ) ) { |
1919 if ( self.$sectionContent.hasClass( 'reordering' ) ) { |
1665 return; |
1920 return; |
1666 } |
1921 } |
1667 |
1922 |
1668 if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) { |
1923 if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) { |
|
1924 addNewWidgetBtn.attr( 'aria-expanded', 'true' ); |
1669 api.Widgets.availableWidgetsPanel.open( self ); |
1925 api.Widgets.availableWidgetsPanel.open( self ); |
1670 } else { |
1926 } else { |
|
1927 addNewWidgetBtn.attr( 'aria-expanded', 'false' ); |
1671 api.Widgets.availableWidgetsPanel.close(); |
1928 api.Widgets.availableWidgetsPanel.close(); |
1672 } |
1929 } |
1673 } ); |
1930 } ); |
1674 }, |
1931 }, |
1675 |
1932 |
1733 if ( showOrHide ) { |
1994 if ( showOrHide ) { |
1734 _( this.getWidgetFormControls() ).each( function( formControl ) { |
1995 _( this.getWidgetFormControls() ).each( function( formControl ) { |
1735 formControl.collapse(); |
1996 formControl.collapse(); |
1736 } ); |
1997 } ); |
1737 |
1998 |
1738 this.$sectionContent.find( '.first-widget .move-widget' ).focus(); |
1999 addNewWidgetBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' }); |
1739 this.$sectionContent.find( '.add-new-widget' ).prop( 'tabIndex', -1 ); |
2000 reorderBtn.attr( 'aria-label', l10n.reorderLabelOff ); |
|
2001 wp.a11y.speak( l10n.reorderModeOn ); |
|
2002 // Hide widget titles while reordering: title is already in the reorder controls. |
|
2003 widgetsTitle.attr( 'aria-hidden', 'true' ); |
1740 } else { |
2004 } else { |
1741 this.$sectionContent.find( '.add-new-widget' ).prop( 'tabIndex', 0 ); |
2005 addNewWidgetBtn.removeAttr( 'tabindex aria-hidden' ); |
|
2006 reorderBtn.attr( 'aria-label', l10n.reorderLabelOn ); |
|
2007 wp.a11y.speak( l10n.reorderModeOff ); |
|
2008 widgetsTitle.attr( 'aria-hidden', 'false' ); |
1742 } |
2009 } |
1743 }, |
2010 }, |
1744 |
2011 |
1745 /** |
2012 /** |
1746 * Get the widget_form Customize controls associated with the current sidebar. |
2013 * Get the widget_form Customize controls associated with the current sidebar. |
1747 * |
2014 * |
1748 * @since 3.9 |
2015 * @since 3.9.0 |
1749 * @return {wp.customize.controlConstructor.widget_form[]} |
2016 * @return {wp.customize.controlConstructor.widget_form[]} |
1750 */ |
2017 */ |
1751 getWidgetFormControls: function() { |
2018 getWidgetFormControls: function() { |
1752 var formControls = []; |
2019 var formControls = []; |
1753 |
2020 |
1824 |
2091 |
1825 // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) |
2092 // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) |
1826 isExistingWidget = api.has( settingId ); |
2093 isExistingWidget = api.has( settingId ); |
1827 if ( ! isExistingWidget ) { |
2094 if ( ! isExistingWidget ) { |
1828 settingArgs = { |
2095 settingArgs = { |
1829 transport: 'refresh', |
2096 transport: api.Widgets.data.selectiveRefreshableWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh', |
1830 previewer: this.setting.previewer |
2097 previewer: this.setting.previewer |
1831 }; |
2098 }; |
1832 setting = api.create( settingId, settingId, '', settingArgs ); |
2099 setting = api.create( settingId, settingId, '', settingArgs ); |
1833 setting.set( {} ); // mark dirty, changing from '' to {} |
2100 setting.set( {} ); // mark dirty, changing from '' to {} |
1834 } |
2101 } |
1835 |
2102 |
1836 controlConstructor = api.controlConstructor[controlType]; |
2103 controlConstructor = api.controlConstructor[controlType]; |
1837 widgetFormControl = new controlConstructor( settingId, { |
2104 widgetFormControl = new controlConstructor( settingId, { |
1838 params: { |
2105 settings: { |
1839 settings: { |
2106 'default': settingId |
1840 'default': settingId |
|
1841 }, |
|
1842 content: controlContainer, |
|
1843 sidebar_id: self.params.sidebar_id, |
|
1844 widget_id: widgetId, |
|
1845 widget_id_base: widget.get( 'id_base' ), |
|
1846 type: controlType, |
|
1847 is_new: ! isExistingWidget, |
|
1848 width: widget.get( 'width' ), |
|
1849 height: widget.get( 'height' ), |
|
1850 is_wide: widget.get( 'is_wide' ) |
|
1851 }, |
2107 }, |
1852 previewer: self.setting.previewer |
2108 content: controlContainer, |
1853 } ); |
2109 sidebar_id: self.params.sidebar_id, |
1854 api.control.add( settingId, widgetFormControl ); |
2110 widget_id: widgetId, |
|
2111 widget_id_base: widget.get( 'id_base' ), |
|
2112 type: controlType, |
|
2113 is_new: ! isExistingWidget, |
|
2114 width: widget.get( 'width' ), |
|
2115 height: widget.get( 'height' ), |
|
2116 is_wide: widget.get( 'is_wide' ) |
|
2117 } ); |
|
2118 api.control.add( widgetFormControl ); |
1855 |
2119 |
1856 // Make sure widget is removed from the other sidebars |
2120 // Make sure widget is removed from the other sidebars |
1857 api.each( function( otherSetting ) { |
2121 api.each( function( otherSetting ) { |
1858 if ( otherSetting.id === self.setting.id ) { |
2122 if ( otherSetting.id === self.setting.id ) { |
1859 return; |
2123 return; |
1889 |
2153 |
1890 return widgetFormControl; |
2154 return widgetFormControl; |
1891 } |
2155 } |
1892 } ); |
2156 } ); |
1893 |
2157 |
1894 // Register models for custom section and control types |
2158 // Register models for custom panel, section, and control types |
|
2159 $.extend( api.panelConstructor, { |
|
2160 widgets: api.Widgets.WidgetsPanel |
|
2161 }); |
1895 $.extend( api.sectionConstructor, { |
2162 $.extend( api.sectionConstructor, { |
1896 sidebar: api.Widgets.SidebarSection |
2163 sidebar: api.Widgets.SidebarSection |
1897 }); |
2164 }); |
1898 $.extend( api.controlConstructor, { |
2165 $.extend( api.controlConstructor, { |
1899 widget_form: api.Widgets.WidgetControl, |
2166 widget_form: api.Widgets.WidgetControl, |
1900 sidebar_widgets: api.Widgets.SidebarControl |
2167 sidebar_widgets: api.Widgets.SidebarControl |
1901 }); |
|
1902 |
|
1903 // Refresh the nonce if login sends updated nonces over. |
|
1904 api.bind( 'nonce-refresh', function( nonces ) { |
|
1905 api.Widgets.data.nonce = nonces['update-widget']; |
|
1906 }); |
2168 }); |
1907 |
2169 |
1908 /** |
2170 /** |
1909 * Init Customizer for widgets. |
2171 * Init Customizer for widgets. |
1910 */ |
2172 */ |
1981 } |
2243 } |
1982 } ); |
2244 } ); |
1983 |
2245 |
1984 return foundControl; |
2246 return foundControl; |
1985 }; |
2247 }; |
|
2248 |
|
2249 /** |
|
2250 * Initialize Edit Menu button in Nav Menu widget. |
|
2251 */ |
|
2252 $( document ).on( 'widget-added', function( event, widgetContainer ) { |
|
2253 var parsedWidgetId, widgetControl, navMenuSelect, editMenuButton; |
|
2254 parsedWidgetId = parseWidgetId( widgetContainer.find( '> .widget-inside > .form > .widget-id' ).val() ); |
|
2255 if ( 'nav_menu' !== parsedWidgetId.id_base ) { |
|
2256 return; |
|
2257 } |
|
2258 widgetControl = api.control( 'widget_nav_menu[' + String( parsedWidgetId.number ) + ']' ); |
|
2259 if ( ! widgetControl ) { |
|
2260 return; |
|
2261 } |
|
2262 navMenuSelect = widgetContainer.find( 'select[name*="nav_menu"]' ); |
|
2263 editMenuButton = widgetContainer.find( '.edit-selected-nav-menu > button' ); |
|
2264 if ( 0 === navMenuSelect.length || 0 === editMenuButton.length ) { |
|
2265 return; |
|
2266 } |
|
2267 navMenuSelect.on( 'change', function() { |
|
2268 if ( api.section.has( 'nav_menu[' + navMenuSelect.val() + ']' ) ) { |
|
2269 editMenuButton.parent().show(); |
|
2270 } else { |
|
2271 editMenuButton.parent().hide(); |
|
2272 } |
|
2273 }); |
|
2274 editMenuButton.on( 'click', function() { |
|
2275 var section = api.section( 'nav_menu[' + navMenuSelect.val() + ']' ); |
|
2276 if ( section ) { |
|
2277 focusConstructWithBreadcrumb( section, widgetControl ); |
|
2278 } |
|
2279 } ); |
|
2280 } ); |
|
2281 |
|
2282 /** |
|
2283 * Focus (expand) one construct and then focus on another construct after the first is collapsed. |
|
2284 * |
|
2285 * This overrides the back button to serve the purpose of breadcrumb navigation. |
|
2286 * |
|
2287 * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} focusConstruct - The object to initially focus. |
|
2288 * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} returnConstruct - The object to return focus. |
|
2289 */ |
|
2290 function focusConstructWithBreadcrumb( focusConstruct, returnConstruct ) { |
|
2291 focusConstruct.focus(); |
|
2292 function onceCollapsed( isExpanded ) { |
|
2293 if ( ! isExpanded ) { |
|
2294 focusConstruct.expanded.unbind( onceCollapsed ); |
|
2295 returnConstruct.focus(); |
|
2296 } |
|
2297 } |
|
2298 focusConstruct.expanded.bind( onceCollapsed ); |
|
2299 } |
1986 |
2300 |
1987 /** |
2301 /** |
1988 * @param {String} widgetId |
2302 * @param {String} widgetId |
1989 * @returns {Object} |
2303 * @returns {Object} |
1990 */ |
2304 */ |