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

see #27690.
Built from https://develop.svn.wordpress.org/trunk@27986


git-svn-id: http://core.svn.wordpress.org/trunk@27816 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Dominik Schilling 2014-04-07 14:10:14 +00:00
parent fabc65b787
commit 5f76bf3b0d
2 changed files with 323 additions and 258 deletions

View File

@ -15,7 +15,12 @@
delete api.Widgets.data.l10n;
/**
* Set up model
* wp.customize.Widgets.WidgetModel
*
* A single widget model.
*
* @constructor
* @augments Backbone.Model
*/
api.Widgets.WidgetModel = Backbone.Model.extend({
id: null,
@ -34,6 +39,14 @@
height: null
});
/**
* wp.customize.Widgets.WidgetCollection
*
* Collection for widget models.
*
* @constructor
* @augments Backbone.Model
*/
api.Widgets.WidgetCollection = Backbone.Collection.extend({
model: api.Widgets.WidgetModel,
@ -92,6 +105,14 @@
});
api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets );
/**
* wp.customize.Widgets.SidebarModel
*
* A single sidebar model.
*
* @constructor
* @augments Backbone.Model
*/
api.Widgets.SidebarModel = Backbone.Model.extend({
after_title: null,
after_widget: null,
@ -104,11 +125,251 @@
is_rendered: false
});
/**
* wp.customize.Widgets.SidebarCollection
*
* Collection for sidebar models.
*
* @constructor
* @augments Backbone.Collection
*/
api.Widgets.SidebarCollection = Backbone.Collection.extend({
model: api.Widgets.SidebarModel
});
api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars );
/**
* wp.customize.Widgets.AvailableWidgetsPanelView
*
* View class for the available widgets panel.
*
* @constructor
* @augments wp.Backbone.View
* @augments Backbone.View
*/
api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend({
el: '#available-widgets',
events: {
'input #widgets-search': 'search',
'keyup #widgets-search': 'search',
'change #widgets-search': 'search',
'search #widgets-search': 'search',
'focus .widget-tpl' : 'focus',
'click .widget-tpl' : '_submit',
'keypress .widget-tpl' : '_submit',
'keydown' : 'keyboardAccessible'
},
// Cache current selected widget
selected: null,
// Cache sidebar control which has opened panel
currentSidebarControl: null,
$search: null,
initialize: function() {
var self = this;
this.$search = $( '#widgets-search' );
_.bindAll( this, 'close' );
this.listenTo( this.collection, 'update', this.updateList );
this.updateList();
// If the available widgets panel is open and the customize controls are
// interacted with (i.e. available widgets panel is blurred) then close the
// available widgets panel.
$( '#customize-controls' ).on( 'click keydown', function( e ) {
var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) {
self.close();
}
} );
// Close the panel if the URL in the preview changes
api.Widgets.Previewer.bind( 'url', this.close );
},
// Performs a search and handles selected widget
search: function( event ) {
var firstVisible;
this.collection.doSearch( event.target.value );
// Remove a widget from being selected if it is no longer visible
if ( this.selected && ! this.selected.is( ':visible' ) ) {
this.selected.removeClass( 'selected' );
this.selected = null;
}
// If a widget was selected but the filter value has been cleared out, clear selection
if ( this.selected && ! event.target.value ) {
this.selected.removeClass( 'selected' );
this.selected = null;
}
// If a filter has been entered and a widget hasn't been selected, select the first one shown
if ( ! this.selected && event.target.value ) {
firstVisible = this.$el.find( '> .widget-tpl:visible:first' );
if ( firstVisible.length ) {
this.select( firstVisible );
}
}
},
// Changes visibilty of available widgets
updateList: function() {
// First hide all widgets...
this.$el.find( '.widget-tpl' ).hide();
// ..and then show only available widgets which could be filtered
this.collection.each( function( widget ) {
var widgetTpl = $( '#widget-tpl-' + widget.id );
widgetTpl.toggle( ! widget.get( 'is_disabled' ) );
if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) {
this.selected = null;
}
} );
},
// Hightlights a widget
select: function( widgetTpl ) {
this.selected = $( widgetTpl );
this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' );
this.selected.addClass( 'selected' );
},
// Hightlights a widget on focus
focus: function( event ) {
this.select( $( event.currentTarget ) );
},
// Submit handler for keypress and click on widget
_submit: function( event ) {
// Only proceed with keypress if it is Enter or Spacebar
if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
return;
}
this.submit( $( event.currentTarget ) );
},
// Adds a selected widget to the sidebar
submit: function( widgetTpl ) {
var widgetId, widget;
if ( ! widgetTpl ) {
widgetTpl = this.selected;
}
if ( ! widgetTpl || ! this.currentSidebarControl ) {
return;
}
this.select( widgetTpl );
widgetId = $( this.selected ).data( 'widget-id' );
widget = this.collection.findWhere( { id: widgetId } );
if ( ! widget ) {
return;
}
this.currentSidebarControl.addWidget( widget.get( 'id_base' ) );
this.close();
},
// Opens the panel
open: function( sidebarControl ) {
this.currentSidebarControl = sidebarControl;
// Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens
_( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) {
if ( control.params.is_wide ) {
control.collapseForm();
}
} );
$( 'body' ).addClass( 'adding-widget' );
this.$el.find( '.selected' ).removeClass( 'selected' );
// Reset search
this.collection.doSearch( '' );
this.$search.focus();
},
// Closes the panel
close: function( options ) {
options = options || {};
if ( options.returnFocus && this.currentSidebarControl ) {
this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
}
this.currentSidebarControl = null;
this.selected = null;
$( 'body' ).removeClass( 'adding-widget' );
this.$search.val( '' );
},
// Add keyboard accessiblity to the panel
keyboardAccessible: function( event ) {
var isEnter = ( event.which === 13 ),
isEsc = ( event.which === 27 ),
isDown = ( event.which === 40 ),
isUp = ( event.which === 38 ),
selected = null,
firstVisible = this.$el.find( '> .widget-tpl:visible:first' ),
lastVisible = this.$el.find( '> .widget-tpl:visible:last' ),
isSearchFocused = $( event.target ).is( this.$search );
if ( isDown || isUp ) {
if ( isDown ) {
if ( isSearchFocused ) {
selected = firstVisible;
} else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
selected = this.selected.nextAll( '.widget-tpl:visible:first' );
}
} else if ( isUp ) {
if ( isSearchFocused ) {
selected = lastVisible;
} else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
selected = this.selected.prevAll( '.widget-tpl:visible:first' );
}
}
this.select( selected );
if ( selected ) {
selected.focus();
} else {
this.$search.focus();
}
return;
}
// If enter pressed but nothing entered, don't do anything
if ( isEnter && ! this.$search.val() ) {
return;
}
if ( isEnter ) {
this.submit();
} else if ( isEsc ) {
this.close( { returnFocus: true } );
}
}
});
/**
* Handlers for the widget-synced event, organized by widget ID base.
* Other widgets may provide their own update handlers by adding
@ -136,8 +397,13 @@
};
/**
* Widget Form control
* wp.customize.Widgets.WidgetControl
*
* Customizer control for widgets.
* Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type
*
* @constructor
* @augments wp.customize.Control
*/
api.Widgets.WidgetControl = api.Control.extend({
/**
@ -1043,8 +1309,13 @@
} );
/**
* Sidebar Widgets control
* wp.customize.Widgets.SidebarControl
*
* Customizer control for widgets.
* Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type
*
* @constructor
* @augments wp.customize.Control
*/
api.Widgets.SidebarControl = api.Control.extend({
/**
@ -1488,7 +1759,9 @@
api.bind( 'ready', function() {
// Set up the widgets panel
api.Widgets.availableWidgetsPanel.setup();
api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
collection: api.Widgets.availableWidgets
});
// Highlight widget control
api.Widgets.Previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );
@ -1569,214 +1842,6 @@
return found_control;
};
/**
* Available Widgets Panel
*/
api.Widgets.availableWidgetsPanel = {
active_sidebar_widgets_control: null,
selected_widget_tpl: null,
container: null,
filter_input: null,
/**
* Set up event listeners
*/
setup: function() {
var panel = this;
panel.container = $( '#available-widgets' );
panel.filter_input = $( '#available-widgets-filter' ).find( 'input' );
api.Widgets.availableWidgets.on( 'change update', panel.update_available_widgets_list );
panel.update_available_widgets_list();
// If the available widgets panel is open and the customize controls are
// interacted with (i.e. available widgets panel is blurred) then close the
// available widgets panel.
$( '#customize-controls' ).on( 'click keydown', function ( e ) {
var is_add_new_widget_btn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
if ( $( 'body' ).hasClass( 'adding-widget' ) && ! is_add_new_widget_btn ) {
panel.close();
}
} );
// Close the panel if the URL in the preview changes
api.Widgets.Previewer.bind( 'url', function() {
panel.close();
} );
// Submit a selection when clicked or keypressed
panel.container.find( '.widget-tpl' ).on( 'click keypress', function( event ) {
// Only proceed with keypress if it is Enter or Spacebar
if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
return;
}
panel.submit( this );
} );
panel.filter_input.on( 'input keyup change', function( event ) {
var first_visible_widget;
api.Widgets.availableWidgets.doSearch( event.target.value );
// Remove a widget from being selected if it is no longer visible
if ( panel.selected_widget_tpl && ! panel.selected_widget_tpl.is( ':visible' ) ) {
panel.selected_widget_tpl.removeClass( 'selected' );
panel.selected_widget_tpl = null;
}
// If a widget was selected but the filter value has been cleared out, clear selection
if ( panel.selected_widget_tpl && ! event.target.value ) {
panel.selected_widget_tpl.removeClass( 'selected' );
panel.selected_widget_tpl = null;
}
// If a filter has been entered and a widget hasn't been selected, select the first one shown
if ( ! panel.selected_widget_tpl && event.target.value ) {
first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' );
if ( first_visible_widget.length ) {
panel.select( first_visible_widget );
}
}
} );
// Select a widget when it is focused on
panel.container.find( ' > .widget-tpl' ).on( 'focus', function() {
panel.select( this );
} );
panel.container.on( 'keydown', function ( event ) {
var is_enter = ( event.which === 13 ),
is_esc = ( event.which === 27 ),
is_down = ( event.which === 40 ),
is_up = ( event.which === 38 ),
selected_widget_tpl = null,
first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' ),
last_visible_widget = panel.container.find( '> .widget-tpl:visible:last' ),
is_input_focused = $( event.target ).is( panel.filter_input );
if ( is_down || is_up ) {
if ( is_down ) {
if ( is_input_focused ) {
selected_widget_tpl = first_visible_widget;
} else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
selected_widget_tpl = panel.selected_widget_tpl.nextAll( '.widget-tpl:visible:first' );
}
} else if ( is_up ) {
if ( is_input_focused ) {
selected_widget_tpl = last_visible_widget;
} else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
selected_widget_tpl = panel.selected_widget_tpl.prevAll( '.widget-tpl:visible:first' );
}
}
panel.select( selected_widget_tpl );
if ( selected_widget_tpl ) {
selected_widget_tpl.focus();
} else {
panel.filter_input.focus();
}
return;
}
// If enter pressed but nothing entered, don't do anything
if ( is_enter && ! panel.filter_input.val() ) {
return;
}
if ( is_enter ) {
panel.submit();
} else if ( is_esc ) {
panel.close( { return_focus: true } );
}
} );
},
/**
* Updates widgets list.
*/
update_available_widgets_list: function() {
var panel = api.Widgets.availableWidgetsPanel;
// First hide all widgets...
panel.container.find( '.widget-tpl' ).hide();
// ..and then show only available widgets which could be filtered
api.Widgets.availableWidgets.each( function ( widget ) {
var widget_tpl = $( '#widget-tpl-' + widget.id );
widget_tpl.toggle( ! widget.get( 'is_disabled' ) );
if ( widget.get( 'is_disabled' ) && widget_tpl.is( panel.selected_widget_tpl ) ) {
panel.selected_widget_tpl = null;
}
} );
},
/**
* @param widget_tpl
*/
select: function ( widget_tpl ) {
var panel = this;
panel.selected_widget_tpl = $( widget_tpl );
panel.selected_widget_tpl.siblings( '.widget-tpl' ).removeClass( 'selected' );
panel.selected_widget_tpl.addClass( 'selected' );
},
submit: function ( widget_tpl ) {
var panel = this, widget_id, widget;
if ( ! widget_tpl ) {
widget_tpl = panel.selected_widget_tpl;
}
if ( ! widget_tpl || ! panel.active_sidebar_widgets_control ) {
return;
}
panel.select( widget_tpl );
widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' );
widget = api.Widgets.availableWidgets.findWhere( {id: widget_id} );
if ( ! widget ) {
throw new Error( 'Widget unexpectedly not found.' );
}
panel.active_sidebar_widgets_control.addWidget( widget.get( 'id_base' ) );
panel.close();
},
/**
* @param sidebars_widgets_control
*/
open: function ( sidebars_widgets_control ) {
var panel = this;
panel.active_sidebar_widgets_control = sidebars_widgets_control;
// Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens
_( sidebars_widgets_control.getWidgetFormControls() ).each( function ( control ) {
if ( control.params.is_wide ) {
control.collapseForm();
}
} );
$( 'body' ).addClass( 'adding-widget' );
panel.container.find( '.widget-tpl' ).removeClass( 'selected' );
api.Widgets.availableWidgets.doSearch( '' );
panel.filter_input.focus();
},
/**
* Hide the panel
*/
close: function ( options ) {
var panel = this;
options = options || {};
if ( options.return_focus && panel.active_sidebar_widgets_control ) {
panel.active_sidebar_widgets_control.container.find( '.add-new-widget' ).focus();
}
panel.active_sidebar_widgets_control = null;
panel.selected_widget_tpl = null;
$( 'body' ).removeClass( 'adding-widget' );
panel.filter_input.val( '' );
}
};
/**
* @param {String} widget_id
* @returns {Object}

File diff suppressed because one or more lines are too long