: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @borrows wp.customize~Container#activate as this#activate
* @borrows wp.customize~Container#deactivate as this#deactivate
* @borrows wp.customize~Container#_toggleActive as this#_toggleActive
* @param {string} id - Unique identifier for the control instance.
* @param {Object} options - Options hash for the control instance.
* @param {Object} options.type - Type of control (e.g. text, radio, dropdown-pages, etc.)
* @param {string} [options.content] - The HTML content for the control or at least its container. This should normally be left blank and instead supplying a templateId.
* @param {string} [options.templateId] - Template ID for control's content.
* @param {string} [options.priority=10] - Order of priority to show the control within the section.
* @param {string} [options.active=true] - Whether the control is active.
* @param {string} options.section - The ID of the section the control belongs to.
* @param {mixed} [options.setting] - The ID of the main setting or an instance of this setting.
* @param {mixed} options.settings - An object with keys (e.g. default) that maps to setting IDs or Setting/Value objects, or an array of setting IDs or Setting/Value objects.
* @param {mixed} options.settings.default - The ID of the setting the control relates to.
* @param {string} options.settings.data - @todo Is this used?
* @param {string} options.label - Label.
* @param {string} options.description - Description.
* @param {number} [options.instanceNumber] - Order in which this instance was created in relation to other instances.
* @param {Object} [options.params] - Deprecated wrapper for the above properties.
initialize: function( id, options ) {
var control = this, deferredSettingIds = [], settings, gatherSettings;
control.params = _.extend(
control.params || {}, // In case subclass already defines.
options.params || options || {} // The options.params property is deprecated, but it is checked first for back-compat.
if ( ! api.Control.instanceCounter ) {
api.Control.instanceCounter = 0;
api.Control.instanceCounter++;
if ( ! control.params.instanceNumber ) {
control.params.instanceNumber = api.Control.instanceCounter;
// Look up the type if one was not supplied.
if ( ! control.params.type ) {
_.find( api.controlConstructor, function( Constructor, type ) {
if ( Constructor === control.constructor ) {
control.params.type = type;
if ( ! control.params.content ) {
control.params.content = $( '<li></li>', {
id: 'customize-control-' + id.replace( /]/g, '' ).replace( /\[/g, '-' ),
'class': 'customize-control customize-control-' + control.params.type
control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); // Deprecated, likely dead code from time before #28709.
if ( control.params.content ) {
control.container = $( control.params.content );
control.container = $( control.selector ); // Likely dead, per above. See #28709.
if ( control.params.templateId ) {
control.templateSelector = control.params.templateId;
control.templateSelector = 'customize-control-' + control.params.type + '-content';
control.deferred = _.extend( control.deferred || {}, {
embedded: new $.Deferred()
control.section = new api.Value();
control.priority = new api.Value();
control.active = new api.Value();
control.activeArgumentsQueue = [];
control.notifications = new api.Notifications({
control.active.bind( function ( active ) {
var args = control.activeArgumentsQueue.shift();
args = $.extend( {}, control.defaultActiveArguments, args );
control.onChangeActive( active, args );
control.section.set( control.params.section );
control.priority.set( isNaN( control.params.priority ) ? 10 : control.params.priority );
control.active.set( control.params.active );
api.utils.bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
if ( control.params.setting ) {
settings['default'] = control.params.setting;
_.extend( settings, control.params.settings );
// Note: Settings can be an array or an object, with values being either setting IDs or Setting (or Value) objects.
_.each( settings, function( value, key ) {
if ( _.isObject( value ) && _.isFunction( value.extended ) && value.extended( api.Value ) ) {
control.settings[ key ] = value;
} else if ( _.isString( value ) ) {
control.settings[ key ] = setting;
deferredSettingIds.push( value );
gatherSettings = function() {
// Fill-in all resolved settings.
_.each( settings, function ( settingId, key ) {
if ( ! control.settings[ key ] && _.isString( settingId ) ) {
control.settings[ key ] = api( settingId );
// Make sure settings passed as array gets associated with default.
if ( control.settings[0] && ! control.settings['default'] ) {
control.settings['default'] = control.settings[0];
// Identify the main setting.
control.setting = control.settings['default'] || null;
control.linkElements(); // Link initial elements present in server-rendered content.
if ( 0 === deferredSettingIds.length ) {
api.apply( api, deferredSettingIds.concat( gatherSettings ) );
// After the control is embedded on the page, invoke the "ready" method.
control.deferred.embedded.done( function () {
control.linkElements(); // Link any additional elements after template is rendered by renderContent().
control.setupNotifications();
* Link elements between settings and inputs.
linkElements: function () {
var control = this, nodes, radios, element;
nodes = control.container.find( '[data-customize-setting-link], [data-customize-setting-key-link]' );
nodes.each( function () {
var node = $( this ), name, setting;
if ( node.data( 'customizeSettingLinked' ) ) {
node.data( 'customizeSettingLinked', true ); // Prevent re-linking element.
if ( node.is( ':radio' ) ) {
name = node.prop( 'name' );
node = nodes.filter( '[name="' + name + '"]' );
// Let link by default refer to setting ID. If it doesn't exist, fallback to looking up by setting key.
if ( node.data( 'customizeSettingLink' ) ) {
setting = api( node.data( 'customizeSettingLink' ) );
} else if ( node.data( 'customizeSettingKeyLink' ) ) {
setting = control.settings[ node.data( 'customizeSettingKeyLink' ) ];
element = new api.Element( node );
control.elements.push( element );
element.set( setting() );
* Embed the control into the page.
// Watch for changes to the section state.
inject = function ( sectionId ) {
if ( ! sectionId ) { // @todo Allow a control to be embedded without a section, for instance a control embedded in the front end.
// Wait for the section to be registered.
api.section( sectionId, function ( section ) {
// Wait for the section to be ready/initialized.
section.deferred.embedded.done( function () {
parentContainer = ( section.contentContainer.is( 'ul' ) ) ? section.contentContainer : section.contentContainer.find( 'ul:first' );
if ( ! control.container.parent().is( parentContainer ) ) {
parentContainer.append( control.container );
control.deferred.embedded.resolve();
control.section.bind( inject );
inject( control.section.get() );
* Triggered when the control's markup has been injected into the DOM.
var control = this, newItem;
if ( 'dropdown-pages' === control.params.type && control.params.allow_addition ) {
newItem = control.container.find( '.new-content-item-wrapper' );
newItem.hide(); // Hide in JS to preserve flex display when showing.
control.container.on( 'click', '.add-new-toggle', function( e ) {
$( e.currentTarget ).slideUp( 180 );
newItem.slideDown( 180 );
newItem.find( '.create-item-input' ).focus();
control.container.on( 'click', '.add-content', function() {
control.container.on( 'keydown', '.create-item-input', function( e ) {
if ( 13 === e.which ) { // Enter.
* Get the element inside of a control's container that contains the validation error message.
* Control subclasses may override this to return the proper container to render notifications into.
* Injects the notification container for existing controls that lack the necessary container,
* including special handling for nav menu items and widgets.
* @return {jQuery} Setting validation message element.
getNotificationsContainerElement: function() {
var control = this, controlTitle, notificationsContainer;
notificationsContainer = control.container.find( '.customize-control-notifications-container:first' );
if ( notificationsContainer.length ) {
return notificationsContainer;
notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' );
if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) {
control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer );
} else if ( control.container.hasClass( 'customize-control-widget_form' ) ) {
control.container.find( '.widget-inside:first' ).prepend( notificationsContainer );
controlTitle = control.container.find( '.customize-control-title' );
if ( controlTitle.length ) {
controlTitle.after( notificationsContainer );
control.container.prepend( notificationsContainer );
return notificationsContainer;
setupNotifications: function() {
var control = this, renderNotificationsIfVisible, onSectionAssigned;
// Add setting notifications to the control notification.
_.each( control.settings, function( setting ) {
if ( ! setting.notifications ) {
setting.notifications.bind( 'add', function( settingNotification ) {
control.notifications.add( new api.Notification( setting.id + ':' + settingNotification.code, params ) );
setting.notifications.bind( 'remove', function( settingNotification ) {
control.notifications.remove( setting.id + ':' + settingNotification.code );
renderNotificationsIfVisible = function() {
var sectionId = control.section();
if ( ! sectionId || ( api.section.has( sectionId ) && api.section( sectionId ).expanded() ) ) {
control.notifications.render();
control.notifications.bind( 'rendered', function() {
var notifications = control.notifications.get();
control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
control.container.toggleClass( 'has-error', 0 !== _.where( notifications, { type: 'error' } ).length );
onSectionAssigned = function( newSectionId, oldSectionId ) {
if ( oldSectionId && api.section.has( oldSectionId ) ) {
api.section( oldSectionId ).expanded.unbind( renderNotificationsIfVisible );
api.section( newSectionId, function( section ) {
section.expanded.bind( renderNotificationsIfVisible );
renderNotificationsIfVisible();
control.section.bind( onSectionAssigned );
onSectionAssigned( control.section.get() );
control.notifications.bind( 'change', _.debounce( renderNotificationsIfVisible ) );
* Renders the `control.notifications` into the control's container.
* Control subclasses may override this method to do their own handling
* of rendering notifications.
* @deprecated in favor of `control.notifications.render()`
* @this {wp.customize.Control}
renderNotifications: function() {
var control = this, container, notifications, hasError = false;
if ( 'undefined' !== typeof console && console.warn ) {
console.warn( '[DEPRECATED] wp.customize.Control.prototype.renderNotifications() is deprecated in favor of instantiating a wp.customize.Notifications and calling its render() method.' );
container = control.getNotificationsContainerElement();
if ( ! container || ! container.length ) {
control.notifications.each( function( notification ) {
notifications.push( notification );
if ( 'error' === notification.type ) {
if ( 0 === notifications.length ) {
container.stop().slideUp( 'fast' );
container.stop().slideDown( 'fast', null, function() {
$( this ).css( 'height', 'auto' );
if ( ! control.notificationsTemplate ) {
control.notificationsTemplate = wp.template( 'customize-control-notifications' );
control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
control.container.toggleClass( 'has-error', hasError );
container.empty().append(
control.notificationsTemplate( { notifications: notifications, altNotice: Boolean( control.altNotice ) } ).trim()
* Normal controls do not expand, so just expand its parent
* @param {Object} [params]
expand: function ( params ) {
api.section( this.section() ).expand( params );
* Documented using @borrows in the constructor.
* Update UI in response to a change in the control's active state.
* This does not change the active state, it merely handles the behavior
* for when it does change.
* @param {boolean} active
* @param {number} args.duration
* @param {Function} args.completeCallback
onChangeActive: function ( active, args ) {
if ( args.completeCallback ) {
if ( ! $.contains( document, this.container[0] ) ) {
// jQuery.fn.slideUp is not hiding an element if it is not in the DOM.
this.container.toggle( active );
if ( args.completeCallback ) {
this.container.slideDown( args.duration, args.completeCallback );
this.container.slideUp( args.duration, args.completeCallback );
* @deprecated 4.1.0 Use this.onChangeActive() instead.
toggle: function ( active ) {
return this.onChangeActive( active, this.defaultActiveArguments );
* Documented using @borrows in the constructor
activate: Container.prototype.activate,
* Documented using @borrows in the constructor
deactivate: Container.prototype.deactivate,
* Documented using @borrows in the constructor
_toggleActive: Container.prototype._toggleActive,
// @todo This function appears to be dead code and can be removed.
dropdownInit: function() {
statuses = this.container.find('.dropdown-status'),
update = function( to ) {
if ( 'string' === typeof to && params.statuses && params.statuses[ to ] ) {
statuses.html( params.statuses[ to ] ).show();
// Support the .dropdown class to open/close complex elements.
this.container.on( 'click keydown', '.dropdown', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
control.container.toggleClass( 'open' );
if ( control.container.hasClass( 'open' ) ) {
control.container.parent().parent().find( 'li.library-selected' ).focus();