Make WordPress Core

Ticket #27690: 27690.2.patch

File 27690.2.patch, 27.9 KB (added by ocean90, 12 years ago)

Move panel to a Backbone view

  • src/wp-admin/js/customize-widgets.js

     
    121121                 * @param {jQuery} widget
    122122                 * @param {String} newForm
    123123                 */
    124                 rss: function ( e, widget, newForm ) {
     124                rss: function( e, widget, newForm ) {
    125125                        var oldWidgetError = widget.find( '.widget-error:first' ),
    126126                                newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' );
    127127
     
    246246                        } );
    247247
    248248                        // Reposition whenever a sidebar's widgets are changed
    249                         api.each( function ( setting ) {
     249                        api.each( function( setting ) {
    250250                                if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
    251251                                        setting.bind( function() {
    252252                                                if ( control.container.hasClass( 'expanded' ) ) {
     
    264264                _setupControlToggle: function() {
    265265                        var control = this, close_btn;
    266266
    267                         control.container.find( '.widget-top' ).on( 'click', function ( e ) {
     267                        control.container.find( '.widget-top' ).on( 'click', function( e ) {
    268268                                e.preventDefault();
    269269                                var sidebar_widgets_control = control.getSidebarWidgetsControl();
    270270                                if ( sidebar_widgets_control.is_reordering ) {
     
    275275
    276276                        close_btn = control.container.find( '.widget-control-close' );
    277277                        // @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 ) {
     278                        close_btn.on( 'click', function( e ) {
    279279                                e.preventDefault();
    280280                                control.collapseForm();
    281281                                control.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility
     
    317317                         *
    318318                         * @param {jQuery} li
    319319                         */
    320                         select_sidebar_item = function ( li ) {
     320                        select_sidebar_item = function( li ) {
    321321                                li.siblings( '.selected' ).removeClass( 'selected' );
    322322                                li.addClass( 'selected' );
    323323                                var is_self_sidebar = ( li.data( 'id' ) === control.params.sidebar_id );
     
    363363                         * Handle clicks for up/down/move on the reorder nav
    364364                         */
    365365                        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 ) {
     366                        reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function( event ) {
    367367                                if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
    368368                                        return;
    369369                                }
     
    393393                        /**
    394394                         * Handle selecting a sidebar to move to
    395395                         */
    396                         control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function ( e ) {
     396                        control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function( e ) {
    397397                                if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
    398398                                        return;
    399399                                }
     
    472472                        save_btn.val( l10n.saveBtnLabel );
    473473                        save_btn.attr( 'title', l10n.saveBtnTooltip );
    474474                        save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' );
    475                         save_btn.on( 'click', function ( e ) {
     475                        save_btn.on( 'click', function( e ) {
    476476                                e.preventDefault();
    477477                                control.updateWidget( { disable_form: true } );
    478478                        } );
     
    491491                        } );
    492492
    493493                        // Handle widgets that support live previews
    494                         widget_content.on( 'change input propertychange', ':input', function ( e ) {
     494                        widget_content.on( 'change input propertychange', ':input', function( e ) {
    495495                                if ( control.live_update_mode ) {
    496496                                        if ( e.type === 'change' ) {
    497497                                                control.updateWidget();
     
    505505                        control.setting.previewer.channel.bind( 'synced', function() {
    506506                                control.container.removeClass( 'previewer-loading' );
    507507                        } );
    508                         api.Widgets.Previewer.bind( 'widget-updated', function ( updated_widget_id ) {
     508                        api.Widgets.Previewer.bind( 'widget-updated', function( updated_widget_id ) {
    509509                                if ( updated_widget_id === control.params.widget_id ) {
    510510                                        control.container.removeClass( 'previewer-loading' );
    511511                                }
    512512                        } );
    513513
    514514                        // Update widget control to indicate whether it is currently rendered (cf. Widget Visibility)
    515                         api.Widgets.Previewer.bind( 'rendered-widgets', function ( rendered_widgets ) {
     515                        api.Widgets.Previewer.bind( 'rendered-widgets', function( rendered_widgets ) {
    516516                                var is_rendered = !! rendered_widgets[control.params.widget_id];
    517517                                control.container.toggleClass( 'widget-rendered', is_rendered );
    518518                        } );
    519519
    520520                        form_update_event_handler = api.Widgets.formSyncHandlers[ control.params.widget_id_base ];
    521521                        if ( form_update_event_handler ) {
    522                                 $( document ).on( 'widget-synced', function ( e, widget_el ) {
     522                                $( document ).on( 'widget-synced', function( e, widget_el ) {
    523523                                        if ( widget_root.is( widget_el ) ) {
    524524                                                form_update_event_handler.apply( document, arguments );
    525525                                        }
     
    538538                        // Configure remove button
    539539                        remove_btn = control.container.find( 'a.widget-control-remove' );
    540540                        // @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 ) {
     541                        remove_btn.on( 'click', function( e ) {
    542542                                e.preventDefault();
    543543
    544544                                // Find an adjacent element to add focus to when this widget goes away
     
    592592                 * @returns {jQuery} inputs
    593593                 * @private
    594594                 */
    595                 _getInputs: function ( container ) {
     595                _getInputs: function( container ) {
    596596                        return $( container ).find( ':input[name]' );
    597597                },
    598598
     
    604604                 * @returns {string}
    605605                 * @private
    606606                 */
    607                 _getInputsSignature: function ( inputs ) {
    608                         var inputs_signatures = _( inputs ).map( function ( input ) {
     607                _getInputsSignature: function( inputs ) {
     608                        var inputs_signatures = _( inputs ).map( function( input ) {
    609609                                input = $( input );
    610610                                var signature_parts;
    611611                                if ( input.is( ':checkbox, :radio' ) ) {
     
    625625                 * @returns {string}
    626626                 * @private
    627627                 */
    628                 _getInputStatePropertyName: function ( input ) {
     628                _getInputStatePropertyName: function( input ) {
    629629                        input = $( input );
    630630                        if ( input.is( ':radio, :checkbox' ) ) {
    631631                                return 'checked';
     
    661661                 * @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.
    662662                 * @param {Boolean} [args.ignore_active_element=false] Whether or not updating a field will be deferred if focus is still on the element.
    663663                 */
    664                 updateWidget: function ( args ) {
     664                updateWidget: function( args ) {
    665665                        var control = this,
    666666                                instance_override,
    667667                                complete_callback,
     
    726726                        }
    727727                        data += '&' + widget_content.find( '~ :input' ).serialize();
    728728
    729                         jqxhr = $.post( wp.ajax.settings.url, data, function ( r ) {
     729                        jqxhr = $.post( wp.ajax.settings.url, data, function( r ) {
    730730                                var message,
    731731                                        sanitized_form,
    732732                                        sanitized_inputs,
     
    763763
    764764                                        // Sync sanitized field states to existing fields if they are aligned
    765765                                        if ( has_same_inputs_in_response && control.live_update_mode ) {
    766                                                 inputs.each( function ( i ) {
     766                                                inputs.each( function( i ) {
    767767                                                        var input = $( this ),
    768768                                                                sanitized_input = $( sanitized_inputs[i] ),
    769769                                                                property = control._getInputStatePropertyName( this ),
     
    827827                                        }
    828828                                }
    829829                        } );
    830                         jqxhr.fail( function ( jqXHR, textStatus ) {
     830                        jqxhr.fail( function( jqXHR, textStatus ) {
    831831                                if ( complete_callback ) {
    832832                                        complete_callback.call( control, textStatus );
    833833                                }
     
    872872                 *
    873873                 * @param {boolean|undefined} [do_expand] If not supplied, will be inverse of current visibility
    874874                 */
    875                 toggleForm: function ( do_expand ) {
     875                toggleForm: function( do_expand ) {
    876876                        var control = this, widget, inside, complete;
    877877
    878878                        widget = control.container.find( 'div.widget:first' );
     
    888888
    889889                        if ( do_expand ) {
    890890                                // Close all other widget controls before expanding this one
    891                                 api.control.each( function ( other_control ) {
     891                                api.control.each( function( other_control ) {
    892892                                        if ( control.params.type === other_control.params.type && control !== other_control ) {
    893893                                                other_control.collapseForm();
    894894                                        }
     
    974974                 *
    975975                 * @param {Number} offset 1|-1
    976976                 */
    977                 _moveWidgetByOne: function ( offset ) {
     977                _moveWidgetByOne: function( offset ) {
    978978                        var control = this,
    979979                                i,
    980980                                sidebar_widgets_setting,
     
    997997                 *
    998998                 * @param {Boolean} [toggle]
    999999                 */
    1000                 toggleWidgetMoveArea: function ( toggle ) {
     1000                toggleWidgetMoveArea: function( toggle ) {
    10011001                        var control = this, move_widget_area;
    10021002                        move_widget_area = control.container.find( '.move-widget-area' );
    10031003                        if ( typeof toggle === 'undefined' ) {
     
    10741074                                        removed_widget_ids = _( old_widget_ids ).difference( new_widget_ids );
    10751075
    10761076                                // Filter out any persistent widget_ids for widgets which have been deactivated
    1077                                 new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) {
     1077                                new_widget_ids = _( new_widget_ids ).filter( function( new_widget_id ) {
    10781078                                        var parsed_widget_id = parse_widget_id( new_widget_id );
    10791079                                        return !! api.Widgets.availableWidgets.findWhere( { id_base: parsed_widget_id.id_base } );
    10801080                                } );
    10811081
    1082                                 widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) {
     1082                                widget_form_controls = _( new_widget_ids ).map( function( widget_id ) {
    10831083                                        var widget_form_control = api.Widgets.getWidgetFormControlForWidget( widget_id );
    10841084                                        if ( ! widget_form_control ) {
    10851085                                                widget_form_control = control.addWidget( widget_id );
     
    10881088                                } );
    10891089
    10901090                                // Sort widget controls to their new positions
    1091                                 widget_form_controls.sort( function ( a, b ) {
     1091                                widget_form_controls.sort( function( a, b ) {
    10921092                                        var a_index = _.indexOf( new_widget_ids, a.params.widget_id ),
    10931093                                                b_index = _.indexOf( new_widget_ids, b.params.widget_id );
    10941094                                        if ( a_index === b_index ) {
     
    11091109                                control._applyCardinalOrderClassNames();
    11101110
    11111111                                // 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 ) {
     1112                                _( widget_form_controls ).each( function( widget_form_control ) {
    11131113                                        widget_form_control.params.sidebar_id = control.params.sidebar_id;
    11141114                                } );
    11151115
    11161116                                // Cleanup after widget removal
    1117                                 _( removed_widget_ids ).each( function ( removed_widget_id ) {
     1117                                _( removed_widget_ids ).each( function( removed_widget_id ) {
    11181118
    11191119                                        // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
    11201120                                        setTimeout( function() {
     
    11261126                                                        widget;
    11271127
    11281128                                                // Check if the widget is in another sidebar
    1129                                                 api.each( function ( other_setting ) {
     1129                                                api.each( function( other_setting ) {
    11301130                                                        if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
    11311131                                                                return;
    11321132                                                        }
     
    11781178                        } );
    11791179
    11801180                        // Update the model with whether or not the sidebar is rendered
    1181                         api.Widgets.Previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) {
     1181                        api.Widgets.Previewer.bind( 'rendered-sidebars', function( rendered_sidebars ) {
    11821182                                var is_rendered = !! rendered_sidebars[control.params.sidebar_id];
    11831183                                registered_sidebar.set( 'is_rendered', is_rendered );
    11841184                        } );
    11851185
    11861186                        // Show the sidebar section when it becomes visible
    1187                         registered_sidebar.on( 'change:is_rendered', function ( ) {
     1187                        registered_sidebar.on( 'change:is_rendered', function( ) {
    11881188                                var section_selector = '#accordion-section-sidebar-widgets-' + this.get( 'id' ), section;
    11891189                                section = $( section_selector );
    11901190                                if ( this.get( 'is_rendered' ) ) {
     
    12191219                                connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
    12201220                                update: function() {
    12211221                                        var widget_container_ids = control.section_content.sortable( 'toArray' ), widget_ids;
    1222                                         widget_ids = $.map( widget_container_ids, function ( widget_container_id ) {
     1222                                        widget_ids = $.map( widget_container_ids, function( widget_container_id ) {
    12231223                                                return $( '#' + widget_container_id ).find( ':input[name=widget-id]' ).val();
    12241224                                        } );
    12251225                                        control.setting( widget_ids );
     
    13061306                 *
    13071307                 * @param {Boolean} toggle to enable/disable reordering
    13081308                 */
    1309                 toggleReordering: function ( toggle ) {
     1309                toggleReordering: function( toggle ) {
    13101310                        var control = this;
    13111311                        toggle = Boolean( toggle );
    13121312                        if ( toggle === control.section_content.hasClass( 'reordering' ) ) {
     
    13171317                        control.section_content.toggleClass( 'reordering', toggle );
    13181318
    13191319                        if ( toggle ) {
    1320                                 _( control.getWidgetFormControls() ).each( function ( form_control ) {
     1320                                _( control.getWidgetFormControls() ).each( function( form_control ) {
    13211321                                        form_control.collapseForm();
    13221322                                } );
    13231323                        }
     
    13291329                getWidgetFormControls: function() {
    13301330                        var control = this, form_controls;
    13311331
    1332                         form_controls = _( control.setting() ).map( function ( widget_id ) {
     1332                        form_controls = _( control.setting() ).map( function( widget_id ) {
    13331333                                var setting_id = widget_id_to_setting_id( widget_id ),
    13341334                                        form_control = api.control( setting_id );
    13351335
     
    13451345                 * @param {string} widget_id or an id_base for adding a previously non-existing widget
    13461346                 * @returns {object} widget_form control instance
    13471347                 */
    1348                 addWidget: function ( widget_id ) {
     1348                addWidget: function( widget_id ) {
    13491349                        var control = this,
    13501350                                control_html,
    13511351                                widget_el,
     
    13771377
    13781378                        control_html = $( '#widget-tpl-' + widget.get( 'id' ) ).html();
    13791379                        if ( widget.get( 'is_multi' ) ) {
    1380                                 control_html = control_html.replace( /<[^<>]+>/g, function ( m ) {
     1380                                control_html = control_html.replace( /<[^<>]+>/g, function( m ) {
    13811381                                        return m.replace( /__i__|%i%/g, widget_number );
    13821382                                } );
    13831383                        } else {
     
    14351435                        api.control.add( setting_id, widget_form_control );
    14361436
    14371437                        // Make sure widget is removed from the other sidebars
    1438                         api.each( function ( other_setting ) {
     1438                        api.each( function( other_setting ) {
    14391439                                if ( other_setting.id === control.setting.id ) {
    14401440                                        return;
    14411441                                }
     
    14621462                                        widget_form_control.expandForm();
    14631463                                        widget_form_control.updateWidget( {
    14641464                                                instance: widget_form_control.setting(),
    1465                                                 complete: function ( error ) {
     1465                                                complete: function( error ) {
    14661466                                                        if ( error ) {
    14671467                                                                throw error;
    14681468                                                        }
     
    14881488
    14891489        api.bind( 'ready', function() {
    14901490                // Set up the widgets panel
    1491                 api.Widgets.availableWidgetsPanel.setup();
     1491                api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
     1492                        collection: api.Widgets.availableWidgets
     1493                });
    14921494
    14931495                // Highlight widget control
    14941496                api.Widgets.Previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );
     
    15401542         * @param {string} widget_id
    15411543         * @return {object|null}
    15421544         */
    1543         api.Widgets.getSidebarWidgetControlContainingWidget = function ( widget_id ) {
     1545        api.Widgets.getSidebarWidgetControlContainingWidget = function( widget_id ) {
    15441546                var found_control = null;
    15451547                // @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
    1546                 api.control.each( function ( control ) {
     1548                api.control.each( function( control ) {
    15471549                        if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widget_id ) ) {
    15481550                                found_control = control;
    15491551                        }
     
    15571559         * @param {string} widget_id
    15581560         * @return {object|null}
    15591561         */
    1560         api.Widgets.getWidgetFormControlForWidget = function ( widget_id ) {
     1562        api.Widgets.getWidgetFormControlForWidget = function( widget_id ) {
    15611563                var found_control = null;
    15621564                // @todo We can just use widget_id_to_setting_id() here
    1563                 api.control.each( function ( control ) {
     1565                api.control.each( function( control ) {
    15641566                        if ( control.params.type === 'widget_form' && control.params.widget_id === widget_id ) {
    15651567                                found_control = control;
    15661568                        }
     
    15691571                return found_control;
    15701572        };
    15711573
    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,
     1574        api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend({
    15801575
    1581                 /**
    1582                  * Set up event listeners
    1583                  */
    1584                 setup: function() {
    1585                         var panel = this;
     1576                el: '#available-widgets',
    15861577
    1587                         panel.container = $( '#available-widgets' );
    1588                         panel.filter_input = $( '#available-widgets-filter' ).find( 'input' );
     1578                events: {
     1579                        'input #widgets-search': 'search',
     1580                        'keyup #widgets-search': 'search',
     1581                        'change #widgets-search': 'search',
     1582                        'search #widgets-search': 'search',
     1583                        'focus .widget-tpl' : 'focus',
     1584                        'click .widget-tpl' : '_submit',
     1585                        'keypress .widget-tpl' : '_submit',
     1586                        'keydown' : 'keyboardAccessible'
     1587                },
    15891588
    1590                         api.Widgets.availableWidgets.on( 'change update', panel.update_available_widgets_list );
    1591                         panel.update_available_widgets_list();
     1589                selected: null,
     1590                currentSidebarControl: null,
     1591                $search: null,
    15921592
     1593                initialize: function() {
     1594                        var self = this;
     1595
     1596                        this.$search = $( '#widgets-search' );
     1597
     1598                        this.listenTo( this.collection, 'update', this.updateList );
     1599
     1600                        this.updateList();
     1601
    15931602                        // If the available widgets panel is open and the customize controls are
    15941603                        // interacted with (i.e. available widgets panel is blurred) then close the
    15951604                        // 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();
     1605                        $( '#customize-controls' ).on( 'click keydown', function( e ) {
     1606                                var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
     1607                                if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) {
     1608                                        self.close();
    16001609                                }
    16011610                        } );
    16021611
    16031612                        // Close the panel if the URL in the preview changes
    1604                         api.Widgets.Previewer.bind( 'url', function() {
    1605                                 panel.close();
    1606                         } );
     1613                        api.Widgets.Previewer.bind( 'url', this.close );
     1614                },
    16071615
    1608                         // Submit a selection when clicked or keypressed
    1609                         panel.container.find( '.widget-tpl' ).on( 'click keypress', function( event ) {
     1616                search: function( event ) {
     1617                        var firstVisible;
    16101618
    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                                 }
     1619                        this.collection.doSearch( event.target.value );
    16151620
    1616                                 panel.submit( this );
    1617                         } );
     1621                        // Remove a widget from being selected if it is no longer visible
     1622                        if ( this.selected && ! this.selected.is( ':visible' ) ) {
     1623                                this.selected.removeClass( 'selected' );
     1624                                this.selected = null;
     1625                        }
    16181626
    1619                         panel.filter_input.on( 'input keyup change', function( event ) {
    1620                                 var first_visible_widget;
     1627                        // If a widget was selected but the filter value has been cleared out, clear selection
     1628                        if ( this.selected && ! event.target.value ) {
     1629                                this.selected.removeClass( 'selected' );
     1630                                this.selected = null;
     1631                        }
    16211632
    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;
     1633                        // If a filter has been entered and a widget hasn't been selected, select the first one shown
     1634                        if ( ! this.selected && event.target.value ) {
     1635                                firstVisible = this.$el.find( '> .widget-tpl:visible:first' );
     1636                                if ( firstVisible.length ) {
     1637                                        this.select( firstVisible );
    16281638                                }
     1639                        }
     1640                },
    16291641
    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                                 }
     1642                updateList: function() {
     1643                        // First hide all widgets...
     1644                        this.$el.find( '.widget-tpl' ).hide();
    16351645
    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                                         }
     1646                        // ..and then show only available widgets which could be filtered
     1647                        this.collection.each( function( widget ) {
     1648                                var widgetTpl = $( '#widget-tpl-' + widget.id );
     1649                                widgetTpl.toggle( ! widget.get( 'is_disabled' ) );
     1650                                if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) {
     1651                                        this.selected = null;
    16421652                                }
    16431653                        } );
     1654                },
    16441655
    1645                         // Select a widget when it is focused on
    1646                         panel.container.find( ' > .widget-tpl' ).on( 'focus', function() {
    1647                                 panel.select( this );
    1648                         } );
     1656                select: function( widgetTpl ) {
     1657                        this.selected = $( widgetTpl );
     1658                        this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' );
     1659                        this.selected.addClass( 'selected' );
     1660                },
    16491661
    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                         } );
     1662                focus: function( event ) {
     1663                        this.select( $( event.currentTarget ) );
    16941664                },
    16951665
    1696                 /**
    1697                  * Updates widgets list.
    1698                  */
    1699                 update_available_widgets_list: function() {
    1700                         var panel = api.Widgets.availableWidgetsPanel;
     1666                _submit: function( event ) {
     1667                        // Only proceed with keypress if it is Enter or Spacebar
     1668                        if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
     1669                                return;
     1670                        }
    17011671
    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                         } );
     1672                        this.submit( $( event.currentTarget ) );
    17131673                },
    17141674
    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                 },
     1675                submit: function( widgetTpl ) {
     1676                        var widgetId, widget;
    17241677
    1725                 submit: function ( widget_tpl ) {
    1726                         var panel = this, widget_id, widget;
    1727                         if ( ! widget_tpl ) {
    1728                                 widget_tpl = panel.selected_widget_tpl;
     1678                        if ( ! widgetTpl ) {
     1679                                widgetTpl = this.selected;
    17291680                        }
    1730                         if ( ! widget_tpl || ! panel.active_sidebar_widgets_control ) {
     1681
     1682                        if ( ! widgetTpl || ! this.currentSidebarControl ) {
    17311683                                return;
    17321684                        }
    1733                         panel.select( widget_tpl );
    17341685
    1735                         widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' );
    1736                         widget = api.Widgets.availableWidgets.findWhere( {id: widget_id} );
     1686                        this.select( widgetTpl );
     1687
     1688                        widgetId = $( this.selected ).data( 'widget-id' );
     1689                        widget = this.collection.findWhere( { id: widgetId } );
    17371690                        if ( ! widget ) {
    1738                                 throw new Error( 'Widget unexpectedly not found.' );
     1691                                return;
    17391692                        }
    1740                         panel.active_sidebar_widgets_control.addWidget( widget.get( 'id_base' ) );
    1741                         panel.close();
     1693
     1694                        this.currentSidebarControl.addWidget( widget.get( 'id_base' ) );
     1695
     1696                        this.close();
    17421697                },
    17431698
    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;
     1699                open: function( sidebarControl ) {
     1700                        this.currentSidebarControl = sidebarControl;
    17501701
    17511702                        // 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 ) {
     1703                        _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) {
    17531704                                if ( control.params.is_wide ) {
    17541705                                        control.collapseForm();
    17551706                                }
    17561707                        } );
    17571708
    17581709                        $( 'body' ).addClass( 'adding-widget' );
    1759                         panel.container.find( '.widget-tpl' ).removeClass( 'selected' );
    1760                         api.Widgets.availableWidgets.doSearch( '' );
    1761                         panel.filter_input.focus();
     1710
     1711                        this.$el.find( '.selected' ).removeClass( 'selected' );
     1712
     1713                        // Reset search
     1714                        this.collection.doSearch( '' );
     1715
     1716                        this.$search.focus();
    17621717                },
    17631718
    1764                 /**
    1765                  * Hide the panel
    1766                  */
    1767                 close: function ( options ) {
    1768                         var panel = this;
     1719                close: function( options ) {
    17691720                        options = options || {};
    1770                         if ( options.return_focus && panel.active_sidebar_widgets_control ) {
    1771                                 panel.active_sidebar_widgets_control.container.find( '.add-new-widget' ).focus();
     1721
     1722                        if ( options.returnFocus && this.currentSidebarControl ) {
     1723                                this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
    17721724                        }
    1773                         panel.active_sidebar_widgets_control = null;
    1774                         panel.selected_widget_tpl = null;
     1725
     1726                        this.currentSidebarControl = null;
     1727                        this.selected = null;
     1728
    17751729                        $( 'body' ).removeClass( 'adding-widget' );
    1776                         panel.filter_input.val( '' );
     1730
     1731                        this.$search.val( '' );
     1732                },
     1733
     1734                keyboardAccessible: function( event ) {
     1735                        var isEnter = ( event.which === 13 ),
     1736                                isEsc = ( event.which === 27 ),
     1737                                isDown = ( event.which === 40 ),
     1738                                isUp = ( event.which === 38 ),
     1739                                selected = null,
     1740                                firstVisible = this.$el.find( '> .widget-tpl:visible:first' ),
     1741                                lastVisible = this.$el.find( '> .widget-tpl:visible:last' ),
     1742                                isSearchFocused = $( event.target ).is( this.$search );
     1743
     1744                        if ( isDown || isUp ) {
     1745                                if ( isDown ) {
     1746                                        if ( isSearchFocused ) {
     1747                                                selected = firstVisible;
     1748                                        } else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
     1749                                                selected = this.selected.nextAll( '.widget-tpl:visible:first' );
     1750                                        }
     1751                                } else if ( isUp ) {
     1752                                        if ( isSearchFocused ) {
     1753                                                selected = lastVisible;
     1754                                        } else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
     1755                                                selected = this.selected.prevAll( '.widget-tpl:visible:first' );
     1756                                        }
     1757                                }
     1758
     1759                                this.select( selected );
     1760
     1761                                if ( selected ) {
     1762                                        selected.focus();
     1763                                } else {
     1764                                        this.$search.focus();
     1765                                }
     1766
     1767                                return;
     1768                        }
     1769
     1770                        // If enter pressed but nothing entered, don't do anything
     1771                        if ( isEnter && ! this.$search.val() ) {
     1772                                return;
     1773                        }
     1774
     1775                        if ( isEnter ) {
     1776                                this.submit();
     1777                        } else if ( isEsc ) {
     1778                                this.close( { returnFocus: true } );
     1779                        }
    17771780                }
    1778         };
     1781        });
    17791782
    17801783        /**
    17811784         * @param {String} widget_id