Make WordPress Core

Ticket #42083: 30741.0.diff

File 30741.0.diff, 42.9 KB (added by westonruter, 7 years ago)

https://github.com/xwp/wordpress-develop/pull/274

  • src/wp-admin/js/customize-controls.js

    diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
    index f2f6196281..bcfe9a8278 100644
     
    3030                initialize: function( code, params ) {
    3131                        var notification = this;
    3232                        api.Notification.prototype.initialize.call( notification, code, params );
    33                         notification.classes += ' notification-overlay';
     33                        notification.containerClasses += ' notification-overlay';
    3434                        if ( notification.loading ) {
    35                                 notification.classes += ' notification-loading';
     35                                notification.containerClasses += ' notification-loading';
    3636                        }
    3737                }
    3838        });
     
    105105                 * Add notification to the collection.
    106106                 *
    107107                 * @since 4.9.0
    108                  * @param {string} code - Notification code.
    109                  * @param {object} params - Notification params.
    110                  * @return {api.Notification} Added instance (or existing instance if it was already added).
     108                 *
     109                 * @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.
     110                 * @param {wp.customize.Notification} [notificationObject] - Notification to add when first argument is the code string.
     111                 * @returns {wp.customize.Notification} Added notification (or existing instance if it was already added).
    111112                 */
    112                 add: function( code, params ) {
    113                         var collection = this;
     113                add: function( notification, notificationObject ) {
     114                        var collection = this, code, instance;
     115                        if ( 'string' === typeof notification ) {
     116                                code = notification;
     117                                instance = notificationObject;
     118                        } else {
     119                                code = notification.code;
     120                                instance = notification;
     121                        }
    114122                        if ( ! collection.has( code ) ) {
    115123                                collection._addedIncrement += 1;
    116124                                collection._addedOrder[ code ] = collection._addedIncrement;
    117125                        }
    118                         return api.Values.prototype.add.call( this, code, params );
     126                        return api.Values.prototype.add.call( collection, code, instance );
    119127                },
    120128
    121129                /**
     
    269277                        setting.id = id;
    270278                        setting.transport = setting.transport || 'refresh';
    271279                        setting._dirty = options.dirty || false;
    272                         setting.notifications = new api.Values({ defaultConstructor: api.Notification });
     280                        setting.notifications = new api.Notifications();
    273281
    274282                        // Whenever the setting's value changes, refresh the preview.
    275283                        setting.bind( setting.preview );
     
    745753                 *
    746754                 * @param {string}         id - The ID for the container.
    747755                 * @param {object}         options - Object containing one property: params.
    748                  * @param {object}         options.params - Object containing the following properties.
    749                  * @param {string}         options.params.title - Title shown when panel is collapsed and expanded.
    750                  * @param {string=}        [options.params.description] - Description shown at the top of the panel.
    751                  * @param {number=100}     [options.params.priority] - The sort priority for the panel.
    752                  * @param {string=default} [options.params.type] - The type of the panel. See wp.customize.panelConstructor.
    753                  * @param {string=}        [options.params.content] - The markup to be used for the panel container. If empty, a JS template is used.
    754                  * @param {boolean=true}   [options.params.active] - Whether the panel is active or not.
     756                 * @param {string}         options.title - Title shown when panel is collapsed and expanded.
     757                 * @param {string=}        [options.description] - Description shown at the top of the panel.
     758                 * @param {number=100}     [options.priority] - The sort priority for the panel.
     759                 * @param {string}         [options.templateId] - Template selector for container.
     760                 * @param {string=default} [options.type] - The type of the panel. See wp.customize.panelConstructor.
     761                 * @param {string=}        [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
     762                 * @param {boolean=true}   [options.active] - Whether the panel is active or not.
     763                 * @param {object}         [options.params] - Deprecated wrapper for the above properties.
    755764                 */
    756765                initialize: function ( id, options ) {
    757766                        var container = this;
    758767                        container.id = id;
    759                         options = options || {};
    760768
    761                         options.params = _.defaults(
    762                                 options.params || {},
    763                                 container.defaults
    764                         );
     769                        if ( ! Container.instanceCounter ) {
     770                                Container.instanceCounter = 0;
     771                        }
     772                        Container.instanceCounter++;
    765773
    766                         $.extend( container, options );
     774                        $.extend( container, {
     775                                params: _.defaults(
     776                                        options.params || options, // Passing the params is deprecated.
     777                                        container.defaults
     778                                )
     779                        } );
     780                        if ( ! container.params.instanceNumber ) {
     781                                container.params.instanceNumber = Container.instanceCounter;
     782                        }
    767783                        container.notifications = new api.Notifications();
    768                         container.templateSelector = 'customize-' + container.containerType + '-' + container.params.type;
     784                        container.templateSelector = container.params.templateId || 'customize-' + container.containerType + '-' + container.params.type;
    769785                        container.container = $( container.params.content );
    770786                        if ( 0 === container.container.length ) {
    771787                                container.container = $( container.getContainer() );
     
    12061222                 * @since 4.1.0
    12071223                 *
    12081224                 * @param {string}         id - The ID for the section.
    1209                  * @param {object}         options - Object containing one property: params.
    1210                  * @param {object}         options.params - Object containing the following properties.
    1211                  * @param {string}         options.params.title - Title shown when section is collapsed and expanded.
    1212                  * @param {string=}        [options.params.description] - Description shown at the top of the section.
    1213                  * @param {number=100}     [options.params.priority] - The sort priority for the section.
    1214                  * @param {string=default} [options.params.type] - The type of the section. See wp.customize.sectionConstructor.
    1215                  * @param {string=}        [options.params.content] - The markup to be used for the section container. If empty, a JS template is used.
    1216                  * @param {boolean=true}   [options.params.active] - Whether the section is active or not.
    1217                  * @param {string}         options.params.panel - The ID for the panel this section is associated with.
    1218                  * @param {string=}        [options.params.customizeAction] - Additional context information shown before the section title when expanded.
     1225                 * @param {object}         options - Options.
     1226                 * @param {string}         options.title - Title shown when section is collapsed and expanded.
     1227                 * @param {string=}        [options.description] - Description shown at the top of the section.
     1228                 * @param {number=100}     [options.priority] - The sort priority for the section.
     1229                 * @param {string=default} [options.type] - The type of the section. See wp.customize.sectionConstructor.
     1230                 * @param {string=}        [options.content] - The markup to be used for the section container. If empty, a JS template is used.
     1231                 * @param {boolean=true}   [options.active] - Whether the section is active or not.
     1232                 * @param {string}         options.panel - The ID for the panel this section is associated with.
     1233                 * @param {string=}        [options.customizeAction] - Additional context information shown before the section title when expanded.
     1234                 * @param {object}         [options.params] - Deprecated wrapper for the above properties.
    12191235                 */
    12201236                initialize: function ( id, options ) {
    12211237                        var section = this;
     
    18381854                        section.container.find( '.no-themes' ).hide();
    18391855                        request = wp.ajax.post( 'customize_load_themes', params );
    18401856                        request.done(function( data ) {
    1841                                 var themes = data.themes, themeControl, newThemeControls;
     1857                                var themes = data.themes, newThemeControls;
    18421858
    18431859                                // Stop and try again if the term changed while loading.
    18441860                                if ( '' !== section.nextTerm || '' !== section.nextTags ) {
     
    18601876
    18611877                                        // Add controls for each theme.
    18621878                                        _.each( themes, function( theme ) {
    1863                                                 var customizeId = section.params.action + '_theme_' + theme.id;
    1864                                                 themeControl = new api.controlConstructor.theme( customizeId, {
    1865                                                         params: {
    1866                                                                 type: 'theme',
    1867                                                                 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>',
    1868                                                                 section: section.params.id,
    1869                                                                 active: true,
    1870                                                                 theme: theme,
    1871                                                                 priority: section.loaded + 1
    1872                                                         },
    1873                                                         previewer: api.previewer
     1879                                                var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
     1880                                                        type: 'theme',
     1881                                                        section: section.params.id,
     1882                                                        theme: theme,
     1883                                                        priority: section.loaded + 1
    18741884                                                } );
    18751885
    1876                                                 api.control.add( customizeId, themeControl );
     1886                                                api.control.add( themeControl );
    18771887                                                newThemeControls.push( themeControl );
    18781888                                                section.loaded = section.loaded + 1;
    18791889                                        });
     
    24822492                 *
    24832493                 * @param {string}         id - The ID for the panel.
    24842494                 * @param {object}         options - Object containing one property: params.
    2485                  * @param {object}         options.params - Object containing the following properties.
    2486                  * @param {string}         options.params.title - Title shown when panel is collapsed and expanded.
    2487                  * @param {string=}        [options.params.description] - Description shown at the top of the panel.
    2488                  * @param {number=100}     [options.params.priority] - The sort priority for the panel.
    2489                  * @param {string=default} [options.params.type] - The type of the panel. See wp.customize.panelConstructor.
    2490                  * @param {string=}        [options.params.content] - The markup to be used for the panel container. If empty, a JS template is used.
    2491                  * @param {boolean=true}   [options.params.active] - Whether the panel is active or not.
     2495                 * @param {string}         options.title - Title shown when panel is collapsed and expanded.
     2496                 * @param {string=}        [options.description] - Description shown at the top of the panel.
     2497                 * @param {number=100}     [options.priority] - The sort priority for the panel.
     2498                 * @param {string=default} [options.type] - The type of the panel. See wp.customize.panelConstructor.
     2499                 * @param {string=}        [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
     2500                 * @param {boolean=true}   [options.active] - Whether the panel is active or not.
     2501                 * @param {object}         [options.params] - Deprecated wrapper for the above properties.
    24922502                 */
    24932503                initialize: function ( id, options ) {
    24942504                        var panel = this;
     
    28562866                        wp.updates.maybeRequestFilesystemCredentials( event );
    28572867
    28582868                        $( document ).one( 'wp-theme-install-success', function( event, response ) {
    2859                                 var theme = false, customizeId, themeControl;
     2869                                var theme = false, themeControl;
    28602870                                if ( preview ) {
    28612871                                        api.notifications.remove( 'theme_installing' );
    28622872
     
    28772887
    28782888                                        // Add theme control to installed section.
    28792889                                        theme.type = 'installed';
    2880                                         customizeId = 'installed_theme_' + theme.id;
    2881                                         themeControl = new api.controlConstructor.theme( customizeId, {
    2882                                                 params: {
    2883                                                         type: 'theme',
    2884                                                         content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ),
    2885                                                         section: 'installed_themes',
    2886                                                         active: true,
    2887                                                         theme: theme,
    2888                                                         priority: 0 // Add all newly-installed themes to the top.
    2889                                                 },
    2890                                                 previewer: api.previewer
     2890                                        themeControl = new api.controlConstructor.theme( 'installed_theme_' + theme.id, {
     2891                                                type: 'theme',
     2892                                                section: 'installed_themes',
     2893                                                theme: theme,
     2894                                                priority: 0 // Add all newly-installed themes to the top.
    28912895                                        } );
    28922896
    2893                                         api.control.add( customizeId, themeControl );
    2894                                         api.control( customizeId ).container.trigger( 'render-screenshot' );
     2897                                        api.control.add( themeControl );
     2898                                        api.control( themeControl.id ).container.trigger( 'render-screenshot' );
    28952899
    28962900                                        // Close the details modal if it's open to the installed theme.
    28972901                                        api.section.each( function( section ) {
     
    29132917                        if ( $( event.target ).hasClass( 'preview' ) ) {
    29142918                                preview = true;
    29152919
    2916                                 api.notifications.add( 'theme_installing', new api.OverlayNotification( 'theme_installing', {
     2920                                api.notifications.add( new api.OverlayNotification( 'theme_installing', {
    29172921                                        message: api.l10n.themeDownloading,
    29182922                                        type: 'info',
    29192923                                        loading: true
     
    29502954                        urlParser.search = $.param( queryParams );
    29512955
    29522956                        // Update loading message. Everything else is handled by reloading the page.
    2953                         api.notifications.add( 'theme_previewing', new api.OverlayNotification( 'theme_previewing', {
     2957                        api.notifications.add( new api.OverlayNotification( 'theme_previewing', {
    29542958                                message: api.l10n.themePreviewWait,
    29552959                                type: 'info',
    29562960                                loading: true
     
    30763080         * @class
    30773081         * @augments wp.customize.Class
    30783082         *
    3079          * @param {string} id                              Unique identifier for the control instance.
    3080          * @param {object} options                         Options hash for the control instance.
    3081          * @param {object} options.params
    3082          * @param {object} options.params.type             Type of control (e.g. text, radio, dropdown-pages, etc.)
    3083          * @param {string} options.params.content          The HTML content for the control.
    3084          * @param {string} options.params.priority         Order of priority to show the control within the section.
    3085          * @param {string} options.params.active
    3086          * @param {string} options.params.section          The ID of the section the control belongs to.
    3087          * @param {string} options.params.settings.default The ID of the setting the control relates to.
    3088          * @param {string} options.params.settings.data
    3089          * @param {string} options.params.label
    3090          * @param {string} options.params.description
    3091          * @param {string} options.params.instanceNumber Order in which this instance was created in relation to other instances.
     3083         * @param {string} id                       - Unique identifier for the control instance.
     3084         * @param {object} options                  - Options hash for the control instance.
     3085         * @param {object} options.type             - Type of control (e.g. text, radio, dropdown-pages, etc.)
     3086         * @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.
     3087         * @param {string} [options.templateId]     - Template ID for control's content.
     3088         * @param {string} [options.priority=10]    - Order of priority to show the control within the section.
     3089         * @param {string} [options.active=true]    - Whether the control is active.
     3090         * @param {string} options.section          - The ID of the section the control belongs to.
     3091         * @param {string} options.settings.default - The ID of the setting the control relates to.
     3092         * @param {string} options.settings.data
     3093         * @param {string} options.label            - Label.
     3094         * @param {string} options.description      - Description.
     3095         * @param {number} [options.instanceNumber] - Order in which this instance was created in relation to other instances.
     3096         * @param {object} [options.params]         - Deprecated wrapper for the above properties.
    30923097         */
    30933098        api.Control = api.Class.extend({
    30943099                defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
    30953100
     3101                defaults: {
     3102                        active: true,
     3103                        priority: 10
     3104                },
     3105
    30963106                initialize: function( id, options ) {
    30973107                        var control = this,
    30983108                                nodes, radios, settings;
    30993109
    3100                         control.params = {};
    3101                         $.extend( control, options || {} );
     3110                        control.params = _.extend( {}, control.defaults );
     3111
     3112                        if ( ! api.Control.instanceCounter ) {
     3113                                api.Control.instanceCounter = 0;
     3114                        }
     3115                        api.Control.instanceCounter++;
     3116                        if ( ! control.params.instanceNumber ) {
     3117                                control.params.instanceNumber = api.Control.instanceCounter;
     3118                        }
     3119
     3120                        _.extend( control.params, options.params || options );
     3121                        if ( ! control.params.content ) {
     3122                                control.params.content = $( '<li></li>', {
     3123                                        id: 'customize-control-' + id.replace( /]/g, '' ).replace( /\[/g, '-' ),
     3124                                        'class': 'customize-control customize-control-' + control.params.type
     3125                                } );
     3126                        }
     3127
    31023128                        control.id = id;
    3103                         control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' );
    3104                         control.templateSelector = 'customize-control-' + control.params.type + '-content';
    3105                         control.container = control.params.content ? $( control.params.content ) : $( control.selector );
     3129                        control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); // Deprecated, likely dead code from time before #28709.
     3130                        control.templateSelector = control.params.templateId || 'customize-control-' + control.params.type + '-content';
     3131                        if ( control.params.content ) {
     3132                                control.container = $( control.params.content );
     3133                        } else {
     3134                                control.container = $( control.selector ); // Likely dead, per above. See #28709.
     3135                        }
    31063136
    31073137                        control.deferred = {
    31083138                                embedded: new $.Deferred()
     
    31803210                                        // Add setting notifications to the control notification.
    31813211                                        _.each( control.settings, function( setting ) {
    31823212                                                setting.notifications.bind( 'add', function( settingNotification ) {
    3183                                                         var controlNotification, code, params;
    3184                                                         code = setting.id + ':' + settingNotification.code;
    3185                                                         params = _.extend(
     3213                                                        var params = _.extend(
    31863214                                                                {},
    31873215                                                                settingNotification,
    31883216                                                                {
    31893217                                                                        setting: setting.id
    31903218                                                                }
    31913219                                                        );
    3192                                                         controlNotification = new api.Notification( code, params );
    3193                                                         control.notifications.add( controlNotification.code, controlNotification );
     3220                                                        control.notifications.add( new api.Notification( setting.id + ':' + settingNotification.code, params ) );
    31943221                                                } );
    31953222                                                setting.notifications.bind( 'remove', function( settingNotification ) {
    31963223                                                        control.notifications.remove( setting.id + ':' + settingNotification.code );
     
    34723499                 */
    34733500                _toggleActive: Container.prototype._toggleActive,
    34743501
     3502                // @todo This function appears to be dead code and can be removed.
    34753503                dropdownInit: function() {
    34763504                        var control      = this,
    34773505                                statuses     = this.container.find('.dropdown-status'),
    34783506                                params       = this.params,
    34793507                                toggleFreeze = false,
    34803508                                update       = function( to ) {
    3481                                         if ( typeof to === 'string' && params.statuses && params.statuses[ to ] )
     3509                                        if ( 'string' === typeof to && params.statuses && params.statuses[ to ] ) {
    34823510                                                statuses.html( params.statuses[ to ] ).show();
    3483                                         else
     3511                                        } else {
    34843512                                                statuses.hide();
     3513                                        }
    34853514                                };
    34863515
    34873516                        // Support the .dropdown class to open/close complex elements
     
    34923521
    34933522                                event.preventDefault();
    34943523
    3495                                 if (!toggleFreeze)
    3496                                         control.container.toggleClass('open');
     3524                                if ( ! toggleFreeze ) {
     3525                                        control.container.toggleClass( 'open' );
     3526                                }
    34973527
    3498                                 if ( control.container.hasClass('open') )
    3499                                         control.container.parent().parent().find('li.library-selected').focus();
     3528                                if ( control.container.hasClass( 'open' ) ) {
     3529                                        control.container.parent().parent().find( 'li.library-selected' ).focus();
     3530                                }
    35003531
    35013532                                // Don't want to fire focus and click at same time
    35023533                                toggleFreeze = true;
     
    48694900                                } else {
    48704901                                        message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
    48714902                                }
    4872                                 control.setting.notifications.add( 'csslint_error', new api.Notification( 'csslint_error', {
     4903                                control.setting.notifications.add( new api.Notification( 'csslint_error', {
    48734904                                        message: message,
    48744905                                        type: 'error'
    48754906                                } ) );
     
    52565287                                        type: 'error',
    52575288                                        message: api.l10n.futureDateError
    52585289                                } );
    5259                                 control.notifications.add( notificationCode, notification );
     5290                                control.notifications.add( notification );
    52605291                        } else {
    52615292                                control.notifications.remove( notificationCode );
    52625293                        }
     
    52755306         */
    52765307        api.PreviewLinkControl = api.Control.extend({
    52775308
    5278                 /**
    5279                  * Override the templateSelector before embedding the control into the page.
    5280                  *
    5281                  * @since 4.9.0
    5282                  * @return {void}
    5283                  */
    5284                 embed: function() {
    5285                         var control = this;
    5286                         control.templateSelector = 'customize-preview-link-control';
    5287                         return api.Control.prototype.embed.apply( control, arguments );
    5288                 },
     5309                defaults: _.extend( {}, api.Control.prototype.defaults, {
     5310                        templateId: 'customize-preview-link-control'
     5311                } ),
    52895312
    52905313                /**
    52915314                 * Initialize behaviors.
     
    53865409                                        type: 'info',
    53875410                                        message: api.l10n.saveBeforeShare
    53885411                                } );
    5389                                 control.notifications.add( notificationCode, notification );
     5412                                control.notifications.add( notification );
    53905413                        } else {
    53915414                                control.notifications.remove( notificationCode );
    53925415                        }
     
    60146037                        var previewer = this,
    60156038                                deferred, messenger, iframe;
    60166039
    6017                         if ( this._login )
     6040                        if ( this._login ) {
    60186041                                return this._login;
     6042                        }
    60196043
    60206044                        deferred = $.Deferred();
    60216045                        this._login = deferred.promise();
     
    61416165                                                }
    61426166
    61436167                                                if ( ! setting.notifications.has( notification.code ) ) {
    6144                                                         setting.notifications.add( code, notification );
     6168                                                        setting.notifications.add( notification );
    61456169                                                }
    61466170                                                invalidSettings.push( setting.id );
    61476171                                        } );
     
    63316355                        footerActions = $( '#customize-footer-actions' );
    63326356
    63336357                api.section( 'publish_settings', function( section ) {
    6334                         var updateButtonsState, previewLinkControl, TrashControl, trashControlInstance, trashControlId = 'trash_changeset', previewLinkControlId = 'changeset_preview_link', updateSectionActive, isSectionActive;
     6358                        var updateButtonsState, trashControl, updateSectionActive, isSectionActive;
    63356359
    6336                         TrashControl = api.Control.extend( {
    6337 
    6338                                 // This is a temporary hack while waiting for richer JS templating and dynamic instantiation.
    6339                                 embed: function() {
    6340                                         var control = this;
    6341                                         control.templateSelector = 'customize-trash-changeset-control';
    6342                                         return api.Control.prototype.embed.apply( control, arguments );
    6343                                 }
    6344                         } );
    6345 
    6346                         trashControlInstance = new TrashControl( trashControlId, {
    6347                                 params: {
    6348                                         type: 'button',
    6349                                         section: section.id,
    6350                                         active: true,
    6351                                         priority: 30,
    6352                                         content: '<li id="customize-control-' + trashControlId + '" class="customize-control"></li>'
    6353                                 }
     6360                        trashControl = new api.Control( 'trash_changeset', {
     6361                                type: 'button',
     6362                                section: section.id,
     6363                                priority: 30,
     6364                                templateId: 'customize-trash-changeset-control'
    63546365                        } );
    6355                         api.control.add( trashControlId, trashControlInstance );
    6356                         trashControlInstance.deferred.embedded.done( function() {
    6357                                 trashControlInstance.container.find( 'button' ).on( 'click', function() {
     6366                        api.control.add( trashControl );
     6367                        trashControl.deferred.embedded.done( function() {
     6368                                trashControl.container.find( 'button' ).on( 'click', function() {
    63586369                                        if ( confirm( api.l10n.trashConfirm ) ) {
    63596370                                                wp.customize.previewer.trash();
    63606371                                        }
    63616372                                } );
    63626373                        } );
    63636374
    6364                         previewLinkControl = new api.PreviewLinkControl( previewLinkControlId, {
    6365                                 params: {
    6366                                         section: section.id,
    6367                                         active: true,
    6368                                         priority: 100,
    6369                                         content: '<li id="customize-control-' + previewLinkControlId + '" class="customize-control"></li>'
    6370                                 }
    6371                         } );
    6372 
    6373                         api.control.add( previewLinkControlId, previewLinkControl );
     6375                        api.control.add( new api.PreviewLinkControl( 'changeset_preview_link', {
     6376                                section: section.id,
     6377                                priority: 100
     6378                        } ) );
    63746379
    63756380                        /**
    63766381                         * Return whether the pubish settings section should be active.
     
    65886593                                                api.unbind( 'change', captureSettingModifiedDuringSave );
    65896594
    65906595                                                if ( invalidSettings.length ) {
    6591                                                         api.notifications.add( errorCode, new api.Notification( errorCode, {
     6596                                                        api.notifications.add( new api.Notification( errorCode, {
    65926597                                                                message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ),
    65936598                                                                type: 'error',
    65946599                                                                dismissible: true,
     
    66906695                                                }
    66916696
    66926697                                                if ( notification ) {
    6693                                                         api.notifications.add( notification.code, notification );
     6698                                                        api.notifications.add( notification );
    66946699                                                }
    66956700
    66966701                                                if ( response.setting_validities ) {
     
    67956800                                        customize_changeset_uuid: api.settings.changeset.uuid,
    67966801                                        nonce: api.settings.nonce.trash
    67976802                                } );
    6798                                 api.notifications.add( 'changeset_trashing', new api.OverlayNotification( 'changeset_trashing', {
     6803                                api.notifications.add( new api.OverlayNotification( 'changeset_trashing', {
    67996804                                        type: 'info',
    68006805                                        message: api.l10n.revertingChanges,
    68016806                                        loading: true
     
    68236828                                        api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 );
    68246829                                        api.state( 'trashing' ).set( false );
    68256830                                        api.notifications.remove( 'changeset_trashing' );
    6826                                         api.notifications.add( notificationCode, new api.Notification( notificationCode, {
     6831                                        api.notifications.add( new api.Notification( notificationCode, {
    68276832                                                message: message || api.l10n.unknownError,
    68286833                                                dismissible: true,
    68296834                                                type: 'error'
     
    68916896
    68926897                // Create Settings
    68936898                $.each( api.settings.settings, function( id, data ) {
    6894                         var constructor = api.settingConstructor[ data.type ] || api.Setting,
    6895                                 setting;
    6896 
    6897                         setting = new constructor( id, data.value, {
     6899                        var Constructor = api.settingConstructor[ data.type ] || api.Setting;
     6900                        api.add( new Constructor( id, data.value, {
    68986901                                transport: data.transport,
    68996902                                previewer: api.previewer,
    69006903                                dirty: !! data.dirty
    6901                         } );
    6902                         api.add( id, setting );
     6904                        } ) );
    69036905                });
    69046906
    69056907                // Create Panels
    69066908                $.each( api.settings.panels, function ( id, data ) {
    6907                         var constructor = api.panelConstructor[ data.type ] || api.Panel,
    6908                                 panel;
    6909 
    6910                         panel = new constructor( id, {
    6911                                 params: data
    6912                         } );
    6913                         api.panel.add( id, panel );
     6909                        var Constructor = api.panelConstructor[ data.type ] || api.Panel;
     6910                        api.panel.add( new Constructor( id, data ) );
    69146911                });
    69156912
    69166913                // Create Sections
    69176914                $.each( api.settings.sections, function ( id, data ) {
    6918                         var constructor = api.sectionConstructor[ data.type ] || api.Section,
    6919                                 section;
    6920 
    6921                         section = new constructor( id, {
    6922                                 params: data
    6923                         } );
    6924                         api.section.add( id, section );
     6915                        var Constructor = api.sectionConstructor[ data.type ] || api.Section;
     6916                        api.section.add( new Constructor( id, data ) );
    69256917                });
    69266918
    69276919                // Create Controls
    69286920                $.each( api.settings.controls, function( id, data ) {
    6929                         var constructor = api.controlConstructor[ data.type ] || api.Control,
    6930                                 control;
    6931 
    6932                         control = new constructor( id, {
    6933                                 params: data,
    6934                                 previewer: api.previewer
    6935                         } );
    6936                         api.control.add( id, control );
     6921                        var Constructor = api.controlConstructor[ data.type ] || api.Control;
     6922                        api.control.add( new Constructor( id, data ) );
    69376923                });
    69386924
    69396925                // Focus the autofocused element
     
    72407226                                var code = 'autosave_available', onStateChange;
    72417227
    72427228                                // Since there is an autosave revision and the user hasn't loaded with autosaved, add notification to prompt to load autosaved version.
    7243                                 api.notifications.add( code, new api.Notification( code, {
     7229                                api.notifications.add( new api.Notification( code, {
    72447230                                        message: api.l10n.autosaveNotice,
    72457231                                        type: 'warning',
    72467232                                        dismissible: true,
     
    78937879                        control.element.set( 'blank' !== control.setting() );
    78947880
    78957881                        control.element.bind( function( to ) {
    7896                                 if ( ! to )
     7882                                if ( ! to ) {
    78977883                                        last = api( 'header_textcolor' ).get();
     7884                                }
    78987885
    78997886                                control.setting.set( to ? last : 'blank' );
    79007887                        });
     
    79267913
    79277914                                // Toggle notification when the homepage and posts page are both set and the same.
    79287915                                if ( 'page' === showOnFront() && pageOnFrontId && pageForPostsId && pageOnFrontId === pageForPostsId ) {
    7929                                         showOnFront.notifications.add( errorCode, new api.Notification( errorCode, {
     7916                                        showOnFront.notifications.add( new api.Notification( errorCode, {
    79307917                                                type: 'error',
    79317918                                                message: api.l10n.pageOnFrontError
    79327919                                        } ) );
     
    80778064                                        if ( headerVideoControl.active.get() ) {
    80788065                                                section.notifications.remove( noticeCode );
    80798066                                        } else {
    8080                                                 section.notifications.add( noticeCode, new api.Notification( noticeCode, {
     8067                                                section.notifications.add( new api.Notification( noticeCode, {
    80818068                                                        type: 'info',
    80828069                                                        message: api.l10n.videoHeaderNotice
    80838070                                                } ) );
  • src/wp-admin/js/customize-nav-menus.js

    diff --git src/wp-admin/js/customize-nav-menus.js src/wp-admin/js/customize-nav-menus.js
    index 4c8447b9fd..258e1bb376 100644
     
    968968                        menuNameControl = api.control( menuNameControlId );
    969969                        if ( ! menuNameControl ) {
    970970                                menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
    971                                         params: {
    972                                                 type: 'nav_menu_name',
    973                                                 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
    974                                                 label: api.Menus.data.l10n.menuNameLabel,
    975                                                 active: true,
    976                                                 section: section.id,
    977                                                 priority: 0,
    978                                                 settings: {
    979                                                         'default': section.id
    980                                                 }
     971                                        type: 'nav_menu_name',
     972                                        label: api.Menus.data.l10n.menuNameLabel,
     973                                        section: section.id,
     974                                        priority: 0,
     975                                        settings: {
     976                                                'default': section.id
    981977                                        }
    982978                                } );
    983                                 api.control.add( menuNameControl.id, menuNameControl );
     979                                api.control.add( menuNameControl );
    984980                                menuNameControl.active.set( true );
    985981                        }
    986982
     
    988984                        menuControl = api.control( section.id );
    989985                        if ( ! menuControl ) {
    990986                                menuControl = new api.controlConstructor.nav_menu( section.id, {
    991                                         params: {
    992                                                 type: 'nav_menu',
    993                                                 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
    994                                                 section: section.id,
    995                                                 priority: 998,
    996                                                 active: true,
    997                                                 settings: {
    998                                                         'default': section.id
    999                                                 },
    1000                                                 menu_id: section.params.menu_id
    1001                                         }
     987                                        type: 'nav_menu',
     988                                        section: section.id,
     989                                        priority: 998,
     990                                        settings: {
     991                                                'default': section.id
     992                                        },
     993                                        menu_id: section.params.menu_id
    1002994                                } );
    1003                                 api.control.add( menuControl.id, menuControl );
     995                                api.control.add( menuControl );
    1004996                                menuControl.active.set( true );
    1005997                        }
    1006998
     
    10091001                        menuAutoAddControl = api.control( menuAutoAddControlId );
    10101002                        if ( ! menuAutoAddControl ) {
    10111003                                menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, {
    1012                                         params: {
    1013                                                 type: 'nav_menu_auto_add',
    1014                                                 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
    1015                                                 label: '',
    1016                                                 active: true,
    1017                                                 section: section.id,
    1018                                                 priority: 999,
    1019                                                 settings: {
    1020                                                         'default': section.id
    1021                                                 }
     1004                                        type: 'nav_menu_auto_add',
     1005                                        label: '',
     1006                                        section: section.id,
     1007                                        priority: 999,
     1008                                        settings: {
     1009                                                'default': section.id
    10221010                                        }
    10231011                                } );
    1024                                 api.control.add( menuAutoAddControl.id, menuAutoAddControl );
     1012                                api.control.add( menuAutoAddControl );
    10251013                                menuAutoAddControl.active.set( true );
    10261014                        }
    10271015
     
    26772665
    26782666                        // Add the menu item control.
    26792667                        menuItemControl = new api.controlConstructor.nav_menu_item( customizeId, {
    2680                                 params: {
    2681                                         type: 'nav_menu_item',
    2682                                         content: '<li id="customize-control-nav_menu_item-' + String( placeholderId ) + '" class="customize-control customize-control-nav_menu_item"></li>',
    2683                                         section: menuControl.id,
    2684                                         priority: priority,
    2685                                         active: true,
    2686                                         settings: {
    2687                                                 'default': customizeId
    2688                                         },
    2689                                         menu_item_id: placeholderId
     2668                                type: 'nav_menu_item',
     2669                                section: menuControl.id,
     2670                                priority: priority,
     2671                                settings: {
     2672                                        'default': customizeId
    26902673                                },
    2691                                 previewer: api.previewer
     2674                                menu_item_id: placeholderId
    26922675                        } );
    26932676
    2694                         api.control.add( customizeId, menuItemControl );
     2677                        api.control.add( menuItemControl );
    26952678                        setting.preview();
    26962679                        menuControl.debouncedReflowMenuItems();
    26972680
     
    27752758                         * inside via the Section's ready method.
    27762759                         */
    27772760                        menuSection = new api.Menus.MenuSection( customizeId, {
    2778                                 params: {
    2779                                         id: customizeId,
    2780                                         panel: 'nav_menus',
    2781                                         title: displayNavMenuName( name ),
    2782                                         customizeAction: api.Menus.data.l10n.customizingMenus,
    2783                                         type: 'nav_menu',
    2784                                         priority: 10,
    2785                                         menu_id: placeholderId
    2786                                 }
     2761                                panel: 'nav_menus',
     2762                                title: displayNavMenuName( name ),
     2763                                customizeAction: api.Menus.data.l10n.customizingMenus,
     2764                                type: 'nav_menu',
     2765                                priority: 10,
     2766                                menu_id: placeholderId
    27872767                        } );
    2788                         api.section.add( customizeId, menuSection );
     2768                        api.section.add( menuSection );
    27892769
    27902770                        // Clear name field.
    27912771                        nameInput.val( '' );
     
    29092889
    29102890                                // Add the menu section.
    29112891                                newSection = new api.Menus.MenuSection( newCustomizeId, {
    2912                                         params: {
    2913                                                 id: newCustomizeId,
    2914                                                 panel: 'nav_menus',
    2915                                                 title: settingValue.name,
    2916                                                 customizeAction: api.Menus.data.l10n.customizingMenus,
    2917                                                 type: 'nav_menu',
    2918                                                 priority: oldSection.priority.get(),
    2919                                                 active: true,
    2920                                                 menu_id: update.term_id
    2921                                         }
     2892                                        panel: 'nav_menus',
     2893                                        title: settingValue.name,
     2894                                        customizeAction: api.Menus.data.l10n.customizingMenus,
     2895                                        type: 'nav_menu',
     2896                                        priority: oldSection.priority.get(),
     2897                                        menu_id: update.term_id
    29222898                                } );
    29232899
    29242900                                // Add new control for the new menu.
    2925                                 api.section.add( newCustomizeId, newSection );
     2901                                api.section.add( newSection );
    29262902
    29272903                                // Update the values for nav menus in Custom Menu controls.
    29282904                                api.control.each( function( setting ) {
     
    30523028
    30533029                                // Add the menu control.
    30543030                                newControl = new api.controlConstructor.nav_menu_item( newCustomizeId, {
    3055                                         params: {
    3056                                                 type: 'nav_menu_item',
    3057                                                 content: '<li id="customize-control-nav_menu_item-' + String( update.post_id ) + '" class="customize-control customize-control-nav_menu_item"></li>',
    3058                                                 menu_id: update.post_id,
    3059                                                 section: 'nav_menu[' + String( settingValue.nav_menu_term_id ) + ']',
    3060                                                 priority: oldControl.priority.get(),
    3061                                                 active: true,
    3062                                                 settings: {
    3063                                                         'default': newCustomizeId
    3064                                                 },
    3065                                                 menu_item_id: update.post_id
     3031                                        type: 'nav_menu_item',
     3032                                        menu_id: update.post_id,
     3033                                        section: 'nav_menu[' + String( settingValue.nav_menu_term_id ) + ']',
     3034                                        priority: oldControl.priority.get(),
     3035                                        settings: {
     3036                                                'default': newCustomizeId
    30663037                                        },
    3067                                         previewer: api.previewer
     3038                                        menu_item_id: update.post_id
    30683039                                } );
    30693040
    30703041                                // Remove old control.
     
    30723043                                api.control.remove( oldCustomizeId );
    30733044
    30743045                                // Add new control to take its place.
    3075                                 api.control.add( newCustomizeId, newControl );
     3046                                api.control.add( newControl );
    30763047
    30773048                                // Delete the placeholder and preview the new setting.
    30783049                                oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set.
  • src/wp-admin/js/customize-widgets.js

    diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js
    index dc46d73410..b8f6d7a8c6 100644
     
    20982098
    20992099                        controlConstructor = api.controlConstructor[controlType];
    21002100                        widgetFormControl = new controlConstructor( settingId, {
    2101                                 params: {
    2102                                         settings: {
    2103                                                 'default': settingId
    2104                                         },
    2105                                         content: controlContainer,
    2106                                         sidebar_id: self.params.sidebar_id,
    2107                                         widget_id: widgetId,
    2108                                         widget_id_base: widget.get( 'id_base' ),
    2109                                         type: controlType,
    2110                                         is_new: ! isExistingWidget,
    2111                                         width: widget.get( 'width' ),
    2112                                         height: widget.get( 'height' ),
    2113                                         is_wide: widget.get( 'is_wide' ),
    2114                                         active: true
     2101                                settings: {
     2102                                        'default': settingId
    21152103                                },
    2116                                 previewer: self.setting.previewer
     2104                                content: controlContainer,
     2105                                sidebar_id: self.params.sidebar_id,
     2106                                widget_id: widgetId,
     2107                                widget_id_base: widget.get( 'id_base' ),
     2108                                type: controlType,
     2109                                is_new: ! isExistingWidget,
     2110                                width: widget.get( 'width' ),
     2111                                height: widget.get( 'height' ),
     2112                                is_wide: widget.get( 'is_wide' )
    21172113                        } );
    2118                         api.control.add( settingId, widgetFormControl );
     2114                        api.control.add( widgetFormControl );
    21192115
    21202116                        // Make sure widget is removed from the other sidebars
    21212117                        api.each( function( otherSetting ) {
  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index eba2143a2c..873b093dfc 100644
    final class WP_Customize_Manager { 
    36043604
    36053605                ?>
    36063606                <script type="text/html" id="tmpl-customize-notification">
    3607                         <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 }}">
     3607                        <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 }}">
    36083608                                <div class="notification-message">{{{ data.message || data.code }}}</div>
    36093609                                <# if ( data.dismissible ) { #>
    36103610                                        <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>
  • src/wp-includes/js/customize-base.js

    diff --git src/wp-includes/js/customize-base.js src/wp-includes/js/customize-base.js
    index 7db5084ada..3b943a61be 100644
    window.wp = window.wp || {}; 
    376376                /**
    377377                 * Add an item to the collection.
    378378                 *
    379                  * @param {string} id    The ID of the item.
    380                  * @param {mixed}  value The item instance.
    381                  * @return {mixed} The new item's instance.
     379                 * @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.
     380                 * @param {wp.customize.Class}        [itemObject] - The item instance when the first argument is a ID string.
     381                 * @return {wp.customize.Class} The new item's instance, or an existing instance if already added.
    382382                 */
    383                 add: function( id, value ) {
    384                         if ( this.has( id ) )
    385                                 return this.value( id );
     383                add: function( item, itemObject ) {
     384                        var collection = this, id, instance;
     385                        if ( 'string' === typeof item ) {
     386                                id = item;
     387                                instance = itemObject;
     388                        } else {
     389                                if ( 'string' !== typeof item.id ) {
     390                                        throw new Error( 'Unknown key' );
     391                                }
     392                                id = item.id;
     393                                instance = item;
     394                        }
    386395
    387                         this._value[ id ] = value;
    388                         value.parent = this;
     396                        if ( collection.has( id ) ) {
     397                                return collection.value( id );
     398                        }
     399
     400                        collection._value[ id ] = instance;
     401                        instance.parent = collection;
    389402
    390403                        // Propagate a 'change' event on an item up to the collection.
    391                         if ( value.extended( api.Value ) )
    392                                 value.bind( this._change );
     404                        if ( instance.extended( api.Value ) ) {
     405                                instance.bind( collection._change );
     406                        }
    393407
    394                         this.trigger( 'add', value );
     408                        collection.trigger( 'add', instance );
    395409
    396410                        // If a deferred object exists for this item,
    397411                        // resolve it.
    398                         if ( this._deferreds[ id ] )
    399                                 this._deferreds[ id ].resolve();
     412                        if ( collection._deferreds[ id ] ) {
     413                                collection._deferreds[ id ].resolve();
     414                        }
    400415
    401                         return this._value[ id ];
     416                        return collection._value[ id ];
    402417                },
    403418
    404419                /**
    window.wp = window.wp || {}; 
    815830                 * @since 4.9.0
    816831                 * @var {string}
    817832                 */
    818                 classes: '',
     833                containerClasses: '',
    819834
    820835                /**
    821836                 * Initialize notification.
    window.wp = window.wp || {}; 
    829844                 * @param {string}   [params.setting] - Related setting ID.
    830845                 * @param {Function} [params.template] - Function for rendering template. If not provided, this will come from templateId.
    831846                 * @param {string}   [params.templateId] - ID for template to render the notification.
    832                  * @param {string}   [params.classes] - Additional class names to add to the notification container.
     847                 * @param {string}   [params.containerClasses] - Additional class names to add to the notification container.
    833848                 * @param {boolean}  [params.dismissible] - Whether the notification can be dismissed.
    834849                 */
    835850                initialize: function( code, params ) {
    window.wp = window.wp || {}; 
    844859                                        setting: null,
    845860                                        template: null,
    846861                                        dismissible: false,
    847                                         classes: ''
     862                                        containerClasses: ''
    848863                                },
    849864                                params
    850865                        );
  • src/wp-includes/js/customize-preview-widgets.js

    diff --git src/wp-includes/js/customize-preview-widgets.js src/wp-includes/js/customize-preview-widgets.js
    index b981788ed3..b2a60d892c 100644
    wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 
    407407                                wasInserted = true;
    408408                        } );
    409409
    410                         api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
     410                        api.selectiveRefresh.partial.add( widgetPartial );
    411411
    412412                        if ( wasInserted ) {
    413413                                sidebarPartial.reflowWidgets();
    wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 
    510510                                                sidebarArgs: registeredSidebar
    511511                                        }
    512512                                } );
    513                                 api.selectiveRefresh.partial.add( partial.id, partial );
     513                                api.selectiveRefresh.partial.add( partial );
    514514                        }
    515515                } );
    516516        };
  • src/wp-includes/js/customize-selective-refresh.js

    diff --git src/wp-includes/js/customize-selective-refresh.js src/wp-includes/js/customize-selective-refresh.js
    index 62bcfd8de0..3da7c0a237 100644
    wp.customize.selectiveRefresh = ( function( $, api ) { 
    877877                                partialOptions.constructingContainerContext = containerElement.data( 'customize-partial-placement-context' ) || {};
    878878                                Constructor = self.partialConstructor[ containerElement.data( 'customize-partial-type' ) ] || self.Partial;
    879879                                partial = new Constructor( id, partialOptions );
    880                                 self.partial.add( partial.id, partial );
     880                                self.partial.add( partial );
    881881                        }
    882882
    883883                        /*
    wp.customize.selectiveRefresh = ( function( $, api ) { 
    918918                        if ( ! partial ) {
    919919                                Constructor = self.partialConstructor[ data.type ] || self.Partial;
    920920                                partial = new Constructor( id, { params: data } );
    921                                 self.partial.add( id, partial );
     921                                self.partial.add( partial );
    922922                        } else {
    923923                                _.extend( partial.params, data );
    924924                        }
  • tests/qunit/wp-admin/js/customize-controls.js

    diff --git tests/qunit/wp-admin/js/customize-controls.js tests/qunit/wp-admin/js/customize-controls.js
    index 32b48127a2..dc3e391121 100644
    jQuery( window ).load( function (){ 
    306306                        type: 'default',
    307307                        content: null,
    308308                        active: true,
    309                         instanceNumber: null,
    310309                        customizeAction: ''
    311310                };
    312311                jQuery.each( defaultParams, function ( key, value ) {
    313312                        ok( 'undefined' !== typeof section.params[ key ] );
    314313                        equal( value, section.params[ key ] );
    315314                } );
     315                ok( _.isNumber( section.params.instanceNumber ) );
    316316        } );
    317317
    318318
    jQuery( window ).load( function (){ 
    417417                        priority: 100,
    418418                        type: 'default',
    419419                        content: null,
    420                         active: true,
    421                         instanceNumber: null
     420                        active: true
    422421                };
    423422                jQuery.each( defaultParams, function ( key, value ) {
    424423                        ok( 'undefined' !== typeof panel.params[ key ] );
    425424                        equal( value, panel.params[ key ] );
    426425                } );
     426                ok( _.isNumber( panel.params.instanceNumber ) );
    427427        } );
    428428
    429429        module( 'Dynamically-created Customizer Setting Model' );