2014-03-05 21:41:14 +01:00
/*global wp, Backbone, _, jQuery, WidgetCustomizer_exports */
/*exported WidgetCustomizer */
var WidgetCustomizer = ( function ( $ ) {
'use strict' ;
2014-03-06 17:45:15 +01:00
var Widget ,
WidgetCollection ,
Sidebar ,
SidebarCollection ,
OldPreviewer ,
customize = wp . customize , self = {
2014-03-05 21:41:14 +01:00
update _widget _ajax _action : null ,
update _widget _nonce _value : null ,
update _widget _nonce _post _key : null ,
i18n : {
save _btn _label : '' ,
save _btn _tooltip : '' ,
remove _btn _label : '' ,
2014-03-22 21:55:18 +01:00
remove _btn _tooltip : '' ,
2014-03-25 00:02:13 +01:00
error : ''
2014-03-05 21:41:14 +01:00
} ,
available _widgets : [ ] , // available widgets for instantiating
registered _widgets : [ ] , // all widgets registered
active _sidebar _control : null ,
previewer : null ,
saved _widget _ids : { } ,
registered _sidebars : [ ] ,
tpl : {
move _widget _area : '' ,
widget _reorder _nav : ''
}
} ;
$ . extend ( self , WidgetCustomizer _exports ) ;
// Lots of widgets expect this old ajaxurl global to be available
if ( typeof window . ajaxurl === 'undefined' ) {
window . ajaxurl = wp . ajax . settings . url ;
}
// Unfortunately many widgets try to look for instances under div#widgets-right,
// so we have to add that ID to a container div in the customizer for compat
$ ( '#customize-theme-controls' ) . closest ( 'div:not([id])' ) . attr ( 'id' , 'widgets-right' ) ;
/ * *
* Set up model
* /
2014-03-06 17:45:15 +01:00
Widget = self . Widget = Backbone . Model . extend ( {
2014-03-05 21:41:14 +01:00
id : null ,
temp _id : null ,
classname : null ,
control _tpl : null ,
description : null ,
is _disabled : null ,
is _multi : null ,
multi _number : null ,
name : null ,
id _base : null ,
transport : 'refresh' ,
params : [ ] ,
width : null ,
height : null
} ) ;
2014-03-06 17:45:15 +01:00
WidgetCollection = self . WidgetCollection = Backbone . Collection . extend ( {
2014-03-21 22:13:15 +01:00
model : Widget ,
// Controls searching on the current widget collection
// and triggers an update event
doSearch : function ( value ) {
// Don't do anything if we've already done this search
// Useful because the search handler fires multiple times per keystroke
if ( this . terms === value ) {
return ;
}
// Updates terms with the value passed
this . terms = value ;
// If we have terms, run a search...
if ( this . terms . length > 0 ) {
this . search ( this . terms ) ;
}
// If search is blank, show all themes
// Useful for resetting the views when you clean the input
if ( this . terms === '' ) {
this . reset ( WidgetCustomizer _exports . available _widgets ) ;
}
// Trigger an 'update' event
this . trigger ( 'update' ) ;
} ,
// Performs a search within the collection
// @uses RegExp
search : function ( term ) {
var match , results , haystack ;
// Start with a full collection
this . reset ( WidgetCustomizer _exports . available _widgets , { silent : true } ) ;
// Escape the term string for RegExp meta characters
term = term . replace ( /[-\/\\^$*+?.()|[\]{}]/g , '\\$&' ) ;
// Consider spaces as word delimiters and match the whole string
// so matching terms can be combined
term = term . replace ( / /g , ')(?=.*' ) ;
match = new RegExp ( '^(?=.*' + term + ').+' , 'i' ) ;
results = this . filter ( function ( data ) {
haystack = _ . union ( data . get ( 'name' ) , data . get ( 'id' ) , data . get ( 'description' ) ) ;
return match . test ( haystack ) ;
} ) ;
this . reset ( results ) ;
}
2014-03-05 21:41:14 +01:00
} ) ;
self . available _widgets = new WidgetCollection ( self . available _widgets ) ;
2014-03-06 17:45:15 +01:00
Sidebar = self . Sidebar = Backbone . Model . extend ( {
2014-03-05 21:41:14 +01:00
after _title : null ,
after _widget : null ,
before _title : null ,
before _widget : null ,
'class' : null ,
description : null ,
id : null ,
name : null ,
is _rendered : false
} ) ;
2014-03-06 17:45:15 +01:00
SidebarCollection = self . SidebarCollection = Backbone . Collection . extend ( {
2014-03-05 21:41:14 +01:00
model : Sidebar
} ) ;
self . registered _sidebars = new SidebarCollection ( self . registered _sidebars ) ;
/ * *
* On DOM ready , initialize some meta functionality independent of specific
* customizer controls .
* /
self . init = function ( ) {
this . showFirstSidebarIfRequested ( ) ;
this . availableWidgetsPanel . setup ( ) ;
} ;
wp . customize . bind ( 'ready' , function ( ) {
self . init ( ) ;
} ) ;
/ * *
* Listen for updates to which sidebars are rendered in the preview and toggle
* the customizer sections accordingly .
* /
self . showFirstSidebarIfRequested = function ( ) {
if ( ! /widget-customizer=open/ . test ( location . search ) ) {
return ;
}
var show _first _visible _sidebar = function ( ) {
self . registered _sidebars . off ( 'change:is_rendered' , show _first _visible _sidebar ) ;
2014-03-06 17:45:15 +01:00
var section , first _rendered _sidebar = self . registered _sidebars . find ( function ( sidebar ) {
2014-03-05 21:41:14 +01:00
return sidebar . get ( 'is_rendered' ) ;
} ) ;
if ( ! first _rendered _sidebar ) {
return ;
}
2014-03-06 17:45:15 +01:00
section = $ ( '#accordion-section-sidebar-widgets-' + first _rendered _sidebar . get ( 'id' ) ) ;
2014-03-05 21:41:14 +01:00
if ( ! section . hasClass ( 'open' ) ) {
section . find ( '.accordion-section-title' ) . trigger ( 'click' ) ;
}
section [ 0 ] . scrollIntoView ( ) ;
} ;
show _first _visible _sidebar = _ . debounce ( show _first _visible _sidebar , 100 ) ; // so only fires when all updated at end
self . registered _sidebars . on ( 'change:is_rendered' , show _first _visible _sidebar ) ;
} ;
/ * *
* Sidebar Widgets control
* Note that 'sidebar_widgets' must match the Sidebar _Widgets _WP _Customize _Control : : $type
* /
customize . controlConstructor . sidebar _widgets = customize . Control . extend ( {
/ * *
* Set up the control
* /
ready : function ( ) {
var control = this ;
control . control _section = control . container . closest ( '.control-section' ) ;
control . section _content = control . container . closest ( '.accordion-section-content' ) ;
control . _setupModel ( ) ;
control . _setupSortable ( ) ;
control . _setupAddition ( ) ;
control . _applyCardinalOrderClassNames ( ) ;
} ,
/ * *
* Update ordering of widget control forms when the setting is updated
* /
_setupModel : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
registered _sidebar = self . registered _sidebars . get ( control . params . sidebar _id ) ;
2014-03-05 21:41:14 +01:00
control . setting . bind ( function ( new _widget _ids , old _widget _ids ) {
2014-03-06 17:45:15 +01:00
var widget _form _controls ,
sidebar _widgets _add _control ,
final _control _containers ,
removed _widget _ids = _ ( old _widget _ids ) . difference ( new _widget _ids ) ;
2014-03-05 21:41:14 +01:00
// Filter out any persistent widget_ids for widgets which have been deactivated
new _widget _ids = _ ( new _widget _ids ) . filter ( function ( new _widget _id ) {
var parsed _widget _id = parse _widget _id ( new _widget _id ) ;
return ! ! self . available _widgets . findWhere ( { id _base : parsed _widget _id . id _base } ) ;
} ) ;
2014-03-06 17:45:15 +01:00
widget _form _controls = _ ( new _widget _ids ) . map ( function ( widget _id ) {
2014-03-05 21:41:14 +01:00
var widget _form _control = self . getWidgetFormControlForWidget ( widget _id ) ;
if ( ! widget _form _control ) {
widget _form _control = control . addWidget ( widget _id ) ;
}
return widget _form _control ;
} ) ;
// Sort widget controls to their new positions
widget _form _controls . sort ( function ( a , b ) {
2014-03-22 22:22:13 +01:00
var a _index = _ . indexOf ( new _widget _ids , a . params . widget _id ) ,
b _index = _ . indexOf ( new _widget _ids , b . params . widget _id ) ;
2014-03-05 21:41:14 +01:00
if ( a _index === b _index ) {
return 0 ;
}
return a _index < b _index ? - 1 : 1 ;
} ) ;
2014-03-06 17:45:15 +01:00
sidebar _widgets _add _control = control . section _content . find ( '.customize-control-sidebar_widgets' ) ;
2014-03-05 21:41:14 +01:00
// Append the controls to put them in the right order
2014-03-06 17:45:15 +01:00
final _control _containers = _ ( widget _form _controls ) . map ( function ( widget _form _controls ) {
2014-03-05 21:41:14 +01:00
return widget _form _controls . container [ 0 ] ;
} ) ;
// Re-sort widget form controls (including widgets form other sidebars newly moved here)
sidebar _widgets _add _control . before ( final _control _containers ) ;
control . _applyCardinalOrderClassNames ( ) ;
// If the widget was dragged into the sidebar, make sure the sidebar_id param is updated
_ ( widget _form _controls ) . each ( function ( widget _form _control ) {
widget _form _control . params . sidebar _id = control . params . sidebar _id ;
} ) ;
// Cleanup after widget removal
_ ( removed _widget _ids ) . each ( function ( removed _widget _id ) {
// Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
setTimeout ( function ( ) {
2014-03-06 17:45:15 +01:00
var is _present _in _another _sidebar = false ,
removed _control ,
was _dragged _to _another _sidebar ,
inactive _widgets ,
removed _id _base ,
widget ;
2014-03-05 21:41:14 +01:00
// Check if the widget is in another sidebar
wp . customize . each ( function ( other _setting ) {
2014-03-22 22:22:13 +01:00
if ( other _setting . id === control . setting . id || 0 !== _ . indexOf ( other _setting . id , 'sidebars_widgets[' ) || other _setting . id === 'sidebars_widgets[wp_inactive_widgets]' ) {
2014-03-05 21:41:14 +01:00
return ;
}
2014-03-06 17:45:15 +01:00
var other _sidebar _widgets = other _setting ( ) , i ;
2014-03-22 22:22:13 +01:00
i = _ . indexOf ( other _sidebar _widgets , removed _widget _id ) ;
2014-03-05 21:41:14 +01:00
if ( - 1 !== i ) {
is _present _in _another _sidebar = true ;
}
} ) ;
// If the widget is present in another sidebar, abort!
if ( is _present _in _another _sidebar ) {
return ;
}
2014-03-06 17:45:15 +01:00
removed _control = self . getWidgetFormControlForWidget ( removed _widget _id ) ;
2014-03-05 21:41:14 +01:00
// Detect if widget control was dragged to another sidebar
2014-03-06 17:45:15 +01:00
was _dragged _to _another _sidebar = (
2014-03-05 21:41:14 +01:00
removed _control &&
$ . contains ( document , removed _control . container [ 0 ] ) &&
! $ . contains ( control . section _content [ 0 ] , removed _control . container [ 0 ] )
) ;
// Delete any widget form controls for removed widgets
if ( removed _control && ! was _dragged _to _another _sidebar ) {
wp . customize . control . remove ( removed _control . id ) ;
removed _control . container . remove ( ) ;
}
// Move widget to inactive widgets sidebar (move it to trash) if has been previously saved
// This prevents the inactive widgets sidebar from overflowing with throwaway widgets
if ( self . saved _widget _ids [ removed _widget _id ] ) {
2014-03-06 17:45:15 +01:00
inactive _widgets = wp . customize . value ( 'sidebars_widgets[wp_inactive_widgets]' ) ( ) . slice ( ) ;
2014-03-05 21:41:14 +01:00
inactive _widgets . push ( removed _widget _id ) ;
wp . customize . value ( 'sidebars_widgets[wp_inactive_widgets]' ) ( _ ( inactive _widgets ) . unique ( ) ) ;
}
// Make old single widget available for adding again
2014-03-06 17:45:15 +01:00
removed _id _base = parse _widget _id ( removed _widget _id ) . id _base ;
widget = self . available _widgets . findWhere ( { id _base : removed _id _base } ) ;
2014-03-05 21:41:14 +01:00
if ( widget && ! widget . get ( 'is_multi' ) ) {
widget . set ( 'is_disabled' , false ) ;
}
} ) ;
} ) ;
} ) ;
// Update the model with whether or not the sidebar is rendered
self . previewer . bind ( 'rendered-sidebars' , function ( rendered _sidebars ) {
var is _rendered = ! ! rendered _sidebars [ control . params . sidebar _id ] ;
registered _sidebar . set ( 'is_rendered' , is _rendered ) ;
} ) ;
// Show the sidebar section when it becomes visible
registered _sidebar . on ( 'change:is_rendered' , function ( ) {
2014-03-06 17:45:15 +01:00
var section _selector = '#accordion-section-sidebar-widgets-' + this . get ( 'id' ) , section ;
section = $ ( section _selector ) ;
2014-03-05 21:41:14 +01:00
if ( this . get ( 'is_rendered' ) ) {
section . stop ( ) . slideDown ( function ( ) {
$ ( this ) . css ( 'height' , 'auto' ) ; // so that the .accordion-section-content won't overflow
} ) ;
} else {
// Make sure that hidden sections get closed first
if ( section . hasClass ( 'open' ) ) {
// it would be nice if accordionSwitch() in accordion.js was public
section . find ( '.accordion-section-title' ) . trigger ( 'click' ) ;
}
section . stop ( ) . slideUp ( ) ;
}
} ) ;
} ,
/ * *
* Allow widgets in sidebar to be re - ordered , and for the order to be previewed
* /
_setupSortable : function ( ) {
var control = this ;
control . is _reordering = false ;
/ * *
* Update widget order setting when controls are re - ordered
* /
control . section _content . sortable ( {
items : '> .customize-control-widget_form' ,
handle : '.widget-top' ,
axis : 'y' ,
connectWith : '.accordion-section-content:has(.customize-control-sidebar_widgets)' ,
update : function ( ) {
2014-03-06 17:45:15 +01:00
var widget _container _ids = control . section _content . sortable ( 'toArray' ) , widget _ids ;
widget _ids = $ . map ( widget _container _ids , function ( widget _container _id ) {
2014-03-05 21:41:14 +01:00
return $ ( '#' + widget _container _id ) . find ( ':input[name=widget-id]' ) . val ( ) ;
} ) ;
control . setting ( widget _ids ) ;
}
} ) ;
/ * *
* Expand other customizer sidebar section when dragging a control widget over it ,
* allowing the control to be dropped into another section
* /
control . control _section . find ( '.accordion-section-title' ) . droppable ( {
accept : '.customize-control-widget_form' ,
over : function ( ) {
if ( ! control . control _section . hasClass ( 'open' ) ) {
control . control _section . addClass ( 'open' ) ;
control . section _content . toggle ( false ) . slideToggle ( 150 , function ( ) {
control . section _content . sortable ( 'refreshPositions' ) ;
} ) ;
}
}
} ) ;
/ * *
* Keyboard - accessible reordering
* /
control . container . find ( '.reorder-toggle' ) . on ( 'click keydown' , function ( event ) {
if ( event . type === 'keydown' && ! ( event . which === 13 || event . which === 32 ) ) { // Enter or Spacebar
return ;
}
control . toggleReordering ( ! control . is _reordering ) ;
} ) ;
} ,
/ * *
* Set up UI for adding a new widget
* /
_setupAddition : function ( ) {
var control = this ;
control . container . find ( '.add-new-widget' ) . on ( 'click keydown' , function ( event ) {
if ( event . type === 'keydown' && ! ( event . which === 13 || event . which === 32 ) ) { // Enter or Spacebar
return ;
}
if ( control . section _content . hasClass ( 'reordering' ) ) {
return ;
}
// @todo Use an control.is_adding state
if ( ! $ ( 'body' ) . hasClass ( 'adding-widget' ) ) {
self . availableWidgetsPanel . open ( control ) ;
} else {
self . availableWidgetsPanel . close ( ) ;
}
} ) ;
} ,
/ * *
* Add classes to the widget _form controls to assist with styling
* /
_applyCardinalOrderClassNames : function ( ) {
var control = this ;
control . section _content . find ( '.customize-control-widget_form' )
. removeClass ( 'first-widget' )
. removeClass ( 'last-widget' )
. find ( '.move-widget-down, .move-widget-up' ) . prop ( 'tabIndex' , 0 ) ;
control . section _content . find ( '.customize-control-widget_form:first' )
. addClass ( 'first-widget' )
. find ( '.move-widget-up' ) . prop ( 'tabIndex' , - 1 ) ;
control . section _content . find ( '.customize-control-widget_form:last' )
. addClass ( 'last-widget' )
. find ( '.move-widget-down' ) . prop ( 'tabIndex' , - 1 ) ;
} ,
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Begin public API methods
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * *
* Enable / disable the reordering UI
*
* @ param { Boolean } toggle to enable / disable reordering
* /
toggleReordering : function ( toggle ) {
var control = this ;
toggle = Boolean ( toggle ) ;
if ( toggle === control . section _content . hasClass ( 'reordering' ) ) {
return ;
}
control . is _reordering = toggle ;
control . section _content . toggleClass ( 'reordering' , toggle ) ;
if ( toggle ) {
_ ( control . getWidgetFormControls ( ) ) . each ( function ( form _control ) {
form _control . collapseForm ( ) ;
} ) ;
}
} ,
/ * *
* @ return { wp . customize . controlConstructor . widget _form [ ] }
* /
getWidgetFormControls : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , form _controls ;
form _controls = _ ( control . setting ( ) ) . map ( function ( widget _id ) {
var setting _id = widget _id _to _setting _id ( widget _id ) ,
form _control = customize . control ( setting _id ) ;
2014-03-05 21:41:14 +01:00
if ( ! form _control ) {
throw new Error ( 'Unable to find widget_form control for ' + widget _id ) ;
}
return form _control ;
} ) ;
return form _controls ;
} ,
/ * *
* @ param { string } widget _id or an id _base for adding a previously non - existing widget
* @ returns { object } widget _form control instance
* /
addWidget : function ( widget _id ) {
2014-03-06 17:45:15 +01:00
var control = this ,
control _html ,
customize _control _type = 'widget_form' ,
customize _control ,
parsed _widget _id = parse _widget _id ( widget _id ) ,
widget _number = parsed _widget _id . number ,
widget _id _base = parsed _widget _id . id _base ,
widget = self . available _widgets . findWhere ( { id _base : widget _id _base } ) ,
setting _id ,
is _existing _widget ,
Constructor ,
widget _form _control ,
sidebar _widgets ,
setting _args ;
2014-03-05 21:41:14 +01:00
if ( ! widget ) {
throw new Error ( 'Widget unexpectedly not found.' ) ;
}
if ( widget _number && ! widget . get ( 'is_multi' ) ) {
throw new Error ( 'Did not expect a widget number to be supplied for a non-multi widget' ) ;
}
// Set up new multi widget
if ( widget . get ( 'is_multi' ) && ! widget _number ) {
widget . set ( 'multi_number' , widget . get ( 'multi_number' ) + 1 ) ;
widget _number = widget . get ( 'multi_number' ) ;
}
2014-03-06 17:45:15 +01:00
control _html = $ ( '#widget-tpl-' + widget . get ( 'id' ) ) . html ( ) ;
2014-03-05 21:41:14 +01:00
if ( widget . get ( 'is_multi' ) ) {
control _html = control _html . replace ( /<[^<>]+>/g , function ( m ) {
return m . replace ( /__i__|%i%/g , widget _number ) ;
} ) ;
} else {
widget . set ( 'is_disabled' , true ) ; // Prevent single widget from being added again now
}
2014-03-06 17:45:15 +01:00
customize _control = $ ( '<li></li>' ) ;
2014-03-05 21:41:14 +01:00
customize _control . addClass ( 'customize-control' ) ;
customize _control . addClass ( 'customize-control-' + customize _control _type ) ;
customize _control . append ( $ ( control _html ) ) ;
customize _control . find ( '> .widget-icon' ) . remove ( ) ;
if ( widget . get ( 'is_multi' ) ) {
customize _control . find ( 'input[name="widget_number"]' ) . val ( widget _number ) ;
customize _control . find ( 'input[name="multi_number"]' ) . val ( widget _number ) ;
}
widget _id = customize _control . find ( '[name="widget-id"]' ) . val ( ) ;
customize _control . hide ( ) ; // to be slid-down below
2014-03-06 17:45:15 +01:00
setting _id = 'widget_' + widget . get ( 'id_base' ) ;
2014-03-05 21:41:14 +01:00
if ( widget . get ( 'is_multi' ) ) {
setting _id += '[' + widget _number + ']' ;
}
customize _control . attr ( 'id' , 'customize-control-' + setting _id . replace ( /\]/g , '' ) . replace ( /\[/g , '-' ) ) ;
control . container . after ( customize _control ) ;
// Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget)
2014-03-06 17:45:15 +01:00
is _existing _widget = wp . customize . has ( setting _id ) ;
2014-03-05 21:41:14 +01:00
if ( ! is _existing _widget ) {
2014-03-06 17:45:15 +01:00
setting _args = {
2014-03-05 21:41:14 +01:00
transport : 'refresh' ,
previewer : control . setting . previewer
} ;
wp . customize . create ( setting _id , setting _id , { } , setting _args ) ;
}
2014-03-06 17:45:15 +01:00
Constructor = wp . customize . controlConstructor [ customize _control _type ] ;
widget _form _control = new Constructor ( setting _id , {
2014-03-05 21:41:14 +01:00
params : {
settings : {
'default' : setting _id
} ,
sidebar _id : control . params . sidebar _id ,
widget _id : widget _id ,
widget _id _base : widget . get ( 'id_base' ) ,
type : customize _control _type ,
is _new : ! is _existing _widget ,
width : widget . get ( 'width' ) ,
height : widget . get ( 'height' ) ,
is _wide : widget . get ( 'is_wide' )
} ,
previewer : control . setting . previewer
} ) ;
wp . customize . control . add ( setting _id , widget _form _control ) ;
// Make sure widget is removed from the other sidebars
wp . customize . each ( function ( other _setting ) {
if ( other _setting . id === control . setting . id ) {
return ;
}
2014-03-22 22:22:13 +01:00
if ( 0 !== _ . indexOf ( other _setting . id , 'sidebars_widgets[' ) ) {
2014-03-05 21:41:14 +01:00
return ;
}
2014-03-06 17:45:15 +01:00
var other _sidebar _widgets = other _setting ( ) . slice ( ) , i ;
2014-03-22 22:22:13 +01:00
i = _ . indexOf ( other _sidebar _widgets , widget _id ) ;
2014-03-05 21:41:14 +01:00
if ( - 1 !== i ) {
other _sidebar _widgets . splice ( i ) ;
other _setting ( other _sidebar _widgets ) ;
}
} ) ;
// Add widget to this sidebar
2014-03-06 17:45:15 +01:00
sidebar _widgets = control . setting ( ) . slice ( ) ;
2014-03-22 22:22:13 +01:00
if ( - 1 === _ . indexOf ( sidebar _widgets , widget _id ) ) {
2014-03-05 21:41:14 +01:00
sidebar _widgets . push ( widget _id ) ;
control . setting ( sidebar _widgets ) ;
}
customize _control . slideDown ( function ( ) {
if ( is _existing _widget ) {
widget _form _control . expandForm ( ) ;
widget _form _control . updateWidget ( {
instance : widget _form _control . setting ( ) ,
complete : function ( error ) {
if ( error ) {
throw error ;
}
widget _form _control . focus ( ) ;
}
} ) ;
} else {
widget _form _control . focus ( ) ;
}
} ) ;
return widget _form _control ;
}
} ) ;
/ * *
* Widget Form control
* Note that 'widget_form' must match the Widget _Form _WP _Customize _Control : : $type
* /
customize . controlConstructor . widget _form = customize . Control . extend ( {
/ * *
* Set up the control
* /
ready : function ( ) {
var control = this ;
control . _setupModel ( ) ;
control . _setupWideWidget ( ) ;
control . _setupControlToggle ( ) ;
control . _setupWidgetTitle ( ) ;
control . _setupReorderUI ( ) ;
control . _setupHighlightEffects ( ) ;
control . _setupUpdateUI ( ) ;
control . _setupRemoveUI ( ) ;
control . hook ( 'init' ) ;
} ,
/ * *
* Hooks for widgets to support living in the customizer control
* /
hooks : {
_default : { } ,
rss : {
formUpdated : function ( serialized _form ) {
2014-03-06 17:45:15 +01:00
var control = this ,
old _widget _error = control . container . find ( '.widget-error:first' ) ,
new _widget _error = serialized _form . find ( '.widget-error:first' ) ;
2014-03-05 21:41:14 +01:00
if ( old _widget _error . length && new _widget _error . length ) {
old _widget _error . replaceWith ( new _widget _error ) ;
} else if ( old _widget _error . length ) {
old _widget _error . remove ( ) ;
} else if ( new _widget _error . length ) {
control . container . find ( '.widget-content' ) . prepend ( new _widget _error ) ;
}
}
}
} ,
/ * *
* Trigger an 'action' which a specific widget type can handle
*
* @ param name
* /
hook : function ( name ) {
2014-03-06 17:45:15 +01:00
var args = Array . prototype . slice . call ( arguments , 1 ) , handler ;
2014-03-05 21:41:14 +01:00
if ( this . hooks [ this . params . widget _id _base ] && this . hooks [ this . params . widget _id _base ] [ name ] ) {
handler = this . hooks [ this . params . widget _id _base ] [ name ] ;
} else if ( this . hooks . _default [ name ] ) {
handler = this . hooks . _default [ name ] ;
}
if ( handler ) {
handler . apply ( this , args ) ;
}
} ,
/ * *
* Handle changes to the setting
* /
_setupModel : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , remember _saved _widget _id ;
2014-03-05 21:41:14 +01:00
// Remember saved widgets so we know which to trash (move to inactive widgets sidebar)
2014-03-06 17:45:15 +01:00
remember _saved _widget _id = function ( ) {
2014-03-05 21:41:14 +01:00
self . saved _widget _ids [ control . params . widget _id ] = true ;
} ;
wp . customize . bind ( 'ready' , remember _saved _widget _id ) ;
wp . customize . bind ( 'saved' , remember _saved _widget _id ) ;
control . _update _count = 0 ;
control . is _widget _updating = false ;
// Update widget whenever model changes
control . setting . bind ( function ( to , from ) {
if ( ! _ ( from ) . isEqual ( to ) && ! control . is _widget _updating ) {
control . updateWidget ( { instance : to } ) ;
}
} ) ;
} ,
/ * *
* Add special behaviors for wide widget controls
* /
_setupWideWidget : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
widget _inside ,
customize _sidebar ,
position _widget ,
theme _controls _container ;
2014-03-05 21:41:14 +01:00
if ( ! control . params . is _wide ) {
return ;
}
2014-03-06 17:45:15 +01:00
widget _inside = control . container . find ( '.widget-inside' ) ;
customize _sidebar = $ ( '.wp-full-overlay-sidebar-content:first' ) ;
2014-03-05 21:41:14 +01:00
control . container . addClass ( 'wide-widget-control' ) ;
control . container . find ( '.widget-content:first' ) . css ( {
'min-width' : control . params . width ,
'min-height' : control . params . height
} ) ;
/ * *
* Keep the widget - inside positioned so the top of fixed - positioned
* element is at the same top position as the widget - top . When the
* widget - top is scrolled out of view , keep the widget - top in view ;
* likewise , don ' t allow the widget to drop off the bottom of the window .
* /
2014-03-06 17:45:15 +01:00
position _widget = function ( ) {
var offset _top = control . container . offset ( ) . top ,
height ,
top ,
max _top ;
height = widget _inside . outerHeight ( ) ;
top = Math . max ( offset _top , 0 ) ;
max _top = $ ( window ) . height ( ) - height ;
2014-03-05 21:41:14 +01:00
top = Math . min ( top , max _top ) ;
widget _inside . css ( 'top' , top ) ;
} ;
2014-03-06 17:45:15 +01:00
theme _controls _container = $ ( '#customize-theme-controls' ) ;
2014-03-05 21:41:14 +01:00
control . container . on ( 'expand' , function ( ) {
customize _sidebar . on ( 'scroll' , position _widget ) ;
$ ( window ) . on ( 'resize' , position _widget ) ;
theme _controls _container . on ( 'expanded collapsed' , position _widget ) ;
position _widget ( ) ;
} ) ;
control . container . on ( 'collapsed' , function ( ) {
customize _sidebar . off ( 'scroll' , position _widget ) ;
theme _controls _container . off ( 'expanded collapsed' , position _widget ) ;
$ ( window ) . off ( 'resize' , position _widget ) ;
} ) ;
// Reposition whenever a sidebar's widgets are changed
wp . customize . each ( function ( setting ) {
2014-03-22 22:22:13 +01:00
if ( 0 === _ . indexOf ( setting . id , 'sidebars_widgets[' ) ) {
2014-03-05 21:41:14 +01:00
setting . bind ( function ( ) {
if ( control . container . hasClass ( 'expanded' ) ) {
position _widget ( ) ;
}
} ) ;
}
} ) ;
} ,
/ * *
* Show / hide the control when clicking on the form title , when clicking
* the close button
* /
_setupControlToggle : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , close _btn ;
2014-03-05 21:41:14 +01:00
control . container . find ( '.widget-top' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
var sidebar _widgets _control = control . getSidebarWidgetsControl ( ) ;
if ( sidebar _widgets _control . is _reordering ) {
return ;
}
control . toggleForm ( ) ;
} ) ;
2014-03-06 17:45:15 +01:00
close _btn = control . container . find ( '.widget-control-close' ) ;
2014-03-05 21:41:14 +01:00
// @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
close _btn . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
control . collapseForm ( ) ;
control . container . find ( '.widget-top .widget-action:first' ) . focus ( ) ; // keyboard accessibility
} ) ;
} ,
/ * *
* Update the title of the form if a title field is entered
* /
_setupWidgetTitle : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , update _title ;
update _title = function ( ) {
var title = control . setting ( ) . title ,
in _widget _title = control . container . find ( '.in-widget-title' ) ;
2014-03-05 21:41:14 +01:00
if ( title ) {
in _widget _title . text ( ': ' + title ) ;
} else {
in _widget _title . text ( '' ) ;
}
} ;
control . setting . bind ( update _title ) ;
update _title ( ) ;
} ,
/ * *
* Set up the widget - reorder - nav
* /
_setupReorderUI : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
select _sidebar _item ,
move _widget _area ,
reorder _nav ,
update _available _sidebars ;
2014-03-05 21:41:14 +01:00
/ * *
* select the provided sidebar list item in the move widget area
*
* @ param { jQuery } li
* /
2014-03-06 17:45:15 +01:00
select _sidebar _item = function ( li ) {
2014-03-05 21:41:14 +01:00
li . siblings ( '.selected' ) . removeClass ( 'selected' ) ;
li . addClass ( 'selected' ) ;
var is _self _sidebar = ( li . data ( 'id' ) === control . params . sidebar _id ) ;
control . container . find ( '.move-widget-btn' ) . prop ( 'disabled' , is _self _sidebar ) ;
} ;
/ * *
* Add the widget reordering elements to the widget control
* /
control . container . find ( '.widget-title-action' ) . after ( $ ( self . tpl . widget _reorder _nav ) ) ;
2014-03-06 17:45:15 +01:00
move _widget _area = $ (
2014-03-05 21:41:14 +01:00
_ . template ( self . tpl . move _widget _area , {
sidebars : _ ( self . registered _sidebars . toArray ( ) ) . pluck ( 'attributes' )
} )
) ;
control . container . find ( '.widget-top' ) . after ( move _widget _area ) ;
/ * *
* Update available sidebars when their rendered state changes
* /
2014-03-06 17:45:15 +01:00
update _available _sidebars = function ( ) {
var sidebar _items = move _widget _area . find ( 'li' ) , self _sidebar _item ;
self _sidebar _item = sidebar _items . filter ( function ( ) {
2014-03-05 21:41:14 +01:00
return $ ( this ) . data ( 'id' ) === control . params . sidebar _id ;
} ) ;
sidebar _items . each ( function ( ) {
2014-03-06 17:45:15 +01:00
var li = $ ( this ) ,
sidebar _id ,
sidebar _model ;
sidebar _id = li . data ( 'id' ) ;
sidebar _model = self . registered _sidebars . get ( sidebar _id ) ;
2014-03-05 21:41:14 +01:00
li . toggle ( sidebar _model . get ( 'is_rendered' ) ) ;
if ( li . hasClass ( 'selected' ) && ! sidebar _model . get ( 'is_rendered' ) ) {
select _sidebar _item ( self _sidebar _item ) ;
}
} ) ;
} ;
update _available _sidebars ( ) ;
self . registered _sidebars . on ( 'change:is_rendered' , update _available _sidebars ) ;
/ * *
* Handle clicks for up / down / move on the reorder nav
* /
2014-03-06 17:45:15 +01:00
reorder _nav = control . container . find ( '.widget-reorder-nav' ) ;
2014-03-05 21:41:14 +01:00
reorder _nav . find ( '.move-widget, .move-widget-down, .move-widget-up' ) . on ( 'click keypress' , function ( event ) {
if ( event . type === 'keypress' && ( event . which !== 13 && event . which !== 32 ) ) {
return ;
}
$ ( this ) . focus ( ) ;
if ( $ ( this ) . is ( '.move-widget' ) ) {
control . toggleWidgetMoveArea ( ) ;
} else {
2014-03-06 17:45:15 +01:00
var is _move _down = $ ( this ) . is ( '.move-widget-down' ) ,
is _move _up = $ ( this ) . is ( '.move-widget-up' ) ,
i = control . getWidgetSidebarPosition ( ) ;
2014-03-05 21:41:14 +01:00
if ( ( is _move _up && i === 0 ) || ( is _move _down && i === control . getSidebarWidgetsControl ( ) . setting ( ) . length - 1 ) ) {
return ;
}
if ( is _move _up ) {
control . moveUp ( ) ;
} else {
control . moveDown ( ) ;
}
$ ( this ) . focus ( ) ; // re-focus after the container was moved
}
} ) ;
/ * *
* Handle selecting a sidebar to move to
* /
control . container . find ( '.widget-area-select' ) . on ( 'click keypress' , 'li' , function ( e ) {
if ( event . type === 'keypress' && ( event . which !== 13 && event . which !== 32 ) ) {
return ;
}
e . preventDefault ( ) ;
select _sidebar _item ( $ ( this ) ) ;
} ) ;
/ * *
* Move widget to another sidebar
* /
control . container . find ( '.move-widget-btn' ) . click ( function ( ) {
control . getSidebarWidgetsControl ( ) . toggleReordering ( false ) ;
2014-03-06 17:45:15 +01:00
var old _sidebar _id = control . params . sidebar _id ,
new _sidebar _id = control . container . find ( '.widget-area-select li.selected' ) . data ( 'id' ) ,
old _sidebar _widgets _setting ,
new _sidebar _widgets _setting ,
old _sidebar _widget _ids ,
new _sidebar _widget _ids ,
i ;
2014-03-05 21:41:14 +01:00
2014-03-06 17:45:15 +01:00
old _sidebar _widgets _setting = customize ( 'sidebars_widgets[' + old _sidebar _id + ']' ) ;
new _sidebar _widgets _setting = customize ( 'sidebars_widgets[' + new _sidebar _id + ']' ) ;
old _sidebar _widget _ids = Array . prototype . slice . call ( old _sidebar _widgets _setting ( ) ) ;
new _sidebar _widget _ids = Array . prototype . slice . call ( new _sidebar _widgets _setting ( ) ) ;
i = control . getWidgetSidebarPosition ( ) ;
2014-03-05 21:41:14 +01:00
old _sidebar _widget _ids . splice ( i , 1 ) ;
new _sidebar _widget _ids . push ( control . params . widget _id ) ;
old _sidebar _widgets _setting ( old _sidebar _widget _ids ) ;
new _sidebar _widgets _setting ( new _sidebar _widget _ids ) ;
control . focus ( ) ;
} ) ;
} ,
/ * *
* Highlight widgets in preview when interacted with in the customizer
* /
_setupHighlightEffects : function ( ) {
var control = this ;
// Highlight whenever hovering or clicking over the form
control . container . on ( 'mouseenter click' , function ( ) {
control . highlightPreviewWidget ( ) ;
} ) ;
// Highlight when the setting is updated
control . setting . bind ( function ( ) {
control . scrollPreviewWidgetIntoView ( ) ;
control . highlightPreviewWidget ( ) ;
} ) ;
// Highlight when the widget form is expanded
control . container . on ( 'expand' , function ( ) {
control . scrollPreviewWidgetIntoView ( ) ;
} ) ;
} ,
/ * *
* Set up event handlers for widget updating
* /
_setupUpdateUI : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
widget _content ,
save _btn ,
2014-03-14 20:16:14 +01:00
update _widget _debounced ;
2014-03-05 21:41:14 +01:00
2014-03-06 17:45:15 +01:00
widget _content = control . container . find ( '.widget-content' ) ;
2014-03-05 21:41:14 +01:00
// Configure update button
2014-03-06 17:45:15 +01:00
save _btn = control . container . find ( '.widget-control-save' ) ;
2014-03-05 21:41:14 +01:00
save _btn . val ( self . i18n . save _btn _label ) ;
save _btn . attr ( 'title' , self . i18n . save _btn _tooltip ) ;
save _btn . removeClass ( 'button-primary' ) . addClass ( 'button-secondary' ) ;
save _btn . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
control . updateWidget ( ) ;
} ) ;
2014-03-14 20:16:14 +01:00
update _widget _debounced = _ . debounce ( function ( ) {
2014-03-05 21:41:14 +01:00
// @todo For compatibility with other plugins, should we trigger a click event? What about form submit event?
control . updateWidget ( ) ;
} , 250 ) ;
// Trigger widget form update when hitting Enter within an input
control . container . find ( '.widget-content' ) . on ( 'keydown' , 'input' , function ( e ) {
if ( 13 === e . which ) { // Enter
e . preventDefault ( ) ;
control . updateWidget ( { ignore _active _element : true } ) ;
}
} ) ;
// Handle widgets that support live previews
widget _content . on ( 'change input propertychange' , ':input' , function ( e ) {
2014-03-14 20:16:14 +01:00
if ( e . type === 'change' ) {
control . updateWidget ( ) ;
} else if ( this . checkValidity && this . checkValidity ( ) ) {
update _widget _debounced ( ) ;
2014-03-05 21:41:14 +01:00
}
} ) ;
// Remove loading indicators when the setting is saved and the preview updates
control . setting . previewer . channel . bind ( 'synced' , function ( ) {
control . container . removeClass ( 'previewer-loading' ) ;
} ) ;
self . previewer . bind ( 'widget-updated' , function ( updated _widget _id ) {
if ( updated _widget _id === control . params . widget _id ) {
control . container . removeClass ( 'previewer-loading' ) ;
}
} ) ;
// Update widget control to indicate whether it is currently rendered (cf. Widget Visibility)
self . previewer . bind ( 'rendered-widgets' , function ( rendered _widgets ) {
var is _rendered = ! ! rendered _widgets [ control . params . widget _id ] ;
control . container . toggleClass ( 'widget-rendered' , is _rendered ) ;
} ) ;
} ,
/ * *
* Set up event handlers for widget removal
* /
_setupRemoveUI : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
remove _btn ,
replace _delete _with _remove ;
2014-03-05 21:41:14 +01:00
// Configure remove button
2014-03-06 17:45:15 +01:00
remove _btn = control . container . find ( 'a.widget-control-remove' ) ;
2014-03-05 21:41:14 +01:00
// @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
remove _btn . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
// Find an adjacent element to add focus to when this widget goes away
var adjacent _focus _target ;
if ( control . container . next ( ) . is ( '.customize-control-widget_form' ) ) {
adjacent _focus _target = control . container . next ( ) . find ( '.widget-action:first' ) ;
} else if ( control . container . prev ( ) . is ( '.customize-control-widget_form' ) ) {
adjacent _focus _target = control . container . prev ( ) . find ( '.widget-action:first' ) ;
} else {
adjacent _focus _target = control . container . next ( '.customize-control-sidebar_widgets' ) . find ( '.add-new-widget:first' ) ;
}
control . container . slideUp ( function ( ) {
2014-03-06 17:45:15 +01:00
var sidebars _widgets _control = self . getSidebarWidgetControlContainingWidget ( control . params . widget _id ) ,
sidebar _widget _ids ,
i ;
2014-03-05 21:41:14 +01:00
if ( ! sidebars _widgets _control ) {
throw new Error ( 'Unable to find sidebars_widgets_control' ) ;
}
2014-03-06 17:45:15 +01:00
sidebar _widget _ids = sidebars _widgets _control . setting ( ) . slice ( ) ;
2014-03-22 22:22:13 +01:00
i = _ . indexOf ( sidebar _widget _ids , control . params . widget _id ) ;
2014-03-05 21:41:14 +01:00
if ( - 1 === i ) {
throw new Error ( 'Widget is not in sidebar' ) ;
}
sidebar _widget _ids . splice ( i , 1 ) ;
sidebars _widgets _control . setting ( sidebar _widget _ids ) ;
adjacent _focus _target . focus ( ) ; // keyboard accessibility
} ) ;
} ) ;
2014-03-06 17:45:15 +01:00
replace _delete _with _remove = function ( ) {
2014-03-05 21:41:14 +01:00
remove _btn . text ( self . i18n . remove _btn _label ) ; // wp_widget_control() outputs the link as "Delete"
remove _btn . attr ( 'title' , self . i18n . remove _btn _tooltip ) ;
} ;
if ( control . params . is _new ) {
wp . customize . bind ( 'saved' , replace _delete _with _remove ) ;
} else {
replace _delete _with _remove ( ) ;
}
} ,
/ * *
* Iterate over supplied inputs and create a signature string for all of them together .
* This string can be used to compare whether or not the form has all of the same fields .
*
* @ param { jQuery } inputs
* @ returns { string }
* @ private
* /
_getInputsSignature : function ( inputs ) {
var inputs _signatures = _ ( inputs ) . map ( function ( input ) {
input = $ ( input ) ;
var signature _parts ;
if ( input . is ( 'option' ) ) {
signature _parts = [ input . prop ( 'nodeName' ) , input . prop ( 'value' ) ] ;
} else if ( input . is ( ':checkbox, :radio' ) ) {
signature _parts = [ input . prop ( 'type' ) , input . attr ( 'id' ) , input . attr ( 'name' ) , input . prop ( 'value' ) ] ;
} else {
signature _parts = [ input . prop ( 'nodeName' ) , input . attr ( 'id' ) , input . attr ( 'name' ) , input . attr ( 'type' ) ] ;
}
return signature _parts . join ( ',' ) ;
} ) ;
return inputs _signatures . join ( ';' ) ;
} ,
/ * *
* Get the property that represents the state of an input .
*
* @ param { jQuery | DOMElement } input
* @ returns { string }
* @ private
* /
_getInputStatePropertyName : function ( input ) {
input = $ ( input ) ;
if ( input . is ( ':radio, :checkbox' ) ) {
return 'checked' ;
} else if ( input . is ( 'option' ) ) {
return 'selected' ;
} else {
return 'value' ;
}
} ,
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Begin public API methods
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * *
* @ return { wp . customize . controlConstructor . sidebar _widgets [ ] }
* /
getSidebarWidgetsControl : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , setting _id , sidebar _widgets _control ;
setting _id = 'sidebars_widgets[' + control . params . sidebar _id + ']' ;
sidebar _widgets _control = customize . control ( setting _id ) ;
2014-03-05 21:41:14 +01:00
if ( ! sidebar _widgets _control ) {
throw new Error ( 'Unable to locate sidebar_widgets control for ' + control . params . sidebar _id ) ;
}
return sidebar _widgets _control ;
} ,
/ * *
* Submit the widget form via Ajax and get back the updated instance ,
* along with the new widget control form to render .
*
* @ param { object } [ args ]
* @ param { Object | null } [ args . instance = null ] When the model changes , the instance is sent here ; otherwise , the inputs from the form are used
* @ 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 .
* @ param { Boolean } [ args . ignore _active _element = false ] Whether or not updating a field will be deferred if focus is still on the element .
* /
updateWidget : function ( args ) {
2014-03-06 17:45:15 +01:00
var control = this ,
instance _override ,
complete _callback ,
update _number ,
widget _content ,
element _id _to _refocus = null ,
active _input _selection _start = null ,
active _input _selection _end = null ,
2014-03-14 20:16:14 +01:00
params ,
2014-03-06 17:45:15 +01:00
data ,
inputs ,
2014-03-14 20:16:14 +01:00
processing ,
2014-03-06 17:45:15 +01:00
jqxhr ;
2014-03-05 21:41:14 +01:00
args = $ . extend ( {
instance : null ,
complete : null ,
ignore _active _element : false
} , args ) ;
2014-03-06 17:45:15 +01:00
instance _override = args . instance ;
complete _callback = args . complete ;
2014-03-05 21:41:14 +01:00
control . _update _count += 1 ;
2014-03-06 17:45:15 +01:00
update _number = control . _update _count ;
2014-03-05 21:41:14 +01:00
2014-03-06 17:45:15 +01:00
widget _content = control . container . find ( '.widget-content' ) ;
2014-03-05 21:41:14 +01:00
2014-03-22 21:55:18 +01:00
// Remove a previous error message
widget _content . find ( '.widget-error' ) . remove ( ) ;
2014-03-05 21:41:14 +01:00
// @todo Support more selectors than IDs?
if ( $ . contains ( control . container [ 0 ] , document . activeElement ) && $ ( document . activeElement ) . is ( '[id]' ) ) {
element _id _to _refocus = $ ( document . activeElement ) . prop ( 'id' ) ;
// @todo IE8 support: http://stackoverflow.com/a/4207763/93579
try {
active _input _selection _start = document . activeElement . selectionStart ;
active _input _selection _end = document . activeElement . selectionEnd ;
}
catch ( e ) { } // catch InvalidStateError in case of checkboxes
}
control . container . addClass ( 'widget-form-loading' ) ;
control . container . addClass ( 'previewer-loading' ) ;
2014-03-14 20:16:14 +01:00
processing = wp . customize . state ( 'processing' ) ;
processing ( processing ( ) + 1 ) ;
2014-03-05 21:41:14 +01:00
2014-03-06 17:45:15 +01:00
params = { } ;
2014-03-05 21:41:14 +01:00
params . action = self . update _widget _ajax _action ;
2014-03-13 19:38:14 +01:00
params . wp _customize = 'on' ;
2014-03-05 21:41:14 +01:00
params [ self . update _widget _nonce _post _key ] = self . update _widget _nonce _value ;
2014-03-06 17:45:15 +01:00
data = $ . param ( params ) ;
inputs = widget _content . find ( ':input, option' ) ;
2014-03-05 21:41:14 +01:00
// Store the value we're submitting in data so that when the response comes back,
// we know if it got sanitized; if there is no difference in the sanitized value,
// then we do not need to touch the UI and mess up the user's ongoing editing.
inputs . each ( function ( ) {
2014-03-06 17:45:15 +01:00
var input = $ ( this ) ,
property = control . _getInputStatePropertyName ( this ) ;
2014-03-05 21:41:14 +01:00
input . data ( 'state' + update _number , input . prop ( property ) ) ;
} ) ;
if ( instance _override ) {
data += '&' + $ . param ( { 'sanitized_widget_setting' : JSON . stringify ( instance _override ) } ) ;
} else {
data += '&' + inputs . serialize ( ) ;
}
data += '&' + widget _content . find ( '~ :input' ) . serialize ( ) ;
2014-03-06 17:45:15 +01:00
jqxhr = $ . post ( wp . ajax . settings . url , data , function ( r ) {
var message ,
sanitized _form ,
sanitized _inputs ,
has _same _inputs _in _response ,
is _instance _identical ;
2014-03-22 21:55:18 +01:00
// Check if the user is logged out.
if ( '0' === r ) {
self . previewer . preview . iframe . hide ( ) ;
self . previewer . login ( ) . done ( function ( ) {
control . updateWidget ( args ) ;
self . previewer . preview . iframe . show ( ) ;
} ) ;
return ;
}
// Check for cheaters.
if ( '-1' === r ) {
self . previewer . cheatin ( ) ;
return ;
}
2014-03-05 21:41:14 +01:00
if ( r . success ) {
2014-03-06 17:45:15 +01:00
sanitized _form = $ ( '<div>' + r . data . form + '</div>' ) ;
2014-03-05 21:41:14 +01:00
control . hook ( 'formUpdate' , sanitized _form ) ;
2014-03-06 17:45:15 +01:00
sanitized _inputs = sanitized _form . find ( ':input, option' ) ;
has _same _inputs _in _response = control . _getInputsSignature ( inputs ) === control . _getInputsSignature ( sanitized _inputs ) ;
2014-03-05 21:41:14 +01:00
if ( has _same _inputs _in _response ) {
inputs . each ( function ( i ) {
2014-03-06 17:45:15 +01:00
var input = $ ( this ) ,
sanitized _input = $ ( sanitized _inputs [ i ] ) ,
property = control . _getInputStatePropertyName ( this ) ,
state ,
sanitized _state ;
state = input . data ( 'state' + update _number ) ;
sanitized _state = sanitized _input . prop ( property ) ;
2014-03-05 21:41:14 +01:00
input . data ( 'sanitized' , sanitized _state ) ;
if ( state !== sanitized _state ) {
// Only update now if not currently focused on it,
// so that we don't cause the cursor
// it will be updated upon the change event
if ( args . ignore _active _element || ! input . is ( document . activeElement ) ) {
input . prop ( property , sanitized _state ) ;
}
control . hook ( 'unsanitaryField' , input , sanitized _state , state ) ;
} else {
control . hook ( 'sanitaryField' , input , state ) ;
}
} ) ;
control . hook ( 'formUpdated' , sanitized _form ) ;
} else {
widget _content . html ( sanitized _form . html ( ) ) ;
if ( element _id _to _refocus ) {
// not using jQuery selector so we don't have to worry about escaping IDs with brackets and other characters
$ ( document . getElementById ( element _id _to _refocus ) )
. prop ( {
selectionStart : active _input _selection _start ,
selectionEnd : active _input _selection _end
} )
. focus ( ) ;
}
control . hook ( 'formRefreshed' ) ;
}
/ * *
* If the old instance is identical to the new one , there is nothing new
* needing to be rendered , and so we can preempt the event for the
* preview finishing loading .
* /
2014-03-06 17:45:15 +01:00
is _instance _identical = _ ( control . setting ( ) ) . isEqual ( r . data . instance ) ;
2014-03-22 21:55:18 +01:00
if ( ! is _instance _identical ) {
2014-03-05 21:41:14 +01:00
control . is _widget _updating = true ; // suppress triggering another updateWidget
control . setting ( r . data . instance ) ;
control . is _widget _updating = false ;
}
if ( complete _callback ) {
complete _callback . call ( control , null , { no _change : is _instance _identical , ajax _finished : true } ) ;
}
} else {
2014-03-22 21:55:18 +01:00
message = self . i18n . error ;
2014-03-05 21:41:14 +01:00
if ( r . data && r . data . message ) {
message = r . data . message ;
}
if ( complete _callback ) {
complete _callback . call ( control , message ) ;
} else {
2014-03-22 21:55:18 +01:00
widget _content . prepend ( '<p class="widget-error"><strong>' + message + '</strong></p>' ) ;
2014-03-05 21:41:14 +01:00
}
}
} ) ;
jqxhr . fail ( function ( jqXHR , textStatus ) {
if ( complete _callback ) {
complete _callback . call ( control , textStatus ) ;
}
} ) ;
jqxhr . always ( function ( ) {
2014-03-22 21:55:18 +01:00
control . container . removeClass ( 'previewer-loading' ) ;
2014-03-05 21:41:14 +01:00
control . container . removeClass ( 'widget-form-loading' ) ;
inputs . each ( function ( ) {
$ ( this ) . removeData ( 'state' + update _number ) ;
} ) ;
2014-03-14 20:16:14 +01:00
processing ( processing ( ) - 1 ) ;
2014-03-05 21:41:14 +01:00
} ) ;
} ,
/ * *
* Expand the accordion section containing a control
* @ todo it would be nice if accordion had a proper API instead of having to trigger UI events on its elements
* /
expandControlSection : function ( ) {
var section = this . container . closest ( '.accordion-section' ) ;
if ( ! section . hasClass ( 'open' ) ) {
section . find ( '.accordion-section-title:first' ) . trigger ( 'click' ) ;
}
} ,
/ * *
* Expand the widget form control
* /
expandForm : function ( ) {
this . toggleForm ( true ) ;
} ,
/ * *
* Collapse the widget form control
* /
collapseForm : function ( ) {
this . toggleForm ( false ) ;
} ,
/ * *
* Expand or collapse the widget control
*
* @ param { boolean | undefined } [ do _expand ] If not supplied , will be inverse of current visibility
* /
toggleForm : function ( do _expand ) {
2014-03-06 17:45:15 +01:00
var control = this , widget , inside , complete ;
widget = control . container . find ( 'div.widget:first' ) ;
inside = widget . find ( '.widget-inside:first' ) ;
2014-03-05 21:41:14 +01:00
if ( typeof do _expand === 'undefined' ) {
do _expand = ! inside . is ( ':visible' ) ;
}
// Already expanded or collapsed, so noop
if ( inside . is ( ':visible' ) === do _expand ) {
return ;
}
2014-03-06 17:45:15 +01:00
complete ;
2014-03-05 21:41:14 +01:00
if ( do _expand ) {
// Close all other widget controls before expanding this one
wp . customize . control . each ( function ( other _control ) {
if ( control . params . type === other _control . params . type && control !== other _control ) {
other _control . collapseForm ( ) ;
}
} ) ;
control . container . trigger ( 'expand' ) ;
control . container . addClass ( 'expanding' ) ;
complete = function ( ) {
control . container . removeClass ( 'expanding' ) ;
control . container . addClass ( 'expanded' ) ;
control . container . trigger ( 'expanded' ) ;
} ;
if ( control . params . is _wide ) {
inside . animate ( { width : 'show' } , 'fast' , complete ) ;
} else {
inside . slideDown ( 'fast' , complete ) ;
}
} else {
control . container . trigger ( 'collapse' ) ;
control . container . addClass ( 'collapsing' ) ;
complete = function ( ) {
control . container . removeClass ( 'collapsing' ) ;
control . container . removeClass ( 'expanded' ) ;
control . container . trigger ( 'collapsed' ) ;
} ;
if ( control . params . is _wide ) {
inside . animate ( { width : 'hide' } , 'fast' , complete ) ;
} else {
inside . slideUp ( 'fast' , function ( ) {
widget . css ( { width : '' , margin : '' } ) ;
complete ( ) ;
} ) ;
}
}
} ,
/ * *
* Expand the containing sidebar section , expand the form , and focus on
* the first input in the control
* /
focus : function ( ) {
var control = this ;
control . expandControlSection ( ) ;
control . expandForm ( ) ;
control . container . find ( ':focusable:first' ) . focus ( ) . trigger ( 'click' ) ;
} ,
/ * *
* Get the position ( index ) of the widget in the containing sidebar
*
* @ throws Error
* @ returns { Number }
* /
getWidgetSidebarPosition : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
sidebar _widget _ids ,
position ;
sidebar _widget _ids = control . getSidebarWidgetsControl ( ) . setting ( ) ;
2014-03-22 22:22:13 +01:00
position = _ . indexOf ( sidebar _widget _ids , control . params . widget _id ) ;
2014-03-05 21:41:14 +01:00
if ( position === - 1 ) {
throw new Error ( 'Widget was unexpectedly not present in the sidebar.' ) ;
}
return position ;
} ,
/ * *
* Move widget up one in the sidebar
* /
moveUp : function ( ) {
this . _moveWidgetByOne ( - 1 ) ;
} ,
/ * *
* Move widget up one in the sidebar
* /
moveDown : function ( ) {
this . _moveWidgetByOne ( 1 ) ;
} ,
/ * *
* @ private
*
* @ param { Number } offset 1 | - 1
* /
_moveWidgetByOne : function ( offset ) {
2014-03-06 17:45:15 +01:00
var control = this ,
i ,
sidebar _widgets _setting ,
sidebar _widget _ids ,
adjacent _widget _id ;
2014-03-05 21:41:14 +01:00
2014-03-06 17:45:15 +01:00
i = control . getWidgetSidebarPosition ( ) ;
sidebar _widgets _setting = control . getSidebarWidgetsControl ( ) . setting ;
sidebar _widget _ids = Array . prototype . slice . call ( sidebar _widgets _setting ( ) ) ; // clone
adjacent _widget _id = sidebar _widget _ids [ i + offset ] ;
2014-03-05 21:41:14 +01:00
sidebar _widget _ids [ i + offset ] = control . params . widget _id ;
sidebar _widget _ids [ i ] = adjacent _widget _id ;
sidebar _widgets _setting ( sidebar _widget _ids ) ;
} ,
/ * *
* Toggle visibility of the widget move area
*
* @ param { Boolean } [ toggle ]
* /
toggleWidgetMoveArea : function ( toggle ) {
2014-03-06 17:45:15 +01:00
var control = this , move _widget _area ;
move _widget _area = control . container . find ( '.move-widget-area' ) ;
2014-03-05 21:41:14 +01:00
if ( typeof toggle === 'undefined' ) {
toggle = ! move _widget _area . hasClass ( 'active' ) ;
}
if ( toggle ) {
// reset the selected sidebar
move _widget _area . find ( '.selected' ) . removeClass ( 'selected' ) ;
move _widget _area . find ( 'li' ) . filter ( function ( ) {
return $ ( this ) . data ( 'id' ) === control . params . sidebar _id ;
} ) . addClass ( 'selected' ) ;
control . container . find ( '.move-widget-btn' ) . prop ( 'disabled' , true ) ;
}
move _widget _area . toggleClass ( 'active' , toggle ) ;
} ,
/ * *
* Inverse of WidgetCustomizer . getControlInstanceForWidget
* @ return { jQuery }
* /
getPreviewWidgetElement : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this ,
2014-03-22 22:04:15 +01:00
widget _customizer _preview = self . getPreviewWindow ( ) . wp . customize . WidgetCustomizerPreview ;
2014-03-18 16:21:15 +01:00
return widget _customizer _preview . getWidgetElement ( control . params . widget _id ) ;
2014-03-05 21:41:14 +01:00
} ,
/ * *
* Inside of the customizer preview , scroll the widget into view
* /
scrollPreviewWidgetIntoView : function ( ) {
// @todo scrollIntoView() provides a robust but very poor experience. Animation is needed. See https://github.com/x-team/wp-widget-customizer/issues/16
} ,
/ * *
* Highlight the widget control and section
* /
highlightSectionAndControl : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , target _element ;
2014-03-05 21:41:14 +01:00
if ( control . container . is ( ':hidden' ) ) {
target _element = control . container . closest ( '.control-section' ) ;
} else {
target _element = control . container ;
}
$ ( '.widget-customizer-highlighted' ) . removeClass ( 'widget-customizer-highlighted' ) ;
target _element . addClass ( 'widget-customizer-highlighted' ) ;
setTimeout ( function ( ) {
target _element . removeClass ( 'widget-customizer-highlighted' ) ;
} , 500 ) ;
} ,
/ * *
* Add the widget - customizer - highlighted - widget class to the widget for 500 ms
* /
highlightPreviewWidget : function ( ) {
2014-03-06 17:45:15 +01:00
var control = this , widget _el , root _el ;
widget _el = control . getPreviewWidgetElement ( ) ;
root _el = widget _el . closest ( 'html' ) ;
2014-03-05 21:41:14 +01:00
root _el . find ( '.widget-customizer-highlighted-widget' ) . removeClass ( 'widget-customizer-highlighted-widget' ) ;
widget _el . addClass ( 'widget-customizer-highlighted-widget' ) ;
setTimeout ( function ( ) {
widget _el . removeClass ( 'widget-customizer-highlighted-widget' ) ;
} , 500 ) ;
}
} ) ;
/ * *
* Capture the instance of the Previewer since it is private
* /
2014-03-06 17:45:15 +01:00
OldPreviewer = wp . customize . Previewer ;
2014-03-05 21:41:14 +01:00
wp . customize . Previewer = OldPreviewer . extend ( {
initialize : function ( params , options ) {
self . previewer = this ;
OldPreviewer . prototype . initialize . call ( this , params , options ) ;
this . bind ( 'refresh' , this . refresh ) ;
}
} ) ;
/ * *
* Given a widget control , find the sidebar widgets control that contains it .
* @ param { string } widget _id
* @ return { object | null }
* /
self . getSidebarWidgetControlContainingWidget = function ( widget _id ) {
var found _control = null ;
// @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
wp . customize . control . each ( function ( control ) {
2014-03-22 22:22:13 +01:00
if ( control . params . type === 'sidebar_widgets' && - 1 !== _ . indexOf ( control . setting ( ) , widget _id ) ) {
2014-03-05 21:41:14 +01:00
found _control = control ;
}
} ) ;
return found _control ;
} ;
/ * *
* Given a widget _id for a widget appearing in the preview , get the widget form control associated with it
* @ param { string } widget _id
* @ return { object | null }
* /
self . getWidgetFormControlForWidget = function ( widget _id ) {
var found _control = null ;
// @todo We can just use widget_id_to_setting_id() here
wp . customize . control . each ( function ( control ) {
if ( control . params . type === 'widget_form' && control . params . widget _id === widget _id ) {
found _control = control ;
}
} ) ;
return found _control ;
} ;
/ * *
* @ returns { Window }
* /
self . getPreviewWindow = function ( ) {
return $ ( '#customize-preview' ) . find ( 'iframe' ) . prop ( 'contentWindow' ) ;
} ;
/ * *
* Available Widgets Panel
* /
self . availableWidgetsPanel = {
active _sidebar _widgets _control : null ,
selected _widget _tpl : null ,
container : null ,
filter _input : null ,
/ * *
* Set up event listeners
* /
setup : function ( ) {
2014-03-21 22:13:15 +01:00
var panel = this ;
2014-03-06 17:45:15 +01:00
2014-03-05 21:41:14 +01:00
panel . container = $ ( '#available-widgets' ) ;
panel . filter _input = $ ( '#available-widgets-filter' ) . find ( 'input' ) ;
2014-03-21 22:13:15 +01:00
self . available _widgets . on ( 'change update' , panel . update _available _widgets _list ) ;
panel . update _available _widgets _list ( ) ;
2014-03-05 21:41:14 +01:00
// 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
self . 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 ) ;
} ) ;
2014-03-21 22:13:15 +01:00
panel . filter _input . on ( 'input keyup change' , function ( event ) {
var first _visible _widget ;
2014-03-05 21:41:14 +01:00
2014-03-21 22:13:15 +01:00
self . available _widgets . doSearch ( event . target . value ) ;
2014-03-05 21:41:14 +01:00
2014-03-21 22:13:15 +01:00
// 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 ;
}
2014-03-05 21:41:14 +01:00
2014-03-21 22:13:15 +01:00
// 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 ) ;
2014-03-05 21:41:14 +01:00
}
}
2014-03-21 22:13:15 +01:00
} ) ;
2014-03-05 21:41:14 +01:00
// 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 ) {
2014-03-06 17:45:15 +01:00
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 ) ;
2014-03-05 21:41:14 +01:00
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 } ) ;
}
} ) ;
} ,
2014-03-21 22:13:15 +01:00
/ * *
* Updates widgets list .
* /
update _available _widgets _list : function ( ) {
var panel = self . availableWidgetsPanel ;
// First hide all widgets...
panel . container . find ( '.widget-tpl' ) . hide ( ) ;
// ..and then show only available widgets which could be filtered
self . available _widgets . each ( function ( widget ) {
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 ;
}
} ) ;
} ,
2014-03-05 21:41:14 +01:00
/ * *
* @ 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 ) {
2014-03-06 17:45:15 +01:00
var panel = this , widget _id , widget ;
2014-03-05 21:41:14 +01:00
if ( ! widget _tpl ) {
widget _tpl = panel . selected _widget _tpl ;
}
if ( ! widget _tpl || ! panel . active _sidebar _widgets _control ) {
return ;
}
panel . select ( widget _tpl ) ;
2014-03-06 17:45:15 +01:00
widget _id = $ ( panel . selected _widget _tpl ) . data ( 'widget-id' ) ;
widget = self . available _widgets . findWhere ( { id : widget _id } ) ;
2014-03-05 21:41:14 +01:00
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' ) ;
2014-03-21 22:13:15 +01:00
self . available _widgets . doSearch ( '' ) ;
2014-03-05 21:41:14 +01:00
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 }
* /
function parse _widget _id ( widget _id ) {
2014-03-06 17:45:15 +01:00
var matches , parsed = {
2014-03-05 21:41:14 +01:00
number : null ,
id _base : null
} ;
2014-03-06 17:45:15 +01:00
matches = widget _id . match ( /^(.+)-(\d+)$/ ) ;
2014-03-05 21:41:14 +01:00
if ( matches ) {
parsed . id _base = matches [ 1 ] ;
parsed . number = parseInt ( matches [ 2 ] , 10 ) ;
} else {
// likely an old single widget
parsed . id _base = widget _id ;
}
return parsed ;
}
/ * *
* @ param { String } widget _id
* @ returns { String } setting _id
* /
function widget _id _to _setting _id ( widget _id ) {
2014-03-06 17:45:15 +01:00
var parsed = parse _widget _id ( widget _id ) , setting _id ;
setting _id = 'widget_' + parsed . id _base ;
2014-03-05 21:41:14 +01:00
if ( parsed . number ) {
setting _id += '[' + parsed . number + ']' ;
}
return setting _id ;
}
return self ;
} ( jQuery ) ) ;