Ticket #28709: 28709.wip.5.diff
File 28709.wip.5.diff, 39.8 KB (added by , 10 years ago) |
---|
-
src/wp-admin/customize.php
diff --git src/wp-admin/customize.php src/wp-admin/customize.php index 7828ee4..109bc07 100644
do_action( 'customize_controls_init' ); 53 53 wp_enqueue_script( 'customize-controls' ); 54 54 wp_enqueue_style( 'customize-controls' ); 55 55 56 wp_enqueue_script( 'accordion' );57 58 56 /** 59 57 * Enqueue Customizer control scripts. 60 58 * … … do_action( 'customize_controls_print_scripts' ); 160 158 <?php endif; ?> 161 159 </div> 162 160 163 <div id="customize-theme-controls"><ul> 164 <?php 165 foreach ( $wp_customize->containers() as $container ) { 166 $container->maybe_render(); 167 } 168 ?> 169 </ul></div> 161 <div id="customize-theme-controls"> 162 <ul><?php // Panels and sections are managed here via JavaScript ?></ul> 163 </div> 170 164 </div> 171 165 </div> 172 166 … … do_action( 'customize_controls_print_scripts' ); 249 243 ), 250 244 'settings' => array(), 251 245 'controls' => array(), 246 'panels' => array(), 247 'sections' => array(), 252 248 'nonce' => array( 253 249 'save' => wp_create_nonce( 'save-customize_' . $wp_customize->get_stylesheet() ), 254 250 'preview' => wp_create_nonce( 'preview-customize_' . $wp_customize->get_stylesheet() ) … … do_action( 'customize_controls_print_scripts' ); 263 259 ); 264 260 } 265 261 266 // Prepare Customize Control objects to pass to Java script.262 // Prepare Customize Control objects to pass to JavaScript. 267 263 foreach ( $wp_customize->controls() as $id => $control ) { 268 $control->to_json(); 269 $settings['controls'][ $id ] = $control->json; 264 $settings['controls'][ $id ] = $control->json(); 265 } 266 267 // Prepare Customize Section objects to pass to JavaScript. 268 foreach ( $wp_customize->sections() as $id => $section ) { 269 $settings['sections'][ $id ] = $section->json(); 270 } 271 272 // Prepare Customize Panel objects to pass to JavaScript. 273 foreach ( $wp_customize->panels() as $id => $panel ) { 274 $settings['panels'][ $id ] = $panel->json(); 275 foreach ( $panel->sections as $section_id => $section ) { 276 $settings['sections'][ $section_id ] = $section->json(); 277 } 270 278 } 271 279 272 280 ?> -
src/wp-admin/js/accordion.js
diff --git src/wp-admin/js/accordion.js src/wp-admin/js/accordion.js index 6cb1c1c..63e14e8 100644
58 58 }); 59 59 }); 60 60 61 var sectionContent = $( '.accordion-section-content' );62 63 61 /** 64 62 * Close the current accordion section and open a new one. 65 63 * … … 69 67 function accordionSwitch ( el ) { 70 68 var section = el.closest( '.accordion-section' ), 71 69 siblings = section.closest( '.accordion-container' ).find( '.open' ), 72 content = section.find( sectionContent);70 content = section.find( '.accordion-section-content' ); 73 71 74 72 // This section has no content and cannot be expanded. 75 73 if ( section.hasClass( 'cannot-expand' ) ) { … … 87 85 content.toggle( true ).slideToggle( 150 ); 88 86 } else { 89 87 siblings.removeClass( 'open' ); 90 siblings.find( sectionContent).show().slideUp( 150 );88 siblings.find( '.accordion-section-content' ).show().slideUp( 150 ); 91 89 content.toggle( false ).slideToggle( 150 ); 92 90 section.toggleClass( 'open' ); 93 91 } … … 125 123 } else { 126 124 // Close all open sections in any accordion level. 127 125 siblings.removeClass( 'open' ); 128 siblings.find( sectionContent).show().slideUp( 0 );126 siblings.find( '.accordion-section-content' ).show().slideUp( 0 ); 129 127 content.show( 0, function() { 130 128 position = content.offset().top; 131 129 scroll = container.scrollTop(); -
src/wp-admin/js/customize-controls.js
diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js index fad223e..a0688a7 100644
1 1 /* globals _wpCustomizeHeader, _wpMediaViewsL10n */ 2 2 (function( exports, $ ){ 3 var api = wp.customize;3 var bubbleChildValueChanges, Container, api = wp.customize; 4 4 5 5 /** 6 6 * @constructor … … 31 31 }); 32 32 33 33 /** 34 * Watch all changes to Value properties, and bubble changes to parent Values instance 35 * 36 * @param {wp.customize.Class} instance 37 * @param {Array} properties The names of the Value instances to watch. 38 */ 39 bubbleChildValueChanges = function ( instance, properties ) { 40 $.each( properties, function ( i, key ) { 41 instance[ key ].bind( function ( to, from ) { 42 if ( instance.parent && to !== from ) { 43 instance.parent.trigger( 'change', instance ); 44 } 45 } ); 46 } ); 47 }; 48 49 /** 50 * Base class for Panel and Section 51 * 34 52 * @constructor 35 53 * @augments wp.customize.Class 36 54 */ 37 api.Control = api.Class.extend({ 38 initialize: function( id, options ) { 39 var control = this, 40 nodes, radios, settings; 55 Container = api.Class.extend({ 56 slideSpeed: 150, 41 57 42 this.params = {}; 43 $.extend( this, options || {} ); 58 initialize: function ( id, options ) { 59 var self = this; 60 self.id = id; 61 self.params = {}; 62 $.extend( self, options || {} ); 63 self.container = $( self.params.content ); 64 65 self.priority = new api.Value(); 66 self.active = new api.Value(); 67 self.expanded = new api.Value(); 68 69 self.active.bind( function ( active ) { 70 self.toggleActive( active && self.isContextuallyActive() ); 71 // @todo trigger 'activatged' and 'deactivated' events based on the expanded param? 72 }); 73 self.expanded.bind( function ( expanded ) { 74 self.toggleExpanded( expanded ); 75 // @todo trigger 'expanded' and 'collapsed' events based on the expanded param? 76 }); 44 77 45 this.id = id; 46 this.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); 47 this.container = $( this.selector ); 48 this.active = new api.Value( this.params.active ); 78 self.attachEvents(); 49 79 50 settings = $.map( this.params.settings, function( value ) { 51 return value; 80 bubbleChildValueChanges( self, [ 'priority', 'active' ] ); 81 82 self.priority.set( isNaN( self.params.priority ) ? 100 : self.params.priority ); 83 self.active.set( true ); // @todo pass from params, eventually from active_callback when defining panel/section 84 self.expanded.set( false ); // @todo True if deeplinking? 85 }, 86 87 /** 88 * Get the child models associated with this parent, sorting them by their priority Value. 89 * 90 * @param {String} parentType 91 * @param {String} childType 92 * @returns {Array} 93 */ 94 _children: function ( parentType, childType ) { 95 var parent = this, 96 children = []; 97 api[ childType ].each( function ( child ) { 98 if ( child[ parentType ].get() === parent.id ) { 99 children.push( child ); 100 } 101 } ); 102 children.sort( function ( a, b ) { 103 return a.priority() - b.priority(); 104 } ); 105 return children; 106 }, 107 108 /** 109 * To override by subclass, to return whether the container has active children. 110 */ 111 isContextuallyActive: function () { 112 throw new Error( 'Must override with subclass.' ); 113 }, 114 115 /** 116 * Handle changes to the active state. 117 * This does not change the active state, it merely handles the behavior 118 * for when it does change. 119 * 120 * To override by subclass, update the container's UI to reflect the provided active state. 121 * 122 * @param {Boolean} active 123 */ 124 toggleActive: function ( active ) { 125 if ( active ) { 126 this.container.stop( true, true ).slideDown(); 127 } else { 128 this.container.stop( true, true ).slideUp(); 129 } 130 }, 131 132 /** 133 * 134 */ 135 activate: function () { 136 this.active.set( true ); 137 }, 138 139 /** 140 * 141 */ 142 deactivate: function () { 143 this.active.set( false ); 144 }, 145 146 /** 147 * To override by subclass, update the container's UI to reflect the provided active state. 148 */ 149 toggleExpanded: function () { 150 throw new Error( 'Must override with subclass.' ); 151 }, 152 153 /** 154 * 155 */ 156 expand: function () { 157 this.expanded.set( true ); 158 }, 159 160 /** 161 * 162 */ 163 collapse: function () { 164 this.expanded( false ); 165 }, 166 167 /** 168 * 169 */ 170 focus: function () { 171 throw new Error( 'focus method must be overridden' ); 172 } 173 }); 174 175 /** 176 * @constructor 177 * @augments wp.customize.Class 178 */ 179 api.Section = Container.extend({ 180 181 /** 182 * @param {String} id 183 * @param {Array} options 184 */ 185 initialize: function ( id, options ) { 186 var section = this; 187 Container.prototype.initialize.call( section, id, options ); 188 189 section.panel = new api.Value(); 190 section.panel.bind( function ( id ) { 191 $( section.container ).toggleClass( 'control-subsection', !! id ); 52 192 }); 193 section.panel.set( section.params.panel || '' ); 194 bubbleChildValueChanges( section, [ 'panel' ] ); 195 }, 53 196 54 api.apply( api, settings.concat( function() { 55 var key; 197 /** 198 * 199 */ 200 embed: function ( readyCallback ) { 201 var panel_id, 202 section = this; 203 204 panel_id = this.panel.get(); 205 if ( ! panel_id ) { 206 $( '#customize-theme-controls > ul' ).append( section.container ); 207 readyCallback(); 208 } else { 209 api.panel( panel_id, function ( panel ) { 210 panel.embed(); 211 panel.container.find( 'ul:first' ).append( section.container ); 212 readyCallback(); 213 } ); 214 } 215 }, 56 216 57 control.settings = {}; 58 for ( key in control.params.settings ) { 59 control.settings[ key ] = api( control.params.settings[ key ] ); 217 /** 218 * Add behaviors for the accordion section 219 */ 220 attachEvents: function () { 221 var section = this; 222 223 // Expand/Collapse accordion sections on click. 224 section.container.find( '.accordion-section-title' ).on( 'click keydown', function( e ) { 225 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 226 return; 60 227 } 228 e.preventDefault(); // Keep this AFTER the key filter above 61 229 62 control.setting = control.settings['default'] || null; 63 control.ready(); 64 }) ); 230 if ( section.expanded() ) { 231 section.collapse(); 232 } else { 233 section.expand(); 234 } 235 }); 236 }, 237 238 /** 239 * Return whether this section has any active controls. 240 * 241 * @returns {boolean} 242 */ 243 isContextuallyActive: function () { 244 var section = this, 245 controls = section.controls(), 246 activeCount = 0; 247 _( controls ).each( function ( control ) { 248 if ( control.active() ) { 249 activeCount += 1; 250 } 251 } ); 252 return ( activeCount !== 0 ); 253 }, 254 255 /** 256 * Get the controls that are associated with this section, sorted by their priority Value. 257 * 258 * @returns {Array} 259 */ 260 controls: function () { 261 return this._children( 'section', 'control' ); 262 }, 263 264 /** 265 * Update UI to reflect expanded state 266 * 267 * @param {Boolean} expanded 268 */ 269 toggleExpanded: function ( expanded ) { 270 var section = this, 271 content = section.container.find( '.accordion-section-content' ); 272 273 if ( expanded ) { 274 275 if ( section.panel() ) { 276 api.panel( section.panel() ).expand(); 277 } 278 279 api.section.each( function ( otherSection ) { 280 if ( otherSection !== section ) { 281 otherSection.collapse(); 282 } 283 }); 284 285 content.stop().slideDown( section.slideSpeed ); 286 section.container.addClass( 'open' ); 287 } else { 288 289 section.container.removeClass( 'open' ); 290 content.slideUp( section.slideSpeed ); 291 } 292 }, 293 294 /** 295 * Bring the containing panel into view and then expand this section and bring it into view 296 * 297 * @todo This is an alias for expand(); do we need it? 298 */ 299 focus: function () { 300 var section = this; 301 // @todo What if it is not active? Return false? 302 section.expand(); 303 } 304 }); 305 306 /** 307 * @constructor 308 * @augments wp.customize.Class 309 */ 310 api.Panel = Container.extend({ 311 initialize: function ( id, options ) { 312 var panel = this; 313 Container.prototype.initialize.call( panel, id, options ); 314 }, 315 316 /** 317 * 318 */ 319 embed: function ( readyCallback ) { 320 $( '#customize-theme-controls > ul' ).append( this.container ); 321 if ( readyCallback ) { 322 readyCallback(); 323 } 324 }, 325 326 /** 327 * 328 */ 329 attachEvents: function () { 330 var meta, panel = this; 331 332 // Expand/Collapse accordion sections on click. 333 panel.container.find( '.accordion-section-title' ).on( 'click keydown', function( e ) { 334 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 335 return; 336 } 337 e.preventDefault(); // Keep this AFTER the key filter above 338 339 if ( ! panel.expanded() ) { 340 panel.expand(); 341 } 342 }); 343 344 meta = panel.container.find( '.panel-meta:first' ); 345 346 meta.find( '> .accordion-section-title' ).on( 'click keydown', function( e ) { 347 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 348 return; 349 } 350 e.preventDefault(); // Keep this AFTER the key filter above 351 352 if ( meta.hasClass( 'cannot-expand' ) ) { 353 return; 354 } 355 356 var content = meta.find( '.accordion-section-content:first' ); 357 if ( meta.hasClass( 'open' ) ) { 358 meta.toggleClass( 'open' ); 359 content.slideUp( 150 ); 360 } else { 361 content.slideDown( 150 ); 362 meta.toggleClass( 'open' ); 363 } 364 }); 365 366 }, 367 368 /** 369 * Get the sections that are associated with this panel, sorted by their priority Value. 370 * 371 * @returns {Array} 372 */ 373 sections: function () { 374 return this._children( 'panel', 'section' ); 375 }, 376 377 /** 378 * Return whether this section has any active sections. 379 * 380 * @returns {boolean} 381 */ 382 isContextuallyActive: function () { 383 var panel = this, 384 sections = panel.sections(), 385 activeCount = 0; 386 _( sections ).each( function ( section ) { 387 if ( section.active() && section.isContextuallyActive() ) { 388 activeCount += 1; 389 } 390 } ); 391 return ( activeCount !== 0 ); 392 }, 393 394 /** 395 * Update UI to reflect expanded state 396 * 397 * @param {Boolean} expanded 398 */ 399 toggleExpanded: function ( expanded ) { 400 var position, scroll, 401 panel = this, 402 section = panel.container.closest( '.accordion-section' ), 403 overlay = section.closest( '.wp-full-overlay' ), 404 container = section.closest( '.accordion-container' ), 405 siblings = container.find( '.open' ), 406 topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ), 407 backBtn = overlay.find( '.control-panel-back' ), 408 panelTitle = section.find( '.accordion-section-title' ).first(), 409 content = section.find( '.control-panel-content' ); 410 411 if ( expanded ) { 412 413 // Collapse any sibling sections/panels 414 api.section.each( function ( section ) { 415 if ( ! section.panel() ) { 416 section.collapse(); // @todo If any sections are open, then the position calculation below will fire too early 417 } 418 }); 419 api.panel.each( function ( otherPanel ) { 420 if ( panel !== otherPanel ) { 421 otherPanel.collapse(); // @todo the position calculation below probably will fire too early 422 } 423 }); 424 425 content.show( 0, function() { 426 position = content.offset().top; 427 scroll = container.scrollTop(); 428 content.css( 'margin-top', ( 45 - position - scroll ) ); 429 section.addClass( 'current-panel' ); 430 overlay.addClass( 'in-sub-panel' ); 431 container.scrollTop( 0 ); 432 } ); 433 topPanel.attr( 'tabindex', '-1' ); 434 backBtn.attr( 'tabindex', '0' ); 435 backBtn.focus(); 436 } else { 437 siblings.removeClass( 'open' ); 438 section.removeClass( 'current-panel' ); 439 overlay.removeClass( 'in-sub-panel' ); 440 content.delay( 180 ).hide( 0, function() { 441 content.css( 'margin-top', 'inherit' ); // Reset 442 } ); 443 topPanel.attr( 'tabindex', '0' ); 444 backBtn.attr( 'tabindex', '-1' ); 445 panelTitle.focus(); 446 container.scrollTop( 0 ); 447 } 448 }, 449 450 /** 451 * Bring the containing panel into view and then expand this section and bring it into view 452 */ 453 focus: function () { 454 var panel = this; 455 // @todo What if it is not active? Return false? 456 panel.expand(); 457 } 458 459 // @todo Need to first exit out of the Panel 460 }); 461 462 /** 463 * @constructor 464 * @augments wp.customize.Class 465 */ 466 api.Control = api.Class.extend({ 467 initialize: function( id, options ) { 468 var control = this, 469 nodes, radios, settings; 470 471 control.params = {}; 472 $.extend( control, options || {} ); 473 474 control.id = id; 475 control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); 476 control.container = control.params.content ? $( control.params.content ) : $( control.selector ); 477 478 control.section = new api.Value(); 479 control.priority = new api.Value(); 480 control.active = new api.Value(); 65 481 66 482 control.elements = []; 67 483 68 nodes = this.container.find('[data-customize-setting-link]');484 nodes = control.container.find('[data-customize-setting-link]'); 69 485 radios = {}; 70 486 71 487 nodes.each( function() { 72 var node = $( this),488 var node = $( this ), 73 489 name; 74 490 75 if ( node.is( ':radio') ) {76 name = node.prop( 'name');77 if ( radios[ name ] ) 491 if ( node.is( ':radio' ) ) { 492 name = node.prop( 'name' ); 493 if ( radios[ name ] ) { 78 494 return; 495 } 79 496 80 497 radios[ name ] = true; 81 498 node = nodes.filter( '[name="' + name + '"]' ); 82 499 } 83 500 84 api( node.data( 'customizeSettingLink'), function( setting ) {501 api( node.data( 'customizeSettingLink' ), function( setting ) { 85 502 var element = new api.Element( node ); 86 503 control.elements.push( element ); 87 504 element.sync( setting ); … … 90 507 }); 91 508 92 509 control.active.bind( function ( active ) { 93 control.toggle( active ); 510 control.toggleActive( active ); 511 } ); 512 513 control.section.set( control.params.section ); 514 control.priority.set( isNaN( control.params.priority ) ? 10 : control.params.priority ); 515 control.active.set( control.params.active ); 516 517 bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] ); 518 519 // Associate this control with its settings when they are created 520 settings = $.map( control.params.settings, function( value ) { 521 return value; 522 }); 523 api.apply( api, settings.concat( function () { 524 var key; 525 526 control.settings = {}; 527 for ( key in control.params.settings ) { 528 control.settings[ key ] = api( control.params.settings[ key ] ); 529 } 530 531 control.setting = control.settings['default'] || null; 532 control.embed( function () { 533 control.ready(); 534 }); 535 }) ); 536 }, 537 538 /** 539 * @param {Function} [readyCallback] Callback to fire when the embedding is done. 540 */ 541 embed: function ( readyCallback ) { 542 var section_id, 543 control = this; 544 545 section_id = control.section.get(); 546 if ( ! section_id ) { 547 throw new Error( 'A control must have an associated section.' ); 548 } 549 550 // Defer until the associated section is available 551 api.section( section_id, function ( section ) { 552 section.embed( function () { 553 section.container.find( 'ul:first' ).append( control.container ); 554 readyCallback(); 555 } ); 94 556 } ); 95 control.toggle( control.active() );96 557 }, 97 558 98 559 /** … … 101 562 ready: function() {}, 102 563 103 564 /** 104 * Callback for change to the control's active state. 105 * 106 * Override function for custom behavior for the control being active/inactive. 565 * Bring the containing section and panel into view and then this control into view, focusing on the first input 566 */ 567 focus: function () { 568 throw new Error( 'Not implemented yet' ); 569 }, 570 571 /** 572 * Update UI in response to a change in the control's active state. 573 * This does not change the active state, it merely handles the behavior 574 * for when it does change. 107 575 * 108 576 * @param {Boolean} active 109 577 */ 110 toggle : function ( active ) {578 toggleActive: function ( active ) { 111 579 if ( active ) { 112 580 this.container.slideDown(); 113 581 } else { … … 115 583 } 116 584 }, 117 585 586 /** 587 * @deprecated alias of toggleActive 588 */ 589 toggle: function ( active ) { 590 return this.toggleActive( active ); 591 }, 592 593 /** 594 * Shorthand way to enable the active state. 595 */ 596 activate: function () { 597 this.active.set( true ); 598 }, 599 600 /** 601 * Shorthand way to disable the active state. 602 */ 603 deactivate: function () { 604 this.active.set( false ); 605 }, 606 118 607 dropdownInit: function() { 119 608 var control = this, 120 609 statuses = this.container.find('.dropdown-status'), … … 575 1064 576 1065 // Create the collection of Control objects. 577 1066 api.control = new api.Values({ defaultConstructor: api.Control }); 1067 api.section = new api.Values({ defaultConstructor: api.Section }); 1068 api.panel = new api.Values({ defaultConstructor: api.Panel }); 578 1069 579 1070 /** 580 1071 * @constructor … … 979 1470 image: api.ImageControl, 980 1471 header: api.HeaderControl 981 1472 }; 1473 api.panelConstructor = {}; 1474 api.sectionConstructor = {}; 982 1475 983 1476 $( function() { 984 1477 api.settings = window._wpCustomizeSettings; … … 1009 1502 } 1010 1503 }); 1011 1504 1505 // Expand/Collapse the main customizer customize info 1506 $( '#customize-info' ).find( '> .accordion-section-title' ).on( 'click keydown', function( e ) { 1507 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 1508 return; 1509 } 1510 e.preventDefault(); // Keep this AFTER the key filter above 1511 1512 var section = $( this ).parent(), 1513 content = section.find( '.accordion-section-content:first' ); 1514 1515 if ( section.hasClass( 'cannot-expand' ) ) { 1516 return; 1517 } 1518 1519 if ( section.hasClass( 'open' ) ) { 1520 section.toggleClass( 'open' ); 1521 content.slideUp( 150 ); 1522 } else { 1523 content.slideDown( 150 ); 1524 section.toggleClass( 'open' ); 1525 } 1526 }); 1527 1012 1528 // Initialize Previewer 1013 1529 api.previewer = new api.Previewer({ 1014 1530 container: '#customize-preview', … … 1102 1618 $.extend( this.nonce, nonce ); 1103 1619 }); 1104 1620 1621 // Create Settings 1105 1622 $.each( api.settings.settings, function( id, data ) { 1106 1623 api.create( id, id, data.value, { 1107 1624 transport: data.transport, … … 1109 1626 } ); 1110 1627 }); 1111 1628 1629 // Create Panels 1630 $.each( api.settings.panels, function ( id, data ) { 1631 var constructor = api.panelConstructor[ data.type ] || api.Panel, 1632 panel; 1633 1634 panel = new constructor( id, { 1635 params: data 1636 } ); 1637 api.panel.add( id, panel ); 1638 }); 1639 1640 // Create Sections 1641 $.each( api.settings.sections, function ( id, data ) { 1642 var constructor = api.sectionConstructor[ data.type ] || api.Section, 1643 section; 1644 1645 section = new constructor( id, { 1646 params: data 1647 } ); 1648 api.section.add( id, section ); 1649 }); 1650 1651 // Create Controls 1652 // @todo factor this out 1112 1653 $.each( api.settings.controls, function( id, data ) { 1113 1654 var constructor = api.controlConstructor[ data.type ] || api.Control, 1114 1655 control; 1115 1656 1116 control = api.control.add( id,new constructor( id, {1657 control = new constructor( id, { 1117 1658 params: data, 1118 1659 previewer: api.previewer 1119 } ) ); 1660 } ); 1661 api.control.add( id, control ); 1120 1662 }); 1121 1663 1664 /** 1665 * Sort panels, sections, controls by priorities. Hide empty sections and panels. 1666 */ 1667 api.reflowPaneContents = _.bind( function () { 1668 1669 var appendContainer, activeElement, rootNodes = []; 1670 1671 if ( document.activeElement ) { 1672 activeElement = $( document.activeElement ); 1673 } 1674 1675 api.panel.each( function ( panel ) { 1676 var sections = panel.sections(); 1677 rootNodes.push( panel ); 1678 appendContainer = panel.container.find( 'ul:first' ); 1679 // @todo Skip doing any DOM manipulation if the ordering is already correct 1680 _( sections ).each( function ( section ) { 1681 appendContainer.append( section.container ); 1682 } ); 1683 } ); 1684 1685 api.section.each( function ( section ) { 1686 var controls = section.controls(); 1687 if ( ! section.panel() ) { 1688 rootNodes.push( section ); 1689 } 1690 appendContainer = section.container.find( 'ul:first' ); 1691 // @todo Skip doing any DOM manipulation if the ordering is already correct 1692 _( controls ).each( function ( control ) { 1693 appendContainer.append( control.container ); 1694 } ); 1695 } ); 1696 1697 // Sort the root elements 1698 rootNodes.sort( function ( a, b ) { 1699 return a.priority() - b.priority(); 1700 } ); 1701 appendContainer = $( '#customize-theme-controls > ul' ); 1702 // @todo Skip doing any DOM manipulation if the ordering is already correct 1703 _( rootNodes ).each( function ( rootNode ) { 1704 appendContainer.append( rootNode.container ); 1705 } ); 1706 1707 // Now re-trigger the active Value callbacks to that the panels and sections can decide whether they can be rendered 1708 api.panel.each( function ( panel ) { 1709 var value = panel.active(); 1710 panel.active.callbacks.fireWith( panel.active, [ value, value ] ); 1711 } ); 1712 api.section.each( function ( section ) { 1713 var value = section.active(); 1714 section.active.callbacks.fireWith( section.active, [ value, value ] ); 1715 } ); 1716 1717 if ( activeElement ) { 1718 activeElement.focus(); 1719 } 1720 }, api ); 1721 api.reflowPaneContents = _.debounce( api.reflowPaneContents, 100 ); 1722 $( [ api.panel, api.section, api.control ] ).each( function ( i, values ) { 1723 values.bind( 'add', api.reflowPaneContents ); 1724 values.bind( 'change', api.reflowPaneContents ); 1725 values.bind( 'remove', api.reflowPaneContents ); 1726 } ); 1727 api.bind( 'ready', api.reflowPaneContents ); 1728 1122 1729 // Check if preview url is valid and load the preview frame. 1123 1730 if ( api.previewer.previewUrl() ) { 1124 1731 api.previewer.refresh(); … … 1183 1790 event.preventDefault(); 1184 1791 }); 1185 1792 1793 // Go back to the top-level Customizer accordion. 1794 $( '#customize-header-actions' ).on( 'click keydown', '.control-panel-back', function( e ) { 1795 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 1796 return; 1797 } 1798 1799 e.preventDefault(); // Keep this AFTER the key filter above 1800 api.panel.each( function ( panel ) { 1801 panel.collapse(); 1802 }); 1803 }); 1804 1186 1805 closeBtn.keydown( function( event ) { 1187 1806 if ( 9 === event.which ) // tab 1188 1807 return; -
src/wp-admin/js/customize-widgets.js
diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js index 6be9a08..60ed1c1 100644
404 404 * @augments wp.customize.Control 405 405 */ 406 406 api.Widgets.WidgetControl = api.Control.extend({ 407 408 initialize: function ( id, options ) { 409 var control = this; 410 api.Control.prototype.initialize.call( control, id, options ); 411 control.expanded = new api.Value(); 412 control.expanded.bind( function ( expanded ) { 413 control.toggleExpanded( expanded ); 414 }); 415 control.expanded.set( false ); 416 }, 417 407 418 /** 408 419 * Set up the control 409 420 */ … … 529 540 if ( sidebarWidgetsControl.isReordering ) { 530 541 return; 531 542 } 532 self. toggleForm();543 self.expanded( ! self.expanded() ); 533 544 } ); 534 545 535 546 $closeBtn = this.container.find( '.widget-control-close' ); 536 547 $closeBtn.on( 'click', function( e ) { 537 548 e.preventDefault(); 538 self.collapse Form();549 self.collapse(); 539 550 self.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility 540 551 } ); 541 552 }, … … 778 789 * 779 790 * @param {Boolean} active 780 791 */ 781 toggle : function ( active ) {792 toggleActive: function ( active ) { 782 793 this.container.toggleClass( 'widget-rendered', active ); 783 794 }, 784 795 … … 1101 1112 * Expand the accordion section containing a control 1102 1113 */ 1103 1114 expandControlSection: function() { 1104 var $section = this.container.closest( '.accordion-section' ); 1115 api.section( this.section() ).expand(); 1116 }, 1105 1117 1106 if ( ! $section.hasClass( 'open' ) ) { 1107 $section.find( '.accordion-section-title:first' ).trigger( 'click' ); 1108 } 1118 /** 1119 * Expand the widget form control 1120 */ 1121 expand: function () { 1122 this.expanded( true ); 1109 1123 }, 1110 1124 1111 1125 /** 1112 1126 * Expand the widget form control 1127 * 1128 * @deprecated alias of expand() 1113 1129 */ 1114 1130 expandForm: function() { 1115 this. toggleForm( true);1131 this.expand(); 1116 1132 }, 1117 1133 1118 1134 /** 1119 1135 * Collapse the widget form control 1120 1136 */ 1137 collapse: function () { 1138 this.expanded( false ); 1139 }, 1140 1141 /** 1142 * Collapse the widget form control 1143 * 1144 * @deprecated alias of expand() 1145 */ 1121 1146 collapseForm: function() { 1122 this. toggleForm( false);1147 this.collapse(); 1123 1148 }, 1124 1149 1125 1150 /** 1126 1151 * Expand or collapse the widget control 1127 1152 * 1153 * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide ) 1154 * 1128 1155 * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility 1129 1156 */ 1130 1157 toggleForm: function( showOrHide ) { 1131 var self = this, $widget, $inside, complete; 1158 if ( typeof showOrHide === 'undefined' ) { 1159 showOrHide = ! this.expanded(); 1160 } 1161 this.expanded( showOrHide ); 1162 }, 1132 1163 1164 /** 1165 * Respond to change in the expanded state. 1166 * 1167 * @param {Boolean} expanded 1168 */ 1169 toggleExpanded: function ( expanded ) { 1170 1171 var self = this, $widget, $inside, complete; 1133 1172 $widget = this.container.find( 'div.widget:first' ); 1134 1173 $inside = $widget.find( '.widget-inside:first' ); 1135 if ( typeof showOrHide === 'undefined' ) {1136 showOrHide = ! $inside.is( ':visible' );1137 }1138 1174 1139 // Already expanded or collapsed, so noop 1140 if ( $inside.is( ':visible' ) === showOrHide ) { 1141 return; 1142 } 1175 if ( expanded ) { 1176 1177 self.expandControlSection(); 1143 1178 1144 if ( showOrHide ) {1145 1179 // Close all other widget controls before expanding this one 1146 1180 api.control.each( function( otherControl ) { 1147 1181 if ( self.params.type === otherControl.params.type && self !== otherControl ) { 1148 otherControl.collapse Form();1182 otherControl.collapse(); 1149 1183 } 1150 1184 } ); 1151 1185 … … 1164 1198 self.container.trigger( 'expand' ); 1165 1199 self.container.addClass( 'expanding' ); 1166 1200 } else { 1201 1167 1202 complete = function() { 1168 1203 self.container.removeClass( 'collapsing' ); 1169 1204 self.container.removeClass( 'expanded' ); … … 1189 1224 * the first input in the control 1190 1225 */ 1191 1226 focus: function() { 1192 this.expandControlSection(); 1193 this.expandForm(); 1227 this.expand(); 1194 1228 this.container.find( '.widget-content :focusable:first' ).focus(); 1195 1229 }, 1196 1230 … … 1325 1359 registeredSidebar = api.Widgets.registeredSidebars.get( this.params.sidebar_id ); 1326 1360 1327 1361 this.setting.bind( function( newWidgetIds, oldWidgetIds ) { 1328 var widgetFormControls, $sidebarWidgetsAddControl, finalControlContainers, removedWidgetIds;1362 var widgetFormControls, removedWidgetIds, priority; 1329 1363 1330 1364 removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds ); 1331 1365 … … 1350 1384 widgetFormControls.sort( function( a, b ) { 1351 1385 var aIndex = _.indexOf( newWidgetIds, a.params.widget_id ), 1352 1386 bIndex = _.indexOf( newWidgetIds, b.params.widget_id ); 1387 return aIndex - bIndex; 1388 }); 1353 1389 1354 if ( aIndex === bIndex ) { 1355 return 0; 1356 } 1357 1358 return aIndex < bIndex ? -1 : 1; 1359 } ); 1360 1361 // Append the controls to put them in the right order 1362 finalControlContainers = _( widgetFormControls ).map( function( widgetFormControls ) { 1363 return widgetFormControls.container[0]; 1364 } ); 1365 1366 $sidebarWidgetsAddControl = self.$sectionContent.find( '.customize-control-sidebar_widgets' ); 1367 $sidebarWidgetsAddControl.before( finalControlContainers ); 1390 priority = 0; 1391 _( widgetFormControls ).each( function ( control ) { 1392 control.priority( priority ); 1393 control.section( self.section() ); 1394 priority += 1; 1395 }); 1396 self.priority( priority ); // Make sure sidebar control remains at end 1368 1397 1369 1398 // Re-sort widget form controls (including widgets form other sidebars newly moved here) 1370 1399 self._applyCardinalOrderClassNames(); … … 1438 1467 }, 1439 1468 1440 1469 /** 1441 * Show the sidebar section when it becomes visible.1470 * Respond to change in active state to show the sidebar section. 1442 1471 * 1443 * Overrides api.Control.toggle ()1472 * Overrides api.Control.toggleActive() 1444 1473 * 1445 1474 * @param {Boolean} active 1446 1475 */ 1447 toggle: function ( active ) { 1476 toggleActive: function ( active ) { 1477 // @todo this seems wrong. It seems we should be linking this.active with api.section( this.section.get() ).active 1478 1448 1479 var $section, sectionSelector; 1449 1480 1450 1481 sectionSelector = '#accordion-section-sidebar-widgets-' + this.params.sidebar_id; … … 1584 1615 1585 1616 if ( showOrHide ) { 1586 1617 _( this.getWidgetFormControls() ).each( function( formControl ) { 1587 formControl.collapse Form();1618 formControl.collapse(); 1588 1619 } ); 1589 1620 1590 1621 this.$sectionContent.find( '.first-widget .move-widget' ).focus(); … … 1651 1682 1652 1683 $widget = $( controlHtml ); 1653 1684 1685 // @todo need to pass this in as the control's 'content' property 1654 1686 $control = $( '<li/>' ) 1655 1687 .addClass( 'customize-control' ) 1656 1688 .addClass( 'customize-control-' + controlType ) … … 1674 1706 } 1675 1707 $control.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); 1676 1708 1709 // @todo Eliminate this 1677 1710 this.container.after( $control ); 1678 1711 1679 1712 // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) … … 1733 1766 1734 1767 $control.slideDown( function() { 1735 1768 if ( isExistingWidget ) { 1736 widgetFormControl.expand Form();1769 widgetFormControl.expand(); 1737 1770 widgetFormControl.updateWidget( { 1738 1771 instance: widgetFormControl.setting(), 1739 1772 complete: function( error ) { -
src/wp-includes/class-wp-customize-control.php
diff --git src/wp-includes/class-wp-customize-control.php src/wp-includes/class-wp-customize-control.php index 7937d2d..47d925b 100644
class WP_Customize_Control { 74 74 public $input_attrs = array(); 75 75 76 76 /** 77 * @deprecated It is better to just call the json() method 77 78 * @access public 78 79 * @var array 79 80 */ … … class WP_Customize_Control { 218 219 } 219 220 220 221 $this->json['type'] = $this->type; 222 $this->json['priority'] = $this->priority; 221 223 $this->json['active'] = $this->active(); 224 $this->json['section'] = $this->section; 225 $this->json['content'] = $this->get_content(); 226 } 227 228 /** 229 * Get the data to export to the client via JSON. 230 * 231 * @since 4.1.0 232 * 233 * @return array 234 */ 235 public function json() { 236 $this->to_json(); 237 return $this->json; 222 238 } 223 239 224 240 /** … … class WP_Customize_Control { 242 258 } 243 259 244 260 /** 261 * Get the control's content for insertion into the Customizer pane. 262 * 263 * @since 4.1.0 264 * 265 * @return string 266 */ 267 public final function get_content() { 268 ob_start(); 269 $this->maybe_render(); 270 $template = trim( ob_get_contents() ); 271 ob_end_clean(); 272 return $template; 273 } 274 275 /** 245 276 * Check capabilities and render the control. 246 277 * 247 278 * @since 3.4.0 -
src/wp-includes/class-wp-customize-manager.php
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php index febd8bc..5041f82 100644
final class WP_Customize_Manager { 878 878 879 879 if ( ! $section->panel ) { 880 880 // Top-level section. 881 $sections[ ] = $section;881 $sections[ $section->id ] = $section; 882 882 } else { 883 883 // This section belongs to a panel. 884 884 if ( isset( $this->panels [ $section->panel ] ) ) { 885 $this->panels[ $section->panel ]->sections[ ] = $section;885 $this->panels[ $section->panel ]->sections[ $section->id ] = $section; 886 886 } 887 887 } 888 888 } … … final class WP_Customize_Manager { 899 899 continue; 900 900 } 901 901 902 u sort( $panel->sections, array( $this, '_cmp_priority' ) );903 $panels[ ] = $panel;902 uasort( $panel->sections, array( $this, '_cmp_priority' ) ); 903 $panels[ $panel->id ] = $panel; 904 904 } 905 905 $this->panels = $panels; 906 906 -
src/wp-includes/class-wp-customize-panel.php
diff --git src/wp-includes/class-wp-customize-panel.php src/wp-includes/class-wp-customize-panel.php index f289cb7..9ba0295 100644
class WP_Customize_Panel { 83 83 public $sections; 84 84 85 85 /** 86 * @since 4.1.0 87 * @access public 88 * @var string 89 */ 90 public $type; 91 92 /** 86 93 * Constructor. 87 94 * 88 95 * Any supplied $args override class property defaults. … … class WP_Customize_Panel { 110 117 } 111 118 112 119 /** 120 * Gather the parameters passed to client JavaScript via JSON. 121 * 122 * @since 4.1.0 123 * 124 * @return array The array to be exported to the client as JSON 125 */ 126 public function json() { 127 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'type' ) ); 128 $array['content'] = $this->get_content(); 129 return $array; 130 } 131 132 /** 113 133 * Checks required user capabilities and whether the theme has the 114 134 * feature support required by the panel. 115 135 * … … class WP_Customize_Panel { 130 150 } 131 151 132 152 /** 153 * Get the panel's content template for insertion into the Customizer pane. 154 * 155 * @since 4.1.0 156 * 157 * @return string 158 */ 159 public final function get_content() { 160 ob_start(); 161 $this->maybe_render(); 162 $template = trim( ob_get_contents() ); 163 ob_end_clean(); 164 return $template; 165 } 166 167 /** 133 168 * Check capabilities and render the panel. 134 169 * 135 170 * @since 4.0.0 … … class WP_Customize_Panel { 189 224 */ 190 225 protected function render_content() { 191 226 ?> 192 <li class=" accordion-section control-section<?php if ( empty( $this->description ) ) echo ' cannot-expand';?>">227 <li class="panel-meta accordion-section control-section<?php if ( empty( $this->description ) ) { echo ' cannot-expand'; } ?>"> 193 228 <div class="accordion-section-title" tabindex="0"> 194 229 <span class="preview-notice"><?php 195 230 /* translators: %s is the site/panel title in the Customizer */ … … class WP_Customize_Panel { 203 238 <?php endif; ?> 204 239 </li> 205 240 <?php 206 foreach ( $this->sections as $section ) {207 $section->maybe_render();208 }209 241 } 210 242 } -
src/wp-includes/class-wp-customize-section.php
diff --git src/wp-includes/class-wp-customize-section.php src/wp-includes/class-wp-customize-section.php index d740ddb..a905f32 100644
class WP_Customize_Section { 92 92 public $controls; 93 93 94 94 /** 95 * @since 4.1.0 96 * @access public 97 * @var string 98 */ 99 public $type; 100 101 /** 95 102 * Constructor. 96 103 * 97 104 * Any supplied $args override class property defaults. … … class WP_Customize_Section { 118 125 } 119 126 120 127 /** 128 * Gather the parameters passed to client JavaScript via JSON. 129 * 130 * @since 4.1.0 131 * 132 * @return array The array to be exported to the client as JSON 133 */ 134 public function json() { 135 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'panel', 'type' ) ); 136 $array['content'] = $this->get_content(); 137 return $array; 138 } 139 140 /** 121 141 * Checks required user capabilities and whether the theme has the 122 142 * feature support required by the section. 123 143 * … … class WP_Customize_Section { 136 156 } 137 157 138 158 /** 159 * Get the section's content template for insertion into the Customizer pane. 160 * 161 * @since 4.1.0 162 * 163 * @return string 164 */ 165 public final function get_content() { 166 ob_start(); 167 $this->maybe_render(); 168 $template = trim( ob_get_contents() ); 169 ob_end_clean(); 170 return $template; 171 } 172 173 /** 139 174 * Check capabilities and render the section. 140 175 * 141 176 * @since 3.4.0 … … class WP_Customize_Section { 172 207 */ 173 208 protected function render() { 174 209 $classes = 'control-section accordion-section'; 175 if ( $this->panel ) {176 $classes .= ' control-subsection';177 }178 210 ?> 179 211 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>"> 180 212 <h3 class="accordion-section-title" tabindex="0"> … … class WP_Customize_Section { 183 215 </h3> 184 216 <ul class="accordion-section-content"> 185 217 <?php if ( ! empty( $this->description ) ) : ?> 186 <li><p class="description customize-section-description"><?php echo $this->description; ?></p></li> 218 <li class="customize-section-description-container"> 219 <p class="description customize-section-description"><?php echo $this->description; ?></p> 220 </li> 187 221 <?php endif; ?> 188 <?php189 foreach ( $this->controls as $control )190 $control->maybe_render();191 ?>192 222 </ul> 193 223 </li> 194 224 <?php -
src/wp-includes/js/customize-base.js
diff --git src/wp-includes/js/customize-base.js src/wp-includes/js/customize-base.js index d2488dd..6ea2822 100644
window.wp = window.wp || {}; 184 184 to = this.validate( to ); 185 185 186 186 // Bail if the sanitized value is null or unchanged. 187 if ( null === to || _.isEqual( from, to ) ) 187 if ( null === to || _.isEqual( from, to ) ) { 188 188 return this; 189 } 189 190 190 191 this._value = to; 191 192 this._dirty = true;