Вход Регистрация
Файл: wordpress/wp-admin/js/customize-widgets.js
Строк: 2472
<?php
/* global _wpCustomizeWidgetsSettings */
(function( wp, $ ){

    if ( ! 
wp || ! wp.customize ) { return; }

    
// Set up our namespace...
    
var api wp.customize,
        
l10n;

    
api.Widgets api.Widgets || {};

    
// Link settings
    
api.Widgets.data _wpCustomizeWidgetsSettings || {};
    
l10n api.Widgets.data.l10n;
    
delete api.Widgets.data.l10n;

    
/**
     * wp.customize.Widgets.WidgetModel
     *
     * A single widget model.
     *
     * @constructor
     * @augments Backbone.Model
     */
    
api.Widgets.WidgetModel Backbone.Model.extend({
        
idnull,
        
temp_idnull,
        
classnamenull,
        
control_tplnull,
        
descriptionnull,
        
is_disablednull,
        
is_multinull,
        
multi_numbernull,
        
namenull,
        
id_basenull,
        
transport'refresh',
        
params: [],
        
widthnull,
        
heightnull,
        
search_matchedtrue
    
});

    
/**
     * wp.customize.Widgets.WidgetCollection
     *
     * Collection for widget models.
     *
     * @constructor
     * @augments Backbone.Model
     */
    
api.Widgets.WidgetCollection Backbone.Collection.extend({
        
modelapi.Widgets.WidgetModel,

        
// 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 ) {
                
this.searchthis.terms );
            }

            
// If search is blank, show all themes
            // Useful for resetting the views when you clean the input
            
if ( this.terms === '' ) {
                
this.each( function ( widget ) {
                    
widget.set'search_matched'true );
                } );
            }
        },

        
// Performs a search within the collection
        // @uses RegExp
        
search: function( term ) {
            var 
matchhaystack;

            
// 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' );

            
this.each( function ( data ) {
                
haystack = [ data.get'name' ), data.get'id' ), data.get'description' ) ].join' ' );
                
data.set'search_matched'match.testhaystack ) );
            } );
        }
    });
    
api.Widgets.availableWidgets = new api.Widgets.WidgetCollectionapi.Widgets.data.availableWidgets );

    
/**
     * wp.customize.Widgets.SidebarModel
     *
     * A single sidebar model.
     *
     * @constructor
     * @augments Backbone.Model
     */
    
api.Widgets.SidebarModel Backbone.Model.extend({
        
after_titlenull,
        
after_widgetnull,
        
before_titlenull,
        
before_widgetnull,
        
'class'null,
        
descriptionnull,
        
idnull,
        
namenull,
        
is_renderedfalse
    
});

    
/**
     * wp.customize.Widgets.SidebarCollection
     *
     * Collection for sidebar models.
     *
     * @constructor
     * @augments Backbone.Collection
     */
    
api.Widgets.SidebarCollection Backbone.Collection.extend({
        
modelapi.Widgets.SidebarModel
    
});
    
api.Widgets.registeredSidebars = new api.Widgets.SidebarCollectionapi.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
        
selectednull,

        
// Cache sidebar control which has opened panel
        
currentSidebarControlnull,
        
$searchnull,

        
initialize: function() {
            var 
self this;

            
this.$search = $( '#widgets-search' );

            
_.bindAllthis'close' );

            
this.listenTothis.collection'change'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, .customize-overlay-close' ).on'click keydown', function( ) {
                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.previewer.bind'url'this.close );
        },

        
// Performs a search and handles selected widget
        
search: function( event ) {
            var 
firstVisible;

            
this.collection.doSearchevent.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.selectfirstVisible );
                }
            }
        },

        
// Changes visibility of available widgets
        
updateList: function() {
            
this.collection.each( function( widget ) {
                var 
widgetTpl = $( '#widget-tpl-' widget.id );
                
widgetTpl.togglewidget.get'search_matched' ) && ! widget.get'is_disabled' ) );
                if ( 
widget.get'is_disabled' ) && widgetTpl.isthis.selected ) ) {
                    
this.selected null;
                }
            } );
        },

        
// Highlights a widget
        
select: function( widgetTpl ) {
            
this.selected = $( widgetTpl );
            
this.selected.siblings'.widget-tpl' ).removeClass'selected' );
            
this.selected.addClass'selected' );
        },

        
// Highlights 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 
widgetIdwidgetwidgetFormControl;

            if ( ! 
widgetTpl ) {
                
widgetTpl this.selected;
            }

            if ( ! 
widgetTpl || ! this.currentSidebarControl ) {
                return;
            }

            
this.selectwidgetTpl );

            
widgetId = $( this.selected ).data'widget-id' );
            
widget this.collection.findWhere( { idwidgetId } );
            if ( ! 
widget ) {
                return;
            }

            
widgetFormControl this.currentSidebarControl.addWidgetwidget.get'id_base' ) );
            if ( 
widgetFormControl ) {
                
widgetFormControl.focus();
            }

            
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'' );

            if ( ! 
api.settings.browser.mobile ) {
                
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 ),
                
isTab = ( event.which === ),
                
isShift = ( event.shiftKey ),
                
selected null,
                
firstVisible this.$el.find'> .widget-tpl:visible:first' ),
                
lastVisible this.$el.find'> .widget-tpl:visible:last' ),
                
isSearchFocused = $( event.target ).isthis.$search ),
                
isLastWidgetFocused = $( event.target ).is'.widget-tpl:visible:last' );

            if ( 
isDown || isUp ) {
                if ( 
isDown ) {
                    if ( 
isSearchFocused ) {
                        
selected firstVisible;
                    } else if ( 
this.selected && this.selected.nextAll'.widget-tpl:visible' ).length !== ) {
                        
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 !== ) {
                        
selected this.selected.prevAll'.widget-tpl:visible:first' );
                    }
                }

                
this.selectselected );

                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( { returnFocustrue } );
            }

            if ( 
isTab && ( isShift && isSearchFocused || ! isShift && isLastWidgetFocused ) ) {
                
this.currentSidebarControl.container.find'.add-new-widget' ).focus();
                
event.preventDefault();
            }
        }
    });

    
/**
     * Handlers for the widget-synced event, organized by widget ID base.
     * Other widgets may provide their own update handlers by adding
     * listeners for the widget-synced event.
     */
    
api.Widgets.formSyncHandlers = {

        
/**
         * @param {jQuery.Event} e
         * @param {jQuery} widget
         * @param {String} newForm
         */
        
rss: function( ewidgetnewForm ) {
            var 
oldWidgetError widget.find'.widget-error:first' ),
                
newWidgetError = $( '<div>' newForm '</div>' ).find'.widget-error:first' );

            if ( 
oldWidgetError.length && newWidgetError.length ) {
                
oldWidgetError.replaceWithnewWidgetError );
            } else if ( 
oldWidgetError.length ) {
                
oldWidgetError.remove();
            } else if ( 
newWidgetError.length ) {
                
widget.find'.widget-content:first' ).prependnewWidgetError );
            }
        }
    };

    
/**
     * 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({
        
defaultExpandedArguments: {
            
duration'fast',
            
completeCallback: $.noop
        
},

        
/**
         * @since 4.1.0
         */
        
initialize: function ( idoptions ) {
            var 
control this;
            
api.Control.prototype.initialize.callcontrolidoptions );
            
control.expanded = new api.Value();
            
control.expandedArgumentsQueue = [];
            
control.expanded.bind( function ( expanded ) {
                var 
args control.expandedArgumentsQueue.shift();
                
args = $.extend( {}, control.defaultExpandedArgumentsargs );
                
control.onChangeExpandedexpandedargs );
            });
            
control.expanded.setfalse );
        },

        
/**
         * Set up the control
         */
        
ready: function() {
            
this._setupModel();
            
this._setupWideWidget();
            
this._setupControlToggle();
            
this._setupWidgetTitle();
            
this._setupReorderUI();
            
this._setupHighlightEffects();
            
this._setupUpdateUI();
            
this._setupRemoveUI();

            
/*
             * Trigger widget-added event so that plugins can attach any event
             * listeners and dynamic UI elements.
             */
            
$( document ).trigger'widget-added', [ this.container.find'.widget:first' ) ] );
        },

        
/**
         * Handle changes to the setting
         */
        
_setupModel: function() {
            var 
self thisrememberSavedWidgetId;

            
api.Widgets.savedWidgetIds api.Widgets.savedWidgetIds || [];

            
// Remember saved widgets so we know which to trash (move to inactive widgets sidebar)
            
rememberSavedWidgetId = function() {
                
api.Widgets.savedWidgetIds[self.params.widget_id] = true;
            };
            
api.bind'ready'rememberSavedWidgetId );
            
api.bind'saved'rememberSavedWidgetId );

            
this._updateCount 0;
            
this.isWidgetUpdating false;
            
this.liveUpdateMode true;

            
// Update widget whenever model changes
            
this.setting.bind( function( tofrom ) {
                if ( ! 
_from ).isEqualto ) && ! self.isWidgetUpdating ) {
                    
self.updateWidget( { instanceto } );
                }
            } );
        },

        
/**
         * Add special behaviors for wide widget controls
         */
        
_setupWideWidget: function() {
            var 
self this$widgetInside$widgetForm$customizeSidebar,
                
$themeControlsContainerpositionWidget;

            if ( ! 
this.params.is_wide ) {
                return;
            }

            
$widgetInside this.container.find'.widget-inside' );
            
$widgetForm $widgetInside.find'> .form' );
            
$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' );
            
this.container.addClass'wide-widget-control' );

            
this.container.find'.widget-content:first' ).css( {
                
'max-width'this.params.width,
                
'min-height'this.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.
             * If a widget is too tall to fit in the window, don't let the height
             * exceed the window height so that the contents of the widget control
             * will become scrollable (overflow:auto).
             */
            
positionWidget = function() {
                var 
offsetTop self.container.offset().top,
                    
windowHeight = $( window ).height(),
                    
formHeight $widgetForm.outerHeight(),
                    
top;
                
$widgetInside.css'max-height'windowHeight );
                
top Math.max(
                    
0// prevent top from going off screen
                    
Math.min(
                        
Math.maxoffsetTop), // distance widget in panel is from top of screen
                        
windowHeight formHeight // flush up against bottom of screen
                    
)
                );
                
$widgetInside.css'top'top );
            };

            
$themeControlsContainer = $( '#customize-theme-controls' );
            
this.container.on'expand', function() {
                
positionWidget();
                
$customizeSidebar.on'scroll'positionWidget );
                $( 
window ).on'resize'positionWidget );
                
$themeControlsContainer.on'expanded collapsed'positionWidget );
            } );
            
this.container.on'collapsed', function() {
                
$customizeSidebar.off'scroll'positionWidget );
                $( 
window ).off'resize'positionWidget );
                
$themeControlsContainer.off'expanded collapsed'positionWidget );
            } );

            
// Reposition whenever a sidebar's widgets are changed
            
api.each( function( setting ) {
                if ( 
=== setting.id.indexOf'sidebars_widgets[' ) ) {
                    
setting.bind( function() {
                        if ( 
self.container.hasClass'expanded' ) ) {
                            
positionWidget();
                        }
                    } );
                }
            } );
        },

        
/**
         * Show/hide the control when clicking on the form title, when clicking
         * the close button
         */
        
_setupControlToggle: function() {
            var 
self this$closeBtn;

            
this.container.find'.widget-top' ).on'click', function( ) {
                
e.preventDefault();
                var 
sidebarWidgetsControl self.getSidebarWidgetsControl();
                if ( 
sidebarWidgetsControl.isReordering ) {
                    return;
                }
                
self.expanded( ! self.expanded() );
            } );

            
$closeBtn this.container.find'.widget-control-close' );
            
$closeBtn.on'click', function( ) {
                
e.preventDefault();
                
self.collapse();
                
self.container.find'.widget-top .widget-action:first' ).focus(); // keyboard accessibility
            
} );
        },

        
/**
         * Update the title of the form if a title field is entered
         */
        
_setupWidgetTitle: function() {
            var 
self thisupdateTitle;

            
updateTitle = function() {
                var 
title self.setting().title,
                    
inWidgetTitle self.container.find'.in-widget-title' );

                if ( 
title ) {
                    
inWidgetTitle.text': ' title );
                } else {
                    
inWidgetTitle.text'' );
                }
            };
            
this.setting.bindupdateTitle );
            
updateTitle();
        },

        
/**
         * Set up the widget-reorder-nav
         */
        
_setupReorderUI: function() {
            var 
self thisselectSidebarItem$moveWidgetArea,
                
$reorderNavupdateAvailableSidebars;

            
/**
             * select the provided sidebar list item in the move widget area
             *
             * @param {jQuery} li
             */
            
selectSidebarItem = function( li ) {
                
li.siblings'.selected' ).removeClass'selected' );
                
li.addClass'selected' );
                var 
isSelfSidebar = ( li.data'id' ) === self.params.sidebar_id );
                
self.container.find'.move-widget-btn' ).prop'disabled'isSelfSidebar );
            };

            
/**
             * Add the widget reordering elements to the widget control
             */
            
this.container.find'.widget-title-action' ).after( $( api.Widgets.data.tpl.widgetReorderNav ) );
            
$moveWidgetArea = $(
                
_.templateapi.Widgets.data.tpl.moveWidgetArea, {
                    
sidebars_api.Widgets.registeredSidebars.toArray() ).pluck'attributes' )
                } )
            );
            
this.container.find'.widget-top' ).after$moveWidgetArea );

            
/**
             * Update available sidebars when their rendered state changes
             */
            
updateAvailableSidebars = function() {
                var 
$sidebarItems $moveWidgetArea.find'li' ), selfSidebarItem,
                    
renderedSidebarCount 0;

                
selfSidebarItem $sidebarItems.filter( function(){
                    return $( 
this ).data'id' ) === self.params.sidebar_id;
                } );

                
$sidebarItems.each( function() {
                    var 
li = $( this ),
                        
sidebarIdsidebarsidebarIsRendered;

                    
sidebarId li.data'id' );
                    
sidebar api.Widgets.registeredSidebars.getsidebarId );
                    
sidebarIsRendered sidebar.get'is_rendered' );

                    
li.togglesidebarIsRendered );

                    if ( 
sidebarIsRendered ) {
                        
renderedSidebarCount += 1;
                    }

                    if ( 
li.hasClass'selected' ) && ! sidebarIsRendered ) {
                        
selectSidebarItemselfSidebarItem );
                    }
                } );

                if ( 
renderedSidebarCount ) {
                    
self.container.find'.move-widget' ).show();
                } else {
                    
self.container.find'.move-widget' ).hide();
                }
            };

            
updateAvailableSidebars();
            
api.Widgets.registeredSidebars.on'change:is_rendered'updateAvailableSidebars );

            
/**
             * Handle clicks for up/down/move on the reorder nav
             */
            
$reorderNav this.container.find'.widget-reorder-nav' );
            
$reorderNav.find'.move-widget, .move-widget-down, .move-widget-up' ).each( function() {
                $( 
this ).prependself.container.find'.widget-title' ).text() + ': ' );
            } ).
on'click keypress', function( event ) {
                if ( 
event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
                    return;
                }
                $( 
this ).focus();

                if ( $( 
this ).is'.move-widget' ) ) {
                    
self.toggleWidgetMoveArea();
                } else {
                    var 
isMoveDown = $( this ).is'.move-widget-down' ),
                        
isMoveUp = $( this ).is'.move-widget-up' ),
                        
self.getWidgetSidebarPosition();

                    if ( ( 
isMoveUp && === ) || ( isMoveDown && === self.getSidebarWidgetsControl().setting().length ) ) {
                        return;
                    }

                    if ( 
isMoveUp ) {
                        
self.moveUp();
                        
wp.a11y.speakl10n.widgetMovedUp );
                    } else {
                        
self.moveDown();
                        
wp.a11y.speakl10n.widgetMovedDown );
                    }

                    $( 
this ).focus(); // re-focus after the container was moved
                
}
            } );

            
/**
             * Handle selecting a sidebar to move to
             */
            
this.container.find'.widget-area-select' ).on'click keypress''li', function( event ) {
                if ( 
event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
                    return;
                }
                
event.preventDefault();
                
selectSidebarItem( $( this ) );
            } );

            
/**
             * Move widget to another sidebar
             */
            
this.container.find'.move-widget-btn' ).click( function() {
                
self.getSidebarWidgetsControl().toggleReorderingfalse );

                var 
oldSidebarId self.params.sidebar_id,
                    
newSidebarId self.container.find'.widget-area-select li.selected' ).data'id' ),
                    
oldSidebarWidgetsSettingnewSidebarWidgetsSetting,
                    
oldSidebarWidgetIdsnewSidebarWidgetIdsi;

                
oldSidebarWidgetsSetting api'sidebars_widgets[' oldSidebarId ']' );
                
newSidebarWidgetsSetting api'sidebars_widgets[' newSidebarId ']' );
                
oldSidebarWidgetIds = Array.prototype.slice.calloldSidebarWidgetsSetting() );
                
newSidebarWidgetIds = Array.prototype.slice.callnewSidebarWidgetsSetting() );

                
self.getWidgetSidebarPosition();
                
oldSidebarWidgetIds.splicei);
                
newSidebarWidgetIds.pushself.params.widget_id );

                
oldSidebarWidgetsSettingoldSidebarWidgetIds );
                
newSidebarWidgetsSettingnewSidebarWidgetIds );

                
self.focus();
            } );
        },

        
/**
         * Highlight widgets in preview when interacted with in the Customizer
         */
        
_setupHighlightEffects: function() {
            var 
self this;

            
// Highlight whenever hovering or clicking over the form
            
this.container.on'mouseenter click', function() {
                
self.setting.previewer.send'highlight-widget'self.params.widget_id );
            } );

            
// Highlight when the setting is updated
            
this.setting.bind( function() {
                
self.setting.previewer.send'highlight-widget'self.params.widget_id );
            } );
        },

        
/**
         * Set up event handlers for widget updating
         */
        
_setupUpdateUI: function() {
            var 
self this$widgetRoot$widgetContent,
                
$saveBtnupdateWidgetDebouncedformSyncHandler;

            
$widgetRoot this.container.find'.widget:first' );
            
$widgetContent $widgetRoot.find'.widget-content:first' );

            
// Configure update button
            
$saveBtn this.container.find'.widget-control-save' );
            
$saveBtn.vall10n.saveBtnLabel );
            
$saveBtn.attr'title'l10n.saveBtnTooltip );
            
$saveBtn.removeClass'button-primary' ).addClass'button-secondary' );
            
$saveBtn.on'click', function( ) {
                
e.preventDefault();
                
self.updateWidget( { disable_formtrue } ); // @todo disable_form is unused?
            
} );

            
updateWidgetDebounced _.debounce( function() {
                
self.updateWidget();
            }, 
250 );

            
// Trigger widget form update when hitting Enter within an input
            
$widgetContent.on'keydown''input', function( ) {
                if ( 
13 === e.which ) { // Enter
                    
e.preventDefault();
                    
self.updateWidget( { ignoreActiveElementtrue } );
                }
            } );

            
// Handle widgets that support live previews
            
$widgetContent.on'change input propertychange'':input', function( ) {
                if ( 
self.liveUpdateMode ) {
                    if ( 
e.type === 'change' ) {
                        
self.updateWidget();
                    } else if ( 
this.checkValidity && this.checkValidity() ) {
                        
updateWidgetDebounced();
                    }
                }
            } );

            
// Remove loading indicators when the setting is saved and the preview updates
            
this.setting.previewer.channel.bind'synced', function() {
                
self.container.removeClass'previewer-loading' );
            } );

            
api.previewer.bind'widget-updated', function( updatedWidgetId ) {
                if ( 
updatedWidgetId === self.params.widget_id ) {
                    
self.container.removeClass'previewer-loading' );
                }
            } );

            
formSyncHandler api.Widgets.formSyncHandlersthis.params.widget_id_base ];
            if ( 
formSyncHandler ) {
                $( 
document ).on'widget-synced', function( ewidget ) {
                    if ( 
$widgetRoot.iswidget ) ) {
                        
formSyncHandler.applydocumentarguments );
                    }
                } );
            }
        },

        
/**
         * Update widget control to indicate whether it is currently rendered.
         *
         * Overrides api.Control.toggle()
         *
         * @since 4.1.0
         *
         * @param {Boolean}   active
         * @param {Object}    args
         * @param {Callback}  args.completeCallback
         */
        
onChangeActive: function ( activeargs ) {
            
// Note: there is a second 'args' parameter being passed, merged on top of this.defaultActiveArguments
            
this.container.toggleClass'widget-rendered'active );
            if ( 
args.completeCallback ) {
                
args.completeCallback();
            }
        },

        
/**
         * Set up event handlers for widget removal
         */
        
_setupRemoveUI: function() {
            var 
self this$removeBtnreplaceDeleteWithRemove;

            
// Configure remove button
            
$removeBtn this.container.find'a.widget-control-remove' );
            
$removeBtn.on'click', function( ) {
                
e.preventDefault();

                
// Find an adjacent element to add focus to when this widget goes away
                
var $adjacentFocusTarget;
                if ( 
self.container.next().is'.customize-control-widget_form' ) ) {
                    
$adjacentFocusTarget self.container.next().find'.widget-action:first' );
                } else if ( 
self.container.prev().is'.customize-control-widget_form' ) ) {
                    
$adjacentFocusTarget self.container.prev().find'.widget-action:first' );
                } else {
                    
$adjacentFocusTarget self.container.next'.customize-control-sidebar_widgets' ).find'.add-new-widget:first' );
                }

                
self.container.slideUp( function() {
                    var 
sidebarsWidgetsControl api.Widgets.getSidebarWidgetControlContainingWidgetself.params.widget_id ),
                        
sidebarWidgetIdsi;

                    if ( ! 
sidebarsWidgetsControl ) {
                        return;
                    }

                    
sidebarWidgetIds sidebarsWidgetsControl.setting().slice();
                    
_.indexOfsidebarWidgetIdsself.params.widget_id );
                    if ( -
=== ) {
                        return;
                    }

                    
sidebarWidgetIds.splicei);
                    
sidebarsWidgetsControl.settingsidebarWidgetIds );

                    
$adjacentFocusTarget.focus(); // keyboard accessibility
                
} );
            } );

            
replaceDeleteWithRemove = function() {
                
$removeBtn.textl10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete"
                
$removeBtn.attr'title'l10n.removeBtnTooltip );
            };

            if ( 
this.params.is_new ) {
                
api.bind'saved'replaceDeleteWithRemove );
            } else {
                
replaceDeleteWithRemove();
            }
        },

        
/**
         * Find all inputs in a widget container that should be considered when
         * comparing the loaded form with the sanitized form, whose fields will
         * be aligned to copy the sanitized over. The elements returned by this
         * are passed into this._getInputsSignature(), and they are iterated
         * over when copying sanitized values over to the the form loaded.
         *
         * @param {jQuery} container element in which to look for inputs
         * @returns {jQuery} inputs
         * @private
         */
        
_getInputs: function( container ) {
            return $( 
container ).find':input[name]' );
        },

        
/**
         * 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 
inputsSignatures _inputs ).map( function( input ) {
                var 
$input = $( input ), signatureParts;

                if ( 
$input.is':checkbox, :radio' ) ) {
                    
signatureParts = [ $input.attr'id' ), $input.attr'name' ), $input.prop'value' ) ];
                } else {
                    
signatureParts = [ $input.attr'id' ), $input.attr'name' ) ];
                }

                return 
signatureParts.join',' );
            } );

            return 
inputsSignatures.join';' );
        },

        
/**
         * Get the state for an input depending on its type.
         *
         * @param {jQuery|Element} input
         * @returns {string|boolean|array|*}
         * @private
         */
        
_getInputState: function( input ) {
            
input = $( input );
            if ( 
input.is':radio, :checkbox' ) ) {
                return 
input.prop'checked' );
            } else if ( 
input.is'select[multiple]' ) ) {
                return 
input.find'option:selected' ).map( function () {
                    return $( 
this ).val();
                } ).
get();
            } else {
                return 
input.val();
            }
        },

        
/**
         * Update an input's state based on its type.
         *
         * @param {jQuery|Element} input
         * @param {string|boolean|array|*} state
         * @private
         */
        
_setInputState: function ( inputstate ) {
            
input = $( input );
            if ( 
input.is':radio, :checkbox' ) ) {
                
input.prop'checked'state );
            } else if ( 
input.is'select[multiple]' ) ) {
                if ( ! $.
isArraystate ) ) {
                    
state = [];
                } else {
                    
// Make sure all state items are strings since the DOM value is a string
                    
state _.mapstate, function ( value ) {
                        return 
Stringvalue );
                    } );
                }
                
input.find'option' ).each( function () {
                    $( 
this ).prop'selected', -!== _.indexOfstateStringthis.value ) ) );
                } );
            } else {
                
input.valstate );
            }
        },

        
/***********************************************************************
         * Begin public API methods
         **********************************************************************/

        /**
         * @return {wp.customize.controlConstructor.sidebar_widgets[]}
         */
        
getSidebarWidgetsControl: function() {
            var 
settingIdsidebarWidgetsControl;

            
settingId 'sidebars_widgets[' this.params.sidebar_id ']';
            
sidebarWidgetsControl api.controlsettingId );

            if ( ! 
sidebarWidgetsControl ) {
                return;
            }

            return 
sidebarWidgetsControl;
        },

        
/**
         * 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.ignoreActiveElement=false] Whether or not updating a field will be deferred if focus is still on the element.
         */
        
updateWidget: function( args ) {
            var 
self thisinstanceOverridecompleteCallback$widgetRoot$widgetContent,
                
updateNumberparamsdata$inputsprocessingjqxhrisChanged;

            
args = $.extend( {
                
instancenull,
                
completenull,
                
ignoreActiveElementfalse
            
}, args );

            
instanceOverride args.instance;
            
completeCallback args.complete;

            
this._updateCount += 1;
            
updateNumber this._updateCount;

            
$widgetRoot this.container.find'.widget:first' );
            
$widgetContent $widgetRoot.find'.widget-content:first' );

            
// Remove a previous error message
            
$widgetContent.find'.widget-error' ).remove();

            
this.container.addClass'widget-form-loading' );
            
this.container.addClass'previewer-loading' );
            
processing api.state'processing' );
            
processingprocessing() + );

            if ( ! 
this.liveUpdateMode ) {
                
this.container.addClass'widget-form-disabled' );
            }

            
params = {};
            
params.action 'update-widget';
            
params.wp_customize 'on';
            
params.nonce api.Widgets.data.nonce;
            
params.theme api.settings.theme.stylesheet;

            
data = $.paramparams );
            
$inputs this._getInputs$widgetContent );

            
// 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() {
                $( 
this ).data'state' updateNumberself._getInputStatethis ) );
            } );

            if ( 
instanceOverride ) {
                
data += '&' + $.param( { 'sanitized_widget_setting'JSON.stringifyinstanceOverride ) } );
            } else {
                
data += '&' $inputs.serialize();
            }
            
data += '&' $widgetContent.find'~ :input' ).serialize();

            if ( 
this._previousUpdateRequest ) {
                
this._previousUpdateRequest.abort();
            }
            
jqxhr = $.postwp.ajax.settings.urldata );
            
this._previousUpdateRequest jqxhr;

            
jqxhr.done( function( ) {
                var 
messagesanitizedForm,    $sanitizedInputshasSameInputsInResponse,
                    
isLiveUpdateAborted false;

                
// Check if the user is logged out.
                
if ( '0' === ) {
                    
api.previewer.preview.iframe.hide();
                    
api.previewer.login().done( function() {
                        
self.updateWidgetargs );
                        
api.previewer.preview.iframe.show();
                    } );
                    return;
                }

                
// Check for cheaters.
                
if ( '-1' === ) {
                    
api.previewer.cheatin();
                    return;
                }

                if ( 
r.success ) {
                    
sanitizedForm = $( '<div>' r.data.form '</div>' );
                    
$sanitizedInputs self._getInputssanitizedForm );
                    
hasSameInputsInResponse self._getInputsSignature$inputs ) === self._getInputsSignature$sanitizedInputs );

                    
// Restore live update mode if sanitized fields are now aligned with the existing fields
                    
if ( hasSameInputsInResponse && ! self.liveUpdateMode ) {
                        
self.liveUpdateMode true;
                        
self.container.removeClass'widget-form-disabled' );
                        
self.container.find'input[name="savewidget"]' ).hide();
                    }

                    
// Sync sanitized field states to existing fields if they are aligned
                    
if ( hasSameInputsInResponse && self.liveUpdateMode ) {
                        
$inputs.each( function( ) {
                            var 
$input = $( this ),
                                
$sanitizedInput = $( $sanitizedInputs[i] ),
                                
submittedStatesanitizedState,    canUpdateState;

                            
submittedState $input.data'state' updateNumber );
                            
sanitizedState self._getInputState$sanitizedInput );
                            
$input.data'sanitized'sanitizedState );

                            
canUpdateState = ( ! _.isEqualsubmittedStatesanitizedState ) && ( args.ignoreActiveElement || ! $input.isdocument.activeElement ) ) );
                            if ( 
canUpdateState ) {
                                
self._setInputState$inputsanitizedState );
                            }
                        } );

                        $( 
document ).trigger'widget-synced', [ $widgetRootr.data.form ] );

                    
// Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled
                    
} else if ( self.liveUpdateMode ) {
                        
self.liveUpdateMode false;
                        
self.container.find'input[name="savewidget"]' ).show();
                        
isLiveUpdateAborted true;

                    
// Otherwise, replace existing form with the sanitized form
                    
} else {
                        
$widgetContent.htmlr.data.form );

                        
self.container.removeClass'widget-form-disabled' );

                        $( 
document ).trigger'widget-updated', [ $widgetRoot ] );
                    }

                    
/**
                     * 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.
                     */
                    
isChanged = ! isLiveUpdateAborted && ! _self.setting() ).isEqualr.data.instance );
                    if ( 
isChanged ) {
                        
self.isWidgetUpdating true// suppress triggering another updateWidget
                        
self.settingr.data.instance );
                        
self.isWidgetUpdating false;
                    } else {
                        
// no change was made, so stop the spinner now instead of when the preview would updates
                        
self.container.removeClass'previewer-loading' );
                    }

                    if ( 
completeCallback ) {
                        
completeCallback.callselfnull, { noChange: ! isChangedajaxFinishedtrue } );
                    }
                } else {
                    
// General error message
                    
message l10n.error;

                    if ( 
r.data && r.data.message ) {
                        
message r.data.message;
                    }

                    if ( 
completeCallback ) {
                        
completeCallback.callselfmessage );
                    } else {
                        
$widgetContent.prepend'<p class="widget-error"><strong>' message '</strong></p>' );
                    }
                }
            } );

            
jqxhr.fail( function( jqXHRtextStatus ) {
                if ( 
completeCallback ) {
                    
completeCallback.callselftextStatus );
                }
            } );

            
jqxhr.always( function() {
                
self.container.removeClass'widget-form-loading' );

                
$inputs.each( function() {
                    $( 
this ).removeData'state' updateNumber );
                } );

                
processingprocessing() - );
            } );
        },

        
/**
         * Expand the accordion section containing a control
         */
        
expandControlSection: function() {
            
api.Control.prototype.expand.callthis );
        },

        
/**
         * @since 4.1.0
         *
         * @param {Boolean} expanded
         * @param {Object} [params]
         * @returns {Boolean} false if state already applied
         */
        
_toggleExpandedapi.Section.prototype._toggleExpanded,

        
/**
         * @since 4.1.0
         *
         * @param {Object} [params]
         * @returns {Boolean} false if already expanded
         */
        
expandapi.Section.prototype.expand,

        
/**
         * Expand the widget form control
         *
         * @deprecated 4.1.0 Use this.expand() instead.
         */
        
expandForm: function() {
            
this.expand();
        },

        
/**
         * @since 4.1.0
         *
         * @param {Object} [params]
         * @returns {Boolean} false if already collapsed
         */
        
collapseapi.Section.prototype.collapse,

        
/**
         * Collapse the widget form control
         *
         * @deprecated 4.1.0 Use this.collapse() instead.
         */
        
collapseForm: function() {
            
this.collapse();
        },

        
/**
         * Expand or collapse the widget control
         *
         * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide )
         *
         * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility
         */
        
toggleForm: function( showOrHide ) {
            if ( 
typeof showOrHide === 'undefined' ) {
                
showOrHide = ! this.expanded();
            }
            
this.expandedshowOrHide );
        },

        
/**
         * Respond to change in the expanded state.
         *
         * @param {Boolean} expanded
         * @param {Object} args  merged on top of this.defaultActiveArguments
         */
        
onChangeExpanded: function ( expandedargs ) {
            var 
self this$widget$insidecompleteprevComplete;

            
// If the expanded state is unchanged only manipulate container expanded states
            
if ( args.unchanged ) {
                if ( 
expanded ) {
                    
api.Control.prototype.expand.callself, {
                        
completeCallback:  args.completeCallback
                    
});
                }
                return;
            }

            
$widget this.container.find'div.widget:first' );
            
$inside $widget.find'.widget-inside:first' );

            if ( 
expanded ) {

                
self.expandControlSection();

                
// Close all other widget controls before expanding this one
                
api.control.each( function( otherControl ) {
                    if ( 
self.params.type === otherControl.params.type && self !== otherControl ) {
                        
otherControl.collapse();
                    }
                } );

                
complete = function() {
                    
self.container.removeClass'expanding' );
                    
self.container.addClass'expanded' );
                    
self.container.trigger'expanded' );
                };
                if ( 
args.completeCallback ) {
                    
prevComplete complete;
                    
complete = function () {
                        
prevComplete();
                        
args.completeCallback();
                    };
                }

                if ( 
self.params.is_wide ) {
                    
$inside.fadeInargs.durationcomplete );
                } else {
                    
$inside.slideDownargs.durationcomplete );
                }

                
self.container.trigger'expand' );
                
self.container.addClass'expanding' );
            } else {

                
complete = function() {
                    
self.container.removeClass'collapsing' );
                    
self.container.removeClass'expanded' );
                    
self.container.trigger'collapsed' );
                };
                if ( 
args.completeCallback ) {
                    
prevComplete complete;
                    
complete = function () {
                        
prevComplete();
                        
args.completeCallback();
                    };
                }

                
self.container.trigger'collapse' );
                
self.container.addClass'collapsing' );

                if ( 
self.params.is_wide ) {
                    
$inside.fadeOutargs.durationcomplete );
                } else {
                    
$inside.slideUpargs.duration, function() {
                        
$widget.css( { width:''margin:'' } );
                        
complete();
                    } );
                }
            }
        },

        
/**
         * Get the position (index) of the widget in the containing sidebar
         *
         * @returns {Number}
         */
        
getWidgetSidebarPosition: function() {
            var 
sidebarWidgetIdsposition;

            
sidebarWidgetIds this.getSidebarWidgetsControl().setting();
            
position _.indexOfsidebarWidgetIdsthis.params.widget_id );

            if ( 
position === -) {
                return;
            }

            return 
position;
        },

        
/**
         * Move widget up one in the sidebar
         */
        
moveUp: function() {
            
this._moveWidgetByOne( -);
        },

        
/**
         * Move widget up one in the sidebar
         */
        
moveDown: function() {
            
this._moveWidgetByOne);
        },

        
/**
         * @private
         *
         * @param {Number} offset 1|-1
         */
        
_moveWidgetByOne: function( offset ) {
            var 
isidebarWidgetsSettingsidebarWidgetIds,    adjacentWidgetId;

            
this.getWidgetSidebarPosition();

            
sidebarWidgetsSetting this.getSidebarWidgetsControl().setting;
            
sidebarWidgetIds = Array.prototype.slice.callsidebarWidgetsSetting() ); // clone
            
adjacentWidgetId sidebarWidgetIds[offset];
            
sidebarWidgetIds[offset] = this.params.widget_id;
            
sidebarWidgetIds[i] = adjacentWidgetId;

            
sidebarWidgetsSettingsidebarWidgetIds );
        },

        
/**
         * Toggle visibility of the widget move area
         *
         * @param {Boolean} [showOrHide]
         */
        
toggleWidgetMoveArea: function( showOrHide ) {
            var 
self this$moveWidgetArea;

            
$moveWidgetArea this.container.find'.move-widget-area' );

            if ( 
typeof showOrHide === 'undefined' ) {
                
showOrHide = ! $moveWidgetArea.hasClass'active' );
            }

            if ( 
showOrHide ) {
                
// reset the selected sidebar
                
$moveWidgetArea.find'.selected' ).removeClass'selected' );

                
$moveWidgetArea.find'li' ).filter( function() {
                    return $( 
this ).data'id' ) === self.params.sidebar_id;
                } ).
addClass'selected' );

                
this.container.find'.move-widget-btn' ).prop'disabled'true );
            }

            
$moveWidgetArea.toggleClass'active'showOrHide );
        },

        
/**
         * Highlight the widget control and section
         */
        
highlightSectionAndControl: function() {
            var 
$target;

            if ( 
this.container.is':hidden' ) ) {
                
$target this.container.closest'.control-section' );
            } else {
                
$target this.container;
            }

            $( 
'.highlighted' ).removeClass'highlighted' );
            
$target.addClass'highlighted' );

            
setTimeout( function() {
                
$target.removeClass'highlighted' );
            }, 
500 );
        }
    } );

    
/**
     * wp.customize.Widgets.SidebarSection
     *
     * Customizer section representing a widget area widget
     *
     * @since 4.1.0
     */
    
api.Widgets.SidebarSection api.Section.extend({

        
/**
         * Sync the section's active state back to the Backbone model's is_rendered attribute
         *
         * @since 4.1.0
         */
        
ready: function () {
            var 
section thisregisteredSidebar;
            
api.Section.prototype.ready.callthis );
            
registeredSidebar api.Widgets.registeredSidebars.getsection.params.sidebarId );
            
section.active.bind( function ( active ) {
                
registeredSidebar.set'is_rendered'active );
            });
            
registeredSidebar.set'is_rendered'section.active() );
        }
    });

    
/**
     * wp.customize.Widgets.SidebarControl
     *
     * Customizer control for widgets.
     * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type
     *
     * @since 3.9.0
     *
     * @constructor
     * @augments wp.customize.Control
     */
    
api.Widgets.SidebarControl api.Control.extend({

        
/**
         * Set up the control
         */
        
ready: function() {
            
this.$controlSection this.container.closest'.control-section' );
            
this.$sectionContent this.container.closest'.accordion-section-content' );

            
this._setupModel();
            
this._setupSortable();
            
this._setupAddition();
            
this._applyCardinalOrderClassNames();
        },

        
/**
         * Update ordering of widget control forms when the setting is updated
         */
        
_setupModel: function() {
            var 
self this;

            
this.setting.bind( function( newWidgetIdsoldWidgetIds ) {
                var 
widgetFormControlsremovedWidgetIdspriority;

                
removedWidgetIds _oldWidgetIds ).differencenewWidgetIds );

                
// Filter out any persistent widget IDs for widgets which have been deactivated
                
newWidgetIds _newWidgetIds ).filter( function( newWidgetId ) {
                    var 
parsedWidgetId parseWidgetIdnewWidgetId );

                    return !! 
api.Widgets.availableWidgets.findWhere( { id_baseparsedWidgetId.id_base } );
                } );

                
widgetFormControls _newWidgetIds ).map( function( widgetId ) {
                    var 
widgetFormControl api.Widgets.getWidgetFormControlForWidgetwidgetId );

                    if ( ! 
widgetFormControl ) {
                        
widgetFormControl self.addWidgetwidgetId );
                    }

                    return 
widgetFormControl;
                } );

                
// Sort widget controls to their new positions
                
widgetFormControls.sort( function( a) {
                    var 
aIndex _.indexOfnewWidgetIdsa.params.widget_id ),
                        
bIndex _.indexOfnewWidgetIdsb.params.widget_id );
                    return 
aIndex bIndex;
                });

                
priority 0;
                
_widgetFormControls ).each( function ( control ) {
                    
control.prioritypriority );
                    
control.sectionself.section() );
                    
priority += 1;
                });
                
self.prioritypriority ); // Make sure sidebar control remains at end

                // Re-sort widget form controls (including widgets form other sidebars newly moved here)
                
self._applyCardinalOrderClassNames();

                
// If the widget was dragged into the sidebar, make sure the sidebar_id param is updated
                
_widgetFormControls ).each( function( widgetFormControl ) {
                    
widgetFormControl.params.sidebar_id self.params.sidebar_id;
                } );

                
// Cleanup after widget removal
                
_removedWidgetIds ).each( function( removedWidgetId ) {

                    
// Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
                    
setTimeout( function() {
                        var 
removedControlwasDraggedToAnotherSidebarinactiveWidgetsremovedIdBase,
                            
widgetisPresentInAnotherSidebar false;

                        
// Check if the widget is in another sidebar
                        
api.each( function( otherSetting ) {
                            if ( 
otherSetting.id === self.setting.id || !== otherSetting.id.indexOf'sidebars_widgets[' ) || otherSetting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
                                return;
                            }

                            var 
otherSidebarWidgets otherSetting(), i;

                            
_.indexOfotherSidebarWidgetsremovedWidgetId );
                            if ( -
!== ) {
                                
isPresentInAnotherSidebar true;
                            }
                        } );

                        
// If the widget is present in another sidebar, abort!
                        
if ( isPresentInAnotherSidebar ) {
                            return;
                        }

                        
removedControl api.Widgets.getWidgetFormControlForWidgetremovedWidgetId );

                        
// Detect if widget control was dragged to another sidebar
                        
wasDraggedToAnotherSidebar removedControl && $.containsdocumentremovedControl.container[0] ) && ! $.containsself.$sectionContent[0], removedControl.container[0] );

                        
// Delete any widget form controls for removed widgets
                        
if ( removedControl && ! wasDraggedToAnotherSidebar ) {
                            
api.control.removeremovedControl.id );
                            
removedControl.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 ( api.Widgets.savedWidgetIds[removedWidgetId] ) {
                            
inactiveWidgets api.value'sidebars_widgets[wp_inactive_widgets]' )().slice();
                            
inactiveWidgets.pushremovedWidgetId );
                            
api.value'sidebars_widgets[wp_inactive_widgets]' )( _inactiveWidgets ).unique() );
                        }

                        
// Make old single widget available for adding again
                        
removedIdBase parseWidgetIdremovedWidgetId ).id_base;
                        
widget api.Widgets.availableWidgets.findWhere( { id_baseremovedIdBase } );
                        if ( 
widget && ! widget.get'is_multi' ) ) {
                            
widget.set'is_disabled'false );
                        }
                    } );

                } );
            } );
        },

        
/**
         * Allow widgets in sidebar to be re-ordered, and for the order to be previewed
         */
        
_setupSortable: function() {
            var 
self this;

            
this.isReordering false;

            
/**
             * Update widget order setting when controls are re-ordered
             */
            
this.$sectionContent.sortable( {
                
items'> .customize-control-widget_form',
                
handle'.widget-top',
                
axis'y',
                
connectWith'.accordion-section-content:has(.customize-control-sidebar_widgets)',
                
update: function() {
                    var 
widgetContainerIds self.$sectionContent.sortable'toArray' ), widgetIds;

                    
widgetIds = $.mapwidgetContainerIds, function( widgetContainerId ) {
                        return $( 
'#' widgetContainerId ).find':input[name=widget-id]' ).val();
                    } );

                    
self.settingwidgetIds );
                }
            } );

            
/**
             * Expand other Customizer sidebar section when dragging a control widget over it,
             * allowing the control to be dropped into another section
             */
            
this.$controlSection.find'.accordion-section-title' ).droppable({
                
accept'.customize-control-widget_form',
                
over: function() {
                    var 
section api.sectionself.section.get() );
                    
section.expand({
                        
allowMultipletrue// Prevent the section being dragged from to be collapsed
                        
completeCallback: function () {
                            
// @todo It is not clear when refreshPositions should be called on which sections, or if it is even needed
                            
api.section.each( function ( otherSection ) {
                                if ( 
otherSection.container.find'.customize-control-sidebar_widgets' ).length ) {
                                    
otherSection.container.find'.accordion-section-content:first' ).sortable'refreshPositions' );
                                }
                            } );
                        }
                    });
                }
            });

            
/**
             * Keyboard-accessible reordering
             */
            
this.container.find'.reorder-toggle' ).on'click keydown', function( event ) {
                if ( 
event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar
                    
return;
                }

                
self.toggleReordering( ! self.isReordering );
            } );
        },

        
/**
         * Set up UI for adding a new widget
         */
        
_setupAddition: function() {
            var 
self this;

            
this.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 ( 
self.$sectionContent.hasClass'reordering' ) ) {
                    return;
                }

                if ( ! $( 
'body' ).hasClass'adding-widget' ) ) {
                    
api.Widgets.availableWidgetsPanel.openself );
                } else {
                    
api.Widgets.availableWidgetsPanel.close();
                }
            } );
        },

        
/**
         * Add classes to the widget_form controls to assist with styling
         */
        
_applyCardinalOrderClassNames: function() {
            var 
widgetControls = [];
            
_.eachthis.setting(), function ( widgetId ) {
                var 
widgetControl api.Widgets.getWidgetFormControlForWidgetwidgetId );
                if ( 
widgetControl ) {
                    
widgetControls.pushwidgetControl );
                }
            });

            if ( ! 
widgetControls.length ) {
                
this.container.find'.reorder-toggle' ).hide();
                return;
            } else {
                
this.container.find'.reorder-toggle' ).show();
            }

            $( 
widgetControls ).each( function () {
                $( 
this.container )
                    .
removeClass'first-widget' )
                    .
removeClass'last-widget' )
                    .
find'.move-widget-down, .move-widget-up' ).prop'tabIndex');
            });

            
_.firstwidgetControls ).container
                
.addClass'first-widget' )
                .
find'.move-widget-up' ).prop'tabIndex', -);

            
_.lastwidgetControls ).container
                
.addClass'last-widget' )
                .
find'.move-widget-down' ).prop'tabIndex', -);
        },


        
/***********************************************************************
         * Begin public API methods
         **********************************************************************/

        /**
         * Enable/disable the reordering UI
         *
         * @param {Boolean} showOrHide to enable/disable reordering
         *
         * @todo We should have a reordering state instead and rename this to onChangeReordering
         */
        
toggleReordering: function( showOrHide ) {
            
showOrHide BooleanshowOrHide );

            if ( 
showOrHide === this.$sectionContent.hasClass'reordering' ) ) {
                return;
            }

            
this.isReordering showOrHide;
            
this.$sectionContent.toggleClass'reordering'showOrHide );

            if ( 
showOrHide ) {
                
_this.getWidgetFormControls() ).each( function( formControl ) {
                    
formControl.collapse();
                } );

                
this.$sectionContent.find'.first-widget .move-widget' ).focus();
                
this.$sectionContent.find'.add-new-widget' ).prop'tabIndex', -);
            } else {
                
this.$sectionContent.find'.add-new-widget' ).prop'tabIndex');
            }
        },

        
/**
         * Get the widget_form Customize controls associated with the current sidebar.
         *
         * @since 3.9
         * @return {wp.customize.controlConstructor.widget_form[]}
         */
        
getWidgetFormControls: function() {
            var 
formControls = [];

            
_this.setting() ).each( function( widgetId ) {
                var 
settingId widgetIdToSettingIdwidgetId ),
                    
formControl api.controlsettingId );
                if ( 
formControl ) {
                    
formControls.pushformControl );
                }
            } );

            return 
formControls;
        },

        
/**
         * @param {string} widgetId or an id_base for adding a previously non-existing widget
         * @returns {object|false} widget_form control instance, or false on error
         */
        
addWidget: function( widgetId ) {
            var 
self thiscontrolHtml$widgetcontrolType 'widget_form'controlContainercontrolConstructor,
                
parsedWidgetId parseWidgetIdwidgetId ),
                
widgetNumber parsedWidgetId.number,
                
widgetIdBase parsedWidgetId.id_base,
                
widget api.Widgets.availableWidgets.findWhere( {id_basewidgetIdBase} ),
                
settingIdisExistingWidgetwidgetFormControlsidebarWidgetssettingArgssetting;

            if ( ! 
widget ) {
                return 
false;
            }

            if ( 
widgetNumber && ! widget.get'is_multi' ) ) {
                return 
false;
            }

            
// Set up new multi widget
            
if ( widget.get'is_multi' ) && ! widgetNumber ) {
                
widget.set'multi_number'widget.get'multi_number' ) + );
                
widgetNumber widget.get'multi_number' );
            }

            
controlHtml = $.trim( $( '#widget-tpl-' widget.get'id' ) ).html() );
            if ( 
widget.get'is_multi' ) ) {
                
controlHtml controlHtml.replace( /<[^<>]+>/g, function( ) {
                    return 
m.replace( /__i__|%i%/gwidgetNumber );
                } );
            } else {
                
widget.set'is_disabled'true ); // Prevent single widget from being added again now
            
}

            
$widget = $( controlHtml );

            
controlContainer = $( '<li/>' )
                .
addClass'customize-control' )
                .
addClass'customize-control-' controlType )
                .
append$widget );

            
// Remove icon which is visible inside the panel
            
controlContainer.find'> .widget-icon' ).remove();

            if ( 
widget.get'is_multi' ) ) {
                
controlContainer.find'input[name="widget_number"]' ).valwidgetNumber );
                
controlContainer.find'input[name="multi_number"]' ).valwidgetNumber );
            }

            
widgetId controlContainer.find'[name="widget-id"]' ).val();

            
controlContainer.hide(); // to be slid-down below

            
settingId 'widget_' widget.get'id_base' );
            if ( 
widget.get'is_multi' ) ) {
                
settingId += '[' widgetNumber ']';
            }
            
controlContainer.attr'id''customize-control-' settingId.replace( /]/g'' ).replace( /[/g'-' ) );

            
// Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget)
            
isExistingWidget api.hassettingId );
            if ( ! 
isExistingWidget ) {
                
settingArgs = {
                    
transport'refresh',
                    
previewerthis.setting.previewer
                
};
                
setting api.createsettingIdsettingId''settingArgs );
                
setting.set( {} ); // mark dirty, changing from '' to {}
            
}

            
controlConstructor api.controlConstructor[controlType];
            
widgetFormControl = new controlConstructorsettingId, {
                
params: {
                    
settings: {
                        
'default'settingId
                    
},
                    
contentcontrolContainer,
                    
sidebar_idself.params.sidebar_id,
                    
widget_idwidgetId,
                    
widget_id_basewidget.get'id_base' ),
                    
typecontrolType,
                    
is_new: ! isExistingWidget,
                    
widthwidget.get'width' ),
                    
heightwidget.get'height' ),
                    
is_widewidget.get'is_wide' )
                },
                
previewerself.setting.previewer
            
} );
            
api.control.addsettingIdwidgetFormControl );

            
// Make sure widget is removed from the other sidebars
            
api.each( function( otherSetting ) {
                if ( 
otherSetting.id === self.setting.id ) {
                    return;
                }

                if ( 
!== otherSetting.id.indexOf'sidebars_widgets[' ) ) {
                    return;
                }

                var 
otherSidebarWidgets otherSetting().slice(),
                    
_.indexOfotherSidebarWidgetswidgetId );

                if ( -
!== ) {
                    
otherSidebarWidgets.splice);
                    
otherSettingotherSidebarWidgets );
                }
            } );

            
// Add widget to this sidebar
            
sidebarWidgets this.setting().slice();
            if ( -
=== _.indexOfsidebarWidgetswidgetId ) ) {
                
sidebarWidgets.pushwidgetId );
                
this.settingsidebarWidgets );
            }

            
controlContainer.slideDown( function() {
                if ( 
isExistingWidget ) {
                    
widgetFormControl.updateWidget( {
                        
instancewidgetFormControl.setting()
                    } );
                }
            } );

            return 
widgetFormControl;
        }
    } );

    
// Register models for custom section and control types
    
$.extendapi.sectionConstructor, {
        
sidebarapi.Widgets.SidebarSection
    
});
    $.
extendapi.controlConstructor, {
        
widget_formapi.Widgets.WidgetControl,
        
sidebar_widgetsapi.Widgets.SidebarControl
    
});

    
// Refresh the nonce if login sends updated nonces over.
    
api.bind'nonce-refresh', function( nonces ) {
        
api.Widgets.data.nonce nonces['update-widget'];
    });

    
/**
     * Init Customizer for widgets.
     */
    
api.bind'ready', function() {
        
// Set up the widgets panel
        
api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
            
collectionapi.Widgets.availableWidgets
        
});

        
// Highlight widget control
        
api.previewer.bind'highlight-widget-control'api.Widgets.highlightWidgetFormControl );

        
// Open and focus widget control
        
api.previewer.bind'focus-widget-control'api.Widgets.focusWidgetFormControl );
    } );

    
/**
     * Highlight a widget control.
     *
     * @param {string} widgetId
     */
    
api.Widgets.highlightWidgetFormControl = function( widgetId ) {
        var 
control api.Widgets.getWidgetFormControlForWidgetwidgetId );

        if ( 
control ) {
            
control.highlightSectionAndControl();
        }
    },

    
/**
     * Focus a widget control.
     *
     * @param {string} widgetId
     */
    
api.Widgets.focusWidgetFormControl = function( widgetId ) {
        var 
control api.Widgets.getWidgetFormControlForWidgetwidgetId );

        if ( 
control ) {
            
control.focus();
        }
    },

    
/**
     * Given a widget control, find the sidebar widgets control that contains it.
     * @param {string} widgetId
     * @return {object|null}
     */
    
api.Widgets.getSidebarWidgetControlContainingWidget = function( widgetId ) {
        var 
foundControl null;

        
// @todo this can use widgetIdToSettingId(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
        
api.control.each( function( control ) {
            if ( 
control.params.type === 'sidebar_widgets' && -!== _.indexOfcontrol.setting(), widgetId ) ) {
                
foundControl control;
            }
        } );

        return 
foundControl;
    };

    
/**
     * Given a widget ID for a widget appearing in the preview, get the widget form control associated with it.
     *
     * @param {string} widgetId
     * @return {object|null}
     */
    
api.Widgets.getWidgetFormControlForWidget = function( widgetId ) {
        var 
foundControl null;

        
// @todo We can just use widgetIdToSettingId() here
        
api.control.each( function( control ) {
            if ( 
control.params.type === 'widget_form' && control.params.widget_id === widgetId ) {
                
foundControl control;
            }
        } );

        return 
foundControl;
    };

    
/**
     * @param {String} widgetId
     * @returns {Object}
     */
    
function parseWidgetIdwidgetId ) {
        var 
matchesparsed = {
            
numbernull,
            
id_basenull
        
};

        
matches widgetId.match( /^(.+)-(d+)$/ );
        if ( 
matches ) {
            
parsed.id_base matches[1];
            
parsed.number parseIntmatches[2], 10 );
        } else {
            
// likely an old single widget
            
parsed.id_base widgetId;
        }

        return 
parsed;
    }

    
/**
     * @param {String} widgetId
     * @returns {String} settingId
     */
    
function widgetIdToSettingIdwidgetId ) {
        var 
parsed parseWidgetIdwidgetId ), settingId;

        
settingId 'widget_' parsed.id_base;
        if ( 
parsed.number ) {
            
settingId += '[' parsed.number ']';
        }

        return 
settingId;
    }

})( 
window.wpjQuery );
?>
Онлайн: 0
Реклама