WordPress.org

Make WordPress Core

Changeset 27986


Ignore:
Timestamp:
04/07/14 14:09:53 (13 months ago)
Author:
ocean90
Message:

Widget Customizer: Make the available widgets panel a Backbone view.

see #27690.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/js/customize-widgets.js

    r27985 r27986  
    1616 
    1717    /** 
    18      * Set up model 
     18     * wp.customize.Widgets.WidgetModel 
     19     * 
     20     * A single widget model. 
     21     * 
     22     * @constructor 
     23     * @augments Backbone.Model 
    1924     */ 
    2025    api.Widgets.WidgetModel = Backbone.Model.extend({ 
     
    3540    }); 
    3641 
     42    /** 
     43     * wp.customize.Widgets.WidgetCollection 
     44     * 
     45     * Collection for widget models. 
     46     * 
     47     * @constructor 
     48     * @augments Backbone.Model 
     49     */ 
    3750    api.Widgets.WidgetCollection = Backbone.Collection.extend({ 
    3851        model: api.Widgets.WidgetModel, 
     
    93106    api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets ); 
    94107 
     108    /** 
     109     * wp.customize.Widgets.SidebarModel 
     110     * 
     111     * A single sidebar model. 
     112     * 
     113     * @constructor 
     114     * @augments Backbone.Model 
     115     */ 
    95116    api.Widgets.SidebarModel = Backbone.Model.extend({ 
    96117        after_title: null, 
     
    105126    }); 
    106127 
     128    /** 
     129     * wp.customize.Widgets.SidebarCollection 
     130     * 
     131     * Collection for sidebar models. 
     132     * 
     133     * @constructor 
     134     * @augments Backbone.Collection 
     135     */ 
    107136    api.Widgets.SidebarCollection = Backbone.Collection.extend({ 
    108137        model: api.Widgets.SidebarModel 
    109138    }); 
    110139    api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars ); 
     140 
     141    /** 
     142     * wp.customize.Widgets.AvailableWidgetsPanelView 
     143     * 
     144     * View class for the available widgets panel. 
     145     * 
     146     * @constructor 
     147     * @augments wp.Backbone.View 
     148     * @augments Backbone.View 
     149     */ 
     150    api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend({ 
     151 
     152        el: '#available-widgets', 
     153 
     154        events: { 
     155            'input #widgets-search': 'search', 
     156            'keyup #widgets-search': 'search', 
     157            'change #widgets-search': 'search', 
     158            'search #widgets-search': 'search', 
     159            'focus .widget-tpl' : 'focus', 
     160            'click .widget-tpl' : '_submit', 
     161            'keypress .widget-tpl' : '_submit', 
     162            'keydown' : 'keyboardAccessible' 
     163        }, 
     164 
     165        // Cache current selected widget 
     166        selected: null, 
     167 
     168        // Cache sidebar control which has opened panel 
     169        currentSidebarControl: null, 
     170        $search: null, 
     171 
     172        initialize: function() { 
     173            var self = this; 
     174 
     175            this.$search = $( '#widgets-search' ); 
     176 
     177            _.bindAll( this, 'close' ); 
     178 
     179            this.listenTo( this.collection, 'update', this.updateList ); 
     180 
     181            this.updateList(); 
     182 
     183            // If the available widgets panel is open and the customize controls are 
     184            // interacted with (i.e. available widgets panel is blurred) then close the 
     185            // available widgets panel. 
     186            $( '#customize-controls' ).on( 'click keydown', function( e ) { 
     187                var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' ); 
     188                if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) { 
     189                    self.close(); 
     190                } 
     191            } ); 
     192 
     193            // Close the panel if the URL in the preview changes 
     194            api.Widgets.Previewer.bind( 'url', this.close ); 
     195        }, 
     196 
     197        // Performs a search and handles selected widget 
     198        search: function( event ) { 
     199            var firstVisible; 
     200 
     201            this.collection.doSearch( event.target.value ); 
     202 
     203            // Remove a widget from being selected if it is no longer visible 
     204            if ( this.selected && ! this.selected.is( ':visible' ) ) { 
     205                this.selected.removeClass( 'selected' ); 
     206                this.selected = null; 
     207            } 
     208 
     209            // If a widget was selected but the filter value has been cleared out, clear selection 
     210            if ( this.selected && ! event.target.value ) { 
     211                this.selected.removeClass( 'selected' ); 
     212                this.selected = null; 
     213            } 
     214 
     215            // If a filter has been entered and a widget hasn't been selected, select the first one shown 
     216            if ( ! this.selected && event.target.value ) { 
     217                firstVisible = this.$el.find( '> .widget-tpl:visible:first' ); 
     218                if ( firstVisible.length ) { 
     219                    this.select( firstVisible ); 
     220                } 
     221            } 
     222        }, 
     223 
     224        // Changes visibilty of available widgets 
     225        updateList: function() { 
     226            // First hide all widgets... 
     227            this.$el.find( '.widget-tpl' ).hide(); 
     228 
     229            // ..and then show only available widgets which could be filtered 
     230            this.collection.each( function( widget ) { 
     231                var widgetTpl = $( '#widget-tpl-' + widget.id ); 
     232                widgetTpl.toggle( ! widget.get( 'is_disabled' ) ); 
     233                if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) { 
     234                    this.selected = null; 
     235                } 
     236            } ); 
     237        }, 
     238 
     239        // Hightlights a widget 
     240        select: function( widgetTpl ) { 
     241            this.selected = $( widgetTpl ); 
     242            this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' ); 
     243            this.selected.addClass( 'selected' ); 
     244        }, 
     245 
     246        // Hightlights a widget on focus 
     247        focus: function( event ) { 
     248            this.select( $( event.currentTarget ) ); 
     249        }, 
     250 
     251        // Submit handler for keypress and click on widget 
     252        _submit: function( event ) { 
     253            // Only proceed with keypress if it is Enter or Spacebar 
     254            if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { 
     255                return; 
     256            } 
     257 
     258            this.submit( $( event.currentTarget ) ); 
     259        }, 
     260 
     261        // Adds a selected widget to the sidebar 
     262        submit: function( widgetTpl ) { 
     263            var widgetId, widget; 
     264 
     265            if ( ! widgetTpl ) { 
     266                widgetTpl = this.selected; 
     267            } 
     268 
     269            if ( ! widgetTpl || ! this.currentSidebarControl ) { 
     270                return; 
     271            } 
     272 
     273            this.select( widgetTpl ); 
     274 
     275            widgetId = $( this.selected ).data( 'widget-id' ); 
     276            widget = this.collection.findWhere( { id: widgetId } ); 
     277            if ( ! widget ) { 
     278                return; 
     279            } 
     280 
     281            this.currentSidebarControl.addWidget( widget.get( 'id_base' ) ); 
     282 
     283            this.close(); 
     284        }, 
     285 
     286        // Opens the panel 
     287        open: function( sidebarControl ) { 
     288            this.currentSidebarControl = sidebarControl; 
     289 
     290            // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens 
     291            _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) { 
     292                if ( control.params.is_wide ) { 
     293                    control.collapseForm(); 
     294                } 
     295            } ); 
     296 
     297            $( 'body' ).addClass( 'adding-widget' ); 
     298 
     299            this.$el.find( '.selected' ).removeClass( 'selected' ); 
     300 
     301            // Reset search 
     302            this.collection.doSearch( '' ); 
     303 
     304            this.$search.focus(); 
     305        }, 
     306 
     307        // Closes the panel 
     308        close: function( options ) { 
     309            options = options || {}; 
     310 
     311            if ( options.returnFocus && this.currentSidebarControl ) { 
     312                this.currentSidebarControl.container.find( '.add-new-widget' ).focus(); 
     313            } 
     314 
     315            this.currentSidebarControl = null; 
     316            this.selected = null; 
     317 
     318            $( 'body' ).removeClass( 'adding-widget' ); 
     319 
     320            this.$search.val( '' ); 
     321        }, 
     322 
     323        // Add keyboard accessiblity to the panel 
     324        keyboardAccessible: function( event ) { 
     325            var isEnter = ( event.which === 13 ), 
     326                isEsc = ( event.which === 27 ), 
     327                isDown = ( event.which === 40 ), 
     328                isUp = ( event.which === 38 ), 
     329                selected = null, 
     330                firstVisible = this.$el.find( '> .widget-tpl:visible:first' ), 
     331                lastVisible = this.$el.find( '> .widget-tpl:visible:last' ), 
     332                isSearchFocused = $( event.target ).is( this.$search ); 
     333 
     334            if ( isDown || isUp ) { 
     335                if ( isDown ) { 
     336                    if ( isSearchFocused ) { 
     337                        selected = firstVisible; 
     338                    } else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) { 
     339                        selected = this.selected.nextAll( '.widget-tpl:visible:first' ); 
     340                    } 
     341                } else if ( isUp ) { 
     342                    if ( isSearchFocused ) { 
     343                        selected = lastVisible; 
     344                    } else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) { 
     345                        selected = this.selected.prevAll( '.widget-tpl:visible:first' ); 
     346                    } 
     347                } 
     348 
     349                this.select( selected ); 
     350 
     351                if ( selected ) { 
     352                    selected.focus(); 
     353                } else { 
     354                    this.$search.focus(); 
     355                } 
     356 
     357                return; 
     358            } 
     359 
     360            // If enter pressed but nothing entered, don't do anything 
     361            if ( isEnter && ! this.$search.val() ) { 
     362                return; 
     363            } 
     364 
     365            if ( isEnter ) { 
     366                this.submit(); 
     367            } else if ( isEsc ) { 
     368                this.close( { returnFocus: true } ); 
     369            } 
     370        } 
     371    }); 
    111372 
    112373    /** 
     
    122383         * @param {String} newForm 
    123384         */ 
    124         rss: function ( e, widget, newForm ) { 
     385        rss: function( e, widget, newForm ) { 
    125386            var oldWidgetError = widget.find( '.widget-error:first' ), 
    126387                newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' ); 
     
    137398 
    138399    /** 
    139      * Widget Form control 
     400     * wp.customize.Widgets.WidgetControl 
     401     * 
     402     * Customizer control for widgets. 
    140403     * Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type 
     404     * 
     405     * @constructor 
     406     * @augments wp.customize.Control 
    141407     */ 
    142408    api.Widgets.WidgetControl = api.Control.extend({ 
     
    247513 
    248514            // Reposition whenever a sidebar's widgets are changed 
    249             api.each( function ( setting ) { 
     515            api.each( function( setting ) { 
    250516                if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) { 
    251517                    setting.bind( function() { 
     
    265531            var control = this, close_btn; 
    266532 
    267             control.container.find( '.widget-top' ).on( 'click', function ( e ) { 
     533            control.container.find( '.widget-top' ).on( 'click', function( e ) { 
    268534                e.preventDefault(); 
    269535                var sidebar_widgets_control = control.getSidebarWidgetsControl(); 
     
    276542            close_btn = control.container.find( '.widget-control-close' ); 
    277543            // @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 ) { 
     544            close_btn.on( 'click', function( e ) { 
    279545                e.preventDefault(); 
    280546                control.collapseForm(); 
     
    318584             * @param {jQuery} li 
    319585             */ 
    320             select_sidebar_item = function ( li ) { 
     586            select_sidebar_item = function( li ) { 
    321587                li.siblings( '.selected' ).removeClass( 'selected' ); 
    322588                li.addClass( 'selected' ); 
     
    364630             */ 
    365631            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 ) { 
     632            reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function( event ) { 
    367633                if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { 
    368634                    return; 
     
    394660             * Handle selecting a sidebar to move to 
    395661             */ 
    396             control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function ( e ) { 
     662            control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function( e ) { 
    397663                if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { 
    398664                    return; 
     
    473739            save_btn.attr( 'title', l10n.saveBtnTooltip ); 
    474740            save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' ); 
    475             save_btn.on( 'click', function ( e ) { 
     741            save_btn.on( 'click', function( e ) { 
    476742                e.preventDefault(); 
    477743                control.updateWidget( { disable_form: true } ); 
     
    492758 
    493759            // Handle widgets that support live previews 
    494             widget_content.on( 'change input propertychange', ':input', function ( e ) { 
     760            widget_content.on( 'change input propertychange', ':input', function( e ) { 
    495761                if ( control.live_update_mode ) { 
    496762                    if ( e.type === 'change' ) { 
     
    506772                control.container.removeClass( 'previewer-loading' ); 
    507773            } ); 
    508             api.Widgets.Previewer.bind( 'widget-updated', function ( updated_widget_id ) { 
     774            api.Widgets.Previewer.bind( 'widget-updated', function( updated_widget_id ) { 
    509775                if ( updated_widget_id === control.params.widget_id ) { 
    510776                    control.container.removeClass( 'previewer-loading' ); 
     
    513779 
    514780            // Update widget control to indicate whether it is currently rendered (cf. Widget Visibility) 
    515             api.Widgets.Previewer.bind( 'rendered-widgets', function ( rendered_widgets ) { 
     781            api.Widgets.Previewer.bind( 'rendered-widgets', function( rendered_widgets ) { 
    516782                var is_rendered = !! rendered_widgets[control.params.widget_id]; 
    517783                control.container.toggleClass( 'widget-rendered', is_rendered ); 
     
    520786            form_update_event_handler = api.Widgets.formSyncHandlers[ control.params.widget_id_base ]; 
    521787            if ( form_update_event_handler ) { 
    522                 $( document ).on( 'widget-synced', function ( e, widget_el ) { 
     788                $( document ).on( 'widget-synced', function( e, widget_el ) { 
    523789                    if ( widget_root.is( widget_el ) ) { 
    524790                        form_update_event_handler.apply( document, arguments ); 
     
    539805            remove_btn = control.container.find( 'a.widget-control-remove' ); 
    540806            // @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 ) { 
     807            remove_btn.on( 'click', function( e ) { 
    542808                e.preventDefault(); 
    543809 
     
    593859         * @private 
    594860         */ 
    595         _getInputs: function ( container ) { 
     861        _getInputs: function( container ) { 
    596862            return $( container ).find( ':input[name]' ); 
    597863        }, 
     
    605871         * @private 
    606872         */ 
    607         _getInputsSignature: function ( inputs ) { 
    608             var inputs_signatures = _( inputs ).map( function ( input ) { 
     873        _getInputsSignature: function( inputs ) { 
     874            var inputs_signatures = _( inputs ).map( function( input ) { 
    609875                input = $( input ); 
    610876                var signature_parts; 
     
    626892         * @private 
    627893         */ 
    628         _getInputStatePropertyName: function ( input ) { 
     894        _getInputStatePropertyName: function( input ) { 
    629895            input = $( input ); 
    630896            if ( input.is( ':radio, :checkbox' ) ) { 
     
    662928         * @param {Boolean} [args.ignore_active_element=false] Whether or not updating a field will be deferred if focus is still on the element. 
    663929         */ 
    664         updateWidget: function ( args ) { 
     930        updateWidget: function( args ) { 
    665931            var control = this, 
    666932                instance_override, 
     
    727993            data += '&' + widget_content.find( '~ :input' ).serialize(); 
    728994 
    729             jqxhr = $.post( wp.ajax.settings.url, data, function ( r ) { 
     995            jqxhr = $.post( wp.ajax.settings.url, data, function( r ) { 
    730996                var message, 
    731997                    sanitized_form, 
     
    7641030                    // Sync sanitized field states to existing fields if they are aligned 
    7651031                    if ( has_same_inputs_in_response && control.live_update_mode ) { 
    766                         inputs.each( function ( i ) { 
     1032                        inputs.each( function( i ) { 
    7671033                            var input = $( this ), 
    7681034                                sanitized_input = $( sanitized_inputs[i] ), 
     
    8281094                } 
    8291095            } ); 
    830             jqxhr.fail( function ( jqXHR, textStatus ) { 
     1096            jqxhr.fail( function( jqXHR, textStatus ) { 
    8311097                if ( complete_callback ) { 
    8321098                    complete_callback.call( control, textStatus ); 
     
    8731139         * @param {boolean|undefined} [do_expand] If not supplied, will be inverse of current visibility 
    8741140         */ 
    875         toggleForm: function ( do_expand ) { 
     1141        toggleForm: function( do_expand ) { 
    8761142            var control = this, widget, inside, complete; 
    8771143 
     
    8891155            if ( do_expand ) { 
    8901156                // Close all other widget controls before expanding this one 
    891                 api.control.each( function ( other_control ) { 
     1157                api.control.each( function( other_control ) { 
    8921158                    if ( control.params.type === other_control.params.type && control !== other_control ) { 
    8931159                        other_control.collapseForm(); 
     
    9751241         * @param {Number} offset 1|-1 
    9761242         */ 
    977         _moveWidgetByOne: function ( offset ) { 
     1243        _moveWidgetByOne: function( offset ) { 
    9781244            var control = this, 
    9791245                i, 
     
    9981264         * @param {Boolean} [toggle] 
    9991265         */ 
    1000         toggleWidgetMoveArea: function ( toggle ) { 
     1266        toggleWidgetMoveArea: function( toggle ) { 
    10011267            var control = this, move_widget_area; 
    10021268            move_widget_area = control.container.find( '.move-widget-area' ); 
     
    10441310 
    10451311    /** 
    1046      * Sidebar Widgets control 
     1312     * wp.customize.Widgets.SidebarControl 
     1313     * 
     1314     * Customizer control for widgets. 
    10471315     * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type 
     1316     * 
     1317     * @constructor 
     1318     * @augments wp.customize.Control 
    10481319     */ 
    10491320    api.Widgets.SidebarControl = api.Control.extend({ 
     
    10751346 
    10761347                // Filter out any persistent widget_ids for widgets which have been deactivated 
    1077                 new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) { 
     1348                new_widget_ids = _( new_widget_ids ).filter( function( new_widget_id ) { 
    10781349                    var parsed_widget_id = parse_widget_id( new_widget_id ); 
    10791350                    return !! api.Widgets.availableWidgets.findWhere( { id_base: parsed_widget_id.id_base } ); 
    10801351                } ); 
    10811352 
    1082                 widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) { 
     1353                widget_form_controls = _( new_widget_ids ).map( function( widget_id ) { 
    10831354                    var widget_form_control = api.Widgets.getWidgetFormControlForWidget( widget_id ); 
    10841355                    if ( ! widget_form_control ) { 
     
    10891360 
    10901361                // Sort widget controls to their new positions 
    1091                 widget_form_controls.sort( function ( a, b ) { 
     1362                widget_form_controls.sort( function( a, b ) { 
    10921363                    var a_index = _.indexOf( new_widget_ids, a.params.widget_id ), 
    10931364                        b_index = _.indexOf( new_widget_ids, b.params.widget_id ); 
     
    11101381 
    11111382                // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated 
    1112                 _( widget_form_controls ).each( function ( widget_form_control ) { 
     1383                _( widget_form_controls ).each( function( widget_form_control ) { 
    11131384                    widget_form_control.params.sidebar_id = control.params.sidebar_id; 
    11141385                } ); 
    11151386 
    11161387                // Cleanup after widget removal 
    1117                 _( removed_widget_ids ).each( function ( removed_widget_id ) { 
     1388                _( removed_widget_ids ).each( function( removed_widget_id ) { 
    11181389 
    11191390                    // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update 
     
    11271398 
    11281399                        // Check if the widget is in another sidebar 
    1129                         api.each( function ( other_setting ) { 
     1400                        api.each( function( other_setting ) { 
    11301401                            if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) { 
    11311402                                return; 
     
    11791450 
    11801451            // Update the model with whether or not the sidebar is rendered 
    1181             api.Widgets.Previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) { 
     1452            api.Widgets.Previewer.bind( 'rendered-sidebars', function( rendered_sidebars ) { 
    11821453                var is_rendered = !! rendered_sidebars[control.params.sidebar_id]; 
    11831454                registered_sidebar.set( 'is_rendered', is_rendered ); 
     
    11851456 
    11861457            // Show the sidebar section when it becomes visible 
    1187             registered_sidebar.on( 'change:is_rendered', function ( ) { 
     1458            registered_sidebar.on( 'change:is_rendered', function( ) { 
    11881459                var section_selector = '#accordion-section-sidebar-widgets-' + this.get( 'id' ), section; 
    11891460                section = $( section_selector ); 
     
    12201491                update: function() { 
    12211492                    var widget_container_ids = control.section_content.sortable( 'toArray' ), widget_ids; 
    1222                     widget_ids = $.map( widget_container_ids, function ( widget_container_id ) { 
     1493                    widget_ids = $.map( widget_container_ids, function( widget_container_id ) { 
    12231494                        return $( '#' + widget_container_id ).find( ':input[name=widget-id]' ).val(); 
    12241495                    } ); 
     
    13071578         * @param {Boolean} toggle to enable/disable reordering 
    13081579         */ 
    1309         toggleReordering: function ( toggle ) { 
     1580        toggleReordering: function( toggle ) { 
    13101581            var control = this; 
    13111582            toggle = Boolean( toggle ); 
     
    13181589 
    13191590            if ( toggle ) { 
    1320                 _( control.getWidgetFormControls() ).each( function ( form_control ) { 
     1591                _( control.getWidgetFormControls() ).each( function( form_control ) { 
    13211592                    form_control.collapseForm(); 
    13221593                } ); 
     
    13301601            var control = this, form_controls; 
    13311602 
    1332             form_controls = _( control.setting() ).map( function ( widget_id ) { 
     1603            form_controls = _( control.setting() ).map( function( widget_id ) { 
    13331604                var setting_id = widget_id_to_setting_id( widget_id ), 
    13341605                    form_control = api.control( setting_id ); 
     
    13461617         * @returns {object} widget_form control instance 
    13471618         */ 
    1348         addWidget: function ( widget_id ) { 
     1619        addWidget: function( widget_id ) { 
    13491620            var control = this, 
    13501621                control_html, 
     
    13781649            control_html = $( '#widget-tpl-' + widget.get( 'id' ) ).html(); 
    13791650            if ( widget.get( 'is_multi' ) ) { 
    1380                 control_html = control_html.replace( /<[^<>]+>/g, function ( m ) { 
     1651                control_html = control_html.replace( /<[^<>]+>/g, function( m ) { 
    13811652                    return m.replace( /__i__|%i%/g, widget_number ); 
    13821653                } ); 
     
    14361707 
    14371708            // Make sure widget is removed from the other sidebars 
    1438             api.each( function ( other_setting ) { 
     1709            api.each( function( other_setting ) { 
    14391710                if ( other_setting.id === control.setting.id ) { 
    14401711                    return; 
     
    14631734                    widget_form_control.updateWidget( { 
    14641735                        instance: widget_form_control.setting(), 
    1465                         complete: function ( error ) { 
     1736                        complete: function( error ) { 
    14661737                            if ( error ) { 
    14671738                                throw error; 
     
    14891760    api.bind( 'ready', function() { 
    14901761        // Set up the widgets panel 
    1491         api.Widgets.availableWidgetsPanel.setup(); 
     1762        api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({ 
     1763            collection: api.Widgets.availableWidgets 
     1764        }); 
    14921765 
    14931766        // Highlight widget control 
     
    15411814     * @return {object|null} 
    15421815     */ 
    1543     api.Widgets.getSidebarWidgetControlContainingWidget = function ( widget_id ) { 
     1816    api.Widgets.getSidebarWidgetControlContainingWidget = function( widget_id ) { 
    15441817        var found_control = null; 
    15451818        // @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl() 
    1546         api.control.each( function ( control ) { 
     1819        api.control.each( function( control ) { 
    15471820            if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widget_id ) ) { 
    15481821                found_control = control; 
     
    15581831     * @return {object|null} 
    15591832     */ 
    1560     api.Widgets.getWidgetFormControlForWidget = function ( widget_id ) { 
     1833    api.Widgets.getWidgetFormControlForWidget = function( widget_id ) { 
    15611834        var found_control = null; 
    15621835        // @todo We can just use widget_id_to_setting_id() here 
    1563         api.control.each( function ( control ) { 
     1836        api.control.each( function( control ) { 
    15641837            if ( control.params.type === 'widget_form' && control.params.widget_id === widget_id ) { 
    15651838                found_control = control; 
     
    15681841 
    15691842        return found_control; 
    1570     }; 
    1571  
    1572     /** 
    1573      * Available Widgets Panel 
    1574      */ 
    1575     api.Widgets.availableWidgetsPanel = { 
    1576         active_sidebar_widgets_control: null, 
    1577         selected_widget_tpl: null, 
    1578         container: null, 
    1579         filter_input: null, 
    1580  
    1581         /** 
    1582          * Set up event listeners 
    1583          */ 
    1584         setup: function() { 
    1585             var panel = this; 
    1586  
    1587             panel.container = $( '#available-widgets' ); 
    1588             panel.filter_input = $( '#available-widgets-filter' ).find( 'input' ); 
    1589  
    1590             api.Widgets.availableWidgets.on( 'change update', panel.update_available_widgets_list ); 
    1591             panel.update_available_widgets_list(); 
    1592  
    1593             // If the available widgets panel is open and the customize controls are 
    1594             // interacted with (i.e. available widgets panel is blurred) then close the 
    1595             // available widgets panel. 
    1596             $( '#customize-controls' ).on( 'click keydown', function ( e ) { 
    1597                 var is_add_new_widget_btn = $( e.target ).is( '.add-new-widget, .add-new-widget *' ); 
    1598                 if ( $( 'body' ).hasClass( 'adding-widget' ) && ! is_add_new_widget_btn ) { 
    1599                     panel.close(); 
    1600                 } 
    1601             } ); 
    1602  
    1603             // Close the panel if the URL in the preview changes 
    1604             api.Widgets.Previewer.bind( 'url', function() { 
    1605                 panel.close(); 
    1606             } ); 
    1607  
    1608             // Submit a selection when clicked or keypressed 
    1609             panel.container.find( '.widget-tpl' ).on( 'click keypress', function( event ) { 
    1610  
    1611                 // Only proceed with keypress if it is Enter or Spacebar 
    1612                 if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { 
    1613                     return; 
    1614                 } 
    1615  
    1616                 panel.submit( this ); 
    1617             } ); 
    1618  
    1619             panel.filter_input.on( 'input keyup change', function( event ) { 
    1620                 var first_visible_widget; 
    1621  
    1622                 api.Widgets.availableWidgets.doSearch( event.target.value ); 
    1623  
    1624                 // Remove a widget from being selected if it is no longer visible 
    1625                 if ( panel.selected_widget_tpl && ! panel.selected_widget_tpl.is( ':visible' ) ) { 
    1626                     panel.selected_widget_tpl.removeClass( 'selected' ); 
    1627                     panel.selected_widget_tpl = null; 
    1628                 } 
    1629  
    1630                 // If a widget was selected but the filter value has been cleared out, clear selection 
    1631                 if ( panel.selected_widget_tpl && ! event.target.value ) { 
    1632                     panel.selected_widget_tpl.removeClass( 'selected' ); 
    1633                     panel.selected_widget_tpl = null; 
    1634                 } 
    1635  
    1636                 // If a filter has been entered and a widget hasn't been selected, select the first one shown 
    1637                 if ( ! panel.selected_widget_tpl &&  event.target.value ) { 
    1638                     first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' ); 
    1639                     if ( first_visible_widget.length ) { 
    1640                         panel.select( first_visible_widget ); 
    1641                     } 
    1642                 } 
    1643             } ); 
    1644  
    1645             // Select a widget when it is focused on 
    1646             panel.container.find( ' > .widget-tpl' ).on( 'focus', function() { 
    1647                 panel.select( this ); 
    1648             } ); 
    1649  
    1650             panel.container.on( 'keydown', function ( event ) { 
    1651                 var is_enter = ( event.which === 13 ), 
    1652                     is_esc = ( event.which === 27 ), 
    1653                     is_down = ( event.which === 40 ), 
    1654                     is_up = ( event.which === 38 ), 
    1655                     selected_widget_tpl = null, 
    1656                     first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' ), 
    1657                     last_visible_widget = panel.container.find( '> .widget-tpl:visible:last' ), 
    1658                     is_input_focused = $( event.target ).is( panel.filter_input ); 
    1659  
    1660                 if ( is_down || is_up ) { 
    1661                     if ( is_down ) { 
    1662                         if ( is_input_focused ) { 
    1663                             selected_widget_tpl = first_visible_widget; 
    1664                         } else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.nextAll( '.widget-tpl:visible' ).length !== 0 ) { 
    1665                             selected_widget_tpl = panel.selected_widget_tpl.nextAll( '.widget-tpl:visible:first' ); 
    1666                         } 
    1667                     } else if ( is_up ) { 
    1668                         if ( is_input_focused ) { 
    1669                             selected_widget_tpl = last_visible_widget; 
    1670                         } else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.prevAll( '.widget-tpl:visible' ).length !== 0 ) { 
    1671                             selected_widget_tpl = panel.selected_widget_tpl.prevAll( '.widget-tpl:visible:first' ); 
    1672                         } 
    1673                     } 
    1674                     panel.select( selected_widget_tpl ); 
    1675                     if ( selected_widget_tpl ) { 
    1676                         selected_widget_tpl.focus(); 
    1677                     } else { 
    1678                         panel.filter_input.focus(); 
    1679                     } 
    1680                     return; 
    1681                 } 
    1682  
    1683                 // If enter pressed but nothing entered, don't do anything 
    1684                 if ( is_enter && ! panel.filter_input.val() ) { 
    1685                     return; 
    1686                 } 
    1687  
    1688                 if ( is_enter ) { 
    1689                     panel.submit(); 
    1690                 } else if ( is_esc ) { 
    1691                     panel.close( { return_focus: true } ); 
    1692                 } 
    1693             } ); 
    1694         }, 
    1695  
    1696         /** 
    1697          * Updates widgets list. 
    1698          */ 
    1699         update_available_widgets_list: function() { 
    1700             var panel = api.Widgets.availableWidgetsPanel; 
    1701  
    1702             // First hide all widgets... 
    1703             panel.container.find( '.widget-tpl' ).hide(); 
    1704  
    1705             // ..and then show only available widgets which could be filtered 
    1706             api.Widgets.availableWidgets.each( function ( widget ) { 
    1707                 var widget_tpl = $( '#widget-tpl-' + widget.id ); 
    1708                 widget_tpl.toggle( ! widget.get( 'is_disabled' ) ); 
    1709                 if ( widget.get( 'is_disabled' ) && widget_tpl.is( panel.selected_widget_tpl ) ) { 
    1710                     panel.selected_widget_tpl = null; 
    1711                 } 
    1712             } ); 
    1713         }, 
    1714  
    1715         /** 
    1716          * @param widget_tpl 
    1717          */ 
    1718         select: function ( widget_tpl ) { 
    1719             var panel = this; 
    1720             panel.selected_widget_tpl = $( widget_tpl ); 
    1721             panel.selected_widget_tpl.siblings( '.widget-tpl' ).removeClass( 'selected' ); 
    1722             panel.selected_widget_tpl.addClass( 'selected' ); 
    1723         }, 
    1724  
    1725         submit: function ( widget_tpl ) { 
    1726             var panel = this, widget_id, widget; 
    1727             if ( ! widget_tpl ) { 
    1728                 widget_tpl = panel.selected_widget_tpl; 
    1729             } 
    1730             if ( ! widget_tpl || ! panel.active_sidebar_widgets_control ) { 
    1731                 return; 
    1732             } 
    1733             panel.select( widget_tpl ); 
    1734  
    1735             widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' ); 
    1736             widget = api.Widgets.availableWidgets.findWhere( {id: widget_id} ); 
    1737             if ( ! widget ) { 
    1738                 throw new Error( 'Widget unexpectedly not found.' ); 
    1739             } 
    1740             panel.active_sidebar_widgets_control.addWidget( widget.get( 'id_base' ) ); 
    1741             panel.close(); 
    1742         }, 
    1743  
    1744         /** 
    1745          * @param sidebars_widgets_control 
    1746          */ 
    1747         open: function ( sidebars_widgets_control ) { 
    1748             var panel = this; 
    1749             panel.active_sidebar_widgets_control = sidebars_widgets_control; 
    1750  
    1751             // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens 
    1752             _( sidebars_widgets_control.getWidgetFormControls() ).each( function ( control ) { 
    1753                 if ( control.params.is_wide ) { 
    1754                     control.collapseForm(); 
    1755                 } 
    1756             } ); 
    1757  
    1758             $( 'body' ).addClass( 'adding-widget' ); 
    1759             panel.container.find( '.widget-tpl' ).removeClass( 'selected' ); 
    1760             api.Widgets.availableWidgets.doSearch( '' ); 
    1761             panel.filter_input.focus(); 
    1762         }, 
    1763  
    1764         /** 
    1765          * Hide the panel 
    1766          */ 
    1767         close: function ( options ) { 
    1768             var panel = this; 
    1769             options = options || {}; 
    1770             if ( options.return_focus && panel.active_sidebar_widgets_control ) { 
    1771                 panel.active_sidebar_widgets_control.container.find( '.add-new-widget' ).focus(); 
    1772             } 
    1773             panel.active_sidebar_widgets_control = null; 
    1774             panel.selected_widget_tpl = null; 
    1775             $( 'body' ).removeClass( 'adding-widget' ); 
    1776             panel.filter_input.val( '' ); 
    1777         } 
    17781843    }; 
    17791844 
Note: See TracChangeset for help on using the changeset viewer.