Make WordPress Core

Changeset 41935


Ignore:
Timestamp:
10/19/2017 03:03:19 AM (9 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.