Make WordPress Core

Changeset 27985


Ignore:
Timestamp:
04/07/2014 09:03:18 AM (12 years ago)
Author:
ocean90
Message:

Widget Customizer: Move WidgetCustomizer to wp.customize.Widgets. First pass.

see #27690.

Location:
trunk/src
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-controls.css

    r27970 r27985  
    491491        -webkit-border-radius: 2px;
    492492        border: 1px solid #eee;
     493        -webkit-border-radius: 2px;
    493494        border-radius: 2px;
    494495}
  • trunk/src/wp-admin/css/customize-widgets.css

    r27912 r27985  
    108108        display: none;
    109109}
    110 
    111 
    112 /* MP6-compat */
    113 #customize-theme-controls .accordion-section-content .widget {
    114         color: black;
    115 }
    116 
    117110
    118111/**
     
    327320body.adding-widget .add-new-widget,
    328321body.adding-widget .add-new-widget:hover {
    329         background: #EEE;
     322        background: #eee;
    330323        border-color: #999;
    331324        color: #333;
  • trunk/src/wp-admin/js/customize-widgets.js

    r27913 r27985  
    1 /*global wp, Backbone, _, jQuery, WidgetCustomizer_exports */
    2 /*exported WidgetCustomizer */
    3 var WidgetCustomizer = ( function ($) {
    4         'use strict';
    5 
    6         var Widget,
    7                 WidgetCollection,
    8                 Sidebar,
    9                 SidebarCollection,
    10                 OldPreviewer,
    11                 builtin_form_sync_handlers,
    12                 customize = wp.customize, self = {
    13                 nonce: null,
    14                 i18n: {
    15                         save_btn_label: '',
    16                         save_btn_tooltip: '',
    17                         remove_btn_label: '',
    18                         remove_btn_tooltip: '',
    19                         error: ''
    20                 },
    21                 available_widgets: [], // available widgets for instantiating
    22                 registered_widgets: [], // all widgets registered
    23                 active_sidebar_control: null,
    24                 previewer: null,
    25                 saved_widget_ids: {},
    26                 registered_sidebars: [],
    27                 tpl: {
    28                         move_widget_area: '',
    29                         widget_reorder_nav: ''
    30                 }
    31         };
    32         $.extend( self, WidgetCustomizer_exports );
    33 
    34         // Lots of widgets expect this old ajaxurl global to be available
    35         if ( typeof window.ajaxurl === 'undefined' ) {
    36                 window.ajaxurl = wp.ajax.settings.url;
    37         }
     1/* global _wpCustomizeWidgetsSettings */
     2(function( wp, $ ){
     3
     4        if ( ! wp || ! wp.customize ) { return; }
     5
     6        // Set up our namespace...
     7        var api = wp.customize,
     8                l10n, OldPreviewer;
     9
     10        api.Widgets = api.Widgets || {};
     11
     12        // Link settings
     13        api.Widgets.data = _wpCustomizeWidgetsSettings || {};
     14        l10n = api.Widgets.data.l10n;
     15        delete api.Widgets.data.l10n;
    3816
    3917        /**
    4018         * Set up model
    4119         */
    42         Widget = self.Widget = Backbone.Model.extend( {
     20        api.Widgets.WidgetModel = Backbone.Model.extend({
    4321                id: null,
    4422                temp_id: null,
     
    5533                width: null,
    5634                height: null
    57         } );
    58 
    59         WidgetCollection = self.WidgetCollection = Backbone.Collection.extend( {
    60                 model: Widget,
     35        });
     36
     37        api.Widgets.WidgetCollection = Backbone.Collection.extend({
     38                model: api.Widgets.WidgetModel,
    6139
    6240                // Controls searching on the current widget collection
     
    8159                        // Useful for resetting the views when you clean the input
    8260                        if ( this.terms === '' ) {
    83                                 this.reset( WidgetCustomizer_exports.available_widgets );
     61                                this.reset( api.Widgets.data.availableWidgets );
    8462                        }
    8563
     
    9472
    9573                        // Start with a full collection
    96                         this.reset( WidgetCustomizer_exports.available_widgets, { silent: true } );
     74                        this.reset( api.Widgets.data.availableWidgets, { silent: true } );
    9775
    9876                        // Escape the term string for RegExp meta characters
     
    11290                        this.reset( results );
    11391                }
    114         } );
    115         self.available_widgets = new WidgetCollection( self.available_widgets );
    116 
    117         Sidebar = self.Sidebar = Backbone.Model.extend( {
     92        });
     93        api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets );
     94
     95        api.Widgets.SidebarModel = Backbone.Model.extend({
    11896                after_title: null,
    11997                after_widget: null,
     
    125103                name: null,
    126104                is_rendered: false
    127         } );
    128 
    129         SidebarCollection = self.SidebarCollection = Backbone.Collection.extend( {
    130                 model: Sidebar
    131         } );
    132         self.registered_sidebars = new SidebarCollection( self.registered_sidebars );
     105        });
     106
     107        api.Widgets.SidebarCollection = Backbone.Collection.extend({
     108                model: api.Widgets.SidebarModel
     109        });
     110        api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars );
    133111
    134112        /**
     
    137115         * listeners for the widget-synced event.
    138116         */
    139         builtin_form_sync_handlers = {
     117        api.Widgets.formSyncHandlers = {
    140118
    141119                /**
    142120                 * @param {jQuery.Event} e
    143                  * @param {jQuery} widget_el
    144                  * @param {String} new_form
    145                  */
    146                 rss: function ( e, widget_el, new_form ) {
    147                         var old_widget_error = widget_el.find( '.widget-error:first' ),
    148                                 new_widget_error = $( '<div>' + new_form + '</div>' ).find( '.widget-error:first' );
    149 
    150                         if ( old_widget_error.length && new_widget_error.length ) {
    151                                 old_widget_error.replaceWith( new_widget_error );
    152                         } else if ( old_widget_error.length ) {
    153                                 old_widget_error.remove();
    154                         } else if ( new_widget_error.length ) {
    155                                 widget_el.find( '.widget-content:first' ).prepend( new_widget_error );
     121                 * @param {jQuery} widget
     122                 * @param {String} newForm
     123                 */
     124                rss: function ( e, widget, newForm ) {
     125                        var oldWidgetError = widget.find( '.widget-error:first' ),
     126                                newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' );
     127
     128                        if ( oldWidgetError.length && newWidgetError.length ) {
     129                                oldWidgetError.replaceWith( newWidgetError );
     130                        } else if ( oldWidgetError.length ) {
     131                                oldWidgetError.remove();
     132                        } else if ( newWidgetError.length ) {
     133                                widget.find( '.widget-content:first' ).prepend( newWidgetError );
    156134                        }
    157135                }
     
    159137
    160138        /**
    161          * On DOM ready, initialize some meta functionality independent of specific
    162          * customizer controls.
     139         * Widget Form control
     140         * Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type
    163141         */
    164         self.init = function () {
    165                 this.availableWidgetsPanel.setup();
    166 
    167                 // Highlight widget control
    168                 this.previewer.bind( 'highlight-widget-control', self.highlightWidgetFormControl );
    169 
    170                 // Open and focus widget control
    171                 this.previewer.bind( 'focus-widget-control', self.focusWidgetFormControl );
    172         };
    173         wp.customize.bind( 'ready', function () {
    174                 self.init();
     142        api.Widgets.WidgetControl = api.Control.extend({
     143                /**
     144                 * Set up the control
     145                 */
     146                ready: function() {
     147                        var control = this;
     148                        control._setupModel();
     149                        control._setupWideWidget();
     150                        control._setupControlToggle();
     151                        control._setupWidgetTitle();
     152                        control._setupReorderUI();
     153                        control._setupHighlightEffects();
     154                        control._setupUpdateUI();
     155                        control._setupRemoveUI();
     156                },
     157
     158                /**
     159                 * Handle changes to the setting
     160                 */
     161                _setupModel: function() {
     162                        var control = this, remember_saved_widget_id;
     163
     164                        api.Widgets.savedWidgetIds = api.Widgets.savedWidgetIds || [];
     165
     166                        // Remember saved widgets so we know which to trash (move to inactive widgets sidebar)
     167                        remember_saved_widget_id = function() {
     168                                api.Widgets.savedWidgetIds[control.params.widget_id] = true;
     169                        };
     170                        api.bind( 'ready', remember_saved_widget_id );
     171                        api.bind( 'saved', remember_saved_widget_id );
     172
     173                        control._update_count = 0;
     174                        control.is_widget_updating = false;
     175                        control.live_update_mode = true;
     176
     177                        // Update widget whenever model changes
     178                        control.setting.bind( function( to, from ) {
     179                                if ( ! _( from ).isEqual( to ) && ! control.is_widget_updating ) {
     180                                        control.updateWidget( { instance: to } );
     181                                }
     182                        } );
     183                },
     184
     185                /**
     186                 * Add special behaviors for wide widget controls
     187                 */
     188                _setupWideWidget: function() {
     189                        var control = this,
     190                                widget_inside,
     191                                widget_form,
     192                                customize_sidebar,
     193                                position_widget,
     194                                theme_controls_container;
     195
     196                        if ( ! control.params.is_wide ) {
     197                                return;
     198                        }
     199
     200                        widget_inside = control.container.find( '.widget-inside' );
     201                        widget_form = widget_inside.find( '> .form' );
     202                        customize_sidebar = $( '.wp-full-overlay-sidebar-content:first' );
     203                        control.container.addClass( 'wide-widget-control' );
     204
     205                        control.container.find( '.widget-content:first' ).css( {
     206                                'max-width': control.params.width,
     207                                'min-height': control.params.height
     208                        } );
     209
     210                        /**
     211                         * Keep the widget-inside positioned so the top of fixed-positioned
     212                         * element is at the same top position as the widget-top. When the
     213                         * widget-top is scrolled out of view, keep the widget-top in view;
     214                         * likewise, don't allow the widget to drop off the bottom of the window.
     215                         * If a widget is too tall to fit in the window, don't let the height
     216                         * exceed the window height so that the contents of the widget control
     217                         * will become scrollable (overflow:auto).
     218                         */
     219                        position_widget = function() {
     220                                var offset_top = control.container.offset().top,
     221                                        window_height = $( window ).height(),
     222                                        form_height = widget_form.outerHeight(),
     223                                        top;
     224                                widget_inside.css( 'max-height', window_height );
     225                                top = Math.max(
     226                                        0, // prevent top from going off screen
     227                                        Math.min(
     228                                                Math.max( offset_top, 0 ), // distance widget in panel is from top of screen
     229                                                window_height - form_height // flush up against bottom of screen
     230                                        )
     231                                );
     232                                widget_inside.css( 'top', top );
     233                        };
     234
     235                        theme_controls_container = $( '#customize-theme-controls' );
     236                        control.container.on( 'expand', function() {
     237                                position_widget();
     238                                customize_sidebar.on( 'scroll', position_widget );
     239                                $( window ).on( 'resize', position_widget );
     240                                theme_controls_container.on( 'expanded collapsed', position_widget );
     241                        } );
     242                        control.container.on( 'collapsed', function() {
     243                                customize_sidebar.off( 'scroll', position_widget );
     244                                $( window ).off( 'resize', position_widget );
     245                                theme_controls_container.off( 'expanded collapsed', position_widget );
     246                        } );
     247
     248                        // Reposition whenever a sidebar's widgets are changed
     249                        api.each( function ( setting ) {
     250                                if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
     251                                        setting.bind( function() {
     252                                                if ( control.container.hasClass( 'expanded' ) ) {
     253                                                        position_widget();
     254                                                }
     255                                        } );
     256                                }
     257                        } );
     258                },
     259
     260                /**
     261                 * Show/hide the control when clicking on the form title, when clicking
     262                 * the close button
     263                 */
     264                _setupControlToggle: function() {
     265                        var control = this, close_btn;
     266
     267                        control.container.find( '.widget-top' ).on( 'click', function ( e ) {
     268                                e.preventDefault();
     269                                var sidebar_widgets_control = control.getSidebarWidgetsControl();
     270                                if ( sidebar_widgets_control.is_reordering ) {
     271                                        return;
     272                                }
     273                                control.toggleForm();
     274                        } );
     275
     276                        close_btn = control.container.find( '.widget-control-close' );
     277                        // @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
     278                        close_btn.on( 'click', function ( e ) {
     279                                e.preventDefault();
     280                                control.collapseForm();
     281                                control.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility
     282                        } );
     283                },
     284
     285                /**
     286                 * Update the title of the form if a title field is entered
     287                 */
     288                _setupWidgetTitle: function() {
     289                        var control = this, update_title;
     290
     291                        update_title = function() {
     292                                var title = control.setting().title,
     293                                        in_widget_title = control.container.find( '.in-widget-title' );
     294
     295                                if ( title ) {
     296                                        in_widget_title.text( ': ' + title );
     297                                } else {
     298                                        in_widget_title.text( '' );
     299                                }
     300                        };
     301                        control.setting.bind( update_title );
     302                        update_title();
     303                },
     304
     305                /**
     306                 * Set up the widget-reorder-nav
     307                 */
     308                _setupReorderUI: function() {
     309                        var control = this,
     310                                select_sidebar_item,
     311                                move_widget_area,
     312                                reorder_nav,
     313                                update_available_sidebars;
     314
     315                        /**
     316                         * select the provided sidebar list item in the move widget area
     317                         *
     318                         * @param {jQuery} li
     319                         */
     320                        select_sidebar_item = function ( li ) {
     321                                li.siblings( '.selected' ).removeClass( 'selected' );
     322                                li.addClass( 'selected' );
     323                                var is_self_sidebar = ( li.data( 'id' ) === control.params.sidebar_id );
     324                                control.container.find( '.move-widget-btn' ).prop( 'disabled', is_self_sidebar );
     325                        };
     326
     327                        /**
     328                         * Add the widget reordering elements to the widget control
     329                         */
     330                        control.container.find( '.widget-title-action' ).after( $( api.Widgets.data.tpl.widgetReorderNav ) );
     331                        move_widget_area = $(
     332                                _.template( api.Widgets.data.tpl.moveWidgetArea, {
     333                                        sidebars: _( api.Widgets.registeredSidebars.toArray() ).pluck( 'attributes' )
     334                                } )
     335                        );
     336                        control.container.find( '.widget-top' ).after( move_widget_area );
     337
     338                        /**
     339                         * Update available sidebars when their rendered state changes
     340                         */
     341                        update_available_sidebars = function() {
     342                                var sidebar_items = move_widget_area.find( 'li' ), self_sidebar_item;
     343                                self_sidebar_item = sidebar_items.filter( function(){
     344                                        return $( this ).data( 'id' ) === control.params.sidebar_id;
     345                                } );
     346                                sidebar_items.each( function() {
     347                                        var li = $( this ),
     348                                                sidebar_id,
     349                                                sidebar_model;
     350
     351                                        sidebar_id = li.data( 'id' );
     352                                        sidebar_model = api.Widgets.registeredSidebars.get( sidebar_id );
     353                                        li.toggle( sidebar_model.get( 'is_rendered' ) );
     354                                        if ( li.hasClass( 'selected' ) && ! sidebar_model.get( 'is_rendered' ) ) {
     355                                                select_sidebar_item( self_sidebar_item );
     356                                        }
     357                                } );
     358                        };
     359                        update_available_sidebars();
     360                        api.Widgets.registeredSidebars.on( 'change:is_rendered', update_available_sidebars );
     361
     362                        /**
     363                         * Handle clicks for up/down/move on the reorder nav
     364                         */
     365                        reorder_nav = control.container.find( '.widget-reorder-nav' );
     366                        reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function ( event ) {
     367                                if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
     368                                        return;
     369                                }
     370                                $( this ).focus();
     371
     372                                if ( $( this ).is( '.move-widget' ) ) {
     373                                        control.toggleWidgetMoveArea();
     374                                } else {
     375                                        var is_move_down = $( this ).is( '.move-widget-down' ),
     376                                                is_move_up = $( this ).is( '.move-widget-up' ),
     377                                                i = control.getWidgetSidebarPosition();
     378
     379                                        if ( ( is_move_up && i === 0 ) || ( is_move_down && i === control.getSidebarWidgetsControl().setting().length - 1 ) ) {
     380                                                return;
     381                                        }
     382
     383                                        if ( is_move_up ) {
     384                                                control.moveUp();
     385                                        } else {
     386                                                control.moveDown();
     387                                        }
     388
     389                                        $( this ).focus(); // re-focus after the container was moved
     390                                }
     391                        } );
     392
     393                        /**
     394                         * Handle selecting a sidebar to move to
     395                         */
     396                        control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function ( e ) {
     397                                if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
     398                                        return;
     399                                }
     400                                e.preventDefault();
     401                                select_sidebar_item( $( this ) );
     402                        } );
     403
     404                        /**
     405                         * Move widget to another sidebar
     406                         */
     407                        control.container.find( '.move-widget-btn' ).click( function() {
     408                                control.getSidebarWidgetsControl().toggleReordering( false );
     409
     410                                var old_sidebar_id = control.params.sidebar_id,
     411                                        new_sidebar_id = control.container.find( '.widget-area-select li.selected' ).data( 'id' ),
     412                                        old_sidebar_widgets_setting,
     413                                        new_sidebar_widgets_setting,
     414                                        old_sidebar_widget_ids,
     415                                        new_sidebar_widget_ids,
     416                                        i;
     417
     418                                old_sidebar_widgets_setting = api( 'sidebars_widgets[' + old_sidebar_id + ']' );
     419                                new_sidebar_widgets_setting = api( 'sidebars_widgets[' + new_sidebar_id + ']' );
     420                                old_sidebar_widget_ids = Array.prototype.slice.call( old_sidebar_widgets_setting() );
     421                                new_sidebar_widget_ids = Array.prototype.slice.call( new_sidebar_widgets_setting() );
     422
     423                                i = control.getWidgetSidebarPosition();
     424                                old_sidebar_widget_ids.splice( i, 1 );
     425                                new_sidebar_widget_ids.push( control.params.widget_id );
     426
     427                                old_sidebar_widgets_setting( old_sidebar_widget_ids );
     428                                new_sidebar_widgets_setting( new_sidebar_widget_ids );
     429
     430                                control.focus();
     431                        } );
     432                },
     433
     434                /**
     435                 * Highlight widgets in preview when interacted with in the customizer
     436                 */
     437                _setupHighlightEffects: function() {
     438                        var control = this;
     439
     440                        // Highlight whenever hovering or clicking over the form
     441                        control.container.on( 'mouseenter click', function() {
     442                                control.setting.previewer.send( 'highlight-widget', control.params.widget_id );
     443                        } );
     444
     445                        // Highlight when the setting is updated
     446                        control.setting.bind( function() {
     447                                control.setting.previewer.send( 'highlight-widget', control.params.widget_id );
     448                        } );
     449
     450                        // Highlight when the widget form is expanded
     451                        control.container.on( 'expand', function() {
     452                                control.scrollPreviewWidgetIntoView();
     453                        } );
     454                },
     455
     456                /**
     457                 * Set up event handlers for widget updating
     458                 */
     459                _setupUpdateUI: function() {
     460                        var control = this,
     461                                widget_root,
     462                                widget_content,
     463                                save_btn,
     464                                update_widget_debounced,
     465                                form_update_event_handler;
     466
     467                        widget_root = control.container.find( '.widget:first' );
     468                        widget_content = widget_root.find( '.widget-content:first' );
     469
     470                        // Configure update button
     471                        save_btn = control.container.find( '.widget-control-save' );
     472                        save_btn.val( l10n.saveBtnLabel );
     473                        save_btn.attr( 'title', l10n.saveBtnTooltip );
     474                        save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' );
     475                        save_btn.on( 'click', function ( e ) {
     476                                e.preventDefault();
     477                                control.updateWidget( { disable_form: true } );
     478                        } );
     479
     480                        update_widget_debounced = _.debounce( function() {
     481                                // @todo For compatibility with other plugins, should we trigger a click event? What about form submit event?
     482                                control.updateWidget();
     483                        }, 250 );
     484
     485                        // Trigger widget form update when hitting Enter within an input
     486                        control.container.find( '.widget-content' ).on( 'keydown', 'input', function( e ) {
     487                                if ( 13 === e.which ) { // Enter
     488                                        e.preventDefault();
     489                                        control.updateWidget( { ignore_active_element: true } );
     490                                }
     491                        } );
     492
     493                        // Handle widgets that support live previews
     494                        widget_content.on( 'change input propertychange', ':input', function ( e ) {
     495                                if ( control.live_update_mode ) {
     496                                        if ( e.type === 'change' ) {
     497                                                control.updateWidget();
     498                                        } else if ( this.checkValidity && this.checkValidity() ) {
     499                                                update_widget_debounced();
     500                                        }
     501                                }
     502                        } );
     503
     504                        // Remove loading indicators when the setting is saved and the preview updates
     505                        control.setting.previewer.channel.bind( 'synced', function() {
     506                                control.container.removeClass( 'previewer-loading' );
     507                        } );
     508                        api.Widgets.Previewer.bind( 'widget-updated', function ( updated_widget_id ) {
     509                                if ( updated_widget_id === control.params.widget_id ) {
     510                                        control.container.removeClass( 'previewer-loading' );
     511                                }
     512                        } );
     513
     514                        // Update widget control to indicate whether it is currently rendered (cf. Widget Visibility)
     515                        api.Widgets.Previewer.bind( 'rendered-widgets', function ( rendered_widgets ) {
     516                                var is_rendered = !! rendered_widgets[control.params.widget_id];
     517                                control.container.toggleClass( 'widget-rendered', is_rendered );
     518                        } );
     519
     520                        form_update_event_handler = api.Widgets.formSyncHandlers[ control.params.widget_id_base ];
     521                        if ( form_update_event_handler ) {
     522                                $( document ).on( 'widget-synced', function ( e, widget_el ) {
     523                                        if ( widget_root.is( widget_el ) ) {
     524                                                form_update_event_handler.apply( document, arguments );
     525                                        }
     526                                } );
     527                        }
     528                },
     529
     530                /**
     531                 * Set up event handlers for widget removal
     532                 */
     533                _setupRemoveUI: function() {
     534                        var control = this,
     535                                remove_btn,
     536                                replace_delete_with_remove;
     537
     538                        // Configure remove button
     539                        remove_btn = control.container.find( 'a.widget-control-remove' );
     540                        // @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
     541                        remove_btn.on( 'click', function ( e ) {
     542                                e.preventDefault();
     543
     544                                // Find an adjacent element to add focus to when this widget goes away
     545                                var adjacent_focus_target;
     546                                if ( control.container.next().is( '.customize-control-widget_form' ) ) {
     547                                        adjacent_focus_target = control.container.next().find( '.widget-action:first' );
     548                                } else if ( control.container.prev().is( '.customize-control-widget_form' ) ) {
     549                                        adjacent_focus_target = control.container.prev().find( '.widget-action:first' );
     550                                } else {
     551                                        adjacent_focus_target = control.container.next( '.customize-control-sidebar_widgets' ).find( '.add-new-widget:first' );
     552                                }
     553
     554                                control.container.slideUp( function() {
     555                                        var sidebars_widgets_control = api.Widgets.getSidebarWidgetControlContainingWidget( control.params.widget_id ),
     556                                                sidebar_widget_ids,
     557                                                i;
     558
     559                                        if ( ! sidebars_widgets_control ) {
     560                                                throw new Error( 'Unable to find sidebars_widgets_control' );
     561                                        }
     562                                        sidebar_widget_ids = sidebars_widgets_control.setting().slice();
     563                                        i = _.indexOf( sidebar_widget_ids, control.params.widget_id );
     564                                        if ( -1 === i ) {
     565                                                throw new Error( 'Widget is not in sidebar' );
     566                                        }
     567                                        sidebar_widget_ids.splice( i, 1 );
     568                                        sidebars_widgets_control.setting( sidebar_widget_ids );
     569                                        adjacent_focus_target.focus(); // keyboard accessibility
     570                                } );
     571                        } );
     572
     573                        replace_delete_with_remove = function() {
     574                                remove_btn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete"
     575                                remove_btn.attr( 'title', l10n.removeBtnTooltip );
     576                        };
     577                        if ( control.params.is_new ) {
     578                                api.bind( 'saved', replace_delete_with_remove );
     579                        } else {
     580                                replace_delete_with_remove();
     581                        }
     582                },
     583
     584                /**
     585                 * Find all inputs in a widget container that should be considered when
     586                 * comparing the loaded form with the sanitized form, whose fields will
     587                 * be aligned to copy the sanitized over. The elements returned by this
     588                 * are passed into this._getInputsSignature(), and they are iterated
     589                 * over when copying sanitized values over to the the form loaded.
     590                 *
     591                 * @param {jQuery} container element in which to look for inputs
     592                 * @returns {jQuery} inputs
     593                 * @private
     594                 */
     595                _getInputs: function ( container ) {
     596                        return $( container ).find( ':input[name]' );
     597                },
     598
     599                /**
     600                 * Iterate over supplied inputs and create a signature string for all of them together.
     601                 * This string can be used to compare whether or not the form has all of the same fields.
     602                 *
     603                 * @param {jQuery} inputs
     604                 * @returns {string}
     605                 * @private
     606                 */
     607                _getInputsSignature: function ( inputs ) {
     608                        var inputs_signatures = _( inputs ).map( function ( input ) {
     609                                input = $( input );
     610                                var signature_parts;
     611                                if ( input.is( ':checkbox, :radio' ) ) {
     612                                        signature_parts = [ input.attr( 'id' ), input.attr( 'name' ), input.prop( 'value' ) ];
     613                                } else {
     614                                        signature_parts = [ input.attr( 'id' ), input.attr( 'name' ) ];
     615                                }
     616                                return signature_parts.join( ',' );
     617                        } );
     618                        return inputs_signatures.join( ';' );
     619                },
     620
     621                /**
     622                 * Get the property that represents the state of an input.
     623                 *
     624                 * @param {jQuery|DOMElement} input
     625                 * @returns {string}
     626                 * @private
     627                 */
     628                _getInputStatePropertyName: function ( input ) {
     629                        input = $( input );
     630                        if ( input.is( ':radio, :checkbox' ) ) {
     631                                return 'checked';
     632                        } else {
     633                                return 'value';
     634                        }
     635                },
     636
     637                /***********************************************************************
     638                 * Begin public API methods
     639                 **********************************************************************/
     640
     641                /**
     642                 * @return {wp.customize.controlConstructor.sidebar_widgets[]}
     643                 */
     644                getSidebarWidgetsControl: function() {
     645                        var control = this, setting_id, sidebar_widgets_control;
     646
     647                        setting_id = 'sidebars_widgets[' + control.params.sidebar_id + ']';
     648                        sidebar_widgets_control = api.control( setting_id );
     649                        if ( ! sidebar_widgets_control ) {
     650                                throw new Error( 'Unable to locate sidebar_widgets control for ' + control.params.sidebar_id );
     651                        }
     652                        return sidebar_widgets_control;
     653                },
     654
     655                /**
     656                 * Submit the widget form via Ajax and get back the updated instance,
     657                 * along with the new widget control form to render.
     658                 *
     659                 * @param {object} [args]
     660                 * @param {Object|null} [args.instance=null]  When the model changes, the instance is sent here; otherwise, the inputs from the form are used
     661                 * @param {Function|null} [args.complete=null]  Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success.
     662                 * @param {Boolean} [args.ignore_active_element=false] Whether or not updating a field will be deferred if focus is still on the element.
     663                 */
     664                updateWidget: function ( args ) {
     665                        var control = this,
     666                                instance_override,
     667                                complete_callback,
     668                                widget_root,
     669                                update_number,
     670                                widget_content,
     671                                params,
     672                                data,
     673                                inputs,
     674                                processing,
     675                                jqxhr,
     676                                is_changed;
     677
     678                        args = $.extend( {
     679                                instance: null,
     680                                complete: null,
     681                                ignore_active_element: false
     682                        }, args );
     683
     684                        instance_override = args.instance;
     685                        complete_callback = args.complete;
     686
     687                        control._update_count += 1;
     688                        update_number = control._update_count;
     689
     690                        widget_root = control.container.find( '.widget:first' );
     691                        widget_content = widget_root.find( '.widget-content:first' );
     692
     693                        // Remove a previous error message
     694                        widget_content.find( '.widget-error' ).remove();
     695
     696                        control.container.addClass( 'widget-form-loading' );
     697                        control.container.addClass( 'previewer-loading' );
     698                        processing = api.state( 'processing' );
     699                        processing( processing() + 1 );
     700
     701                        if ( ! control.live_update_mode ) {
     702                                control.container.addClass( 'widget-form-disabled' );
     703                        }
     704
     705                        params = {};
     706                        params.action = 'update-widget';
     707                        params.wp_customize = 'on';
     708                        params.nonce = api.Widgets.data.nonce;
     709
     710                        data = $.param( params );
     711                        inputs = control._getInputs( widget_content );
     712
     713                        // Store the value we're submitting in data so that when the response comes back,
     714                        // we know if it got sanitized; if there is no difference in the sanitized value,
     715                        // then we do not need to touch the UI and mess up the user's ongoing editing.
     716                        inputs.each( function() {
     717                                var input = $( this ),
     718                                        property = control._getInputStatePropertyName( this );
     719                                input.data( 'state' + update_number, input.prop( property ) );
     720                        } );
     721
     722                        if ( instance_override ) {
     723                                data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instance_override ) } );
     724                        } else {
     725                                data += '&' + inputs.serialize();
     726                        }
     727                        data += '&' + widget_content.find( '~ :input' ).serialize();
     728
     729                        jqxhr = $.post( wp.ajax.settings.url, data, function ( r ) {
     730                                var message,
     731                                        sanitized_form,
     732                                        sanitized_inputs,
     733                                        has_same_inputs_in_response,
     734                                        is_live_update_aborted = false;
     735
     736                                // Check if the user is logged out.
     737                                if ( '0' === r ) {
     738                                        api.Widgets.Previewer.preview.iframe.hide();
     739                                        api.Widgets.Previewer.login().done( function() {
     740                                                control.updateWidget( args );
     741                                                api.Widgets.Previewer.preview.iframe.show();
     742                                        } );
     743                                        return;
     744                                }
     745
     746                                // Check for cheaters.
     747                                if ( '-1' === r ) {
     748                                        api.Widgets.Previewer.cheatin();
     749                                        return;
     750                                }
     751
     752                                if ( r.success ) {
     753                                        sanitized_form = $( '<div>' + r.data.form + '</div>' );
     754                                        sanitized_inputs = control._getInputs( sanitized_form );
     755                                        has_same_inputs_in_response = control._getInputsSignature( inputs ) === control._getInputsSignature( sanitized_inputs );
     756
     757                                        // Restore live update mode if sanitized fields are now aligned with the existing fields
     758                                        if ( has_same_inputs_in_response && ! control.live_update_mode ) {
     759                                                control.live_update_mode = true;
     760                                                control.container.removeClass( 'widget-form-disabled' );
     761                                                control.container.find( 'input[name="savewidget"]' ).hide();
     762                                        }
     763
     764                                        // Sync sanitized field states to existing fields if they are aligned
     765                                        if ( has_same_inputs_in_response && control.live_update_mode ) {
     766                                                inputs.each( function ( i ) {
     767                                                        var input = $( this ),
     768                                                                sanitized_input = $( sanitized_inputs[i] ),
     769                                                                property = control._getInputStatePropertyName( this ),
     770                                                                submitted_state,
     771                                                                sanitized_state,
     772                                                                can_update_state;
     773
     774                                                        submitted_state = input.data( 'state' + update_number );
     775                                                        sanitized_state = sanitized_input.prop( property );
     776                                                        input.data( 'sanitized', sanitized_state );
     777
     778                                                        can_update_state = (
     779                                                                submitted_state !== sanitized_state &&
     780                                                                ( args.ignore_active_element || ! input.is( document.activeElement ) )
     781                                                        );
     782                                                        if ( can_update_state ) {
     783                                                                input.prop( property, sanitized_state );
     784                                                        }
     785                                                } );
     786                                                $( document ).trigger( 'widget-synced', [ widget_root, r.data.form ] );
     787
     788                                        // Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled
     789                                        } else if ( control.live_update_mode ) {
     790                                                control.live_update_mode = false;
     791                                                control.container.find( 'input[name="savewidget"]' ).show();
     792                                                is_live_update_aborted = true;
     793                                        // Otherwise, replace existing form with the sanitized form
     794                                        } else {
     795                                                widget_content.html( r.data.form );
     796                                                control.container.removeClass( 'widget-form-disabled' );
     797                                                $( document ).trigger( 'widget-updated', [ widget_root ] );
     798                                        }
     799
     800                                        /**
     801                                         * If the old instance is identical to the new one, there is nothing new
     802                                         * needing to be rendered, and so we can preempt the event for the
     803                                         * preview finishing loading.
     804                                         */
     805                                        is_changed = ! is_live_update_aborted && ! _( control.setting() ).isEqual( r.data.instance );
     806                                        if ( is_changed ) {
     807                                                control.is_widget_updating = true; // suppress triggering another updateWidget
     808                                                control.setting( r.data.instance );
     809                                                control.is_widget_updating = false;
     810                                        } else {
     811                                                // no change was made, so stop the spinner now instead of when the preview would updates
     812                                                control.container.removeClass( 'previewer-loading' );
     813                                        }
     814
     815                                        if ( complete_callback ) {
     816                                                complete_callback.call( control, null, { no_change: ! is_changed, ajax_finished: true } );
     817                                        }
     818                                } else {
     819                                        message = l10n.error;
     820                                        if ( r.data && r.data.message ) {
     821                                                message = r.data.message;
     822                                        }
     823                                        if ( complete_callback ) {
     824                                                complete_callback.call( control, message );
     825                                        } else {
     826                                                widget_content.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' );
     827                                        }
     828                                }
     829                        } );
     830                        jqxhr.fail( function ( jqXHR, textStatus ) {
     831                                if ( complete_callback ) {
     832                                        complete_callback.call( control, textStatus );
     833                                }
     834                        } );
     835                        jqxhr.always( function() {
     836                                control.container.removeClass( 'widget-form-loading' );
     837                                inputs.each( function() {
     838                                        $( this ).removeData( 'state' + update_number );
     839                                } );
     840
     841                                processing( processing() - 1 );
     842                        } );
     843                },
     844
     845                /**
     846                 * Expand the accordion section containing a control
     847                 * @todo it would be nice if accordion had a proper API instead of having to trigger UI events on its elements
     848                 */
     849                expandControlSection: function() {
     850                        var section = this.container.closest( '.accordion-section' );
     851                        if ( ! section.hasClass( 'open' ) ) {
     852                                section.find( '.accordion-section-title:first' ).trigger( 'click' );
     853                        }
     854                },
     855
     856                /**
     857                 * Expand the widget form control
     858                 */
     859                expandForm: function() {
     860                        this.toggleForm( true );
     861                },
     862
     863                /**
     864                 * Collapse the widget form control
     865                 */
     866                collapseForm: function() {
     867                        this.toggleForm( false );
     868                },
     869
     870                /**
     871                 * Expand or collapse the widget control
     872                 *
     873                 * @param {boolean|undefined} [do_expand] If not supplied, will be inverse of current visibility
     874                 */
     875                toggleForm: function ( do_expand ) {
     876                        var control = this, widget, inside, complete;
     877
     878                        widget = control.container.find( 'div.widget:first' );
     879                        inside = widget.find( '.widget-inside:first' );
     880                        if ( typeof do_expand === 'undefined' ) {
     881                                do_expand = ! inside.is( ':visible' );
     882                        }
     883
     884                        // Already expanded or collapsed, so noop
     885                        if ( inside.is( ':visible' ) === do_expand ) {
     886                                return;
     887                        }
     888
     889                        if ( do_expand ) {
     890                                // Close all other widget controls before expanding this one
     891                                api.control.each( function ( other_control ) {
     892                                        if ( control.params.type === other_control.params.type && control !== other_control ) {
     893                                                other_control.collapseForm();
     894                                        }
     895                                } );
     896
     897                                complete = function() {
     898                                        control.container.removeClass( 'expanding' );
     899                                        control.container.addClass( 'expanded' );
     900                                        control.container.trigger( 'expanded' );
     901                                };
     902                                if ( control.params.is_wide ) {
     903                                        inside.fadeIn( 'fast', complete );
     904                                } else {
     905                                        inside.slideDown( 'fast', complete );
     906                                }
     907                                control.container.trigger( 'expand' );
     908                                control.container.addClass( 'expanding' );
     909                        } else {
     910                                control.container.trigger( 'collapse' );
     911                                control.container.addClass( 'collapsing' );
     912                                complete = function() {
     913                                        control.container.removeClass( 'collapsing' );
     914                                        control.container.removeClass( 'expanded' );
     915                                        control.container.trigger( 'collapsed' );
     916                                };
     917                                if ( control.params.is_wide ) {
     918                                        inside.fadeOut( 'fast', complete );
     919                                } else {
     920                                        inside.slideUp( 'fast', function() {
     921                                                widget.css( { width:'', margin:'' } );
     922                                                complete();
     923                                        } );
     924                                }
     925                        }
     926                },
     927
     928                /**
     929                 * Expand the containing sidebar section, expand the form, and focus on
     930                 * the first input in the control
     931                 */
     932                focus: function() {
     933                        var control = this;
     934                        control.expandControlSection();
     935                        control.expandForm();
     936                        control.container.find( '.widget-content :focusable:first' ).focus();
     937                },
     938
     939                /**
     940                 * Get the position (index) of the widget in the containing sidebar
     941                 *
     942                 * @throws Error
     943                 * @returns {Number}
     944                 */
     945                getWidgetSidebarPosition: function() {
     946                        var control = this,
     947                                sidebar_widget_ids,
     948                                position;
     949
     950                        sidebar_widget_ids = control.getSidebarWidgetsControl().setting();
     951                        position = _.indexOf( sidebar_widget_ids, control.params.widget_id );
     952                        if ( position === -1 ) {
     953                                throw new Error( 'Widget was unexpectedly not present in the sidebar.' );
     954                        }
     955                        return position;
     956                },
     957
     958                /**
     959                 * Move widget up one in the sidebar
     960                 */
     961                moveUp: function() {
     962                        this._moveWidgetByOne( -1 );
     963                },
     964
     965                /**
     966                 * Move widget up one in the sidebar
     967                 */
     968                moveDown: function() {
     969                        this._moveWidgetByOne( 1 );
     970                },
     971
     972                /**
     973                 * @private
     974                 *
     975                 * @param {Number} offset 1|-1
     976                 */
     977                _moveWidgetByOne: function ( offset ) {
     978                        var control = this,
     979                                i,
     980                                sidebar_widgets_setting,
     981                                sidebar_widget_ids,
     982                                adjacent_widget_id;
     983
     984                        i = control.getWidgetSidebarPosition();
     985
     986                        sidebar_widgets_setting = control.getSidebarWidgetsControl().setting;
     987                        sidebar_widget_ids = Array.prototype.slice.call( sidebar_widgets_setting() ); // clone
     988                        adjacent_widget_id = sidebar_widget_ids[i + offset];
     989                        sidebar_widget_ids[i + offset] = control.params.widget_id;
     990                        sidebar_widget_ids[i] = adjacent_widget_id;
     991
     992                        sidebar_widgets_setting( sidebar_widget_ids );
     993                },
     994
     995                /**
     996                 * Toggle visibility of the widget move area
     997                 *
     998                 * @param {Boolean} [toggle]
     999                 */
     1000                toggleWidgetMoveArea: function ( toggle ) {
     1001                        var control = this, move_widget_area;
     1002                        move_widget_area = control.container.find( '.move-widget-area' );
     1003                        if ( typeof toggle === 'undefined' ) {
     1004                                toggle = ! move_widget_area.hasClass( 'active' );
     1005                        }
     1006                        if ( toggle ) {
     1007                                // reset the selected sidebar
     1008                                move_widget_area.find( '.selected' ).removeClass( 'selected' );
     1009                                move_widget_area.find( 'li' ).filter( function() {
     1010                                        return $( this ).data( 'id' ) === control.params.sidebar_id;
     1011                                } ).addClass( 'selected' );
     1012                                control.container.find( '.move-widget-btn' ).prop( 'disabled', true );
     1013                        }
     1014                        move_widget_area.toggleClass( 'active', toggle );
     1015                },
     1016
     1017                /**
     1018                 * Inside of the customizer preview, scroll the widget into view
     1019                 */
     1020                scrollPreviewWidgetIntoView: function() {
     1021                        // @todo scrollIntoView() provides a robust but very poor experience. Animation is needed. See https://github.com/x-team/wp-widget-customizer/issues/16
     1022                },
     1023
     1024                /**
     1025                 * Highlight the widget control and section
     1026                 */
     1027                highlightSectionAndControl: function() {
     1028                        var control = this, target_element;
     1029
     1030                        if ( control.container.is( ':hidden' ) ) {
     1031                                target_element = control.container.closest( '.control-section' );
     1032                        } else {
     1033                                target_element = control.container;
     1034                        }
     1035
     1036                        $( '.widget-customizer-highlighted' ).removeClass( 'widget-customizer-highlighted' );
     1037                        target_element.addClass( 'widget-customizer-highlighted' );
     1038                        setTimeout( function() {
     1039                                target_element.removeClass( 'widget-customizer-highlighted' );
     1040                        }, 500 );
     1041                }
     1042
    1751043        } );
    1761044
    1771045        /**
    1781046         * Sidebar Widgets control
    179          * Note that 'sidebar_widgets' must match the Sidebar_Widgets_WP_Customize_Control::$type
     1047         * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type
    1801048         */
    181         customize.controlConstructor.sidebar_widgets = customize.Control.extend( {
    182 
     1049        api.Widgets.SidebarControl = api.Control.extend({
    1831050                /**
    1841051                 * Set up the control
     
    1991066                _setupModel: function() {
    2001067                        var control = this,
    201                                 registered_sidebar = self.registered_sidebars.get( control.params.sidebar_id );
     1068                                registered_sidebar = api.Widgets.registeredSidebars.get( control.params.sidebar_id );
    2021069
    2031070                        control.setting.bind( function( new_widget_ids, old_widget_ids ) {
     
    2101077                                new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) {
    2111078                                        var parsed_widget_id = parse_widget_id( new_widget_id );
    212                                         return !! self.available_widgets.findWhere( { id_base: parsed_widget_id.id_base } );
     1079                                        return !! api.Widgets.availableWidgets.findWhere( { id_base: parsed_widget_id.id_base } );
    2131080                                } );
    2141081
    2151082                                widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) {
    216                                         var widget_form_control = self.getWidgetFormControlForWidget( widget_id );
     1083                                        var widget_form_control = api.Widgets.getWidgetFormControlForWidget( widget_id );
    2171084                                        if ( ! widget_form_control ) {
    2181085                                                widget_form_control = control.addWidget( widget_id );
     
    2511118
    2521119                                        // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
    253                                         setTimeout( function () {
     1120                                        setTimeout( function() {
    2541121                                                var is_present_in_another_sidebar = false,
    2551122                                                        removed_control,
     
    2601127
    2611128                                                // Check if the widget is in another sidebar
    262                                                 wp.customize.each( function ( other_setting ) {
     1129                                                api.each( function ( other_setting ) {
    2631130                                                        if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
    2641131                                                                return;
     
    2771144                                                }
    2781145
    279                                                 removed_control = self.getWidgetFormControlForWidget( removed_widget_id );
     1146                                                removed_control = api.Widgets.getWidgetFormControlForWidget( removed_widget_id );
    2801147
    2811148                                                // Detect if widget control was dragged to another sidebar
     
    2881155                                                // Delete any widget form controls for removed widgets
    2891156                                                if ( removed_control && ! was_dragged_to_another_sidebar ) {
    290                                                         wp.customize.control.remove( removed_control.id );
     1157                                                        api.control.remove( removed_control.id );
    2911158                                                        removed_control.container.remove();
    2921159                                                }
     
    2941161                                                // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved
    2951162                                                // This prevents the inactive widgets sidebar from overflowing with throwaway widgets
    296                                                 if ( self.saved_widget_ids[removed_widget_id] ) {
    297                                                         inactive_widgets = wp.customize.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice();
     1163                                                if ( api.Widgets.savedWidgetIds[removed_widget_id] ) {
     1164                                                        inactive_widgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice();
    2981165                                                        inactive_widgets.push( removed_widget_id );
    299                                                         wp.customize.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactive_widgets ).unique() );
     1166                                                        api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactive_widgets ).unique() );
    3001167                                                }
    3011168
    3021169                                                // Make old single widget available for adding again
    3031170                                                removed_id_base = parse_widget_id( removed_widget_id ).id_base;
    304                                                 widget = self.available_widgets.findWhere( { id_base: removed_id_base } );
     1171                                                widget = api.Widgets.availableWidgets.findWhere( { id_base: removed_id_base } );
    3051172                                                if ( widget && ! widget.get( 'is_multi' ) ) {
    3061173                                                        widget.set( 'is_disabled', false );
     
    3121179
    3131180                        // Update the model with whether or not the sidebar is rendered
    314                         self.previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) {
     1181                        api.Widgets.Previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) {
    3151182                                var is_rendered = !! rendered_sidebars[control.params.sidebar_id];
    3161183                                registered_sidebar.set( 'is_rendered', is_rendered );
     
    3221189                                section = $( section_selector );
    3231190                                if ( this.get( 'is_rendered' ) ) {
    324                                         section.stop().slideDown( function () {
     1191                                        section.stop().slideDown( function() {
    3251192                                                $( this ).css( 'height', 'auto' ); // so that the .accordion-section-content won't overflow
    3261193                                        } );
     
    3391206                 * Allow widgets in sidebar to be re-ordered, and for the order to be previewed
    3401207                 */
    341                 _setupSortable: function () {
     1208                _setupSortable: function() {
    3421209                        var control = this;
    3431210                        control.is_reordering = false;
     
    3511218                                axis: 'y',
    3521219                                connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
    353                                 update: function () {
     1220                                update: function() {
    3541221                                        var widget_container_ids = control.section_content.sortable( 'toArray' ), widget_ids;
    3551222                                        widget_ids = $.map( widget_container_ids, function ( widget_container_id ) {
     
    3661233                        control.control_section.find( '.accordion-section-title' ).droppable( {
    3671234                                accept: '.customize-control-widget_form',
    368                                 over: function () {
     1235                                over: function() {
    3691236                                        if ( ! control.control_section.hasClass( 'open' ) ) {
    3701237                                                control.control_section.addClass( 'open' );
    371                                                 control.section_content.toggle( false ).slideToggle( 150, function () {
     1238                                                control.section_content.toggle( false ).slideToggle( 150, function() {
    3721239                                                        control.section_content.sortable( 'refreshPositions' );
    3731240                                                } );
     
    3911258                 * Set up UI for adding a new widget
    3921259                 */
    393                 _setupAddition: function () {
     1260                _setupAddition: function() {
    3941261                        var control = this;
    3951262
     
    4051272                                // @todo Use an control.is_adding state
    4061273                                if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) {
    407                                         self.availableWidgetsPanel.open( control );
     1274                                        api.Widgets.availableWidgetsPanel.open( control );
    4081275                                } else {
    409                                         self.availableWidgetsPanel.close();
     1276                                        api.Widgets.availableWidgetsPanel.close();
    4101277                                }
    4111278                        } );
     
    4151282                 * Add classes to the widget_form controls to assist with styling
    4161283                 */
    417                 _applyCardinalOrderClassNames: function () {
     1284                _applyCardinalOrderClassNames: function() {
    4181285                        var control = this;
    4191286                        control.section_content.find( '.customize-control-widget_form' )
     
    4601327                 * @return {wp.customize.controlConstructor.widget_form[]}
    4611328                 */
    462                 getWidgetFormControls: function () {
     1329                getWidgetFormControls: function() {
    4631330                        var control = this, form_controls;
    4641331
    4651332                        form_controls = _( control.setting() ).map( function ( widget_id ) {
    4661333                                var setting_id = widget_id_to_setting_id( widget_id ),
    467                                         form_control = customize.control( setting_id );
     1334                                        form_control = api.control( setting_id );
    4681335
    4691336                                if ( ! form_control ) {
     
    4881355                                widget_number = parsed_widget_id.number,
    4891356                                widget_id_base = parsed_widget_id.id_base,
    490                                 widget = self.available_widgets.findWhere( {id_base: widget_id_base} ),
     1357                                widget = api.Widgets.availableWidgets.findWhere( {id_base: widget_id_base} ),
    4911358                                setting_id,
    4921359                                is_existing_widget,
     
    5401407
    5411408                        // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget)
    542                         is_existing_widget = wp.customize.has( setting_id );
     1409                        is_existing_widget = api.has( setting_id );
    5431410                        if ( ! is_existing_widget ) {
    5441411                                setting_args = {
     
    5461413                                        previewer: control.setting.previewer
    5471414                                };
    548                                 wp.customize.create( setting_id, setting_id, {}, setting_args );
    549                         }
    550 
    551                         Constructor = wp.customize.controlConstructor[customize_control_type];
     1415                                api.create( setting_id, setting_id, {}, setting_args );
     1416                        }
     1417
     1418                        Constructor = api.controlConstructor[customize_control_type];
    5521419                        widget_form_control = new Constructor( setting_id, {
    5531420                                params: {
     
    5661433                                previewer: control.setting.previewer
    5671434                        } );
    568                         wp.customize.control.add( setting_id, widget_form_control );
     1435                        api.control.add( setting_id, widget_form_control );
    5691436
    5701437                        // Make sure widget is removed from the other sidebars
    571                         wp.customize.each( function ( other_setting ) {
     1438                        api.each( function ( other_setting ) {
    5721439                                if ( other_setting.id === control.setting.id ) {
    5731440                                        return;
     
    5911458                        }
    5921459
    593                         customize_control.slideDown( function () {
     1460                        customize_control.slideDown( function() {
    5941461                                if ( is_existing_widget ) {
    5951462                                        widget_form_control.expandForm();
     
    6151482        } );
    6161483
    617         /**
    618          * Widget Form control
    619          * Note that 'widget_form' must match the Widget_Form_WP_Customize_Control::$type
    620          */
    621         customize.controlConstructor.widget_form = customize.Control.extend( {
    622 
    623                 /**
    624                  * Set up the control
    625                  */
    626                 ready: function() {
    627                         var control = this;
    628                         control._setupModel();
    629                         control._setupWideWidget();
    630                         control._setupControlToggle();
    631                         control._setupWidgetTitle();
    632                         control._setupReorderUI();
    633                         control._setupHighlightEffects();
    634                         control._setupUpdateUI();
    635                         control._setupRemoveUI();
    636                 },
    637 
    638                 /**
    639                  * Handle changes to the setting
    640                  */
    641                 _setupModel: function () {
    642                         var control = this, remember_saved_widget_id;
    643 
    644                         // Remember saved widgets so we know which to trash (move to inactive widgets sidebar)
    645                         remember_saved_widget_id = function () {
    646                                 self.saved_widget_ids[control.params.widget_id] = true;
    647                         };
    648                         wp.customize.bind( 'ready', remember_saved_widget_id );
    649                         wp.customize.bind( 'saved', remember_saved_widget_id );
    650 
    651                         control._update_count = 0;
    652                         control.is_widget_updating = false;
    653                         control.live_update_mode = true;
    654 
    655                         // Update widget whenever model changes
    656                         control.setting.bind( function( to, from ) {
    657                                 if ( ! _( from ).isEqual( to ) && ! control.is_widget_updating ) {
    658                                         control.updateWidget( { instance: to } );
    659                                 }
    660                         } );
    661                 },
    662 
    663                 /**
    664                  * Add special behaviors for wide widget controls
    665                  */
    666                 _setupWideWidget: function () {
    667                         var control = this,
    668                                 widget_inside,
    669                                 widget_form,
    670                                 customize_sidebar,
    671                                 position_widget,
    672                                 theme_controls_container;
    673 
    674                         if ( ! control.params.is_wide ) {
    675                                 return;
    676                         }
    677 
    678                         widget_inside = control.container.find( '.widget-inside' );
    679                         widget_form = widget_inside.find( '> .form' );
    680                         customize_sidebar = $( '.wp-full-overlay-sidebar-content:first' );
    681                         control.container.addClass( 'wide-widget-control' );
    682 
    683                         control.container.find( '.widget-content:first' ).css( {
    684                                 'max-width': control.params.width,
    685                                 'min-height': control.params.height
    686                         } );
    687 
    688                         /**
    689                          * Keep the widget-inside positioned so the top of fixed-positioned
    690                          * element is at the same top position as the widget-top. When the
    691                          * widget-top is scrolled out of view, keep the widget-top in view;
    692                          * likewise, don't allow the widget to drop off the bottom of the window.
    693                          * If a widget is too tall to fit in the window, don't let the height
    694                          * exceed the window height so that the contents of the widget control
    695                          * will become scrollable (overflow:auto).
    696                          */
    697                         position_widget = function () {
    698                                 var offset_top = control.container.offset().top,
    699                                         window_height = $( window ).height(),
    700                                         form_height = widget_form.outerHeight(),
    701                                         top;
    702                                 widget_inside.css( 'max-height', window_height );
    703                                 top = Math.max(
    704                                         0, // prevent top from going off screen
    705                                         Math.min(
    706                                                 Math.max( offset_top, 0 ), // distance widget in panel is from top of screen
    707                                                 window_height - form_height // flush up against bottom of screen
    708                                         )
    709                                 );
    710                                 widget_inside.css( 'top', top );
    711                         };
    712 
    713                         theme_controls_container = $( '#customize-theme-controls' );
    714                         control.container.on( 'expand', function () {
    715                                 position_widget();
    716                                 customize_sidebar.on( 'scroll', position_widget );
    717                                 $( window ).on( 'resize', position_widget );
    718                                 theme_controls_container.on( 'expanded collapsed', position_widget );
    719                         } );
    720                         control.container.on( 'collapsed', function () {
    721                                 customize_sidebar.off( 'scroll', position_widget );
    722                                 $( window ).off( 'resize', position_widget );
    723                                 theme_controls_container.off( 'expanded collapsed', position_widget );
    724                         } );
    725 
    726                         // Reposition whenever a sidebar's widgets are changed
    727                         wp.customize.each( function ( setting ) {
    728                                 if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
    729                                         setting.bind( function () {
    730                                                 if ( control.container.hasClass( 'expanded' ) ) {
    731                                                         position_widget();
    732                                                 }
    733                                         } );
    734                                 }
    735                         } );
    736                 },
    737 
    738                 /**
    739                  * Show/hide the control when clicking on the form title, when clicking
    740                  * the close button
    741                  */
    742                 _setupControlToggle: function() {
    743                         var control = this, close_btn;
    744 
    745                         control.container.find( '.widget-top' ).on( 'click', function ( e ) {
    746                                 e.preventDefault();
    747                                 var sidebar_widgets_control = control.getSidebarWidgetsControl();
    748                                 if ( sidebar_widgets_control.is_reordering ) {
    749                                         return;
    750                                 }
    751                                 control.toggleForm();
    752                         } );
    753 
    754                         close_btn = control.container.find( '.widget-control-close' );
    755                         // @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
    756                         close_btn.on( 'click', function ( e ) {
    757                                 e.preventDefault();
    758                                 control.collapseForm();
    759                                 control.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility
    760                         } );
    761                 },
    762 
    763                 /**
    764                  * Update the title of the form if a title field is entered
    765                  */
    766                 _setupWidgetTitle: function () {
    767                         var control = this, update_title;
    768 
    769                         update_title = function () {
    770                                 var title = control.setting().title,
    771                                         in_widget_title = control.container.find( '.in-widget-title' );
    772 
    773                                 if ( title ) {
    774                                         in_widget_title.text( ': ' + title );
    775                                 } else {
    776                                         in_widget_title.text( '' );
    777                                 }
    778                         };
    779                         control.setting.bind( update_title );
    780                         update_title();
    781                 },
    782 
    783                 /**
    784                  * Set up the widget-reorder-nav
    785                  */
    786                 _setupReorderUI: function () {
    787                         var control = this,
    788                                 select_sidebar_item,
    789                                 move_widget_area,
    790                                 reorder_nav,
    791                                 update_available_sidebars;
    792 
    793                         /**
    794                          * select the provided sidebar list item in the move widget area
    795                          *
    796                          * @param {jQuery} li
    797                          */
    798                         select_sidebar_item = function ( li ) {
    799                                 li.siblings( '.selected' ).removeClass( 'selected' );
    800                                 li.addClass( 'selected' );
    801                                 var is_self_sidebar = ( li.data( 'id' ) === control.params.sidebar_id );
    802                                 control.container.find( '.move-widget-btn' ).prop( 'disabled', is_self_sidebar );
    803                         };
    804 
    805                         /**
    806                          * Add the widget reordering elements to the widget control
    807                          */
    808                         control.container.find( '.widget-title-action' ).after( $( self.tpl.widget_reorder_nav ) );
    809                         move_widget_area = $(
    810                                 _.template( self.tpl.move_widget_area, {
    811                                         sidebars: _( self.registered_sidebars.toArray() ).pluck( 'attributes' )
    812                                 } )
    813                         );
    814                         control.container.find( '.widget-top' ).after( move_widget_area );
    815 
    816                         /**
    817                          * Update available sidebars when their rendered state changes
    818                          */
    819                         update_available_sidebars = function () {
    820                                 var sidebar_items = move_widget_area.find( 'li' ), self_sidebar_item;
    821                                 self_sidebar_item = sidebar_items.filter( function(){
    822                                         return $( this ).data( 'id' ) === control.params.sidebar_id;
    823                                 } );
    824                                 sidebar_items.each( function () {
    825                                         var li = $( this ),
    826                                                 sidebar_id,
    827                                                 sidebar_model;
    828 
    829                                         sidebar_id = li.data( 'id' );
    830                                         sidebar_model = self.registered_sidebars.get( sidebar_id );
    831                                         li.toggle( sidebar_model.get( 'is_rendered' ) );
    832                                         if ( li.hasClass( 'selected' ) && ! sidebar_model.get( 'is_rendered' ) ) {
    833                                                 select_sidebar_item( self_sidebar_item );
    834                                         }
    835                                 } );
    836                         };
    837                         update_available_sidebars();
    838                         self.registered_sidebars.on( 'change:is_rendered', update_available_sidebars );
    839 
    840                         /**
    841                          * Handle clicks for up/down/move on the reorder nav
    842                          */
    843                         reorder_nav = control.container.find( '.widget-reorder-nav' );
    844                         reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function ( event ) {
    845                                 if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
    846                                         return;
    847                                 }
    848                                 $( this ).focus();
    849 
    850                                 if ( $( this ).is( '.move-widget' ) ) {
    851                                         control.toggleWidgetMoveArea();
    852                                 } else {
    853                                         var is_move_down = $( this ).is( '.move-widget-down' ),
    854                                                 is_move_up = $( this ).is( '.move-widget-up' ),
    855                                                 i = control.getWidgetSidebarPosition();
    856 
    857                                         if ( ( is_move_up && i === 0 ) || ( is_move_down && i === control.getSidebarWidgetsControl().setting().length - 1 ) ) {
    858                                                 return;
    859                                         }
    860 
    861                                         if ( is_move_up ) {
    862                                                 control.moveUp();
    863                                         } else {
    864                                                 control.moveDown();
    865                                         }
    866 
    867                                         $( this ).focus(); // re-focus after the container was moved
    868                                 }
    869                         } );
    870 
    871                         /**
    872                          * Handle selecting a sidebar to move to
    873                          */
    874                         control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function ( e ) {
    875                                 if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
    876                                         return;
    877                                 }
    878                                 e.preventDefault();
    879                                 select_sidebar_item( $( this ) );
    880                         } );
    881 
    882                         /**
    883                          * Move widget to another sidebar
    884                          */
    885                         control.container.find( '.move-widget-btn' ).click( function () {
    886                                 control.getSidebarWidgetsControl().toggleReordering( false );
    887 
    888                                 var old_sidebar_id = control.params.sidebar_id,
    889                                         new_sidebar_id = control.container.find( '.widget-area-select li.selected' ).data( 'id' ),
    890                                         old_sidebar_widgets_setting,
    891                                         new_sidebar_widgets_setting,
    892                                         old_sidebar_widget_ids,
    893                                         new_sidebar_widget_ids,
    894                                         i;
    895 
    896                                 old_sidebar_widgets_setting = customize( 'sidebars_widgets[' + old_sidebar_id + ']' );
    897                                 new_sidebar_widgets_setting = customize( 'sidebars_widgets[' + new_sidebar_id + ']' );
    898                                 old_sidebar_widget_ids = Array.prototype.slice.call( old_sidebar_widgets_setting() );
    899                                 new_sidebar_widget_ids = Array.prototype.slice.call( new_sidebar_widgets_setting() );
    900 
    901                                 i = control.getWidgetSidebarPosition();
    902                                 old_sidebar_widget_ids.splice( i, 1 );
    903                                 new_sidebar_widget_ids.push( control.params.widget_id );
    904 
    905                                 old_sidebar_widgets_setting( old_sidebar_widget_ids );
    906                                 new_sidebar_widgets_setting( new_sidebar_widget_ids );
    907 
    908                                 control.focus();
    909                         } );
    910                 },
    911 
    912                 /**
    913                  * Highlight widgets in preview when interacted with in the customizer
    914                  */
    915                 _setupHighlightEffects: function() {
    916                         var control = this;
    917 
    918                         // Highlight whenever hovering or clicking over the form
    919                         control.container.on( 'mouseenter click', function () {
    920                                 control.setting.previewer.send( 'highlight-widget', control.params.widget_id );
    921                         } );
    922 
    923                         // Highlight when the setting is updated
    924                         control.setting.bind( function () {
    925                                 control.setting.previewer.send( 'highlight-widget', control.params.widget_id );
    926                         } );
    927 
    928                         // Highlight when the widget form is expanded
    929                         control.container.on( 'expand', function () {
    930                                 control.scrollPreviewWidgetIntoView();
    931                         } );
    932                 },
    933 
    934                 /**
    935                  * Set up event handlers for widget updating
    936                  */
    937                 _setupUpdateUI: function () {
    938                         var control = this,
    939                                 widget_root,
    940                                 widget_content,
    941                                 save_btn,
    942                                 update_widget_debounced,
    943                                 form_update_event_handler;
    944 
    945                         widget_root = control.container.find( '.widget:first' );
    946                         widget_content = widget_root.find( '.widget-content:first' );
    947 
    948                         // Configure update button
    949                         save_btn = control.container.find( '.widget-control-save' );
    950                         save_btn.val( self.i18n.save_btn_label );
    951                         save_btn.attr( 'title', self.i18n.save_btn_tooltip );
    952                         save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' );
    953                         save_btn.on( 'click', function ( e ) {
    954                                 e.preventDefault();
    955                                 control.updateWidget( { disable_form: true } );
    956                         } );
    957 
    958                         update_widget_debounced = _.debounce( function () {
    959                                 // @todo For compatibility with other plugins, should we trigger a click event? What about form submit event?
    960                                 control.updateWidget();
    961                         }, 250 );
    962 
    963                         // Trigger widget form update when hitting Enter within an input
    964                         control.container.find( '.widget-content' ).on( 'keydown', 'input', function( e ) {
    965                                 if ( 13 === e.which ) { // Enter
    966                                         e.preventDefault();
    967                                         control.updateWidget( { ignore_active_element: true } );
    968                                 }
    969                         } );
    970 
    971                         // Handle widgets that support live previews
    972                         widget_content.on( 'change input propertychange', ':input', function ( e ) {
    973                                 if ( control.live_update_mode ) {
    974                                         if ( e.type === 'change' ) {
    975                                                 control.updateWidget();
    976                                         } else if ( this.checkValidity && this.checkValidity() ) {
    977                                                 update_widget_debounced();
    978                                         }
    979                                 }
    980                         } );
    981 
    982                         // Remove loading indicators when the setting is saved and the preview updates
    983                         control.setting.previewer.channel.bind( 'synced', function () {
    984                                 control.container.removeClass( 'previewer-loading' );
    985                         } );
    986                         self.previewer.bind( 'widget-updated', function ( updated_widget_id ) {
    987                                 if ( updated_widget_id === control.params.widget_id ) {
    988                                         control.container.removeClass( 'previewer-loading' );
    989                                 }
    990                         } );
    991 
    992                         // Update widget control to indicate whether it is currently rendered (cf. Widget Visibility)
    993                         self.previewer.bind( 'rendered-widgets', function ( rendered_widgets ) {
    994                                 var is_rendered = !! rendered_widgets[control.params.widget_id];
    995                                 control.container.toggleClass( 'widget-rendered', is_rendered );
    996                         } );
    997 
    998                         form_update_event_handler = builtin_form_sync_handlers[ control.params.widget_id_base ];
    999                         if ( form_update_event_handler ) {
    1000                                 $( document ).on( 'widget-synced', function ( e, widget_el ) {
    1001                                         if ( widget_root.is( widget_el ) ) {
    1002                                                 form_update_event_handler.apply( document, arguments );
    1003                                         }
    1004                                 } );
    1005                         }
    1006                 },
    1007 
    1008                 /**
    1009                  * Set up event handlers for widget removal
    1010                  */
    1011                 _setupRemoveUI: function () {
    1012                         var control = this,
    1013                                 remove_btn,
    1014                                 replace_delete_with_remove;
    1015 
    1016                         // Configure remove button
    1017                         remove_btn = control.container.find( 'a.widget-control-remove' );
    1018                         // @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
    1019                         remove_btn.on( 'click', function ( e ) {
    1020                                 e.preventDefault();
    1021 
    1022                                 // Find an adjacent element to add focus to when this widget goes away
    1023                                 var adjacent_focus_target;
    1024                                 if ( control.container.next().is( '.customize-control-widget_form' ) ) {
    1025                                         adjacent_focus_target = control.container.next().find( '.widget-action:first' );
    1026                                 } else if ( control.container.prev().is( '.customize-control-widget_form' ) ) {
    1027                                         adjacent_focus_target = control.container.prev().find( '.widget-action:first' );
    1028                                 } else {
    1029                                         adjacent_focus_target = control.container.next( '.customize-control-sidebar_widgets' ).find( '.add-new-widget:first' );
    1030                                 }
    1031 
    1032                                 control.container.slideUp( function() {
    1033                                         var sidebars_widgets_control = self.getSidebarWidgetControlContainingWidget( control.params.widget_id ),
    1034                                                 sidebar_widget_ids,
    1035                                                 i;
    1036 
    1037                                         if ( ! sidebars_widgets_control ) {
    1038                                                 throw new Error( 'Unable to find sidebars_widgets_control' );
    1039                                         }
    1040                                         sidebar_widget_ids = sidebars_widgets_control.setting().slice();
    1041                                         i = _.indexOf( sidebar_widget_ids, control.params.widget_id );
    1042                                         if ( -1 === i ) {
    1043                                                 throw new Error( 'Widget is not in sidebar' );
    1044                                         }
    1045                                         sidebar_widget_ids.splice( i, 1 );
    1046                                         sidebars_widgets_control.setting( sidebar_widget_ids );
    1047                                         adjacent_focus_target.focus(); // keyboard accessibility
    1048                                 } );
    1049                         } );
    1050 
    1051                         replace_delete_with_remove = function () {
    1052                                 remove_btn.text( self.i18n.remove_btn_label ); // wp_widget_control() outputs the link as "Delete"
    1053                                 remove_btn.attr( 'title', self.i18n.remove_btn_tooltip );
    1054                         };
    1055                         if ( control.params.is_new ) {
    1056                                 wp.customize.bind( 'saved', replace_delete_with_remove );
    1057                         } else {
    1058                                 replace_delete_with_remove();
    1059                         }
    1060                 },
    1061 
    1062                 /**
    1063                  * Find all inputs in a widget container that should be considered when
    1064                  * comparing the loaded form with the sanitized form, whose fields will
    1065                  * be aligned to copy the sanitized over. The elements returned by this
    1066                  * are passed into this._getInputsSignature(), and they are iterated
    1067                  * over when copying sanitized values over to the the form loaded.
    1068                  *
    1069                  * @param {jQuery} container element in which to look for inputs
    1070                  * @returns {jQuery} inputs
    1071                  * @private
    1072                  */
    1073                 _getInputs: function ( container ) {
    1074                         return $( container ).find( ':input[name]' );
    1075                 },
    1076 
    1077                 /**
    1078                  * Iterate over supplied inputs and create a signature string for all of them together.
    1079                  * This string can be used to compare whether or not the form has all of the same fields.
    1080                  *
    1081                  * @param {jQuery} inputs
    1082                  * @returns {string}
    1083                  * @private
    1084                  */
    1085                 _getInputsSignature: function ( inputs ) {
    1086                         var inputs_signatures = _( inputs ).map( function ( input ) {
    1087                                 input = $( input );
    1088                                 var signature_parts;
    1089                                 if ( input.is( ':checkbox, :radio' ) ) {
    1090                                         signature_parts = [ input.attr( 'id' ), input.attr( 'name' ), input.prop( 'value' ) ];
    1091                                 } else {
    1092                                         signature_parts = [ input.attr( 'id' ), input.attr( 'name' ) ];
    1093                                 }
    1094                                 return signature_parts.join( ',' );
    1095                         } );
    1096                         return inputs_signatures.join( ';' );
    1097                 },
    1098 
    1099                 /**
    1100                  * Get the property that represents the state of an input.
    1101                  *
    1102                  * @param {jQuery|DOMElement} input
    1103                  * @returns {string}
    1104                  * @private
    1105                  */
    1106                 _getInputStatePropertyName: function ( input ) {
    1107                         input = $( input );
    1108                         if ( input.is( ':radio, :checkbox' ) ) {
    1109                                 return 'checked';
    1110                         } else {
    1111                                 return 'value';
    1112                         }
    1113                 },
    1114 
    1115                 /***********************************************************************
    1116                  * Begin public API methods
    1117                  **********************************************************************/
    1118 
    1119                 /**
    1120                  * @return {wp.customize.controlConstructor.sidebar_widgets[]}
    1121                  */
    1122                 getSidebarWidgetsControl: function () {
    1123                         var control = this, setting_id, sidebar_widgets_control;
    1124 
    1125                         setting_id = 'sidebars_widgets[' + control.params.sidebar_id + ']';
    1126                         sidebar_widgets_control = customize.control( setting_id );
    1127                         if ( ! sidebar_widgets_control ) {
    1128                                 throw new Error( 'Unable to locate sidebar_widgets control for ' + control.params.sidebar_id );
    1129                         }
    1130                         return sidebar_widgets_control;
    1131                 },
    1132 
    1133                 /**
    1134                  * Submit the widget form via Ajax and get back the updated instance,
    1135                  * along with the new widget control form to render.
    1136                  *
    1137                  * @param {object} [args]
    1138                  * @param {Object|null} [args.instance=null]  When the model changes, the instance is sent here; otherwise, the inputs from the form are used
    1139                  * @param {Function|null} [args.complete=null]  Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success.
    1140                  * @param {Boolean} [args.ignore_active_element=false] Whether or not updating a field will be deferred if focus is still on the element.
    1141                  */
    1142                 updateWidget: function ( args ) {
    1143                         var control = this,
    1144                                 instance_override,
    1145                                 complete_callback,
    1146                                 widget_root,
    1147                                 update_number,
    1148                                 widget_content,
    1149                                 params,
    1150                                 data,
    1151                                 inputs,
    1152                                 processing,
    1153                                 jqxhr,
    1154                                 is_changed;
    1155 
    1156                         args = $.extend( {
    1157                                 instance: null,
    1158                                 complete: null,
    1159                                 ignore_active_element: false
    1160                         }, args );
    1161 
    1162                         instance_override = args.instance;
    1163                         complete_callback = args.complete;
    1164 
    1165                         control._update_count += 1;
    1166                         update_number = control._update_count;
    1167 
    1168                         widget_root = control.container.find( '.widget:first' );
    1169                         widget_content = widget_root.find( '.widget-content:first' );
    1170 
    1171                         // Remove a previous error message
    1172                         widget_content.find( '.widget-error' ).remove();
    1173 
    1174                         control.container.addClass( 'widget-form-loading' );
    1175                         control.container.addClass( 'previewer-loading' );
    1176                         processing = wp.customize.state( 'processing' );
    1177                         processing( processing() + 1 );
    1178 
    1179                         if ( ! control.live_update_mode ) {
    1180                                 control.container.addClass( 'widget-form-disabled' );
    1181                         }
    1182 
    1183                         params = {};
    1184                         params.action = 'update-widget';
    1185                         params.wp_customize = 'on';
    1186                         params.nonce = self.nonce;
    1187 
    1188                         data = $.param( params );
    1189                         inputs = control._getInputs( widget_content );
    1190 
    1191                         // Store the value we're submitting in data so that when the response comes back,
    1192                         // we know if it got sanitized; if there is no difference in the sanitized value,
    1193                         // then we do not need to touch the UI and mess up the user's ongoing editing.
    1194                         inputs.each( function () {
    1195                                 var input = $( this ),
    1196                                         property = control._getInputStatePropertyName( this );
    1197                                 input.data( 'state' + update_number, input.prop( property ) );
    1198                         } );
    1199 
    1200                         if ( instance_override ) {
    1201                                 data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instance_override ) } );
    1202                         } else {
    1203                                 data += '&' + inputs.serialize();
    1204                         }
    1205                         data += '&' + widget_content.find( '~ :input' ).serialize();
    1206 
    1207                         jqxhr = $.post( wp.ajax.settings.url, data, function ( r ) {
    1208                                 var message,
    1209                                         sanitized_form,
    1210                                         sanitized_inputs,
    1211                                         has_same_inputs_in_response,
    1212                                         is_live_update_aborted = false;
    1213 
    1214                                 // Check if the user is logged out.
    1215                                 if ( '0' === r ) {
    1216                                         self.previewer.preview.iframe.hide();
    1217                                         self.previewer.login().done( function() {
    1218                                                 control.updateWidget( args );
    1219                                                 self.previewer.preview.iframe.show();
    1220                                         } );
    1221                                         return;
    1222                                 }
    1223 
    1224                                 // Check for cheaters.
    1225                                 if ( '-1' === r ) {
    1226                                         self.previewer.cheatin();
    1227                                         return;
    1228                                 }
    1229 
    1230                                 if ( r.success ) {
    1231                                         sanitized_form = $( '<div>' + r.data.form + '</div>' );
    1232                                         sanitized_inputs = control._getInputs( sanitized_form );
    1233                                         has_same_inputs_in_response = control._getInputsSignature( inputs ) === control._getInputsSignature( sanitized_inputs );
    1234 
    1235                                         // Restore live update mode if sanitized fields are now aligned with the existing fields
    1236                                         if ( has_same_inputs_in_response && ! control.live_update_mode ) {
    1237                                                 control.live_update_mode = true;
    1238                                                 control.container.removeClass( 'widget-form-disabled' );
    1239                                                 control.container.find( 'input[name="savewidget"]' ).hide();
    1240                                         }
    1241 
    1242                                         // Sync sanitized field states to existing fields if they are aligned
    1243                                         if ( has_same_inputs_in_response && control.live_update_mode ) {
    1244                                                 inputs.each( function ( i ) {
    1245                                                         var input = $( this ),
    1246                                                                 sanitized_input = $( sanitized_inputs[i] ),
    1247                                                                 property = control._getInputStatePropertyName( this ),
    1248                                                                 submitted_state,
    1249                                                                 sanitized_state,
    1250                                                                 can_update_state;
    1251 
    1252                                                         submitted_state = input.data( 'state' + update_number );
    1253                                                         sanitized_state = sanitized_input.prop( property );
    1254                                                         input.data( 'sanitized', sanitized_state );
    1255 
    1256                                                         can_update_state = (
    1257                                                                 submitted_state !== sanitized_state &&
    1258                                                                 ( args.ignore_active_element || ! input.is( document.activeElement ) )
    1259                                                         );
    1260                                                         if ( can_update_state ) {
    1261                                                                 input.prop( property, sanitized_state );
    1262                                                         }
    1263                                                 } );
    1264                                                 $( document ).trigger( 'widget-synced', [ widget_root, r.data.form ] );
    1265 
    1266                                         // Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled
    1267                                         } else if ( control.live_update_mode ) {
    1268                                                 control.live_update_mode = false;
    1269                                                 control.container.find( 'input[name="savewidget"]' ).show();
    1270                                                 is_live_update_aborted = true;
    1271                                         // Otherwise, replace existing form with the sanitized form
    1272                                         } else {
    1273                                                 widget_content.html( r.data.form );
    1274                                                 control.container.removeClass( 'widget-form-disabled' );
    1275                                                 $( document ).trigger( 'widget-updated', [ widget_root ] );
    1276                                         }
    1277 
    1278                                         /**
    1279                                          * If the old instance is identical to the new one, there is nothing new
    1280                                          * needing to be rendered, and so we can preempt the event for the
    1281                                          * preview finishing loading.
    1282                                          */
    1283                                         is_changed = ! is_live_update_aborted && ! _( control.setting() ).isEqual( r.data.instance );
    1284                                         if ( is_changed ) {
    1285                                                 control.is_widget_updating = true; // suppress triggering another updateWidget
    1286                                                 control.setting( r.data.instance );
    1287                                                 control.is_widget_updating = false;
    1288                                         } else {
    1289                                                 // no change was made, so stop the spinner now instead of when the preview would updates
    1290                                                 control.container.removeClass( 'previewer-loading' );
    1291                                         }
    1292 
    1293                                         if ( complete_callback ) {
    1294                                                 complete_callback.call( control, null, { no_change: ! is_changed, ajax_finished: true } );
    1295                                         }
    1296                                 } else {
    1297                                         message = self.i18n.error;
    1298                                         if ( r.data && r.data.message ) {
    1299                                                 message = r.data.message;
    1300                                         }
    1301                                         if ( complete_callback ) {
    1302                                                 complete_callback.call( control, message );
    1303                                         } else {
    1304                                                 widget_content.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' );
    1305                                         }
    1306                                 }
    1307                         } );
    1308                         jqxhr.fail( function ( jqXHR, textStatus ) {
    1309                                 if ( complete_callback ) {
    1310                                         complete_callback.call( control, textStatus );
    1311                                 }
    1312                         } );
    1313                         jqxhr.always( function () {
    1314                                 control.container.removeClass( 'widget-form-loading' );
    1315                                 inputs.each( function () {
    1316                                         $( this ).removeData( 'state' + update_number );
    1317                                 } );
    1318 
    1319                                 processing( processing() - 1 );
    1320                         } );
    1321                 },
    1322 
    1323                 /**
    1324                  * Expand the accordion section containing a control
    1325                  * @todo it would be nice if accordion had a proper API instead of having to trigger UI events on its elements
    1326                  */
    1327                 expandControlSection: function () {
    1328                         var section = this.container.closest( '.accordion-section' );
    1329                         if ( ! section.hasClass( 'open' ) ) {
    1330                                 section.find( '.accordion-section-title:first' ).trigger( 'click' );
    1331                         }
    1332                 },
    1333 
    1334                 /**
    1335                  * Expand the widget form control
    1336                  */
    1337                 expandForm: function () {
    1338                         this.toggleForm( true );
    1339                 },
    1340 
    1341                 /**
    1342                  * Collapse the widget form control
    1343                  */
    1344                 collapseForm: function () {
    1345                         this.toggleForm( false );
    1346                 },
    1347 
    1348                 /**
    1349                  * Expand or collapse the widget control
    1350                  *
    1351                  * @param {boolean|undefined} [do_expand] If not supplied, will be inverse of current visibility
    1352                  */
    1353                 toggleForm: function ( do_expand ) {
    1354                         var control = this, widget, inside, complete;
    1355 
    1356                         widget = control.container.find( 'div.widget:first' );
    1357                         inside = widget.find( '.widget-inside:first' );
    1358                         if ( typeof do_expand === 'undefined' ) {
    1359                                 do_expand = ! inside.is( ':visible' );
    1360                         }
    1361 
    1362                         // Already expanded or collapsed, so noop
    1363                         if ( inside.is( ':visible' ) === do_expand ) {
    1364                                 return;
    1365                         }
    1366 
    1367                         if ( do_expand ) {
    1368                                 // Close all other widget controls before expanding this one
    1369                                 wp.customize.control.each( function ( other_control ) {
    1370                                         if ( control.params.type === other_control.params.type && control !== other_control ) {
    1371                                                 other_control.collapseForm();
    1372                                         }
    1373                                 } );
    1374 
    1375                                 complete = function () {
    1376                                         control.container.removeClass( 'expanding' );
    1377                                         control.container.addClass( 'expanded' );
    1378                                         control.container.trigger( 'expanded' );
    1379                                 };
    1380                                 if ( control.params.is_wide ) {
    1381                                         inside.fadeIn( 'fast', complete );
    1382                                 } else {
    1383                                         inside.slideDown( 'fast', complete );
    1384                                 }
    1385                                 control.container.trigger( 'expand' );
    1386                                 control.container.addClass( 'expanding' );
    1387                         } else {
    1388                                 control.container.trigger( 'collapse' );
    1389                                 control.container.addClass( 'collapsing' );
    1390                                 complete = function () {
    1391                                         control.container.removeClass( 'collapsing' );
    1392                                         control.container.removeClass( 'expanded' );
    1393                                         control.container.trigger( 'collapsed' );
    1394                                 };
    1395                                 if ( control.params.is_wide ) {
    1396                                         inside.fadeOut( 'fast', complete );
    1397                                 } else {
    1398                                         inside.slideUp( 'fast', function() {
    1399                                                 widget.css( { width:'', margin:'' } );
    1400                                                 complete();
    1401                                         } );
    1402                                 }
    1403                         }
    1404                 },
    1405 
    1406                 /**
    1407                  * Expand the containing sidebar section, expand the form, and focus on
    1408                  * the first input in the control
    1409                  */
    1410                 focus: function () {
    1411                         var control = this;
    1412                         control.expandControlSection();
    1413                         control.expandForm();
    1414                         control.container.find( '.widget-content :focusable:first' ).focus();
    1415                 },
    1416 
    1417                 /**
    1418                  * Get the position (index) of the widget in the containing sidebar
    1419                  *
    1420                  * @throws Error
    1421                  * @returns {Number}
    1422                  */
    1423                 getWidgetSidebarPosition: function () {
    1424                         var control = this,
    1425                                 sidebar_widget_ids,
    1426                                 position;
    1427 
    1428                         sidebar_widget_ids = control.getSidebarWidgetsControl().setting();
    1429                         position = _.indexOf( sidebar_widget_ids, control.params.widget_id );
    1430                         if ( position === -1 ) {
    1431                                 throw new Error( 'Widget was unexpectedly not present in the sidebar.' );
    1432                         }
    1433                         return position;
    1434                 },
    1435 
    1436                 /**
    1437                  * Move widget up one in the sidebar
    1438                  */
    1439                 moveUp: function () {
    1440                         this._moveWidgetByOne( -1 );
    1441                 },
    1442 
    1443                 /**
    1444                  * Move widget up one in the sidebar
    1445                  */
    1446                 moveDown: function () {
    1447                         this._moveWidgetByOne( 1 );
    1448                 },
    1449 
    1450                 /**
    1451                  * @private
    1452                  *
    1453                  * @param {Number} offset 1|-1
    1454                  */
    1455                 _moveWidgetByOne: function ( offset ) {
    1456                         var control = this,
    1457                                 i,
    1458                                 sidebar_widgets_setting,
    1459                                 sidebar_widget_ids,
    1460                                 adjacent_widget_id;
    1461 
    1462                         i = control.getWidgetSidebarPosition();
    1463 
    1464                         sidebar_widgets_setting = control.getSidebarWidgetsControl().setting;
    1465                         sidebar_widget_ids = Array.prototype.slice.call( sidebar_widgets_setting() ); // clone
    1466                         adjacent_widget_id = sidebar_widget_ids[i + offset];
    1467                         sidebar_widget_ids[i + offset] = control.params.widget_id;
    1468                         sidebar_widget_ids[i] = adjacent_widget_id;
    1469 
    1470                         sidebar_widgets_setting( sidebar_widget_ids );
    1471                 },
    1472 
    1473                 /**
    1474                  * Toggle visibility of the widget move area
    1475                  *
    1476                  * @param {Boolean} [toggle]
    1477                  */
    1478                 toggleWidgetMoveArea: function ( toggle ) {
    1479                         var control = this, move_widget_area;
    1480                         move_widget_area = control.container.find( '.move-widget-area' );
    1481                         if ( typeof toggle === 'undefined' ) {
    1482                                 toggle = ! move_widget_area.hasClass( 'active' );
    1483                         }
    1484                         if ( toggle ) {
    1485                                 // reset the selected sidebar
    1486                                 move_widget_area.find( '.selected' ).removeClass( 'selected' );
    1487                                 move_widget_area.find( 'li' ).filter( function () {
    1488                                         return $( this ).data( 'id' ) === control.params.sidebar_id;
    1489                                 } ).addClass( 'selected' );
    1490                                 control.container.find( '.move-widget-btn' ).prop( 'disabled', true );
    1491                         }
    1492                         move_widget_area.toggleClass( 'active', toggle );
    1493                 },
    1494 
    1495                 /**
    1496                  * Inside of the customizer preview, scroll the widget into view
    1497                  */
    1498                 scrollPreviewWidgetIntoView: function () {
    1499                         // @todo scrollIntoView() provides a robust but very poor experience. Animation is needed. See https://github.com/x-team/wp-widget-customizer/issues/16
    1500                 },
    1501 
    1502                 /**
    1503                  * Highlight the widget control and section
    1504                  */
    1505                 highlightSectionAndControl: function() {
    1506                         var control = this, target_element;
    1507 
    1508                         if ( control.container.is( ':hidden' ) ) {
    1509                                 target_element = control.container.closest( '.control-section' );
    1510                         } else {
    1511                                 target_element = control.container;
    1512                         }
    1513 
    1514                         $( '.widget-customizer-highlighted' ).removeClass( 'widget-customizer-highlighted' );
    1515                         target_element.addClass( 'widget-customizer-highlighted' );
    1516                         setTimeout( function () {
    1517                                 target_element.removeClass( 'widget-customizer-highlighted' );
    1518                         }, 500 );
    1519                 }
    1520 
     1484        $.extend( api.controlConstructor, {
     1485                widget_form: api.Widgets.WidgetControl,
     1486                sidebar_widgets: api.Widgets.SidebarControl
     1487        });
     1488
     1489        api.bind( 'ready', function() {
     1490                // Set up the widgets panel
     1491                api.Widgets.availableWidgetsPanel.setup();
     1492
     1493                // Highlight widget control
     1494                api.Widgets.Previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );
     1495
     1496                // Open and focus widget control
     1497                api.Widgets.Previewer.bind( 'focus-widget-control', api.Widgets.focusWidgetFormControl );
    15211498        } );
    15221499
     
    15241501         * Capture the instance of the Previewer since it is private
    15251502         */
    1526         OldPreviewer = wp.customize.Previewer;
    1527         wp.customize.Previewer = OldPreviewer.extend( {
     1503        OldPreviewer = api.Previewer;
     1504        api.Previewer = OldPreviewer.extend({
    15281505                initialize: function( params, options ) {
    1529                         self.previewer = this;
     1506                        api.Widgets.Previewer = this;
    15301507                        OldPreviewer.prototype.initialize.call( this, params, options );
    15311508                        this.bind( 'refresh', this.refresh );
     
    15381515         * @param {string} widgetId
    15391516         */
    1540         self.highlightWidgetFormControl = function( widgetId ) {
    1541                 var control = self.getWidgetFormControlForWidget( widgetId );
     1517        api.Widgets.highlightWidgetFormControl = function( widgetId ) {
     1518                var control = api.Widgets.getWidgetFormControlForWidget( widgetId );
    15421519
    15431520                if ( control ) {
     
    15511528         * @param {string} widgetId
    15521529         */
    1553         self.focusWidgetFormControl = function( widgetId ) {
    1554                 var control = self.getWidgetFormControlForWidget( widgetId );
     1530        api.Widgets.focusWidgetFormControl = function( widgetId ) {
     1531                var control = api.Widgets.getWidgetFormControlForWidget( widgetId );
    15551532
    15561533                if ( control ) {
     
    15641541         * @return {object|null}
    15651542         */
    1566         self.getSidebarWidgetControlContainingWidget = function ( widget_id ) {
     1543        api.Widgets.getSidebarWidgetControlContainingWidget = function ( widget_id ) {
    15671544                var found_control = null;
    15681545                // @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
    1569                 wp.customize.control.each( function ( control ) {
     1546                api.control.each( function ( control ) {
    15701547                        if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widget_id ) ) {
    15711548                                found_control = control;
    15721549                        }
    15731550                } );
     1551
    15741552                return found_control;
    15751553        };
     
    15801558         * @return {object|null}
    15811559         */
    1582         self.getWidgetFormControlForWidget = function ( widget_id ) {
     1560        api.Widgets.getWidgetFormControlForWidget = function ( widget_id ) {
    15831561                var found_control = null;
    15841562                // @todo We can just use widget_id_to_setting_id() here
    1585                 wp.customize.control.each( function ( control ) {
     1563                api.control.each( function ( control ) {
    15861564                        if ( control.params.type === 'widget_form' && control.params.widget_id === widget_id ) {
    15871565                                found_control = control;
    15881566                        }
    15891567                } );
     1568
    15901569                return found_control;
    1591         };
    1592 
    1593         /**
    1594          * @returns {Window}
    1595          */
    1596         self.getPreviewWindow = function (){
    1597                 return $( '#customize-preview' ).find( 'iframe' ).prop( 'contentWindow' );
    15981570        };
    15991571
     
    16011573         * Available Widgets Panel
    16021574         */
    1603         self.availableWidgetsPanel = {
     1575        api.Widgets.availableWidgetsPanel = {
    16041576                active_sidebar_widgets_control: null,
    16051577                selected_widget_tpl: null,
     
    16101582                 * Set up event listeners
    16111583                 */
    1612                 setup: function () {
     1584                setup: function() {
    16131585                        var panel = this;
    16141586
     
    16161588                        panel.filter_input = $( '#available-widgets-filter' ).find( 'input' );
    16171589
    1618                         self.available_widgets.on( 'change update', panel.update_available_widgets_list );
     1590                        api.Widgets.availableWidgets.on( 'change update', panel.update_available_widgets_list );
    16191591                        panel.update_available_widgets_list();
    16201592
     
    16301602
    16311603                        // Close the panel if the URL in the preview changes
    1632                         self.previewer.bind( 'url', function () {
     1604                        api.Widgets.Previewer.bind( 'url', function() {
    16331605                                panel.close();
    16341606                        } );
     
    16481620                                var first_visible_widget;
    16491621
    1650                                 self.available_widgets.doSearch( event.target.value );
     1622                                api.Widgets.availableWidgets.doSearch( event.target.value );
    16511623
    16521624                                // Remove a widget from being selected if it is no longer visible
     
    16721644
    16731645                        // Select a widget when it is focused on
    1674                         panel.container.find( ' > .widget-tpl' ).on( 'focus', function () {
     1646                        panel.container.find( ' > .widget-tpl' ).on( 'focus', function() {
    16751647                                panel.select( this );
    16761648                        } );
     
    17261698                 */
    17271699                update_available_widgets_list: function() {
    1728                         var panel = self.availableWidgetsPanel;
     1700                        var panel = api.Widgets.availableWidgetsPanel;
    17291701
    17301702                        // First hide all widgets...
     
    17321704
    17331705                        // ..and then show only available widgets which could be filtered
    1734                         self.available_widgets.each( function ( widget ) {
     1706                        api.Widgets.availableWidgets.each( function ( widget ) {
    17351707                                var widget_tpl = $( '#widget-tpl-' + widget.id );
    17361708                                widget_tpl.toggle( ! widget.get( 'is_disabled' ) );
     
    17621734
    17631735                        widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' );
    1764                         widget = self.available_widgets.findWhere( {id: widget_id} );
     1736                        widget = api.Widgets.availableWidgets.findWhere( {id: widget_id} );
    17651737                        if ( ! widget ) {
    17661738                                throw new Error( 'Widget unexpectedly not found.' );
     
    17861758                        $( 'body' ).addClass( 'adding-widget' );
    17871759                        panel.container.find( '.widget-tpl' ).removeClass( 'selected' );
    1788                         self.available_widgets.doSearch( '' );
     1760                        api.Widgets.availableWidgets.doSearch( '' );
    17891761                        panel.filter_input.focus();
    17901762                },
     
    18401812        }
    18411813
    1842         return self;
    1843 }( jQuery ));
     1814})( window.wp, jQuery );
  • trunk/src/wp-includes/class-wp-customize-control.php

    r27971 r27985  
    918918                ?>
    919919                <span class="button-secondary add-new-widget" tabindex="0">
    920                         <?php esc_html_e( 'Add a Widget' ); ?>
     920                        <?php _e( 'Add a Widget' ); ?>
    921921                </span>
    922922
    923923                <span class="reorder-toggle" tabindex="0">
    924                         <span class="reorder"><?php esc_html_e( 'Reorder' ); ?></span>
    925                         <span class="reorder-done"><?php esc_html_e( 'Done' ); ?></span>
     924                        <span class="reorder"><?php _ex( 'Reorder', 'Reorder widgets in Customizer' ); ?></span>
     925                        <span class="reorder-done"><?php _ex( 'Done', 'Cancel reordering widgets in Customizer' ); ?></span>
    926926                </span>
    927927                <?php
     
    941941        public $height;
    942942        public $is_wide = false;
    943         public $is_live_previewable = false;
    944943
    945944        public function to_json() {
    946945                parent::to_json();
    947                 $exported_properties = array( 'widget_id', 'widget_id_base', 'sidebar_id', 'width', 'height', 'is_wide', 'is_live_previewable' );
     946                $exported_properties = array( 'widget_id', 'widget_id_base', 'sidebar_id', 'width', 'height', 'is_wide' );
    948947                foreach ( $exported_properties as $key ) {
    949948                        $this->json[ $key ] = $this->$key;
  • trunk/src/wp-includes/class-wp-customize-widgets.php

    r27973 r27985  
    407407                                $id_base           = $GLOBALS['wp_registered_widget_controls'][$widget_id]['id_base'];
    408408
    409                                 assert( false !== is_active_widget( $registered_widget['callback'], $registered_widget['id'], false, false ) );
    410 
    411409                                $control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array(
    412410                                        'label'          => $registered_widget['name'],
     
    600598                        array( '{description}', '{btn}' ),
    601599                        array(
    602                                 ( 'Select an area to move this widget into:' ), // @todo translate
    603                                 esc_html_x( 'Move', 'move widget' ),
     600                                __( 'Select an area to move this widget into:' ),
     601                                _x( 'Move', 'Move widget' ),
    604602                        ),
    605603                        '<div class="move-widget-area">
     
    616614                );
    617615
    618                 /*
    619                  * Why not wp_localize_script? Because we're not localizing,
    620                  * and it forces values into strings.
    621                  */
    622616                global $wp_scripts;
    623617
    624                 $exports = array(
    625                         'nonce'               => wp_create_nonce( 'update-widget' ),
    626                         'registered_sidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ),
    627                         'registered_widgets'  => $GLOBALS['wp_registered_widgets'],
    628                         'available_widgets'   => $available_widgets, // @todo Merge this with registered_widgets
    629                         'i18n' => array(
    630                                 'save_btn_label'     => __( 'Apply' ),
    631                                 // @todo translate? do we want these tooltips?
    632                                 'save_btn_tooltip'   => ( 'Save and preview changes before publishing them.' ),
    633                                 'remove_btn_label'   => __( 'Remove' ),
    634                                 'remove_btn_tooltip' => ( 'Trash widget by moving it to the inactive widgets sidebar.' ),
    635                                 'error'              => __( 'An error has occurred. Please reload the page and try again.' ),
     618                $settings = array(
     619                        'nonce'                => wp_create_nonce( 'update-widget' ),
     620                        'registeredSidebars'   => array_values( $GLOBALS['wp_registered_sidebars'] ),
     621                        'registeredWidgets'    => $GLOBALS['wp_registered_widgets'],
     622                        'availableWidgets'     => $available_widgets, // @todo Merge this with registered_widgets
     623                        'l10n' => array(
     624                                'saveBtnLabel'     => __( 'Apply' ),
     625                                'saveBtnTooltip'   => __( 'Save and preview changes before publishing them.' ),
     626                                'removeBtnLabel'   => __( 'Remove' ),
     627                                'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ),
     628                                'error'            => __( 'An error has occurred. Please reload the page and try again.' ),
    636629                        ),
    637                         'tpl'                 => array(
    638                                 'widget_reorder_nav' => $widget_reorder_nav_tpl,
    639                                 'move_widget_area'   => $move_widget_area_tpl,
     630                        'tpl' => array(
     631                                'widgetReorderNav' => $widget_reorder_nav_tpl,
     632                                'moveWidgetArea'   => $move_widget_area_tpl,
    640633                        ),
    641634                );
    642635
    643                 foreach ( $exports['registered_widgets'] as &$registered_widget ) {
     636                foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
    644637                        unset( $registered_widget['callback'] ); // may not be JSON-serializeable
    645638                }
     
    648641                        'customize-widgets',
    649642                        'data',
    650                         sprintf( 'var WidgetCustomizer_exports = %s;', json_encode( $exports ) )
     643                        sprintf( 'var _wpCustomizeWidgetsSettings = %s;', json_encode( $settings ) )
    651644                );
    652645        }
     
    663656                <div id="available-widgets">
    664657                        <div id="available-widgets-filter">
    665                                 <label class="screen-reader-text" for="widgets-search"><?php _e( 'Find Widgets' ); ?></label>
    666                                 <input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Find widgets&hellip;' ) ?>" />
     658                                <label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
     659                                <input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets&hellip;' ) ?>" />
    667660                        </div>
    668661                        <?php foreach ( $this->get_available_widgets() as $available_widget ): ?>
    669662                                <div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0">
    670                                         <?php echo $available_widget['control_tpl']; // xss ok ?>
     663                                        <?php echo $available_widget['control_tpl']; ?>
    671664                                </div>
    672665                        <?php endforeach; ?>
     
    827820                        $available_widgets[] = $available_widget;
    828821                }
     822
    829823                return $available_widgets;
    830824        }
  • trunk/src/wp-includes/js/customize-preview-widgets.js

    r27892 r27985  
    1 (function( $, wp ){
     1(function( wp, $ ){
    22
    33        if ( ! wp || ! wp.customize ) { return; }
     
    125125        });
    126126
    127 })( jQuery, window.wp );
     127})( window.wp, jQuery );
Note: See TracChangeset for help on using the changeset viewer.