Customize: Improve usability of Customize JS API.

* Eliminate need to pass both ID and instance in calls to `Values#add()` for panels, sections, controls, settings, partials, and notifications.
* Eliminate need to supply `content` param when constructing a `Control`.
* Unwrap the `options.params` object passed in constructors to just pass a flat `options`. (Back-compat is maintained.)
* Add support for `templateId` param for `Control` to override which template is used for the content.
* Remove unused `previewer` being supplied in `Control` instances.
* Rename `classes` to `containerClasses` on `Notification`.
* Automatically supply `instanceNumber` to improve stable sorting.
* Use `api.Notifications` for notifications in settings instead of `api.Value`.

See #30741.
Fixes #42083.


git-svn-id: https://develop.svn.wordpress.org/trunk@41726 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Weston Ruter 2017-10-04 06:47:37 +00:00
parent 1eba2e5e3e
commit 3f1de03834
8 changed files with 272 additions and 303 deletions

View File

@ -30,9 +30,9 @@
initialize: function( code, params ) { initialize: function( code, params ) {
var notification = this; var notification = this;
api.Notification.prototype.initialize.call( notification, code, params ); api.Notification.prototype.initialize.call( notification, code, params );
notification.classes += ' notification-overlay'; notification.containerClasses += ' notification-overlay';
if ( notification.loading ) { if ( notification.loading ) {
notification.classes += ' notification-loading'; notification.containerClasses += ' notification-loading';
} }
} }
}); });
@ -105,17 +105,25 @@
* Add notification to the collection. * Add notification to the collection.
* *
* @since 4.9.0 * @since 4.9.0
* @param {string} code - Notification code. *
* @param {object} params - Notification params. * @param {string|wp.customize.Notification} - Notification object to add. Alternatively code may be supplied, and in that case the second notificationObject argument must be supplied.
* @return {api.Notification} Added instance (or existing instance if it was already added). * @param {wp.customize.Notification} [notificationObject] - Notification to add when first argument is the code string.
* @returns {wp.customize.Notification} Added notification (or existing instance if it was already added).
*/ */
add: function( code, params ) { add: function( notification, notificationObject ) {
var collection = this; var collection = this, code, instance;
if ( 'string' === typeof notification ) {
code = notification;
instance = notificationObject;
} else {
code = notification.code;
instance = notification;
}
if ( ! collection.has( code ) ) { if ( ! collection.has( code ) ) {
collection._addedIncrement += 1; collection._addedIncrement += 1;
collection._addedOrder[ code ] = collection._addedIncrement; collection._addedOrder[ code ] = collection._addedIncrement;
} }
return api.Values.prototype.add.call( this, code, params ); return api.Values.prototype.add.call( collection, code, instance );
}, },
/** /**
@ -269,7 +277,7 @@
setting.id = id; setting.id = id;
setting.transport = setting.transport || 'refresh'; setting.transport = setting.transport || 'refresh';
setting._dirty = options.dirty || false; setting._dirty = options.dirty || false;
setting.notifications = new api.Values({ defaultConstructor: api.Notification }); setting.notifications = new api.Notifications();
// Whenever the setting's value changes, refresh the preview. // Whenever the setting's value changes, refresh the preview.
setting.bind( setting.preview ); setting.bind( setting.preview );
@ -745,27 +753,35 @@
* *
* @param {string} id - The ID for the container. * @param {string} id - The ID for the container.
* @param {object} options - Object containing one property: params. * @param {object} options - Object containing one property: params.
* @param {object} options.params - Object containing the following properties. * @param {string} options.title - Title shown when panel is collapsed and expanded.
* @param {string} options.params.title - Title shown when panel is collapsed and expanded. * @param {string=} [options.description] - Description shown at the top of the panel.
* @param {string=} [options.params.description] - Description shown at the top of the panel. * @param {number=100} [options.priority] - The sort priority for the panel.
* @param {number=100} [options.params.priority] - The sort priority for the panel. * @param {string} [options.templateId] - Template selector for container.
* @param {string=default} [options.params.type] - The type of the panel. See wp.customize.panelConstructor. * @param {string=default} [options.type] - The type of the panel. See wp.customize.panelConstructor.
* @param {string=} [options.params.content] - The markup to be used for the panel container. If empty, a JS template is used. * @param {string=} [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
* @param {boolean=true} [options.params.active] - Whether the panel is active or not. * @param {boolean=true} [options.active] - Whether the panel is active or not.
* @param {object} [options.params] - Deprecated wrapper for the above properties.
*/ */
initialize: function ( id, options ) { initialize: function ( id, options ) {
var container = this; var container = this;
container.id = id; container.id = id;
options = options || {};
options.params = _.defaults( if ( ! Container.instanceCounter ) {
options.params || {}, Container.instanceCounter = 0;
}
Container.instanceCounter++;
$.extend( container, {
params: _.defaults(
options.params || options, // Passing the params is deprecated.
container.defaults container.defaults
); )
} );
$.extend( container, options ); if ( ! container.params.instanceNumber ) {
container.params.instanceNumber = Container.instanceCounter;
}
container.notifications = new api.Notifications(); container.notifications = new api.Notifications();
container.templateSelector = 'customize-' + container.containerType + '-' + container.params.type; container.templateSelector = container.params.templateId || 'customize-' + container.containerType + '-' + container.params.type;
container.container = $( container.params.content ); container.container = $( container.params.content );
if ( 0 === container.container.length ) { if ( 0 === container.container.length ) {
container.container = $( container.getContainer() ); container.container = $( container.getContainer() );
@ -1206,16 +1222,16 @@
* @since 4.1.0 * @since 4.1.0
* *
* @param {string} id - The ID for the section. * @param {string} id - The ID for the section.
* @param {object} options - Object containing one property: params. * @param {object} options - Options.
* @param {object} options.params - Object containing the following properties. * @param {string} options.title - Title shown when section is collapsed and expanded.
* @param {string} options.params.title - Title shown when section is collapsed and expanded. * @param {string=} [options.description] - Description shown at the top of the section.
* @param {string=} [options.params.description] - Description shown at the top of the section. * @param {number=100} [options.priority] - The sort priority for the section.
* @param {number=100} [options.params.priority] - The sort priority for the section. * @param {string=default} [options.type] - The type of the section. See wp.customize.sectionConstructor.
* @param {string=default} [options.params.type] - The type of the section. See wp.customize.sectionConstructor. * @param {string=} [options.content] - The markup to be used for the section container. If empty, a JS template is used.
* @param {string=} [options.params.content] - The markup to be used for the section container. If empty, a JS template is used. * @param {boolean=true} [options.active] - Whether the section is active or not.
* @param {boolean=true} [options.params.active] - Whether the section is active or not. * @param {string} options.panel - The ID for the panel this section is associated with.
* @param {string} options.params.panel - The ID for the panel this section is associated with. * @param {string=} [options.customizeAction] - Additional context information shown before the section title when expanded.
* @param {string=} [options.params.customizeAction] - Additional context information shown before the section title when expanded. * @param {object} [options.params] - Deprecated wrapper for the above properties.
*/ */
initialize: function ( id, options ) { initialize: function ( id, options ) {
var section = this; var section = this;
@ -1838,7 +1854,7 @@
section.container.find( '.no-themes' ).hide(); section.container.find( '.no-themes' ).hide();
request = wp.ajax.post( 'customize_load_themes', params ); request = wp.ajax.post( 'customize_load_themes', params );
request.done(function( data ) { request.done(function( data ) {
var themes = data.themes, themeControl, newThemeControls; var themes = data.themes, newThemeControls;
// Stop and try again if the term changed while loading. // Stop and try again if the term changed while loading.
if ( '' !== section.nextTerm || '' !== section.nextTags ) { if ( '' !== section.nextTerm || '' !== section.nextTags ) {
@ -1860,20 +1876,14 @@
// Add controls for each theme. // Add controls for each theme.
_.each( themes, function( theme ) { _.each( themes, function( theme ) {
var customizeId = section.params.action + '_theme_' + theme.id; var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
themeControl = new api.controlConstructor.theme( customizeId, {
params: {
type: 'theme', type: 'theme',
content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>',
section: section.params.id, section: section.params.id,
active: true,
theme: theme, theme: theme,
priority: section.loaded + 1 priority: section.loaded + 1
},
previewer: api.previewer
} ); } );
api.control.add( customizeId, themeControl ); api.control.add( themeControl );
newThemeControls.push( themeControl ); newThemeControls.push( themeControl );
section.loaded = section.loaded + 1; section.loaded = section.loaded + 1;
}); });
@ -2482,13 +2492,13 @@
* *
* @param {string} id - The ID for the panel. * @param {string} id - The ID for the panel.
* @param {object} options - Object containing one property: params. * @param {object} options - Object containing one property: params.
* @param {object} options.params - Object containing the following properties. * @param {string} options.title - Title shown when panel is collapsed and expanded.
* @param {string} options.params.title - Title shown when panel is collapsed and expanded. * @param {string=} [options.description] - Description shown at the top of the panel.
* @param {string=} [options.params.description] - Description shown at the top of the panel. * @param {number=100} [options.priority] - The sort priority for the panel.
* @param {number=100} [options.params.priority] - The sort priority for the panel. * @param {string=default} [options.type] - The type of the panel. See wp.customize.panelConstructor.
* @param {string=default} [options.params.type] - The type of the panel. See wp.customize.panelConstructor. * @param {string=} [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
* @param {string=} [options.params.content] - The markup to be used for the panel container. If empty, a JS template is used. * @param {boolean=true} [options.active] - Whether the panel is active or not.
* @param {boolean=true} [options.params.active] - Whether the panel is active or not. * @param {object} [options.params] - Deprecated wrapper for the above properties.
*/ */
initialize: function ( id, options ) { initialize: function ( id, options ) {
var panel = this; var panel = this;
@ -2856,7 +2866,7 @@
wp.updates.maybeRequestFilesystemCredentials( event ); wp.updates.maybeRequestFilesystemCredentials( event );
$( document ).one( 'wp-theme-install-success', function( event, response ) { $( document ).one( 'wp-theme-install-success', function( event, response ) {
var theme = false, customizeId, themeControl; var theme = false, themeControl;
if ( preview ) { if ( preview ) {
api.notifications.remove( 'theme_installing' ); api.notifications.remove( 'theme_installing' );
@ -2877,21 +2887,15 @@
// Add theme control to installed section. // Add theme control to installed section.
theme.type = 'installed'; theme.type = 'installed';
customizeId = 'installed_theme_' + theme.id; themeControl = new api.controlConstructor.theme( 'installed_theme_' + theme.id, {
themeControl = new api.controlConstructor.theme( customizeId, {
params: {
type: 'theme', type: 'theme',
content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ),
section: 'installed_themes', section: 'installed_themes',
active: true,
theme: theme, theme: theme,
priority: 0 // Add all newly-installed themes to the top. priority: 0 // Add all newly-installed themes to the top.
},
previewer: api.previewer
} ); } );
api.control.add( customizeId, themeControl ); api.control.add( themeControl );
api.control( customizeId ).container.trigger( 'render-screenshot' ); api.control( themeControl.id ).container.trigger( 'render-screenshot' );
// Close the details modal if it's open to the installed theme. // Close the details modal if it's open to the installed theme.
api.section.each( function( section ) { api.section.each( function( section ) {
@ -2913,7 +2917,7 @@
if ( $( event.target ).hasClass( 'preview' ) ) { if ( $( event.target ).hasClass( 'preview' ) ) {
preview = true; preview = true;
api.notifications.add( 'theme_installing', new api.OverlayNotification( 'theme_installing', { api.notifications.add( new api.OverlayNotification( 'theme_installing', {
message: api.l10n.themeDownloading, message: api.l10n.themeDownloading,
type: 'info', type: 'info',
loading: true loading: true
@ -2950,7 +2954,7 @@
urlParser.search = $.param( queryParams ); urlParser.search = $.param( queryParams );
// Update loading message. Everything else is handled by reloading the page. // Update loading message. Everything else is handled by reloading the page.
api.notifications.add( 'theme_previewing', new api.OverlayNotification( 'theme_previewing', { api.notifications.add( new api.OverlayNotification( 'theme_previewing', {
message: api.l10n.themePreviewWait, message: api.l10n.themePreviewWait,
type: 'info', type: 'info',
loading: true loading: true
@ -3076,33 +3080,59 @@
* @class * @class
* @augments wp.customize.Class * @augments wp.customize.Class
* *
* @param {string} id Unique identifier for the control instance. * @param {string} id - Unique identifier for the control instance.
* @param {object} options Options hash for the control instance. * @param {object} options - Options hash for the control instance.
* @param {object} options.params * @param {object} options.type - Type of control (e.g. text, radio, dropdown-pages, etc.)
* @param {object} options.params.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.params.content The HTML content for the control. * @param {string} [options.templateId] - Template ID for control's content.
* @param {string} options.params.priority Order of priority to show the control within the section. * @param {string} [options.priority=10] - Order of priority to show the control within the section.
* @param {string} options.params.active * @param {string} [options.active=true] - Whether the control is active.
* @param {string} options.params.section The ID of the section the control belongs to. * @param {string} options.section - The ID of the section the control belongs to.
* @param {string} options.params.settings.default The ID of the setting the control relates to. * @param {string} options.settings.default - The ID of the setting the control relates to.
* @param {string} options.params.settings.data * @param {string} options.settings.data
* @param {string} options.params.label * @param {string} options.label - Label.
* @param {string} options.params.description * @param {string} options.description - Description.
* @param {string} options.params.instanceNumber Order in which this instance was created in relation to other instances. * @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.
*/ */
api.Control = api.Class.extend({ api.Control = api.Class.extend({
defaultActiveArguments: { duration: 'fast', completeCallback: $.noop }, defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
defaults: {
active: true,
priority: 10
},
initialize: function( id, options ) { initialize: function( id, options ) {
var control = this, var control = this,
nodes, radios, settings; nodes, radios, settings;
control.params = {}; control.params = _.extend( {}, control.defaults );
$.extend( control, options || {} );
if ( ! api.Control.instanceCounter ) {
api.Control.instanceCounter = 0;
}
api.Control.instanceCounter++;
if ( ! control.params.instanceNumber ) {
control.params.instanceNumber = api.Control.instanceCounter;
}
_.extend( control.params, options.params || options );
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.id = id; control.id = id;
control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); // Deprecated, likely dead code from time before #28709.
control.templateSelector = 'customize-control-' + control.params.type + '-content'; control.templateSelector = control.params.templateId || 'customize-control-' + control.params.type + '-content';
control.container = control.params.content ? $( control.params.content ) : $( control.selector ); if ( control.params.content ) {
control.container = $( control.params.content );
} else {
control.container = $( control.selector ); // Likely dead, per above. See #28709.
}
control.deferred = { control.deferred = {
embedded: new $.Deferred() embedded: new $.Deferred()
@ -3180,17 +3210,14 @@
// Add setting notifications to the control notification. // Add setting notifications to the control notification.
_.each( control.settings, function( setting ) { _.each( control.settings, function( setting ) {
setting.notifications.bind( 'add', function( settingNotification ) { setting.notifications.bind( 'add', function( settingNotification ) {
var controlNotification, code, params; var params = _.extend(
code = setting.id + ':' + settingNotification.code;
params = _.extend(
{}, {},
settingNotification, settingNotification,
{ {
setting: setting.id setting: setting.id
} }
); );
controlNotification = new api.Notification( code, params ); control.notifications.add( new api.Notification( setting.id + ':' + settingNotification.code, params ) );
control.notifications.add( controlNotification.code, controlNotification );
} ); } );
setting.notifications.bind( 'remove', function( settingNotification ) { setting.notifications.bind( 'remove', function( settingNotification ) {
control.notifications.remove( setting.id + ':' + settingNotification.code ); control.notifications.remove( setting.id + ':' + settingNotification.code );
@ -3472,16 +3499,18 @@
*/ */
_toggleActive: Container.prototype._toggleActive, _toggleActive: Container.prototype._toggleActive,
// @todo This function appears to be dead code and can be removed.
dropdownInit: function() { dropdownInit: function() {
var control = this, var control = this,
statuses = this.container.find('.dropdown-status'), statuses = this.container.find('.dropdown-status'),
params = this.params, params = this.params,
toggleFreeze = false, toggleFreeze = false,
update = function( to ) { update = function( to ) {
if ( typeof to === 'string' && params.statuses && params.statuses[ to ] ) if ( 'string' === typeof to && params.statuses && params.statuses[ to ] ) {
statuses.html( params.statuses[ to ] ).show(); statuses.html( params.statuses[ to ] ).show();
else } else {
statuses.hide(); statuses.hide();
}
}; };
// Support the .dropdown class to open/close complex elements // Support the .dropdown class to open/close complex elements
@ -3492,11 +3521,13 @@
event.preventDefault(); event.preventDefault();
if (!toggleFreeze) if ( ! toggleFreeze ) {
control.container.toggleClass('open'); control.container.toggleClass( 'open' );
}
if ( control.container.hasClass('open') ) if ( control.container.hasClass( 'open' ) ) {
control.container.parent().parent().find('li.library-selected').focus(); control.container.parent().parent().find( 'li.library-selected' ).focus();
}
// Don't want to fire focus and click at same time // Don't want to fire focus and click at same time
toggleFreeze = true; toggleFreeze = true;
@ -4869,7 +4900,7 @@
} else { } else {
message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) ); message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
} }
control.setting.notifications.add( 'csslint_error', new api.Notification( 'csslint_error', { control.setting.notifications.add( new api.Notification( 'csslint_error', {
message: message, message: message,
type: 'error' type: 'error'
} ) ); } ) );
@ -5256,7 +5287,7 @@
type: 'error', type: 'error',
message: api.l10n.futureDateError message: api.l10n.futureDateError
} ); } );
control.notifications.add( notificationCode, notification ); control.notifications.add( notification );
} else { } else {
control.notifications.remove( notificationCode ); control.notifications.remove( notificationCode );
} }
@ -5275,17 +5306,9 @@
*/ */
api.PreviewLinkControl = api.Control.extend({ api.PreviewLinkControl = api.Control.extend({
/** defaults: _.extend( {}, api.Control.prototype.defaults, {
* Override the templateSelector before embedding the control into the page. templateId: 'customize-preview-link-control'
* } ),
* @since 4.9.0
* @return {void}
*/
embed: function() {
var control = this;
control.templateSelector = 'customize-preview-link-control';
return api.Control.prototype.embed.apply( control, arguments );
},
/** /**
* Initialize behaviors. * Initialize behaviors.
@ -5386,7 +5409,7 @@
type: 'info', type: 'info',
message: api.l10n.saveBeforeShare message: api.l10n.saveBeforeShare
} ); } );
control.notifications.add( notificationCode, notification ); control.notifications.add( notification );
} else { } else {
control.notifications.remove( notificationCode ); control.notifications.remove( notificationCode );
} }
@ -6014,8 +6037,9 @@
var previewer = this, var previewer = this,
deferred, messenger, iframe; deferred, messenger, iframe;
if ( this._login ) if ( this._login ) {
return this._login; return this._login;
}
deferred = $.Deferred(); deferred = $.Deferred();
this._login = deferred.promise(); this._login = deferred.promise();
@ -6141,7 +6165,7 @@
} }
if ( ! setting.notifications.has( notification.code ) ) { if ( ! setting.notifications.has( notification.code ) ) {
setting.notifications.add( code, notification ); setting.notifications.add( notification );
} }
invalidSettings.push( setting.id ); invalidSettings.push( setting.id );
} ); } );
@ -6331,46 +6355,27 @@
footerActions = $( '#customize-footer-actions' ); footerActions = $( '#customize-footer-actions' );
api.section( 'publish_settings', function( section ) { api.section( 'publish_settings', function( section ) {
var updateButtonsState, previewLinkControl, TrashControl, trashControlInstance, trashControlId = 'trash_changeset', previewLinkControlId = 'changeset_preview_link', updateSectionActive, isSectionActive; var updateButtonsState, trashControl, updateSectionActive, isSectionActive;
TrashControl = api.Control.extend( { trashControl = new api.Control( 'trash_changeset', {
// This is a temporary hack while waiting for richer JS templating and dynamic instantiation.
embed: function() {
var control = this;
control.templateSelector = 'customize-trash-changeset-control';
return api.Control.prototype.embed.apply( control, arguments );
}
} );
trashControlInstance = new TrashControl( trashControlId, {
params: {
type: 'button', type: 'button',
section: section.id, section: section.id,
active: true,
priority: 30, priority: 30,
content: '<li id="customize-control-' + trashControlId + '" class="customize-control"></li>' templateId: 'customize-trash-changeset-control'
}
} ); } );
api.control.add( trashControlId, trashControlInstance ); api.control.add( trashControl );
trashControlInstance.deferred.embedded.done( function() { trashControl.deferred.embedded.done( function() {
trashControlInstance.container.find( 'button' ).on( 'click', function() { trashControl.container.find( 'button' ).on( 'click', function() {
if ( confirm( api.l10n.trashConfirm ) ) { if ( confirm( api.l10n.trashConfirm ) ) {
wp.customize.previewer.trash(); wp.customize.previewer.trash();
} }
} ); } );
} ); } );
previewLinkControl = new api.PreviewLinkControl( previewLinkControlId, { api.control.add( new api.PreviewLinkControl( 'changeset_preview_link', {
params: {
section: section.id, section: section.id,
active: true, priority: 100
priority: 100, } ) );
content: '<li id="customize-control-' + previewLinkControlId + '" class="customize-control"></li>'
}
} );
api.control.add( previewLinkControlId, previewLinkControl );
/** /**
* Return whether the pubish settings section should be active. * Return whether the pubish settings section should be active.
@ -6588,7 +6593,7 @@
api.unbind( 'change', captureSettingModifiedDuringSave ); api.unbind( 'change', captureSettingModifiedDuringSave );
if ( invalidSettings.length ) { if ( invalidSettings.length ) {
api.notifications.add( errorCode, new api.Notification( errorCode, { api.notifications.add( new api.Notification( errorCode, {
message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ), message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ),
type: 'error', type: 'error',
dismissible: true, dismissible: true,
@ -6690,7 +6695,7 @@
} }
if ( notification ) { if ( notification ) {
api.notifications.add( notification.code, notification ); api.notifications.add( notification );
} }
if ( response.setting_validities ) { if ( response.setting_validities ) {
@ -6795,7 +6800,7 @@
customize_changeset_uuid: api.settings.changeset.uuid, customize_changeset_uuid: api.settings.changeset.uuid,
nonce: api.settings.nonce.trash nonce: api.settings.nonce.trash
} ); } );
api.notifications.add( 'changeset_trashing', new api.OverlayNotification( 'changeset_trashing', { api.notifications.add( new api.OverlayNotification( 'changeset_trashing', {
type: 'info', type: 'info',
message: api.l10n.revertingChanges, message: api.l10n.revertingChanges,
loading: true loading: true
@ -6823,7 +6828,7 @@
api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 ); api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 );
api.state( 'trashing' ).set( false ); api.state( 'trashing' ).set( false );
api.notifications.remove( 'changeset_trashing' ); api.notifications.remove( 'changeset_trashing' );
api.notifications.add( notificationCode, new api.Notification( notificationCode, { api.notifications.add( new api.Notification( notificationCode, {
message: message || api.l10n.unknownError, message: message || api.l10n.unknownError,
dismissible: true, dismissible: true,
type: 'error' type: 'error'
@ -6891,49 +6896,30 @@
// Create Settings // Create Settings
$.each( api.settings.settings, function( id, data ) { $.each( api.settings.settings, function( id, data ) {
var constructor = api.settingConstructor[ data.type ] || api.Setting, var Constructor = api.settingConstructor[ data.type ] || api.Setting;
setting; api.add( new Constructor( id, data.value, {
setting = new constructor( id, data.value, {
transport: data.transport, transport: data.transport,
previewer: api.previewer, previewer: api.previewer,
dirty: !! data.dirty dirty: !! data.dirty
} ); } ) );
api.add( id, setting );
}); });
// Create Panels // Create Panels
$.each( api.settings.panels, function ( id, data ) { $.each( api.settings.panels, function ( id, data ) {
var constructor = api.panelConstructor[ data.type ] || api.Panel, var Constructor = api.panelConstructor[ data.type ] || api.Panel;
panel; api.panel.add( new Constructor( id, data ) );
panel = new constructor( id, {
params: data
} );
api.panel.add( id, panel );
}); });
// Create Sections // Create Sections
$.each( api.settings.sections, function ( id, data ) { $.each( api.settings.sections, function ( id, data ) {
var constructor = api.sectionConstructor[ data.type ] || api.Section, var Constructor = api.sectionConstructor[ data.type ] || api.Section;
section; api.section.add( new Constructor( id, data ) );
section = new constructor( id, {
params: data
} );
api.section.add( id, section );
}); });
// Create Controls // Create Controls
$.each( api.settings.controls, function( id, data ) { $.each( api.settings.controls, function( id, data ) {
var constructor = api.controlConstructor[ data.type ] || api.Control, var Constructor = api.controlConstructor[ data.type ] || api.Control;
control; api.control.add( new Constructor( id, data ) );
control = new constructor( id, {
params: data,
previewer: api.previewer
} );
api.control.add( id, control );
}); });
// Focus the autofocused element // Focus the autofocused element
@ -7240,7 +7226,7 @@
var code = 'autosave_available', onStateChange; var code = 'autosave_available', onStateChange;
// Since there is an autosave revision and the user hasn't loaded with autosaved, add notification to prompt to load autosaved version. // Since there is an autosave revision and the user hasn't loaded with autosaved, add notification to prompt to load autosaved version.
api.notifications.add( code, new api.Notification( code, { api.notifications.add( new api.Notification( code, {
message: api.l10n.autosaveNotice, message: api.l10n.autosaveNotice,
type: 'warning', type: 'warning',
dismissible: true, dismissible: true,
@ -7893,8 +7879,9 @@
control.element.set( 'blank' !== control.setting() ); control.element.set( 'blank' !== control.setting() );
control.element.bind( function( to ) { control.element.bind( function( to ) {
if ( ! to ) if ( ! to ) {
last = api( 'header_textcolor' ).get(); last = api( 'header_textcolor' ).get();
}
control.setting.set( to ? last : 'blank' ); control.setting.set( to ? last : 'blank' );
}); });
@ -7926,7 +7913,7 @@
// Toggle notification when the homepage and posts page are both set and the same. // Toggle notification when the homepage and posts page are both set and the same.
if ( 'page' === showOnFront() && pageOnFrontId && pageForPostsId && pageOnFrontId === pageForPostsId ) { if ( 'page' === showOnFront() && pageOnFrontId && pageForPostsId && pageOnFrontId === pageForPostsId ) {
showOnFront.notifications.add( errorCode, new api.Notification( errorCode, { showOnFront.notifications.add( new api.Notification( errorCode, {
type: 'error', type: 'error',
message: api.l10n.pageOnFrontError message: api.l10n.pageOnFrontError
} ) ); } ) );
@ -8077,7 +8064,7 @@
if ( headerVideoControl.active.get() ) { if ( headerVideoControl.active.get() ) {
section.notifications.remove( noticeCode ); section.notifications.remove( noticeCode );
} else { } else {
section.notifications.add( noticeCode, new api.Notification( noticeCode, { section.notifications.add( new api.Notification( noticeCode, {
type: 'info', type: 'info',
message: api.l10n.videoHeaderNotice message: api.l10n.videoHeaderNotice
} ) ); } ) );

View File

@ -968,19 +968,15 @@
menuNameControl = api.control( menuNameControlId ); menuNameControl = api.control( menuNameControlId );
if ( ! menuNameControl ) { if ( ! menuNameControl ) {
menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, { menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
params: {
type: 'nav_menu_name', type: 'nav_menu_name',
content: '<li id="customize-control-' + section.id.replace( '[', '-' ).replace( ']', '' ) + '-name" class="customize-control customize-control-nav_menu_name"></li>', // @todo core should do this for us; see #30741
label: api.Menus.data.l10n.menuNameLabel, label: api.Menus.data.l10n.menuNameLabel,
active: true,
section: section.id, section: section.id,
priority: 0, priority: 0,
settings: { settings: {
'default': section.id 'default': section.id
} }
}
} ); } );
api.control.add( menuNameControl.id, menuNameControl ); api.control.add( menuNameControl );
menuNameControl.active.set( true ); menuNameControl.active.set( true );
} }
@ -988,19 +984,15 @@
menuControl = api.control( section.id ); menuControl = api.control( section.id );
if ( ! menuControl ) { if ( ! menuControl ) {
menuControl = new api.controlConstructor.nav_menu( section.id, { menuControl = new api.controlConstructor.nav_menu( section.id, {
params: {
type: 'nav_menu', type: 'nav_menu',
content: '<li id="customize-control-' + section.id.replace( '[', '-' ).replace( ']', '' ) + '" class="customize-control customize-control-nav_menu"></li>', // @todo core should do this for us; see #30741
section: section.id, section: section.id,
priority: 998, priority: 998,
active: true,
settings: { settings: {
'default': section.id 'default': section.id
}, },
menu_id: section.params.menu_id menu_id: section.params.menu_id
}
} ); } );
api.control.add( menuControl.id, menuControl ); api.control.add( menuControl );
menuControl.active.set( true ); menuControl.active.set( true );
} }
@ -1009,19 +1001,15 @@
menuAutoAddControl = api.control( menuAutoAddControlId ); menuAutoAddControl = api.control( menuAutoAddControlId );
if ( ! menuAutoAddControl ) { if ( ! menuAutoAddControl ) {
menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, { menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, {
params: {
type: 'nav_menu_auto_add', type: 'nav_menu_auto_add',
content: '<li id="customize-control-' + section.id.replace( '[', '-' ).replace( ']', '' ) + '-auto-add" class="customize-control customize-control-nav_menu_auto_add"></li>', // @todo core should do this for us
label: '', label: '',
active: true,
section: section.id, section: section.id,
priority: 999, priority: 999,
settings: { settings: {
'default': section.id 'default': section.id
} }
}
} ); } );
api.control.add( menuAutoAddControl.id, menuAutoAddControl ); api.control.add( menuAutoAddControl );
menuAutoAddControl.active.set( true ); menuAutoAddControl.active.set( true );
} }
@ -2677,21 +2665,16 @@
// Add the menu item control. // Add the menu item control.
menuItemControl = new api.controlConstructor.nav_menu_item( customizeId, { menuItemControl = new api.controlConstructor.nav_menu_item( customizeId, {
params: {
type: 'nav_menu_item', type: 'nav_menu_item',
content: '<li id="customize-control-nav_menu_item-' + String( placeholderId ) + '" class="customize-control customize-control-nav_menu_item"></li>',
section: menuControl.id, section: menuControl.id,
priority: priority, priority: priority,
active: true,
settings: { settings: {
'default': customizeId 'default': customizeId
}, },
menu_item_id: placeholderId menu_item_id: placeholderId
},
previewer: api.previewer
} ); } );
api.control.add( customizeId, menuItemControl ); api.control.add( menuItemControl );
setting.preview(); setting.preview();
menuControl.debouncedReflowMenuItems(); menuControl.debouncedReflowMenuItems();
@ -2775,17 +2758,14 @@
* inside via the Section's ready method. * inside via the Section's ready method.
*/ */
menuSection = new api.Menus.MenuSection( customizeId, { menuSection = new api.Menus.MenuSection( customizeId, {
params: {
id: customizeId,
panel: 'nav_menus', panel: 'nav_menus',
title: displayNavMenuName( name ), title: displayNavMenuName( name ),
customizeAction: api.Menus.data.l10n.customizingMenus, customizeAction: api.Menus.data.l10n.customizingMenus,
type: 'nav_menu', type: 'nav_menu',
priority: 10, priority: 10,
menu_id: placeholderId menu_id: placeholderId
}
} ); } );
api.section.add( customizeId, menuSection ); api.section.add( menuSection );
// Clear name field. // Clear name field.
nameInput.val( '' ); nameInput.val( '' );
@ -2909,20 +2889,16 @@
// Add the menu section. // Add the menu section.
newSection = new api.Menus.MenuSection( newCustomizeId, { newSection = new api.Menus.MenuSection( newCustomizeId, {
params: {
id: newCustomizeId,
panel: 'nav_menus', panel: 'nav_menus',
title: settingValue.name, title: settingValue.name,
customizeAction: api.Menus.data.l10n.customizingMenus, customizeAction: api.Menus.data.l10n.customizingMenus,
type: 'nav_menu', type: 'nav_menu',
priority: oldSection.priority.get(), priority: oldSection.priority.get(),
active: true,
menu_id: update.term_id menu_id: update.term_id
}
} ); } );
// Add new control for the new menu. // Add new control for the new menu.
api.section.add( newCustomizeId, newSection ); api.section.add( newSection );
// Update the values for nav menus in Custom Menu controls. // Update the values for nav menus in Custom Menu controls.
api.control.each( function( setting ) { api.control.each( function( setting ) {
@ -3052,19 +3028,14 @@
// Add the menu control. // Add the menu control.
newControl = new api.controlConstructor.nav_menu_item( newCustomizeId, { newControl = new api.controlConstructor.nav_menu_item( newCustomizeId, {
params: {
type: 'nav_menu_item', type: 'nav_menu_item',
content: '<li id="customize-control-nav_menu_item-' + String( update.post_id ) + '" class="customize-control customize-control-nav_menu_item"></li>',
menu_id: update.post_id, menu_id: update.post_id,
section: 'nav_menu[' + String( settingValue.nav_menu_term_id ) + ']', section: 'nav_menu[' + String( settingValue.nav_menu_term_id ) + ']',
priority: oldControl.priority.get(), priority: oldControl.priority.get(),
active: true,
settings: { settings: {
'default': newCustomizeId 'default': newCustomizeId
}, },
menu_item_id: update.post_id menu_item_id: update.post_id
},
previewer: api.previewer
} ); } );
// Remove old control. // Remove old control.
@ -3072,7 +3043,7 @@
api.control.remove( oldCustomizeId ); api.control.remove( oldCustomizeId );
// Add new control to take its place. // Add new control to take its place.
api.control.add( newCustomizeId, newControl ); api.control.add( newControl );
// Delete the placeholder and preview the new setting. // Delete the placeholder and preview the new setting.
oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set. oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set.

View File

@ -2098,7 +2098,6 @@
controlConstructor = api.controlConstructor[controlType]; controlConstructor = api.controlConstructor[controlType];
widgetFormControl = new controlConstructor( settingId, { widgetFormControl = new controlConstructor( settingId, {
params: {
settings: { settings: {
'default': settingId 'default': settingId
}, },
@ -2110,12 +2109,9 @@
is_new: ! isExistingWidget, is_new: ! isExistingWidget,
width: widget.get( 'width' ), width: widget.get( 'width' ),
height: widget.get( 'height' ), height: widget.get( 'height' ),
is_wide: widget.get( 'is_wide' ), is_wide: widget.get( 'is_wide' )
active: true
},
previewer: self.setting.previewer
} ); } );
api.control.add( settingId, widgetFormControl ); api.control.add( widgetFormControl );
// Make sure widget is removed from the other sidebars // Make sure widget is removed from the other sidebars
api.each( function( otherSetting ) { api.each( function( otherSetting ) {

View File

@ -3604,7 +3604,7 @@ final class WP_Customize_Manager {
?> ?>
<script type="text/html" id="tmpl-customize-notification"> <script type="text/html" id="tmpl-customize-notification">
<li class="notice notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}"> <li class="notice notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.containerClasses || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}">
<div class="notification-message">{{{ data.message || data.code }}}</div> <div class="notification-message">{{{ data.message || data.code }}}</div>
<# if ( data.dismissible ) { #> <# if ( data.dismissible ) { #>
<button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button> <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>

View File

@ -376,29 +376,44 @@ window.wp = window.wp || {};
/** /**
* Add an item to the collection. * Add an item to the collection.
* *
* @param {string} id The ID of the item. * @param {string|wp.customize.Class} item - The item instance to add, or the ID for the instance to add. When an ID string is supplied, then itemObject must be provided.
* @param {mixed} value The item instance. * @param {wp.customize.Class} [itemObject] - The item instance when the first argument is a ID string.
* @return {mixed} The new item's instance. * @return {wp.customize.Class} The new item's instance, or an existing instance if already added.
*/ */
add: function( id, value ) { add: function( item, itemObject ) {
if ( this.has( id ) ) var collection = this, id, instance;
return this.value( id ); if ( 'string' === typeof item ) {
id = item;
instance = itemObject;
} else {
if ( 'string' !== typeof item.id ) {
throw new Error( 'Unknown key' );
}
id = item.id;
instance = item;
}
this._value[ id ] = value; if ( collection.has( id ) ) {
value.parent = this; return collection.value( id );
}
collection._value[ id ] = instance;
instance.parent = collection;
// Propagate a 'change' event on an item up to the collection. // Propagate a 'change' event on an item up to the collection.
if ( value.extended( api.Value ) ) if ( instance.extended( api.Value ) ) {
value.bind( this._change ); instance.bind( collection._change );
}
this.trigger( 'add', value ); collection.trigger( 'add', instance );
// If a deferred object exists for this item, // If a deferred object exists for this item,
// resolve it. // resolve it.
if ( this._deferreds[ id ] ) if ( collection._deferreds[ id ] ) {
this._deferreds[ id ].resolve(); collection._deferreds[ id ].resolve();
}
return this._value[ id ]; return collection._value[ id ];
}, },
/** /**
@ -815,7 +830,7 @@ window.wp = window.wp || {};
* @since 4.9.0 * @since 4.9.0
* @var {string} * @var {string}
*/ */
classes: '', containerClasses: '',
/** /**
* Initialize notification. * Initialize notification.
@ -829,7 +844,7 @@ window.wp = window.wp || {};
* @param {string} [params.setting] - Related setting ID. * @param {string} [params.setting] - Related setting ID.
* @param {Function} [params.template] - Function for rendering template. If not provided, this will come from templateId. * @param {Function} [params.template] - Function for rendering template. If not provided, this will come from templateId.
* @param {string} [params.templateId] - ID for template to render the notification. * @param {string} [params.templateId] - ID for template to render the notification.
* @param {string} [params.classes] - Additional class names to add to the notification container. * @param {string} [params.containerClasses] - Additional class names to add to the notification container.
* @param {boolean} [params.dismissible] - Whether the notification can be dismissed. * @param {boolean} [params.dismissible] - Whether the notification can be dismissed.
*/ */
initialize: function( code, params ) { initialize: function( code, params ) {
@ -844,7 +859,7 @@ window.wp = window.wp || {};
setting: null, setting: null,
template: null, template: null,
dismissible: false, dismissible: false,
classes: '' containerClasses: ''
}, },
params params
); );

View File

@ -407,7 +407,7 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
wasInserted = true; wasInserted = true;
} ); } );
api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial ); api.selectiveRefresh.partial.add( widgetPartial );
if ( wasInserted ) { if ( wasInserted ) {
sidebarPartial.reflowWidgets(); sidebarPartial.reflowWidgets();
@ -510,7 +510,7 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
sidebarArgs: registeredSidebar sidebarArgs: registeredSidebar
} }
} ); } );
api.selectiveRefresh.partial.add( partial.id, partial ); api.selectiveRefresh.partial.add( partial );
} }
} ); } );
}; };

View File

@ -877,7 +877,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {}; partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial; Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
partial = new Constructor( id, partialOptions ); partial = new Constructor( id, partialOptions );
self.partial.add( partial.id, partial ); self.partial.add( partial );
} }
/* /*
@ -918,7 +918,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
if ( ! partial ) { if ( ! partial ) {
Constructor = self.partialConstructor[ data.type ] || self.Partial; Constructor = self.partialConstructor[ data.type ] || self.Partial;
partial = new Constructor( id, { params: data } ); partial = new Constructor( id, { params: data } );
self.partial.add( id, partial ); self.partial.add( partial );
} else { } else {
_.extend( partial.params, data ); _.extend( partial.params, data );
} }

View File

@ -306,13 +306,13 @@ jQuery( window ).load( function (){
type: 'default', type: 'default',
content: null, content: null,
active: true, active: true,
instanceNumber: null,
customizeAction: '' customizeAction: ''
}; };
jQuery.each( defaultParams, function ( key, value ) { jQuery.each( defaultParams, function ( key, value ) {
ok( 'undefined' !== typeof section.params[ key ] ); ok( 'undefined' !== typeof section.params[ key ] );
equal( value, section.params[ key ] ); equal( value, section.params[ key ] );
} ); } );
ok( _.isNumber( section.params.instanceNumber ) );
} ); } );
@ -417,13 +417,13 @@ jQuery( window ).load( function (){
priority: 100, priority: 100,
type: 'default', type: 'default',
content: null, content: null,
active: true, active: true
instanceNumber: null
}; };
jQuery.each( defaultParams, function ( key, value ) { jQuery.each( defaultParams, function ( key, value ) {
ok( 'undefined' !== typeof panel.params[ key ] ); ok( 'undefined' !== typeof panel.params[ key ] );
equal( value, panel.params[ key ] ); equal( value, panel.params[ key ] );
} ); } );
ok( _.isNumber( panel.params.instanceNumber ) );
} ); } );
module( 'Dynamically-created Customizer Setting Model' ); module( 'Dynamically-created Customizer Setting Model' );