From da677f34aa3a60932409628d399bc89f2446b0f3 Mon Sep 17 00:00:00 2001 From: "Dominik Schilling (ocean90)" Date: Mon, 7 Apr 2014 09:03:18 +0000 Subject: [PATCH] Widget Customizer: Move `WidgetCustomizer` to `wp.customize.Widgets`. First pass. see #27690. git-svn-id: https://develop.svn.wordpress.org/trunk@27985 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/customize-controls.css | 1 + src/wp-admin/css/customize-widgets.css | 9 +- src/wp-admin/js/customize-widgets.js | 1205 ++++++++--------- .../class-wp-customize-control.php | 9 +- .../class-wp-customize-widgets.php | 50 +- .../js/customize-preview-widgets.js | 4 +- 6 files changed, 618 insertions(+), 660 deletions(-) diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index 48d5a9463a..1fd2006d5c 100644 --- a/src/wp-admin/css/customize-controls.css +++ b/src/wp-admin/css/customize-controls.css @@ -490,6 +490,7 @@ body { overflow: hidden; -webkit-border-radius: 2px; border: 1px solid #eee; + -webkit-border-radius: 2px; border-radius: 2px; } diff --git a/src/wp-admin/css/customize-widgets.css b/src/wp-admin/css/customize-widgets.css index 96afc1969f..b13c673ec4 100644 --- a/src/wp-admin/css/customize-widgets.css +++ b/src/wp-admin/css/customize-widgets.css @@ -108,13 +108,6 @@ display: none; } - -/* MP6-compat */ -#customize-theme-controls .accordion-section-content .widget { - color: black; -} - - /** * Widget reordering styles **/ @@ -326,7 +319,7 @@ body.adding-widget .add-new-widget, body.adding-widget .add-new-widget:hover { - background: #EEE; + background: #eee; border-color: #999; color: #333; -webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5); diff --git a/src/wp-admin/js/customize-widgets.js b/src/wp-admin/js/customize-widgets.js index a141893509..70747e2826 100644 --- a/src/wp-admin/js/customize-widgets.js +++ b/src/wp-admin/js/customize-widgets.js @@ -1,45 +1,23 @@ -/*global wp, Backbone, _, jQuery, WidgetCustomizer_exports */ -/*exported WidgetCustomizer */ -var WidgetCustomizer = ( function ($) { - 'use strict'; +/* global _wpCustomizeWidgetsSettings */ +(function( wp, $ ){ - var Widget, - WidgetCollection, - Sidebar, - SidebarCollection, - OldPreviewer, - builtin_form_sync_handlers, - customize = wp.customize, self = { - nonce: null, - i18n: { - save_btn_label: '', - save_btn_tooltip: '', - remove_btn_label: '', - remove_btn_tooltip: '', - error: '' - }, - available_widgets: [], // available widgets for instantiating - registered_widgets: [], // all widgets registered - active_sidebar_control: null, - previewer: null, - saved_widget_ids: {}, - registered_sidebars: [], - tpl: { - move_widget_area: '', - widget_reorder_nav: '' - } - }; - $.extend( self, WidgetCustomizer_exports ); + if ( ! wp || ! wp.customize ) { return; } - // Lots of widgets expect this old ajaxurl global to be available - if ( typeof window.ajaxurl === 'undefined' ) { - window.ajaxurl = wp.ajax.settings.url; - } + // Set up our namespace... + var api = wp.customize, + l10n, OldPreviewer; + + api.Widgets = api.Widgets || {}; + + // Link settings + api.Widgets.data = _wpCustomizeWidgetsSettings || {}; + l10n = api.Widgets.data.l10n; + delete api.Widgets.data.l10n; /** * Set up model */ - Widget = self.Widget = Backbone.Model.extend( { + api.Widgets.WidgetModel = Backbone.Model.extend({ id: null, temp_id: null, classname: null, @@ -54,10 +32,10 @@ var WidgetCustomizer = ( function ($) { params: [], width: null, height: null - } ); + }); - WidgetCollection = self.WidgetCollection = Backbone.Collection.extend( { - model: Widget, + api.Widgets.WidgetCollection = Backbone.Collection.extend({ + model: api.Widgets.WidgetModel, // Controls searching on the current widget collection // and triggers an update event @@ -80,7 +58,7 @@ var WidgetCustomizer = ( function ($) { // If search is blank, show all themes // Useful for resetting the views when you clean the input if ( this.terms === '' ) { - this.reset( WidgetCustomizer_exports.available_widgets ); + this.reset( api.Widgets.data.availableWidgets ); } // Trigger an 'update' event @@ -93,7 +71,7 @@ var WidgetCustomizer = ( function ($) { var match, results, haystack; // Start with a full collection - this.reset( WidgetCustomizer_exports.available_widgets, { silent: true } ); + this.reset( api.Widgets.data.availableWidgets, { silent: true } ); // Escape the term string for RegExp meta characters term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); @@ -111,10 +89,10 @@ var WidgetCustomizer = ( function ($) { this.reset( results ); } - } ); - self.available_widgets = new WidgetCollection( self.available_widgets ); + }); + api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets ); - Sidebar = self.Sidebar = Backbone.Model.extend( { + api.Widgets.SidebarModel = Backbone.Model.extend({ after_title: null, after_widget: null, before_title: null, @@ -124,502 +102,44 @@ var WidgetCustomizer = ( function ($) { id: null, name: null, is_rendered: false - } ); + }); - SidebarCollection = self.SidebarCollection = Backbone.Collection.extend( { - model: Sidebar - } ); - self.registered_sidebars = new SidebarCollection( self.registered_sidebars ); + api.Widgets.SidebarCollection = Backbone.Collection.extend({ + model: api.Widgets.SidebarModel + }); + api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars ); /** * Handlers for the widget-synced event, organized by widget ID base. * Other widgets may provide their own update handlers by adding * listeners for the widget-synced event. */ - builtin_form_sync_handlers = { + api.Widgets.formSyncHandlers = { /** * @param {jQuery.Event} e - * @param {jQuery} widget_el - * @param {String} new_form + * @param {jQuery} widget + * @param {String} newForm */ - rss: function ( e, widget_el, new_form ) { - var old_widget_error = widget_el.find( '.widget-error:first' ), - new_widget_error = $( '
' + new_form + '
' ).find( '.widget-error:first' ); + rss: function ( e, widget, newForm ) { + var oldWidgetError = widget.find( '.widget-error:first' ), + newWidgetError = $( '
' + newForm + '
' ).find( '.widget-error:first' ); - if ( old_widget_error.length && new_widget_error.length ) { - old_widget_error.replaceWith( new_widget_error ); - } else if ( old_widget_error.length ) { - old_widget_error.remove(); - } else if ( new_widget_error.length ) { - widget_el.find( '.widget-content:first' ).prepend( new_widget_error ); + if ( oldWidgetError.length && newWidgetError.length ) { + oldWidgetError.replaceWith( newWidgetError ); + } else if ( oldWidgetError.length ) { + oldWidgetError.remove(); + } else if ( newWidgetError.length ) { + widget.find( '.widget-content:first' ).prepend( newWidgetError ); } } }; - /** - * On DOM ready, initialize some meta functionality independent of specific - * customizer controls. - */ - self.init = function () { - this.availableWidgetsPanel.setup(); - - // Highlight widget control - this.previewer.bind( 'highlight-widget-control', self.highlightWidgetFormControl ); - - // Open and focus widget control - this.previewer.bind( 'focus-widget-control', self.focusWidgetFormControl ); - }; - wp.customize.bind( 'ready', function () { - self.init(); - } ); - - /** - * Sidebar Widgets control - * Note that 'sidebar_widgets' must match the Sidebar_Widgets_WP_Customize_Control::$type - */ - customize.controlConstructor.sidebar_widgets = customize.Control.extend( { - - /** - * Set up the control - */ - ready: function() { - var control = this; - control.control_section = control.container.closest( '.control-section' ); - control.section_content = control.container.closest( '.accordion-section-content' ); - control._setupModel(); - control._setupSortable(); - control._setupAddition(); - control._applyCardinalOrderClassNames(); - }, - - /** - * Update ordering of widget control forms when the setting is updated - */ - _setupModel: function() { - var control = this, - registered_sidebar = self.registered_sidebars.get( control.params.sidebar_id ); - - control.setting.bind( function( new_widget_ids, old_widget_ids ) { - var widget_form_controls, - sidebar_widgets_add_control, - final_control_containers, - removed_widget_ids = _( old_widget_ids ).difference( new_widget_ids ); - - // Filter out any persistent widget_ids for widgets which have been deactivated - new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) { - var parsed_widget_id = parse_widget_id( new_widget_id ); - return !! self.available_widgets.findWhere( { id_base: parsed_widget_id.id_base } ); - } ); - - widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) { - var widget_form_control = self.getWidgetFormControlForWidget( widget_id ); - if ( ! widget_form_control ) { - widget_form_control = control.addWidget( widget_id ); - } - return widget_form_control; - } ); - - // Sort widget controls to their new positions - widget_form_controls.sort( function ( a, b ) { - var a_index = _.indexOf( new_widget_ids, a.params.widget_id ), - b_index = _.indexOf( new_widget_ids, b.params.widget_id ); - if ( a_index === b_index ) { - return 0; - } - return a_index < b_index ? -1 : 1; - } ); - - sidebar_widgets_add_control = control.section_content.find( '.customize-control-sidebar_widgets' ); - - // Append the controls to put them in the right order - final_control_containers = _( widget_form_controls ).map( function( widget_form_controls ) { - return widget_form_controls.container[0]; - } ); - - // Re-sort widget form controls (including widgets form other sidebars newly moved here) - sidebar_widgets_add_control.before( final_control_containers ); - control._applyCardinalOrderClassNames(); - - // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated - _( widget_form_controls ).each( function ( widget_form_control ) { - widget_form_control.params.sidebar_id = control.params.sidebar_id; - } ); - - // Cleanup after widget removal - _( removed_widget_ids ).each( function ( removed_widget_id ) { - - // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update - setTimeout( function () { - var is_present_in_another_sidebar = false, - removed_control, - was_dragged_to_another_sidebar, - inactive_widgets, - removed_id_base, - widget; - - // Check if the widget is in another sidebar - wp.customize.each( function ( other_setting ) { - if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) { - return; - } - var other_sidebar_widgets = other_setting(), i; - - i = _.indexOf( other_sidebar_widgets, removed_widget_id ); - if ( -1 !== i ) { - is_present_in_another_sidebar = true; - } - } ); - - // If the widget is present in another sidebar, abort! - if ( is_present_in_another_sidebar ) { - return; - } - - removed_control = self.getWidgetFormControlForWidget( removed_widget_id ); - - // Detect if widget control was dragged to another sidebar - was_dragged_to_another_sidebar = ( - removed_control && - $.contains( document, removed_control.container[0] ) && - ! $.contains( control.section_content[0], removed_control.container[0] ) - ); - - // Delete any widget form controls for removed widgets - if ( removed_control && ! was_dragged_to_another_sidebar ) { - wp.customize.control.remove( removed_control.id ); - removed_control.container.remove(); - } - - // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved - // This prevents the inactive widgets sidebar from overflowing with throwaway widgets - if ( self.saved_widget_ids[removed_widget_id] ) { - inactive_widgets = wp.customize.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); - inactive_widgets.push( removed_widget_id ); - wp.customize.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactive_widgets ).unique() ); - } - - // Make old single widget available for adding again - removed_id_base = parse_widget_id( removed_widget_id ).id_base; - widget = self.available_widgets.findWhere( { id_base: removed_id_base } ); - if ( widget && ! widget.get( 'is_multi' ) ) { - widget.set( 'is_disabled', false ); - } - } ); - - } ); - } ); - - // Update the model with whether or not the sidebar is rendered - self.previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) { - var is_rendered = !! rendered_sidebars[control.params.sidebar_id]; - registered_sidebar.set( 'is_rendered', is_rendered ); - } ); - - // Show the sidebar section when it becomes visible - registered_sidebar.on( 'change:is_rendered', function ( ) { - var section_selector = '#accordion-section-sidebar-widgets-' + this.get( 'id' ), section; - section = $( section_selector ); - if ( this.get( 'is_rendered' ) ) { - section.stop().slideDown( function () { - $( this ).css( 'height', 'auto' ); // so that the .accordion-section-content won't overflow - } ); - } else { - // Make sure that hidden sections get closed first - if ( section.hasClass( 'open' ) ) { - // it would be nice if accordionSwitch() in accordion.js was public - section.find( '.accordion-section-title' ).trigger( 'click' ); - } - section.stop().slideUp(); - } - } ); - }, - - /** - * Allow widgets in sidebar to be re-ordered, and for the order to be previewed - */ - _setupSortable: function () { - var control = this; - control.is_reordering = false; - - /** - * Update widget order setting when controls are re-ordered - */ - control.section_content.sortable( { - items: '> .customize-control-widget_form', - handle: '.widget-top', - axis: 'y', - connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)', - update: function () { - var widget_container_ids = control.section_content.sortable( 'toArray' ), widget_ids; - widget_ids = $.map( widget_container_ids, function ( widget_container_id ) { - return $( '#' + widget_container_id ).find( ':input[name=widget-id]' ).val(); - } ); - control.setting( widget_ids ); - } - } ); - - /** - * Expand other customizer sidebar section when dragging a control widget over it, - * allowing the control to be dropped into another section - */ - control.control_section.find( '.accordion-section-title' ).droppable( { - accept: '.customize-control-widget_form', - over: function () { - if ( ! control.control_section.hasClass( 'open' ) ) { - control.control_section.addClass( 'open' ); - control.section_content.toggle( false ).slideToggle( 150, function () { - control.section_content.sortable( 'refreshPositions' ); - } ); - } - } - } ); - - /** - * Keyboard-accessible reordering - */ - control.container.find( '.reorder-toggle' ).on( 'click keydown', function( event ) { - if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar - return; - } - - control.toggleReordering( ! control.is_reordering ); - } ); - }, - - /** - * Set up UI for adding a new widget - */ - _setupAddition: function () { - var control = this; - - control.container.find( '.add-new-widget' ).on( 'click keydown', function( event ) { - if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar - return; - } - - if ( control.section_content.hasClass( 'reordering' ) ) { - return; - } - - // @todo Use an control.is_adding state - if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) { - self.availableWidgetsPanel.open( control ); - } else { - self.availableWidgetsPanel.close(); - } - } ); - }, - - /** - * Add classes to the widget_form controls to assist with styling - */ - _applyCardinalOrderClassNames: function () { - var control = this; - control.section_content.find( '.customize-control-widget_form' ) - .removeClass( 'first-widget' ) - .removeClass( 'last-widget' ) - .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 ); - - control.section_content.find( '.customize-control-widget_form:first' ) - .addClass( 'first-widget' ) - .find( '.move-widget-up' ).prop( 'tabIndex', -1 ); - control.section_content.find( '.customize-control-widget_form:last' ) - .addClass( 'last-widget' ) - .find( '.move-widget-down' ).prop( 'tabIndex', -1 ); - }, - - - /*********************************************************************** - * Begin public API methods - **********************************************************************/ - - /** - * Enable/disable the reordering UI - * - * @param {Boolean} toggle to enable/disable reordering - */ - toggleReordering: function ( toggle ) { - var control = this; - toggle = Boolean( toggle ); - if ( toggle === control.section_content.hasClass( 'reordering' ) ) { - return; - } - - control.is_reordering = toggle; - control.section_content.toggleClass( 'reordering', toggle ); - - if ( toggle ) { - _( control.getWidgetFormControls() ).each( function ( form_control ) { - form_control.collapseForm(); - } ); - } - }, - - /** - * @return {wp.customize.controlConstructor.widget_form[]} - */ - getWidgetFormControls: function () { - var control = this, form_controls; - - form_controls = _( control.setting() ).map( function ( widget_id ) { - var setting_id = widget_id_to_setting_id( widget_id ), - form_control = customize.control( setting_id ); - - if ( ! form_control ) { - throw new Error( 'Unable to find widget_form control for ' + widget_id ); - } - return form_control; - } ); - return form_controls; - }, - - /** - * @param {string} widget_id or an id_base for adding a previously non-existing widget - * @returns {object} widget_form control instance - */ - addWidget: function ( widget_id ) { - var control = this, - control_html, - widget_el, - customize_control_type = 'widget_form', - customize_control, - parsed_widget_id = parse_widget_id( widget_id ), - widget_number = parsed_widget_id.number, - widget_id_base = parsed_widget_id.id_base, - widget = self.available_widgets.findWhere( {id_base: widget_id_base} ), - setting_id, - is_existing_widget, - Constructor, - widget_form_control, - sidebar_widgets, - setting_args; - - if ( ! widget ) { - throw new Error( 'Widget unexpectedly not found.' ); - } - if ( widget_number && ! widget.get( 'is_multi' ) ) { - throw new Error( 'Did not expect a widget number to be supplied for a non-multi widget' ); - } - - // Set up new multi widget - if ( widget.get( 'is_multi' ) && ! widget_number ) { - widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 ); - widget_number = widget.get( 'multi_number' ); - } - - control_html = $( '#widget-tpl-' + widget.get( 'id' ) ).html(); - if ( widget.get( 'is_multi' ) ) { - control_html = control_html.replace( /<[^<>]+>/g, function ( m ) { - return m.replace( /__i__|%i%/g, widget_number ); - } ); - } else { - widget.set( 'is_disabled', true ); // Prevent single widget from being added again now - } - widget_el = $( control_html ); - - customize_control = $( '
  • ' ); - customize_control.addClass( 'customize-control' ); - customize_control.addClass( 'customize-control-' + customize_control_type ); - customize_control.append( widget_el ); - customize_control.find( '> .widget-icon' ).remove(); - if ( widget.get( 'is_multi' ) ) { - customize_control.find( 'input[name="widget_number"]' ).val( widget_number ); - customize_control.find( 'input[name="multi_number"]' ).val( widget_number ); - } - widget_id = customize_control.find( '[name="widget-id"]' ).val(); - customize_control.hide(); // to be slid-down below - - setting_id = 'widget_' + widget.get( 'id_base' ); - if ( widget.get( 'is_multi' ) ) { - setting_id += '[' + widget_number + ']'; - } - customize_control.attr( 'id', 'customize-control-' + setting_id.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); - - control.container.after( customize_control ); - - // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) - is_existing_widget = wp.customize.has( setting_id ); - if ( ! is_existing_widget ) { - setting_args = { - transport: 'refresh', - previewer: control.setting.previewer - }; - wp.customize.create( setting_id, setting_id, {}, setting_args ); - } - - Constructor = wp.customize.controlConstructor[customize_control_type]; - widget_form_control = new Constructor( setting_id, { - params: { - settings: { - 'default': setting_id - }, - sidebar_id: control.params.sidebar_id, - widget_id: widget_id, - widget_id_base: widget.get( 'id_base' ), - type: customize_control_type, - is_new: ! is_existing_widget, - width: widget.get( 'width' ), - height: widget.get( 'height' ), - is_wide: widget.get( 'is_wide' ) - }, - previewer: control.setting.previewer - } ); - wp.customize.control.add( setting_id, widget_form_control ); - - // Make sure widget is removed from the other sidebars - wp.customize.each( function ( other_setting ) { - if ( other_setting.id === control.setting.id ) { - return; - } - if ( 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) ) { - return; - } - var other_sidebar_widgets = other_setting().slice(), i; - i = _.indexOf( other_sidebar_widgets, widget_id ); - if ( -1 !== i ) { - other_sidebar_widgets.splice( i ); - other_setting( other_sidebar_widgets ); - } - } ); - - // Add widget to this sidebar - sidebar_widgets = control.setting().slice(); - if ( -1 === _.indexOf( sidebar_widgets, widget_id ) ) { - sidebar_widgets.push( widget_id ); - control.setting( sidebar_widgets ); - } - - customize_control.slideDown( function () { - if ( is_existing_widget ) { - widget_form_control.expandForm(); - widget_form_control.updateWidget( { - instance: widget_form_control.setting(), - complete: function ( error ) { - if ( error ) { - throw error; - } - widget_form_control.focus(); - } - } ); - } else { - widget_form_control.focus(); - } - } ); - - $( document ).trigger( 'widget-added', [ widget_el ] ); - - return widget_form_control; - } - - } ); - /** * Widget Form control - * Note that 'widget_form' must match the Widget_Form_WP_Customize_Control::$type + * Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type */ - customize.controlConstructor.widget_form = customize.Control.extend( { - + api.Widgets.WidgetControl = api.Control.extend({ /** * Set up the control */ @@ -638,15 +158,17 @@ var WidgetCustomizer = ( function ($) { /** * Handle changes to the setting */ - _setupModel: function () { + _setupModel: function() { var control = this, remember_saved_widget_id; + api.Widgets.savedWidgetIds = api.Widgets.savedWidgetIds || []; + // Remember saved widgets so we know which to trash (move to inactive widgets sidebar) - remember_saved_widget_id = function () { - self.saved_widget_ids[control.params.widget_id] = true; + remember_saved_widget_id = function() { + api.Widgets.savedWidgetIds[control.params.widget_id] = true; }; - wp.customize.bind( 'ready', remember_saved_widget_id ); - wp.customize.bind( 'saved', remember_saved_widget_id ); + api.bind( 'ready', remember_saved_widget_id ); + api.bind( 'saved', remember_saved_widget_id ); control._update_count = 0; control.is_widget_updating = false; @@ -663,7 +185,7 @@ var WidgetCustomizer = ( function ($) { /** * Add special behaviors for wide widget controls */ - _setupWideWidget: function () { + _setupWideWidget: function() { var control = this, widget_inside, widget_form, @@ -694,7 +216,7 @@ var WidgetCustomizer = ( function ($) { * exceed the window height so that the contents of the widget control * will become scrollable (overflow:auto). */ - position_widget = function () { + position_widget = function() { var offset_top = control.container.offset().top, window_height = $( window ).height(), form_height = widget_form.outerHeight(), @@ -711,22 +233,22 @@ var WidgetCustomizer = ( function ($) { }; theme_controls_container = $( '#customize-theme-controls' ); - control.container.on( 'expand', function () { + control.container.on( 'expand', function() { position_widget(); customize_sidebar.on( 'scroll', position_widget ); $( window ).on( 'resize', position_widget ); theme_controls_container.on( 'expanded collapsed', position_widget ); } ); - control.container.on( 'collapsed', function () { + control.container.on( 'collapsed', function() { customize_sidebar.off( 'scroll', position_widget ); $( window ).off( 'resize', position_widget ); theme_controls_container.off( 'expanded collapsed', position_widget ); } ); // Reposition whenever a sidebar's widgets are changed - wp.customize.each( function ( setting ) { + api.each( function ( setting ) { if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) { - setting.bind( function () { + setting.bind( function() { if ( control.container.hasClass( 'expanded' ) ) { position_widget(); } @@ -763,10 +285,10 @@ var WidgetCustomizer = ( function ($) { /** * Update the title of the form if a title field is entered */ - _setupWidgetTitle: function () { + _setupWidgetTitle: function() { var control = this, update_title; - update_title = function () { + update_title = function() { var title = control.setting().title, in_widget_title = control.container.find( '.in-widget-title' ); @@ -783,7 +305,7 @@ var WidgetCustomizer = ( function ($) { /** * Set up the widget-reorder-nav */ - _setupReorderUI: function () { + _setupReorderUI: function() { var control = this, select_sidebar_item, move_widget_area, @@ -805,10 +327,10 @@ var WidgetCustomizer = ( function ($) { /** * Add the widget reordering elements to the widget control */ - control.container.find( '.widget-title-action' ).after( $( self.tpl.widget_reorder_nav ) ); + control.container.find( '.widget-title-action' ).after( $( api.Widgets.data.tpl.widgetReorderNav ) ); move_widget_area = $( - _.template( self.tpl.move_widget_area, { - sidebars: _( self.registered_sidebars.toArray() ).pluck( 'attributes' ) + _.template( api.Widgets.data.tpl.moveWidgetArea, { + sidebars: _( api.Widgets.registeredSidebars.toArray() ).pluck( 'attributes' ) } ) ); control.container.find( '.widget-top' ).after( move_widget_area ); @@ -816,18 +338,18 @@ var WidgetCustomizer = ( function ($) { /** * Update available sidebars when their rendered state changes */ - update_available_sidebars = function () { + update_available_sidebars = function() { var sidebar_items = move_widget_area.find( 'li' ), self_sidebar_item; self_sidebar_item = sidebar_items.filter( function(){ return $( this ).data( 'id' ) === control.params.sidebar_id; } ); - sidebar_items.each( function () { + sidebar_items.each( function() { var li = $( this ), sidebar_id, sidebar_model; sidebar_id = li.data( 'id' ); - sidebar_model = self.registered_sidebars.get( sidebar_id ); + sidebar_model = api.Widgets.registeredSidebars.get( sidebar_id ); li.toggle( sidebar_model.get( 'is_rendered' ) ); if ( li.hasClass( 'selected' ) && ! sidebar_model.get( 'is_rendered' ) ) { select_sidebar_item( self_sidebar_item ); @@ -835,7 +357,7 @@ var WidgetCustomizer = ( function ($) { } ); }; update_available_sidebars(); - self.registered_sidebars.on( 'change:is_rendered', update_available_sidebars ); + api.Widgets.registeredSidebars.on( 'change:is_rendered', update_available_sidebars ); /** * Handle clicks for up/down/move on the reorder nav @@ -882,7 +404,7 @@ var WidgetCustomizer = ( function ($) { /** * Move widget to another sidebar */ - control.container.find( '.move-widget-btn' ).click( function () { + control.container.find( '.move-widget-btn' ).click( function() { control.getSidebarWidgetsControl().toggleReordering( false ); var old_sidebar_id = control.params.sidebar_id, @@ -893,8 +415,8 @@ var WidgetCustomizer = ( function ($) { new_sidebar_widget_ids, i; - old_sidebar_widgets_setting = customize( 'sidebars_widgets[' + old_sidebar_id + ']' ); - new_sidebar_widgets_setting = customize( 'sidebars_widgets[' + new_sidebar_id + ']' ); + old_sidebar_widgets_setting = api( 'sidebars_widgets[' + old_sidebar_id + ']' ); + new_sidebar_widgets_setting = api( 'sidebars_widgets[' + new_sidebar_id + ']' ); old_sidebar_widget_ids = Array.prototype.slice.call( old_sidebar_widgets_setting() ); new_sidebar_widget_ids = Array.prototype.slice.call( new_sidebar_widgets_setting() ); @@ -916,17 +438,17 @@ var WidgetCustomizer = ( function ($) { var control = this; // Highlight whenever hovering or clicking over the form - control.container.on( 'mouseenter click', function () { + control.container.on( 'mouseenter click', function() { control.setting.previewer.send( 'highlight-widget', control.params.widget_id ); } ); // Highlight when the setting is updated - control.setting.bind( function () { + control.setting.bind( function() { control.setting.previewer.send( 'highlight-widget', control.params.widget_id ); } ); // Highlight when the widget form is expanded - control.container.on( 'expand', function () { + control.container.on( 'expand', function() { control.scrollPreviewWidgetIntoView(); } ); }, @@ -934,7 +456,7 @@ var WidgetCustomizer = ( function ($) { /** * Set up event handlers for widget updating */ - _setupUpdateUI: function () { + _setupUpdateUI: function() { var control = this, widget_root, widget_content, @@ -947,15 +469,15 @@ var WidgetCustomizer = ( function ($) { // Configure update button save_btn = control.container.find( '.widget-control-save' ); - save_btn.val( self.i18n.save_btn_label ); - save_btn.attr( 'title', self.i18n.save_btn_tooltip ); + save_btn.val( l10n.saveBtnLabel ); + save_btn.attr( 'title', l10n.saveBtnTooltip ); save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' ); save_btn.on( 'click', function ( e ) { e.preventDefault(); control.updateWidget( { disable_form: true } ); } ); - update_widget_debounced = _.debounce( function () { + update_widget_debounced = _.debounce( function() { // @todo For compatibility with other plugins, should we trigger a click event? What about form submit event? control.updateWidget(); }, 250 ); @@ -980,22 +502,22 @@ var WidgetCustomizer = ( function ($) { } ); // Remove loading indicators when the setting is saved and the preview updates - control.setting.previewer.channel.bind( 'synced', function () { + control.setting.previewer.channel.bind( 'synced', function() { control.container.removeClass( 'previewer-loading' ); } ); - self.previewer.bind( 'widget-updated', function ( updated_widget_id ) { + api.Widgets.Previewer.bind( 'widget-updated', function ( updated_widget_id ) { if ( updated_widget_id === control.params.widget_id ) { control.container.removeClass( 'previewer-loading' ); } } ); // Update widget control to indicate whether it is currently rendered (cf. Widget Visibility) - self.previewer.bind( 'rendered-widgets', function ( rendered_widgets ) { + api.Widgets.Previewer.bind( 'rendered-widgets', function ( rendered_widgets ) { var is_rendered = !! rendered_widgets[control.params.widget_id]; control.container.toggleClass( 'widget-rendered', is_rendered ); } ); - form_update_event_handler = builtin_form_sync_handlers[ control.params.widget_id_base ]; + form_update_event_handler = api.Widgets.formSyncHandlers[ control.params.widget_id_base ]; if ( form_update_event_handler ) { $( document ).on( 'widget-synced', function ( e, widget_el ) { if ( widget_root.is( widget_el ) ) { @@ -1008,7 +530,7 @@ var WidgetCustomizer = ( function ($) { /** * Set up event handlers for widget removal */ - _setupRemoveUI: function () { + _setupRemoveUI: function() { var control = this, remove_btn, replace_delete_with_remove; @@ -1030,7 +552,7 @@ var WidgetCustomizer = ( function ($) { } control.container.slideUp( function() { - var sidebars_widgets_control = self.getSidebarWidgetControlContainingWidget( control.params.widget_id ), + var sidebars_widgets_control = api.Widgets.getSidebarWidgetControlContainingWidget( control.params.widget_id ), sidebar_widget_ids, i; @@ -1048,12 +570,12 @@ var WidgetCustomizer = ( function ($) { } ); } ); - replace_delete_with_remove = function () { - remove_btn.text( self.i18n.remove_btn_label ); // wp_widget_control() outputs the link as "Delete" - remove_btn.attr( 'title', self.i18n.remove_btn_tooltip ); + replace_delete_with_remove = function() { + remove_btn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete" + remove_btn.attr( 'title', l10n.removeBtnTooltip ); }; if ( control.params.is_new ) { - wp.customize.bind( 'saved', replace_delete_with_remove ); + api.bind( 'saved', replace_delete_with_remove ); } else { replace_delete_with_remove(); } @@ -1119,11 +641,11 @@ var WidgetCustomizer = ( function ($) { /** * @return {wp.customize.controlConstructor.sidebar_widgets[]} */ - getSidebarWidgetsControl: function () { + getSidebarWidgetsControl: function() { var control = this, setting_id, sidebar_widgets_control; setting_id = 'sidebars_widgets[' + control.params.sidebar_id + ']'; - sidebar_widgets_control = customize.control( setting_id ); + sidebar_widgets_control = api.control( setting_id ); if ( ! sidebar_widgets_control ) { throw new Error( 'Unable to locate sidebar_widgets control for ' + control.params.sidebar_id ); } @@ -1173,7 +695,7 @@ var WidgetCustomizer = ( function ($) { control.container.addClass( 'widget-form-loading' ); control.container.addClass( 'previewer-loading' ); - processing = wp.customize.state( 'processing' ); + processing = api.state( 'processing' ); processing( processing() + 1 ); if ( ! control.live_update_mode ) { @@ -1183,7 +705,7 @@ var WidgetCustomizer = ( function ($) { params = {}; params.action = 'update-widget'; params.wp_customize = 'on'; - params.nonce = self.nonce; + params.nonce = api.Widgets.data.nonce; data = $.param( params ); inputs = control._getInputs( widget_content ); @@ -1191,7 +713,7 @@ var WidgetCustomizer = ( function ($) { // Store the value we're submitting in data so that when the response comes back, // we know if it got sanitized; if there is no difference in the sanitized value, // then we do not need to touch the UI and mess up the user's ongoing editing. - inputs.each( function () { + inputs.each( function() { var input = $( this ), property = control._getInputStatePropertyName( this ); input.data( 'state' + update_number, input.prop( property ) ); @@ -1213,17 +735,17 @@ var WidgetCustomizer = ( function ($) { // Check if the user is logged out. if ( '0' === r ) { - self.previewer.preview.iframe.hide(); - self.previewer.login().done( function() { + api.Widgets.Previewer.preview.iframe.hide(); + api.Widgets.Previewer.login().done( function() { control.updateWidget( args ); - self.previewer.preview.iframe.show(); + api.Widgets.Previewer.preview.iframe.show(); } ); return; } // Check for cheaters. if ( '-1' === r ) { - self.previewer.cheatin(); + api.Widgets.Previewer.cheatin(); return; } @@ -1294,7 +816,7 @@ var WidgetCustomizer = ( function ($) { complete_callback.call( control, null, { no_change: ! is_changed, ajax_finished: true } ); } } else { - message = self.i18n.error; + message = l10n.error; if ( r.data && r.data.message ) { message = r.data.message; } @@ -1310,9 +832,9 @@ var WidgetCustomizer = ( function ($) { complete_callback.call( control, textStatus ); } } ); - jqxhr.always( function () { + jqxhr.always( function() { control.container.removeClass( 'widget-form-loading' ); - inputs.each( function () { + inputs.each( function() { $( this ).removeData( 'state' + update_number ); } ); @@ -1324,7 +846,7 @@ var WidgetCustomizer = ( function ($) { * Expand the accordion section containing a control * @todo it would be nice if accordion had a proper API instead of having to trigger UI events on its elements */ - expandControlSection: function () { + expandControlSection: function() { var section = this.container.closest( '.accordion-section' ); if ( ! section.hasClass( 'open' ) ) { section.find( '.accordion-section-title:first' ).trigger( 'click' ); @@ -1334,14 +856,14 @@ var WidgetCustomizer = ( function ($) { /** * Expand the widget form control */ - expandForm: function () { + expandForm: function() { this.toggleForm( true ); }, /** * Collapse the widget form control */ - collapseForm: function () { + collapseForm: function() { this.toggleForm( false ); }, @@ -1366,13 +888,13 @@ var WidgetCustomizer = ( function ($) { if ( do_expand ) { // Close all other widget controls before expanding this one - wp.customize.control.each( function ( other_control ) { + api.control.each( function ( other_control ) { if ( control.params.type === other_control.params.type && control !== other_control ) { other_control.collapseForm(); } } ); - complete = function () { + complete = function() { control.container.removeClass( 'expanding' ); control.container.addClass( 'expanded' ); control.container.trigger( 'expanded' ); @@ -1387,7 +909,7 @@ var WidgetCustomizer = ( function ($) { } else { control.container.trigger( 'collapse' ); control.container.addClass( 'collapsing' ); - complete = function () { + complete = function() { control.container.removeClass( 'collapsing' ); control.container.removeClass( 'expanded' ); control.container.trigger( 'collapsed' ); @@ -1407,7 +929,7 @@ var WidgetCustomizer = ( function ($) { * Expand the containing sidebar section, expand the form, and focus on * the first input in the control */ - focus: function () { + focus: function() { var control = this; control.expandControlSection(); control.expandForm(); @@ -1420,7 +942,7 @@ var WidgetCustomizer = ( function ($) { * @throws Error * @returns {Number} */ - getWidgetSidebarPosition: function () { + getWidgetSidebarPosition: function() { var control = this, sidebar_widget_ids, position; @@ -1436,14 +958,14 @@ var WidgetCustomizer = ( function ($) { /** * Move widget up one in the sidebar */ - moveUp: function () { + moveUp: function() { this._moveWidgetByOne( -1 ); }, /** * Move widget up one in the sidebar */ - moveDown: function () { + moveDown: function() { this._moveWidgetByOne( 1 ); }, @@ -1484,7 +1006,7 @@ var WidgetCustomizer = ( function ($) { if ( toggle ) { // reset the selected sidebar move_widget_area.find( '.selected' ).removeClass( 'selected' ); - move_widget_area.find( 'li' ).filter( function () { + move_widget_area.find( 'li' ).filter( function() { return $( this ).data( 'id' ) === control.params.sidebar_id; } ).addClass( 'selected' ); control.container.find( '.move-widget-btn' ).prop( 'disabled', true ); @@ -1495,7 +1017,7 @@ var WidgetCustomizer = ( function ($) { /** * Inside of the customizer preview, scroll the widget into view */ - scrollPreviewWidgetIntoView: function () { + scrollPreviewWidgetIntoView: function() { // @todo scrollIntoView() provides a robust but very poor experience. Animation is needed. See https://github.com/x-team/wp-widget-customizer/issues/16 }, @@ -1513,20 +1035,475 @@ var WidgetCustomizer = ( function ($) { $( '.widget-customizer-highlighted' ).removeClass( 'widget-customizer-highlighted' ); target_element.addClass( 'widget-customizer-highlighted' ); - setTimeout( function () { + setTimeout( function() { target_element.removeClass( 'widget-customizer-highlighted' ); }, 500 ); } } ); + /** + * Sidebar Widgets control + * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type + */ + api.Widgets.SidebarControl = api.Control.extend({ + /** + * Set up the control + */ + ready: function() { + var control = this; + control.control_section = control.container.closest( '.control-section' ); + control.section_content = control.container.closest( '.accordion-section-content' ); + control._setupModel(); + control._setupSortable(); + control._setupAddition(); + control._applyCardinalOrderClassNames(); + }, + + /** + * Update ordering of widget control forms when the setting is updated + */ + _setupModel: function() { + var control = this, + registered_sidebar = api.Widgets.registeredSidebars.get( control.params.sidebar_id ); + + control.setting.bind( function( new_widget_ids, old_widget_ids ) { + var widget_form_controls, + sidebar_widgets_add_control, + final_control_containers, + removed_widget_ids = _( old_widget_ids ).difference( new_widget_ids ); + + // Filter out any persistent widget_ids for widgets which have been deactivated + new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) { + var parsed_widget_id = parse_widget_id( new_widget_id ); + return !! api.Widgets.availableWidgets.findWhere( { id_base: parsed_widget_id.id_base } ); + } ); + + widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) { + var widget_form_control = api.Widgets.getWidgetFormControlForWidget( widget_id ); + if ( ! widget_form_control ) { + widget_form_control = control.addWidget( widget_id ); + } + return widget_form_control; + } ); + + // Sort widget controls to their new positions + widget_form_controls.sort( function ( a, b ) { + var a_index = _.indexOf( new_widget_ids, a.params.widget_id ), + b_index = _.indexOf( new_widget_ids, b.params.widget_id ); + if ( a_index === b_index ) { + return 0; + } + return a_index < b_index ? -1 : 1; + } ); + + sidebar_widgets_add_control = control.section_content.find( '.customize-control-sidebar_widgets' ); + + // Append the controls to put them in the right order + final_control_containers = _( widget_form_controls ).map( function( widget_form_controls ) { + return widget_form_controls.container[0]; + } ); + + // Re-sort widget form controls (including widgets form other sidebars newly moved here) + sidebar_widgets_add_control.before( final_control_containers ); + control._applyCardinalOrderClassNames(); + + // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated + _( widget_form_controls ).each( function ( widget_form_control ) { + widget_form_control.params.sidebar_id = control.params.sidebar_id; + } ); + + // Cleanup after widget removal + _( removed_widget_ids ).each( function ( removed_widget_id ) { + + // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update + setTimeout( function() { + var is_present_in_another_sidebar = false, + removed_control, + was_dragged_to_another_sidebar, + inactive_widgets, + removed_id_base, + widget; + + // Check if the widget is in another sidebar + api.each( function ( other_setting ) { + if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) { + return; + } + var other_sidebar_widgets = other_setting(), i; + + i = _.indexOf( other_sidebar_widgets, removed_widget_id ); + if ( -1 !== i ) { + is_present_in_another_sidebar = true; + } + } ); + + // If the widget is present in another sidebar, abort! + if ( is_present_in_another_sidebar ) { + return; + } + + removed_control = api.Widgets.getWidgetFormControlForWidget( removed_widget_id ); + + // Detect if widget control was dragged to another sidebar + was_dragged_to_another_sidebar = ( + removed_control && + $.contains( document, removed_control.container[0] ) && + ! $.contains( control.section_content[0], removed_control.container[0] ) + ); + + // Delete any widget form controls for removed widgets + if ( removed_control && ! was_dragged_to_another_sidebar ) { + api.control.remove( removed_control.id ); + removed_control.container.remove(); + } + + // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved + // This prevents the inactive widgets sidebar from overflowing with throwaway widgets + if ( api.Widgets.savedWidgetIds[removed_widget_id] ) { + inactive_widgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); + inactive_widgets.push( removed_widget_id ); + api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactive_widgets ).unique() ); + } + + // Make old single widget available for adding again + removed_id_base = parse_widget_id( removed_widget_id ).id_base; + widget = api.Widgets.availableWidgets.findWhere( { id_base: removed_id_base } ); + if ( widget && ! widget.get( 'is_multi' ) ) { + widget.set( 'is_disabled', false ); + } + } ); + + } ); + } ); + + // Update the model with whether or not the sidebar is rendered + api.Widgets.Previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) { + var is_rendered = !! rendered_sidebars[control.params.sidebar_id]; + registered_sidebar.set( 'is_rendered', is_rendered ); + } ); + + // Show the sidebar section when it becomes visible + registered_sidebar.on( 'change:is_rendered', function ( ) { + var section_selector = '#accordion-section-sidebar-widgets-' + this.get( 'id' ), section; + section = $( section_selector ); + if ( this.get( 'is_rendered' ) ) { + section.stop().slideDown( function() { + $( this ).css( 'height', 'auto' ); // so that the .accordion-section-content won't overflow + } ); + } else { + // Make sure that hidden sections get closed first + if ( section.hasClass( 'open' ) ) { + // it would be nice if accordionSwitch() in accordion.js was public + section.find( '.accordion-section-title' ).trigger( 'click' ); + } + section.stop().slideUp(); + } + } ); + }, + + /** + * Allow widgets in sidebar to be re-ordered, and for the order to be previewed + */ + _setupSortable: function() { + var control = this; + control.is_reordering = false; + + /** + * Update widget order setting when controls are re-ordered + */ + control.section_content.sortable( { + items: '> .customize-control-widget_form', + handle: '.widget-top', + axis: 'y', + connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)', + update: function() { + var widget_container_ids = control.section_content.sortable( 'toArray' ), widget_ids; + widget_ids = $.map( widget_container_ids, function ( widget_container_id ) { + return $( '#' + widget_container_id ).find( ':input[name=widget-id]' ).val(); + } ); + control.setting( widget_ids ); + } + } ); + + /** + * Expand other customizer sidebar section when dragging a control widget over it, + * allowing the control to be dropped into another section + */ + control.control_section.find( '.accordion-section-title' ).droppable( { + accept: '.customize-control-widget_form', + over: function() { + if ( ! control.control_section.hasClass( 'open' ) ) { + control.control_section.addClass( 'open' ); + control.section_content.toggle( false ).slideToggle( 150, function() { + control.section_content.sortable( 'refreshPositions' ); + } ); + } + } + } ); + + /** + * Keyboard-accessible reordering + */ + control.container.find( '.reorder-toggle' ).on( 'click keydown', function( event ) { + if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar + return; + } + + control.toggleReordering( ! control.is_reordering ); + } ); + }, + + /** + * Set up UI for adding a new widget + */ + _setupAddition: function() { + var control = this; + + control.container.find( '.add-new-widget' ).on( 'click keydown', function( event ) { + if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar + return; + } + + if ( control.section_content.hasClass( 'reordering' ) ) { + return; + } + + // @todo Use an control.is_adding state + if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) { + api.Widgets.availableWidgetsPanel.open( control ); + } else { + api.Widgets.availableWidgetsPanel.close(); + } + } ); + }, + + /** + * Add classes to the widget_form controls to assist with styling + */ + _applyCardinalOrderClassNames: function() { + var control = this; + control.section_content.find( '.customize-control-widget_form' ) + .removeClass( 'first-widget' ) + .removeClass( 'last-widget' ) + .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 ); + + control.section_content.find( '.customize-control-widget_form:first' ) + .addClass( 'first-widget' ) + .find( '.move-widget-up' ).prop( 'tabIndex', -1 ); + control.section_content.find( '.customize-control-widget_form:last' ) + .addClass( 'last-widget' ) + .find( '.move-widget-down' ).prop( 'tabIndex', -1 ); + }, + + + /*********************************************************************** + * Begin public API methods + **********************************************************************/ + + /** + * Enable/disable the reordering UI + * + * @param {Boolean} toggle to enable/disable reordering + */ + toggleReordering: function ( toggle ) { + var control = this; + toggle = Boolean( toggle ); + if ( toggle === control.section_content.hasClass( 'reordering' ) ) { + return; + } + + control.is_reordering = toggle; + control.section_content.toggleClass( 'reordering', toggle ); + + if ( toggle ) { + _( control.getWidgetFormControls() ).each( function ( form_control ) { + form_control.collapseForm(); + } ); + } + }, + + /** + * @return {wp.customize.controlConstructor.widget_form[]} + */ + getWidgetFormControls: function() { + var control = this, form_controls; + + form_controls = _( control.setting() ).map( function ( widget_id ) { + var setting_id = widget_id_to_setting_id( widget_id ), + form_control = api.control( setting_id ); + + if ( ! form_control ) { + throw new Error( 'Unable to find widget_form control for ' + widget_id ); + } + return form_control; + } ); + return form_controls; + }, + + /** + * @param {string} widget_id or an id_base for adding a previously non-existing widget + * @returns {object} widget_form control instance + */ + addWidget: function ( widget_id ) { + var control = this, + control_html, + widget_el, + customize_control_type = 'widget_form', + customize_control, + parsed_widget_id = parse_widget_id( widget_id ), + widget_number = parsed_widget_id.number, + widget_id_base = parsed_widget_id.id_base, + widget = api.Widgets.availableWidgets.findWhere( {id_base: widget_id_base} ), + setting_id, + is_existing_widget, + Constructor, + widget_form_control, + sidebar_widgets, + setting_args; + + if ( ! widget ) { + throw new Error( 'Widget unexpectedly not found.' ); + } + if ( widget_number && ! widget.get( 'is_multi' ) ) { + throw new Error( 'Did not expect a widget number to be supplied for a non-multi widget' ); + } + + // Set up new multi widget + if ( widget.get( 'is_multi' ) && ! widget_number ) { + widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 ); + widget_number = widget.get( 'multi_number' ); + } + + control_html = $( '#widget-tpl-' + widget.get( 'id' ) ).html(); + if ( widget.get( 'is_multi' ) ) { + control_html = control_html.replace( /<[^<>]+>/g, function ( m ) { + return m.replace( /__i__|%i%/g, widget_number ); + } ); + } else { + widget.set( 'is_disabled', true ); // Prevent single widget from being added again now + } + widget_el = $( control_html ); + + customize_control = $( '
  • ' ); + customize_control.addClass( 'customize-control' ); + customize_control.addClass( 'customize-control-' + customize_control_type ); + customize_control.append( widget_el ); + customize_control.find( '> .widget-icon' ).remove(); + if ( widget.get( 'is_multi' ) ) { + customize_control.find( 'input[name="widget_number"]' ).val( widget_number ); + customize_control.find( 'input[name="multi_number"]' ).val( widget_number ); + } + widget_id = customize_control.find( '[name="widget-id"]' ).val(); + customize_control.hide(); // to be slid-down below + + setting_id = 'widget_' + widget.get( 'id_base' ); + if ( widget.get( 'is_multi' ) ) { + setting_id += '[' + widget_number + ']'; + } + customize_control.attr( 'id', 'customize-control-' + setting_id.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); + + control.container.after( customize_control ); + + // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) + is_existing_widget = api.has( setting_id ); + if ( ! is_existing_widget ) { + setting_args = { + transport: 'refresh', + previewer: control.setting.previewer + }; + api.create( setting_id, setting_id, {}, setting_args ); + } + + Constructor = api.controlConstructor[customize_control_type]; + widget_form_control = new Constructor( setting_id, { + params: { + settings: { + 'default': setting_id + }, + sidebar_id: control.params.sidebar_id, + widget_id: widget_id, + widget_id_base: widget.get( 'id_base' ), + type: customize_control_type, + is_new: ! is_existing_widget, + width: widget.get( 'width' ), + height: widget.get( 'height' ), + is_wide: widget.get( 'is_wide' ) + }, + previewer: control.setting.previewer + } ); + api.control.add( setting_id, widget_form_control ); + + // Make sure widget is removed from the other sidebars + api.each( function ( other_setting ) { + if ( other_setting.id === control.setting.id ) { + return; + } + if ( 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) ) { + return; + } + var other_sidebar_widgets = other_setting().slice(), i; + i = _.indexOf( other_sidebar_widgets, widget_id ); + if ( -1 !== i ) { + other_sidebar_widgets.splice( i ); + other_setting( other_sidebar_widgets ); + } + } ); + + // Add widget to this sidebar + sidebar_widgets = control.setting().slice(); + if ( -1 === _.indexOf( sidebar_widgets, widget_id ) ) { + sidebar_widgets.push( widget_id ); + control.setting( sidebar_widgets ); + } + + customize_control.slideDown( function() { + if ( is_existing_widget ) { + widget_form_control.expandForm(); + widget_form_control.updateWidget( { + instance: widget_form_control.setting(), + complete: function ( error ) { + if ( error ) { + throw error; + } + widget_form_control.focus(); + } + } ); + } else { + widget_form_control.focus(); + } + } ); + + $( document ).trigger( 'widget-added', [ widget_el ] ); + + return widget_form_control; + } + + } ); + + $.extend( api.controlConstructor, { + widget_form: api.Widgets.WidgetControl, + sidebar_widgets: api.Widgets.SidebarControl + }); + + api.bind( 'ready', function() { + // Set up the widgets panel + api.Widgets.availableWidgetsPanel.setup(); + + // Highlight widget control + api.Widgets.Previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl ); + + // Open and focus widget control + api.Widgets.Previewer.bind( 'focus-widget-control', api.Widgets.focusWidgetFormControl ); + } ); + /** * Capture the instance of the Previewer since it is private */ - OldPreviewer = wp.customize.Previewer; - wp.customize.Previewer = OldPreviewer.extend( { + OldPreviewer = api.Previewer; + api.Previewer = OldPreviewer.extend({ initialize: function( params, options ) { - self.previewer = this; + api.Widgets.Previewer = this; OldPreviewer.prototype.initialize.call( this, params, options ); this.bind( 'refresh', this.refresh ); } @@ -1537,8 +1514,8 @@ var WidgetCustomizer = ( function ($) { * * @param {string} widgetId */ - self.highlightWidgetFormControl = function( widgetId ) { - var control = self.getWidgetFormControlForWidget( widgetId ); + api.Widgets.highlightWidgetFormControl = function( widgetId ) { + var control = api.Widgets.getWidgetFormControlForWidget( widgetId ); if ( control ) { control.highlightSectionAndControl(); @@ -1550,8 +1527,8 @@ var WidgetCustomizer = ( function ($) { * * @param {string} widgetId */ - self.focusWidgetFormControl = function( widgetId ) { - var control = self.getWidgetFormControlForWidget( widgetId ); + api.Widgets.focusWidgetFormControl = function( widgetId ) { + var control = api.Widgets.getWidgetFormControlForWidget( widgetId ); if ( control ) { control.focus(); @@ -1563,14 +1540,15 @@ var WidgetCustomizer = ( function ($) { * @param {string} widget_id * @return {object|null} */ - self.getSidebarWidgetControlContainingWidget = function ( widget_id ) { + api.Widgets.getSidebarWidgetControlContainingWidget = function ( widget_id ) { var found_control = null; // @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl() - wp.customize.control.each( function ( control ) { + api.control.each( function ( control ) { if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widget_id ) ) { found_control = control; } } ); + return found_control; }; @@ -1579,28 +1557,22 @@ var WidgetCustomizer = ( function ($) { * @param {string} widget_id * @return {object|null} */ - self.getWidgetFormControlForWidget = function ( widget_id ) { + api.Widgets.getWidgetFormControlForWidget = function ( widget_id ) { var found_control = null; // @todo We can just use widget_id_to_setting_id() here - wp.customize.control.each( function ( control ) { + api.control.each( function ( control ) { if ( control.params.type === 'widget_form' && control.params.widget_id === widget_id ) { found_control = control; } } ); - return found_control; - }; - /** - * @returns {Window} - */ - self.getPreviewWindow = function (){ - return $( '#customize-preview' ).find( 'iframe' ).prop( 'contentWindow' ); + return found_control; }; /** * Available Widgets Panel */ - self.availableWidgetsPanel = { + api.Widgets.availableWidgetsPanel = { active_sidebar_widgets_control: null, selected_widget_tpl: null, container: null, @@ -1609,13 +1581,13 @@ var WidgetCustomizer = ( function ($) { /** * Set up event listeners */ - setup: function () { + setup: function() { var panel = this; panel.container = $( '#available-widgets' ); panel.filter_input = $( '#available-widgets-filter' ).find( 'input' ); - self.available_widgets.on( 'change update', panel.update_available_widgets_list ); + api.Widgets.availableWidgets.on( 'change update', panel.update_available_widgets_list ); panel.update_available_widgets_list(); // If the available widgets panel is open and the customize controls are @@ -1629,7 +1601,7 @@ var WidgetCustomizer = ( function ($) { } ); // Close the panel if the URL in the preview changes - self.previewer.bind( 'url', function () { + api.Widgets.Previewer.bind( 'url', function() { panel.close(); } ); @@ -1647,7 +1619,7 @@ var WidgetCustomizer = ( function ($) { panel.filter_input.on( 'input keyup change', function( event ) { var first_visible_widget; - self.available_widgets.doSearch( event.target.value ); + api.Widgets.availableWidgets.doSearch( event.target.value ); // Remove a widget from being selected if it is no longer visible if ( panel.selected_widget_tpl && ! panel.selected_widget_tpl.is( ':visible' ) ) { @@ -1671,7 +1643,7 @@ var WidgetCustomizer = ( function ($) { } ); // Select a widget when it is focused on - panel.container.find( ' > .widget-tpl' ).on( 'focus', function () { + panel.container.find( ' > .widget-tpl' ).on( 'focus', function() { panel.select( this ); } ); @@ -1725,13 +1697,13 @@ var WidgetCustomizer = ( function ($) { * Updates widgets list. */ update_available_widgets_list: function() { - var panel = self.availableWidgetsPanel; + var panel = api.Widgets.availableWidgetsPanel; // First hide all widgets... panel.container.find( '.widget-tpl' ).hide(); // ..and then show only available widgets which could be filtered - self.available_widgets.each( function ( widget ) { + api.Widgets.availableWidgets.each( function ( widget ) { var widget_tpl = $( '#widget-tpl-' + widget.id ); widget_tpl.toggle( ! widget.get( 'is_disabled' ) ); if ( widget.get( 'is_disabled' ) && widget_tpl.is( panel.selected_widget_tpl ) ) { @@ -1761,7 +1733,7 @@ var WidgetCustomizer = ( function ($) { panel.select( widget_tpl ); widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' ); - widget = self.available_widgets.findWhere( {id: widget_id} ); + widget = api.Widgets.availableWidgets.findWhere( {id: widget_id} ); if ( ! widget ) { throw new Error( 'Widget unexpectedly not found.' ); } @@ -1785,7 +1757,7 @@ var WidgetCustomizer = ( function ($) { $( 'body' ).addClass( 'adding-widget' ); panel.container.find( '.widget-tpl' ).removeClass( 'selected' ); - self.available_widgets.doSearch( '' ); + api.Widgets.availableWidgets.doSearch( '' ); panel.filter_input.focus(); }, @@ -1839,5 +1811,4 @@ var WidgetCustomizer = ( function ($) { return setting_id; } - return self; -}( jQuery )); +})( window.wp, jQuery ); diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php index 54e45fea9c..8b9147905f 100644 --- a/src/wp-includes/class-wp-customize-control.php +++ b/src/wp-includes/class-wp-customize-control.php @@ -917,12 +917,12 @@ class WP_Widget_Area_Customize_Control extends WP_Customize_Control { public function render_content() { ?> - + - - + + json[ $key ] = $this->$key; } diff --git a/src/wp-includes/class-wp-customize-widgets.php b/src/wp-includes/class-wp-customize-widgets.php index e441305cbc..adeb3f01d1 100644 --- a/src/wp-includes/class-wp-customize-widgets.php +++ b/src/wp-includes/class-wp-customize-widgets.php @@ -406,8 +406,6 @@ final class WP_Customize_Widgets { $setting_id = $this->get_setting_id( $widget_id ); $id_base = $GLOBALS['wp_registered_widget_controls'][$widget_id]['id_base']; - assert( false !== is_active_widget( $registered_widget['callback'], $registered_widget['id'], false, false ) ); - $control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array( 'label' => $registered_widget['name'], 'section' => $section_id, @@ -599,8 +597,8 @@ final class WP_Customize_Widgets { $move_widget_area_tpl = str_replace( array( '{description}', '{btn}' ), array( - ( 'Select an area to move this widget into:' ), // @todo translate - esc_html_x( 'Move', 'move widget' ), + __( 'Select an area to move this widget into:' ), + _x( 'Move', 'Move widget' ), ), '

    {description}

    @@ -615,39 +613,34 @@ final class WP_Customize_Widgets {
    ' ); - /* - * Why not wp_localize_script? Because we're not localizing, - * and it forces values into strings. - */ global $wp_scripts; - $exports = array( - 'nonce' => wp_create_nonce( 'update-widget' ), - 'registered_sidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ), - 'registered_widgets' => $GLOBALS['wp_registered_widgets'], - 'available_widgets' => $available_widgets, // @todo Merge this with registered_widgets - 'i18n' => array( - 'save_btn_label' => __( 'Apply' ), - // @todo translate? do we want these tooltips? - 'save_btn_tooltip' => ( 'Save and preview changes before publishing them.' ), - 'remove_btn_label' => __( 'Remove' ), - 'remove_btn_tooltip' => ( 'Trash widget by moving it to the inactive widgets sidebar.' ), - 'error' => __( 'An error has occurred. Please reload the page and try again.' ), + $settings = array( + 'nonce' => wp_create_nonce( 'update-widget' ), + 'registeredSidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ), + 'registeredWidgets' => $GLOBALS['wp_registered_widgets'], + 'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets + 'l10n' => array( + 'saveBtnLabel' => __( 'Apply' ), + 'saveBtnTooltip' => __( 'Save and preview changes before publishing them.' ), + 'removeBtnLabel' => __( 'Remove' ), + 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ), + 'error' => __( 'An error has occurred. Please reload the page and try again.' ), ), - 'tpl' => array( - 'widget_reorder_nav' => $widget_reorder_nav_tpl, - 'move_widget_area' => $move_widget_area_tpl, + 'tpl' => array( + 'widgetReorderNav' => $widget_reorder_nav_tpl, + 'moveWidgetArea' => $move_widget_area_tpl, ), ); - foreach ( $exports['registered_widgets'] as &$registered_widget ) { + foreach ( $settings['registeredWidgets'] as &$registered_widget ) { unset( $registered_widget['callback'] ); // may not be JSON-serializeable } $wp_scripts->add_data( 'customize-widgets', 'data', - sprintf( 'var WidgetCustomizer_exports = %s;', json_encode( $exports ) ) + sprintf( 'var _wpCustomizeWidgetsSettings = %s;', json_encode( $settings ) ) ); } @@ -662,12 +655,12 @@ final class WP_Customize_Widgets {
    - - + +
    get_available_widgets() as $available_widget ): ?>
    - +
    @@ -826,6 +819,7 @@ final class WP_Customize_Widgets { $available_widgets[] = $available_widget; } + return $available_widgets; } diff --git a/src/wp-includes/js/customize-preview-widgets.js b/src/wp-includes/js/customize-preview-widgets.js index 974a457d7b..6bd7e02464 100644 --- a/src/wp-includes/js/customize-preview-widgets.js +++ b/src/wp-includes/js/customize-preview-widgets.js @@ -1,4 +1,4 @@ -(function( $, wp ){ +(function( wp, $ ){ if ( ! wp || ! wp.customize ) { return; } @@ -124,4 +124,4 @@ api.WidgetCustomizerPreview.init(); }); -})( jQuery, window.wp ); +})( window.wp, jQuery );