Changeset 41935
- Timestamp:
- 10/19/2017 03:03:19 AM (6 years ago)
- Location:
- trunk
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/js/customize-controls.js
r41932 r41935 3363 3363 3364 3364 initialize: function( id, options ) { 3365 var control = this, deferredSettingIds = [], settings, gatherSettings , standardTypes;3365 var control = this, deferredSettingIds = [], settings, gatherSettings; 3366 3366 3367 3367 control.params = _.extend( {}, control.defaults ); … … 3401 3401 control.container = $( control.selector ); // Likely dead, per above. See #28709. 3402 3402 } 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; 3403 3868 3404 3869 standardTypes = [ … … 3422 3887 'url' 3423 3888 ]; 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 } 3891 3896 3892 3897 // 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 ); 3895 3900 if ( template && control.container ) { 3896 3901 control.container.html( template( control.params ) ); -
trunk/src/wp-includes/class-wp-customize-control.php
r41822 r41935 422 422 $class = 'customize-control customize-control-' . $this->type; 423 423 424 ?><li id="<?php echo esc_attr( $id ); ?>" class="<?php echo esc_attr( $class ); ?>">425 <?php $this->render_content(); ?>426 </li><?php424 printf( '<li id="%s" class="%s">', esc_attr( $id ), esc_attr( $class ) ); 425 $this->render_content(); 426 echo '</li>'; 427 427 } 428 428 -
trunk/src/wp-includes/customize/class-wp-customize-header-image-control.php
r41747 r41935 78 78 } 79 79 80 add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_header_image_template' ) ); 81 80 82 // Process default headers and uploaded headers. 81 83 $custom_image_header->process_default_headers(); … … 158 160 */ 159 161 public function render_content() { 160 $this->print_header_image_template();161 162 $visibility = $this->get_current_image_src() ? '' : ' style="display:none" '; 162 163 $width = absint( get_theme_support( 'custom-header', 'width' ) ); -
trunk/tests/phpunit/tests/customize/widgets.php
r40716 r41935 422 422 423 423 $this->assertEquals( 'widget_form', $params['type'] ); 424 $this->assertRegExp( '#^<li[^>]+>\s +</li>$#', $params['content'] );424 $this->assertRegExp( '#^<li[^>]+>\s*</li>$#', $params['content'] ); 425 425 $this->assertRegExp( '#^<div[^>]*class=\'widget\'[^>]*#s', $params['widget_control'] ); 426 426 $this->assertContains( '<div class="widget-content"></div>', $params['widget_control'] );
Note: See TracChangeset
for help on using the changeset viewer.