WordPress.org

Make WordPress Core

Changeset 35231


Ignore:
Timestamp:
10/16/2015 11:47:56 PM (4 years ago)
Author:
westonruter
Message:

Customizer: Always show Widgets panel initially if sidebars are registered; show notice to users in panel if no widget areas are in current preview.

Widgets panel will not wait to display until the preview loads.

Also fixes problems with margin-top in panels where other panels' active states change, as well as ensuring sections of deactivated panel collapse before panel is hidden to prevent the pane from becoming empty of controls.

Fixes #33052.
Fixes #33567.

Location:
trunk
Files:
7 edited

Legend:

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

    r33912 r35231  
    124124}
    125125
    126 #customize-controls .customize-info .customize-panel-description {
     126#customize-controls .customize-info .customize-panel-description,
     127#customize-controls .no-widget-areas-rendered-notice {
    127128    color: #555;
    128129    display: none;
     
    130131    padding: 12px 15px;
    131132    border-top: 1px solid #ddd;
     133}
     134#customize-controls .customize-info .customize-panel-description.open + .no-widget-areas-rendered-notice {
     135    border-top: none;
    132136}
    133137
  • trunk/src/wp-admin/js/customize-controls.js

    r34557 r35231  
    301301         */
    302302        onChangeActive: function( active, args ) {
    303             var duration, construct = this;
     303            var duration, construct = this, expandedOtherPanel;
    304304            if ( args.unchanged ) {
    305305                if ( args.completeCallback ) {
     
    310310
    311311            duration = ( 'resolved' === api.previewer.deferred.active.state() ? args.duration : 0 );
     312
     313            if ( construct.extended( api.Panel ) ) {
     314                // If this is a panel is not currently expanded but another panel is expanded, do not animate.
     315                api.panel.each(function ( panel ) {
     316                    if ( panel !== construct && panel.expanded() ) {
     317                        expandedOtherPanel = panel;
     318                        duration = 0;
     319                    }
     320                });
     321
     322                // Collapse any expanded sections inside of this panel first before deactivating.
     323                if ( ! active ) {
     324                    _.each( construct.sections(), function( section ) {
     325                        section.collapse( { duration: 0 } );
     326                    } );
     327                }
     328            }
     329
    312330            if ( ! $.contains( document, construct.container[0] ) ) {
    313331                // jQuery.fn.slideUp is not hiding an element if it is not in the DOM
     
    330348                }
    331349            }
     350
     351            // Recalculate the margin-top immediately, not waiting for debounced reflow, to prevent momentary (100ms) vertical jiggle.
     352            if ( expandedOtherPanel ) {
     353                expandedOtherPanel._recalculateTopMargin();
     354            }
    332355        },
    333356
     
    379402
    380403        /**
    381          * @param {Boolean} expanded
    382          * @param {Object} [params]
    383          * @returns {Boolean} false if state already applied
    384          */
    385         _toggleExpanded: function ( expanded, params ) {
    386             var self = this;
     404         * Handle the toggle logic for expand/collapse.
     405         *
     406         * @param {Boolean}  expanded - The new state to apply.
     407         * @param {Object}   [params] - Object containing options for expand/collapse.
     408         * @param {Function} [params.completeCallback] - Function to call when expansion/collapse is complete.
     409         * @returns {Boolean} false if state already applied or active state is false
     410         */
     411        _toggleExpanded: function( expanded, params ) {
     412            var instance = this, previousCompleteCallback;
    387413            params = params || {};
    388             var section = this, previousCompleteCallback = params.completeCallback;
    389             params.completeCallback = function () {
     414            previousCompleteCallback = params.completeCallback;
     415
     416            // Short-circuit expand() if the instance is not active.
     417            if ( expanded && ! instance.active() ) {
     418                return false;
     419            }
     420
     421            params.completeCallback = function() {
    390422                if ( previousCompleteCallback ) {
    391                     previousCompleteCallback.apply( section, arguments );
     423                    previousCompleteCallback.apply( instance, arguments );
    392424                }
    393425                if ( expanded ) {
    394                     section.container.trigger( 'expanded' );
     426                    instance.container.trigger( 'expanded' );
    395427                } else {
    396                     section.container.trigger( 'collapsed' );
     428                    instance.container.trigger( 'collapsed' );
    397429                }
    398430            };
    399             if ( ( expanded && this.expanded.get() ) || ( ! expanded && ! this.expanded.get() ) ) {
     431            if ( ( expanded && instance.expanded.get() ) || ( ! expanded && ! instance.expanded.get() ) ) {
    400432                params.unchanged = true;
    401                 self.onChangeExpanded( self.expanded.get(), params );
     433                instance.onChangeExpanded( instance.expanded.get(), params );
    402434                return false;
    403435            } else {
    404436                params.unchanged = false;
    405                 this.expandedArgumentsQueue.push( params );
    406                 this.expanded.set( expanded );
     437                instance.expandedArgumentsQueue.push( params );
     438                instance.expanded.set( expanded );
    407439                return true;
    408440            }
     
    411443        /**
    412444         * @param {Object} [params]
    413          * @returns {Boolean} false if already expanded
     445         * @returns {Boolean} false if already expanded or if inactive.
    414446         */
    415447        expand: function ( params ) {
     
    419451        /**
    420452         * @param {Object} [params]
    421          * @returns {Boolean} false if already collapsed
     453         * @returns {Boolean} false if already collapsed.
    422454         */
    423455        collapse: function ( params ) {
     
    540572            section.panel.bind( inject );
    541573            inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one
     574
     575            section.deferred.embedded.done(function() {
     576                // Fix the top margin after reflow.
     577                api.bind( 'pane-contents-reflowed', _.debounce( function() {
     578                    section._recalculateTopMargin();
     579                }, 100 ) );
     580            });
    542581        },
    543582
     
    647686                        $( window ).on( 'resize.customizer-section', _.debounce( resizeContentHeight, 100 ) );
    648687
    649                         // Fix the top margin after reflow.
    650                         api.bind( 'pane-contents-reflowed', _.debounce( function() {
    651                             var offset = ( content.offset().top - headerActionsHeight );
    652                             if ( 0 < offset ) {
    653                                 content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - offset ) );
    654                             }
    655                         }, 100 ) );
     688                        section._recalculateTopMargin();
    656689                    };
    657690                }
     
    693726                    args.completeCallback();
    694727                }
     728            }
     729        },
     730
     731        /**
     732         * Recalculate the top margin.
     733         *
     734         * @since 4.4.0
     735         * @private
     736         */
     737        _recalculateTopMargin: function() {
     738            var section = this, content, offset, headerActionsHeight;
     739            content = section.container.find( '.accordion-section-content' );
     740            if ( 0 === content.length ) {
     741                return;
     742            }
     743            headerActionsHeight = $( '#customize-header-actions' ).height();
     744            offset = ( content.offset().top - headerActionsHeight );
     745            if ( 0 < offset ) {
     746                content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - offset ) );
    695747            }
    696748        }
     
    11561208                panel.renderContent();
    11571209            }
     1210
     1211            api.bind( 'pane-contents-reflowed', _.debounce( function() {
     1212                panel._recalculateTopMargin();
     1213            }, 100 ) );
     1214
    11581215            panel.deferred.embedded.resolve();
    11591216        },
     
    12541311         * @param {Object}   args
    12551312         * @param {Boolean}  args.unchanged
    1256          * @param {Callback} args.completeCallback
     1313         * @param {Function} args.completeCallback
    12571314         */
    12581315        onChangeExpanded: function ( expanded, args ) {
     
    12691326            var position, scroll,
    12701327                panel = this,
    1271                 section = panel.container.closest( '.accordion-section' ), // This is actually the panel.
    1272                 overlay = section.closest( '.wp-full-overlay' ),
    1273                 container = section.closest( '.wp-full-overlay-sidebar-content' ),
     1328                accordionSection = panel.container.closest( '.accordion-section' ),
     1329                overlay = accordionSection.closest( '.wp-full-overlay' ),
     1330                container = accordionSection.closest( '.wp-full-overlay-sidebar-content' ),
    12741331                siblings = container.find( '.open' ),
    12751332                topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ),
    1276                 backBtn = section.find( '.customize-panel-back' ),
    1277                 panelTitle = section.find( '.accordion-section-title' ).first(),
    1278                 content = section.find( '.control-panel-content' ),
     1333                backBtn = accordionSection.find( '.customize-panel-back' ),
     1334                panelTitle = accordionSection.find( '.accordion-section-title' ).first(),
     1335                content = accordionSection.find( '.control-panel-content' ),
    12791336                headerActionsHeight = $( '#customize-header-actions' ).height();
    12801337
     
    12981355                    scroll = container.scrollTop();
    12991356                    content.css( 'margin-top', ( headerActionsHeight - position - scroll ) );
    1300                     section.addClass( 'current-panel' );
     1357                    accordionSection.addClass( 'current-panel' );
    13011358                    overlay.addClass( 'in-sub-panel' );
    13021359                    container.scrollTop( 0 );
     
    13081365                backBtn.attr( 'tabindex', '0' );
    13091366                backBtn.focus();
    1310 
    1311                 // Fix the top margin after reflow.
    1312                 api.bind( 'pane-contents-reflowed', _.debounce( function() {
    1313                     content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - ( content.offset().top - headerActionsHeight ) ) );
    1314                 }, 100 ) );
     1367                panel._recalculateTopMargin();
    13151368            } else {
    13161369                siblings.removeClass( 'open' );
    1317                 section.removeClass( 'current-panel' );
     1370                accordionSection.removeClass( 'current-panel' );
    13181371                overlay.removeClass( 'in-sub-panel' );
    13191372                content.delay( 180 ).hide( 0, function() {
     
    13281381                container.scrollTop( 0 );
    13291382            }
     1383        },
     1384
     1385        /**
     1386         * Recalculate the top margin.
     1387         *
     1388         * @since 4.4.0
     1389         * @private
     1390         */
     1391        _recalculateTopMargin: function() {
     1392            var panel = this, headerActionsHeight, content, accordionSection;
     1393            headerActionsHeight = $( '#customize-header-actions' ).height();
     1394            accordionSection = panel.container.closest( '.accordion-section' );
     1395            content = accordionSection.find( '.control-panel-content' );
     1396            content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - ( content.offset().top - headerActionsHeight ) ) );
    13301397        },
    13311398
  • trunk/src/wp-admin/js/customize-widgets.js

    r34883 r35231  
    15071507
    15081508    /**
     1509     * wp.customize.Widgets.WidgetsPanel
     1510     *
     1511     * Customizer panel containing the widget area sections.
     1512     *
     1513     * @since 4.4.0
     1514     */
     1515    api.Widgets.WidgetsPanel = api.Panel.extend({
     1516
     1517        /**
     1518         * Add and manage the display of the no-rendered-areas notice.
     1519         *
     1520         * @since 4.4.0
     1521         */
     1522        ready: function () {
     1523            var panel = this;
     1524
     1525            api.Panel.prototype.ready.call( panel );
     1526
     1527            panel.deferred.embedded.done(function() {
     1528                var panelMetaContainer, noRenderedAreasNotice, shouldShowNotice;
     1529                panelMetaContainer = panel.container.find( '.panel-meta' );
     1530                noRenderedAreasNotice = $( '<div></div>', {
     1531                    'class': 'no-widget-areas-rendered-notice'
     1532                });
     1533                noRenderedAreasNotice.append( $( '<em></em>', {
     1534                    text: l10n.noAreasRendered
     1535                } ) );
     1536                panelMetaContainer.append( noRenderedAreasNotice );
     1537
     1538                shouldShowNotice = function() {
     1539                    return ( 0 === _.filter( panel.sections(), function( section ) {
     1540                        return section.active();
     1541                    } ).length );
     1542                };
     1543
     1544                /*
     1545                 * Set the initial visibility state for rendered notice.
     1546                 * Update the visibility of the notice whenever a reflow happens.
     1547                 */
     1548                noRenderedAreasNotice.toggle( shouldShowNotice() );
     1549                api.previewer.deferred.active.done( function () {
     1550                    noRenderedAreasNotice.toggle( shouldShowNotice() );
     1551                });
     1552                api.bind( 'pane-contents-reflowed', function() {
     1553                    var duration = ( 'resolved' === api.previewer.deferred.active.state() ) ? 'fast' : 0;
     1554                    if ( shouldShowNotice() ) {
     1555                        noRenderedAreasNotice.slideDown( duration );
     1556                    } else {
     1557                        noRenderedAreasNotice.slideUp( duration );
     1558                    }
     1559                });
     1560            });
     1561        },
     1562
     1563        /**
     1564         * Allow an active widgets panel to be contextually active even when it has no active sections (widget areas).
     1565         *
     1566         * This ensures that the widgets panel appears even when there are no
     1567         * sidebars displayed on the URL currently being previewed.
     1568         *
     1569         * @since 4.4.0
     1570         *
     1571         * @returns {boolean}
     1572         */
     1573        isContextuallyActive: function() {
     1574            var panel = this;
     1575            return panel.active();
     1576        }
     1577    });
     1578
     1579    /**
    15091580     * wp.customize.Widgets.SidebarSection
    15101581     *
     
    19692040    } );
    19702041
    1971     // Register models for custom section and control types
     2042    // Register models for custom panel, section, and control types
     2043    $.extend( api.panelConstructor, {
     2044        widgets: api.Widgets.WidgetsPanel
     2045    });
    19722046    $.extend( api.sectionConstructor, {
    19732047        sidebar: api.Widgets.SidebarSection
  • trunk/src/wp-includes/class-wp-customize-widgets.php

    r35102 r35231  
    356356
    357357        $this->manager->add_panel( 'widgets', array(
    358             'title'       => __( 'Widgets' ),
    359             'description' => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
    360             'priority'    => 110,
     358            'type'            => 'widgets',
     359            'title'           => __( 'Widgets' ),
     360            'description'     => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
     361            'priority'        => 110,
     362            'active_callback' => array( $this, 'is_panel_active' ),
    361363        ) );
    362364
     
    453455
    454456        add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
     457    }
     458
     459    /**
     460     * Return whether the widgets panel is active, based on whether there are sidebars registered.
     461     *
     462     * @since 4.4.0
     463     * @access public
     464     *
     465     * @see WP_Customize_Panel::$active_callback
     466     *
     467     * @global array $wp_registered_sidebars
     468     * @return bool Active.
     469     */
     470    public function is_panel_active() {
     471        global $wp_registered_sidebars;
     472        return ! empty( $wp_registered_sidebars );
    455473    }
    456474
     
    656674                'widgetMovedUp'    => __( 'Widget moved up' ),
    657675                'widgetMovedDown'  => __( 'Widget moved down' ),
     676                'noAreasRendered'  => __( 'There are no widget areas currently rendered in the preview. Navigate in the preview to a template that makes use of a widget area in order to access its widgets here.' ),
    658677            ),
    659678            'tpl' => array(
  • trunk/tests/qunit/fixtures/customize-widgets.js

    r34563 r35231  
    6565    'description': 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).',
    6666    'priority': 110,
    67     'type': 'default',
     67    'type': 'widgets',
    6868    'title': 'Widgets',
    6969    'content': '',
  • trunk/tests/qunit/wp-admin/js/customize-controls.js

    r32681 r35231  
    394394    panelTitle = 'Mock Panel Title';
    395395    panelDescription = 'Mock panel description';
    396     panelContent = '<li id="accordion-panel-widgets" class="control-section control-panel accordion-section">';
     396    panelContent = '<li id="accordion-panel-mockPanelId" class="accordion-section control-section control-panel control-panel-default"> <h3 class="accordion-section-title" tabindex="0"> Fixture Panel <span class="screen-reader-text">Press return or enter to open this panel</span> </h3> <ul class="accordion-sub-container control-panel-content"> <li class="panel-meta customize-info accordion-section cannot-expand"> <button class="customize-panel-back" tabindex="-1"><span class="screen-reader-text">Back</span></button> <div class="accordion-section-title"> <span class="preview-notice">You are customizing <strong class="panel-title">Fixture Panel</strong></span> <button class="customize-help-toggle dashicons dashicons-editor-help" tabindex="0" aria-expanded="false"><span class="screen-reader-text">Help</span></button> </div> </li> </ul> </li>';
    397397    panelData = {
    398398        content: panelContent,
  • trunk/tests/qunit/wp-admin/js/customize-widgets.js

    r34563 r35231  
    3535        ok( 0 === control.container.find( '> .widget' ).length );
    3636
     37        // Preview sets the active state.
     38        section.active.set( true );
     39        control.active.set( true );
     40        api.control( 'sidebars_widgets[sidebar-1]' ).active.set( true );
     41
    3742        section.expand();
    38         ok( ! widgetAddedEvent );
    39         ok( 1 === control.container.find( '> .widget' ).length );
     43        ok( ! widgetAddedEvent, 'expected widget added event not fired' );
     44        ok( 1 === control.container.find( '> .widget' ).length, 'expected there to be one .widget element in the container' );
    4045        ok( 0 === control.container.find( '.widget-content' ).children().length );
    4146
     
    4853        $( document ).off( 'widget-added' );
    4954    });
     55
     56    test( 'widgets panel should have notice', function() {
     57        var panel = api.panel( 'widgets' );
     58        ok( panel.extended( api.Widgets.WidgetsPanel ) );
     59
     60        panel.deferred.embedded.done( function() {
     61            ok( 1 === panel.container.find( '.no-widget-areas-rendered-notice' ).length );
     62            ok( panel.container.find( '.no-widget-areas-rendered-notice' ).is( ':visible' ) );
     63            api.section( 'sidebar-widgets-sidebar-1' ).active( true );
     64            api.control( 'sidebars_widgets[sidebar-1]' ).active( true );
     65            api.trigger( 'pane-contents-reflowed' );
     66            ok( ! panel.container.find( '.no-widget-areas-rendered-notice' ).is( ':visible' ) );
     67        } );
     68
     69        expect( 4 );
     70    });
    5071});
Note: See TracChangeset for help on using the changeset viewer.