Ticket #28709: 28709.wip.6.diff
File 28709.wip.6.diff, 52.5 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..31593ef 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( self.params.active ); 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 … … 610 1101 loaded = false, 611 1102 ready = false; 612 1103 613 if ( this._ready ) 1104 if ( this._ready ) { 614 1105 this.unbind( 'ready', this._ready ); 1106 } 615 1107 616 1108 this._ready = function() { 617 1109 ready = true; 618 1110 619 if ( loaded ) 1111 if ( loaded ) { 620 1112 deferred.resolveWith( self ); 1113 } 621 1114 }; 622 1115 623 1116 this.bind( 'ready', this._ready ); 624 1117 625 1118 this.bind( 'ready', function ( data ) { 626 if ( ! data || ! data.activeControls) {1119 if ( ! data ) { 627 1120 return; 628 1121 } 629 1122 630 $.each( data.activeControls, function ( id, active ) { 631 var control = api.control( id ); 632 if ( control ) { 633 control.active( active ); 1123 var constructs = { 1124 panel: data.activePanels, 1125 section: data.activeSections, 1126 control: data.activeControls 1127 }; 1128 1129 $.each( constructs, function ( type, activeConstructs ) { 1130 if ( activeConstructs ) { 1131 $.each( activeConstructs, function ( id, active ) { 1132 var construct = api[ type ]( id ); 1133 if ( construct ) { 1134 construct.active( active ); 1135 } 1136 } ); 634 1137 } 635 1138 } ); 1139 636 1140 } ); 637 1141 638 1142 this.request = $.ajax( this.previewUrl(), { … … 654 1158 655 1159 // Check if the location response header differs from the current URL. 656 1160 // If so, the request was redirected; try loading the requested page. 657 if ( location && location != self.previewUrl() ) {1161 if ( location && location !== self.previewUrl() ) { 658 1162 deferred.rejectWith( self, [ 'redirect', location ] ); 659 1163 return; 660 1164 } … … 979 1483 image: api.ImageControl, 980 1484 header: api.HeaderControl 981 1485 }; 1486 api.panelConstructor = {}; 1487 api.sectionConstructor = {}; 982 1488 983 1489 $( function() { 984 1490 api.settings = window._wpCustomizeSettings; … … 1009 1515 } 1010 1516 }); 1011 1517 1518 // Expand/Collapse the main customizer customize info 1519 $( '#customize-info' ).find( '> .accordion-section-title' ).on( 'click keydown', function( e ) { 1520 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 1521 return; 1522 } 1523 e.preventDefault(); // Keep this AFTER the key filter above 1524 1525 var section = $( this ).parent(), 1526 content = section.find( '.accordion-section-content:first' ); 1527 1528 if ( section.hasClass( 'cannot-expand' ) ) { 1529 return; 1530 } 1531 1532 if ( section.hasClass( 'open' ) ) { 1533 section.toggleClass( 'open' ); 1534 content.slideUp( 150 ); 1535 } else { 1536 content.slideDown( 150 ); 1537 section.toggleClass( 'open' ); 1538 } 1539 }); 1540 1012 1541 // Initialize Previewer 1013 1542 api.previewer = new api.Previewer({ 1014 1543 container: '#customize-preview', … … 1102 1631 $.extend( this.nonce, nonce ); 1103 1632 }); 1104 1633 1634 // Create Settings 1105 1635 $.each( api.settings.settings, function( id, data ) { 1106 1636 api.create( id, id, data.value, { 1107 1637 transport: data.transport, … … 1109 1639 } ); 1110 1640 }); 1111 1641 1642 // Create Panels 1643 $.each( api.settings.panels, function ( id, data ) { 1644 var constructor = api.panelConstructor[ data.type ] || api.Panel, 1645 panel; 1646 1647 panel = new constructor( id, { 1648 params: data 1649 } ); 1650 api.panel.add( id, panel ); 1651 }); 1652 1653 // Create Sections 1654 $.each( api.settings.sections, function ( id, data ) { 1655 var constructor = api.sectionConstructor[ data.type ] || api.Section, 1656 section; 1657 1658 section = new constructor( id, { 1659 params: data 1660 } ); 1661 api.section.add( id, section ); 1662 }); 1663 1664 // Create Controls 1665 // @todo factor this out 1112 1666 $.each( api.settings.controls, function( id, data ) { 1113 1667 var constructor = api.controlConstructor[ data.type ] || api.Control, 1114 1668 control; 1115 1669 1116 control = api.control.add( id,new constructor( id, {1670 control = new constructor( id, { 1117 1671 params: data, 1118 1672 previewer: api.previewer 1119 } ) ); 1673 } ); 1674 api.control.add( id, control ); 1120 1675 }); 1121 1676 1677 /** 1678 * Sort panels, sections, controls by priorities. Hide empty sections and panels. 1679 */ 1680 api.reflowPaneContents = _.bind( function () { 1681 1682 var appendContainer, activeElement, rootNodes = []; 1683 1684 if ( document.activeElement ) { 1685 activeElement = $( document.activeElement ); 1686 } 1687 1688 api.panel.each( function ( panel ) { 1689 var sections = panel.sections(); 1690 rootNodes.push( panel ); 1691 appendContainer = panel.container.find( 'ul:first' ); 1692 // @todo Skip doing any DOM manipulation if the ordering is already correct 1693 _( sections ).each( function ( section ) { 1694 appendContainer.append( section.container ); 1695 } ); 1696 } ); 1697 1698 api.section.each( function ( section ) { 1699 var controls = section.controls(); 1700 if ( ! section.panel() ) { 1701 rootNodes.push( section ); 1702 } 1703 appendContainer = section.container.find( 'ul:first' ); 1704 // @todo Skip doing any DOM manipulation if the ordering is already correct 1705 _( controls ).each( function ( control ) { 1706 appendContainer.append( control.container ); 1707 } ); 1708 } ); 1709 1710 // Sort the root elements 1711 rootNodes.sort( function ( a, b ) { 1712 return a.priority() - b.priority(); 1713 } ); 1714 appendContainer = $( '#customize-theme-controls > ul' ); 1715 // @todo Skip doing any DOM manipulation if the ordering is already correct 1716 _( rootNodes ).each( function ( rootNode ) { 1717 appendContainer.append( rootNode.container ); 1718 } ); 1719 1720 // Now re-trigger the active Value callbacks to that the panels and sections can decide whether they can be rendered 1721 api.panel.each( function ( panel ) { 1722 var value = panel.active(); 1723 panel.active.callbacks.fireWith( panel.active, [ value, value ] ); 1724 } ); 1725 api.section.each( function ( section ) { 1726 var value = section.active(); 1727 section.active.callbacks.fireWith( section.active, [ value, value ] ); 1728 } ); 1729 1730 if ( activeElement ) { 1731 activeElement.focus(); 1732 } 1733 }, api ); 1734 api.reflowPaneContents = _.debounce( api.reflowPaneContents, 100 ); 1735 $( [ api.panel, api.section, api.control ] ).each( function ( i, values ) { 1736 values.bind( 'add', api.reflowPaneContents ); 1737 values.bind( 'change', api.reflowPaneContents ); 1738 values.bind( 'remove', api.reflowPaneContents ); 1739 } ); 1740 api.bind( 'ready', api.reflowPaneContents ); 1741 1122 1742 // Check if preview url is valid and load the preview frame. 1123 1743 if ( api.previewer.previewUrl() ) { 1124 1744 api.previewer.refresh(); … … 1183 1803 event.preventDefault(); 1184 1804 }); 1185 1805 1806 // Go back to the top-level Customizer accordion. 1807 $( '#customize-header-actions' ).on( 'click keydown', '.control-panel-back', function( e ) { 1808 if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key 1809 return; 1810 } 1811 1812 e.preventDefault(); // Keep this AFTER the key filter above 1813 api.panel.each( function ( panel ) { 1814 panel.collapse(); 1815 }); 1816 }); 1817 1186 1818 closeBtn.keydown( function( event ) { 1187 1819 if ( 9 === event.which ) // tab 1188 1820 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..9e80fa5 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(); 1132 }, 1133 1134 /** 1135 * Collapse the widget form control 1136 */ 1137 collapse: function () { 1138 this.expanded( false ); 1116 1139 }, 1117 1140 1118 1141 /** 1119 1142 * Collapse the widget form control 1143 * 1144 * @deprecated alias of expand() 1120 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(); … … 1434 1463 // Update the model with whether or not the sidebar is rendered 1435 1464 self.active.bind( function ( active ) { 1436 1465 registeredSidebar.set( 'is_rendered', active ); 1466 api.section( self.section.get() ).active( active ); 1437 1467 } ); 1438 }, 1439 1440 /** 1441 * Show the sidebar section when it becomes visible. 1442 * 1443 * Overrides api.Control.toggle() 1444 * 1445 * @param {Boolean} active 1446 */ 1447 toggle: function ( active ) { 1448 var $section, sectionSelector; 1449 1450 sectionSelector = '#accordion-section-sidebar-widgets-' + this.params.sidebar_id; 1451 $section = $( sectionSelector ); 1452 1453 if ( active ) { 1454 $section.stop().slideDown( function() { 1455 $( this ).css( 'height', 'auto' ); // so that the .accordion-section-content won't overflow 1456 } ); 1457 1458 } else { 1459 // Make sure that hidden sections get closed first 1460 if ( $section.hasClass( 'open' ) ) { 1461 // it would be nice if accordionSwitch() in accordion.js was public 1462 $section.find( '.accordion-section-title' ).trigger( 'click' ); 1463 } 1464 1465 $section.stop().slideUp(); 1466 } 1468 api.section( self.section.get() ).active( self.active() ); 1467 1469 }, 1468 1470 1469 1471 /** … … 1496 1498 /** 1497 1499 * Expand other Customizer sidebar section when dragging a control widget over it, 1498 1500 * allowing the control to be dropped into another section 1501 * 1502 * @todo The wp.customize.Section API should accomodate forcing a single accordion open 1499 1503 */ 1500 1504 this.$controlSection.find( '.accordion-section-title' ).droppable({ 1501 1505 accept: '.customize-control-widget_form', … … 1584 1588 1585 1589 if ( showOrHide ) { 1586 1590 _( this.getWidgetFormControls() ).each( function( formControl ) { 1587 formControl.collapse Form();1591 formControl.collapse(); 1588 1592 } ); 1589 1593 1590 1594 this.$sectionContent.find( '.first-widget .move-widget' ).focus(); … … 1651 1655 1652 1656 $widget = $( controlHtml ); 1653 1657 1658 // @todo need to pass this in as the control's 'content' property 1654 1659 $control = $( '<li/>' ) 1655 1660 .addClass( 'customize-control' ) 1656 1661 .addClass( 'customize-control-' + controlType ) … … 1674 1679 } 1675 1680 $control.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); 1676 1681 1682 // @todo Eliminate this 1677 1683 this.container.after( $control ); 1678 1684 1679 1685 // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget) … … 1733 1739 1734 1740 $control.slideDown( function() { 1735 1741 if ( isExistingWidget ) { 1736 widgetFormControl.expand Form();1742 widgetFormControl.expand(); 1737 1743 widgetFormControl.updateWidget( { 1738 1744 instance: widgetFormControl.setting(), 1739 1745 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..129a85f 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 … … class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control { 1031 1062 /** 1032 1063 * Widget Area Customize Control Class 1033 1064 * 1065 * @since 3.9.0 1034 1066 */ 1035 1067 class WP_Widget_Area_Customize_Control extends WP_Customize_Control { 1036 1068 public $type = 'sidebar_widgets'; … … class WP_Widget_Area_Customize_Control extends WP_Customize_Control { 1072 1104 1073 1105 /** 1074 1106 * Widget Form Customize Control Class 1107 * 1108 * @since 3.9.0 1075 1109 */ 1076 1110 class WP_Widget_Form_Customize_Control extends WP_Customize_Control { 1077 1111 public $type = 'widget_form'; -
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..ac70bdb 100644
final class WP_Customize_Manager { 491 491 $settings = array( 492 492 'values' => array(), 493 493 'channel' => wp_unslash( $_POST['customize_messenger_channel'] ), 494 'activePanels' => array(), 495 'activeSections' => array(), 494 496 'activeControls' => array(), 495 497 ); 496 498 … … final class WP_Customize_Manager { 504 506 foreach ( $this->settings as $id => $setting ) { 505 507 $settings['values'][ $id ] = $setting->js_value(); 506 508 } 509 foreach ( $this->panels as $id => $panel ) { 510 $settings['activePanels'][ $id ] = $panel->active(); 511 } 512 foreach ( $this->sections as $id => $section ) { 513 $settings['activeSections'][ $id ] = $section->active(); 514 } 507 515 foreach ( $this->controls as $id => $control ) { 508 516 $settings['activeControls'][ $id ] = $control->active(); 509 517 } … … final class WP_Customize_Manager { 878 886 879 887 if ( ! $section->panel ) { 880 888 // Top-level section. 881 $sections[ ] = $section;889 $sections[ $section->id ] = $section; 882 890 } else { 883 891 // This section belongs to a panel. 884 892 if ( isset( $this->panels [ $section->panel ] ) ) { 885 $this->panels[ $section->panel ]->sections[ ] = $section;893 $this->panels[ $section->panel ]->sections[ $section->id ] = $section; 886 894 } 887 895 } 888 896 } … … final class WP_Customize_Manager { 899 907 continue; 900 908 } 901 909 902 u sort( $panel->sections, array( $this, '_cmp_priority' ) );903 $panels[ ] = $panel;910 uasort( $panel->sections, array( $this, '_cmp_priority' ) ); 911 $panels[ $panel->id ] = $panel; 904 912 } 905 913 $this->panels = $panels; 906 914 -
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..201c4b9 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 /** 93 * Callback. 94 * 95 * @since 4.1.0 96 * @access public 97 * 98 * @see WP_Customize_Section::active() 99 * 100 * @var callable Callback is called with one argument, the instance of 101 * WP_Customize_Section, and returns bool to indicate whether 102 * the section is active (such as it relates to the URL 103 * currently being previewed). 104 */ 105 public $active_callback = ''; 106 107 /** 86 108 * Constructor. 87 109 * 88 110 * Any supplied $args override class property defaults. … … class WP_Customize_Panel { 103 125 104 126 $this->manager = $manager; 105 127 $this->id = $id; 128 if ( empty( $this->active_callback ) ) { 129 $this->active_callback = array( $this, 'active_callback' ); 130 } 106 131 107 132 $this->sections = array(); // Users cannot customize the $sections array. 108 133 … … class WP_Customize_Panel { 110 135 } 111 136 112 137 /** 138 * Check whether panel is active to current Customizer preview. 139 * 140 * @since 4.1.0 141 * @access public 142 * 143 * @return bool Whether the panel is active to the current preview. 144 */ 145 public final function active() { 146 $panel = $this; 147 $active = call_user_func( $this->active_callback, $this ); 148 149 /** 150 * Filter response of WP_Customize_Panel::active(). 151 * 152 * @since 4.1.0 153 * 154 * @param bool $active Whether the Customizer panel is active. 155 * @param WP_Customize_Panel $panel WP_Customize_Panel instance. 156 */ 157 $active = apply_filters( 'customize_panel_active', $active, $panel ); 158 159 return $active; 160 } 161 162 /** 163 * Default callback used when invoking WP_Customize_Panel::active(). 164 * 165 * Subclasses can override this with their specific logic, or they may 166 * provide an 'active_callback' argument to the constructor. 167 * 168 * @since 4.1.0 169 * @access public 170 * 171 * @return bool Always true. 172 */ 173 public function active_callback() { 174 return true; 175 } 176 177 /** 178 * Gather the parameters passed to client JavaScript via JSON. 179 * 180 * @since 4.1.0 181 * 182 * @return array The array to be exported to the client as JSON 183 */ 184 public function json() { 185 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'type' ) ); 186 $array['content'] = $this->get_content(); 187 $array['active'] = $this->active(); 188 return $array; 189 } 190 191 /** 113 192 * Checks required user capabilities and whether the theme has the 114 193 * feature support required by the panel. 115 194 * … … class WP_Customize_Panel { 130 209 } 131 210 132 211 /** 212 * Get the panel's content template for insertion into the Customizer pane. 213 * 214 * @since 4.1.0 215 * 216 * @return string 217 */ 218 public final function get_content() { 219 ob_start(); 220 $this->maybe_render(); 221 $template = trim( ob_get_contents() ); 222 ob_end_clean(); 223 return $template; 224 } 225 226 /** 133 227 * Check capabilities and render the panel. 134 228 * 135 229 * @since 4.0.0 … … class WP_Customize_Panel { 189 283 */ 190 284 protected function render_content() { 191 285 ?> 192 <li class=" accordion-section control-section<?php if ( empty( $this->description ) ) echo ' cannot-expand';?>">286 <li class="panel-meta accordion-section control-section<?php if ( empty( $this->description ) ) { echo ' cannot-expand'; } ?>"> 193 287 <div class="accordion-section-title" tabindex="0"> 194 288 <span class="preview-notice"><?php 195 289 /* translators: %s is the site/panel title in the Customizer */ … … class WP_Customize_Panel { 203 297 <?php endif; ?> 204 298 </li> 205 299 <?php 206 foreach ( $this->sections as $section ) {207 $section->maybe_render();208 }209 300 } 210 301 } -
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..3553285 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 /** 102 * Callback. 103 * 104 * @since 4.1.0 105 * @access public 106 * 107 * @see WP_Customize_Section::active() 108 * 109 * @var callable Callback is called with one argument, the instance of 110 * WP_Customize_Section, and returns bool to indicate whether 111 * the section is active (such as it relates to the URL 112 * currently being previewed). 113 */ 114 public $active_callback = ''; 115 116 /** 95 117 * Constructor. 96 118 * 97 119 * Any supplied $args override class property defaults. … … class WP_Customize_Section { 105 127 public function __construct( $manager, $id, $args = array() ) { 106 128 $keys = array_keys( get_object_vars( $this ) ); 107 129 foreach ( $keys as $key ) { 108 if ( isset( $args[ $key ] ) ) 130 if ( isset( $args[ $key ] ) ) { 109 131 $this->$key = $args[ $key ]; 132 } 110 133 } 111 134 112 135 $this->manager = $manager; 113 136 $this->id = $id; 137 if ( empty( $this->active_callback ) ) { 138 $this->active_callback = array( $this, 'active_callback' ); 139 } 114 140 115 141 $this->controls = array(); // Users cannot customize the $controls array. 116 142 … … class WP_Customize_Section { 118 144 } 119 145 120 146 /** 147 * Check whether section is active to current Customizer preview. 148 * 149 * @since 4.1.0 150 * @access public 151 * 152 * @return bool Whether the section is active to the current preview. 153 */ 154 public final function active() { 155 $section = $this; 156 $active = call_user_func( $this->active_callback, $this ); 157 158 /** 159 * Filter response of WP_Customize_Section::active(). 160 * 161 * @since 4.1.0 162 * 163 * @param bool $active Whether the Customizer section is active. 164 * @param WP_Customize_Section $section WP_Customize_Section instance. 165 */ 166 $active = apply_filters( 'customize_section_active', $active, $section ); 167 168 return $active; 169 } 170 171 /** 172 * Default callback used when invoking WP_Customize_Section::active(). 173 * 174 * Subclasses can override this with their specific logic, or they may 175 * provide an 'active_callback' argument to the constructor. 176 * 177 * @since 4.1.0 178 * @access public 179 * 180 * @return bool Always true. 181 */ 182 public function active_callback() { 183 return true; 184 } 185 186 /** 187 * Gather the parameters passed to client JavaScript via JSON. 188 * 189 * @since 4.1.0 190 * 191 * @return array The array to be exported to the client as JSON 192 */ 193 public function json() { 194 $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'panel', 'type' ) ); 195 $array['content'] = $this->get_content(); 196 $array['active'] = $this->active(); 197 return $array; 198 } 199 200 /** 121 201 * Checks required user capabilities and whether the theme has the 122 202 * feature support required by the section. 123 203 * … … class WP_Customize_Section { 126 206 * @return bool False if theme doesn't support the section or user doesn't have the capability. 127 207 */ 128 208 public final function check_capabilities() { 129 if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) 209 if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) { 130 210 return false; 211 } 131 212 132 if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) ) 213 if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) ) { 133 214 return false; 215 } 134 216 135 217 return true; 136 218 } 137 219 138 220 /** 221 * Get the section's content template for insertion into the Customizer pane. 222 * 223 * @since 4.1.0 224 * 225 * @return string 226 */ 227 public final function get_content() { 228 ob_start(); 229 $this->maybe_render(); 230 $template = trim( ob_get_contents() ); 231 ob_end_clean(); 232 return $template; 233 } 234 235 /** 139 236 * Check capabilities and render the section. 140 237 * 141 238 * @since 3.4.0 142 239 */ 143 240 public final function maybe_render() { 144 if ( ! $this->check_capabilities() ) 241 if ( ! $this->check_capabilities() ) { 145 242 return; 243 } 146 244 147 245 /** 148 246 * Fires before rendering a Customizer section. … … class WP_Customize_Section { 172 270 */ 173 271 protected function render() { 174 272 $classes = 'control-section accordion-section'; 175 if ( $this->panel ) {176 $classes .= ' control-subsection';177 }178 273 ?> 179 274 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>"> 180 275 <h3 class="accordion-section-title" tabindex="0"> … … class WP_Customize_Section { 183 278 </h3> 184 279 <ul class="accordion-section-content"> 185 280 <?php if ( ! empty( $this->description ) ) : ?> 186 <li><p class="description customize-section-description"><?php echo $this->description; ?></p></li> 281 <li class="customize-section-description-container"> 282 <p class="description customize-section-description"><?php echo $this->description; ?></p> 283 </li> 187 284 <?php endif; ?> 188 <?php189 foreach ( $this->controls as $control )190 $control->maybe_render();191 ?>192 285 </ul> 193 286 </li> 194 287 <?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; -
src/wp-includes/js/customize-preview.js
diff --git src/wp-includes/js/customize-preview.js src/wp-includes/js/customize-preview.js index 6da26f4..1a82565 100644
107 107 }); 108 108 109 109 preview.send( 'ready', { 110 activePanels: api.settings.activePanels, 111 activeSections: api.settings.activeSections, 110 112 activeControls: api.settings.activeControls 111 113 } ); 112 114 -
tests/qunit/index.html
diff --git tests/qunit/index.html tests/qunit/index.html index c5afd52..ce11144 100644
8 8 <script src="../../src/wp-includes/js/underscore.min.js"></script> 9 9 <script src="../../src/wp-includes/js/backbone.min.js"></script> 10 10 <script src="../../src/wp-includes/js/zxcvbn.min.js"></script> 11 11 12 12 <!-- QUnit --> 13 13 <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" /> 14 14 <script src="vendor/qunit.js"></script> … … 28 28 29 29 <!-- Tested files --> 30 30 <script src="../../src/wp-admin/js/password-strength-meter.js"></script> 31 <script src="../../src/wp-includes/js/customize-base.js"></script> 31 32 <script src="../../src/wp-includes/js/customize-models.js"></script> 32 33 <script src="../../src/wp-includes/js/shortcode.js"></script> 33 34 34 35 <!-- Unit tests --> 35 36 <script src="wp-admin/js/password-strength-meter.js"></script> 37 <script src="wp-admin/js/customize-base.js"></script> 36 38 <script src="wp-admin/js/customize-header.js"></script> 37 39 <script src="wp-includes/js/shortcode.js"></script> 38 40 </div> 39 41 </body> 40 42 </html> 41 -
new file tests/qunit/wp-admin/js/customize-base.js
diff --git tests/qunit/wp-admin/js/customize-base.js tests/qunit/wp-admin/js/customize-base.js new file mode 100644 index 0000000..c253940
- + 1 /* global wp, sinon */ 2 3 jQuery( function( $ ) { 4 var FooSuperClass, BarSubClass, foo, bar; 5 6 module( 'Customize Base: Class' ); 7 8 FooSuperClass = wp.customize.Class.extend( 9 { 10 initialize: function ( instanceProps ) { 11 $.extend( this, instanceProps || {} ); 12 }, 13 protoProp: 'protoPropValue' }, 14 { 15 staticProp: 'staticPropValue' 16 } 17 ); 18 test( 'FooSuperClass is a function ', function () { 19 equal( typeof FooSuperClass, 'function' ); 20 }); 21 test( 'FooSuperClass prototype has protoProp', function () { 22 equal( FooSuperClass.prototype.protoProp, 'protoPropValue' ); 23 }); 24 test( 'FooSuperClass does not have protoProp', function () { 25 equal( typeof FooSuperClass.protoProp, 'undefined' ); 26 }); 27 test( 'FooSuperClass has staticProp', function () { 28 equal( FooSuperClass.staticProp, 'staticPropValue' ); 29 }); 30 test( 'FooSuperClass prototype does not have staticProp', function () { 31 equal( typeof FooSuperClass.prototype.staticProp, 'undefined' ); 32 }); 33 34 foo = new FooSuperClass( { instanceProp: 'instancePropValue' } ); 35 test( 'FooSuperClass instance foo extended Class', function () { 36 equal( foo.extended( wp.customize.Class ), true ); 37 }); 38 test( 'foo instance has protoProp', function () { 39 equal( foo.protoProp, 'protoPropValue' ); 40 }); 41 test( 'foo instance does not have staticProp', function () { 42 equal( typeof foo.staticProp, 'undefined' ); 43 }); 44 test( 'FooSuperClass instance foo ran initialize() and has supplied instanceProp', function () { 45 equal( foo.instanceProp, 'instancePropValue' ); 46 }); 47 48 // @todo Test Class.constructor() manipulation 49 // @todo Test Class.applicator? 50 // @todo do we test object.instance? 51 52 53 module( 'Customize Base: Subclass' ); 54 55 BarSubClass = FooSuperClass.extend( 56 { 57 initialize: function ( instanceProps ) { 58 FooSuperClass.prototype.initialize.call( this, instanceProps ); 59 this.subInstanceProp = 'subInstancePropValue'; 60 }, 61 subProtoProp: 'subProtoPropValue' 62 }, 63 { 64 subStaticProp: 'subStaticPropValue' 65 } 66 ); 67 test( 'BarSubClass prototype has subProtoProp', function () { 68 equal( BarSubClass.prototype.subProtoProp, 'subProtoPropValue' ); 69 }); 70 test( 'BarSubClass prototype has parent FooSuperClass protoProp', function () { 71 equal( BarSubClass.prototype.protoProp, 'protoPropValue' ); 72 }); 73 74 bar = new BarSubClass( { instanceProp: 'instancePropValue' } ); 75 test( 'BarSubClass instance bar its initialize() and parent initialize() run', function () { 76 equal( bar.instanceProp, 'instancePropValue' ); 77 equal( bar.subInstanceProp, 'subInstancePropValue' ); 78 }); 79 80 test( 'BarSubClass instance bar extended FooSuperClass', function () { 81 equal( bar.extended( FooSuperClass ), true ); 82 }); 83 84 });