Make WordPress Core

Changeset 41935


Ignore:
Timestamp:
10/19/2017 03:03:19 AM (6 years ago)
Author:
westonruter
Message:

Customize: Move control's fallback selection of default content template to renderContent method to align with sections and panels.

  • Only use default control content template when a more specific template doesn't exist.
  • Remove extraneous whitespace from being output in WP_Customize_Control::render() method.
  • Move Custom Header template printing to customize_controls_print_footer_scripts action.

See #30738.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/js/customize-controls.js

    r41932 r41935  
    33633363
    33643364        initialize: function( id, options ) {
    3365             var control = this, deferredSettingIds = [], settings, gatherSettings, standardTypes;
     3365            var control = this, deferredSettingIds = [], settings, gatherSettings;
    33663366
    33673367            control.params = _.extend( {}, control.defaults );
     
    34013401                control.container = $( control.selector ); // Likely dead, per above. See #28709.
    34023402            }
     3403
     3404            if ( control.params.templateId ) {
     3405                control.templateSelector = control.params.templateId;
     3406            } else {
     3407                control.templateSelector = 'customize-control-' + control.params.type + '-content';
     3408            }
     3409
     3410            control.deferred = {
     3411                embedded: new $.Deferred()
     3412            };
     3413            control.section = new api.Value();
     3414            control.priority = new api.Value();
     3415            control.active = new api.Value();
     3416            control.activeArgumentsQueue = [];
     3417            control.notifications = new api.Notifications({
     3418                alt: control.altNotice
     3419            });
     3420
     3421            control.elements = [];
     3422
     3423            control.active.bind( function ( active ) {
     3424                var args = control.activeArgumentsQueue.shift();
     3425                args = $.extend( {}, control.defaultActiveArguments, args );
     3426                control.onChangeActive( active, args );
     3427            } );
     3428
     3429            control.section.set( control.params.section );
     3430            control.priority.set( isNaN( control.params.priority ) ? 10 : control.params.priority );
     3431            control.active.set( control.params.active );
     3432
     3433            api.utils.bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
     3434
     3435            control.settings = {};
     3436
     3437            settings = {};
     3438            if ( control.params.setting ) {
     3439                settings['default'] = control.params.setting;
     3440            }
     3441            _.extend( settings, control.params.settings );
     3442
     3443            // Note: Settings can be an array or an object.
     3444            _.each( settings, function( setting, key ) {
     3445                if ( _.isObject( setting ) ) { // @todo Or check if instance of api.Setting?
     3446                    control.settings[ key ] = setting;
     3447                } else {
     3448                    deferredSettingIds.push( setting );
     3449                }
     3450            } );
     3451
     3452            gatherSettings = function() {
     3453
     3454                // Fill-in all resolved settings.
     3455                _.each( settings, function ( settingId, key ) {
     3456                    if ( ! control.settings[ key ] && _.isString( settingId ) ) {
     3457                        control.settings[ key ] = api( settingId );
     3458                    }
     3459                } );
     3460
     3461                // Make sure settings passed as array gets associated with default.
     3462                if ( control.settings[0] && ! control.settings['default'] ) {
     3463                    control.settings['default'] = control.settings[0];
     3464                }
     3465
     3466                // Identify the main setting.
     3467                control.setting = control.settings['default'] || null;
     3468
     3469                control.embed();
     3470            };
     3471
     3472            if ( 0 === deferredSettingIds.length ) {
     3473                gatherSettings();
     3474            } else {
     3475                api.apply( api, deferredSettingIds.concat( gatherSettings ) );
     3476            }
     3477
     3478            // After the control is embedded on the page, invoke the "ready" method.
     3479            control.deferred.embedded.done( function () {
     3480                control.linkElements();
     3481                control.setupNotifications();
     3482                control.ready();
     3483            });
     3484        },
     3485
     3486        /**
     3487         * Link elements between settings and inputs.
     3488         *
     3489         * @since 4.7.0
     3490         * @access public
     3491         *
     3492         * @returns {void}
     3493         */
     3494        linkElements: function () {
     3495            var control = this, nodes, radios, element;
     3496
     3497            nodes = control.container.find( '[data-customize-setting-link], [data-customize-setting-key-link]' );
     3498            radios = {};
     3499
     3500            nodes.each( function () {
     3501                var node = $( this ), name, setting;
     3502
     3503                if ( node.data( 'customizeSettingLinked' ) ) {
     3504                    return;
     3505                }
     3506                node.data( 'customizeSettingLinked', true ); // Prevent re-linking element.
     3507
     3508                if ( node.is( ':radio' ) ) {
     3509                    name = node.prop( 'name' );
     3510                    if ( radios[name] ) {
     3511                        return;
     3512                    }
     3513
     3514                    radios[name] = true;
     3515                    node = nodes.filter( '[name="' + name + '"]' );
     3516                }
     3517
     3518                // Let link by default refer to setting ID. If it doesn't exist, fallback to looking up by setting key.
     3519                if ( node.data( 'customizeSettingLink' ) ) {
     3520                    setting = api( node.data( 'customizeSettingLink' ) );
     3521                } else if ( node.data( 'customizeSettingKeyLink' ) ) {
     3522                    setting = control.settings[ node.data( 'customizeSettingKeyLink' ) ];
     3523                }
     3524
     3525                if ( setting ) {
     3526                    element = new api.Element( node );
     3527                    control.elements.push( element );
     3528                    element.sync( setting );
     3529                    element.set( setting() );
     3530                }
     3531            } );
     3532        },
     3533
     3534        /**
     3535         * Embed the control into the page.
     3536         */
     3537        embed: function () {
     3538            var control = this,
     3539                inject;
     3540
     3541            // Watch for changes to the section state
     3542            inject = function ( sectionId ) {
     3543                var parentContainer;
     3544                if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the front end.
     3545                    return;
     3546                }
     3547                // Wait for the section to be registered
     3548                api.section( sectionId, function ( section ) {
     3549                    // Wait for the section to be ready/initialized
     3550                    section.deferred.embedded.done( function () {
     3551                        parentContainer = ( section.contentContainer.is( 'ul' ) ) ? section.contentContainer : section.contentContainer.find( 'ul:first' );
     3552                        if ( ! control.container.parent().is( parentContainer ) ) {
     3553                            parentContainer.append( control.container );
     3554                            control.renderContent();
     3555                        }
     3556                        control.deferred.embedded.resolve();
     3557                    });
     3558                });
     3559            };
     3560            control.section.bind( inject );
     3561            inject( control.section.get() );
     3562        },
     3563
     3564        /**
     3565         * Triggered when the control's markup has been injected into the DOM.
     3566         *
     3567         * @returns {void}
     3568         */
     3569        ready: function() {
     3570            var control = this, newItem;
     3571            if ( 'dropdown-pages' === control.params.type && control.params.allow_addition ) {
     3572                newItem = control.container.find( '.new-content-item' );
     3573                newItem.hide(); // Hide in JS to preserve flex display when showing.
     3574                control.container.on( 'click', '.add-new-toggle', function( e ) {
     3575                    $( e.currentTarget ).slideUp( 180 );
     3576                    newItem.slideDown( 180 );
     3577                    newItem.find( '.create-item-input' ).focus();
     3578                });
     3579                control.container.on( 'click', '.add-content', function() {
     3580                    control.addNewPage();
     3581                });
     3582                control.container.on( 'keydown', '.create-item-input', function( e ) {
     3583                    if ( 13 === e.which ) { // Enter
     3584                        control.addNewPage();
     3585                    }
     3586                });
     3587            }
     3588        },
     3589
     3590        /**
     3591         * Get the element inside of a control's container that contains the validation error message.
     3592         *
     3593         * Control subclasses may override this to return the proper container to render notifications into.
     3594         * Injects the notification container for existing controls that lack the necessary container,
     3595         * including special handling for nav menu items and widgets.
     3596         *
     3597         * @since 4.6.0
     3598         * @returns {jQuery} Setting validation message element.
     3599         * @this {wp.customize.Control}
     3600         */
     3601        getNotificationsContainerElement: function() {
     3602            var control = this, controlTitle, notificationsContainer;
     3603
     3604            notificationsContainer = control.container.find( '.customize-control-notifications-container:first' );
     3605            if ( notificationsContainer.length ) {
     3606                return notificationsContainer;
     3607            }
     3608
     3609            notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' );
     3610
     3611            if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) {
     3612                control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer );
     3613            } else if ( control.container.hasClass( 'customize-control-widget_form' ) ) {
     3614                control.container.find( '.widget-inside:first' ).prepend( notificationsContainer );
     3615            } else {
     3616                controlTitle = control.container.find( '.customize-control-title' );
     3617                if ( controlTitle.length ) {
     3618                    controlTitle.after( notificationsContainer );
     3619                } else {
     3620                    control.container.prepend( notificationsContainer );
     3621                }
     3622            }
     3623            return notificationsContainer;
     3624        },
     3625
     3626        /**
     3627         * Set up notifications.
     3628         *
     3629         * @since 4.9.0
     3630         * @returns {void}
     3631         */
     3632        setupNotifications: function() {
     3633            var control = this, renderNotificationsIfVisible, onSectionAssigned;
     3634
     3635            // Add setting notifications to the control notification.
     3636            _.each( control.settings, function( setting ) {
     3637                if ( ! setting.notifications ) {
     3638                    return;
     3639                }
     3640                setting.notifications.bind( 'add', function( settingNotification ) {
     3641                    var params = _.extend(
     3642                        {},
     3643                        settingNotification,
     3644                        {
     3645                            setting: setting.id
     3646                        }
     3647                    );
     3648                    control.notifications.add( new api.Notification( setting.id + ':' + settingNotification.code, params ) );
     3649                } );
     3650                setting.notifications.bind( 'remove', function( settingNotification ) {
     3651                    control.notifications.remove( setting.id + ':' + settingNotification.code );
     3652                } );
     3653            } );
     3654
     3655            control.notifications.container = control.getNotificationsContainerElement();
     3656
     3657            renderNotificationsIfVisible = function() {
     3658                var sectionId = control.section();
     3659                if ( ! sectionId || ( api.section.has( sectionId ) && api.section( sectionId ).expanded() ) ) {
     3660                    control.notifications.render();
     3661                }
     3662            };
     3663
     3664            control.notifications.bind( 'rendered', function() {
     3665                var notifications = control.notifications.get();
     3666                control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
     3667                control.container.toggleClass( 'has-error', 0 !== _.where( notifications, { type: 'error' } ).length );
     3668            } );
     3669
     3670            onSectionAssigned = function( newSectionId, oldSectionId ) {
     3671                if ( oldSectionId && api.section.has( oldSectionId ) ) {
     3672                    api.section( oldSectionId ).expanded.unbind( renderNotificationsIfVisible );
     3673                }
     3674                if ( newSectionId ) {
     3675                    api.section( newSectionId, function( section ) {
     3676                        section.expanded.bind( renderNotificationsIfVisible );
     3677                        renderNotificationsIfVisible();
     3678                    });
     3679                }
     3680            };
     3681
     3682            control.section.bind( onSectionAssigned );
     3683            onSectionAssigned( control.section.get() );
     3684            control.notifications.bind( 'change', _.debounce( renderNotificationsIfVisible ) );
     3685        },
     3686
     3687        /**
     3688         * Render notifications.
     3689         *
     3690         * Renders the `control.notifications` into the control's container.
     3691         * Control subclasses may override this method to do their own handling
     3692         * of rendering notifications.
     3693         *
     3694         * @deprecated in favor of `control.notifications.render()`
     3695         * @since 4.6.0
     3696         * @this {wp.customize.Control}
     3697         */
     3698        renderNotifications: function() {
     3699            var control = this, container, notifications, hasError = false;
     3700
     3701            if ( 'undefined' !== typeof console && console.warn ) {
     3702                console.warn( '[DEPRECATED] wp.customize.Control.prototype.renderNotifications() is deprecated in favor of instantating a wp.customize.Notifications and calling its render() method.' );
     3703            }
     3704
     3705            container = control.getNotificationsContainerElement();
     3706            if ( ! container || ! container.length ) {
     3707                return;
     3708            }
     3709            notifications = [];
     3710            control.notifications.each( function( notification ) {
     3711                notifications.push( notification );
     3712                if ( 'error' === notification.type ) {
     3713                    hasError = true;
     3714                }
     3715            } );
     3716
     3717            if ( 0 === notifications.length ) {
     3718                container.stop().slideUp( 'fast' );
     3719            } else {
     3720                container.stop().slideDown( 'fast', null, function() {
     3721                    $( this ).css( 'height', 'auto' );
     3722                } );
     3723            }
     3724
     3725            if ( ! control.notificationsTemplate ) {
     3726                control.notificationsTemplate = wp.template( 'customize-control-notifications' );
     3727            }
     3728
     3729            control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
     3730            control.container.toggleClass( 'has-error', hasError );
     3731            container.empty().append( $.trim(
     3732                control.notificationsTemplate( { notifications: notifications, altNotice: Boolean( control.altNotice ) } )
     3733            ) );
     3734        },
     3735
     3736        /**
     3737         * Normal controls do not expand, so just expand its parent
     3738         *
     3739         * @param {Object} [params]
     3740         */
     3741        expand: function ( params ) {
     3742            api.section( this.section() ).expand( params );
     3743        },
     3744
     3745        /**
     3746         * Bring the containing section and panel into view and then
     3747         * this control into view, focusing on the first input.
     3748         */
     3749        focus: focus,
     3750
     3751        /**
     3752         * Update UI in response to a change in the control's active state.
     3753         * This does not change the active state, it merely handles the behavior
     3754         * for when it does change.
     3755         *
     3756         * @since 4.1.0
     3757         *
     3758         * @param {Boolean}  active
     3759         * @param {Object}   args
     3760         * @param {Number}   args.duration
     3761         * @param {Function} args.completeCallback
     3762         */
     3763        onChangeActive: function ( active, args ) {
     3764            if ( args.unchanged ) {
     3765                if ( args.completeCallback ) {
     3766                    args.completeCallback();
     3767                }
     3768                return;
     3769            }
     3770
     3771            if ( ! $.contains( document, this.container[0] ) ) {
     3772                // jQuery.fn.slideUp is not hiding an element if it is not in the DOM
     3773                this.container.toggle( active );
     3774                if ( args.completeCallback ) {
     3775                    args.completeCallback();
     3776                }
     3777            } else if ( active ) {
     3778                this.container.slideDown( args.duration, args.completeCallback );
     3779            } else {
     3780                this.container.slideUp( args.duration, args.completeCallback );
     3781            }
     3782        },
     3783
     3784        /**
     3785         * @deprecated 4.1.0 Use this.onChangeActive() instead.
     3786         */
     3787        toggle: function ( active ) {
     3788            return this.onChangeActive( active, this.defaultActiveArguments );
     3789        },
     3790
     3791        /**
     3792         * Shorthand way to enable the active state.
     3793         *
     3794         * @since 4.1.0
     3795         *
     3796         * @param {Object} [params]
     3797         * @returns {Boolean} false if already active
     3798         */
     3799        activate: Container.prototype.activate,
     3800
     3801        /**
     3802         * Shorthand way to disable the active state.
     3803         *
     3804         * @since 4.1.0
     3805         *
     3806         * @param {Object} [params]
     3807         * @returns {Boolean} false if already inactive
     3808         */
     3809        deactivate: Container.prototype.deactivate,
     3810
     3811        /**
     3812         * Re-use _toggleActive from Container class.
     3813         *
     3814         * @access private
     3815         */
     3816        _toggleActive: Container.prototype._toggleActive,
     3817
     3818        // @todo This function appears to be dead code and can be removed.
     3819        dropdownInit: function() {
     3820            var control      = this,
     3821                statuses     = this.container.find('.dropdown-status'),
     3822                params       = this.params,
     3823                toggleFreeze = false,
     3824                update       = function( to ) {
     3825                    if ( 'string' === typeof to && params.statuses && params.statuses[ to ] ) {
     3826                        statuses.html( params.statuses[ to ] ).show();
     3827                    } else {
     3828                        statuses.hide();
     3829                    }
     3830                };
     3831
     3832            // Support the .dropdown class to open/close complex elements
     3833            this.container.on( 'click keydown', '.dropdown', function( event ) {
     3834                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     3835                    return;
     3836                }
     3837
     3838                event.preventDefault();
     3839
     3840                if ( ! toggleFreeze ) {
     3841                    control.container.toggleClass( 'open' );
     3842                }
     3843
     3844                if ( control.container.hasClass( 'open' ) ) {
     3845                    control.container.parent().parent().find( 'li.library-selected' ).focus();
     3846                }
     3847
     3848                // Don't want to fire focus and click at same time
     3849                toggleFreeze = true;
     3850                setTimeout(function () {
     3851                    toggleFreeze = false;
     3852                }, 400);
     3853            });
     3854
     3855            this.setting.bind( update );
     3856            update( this.setting() );
     3857        },
     3858
     3859        /**
     3860         * Render the control from its JS template, if it exists.
     3861         *
     3862         * The control's container must already exist in the DOM.
     3863         *
     3864         * @since 4.1.0
     3865         */
     3866        renderContent: function () {
     3867            var control = this, template, standardTypes, templateId;
    34033868
    34043869            standardTypes = [
     
    34223887                'url'
    34233888            ];
    3424             if ( control.params.templateId ) {
    3425                 control.templateSelector = control.params.templateId;
    3426             } else if ( _.contains( standardTypes, control.params.type ) && control.container.is( ':empty' ) ) {
    3427                 control.templateSelector = 'customize-control-default-content';
    3428             } else {
    3429                 control.templateSelector = 'customize-control-' + control.params.type + '-content';
    3430             }
    3431 
    3432             control.deferred = {
    3433                 embedded: new $.Deferred()
    3434             };
    3435             control.section = new api.Value();
    3436             control.priority = new api.Value();
    3437             control.active = new api.Value();
    3438             control.activeArgumentsQueue = [];
    3439             control.notifications = new api.Notifications({
    3440                 alt: control.altNotice
    3441             });
    3442 
    3443             control.elements = [];
    3444 
    3445             control.active.bind( function ( active ) {
    3446                 var args = control.activeArgumentsQueue.shift();
    3447                 args = $.extend( {}, control.defaultActiveArguments, args );
    3448                 control.onChangeActive( active, args );
    3449             } );
    3450 
    3451             control.section.set( control.params.section );
    3452             control.priority.set( isNaN( control.params.priority ) ? 10 : control.params.priority );
    3453             control.active.set( control.params.active );
    3454 
    3455             api.utils.bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
    3456 
    3457             control.settings = {};
    3458 
    3459             settings = {};
    3460             if ( control.params.setting ) {
    3461                 settings['default'] = control.params.setting;
    3462             }
    3463             _.extend( settings, control.params.settings );
    3464 
    3465             // Note: Settings can be an array or an object.
    3466             _.each( settings, function( setting, key ) {
    3467                 if ( _.isObject( setting ) ) { // @todo Or check if instance of api.Setting?
    3468                     control.settings[ key ] = setting;
    3469                 } else {
    3470                     deferredSettingIds.push( setting );
    3471                 }
    3472             } );
    3473 
    3474             gatherSettings = function() {
    3475 
    3476                 // Fill-in all resolved settings.
    3477                 _.each( settings, function ( settingId, key ) {
    3478                     if ( ! control.settings[ key ] && _.isString( settingId ) ) {
    3479                         control.settings[ key ] = api( settingId );
    3480                     }
    3481                 } );
    3482 
    3483                 // Make sure settings passed as array gets associated with default.
    3484                 if ( control.settings[0] && ! control.settings['default'] ) {
    3485                     control.settings['default'] = control.settings[0];
    3486                 }
    3487 
    3488                 // Identify the main setting.
    3489                 control.setting = control.settings['default'] || null;
    3490 
    3491                 control.embed();
    3492             };
    3493 
    3494             if ( 0 === deferredSettingIds.length ) {
    3495                 gatherSettings();
    3496             } else {
    3497                 api.apply( api, deferredSettingIds.concat( gatherSettings ) );
    3498             }
    3499 
    3500             // After the control is embedded on the page, invoke the "ready" method.
    3501             control.deferred.embedded.done( function () {
    3502                 control.linkElements();
    3503                 control.setupNotifications();
    3504                 control.ready();
    3505             });
    3506         },
    3507 
    3508         /**
    3509          * Link elements between settings and inputs.
    3510          *
    3511          * @since 4.7.0
    3512          * @access public
    3513          *
    3514          * @returns {void}
    3515          */
    3516         linkElements: function () {
    3517             var control = this, nodes, radios, element;
    3518 
    3519             nodes = control.container.find( '[data-customize-setting-link], [data-customize-setting-key-link]' );
    3520             radios = {};
    3521 
    3522             nodes.each( function () {
    3523                 var node = $( this ), name, setting;
    3524 
    3525                 if ( node.data( 'customizeSettingLinked' ) ) {
    3526                     return;
    3527                 }
    3528                 node.data( 'customizeSettingLinked', true ); // Prevent re-linking element.
    3529 
    3530                 if ( node.is( ':radio' ) ) {
    3531                     name = node.prop( 'name' );
    3532                     if ( radios[name] ) {
    3533                         return;
    3534                     }
    3535 
    3536                     radios[name] = true;
    3537                     node = nodes.filter( '[name="' + name + '"]' );
    3538                 }
    3539 
    3540                 // Let link by default refer to setting ID. If it doesn't exist, fallback to looking up by setting key.
    3541                 if ( node.data( 'customizeSettingLink' ) ) {
    3542                     setting = api( node.data( 'customizeSettingLink' ) );
    3543                 } else if ( node.data( 'customizeSettingKeyLink' ) ) {
    3544                     setting = control.settings[ node.data( 'customizeSettingKeyLink' ) ];
    3545                 }
    3546 
    3547                 if ( setting ) {
    3548                     element = new api.Element( node );
    3549                     control.elements.push( element );
    3550                     element.sync( setting );
    3551                     element.set( setting() );
    3552                 }
    3553             } );
    3554         },
    3555 
    3556         /**
    3557          * Embed the control into the page.
    3558          */
    3559         embed: function () {
    3560             var control = this,
    3561                 inject;
    3562 
    3563             // Watch for changes to the section state
    3564             inject = function ( sectionId ) {
    3565                 var parentContainer;
    3566                 if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the front end.
    3567                     return;
    3568                 }
    3569                 // Wait for the section to be registered
    3570                 api.section( sectionId, function ( section ) {
    3571                     // Wait for the section to be ready/initialized
    3572                     section.deferred.embedded.done( function () {
    3573                         parentContainer = ( section.contentContainer.is( 'ul' ) ) ? section.contentContainer : section.contentContainer.find( 'ul:first' );
    3574                         if ( ! control.container.parent().is( parentContainer ) ) {
    3575                             parentContainer.append( control.container );
    3576                             control.renderContent();
    3577                         }
    3578                         control.deferred.embedded.resolve();
    3579                     });
    3580                 });
    3581             };
    3582             control.section.bind( inject );
    3583             inject( control.section.get() );
    3584         },
    3585 
    3586         /**
    3587          * Triggered when the control's markup has been injected into the DOM.
    3588          *
    3589          * @returns {void}
    3590          */
    3591         ready: function() {
    3592             var control = this, newItem;
    3593             if ( 'dropdown-pages' === control.params.type && control.params.allow_addition ) {
    3594                 newItem = control.container.find( '.new-content-item' );
    3595                 newItem.hide(); // Hide in JS to preserve flex display when showing.
    3596                 control.container.on( 'click', '.add-new-toggle', function( e ) {
    3597                     $( e.currentTarget ).slideUp( 180 );
    3598                     newItem.slideDown( 180 );
    3599                     newItem.find( '.create-item-input' ).focus();
    3600                 });
    3601                 control.container.on( 'click', '.add-content', function() {
    3602                     control.addNewPage();
    3603                 });
    3604                 control.container.on( 'keydown', '.create-item-input', function( e ) {
    3605                     if ( 13 === e.which ) { // Enter
    3606                         control.addNewPage();
    3607                     }
    3608                 });
    3609             }
    3610         },
    3611 
    3612         /**
    3613          * Get the element inside of a control's container that contains the validation error message.
    3614          *
    3615          * Control subclasses may override this to return the proper container to render notifications into.
    3616          * Injects the notification container for existing controls that lack the necessary container,
    3617          * including special handling for nav menu items and widgets.
    3618          *
    3619          * @since 4.6.0
    3620          * @returns {jQuery} Setting validation message element.
    3621          * @this {wp.customize.Control}
    3622          */
    3623         getNotificationsContainerElement: function() {
    3624             var control = this, controlTitle, notificationsContainer;
    3625 
    3626             notificationsContainer = control.container.find( '.customize-control-notifications-container:first' );
    3627             if ( notificationsContainer.length ) {
    3628                 return notificationsContainer;
    3629             }
    3630 
    3631             notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' );
    3632 
    3633             if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) {
    3634                 control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer );
    3635             } else if ( control.container.hasClass( 'customize-control-widget_form' ) ) {
    3636                 control.container.find( '.widget-inside:first' ).prepend( notificationsContainer );
    3637             } else {
    3638                 controlTitle = control.container.find( '.customize-control-title' );
    3639                 if ( controlTitle.length ) {
    3640                     controlTitle.after( notificationsContainer );
    3641                 } else {
    3642                     control.container.prepend( notificationsContainer );
    3643                 }
    3644             }
    3645             return notificationsContainer;
    3646         },
    3647 
    3648         /**
    3649          * Set up notifications.
    3650          *
    3651          * @since 4.9.0
    3652          * @returns {void}
    3653          */
    3654         setupNotifications: function() {
    3655             var control = this, renderNotificationsIfVisible, onSectionAssigned;
    3656 
    3657             // Add setting notifications to the control notification.
    3658             _.each( control.settings, function( setting ) {
    3659                 if ( ! setting.notifications ) {
    3660                     return;
    3661                 }
    3662                 setting.notifications.bind( 'add', function( settingNotification ) {
    3663                     var params = _.extend(
    3664                         {},
    3665                         settingNotification,
    3666                         {
    3667                             setting: setting.id
    3668                         }
    3669                     );
    3670                     control.notifications.add( new api.Notification( setting.id + ':' + settingNotification.code, params ) );
    3671                 } );
    3672                 setting.notifications.bind( 'remove', function( settingNotification ) {
    3673                     control.notifications.remove( setting.id + ':' + settingNotification.code );
    3674                 } );
    3675             } );
    3676 
    3677             control.notifications.container = control.getNotificationsContainerElement();
    3678 
    3679             renderNotificationsIfVisible = function() {
    3680                 var sectionId = control.section();
    3681                 if ( ! sectionId || ( api.section.has( sectionId ) && api.section( sectionId ).expanded() ) ) {
    3682                     control.notifications.render();
    3683                 }
    3684             };
    3685 
    3686             control.notifications.bind( 'rendered', function() {
    3687                 var notifications = control.notifications.get();
    3688                 control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
    3689                 control.container.toggleClass( 'has-error', 0 !== _.where( notifications, { type: 'error' } ).length );
    3690             } );
    3691 
    3692             onSectionAssigned = function( newSectionId, oldSectionId ) {
    3693                 if ( oldSectionId && api.section.has( oldSectionId ) ) {
    3694                     api.section( oldSectionId ).expanded.unbind( renderNotificationsIfVisible );
    3695                 }
    3696                 if ( newSectionId ) {
    3697                     api.section( newSectionId, function( section ) {
    3698                         section.expanded.bind( renderNotificationsIfVisible );
    3699                         renderNotificationsIfVisible();
    3700                     });
    3701                 }
    3702             };
    3703 
    3704             control.section.bind( onSectionAssigned );
    3705             onSectionAssigned( control.section.get() );
    3706             control.notifications.bind( 'change', _.debounce( renderNotificationsIfVisible ) );
    3707         },
    3708 
    3709         /**
    3710          * Render notifications.
    3711          *
    3712          * Renders the `control.notifications` into the control's container.
    3713          * Control subclasses may override this method to do their own handling
    3714          * of rendering notifications.
    3715          *
    3716          * @deprecated in favor of `control.notifications.render()`
    3717          * @since 4.6.0
    3718          * @this {wp.customize.Control}
    3719          */
    3720         renderNotifications: function() {
    3721             var control = this, container, notifications, hasError = false;
    3722 
    3723             if ( 'undefined' !== typeof console && console.warn ) {
    3724                 console.warn( '[DEPRECATED] wp.customize.Control.prototype.renderNotifications() is deprecated in favor of instantating a wp.customize.Notifications and calling its render() method.' );
    3725             }
    3726 
    3727             container = control.getNotificationsContainerElement();
    3728             if ( ! container || ! container.length ) {
    3729                 return;
    3730             }
    3731             notifications = [];
    3732             control.notifications.each( function( notification ) {
    3733                 notifications.push( notification );
    3734                 if ( 'error' === notification.type ) {
    3735                     hasError = true;
    3736                 }
    3737             } );
    3738 
    3739             if ( 0 === notifications.length ) {
    3740                 container.stop().slideUp( 'fast' );
    3741             } else {
    3742                 container.stop().slideDown( 'fast', null, function() {
    3743                     $( this ).css( 'height', 'auto' );
    3744                 } );
    3745             }
    3746 
    3747             if ( ! control.notificationsTemplate ) {
    3748                 control.notificationsTemplate = wp.template( 'customize-control-notifications' );
    3749             }
    3750 
    3751             control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
    3752             control.container.toggleClass( 'has-error', hasError );
    3753             container.empty().append( $.trim(
    3754                 control.notificationsTemplate( { notifications: notifications, altNotice: Boolean( control.altNotice ) } )
    3755             ) );
    3756         },
    3757 
    3758         /**
    3759          * Normal controls do not expand, so just expand its parent
    3760          *
    3761          * @param {Object} [params]
    3762          */
    3763         expand: function ( params ) {
    3764             api.section( this.section() ).expand( params );
    3765         },
    3766 
    3767         /**
    3768          * Bring the containing section and panel into view and then
    3769          * this control into view, focusing on the first input.
    3770          */
    3771         focus: focus,
    3772 
    3773         /**
    3774          * Update UI in response to a change in the control's active state.
    3775          * This does not change the active state, it merely handles the behavior
    3776          * for when it does change.
    3777          *
    3778          * @since 4.1.0
    3779          *
    3780          * @param {Boolean}  active
    3781          * @param {Object}   args
    3782          * @param {Number}   args.duration
    3783          * @param {Function} args.completeCallback
    3784          */
    3785         onChangeActive: function ( active, args ) {
    3786             if ( args.unchanged ) {
    3787                 if ( args.completeCallback ) {
    3788                     args.completeCallback();
    3789                 }
    3790                 return;
    3791             }
    3792 
    3793             if ( ! $.contains( document, this.container[0] ) ) {
    3794                 // jQuery.fn.slideUp is not hiding an element if it is not in the DOM
    3795                 this.container.toggle( active );
    3796                 if ( args.completeCallback ) {
    3797                     args.completeCallback();
    3798                 }
    3799             } else if ( active ) {
    3800                 this.container.slideDown( args.duration, args.completeCallback );
    3801             } else {
    3802                 this.container.slideUp( args.duration, args.completeCallback );
    3803             }
    3804         },
    3805 
    3806         /**
    3807          * @deprecated 4.1.0 Use this.onChangeActive() instead.
    3808          */
    3809         toggle: function ( active ) {
    3810             return this.onChangeActive( active, this.defaultActiveArguments );
    3811         },
    3812 
    3813         /**
    3814          * Shorthand way to enable the active state.
    3815          *
    3816          * @since 4.1.0
    3817          *
    3818          * @param {Object} [params]
    3819          * @returns {Boolean} false if already active
    3820          */
    3821         activate: Container.prototype.activate,
    3822 
    3823         /**
    3824          * Shorthand way to disable the active state.
    3825          *
    3826          * @since 4.1.0
    3827          *
    3828          * @param {Object} [params]
    3829          * @returns {Boolean} false if already inactive
    3830          */
    3831         deactivate: Container.prototype.deactivate,
    3832 
    3833         /**
    3834          * Re-use _toggleActive from Container class.
    3835          *
    3836          * @access private
    3837          */
    3838         _toggleActive: Container.prototype._toggleActive,
    3839 
    3840         // @todo This function appears to be dead code and can be removed.
    3841         dropdownInit: function() {
    3842             var control      = this,
    3843                 statuses     = this.container.find('.dropdown-status'),
    3844                 params       = this.params,
    3845                 toggleFreeze = false,
    3846                 update       = function( to ) {
    3847                     if ( 'string' === typeof to && params.statuses && params.statuses[ to ] ) {
    3848                         statuses.html( params.statuses[ to ] ).show();
    3849                     } else {
    3850                         statuses.hide();
    3851                     }
    3852                 };
    3853 
    3854             // Support the .dropdown class to open/close complex elements
    3855             this.container.on( 'click keydown', '.dropdown', function( event ) {
    3856                 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    3857                     return;
    3858                 }
    3859 
    3860                 event.preventDefault();
    3861 
    3862                 if ( ! toggleFreeze ) {
    3863                     control.container.toggleClass( 'open' );
    3864                 }
    3865 
    3866                 if ( control.container.hasClass( 'open' ) ) {
    3867                     control.container.parent().parent().find( 'li.library-selected' ).focus();
    3868                 }
    3869 
    3870                 // Don't want to fire focus and click at same time
    3871                 toggleFreeze = true;
    3872                 setTimeout(function () {
    3873                     toggleFreeze = false;
    3874                 }, 400);
    3875             });
    3876 
    3877             this.setting.bind( update );
    3878             update( this.setting() );
    3879         },
    3880 
    3881         /**
    3882          * Render the control from its JS template, if it exists.
    3883          *
    3884          * The control's container must already exist in the DOM.
    3885          *
    3886          * @since 4.1.0
    3887          */
    3888         renderContent: function () {
    3889             var template,
    3890                 control = this;
     3889
     3890            templateId = control.templateSelector;
     3891
     3892            // Use default content template when a standard HTML type is used and there isn't a more specific template existing.
     3893            if ( templateId === 'customize-control-' + control.params.type + '-content' && _.contains( standardTypes, control.params.type ) && ! document.getElementById( 'tmpl-' + templateId ) ) {
     3894                templateId = 'customize-control-default-content';
     3895            }
    38913896
    38923897            // Replace the container element's content with the control.
    3893             if ( 0 !== $( '#tmpl-' + control.templateSelector ).length ) {
    3894                 template = wp.template( control.templateSelector );
     3898            if ( document.getElementById( 'tmpl-' + templateId ) ) {
     3899                template = wp.template( templateId );
    38953900                if ( template && control.container ) {
    38963901                    control.container.html( template( control.params ) );
  • trunk/src/wp-includes/class-wp-customize-control.php

    r41822 r41935  
    422422        $class = 'customize-control customize-control-' . $this->type;
    423423
    424         ?><li id="<?php echo esc_attr( $id ); ?>" class="<?php echo esc_attr( $class ); ?>">
    425             <?php $this->render_content(); ?>
    426         </li><?php
     424        printf( '<li id="%s" class="%s">', esc_attr( $id ), esc_attr( $class ) );
     425        $this->render_content();
     426        echo '</li>';
    427427    }
    428428
  • trunk/src/wp-includes/customize/class-wp-customize-header-image-control.php

    r41747 r41935  
    7878        }
    7979
     80        add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_header_image_template' ) );
     81
    8082        // Process default headers and uploaded headers.
    8183        $custom_image_header->process_default_headers();
     
    158160     */
    159161    public function render_content() {
    160         $this->print_header_image_template();
    161162        $visibility = $this->get_current_image_src() ? '' : ' style="display:none" ';
    162163        $width = absint( get_theme_support( 'custom-header', 'width' ) );
  • trunk/tests/phpunit/tests/customize/widgets.php

    r40716 r41935  
    422422
    423423        $this->assertEquals( 'widget_form', $params['type'] );
    424         $this->assertRegExp( '#^<li[^>]+>\s+</li>$#', $params['content'] );
     424        $this->assertRegExp( '#^<li[^>]+>\s*</li>$#', $params['content'] );
    425425        $this->assertRegExp( '#^<div[^>]*class=\'widget\'[^>]*#s', $params['widget_control'] );
    426426        $this->assertContains( '<div class="widget-content"></div>', $params['widget_control'] );
Note: See TracChangeset for help on using the changeset viewer.