Last active
August 14, 2024 13:15
-
-
Save OriginalEXE/9a6183e09f4cae2f30b006232bb154af to your computer and use it in GitHub Desktop.
Extending WordPress Customizer Panels and Sections to allow nesting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel-parent, | |
#customize-theme-controls .customize-pane-child.current-section-parent { | |
-webkit-transform: translateX(-100%); | |
-ms-transform: translateX(-100%); | |
transform: translateX(-100%); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
( function( $ ) { | |
var api = wp.customize; | |
api.bind( 'pane-contents-reflowed', function() { | |
// Reflow sections | |
var sections = []; | |
api.section.each( function( section ) { | |
if ( | |
'pe_section' !== section.params.type || | |
'undefined' === typeof section.params.section | |
) { | |
return; | |
} | |
sections.push( section ); | |
}); | |
sections.sort( api.utils.prioritySort ).reverse(); | |
$.each( sections, function( i, section ) { | |
var parentContainer = $( '#sub-accordion-section-' + section.params.section ); | |
parentContainer.children( '.section-meta' ).after( section.headContainer ); | |
}); | |
// Reflow panels | |
var panels = []; | |
api.panel.each( function( panel ) { | |
if ( | |
'pe_panel' !== panel.params.type || | |
'undefined' === typeof panel.params.panel | |
) { | |
return; | |
} | |
panels.push( panel ); | |
}); | |
panels.sort( api.utils.prioritySort ).reverse(); | |
$.each( panels, function( i, panel ) { | |
var parentContainer = $( '#sub-accordion-panel-' + panel.params.panel ); | |
parentContainer.children( '.panel-meta' ).after( panel.headContainer ); | |
}); | |
}); | |
// Extend Panel | |
var _panelEmbed = wp.customize.Panel.prototype.embed; | |
var _panelIsContextuallyActive = wp.customize.Panel.prototype.isContextuallyActive; | |
var _panelAttachEvents = wp.customize.Panel.prototype.attachEvents; | |
wp.customize.Panel = wp.customize.Panel.extend({ | |
attachEvents: function() { | |
if ( | |
'pe_panel' !== this.params.type || | |
'undefined' === typeof this.params.panel | |
) { | |
_panelAttachEvents.call( this ); | |
return; | |
} | |
_panelAttachEvents.call( this ); | |
var panel = this; | |
panel.expanded.bind( function( expanded ) { | |
var parent = api.panel( panel.params.panel ); | |
if ( expanded ) { | |
parent.contentContainer.addClass( 'current-panel-parent' ); | |
} else { | |
parent.contentContainer.removeClass( 'current-panel-parent' ); | |
} | |
}); | |
panel.container.find( '.customize-panel-back' ) | |
.off( 'click keydown' ) | |
.on( 'click keydown', function( event ) { | |
if ( api.utils.isKeydownButNotEnterEvent( event ) ) { | |
return; | |
} | |
event.preventDefault(); // Keep this AFTER the key filter above | |
if ( panel.expanded() ) { | |
api.panel( panel.params.panel ).expand(); | |
} | |
}); | |
}, | |
embed: function() { | |
if ( | |
'pe_panel' !== this.params.type || | |
'undefined' === typeof this.params.panel | |
) { | |
_panelEmbed.call( this ); | |
return; | |
} | |
_panelEmbed.call( this ); | |
var panel = this; | |
var parentContainer = $( '#sub-accordion-panel-' + this.params.panel ); | |
parentContainer.append( panel.headContainer ); | |
}, | |
isContextuallyActive: function() { | |
if ( | |
'pe_panel' !== this.params.type | |
) { | |
return _panelIsContextuallyActive.call( this ); | |
} | |
var panel = this; | |
var children = this._children( 'panel', 'section' ); | |
api.panel.each( function( child ) { | |
if ( ! child.params.panel ) { | |
return; | |
} | |
if ( child.params.panel !== panel.id ) { | |
return; | |
} | |
children.push( child ); | |
}); | |
children.sort( api.utils.prioritySort ); | |
var activeCount = 0; | |
_( children ).each( function ( child ) { | |
if ( child.active() && child.isContextuallyActive() ) { | |
activeCount += 1; | |
} | |
}); | |
return ( activeCount !== 0 ); | |
} | |
}); | |
// Extend Section | |
var _sectionEmbed = wp.customize.Section.prototype.embed; | |
var _sectionIsContextuallyActive = wp.customize.Section.prototype.isContextuallyActive; | |
var _sectionAttachEvents = wp.customize.Section.prototype.attachEvents; | |
wp.customize.Section = wp.customize.Section.extend({ | |
attachEvents: function() { | |
if ( | |
'pe_section' !== this.params.type || | |
'undefined' === typeof this.params.section | |
) { | |
_sectionAttachEvents.call( this ); | |
return; | |
} | |
_sectionAttachEvents.call( this ); | |
var section = this; | |
section.expanded.bind( function( expanded ) { | |
var parent = api.section( section.params.section ); | |
if ( expanded ) { | |
parent.contentContainer.addClass( 'current-section-parent' ); | |
} else { | |
parent.contentContainer.removeClass( 'current-section-parent' ); | |
} | |
}); | |
section.container.find( '.customize-section-back' ) | |
.off( 'click keydown' ) | |
.on( 'click keydown', function( event ) { | |
if ( api.utils.isKeydownButNotEnterEvent( event ) ) { | |
return; | |
} | |
event.preventDefault(); // Keep this AFTER the key filter above | |
if ( section.expanded() ) { | |
api.section( section.params.section ).expand(); | |
} | |
}); | |
}, | |
embed: function() { | |
if ( | |
'pe_section' !== this.params.type || | |
'undefined' === typeof this.params.section | |
) { | |
_sectionEmbed.call( this ); | |
return; | |
} | |
_sectionEmbed.call( this ); | |
var section = this; | |
var parentContainer = $( '#sub-accordion-section-' + this.params.section ); | |
parentContainer.append( section.headContainer ); | |
}, | |
isContextuallyActive: function() { | |
if ( | |
'pe_section' !== this.params.type | |
) { | |
return _sectionIsContextuallyActive.call( this ); | |
} | |
var section = this; | |
var children = this._children( 'section', 'control' ); | |
api.section.each( function( child ) { | |
if ( ! child.params.section ) { | |
return; | |
} | |
if ( child.params.section !== section.id ) { | |
return; | |
} | |
children.push( child ); | |
}); | |
children.sort( api.utils.prioritySort ); | |
var activeCount = 0; | |
_( children ).each( function ( child ) { | |
if ( 'undefined' !== typeof child.isContextuallyActive ) { | |
if ( child.active() && child.isContextuallyActive() ) { | |
activeCount += 1; | |
} | |
} else { | |
if ( child.active() ) { | |
activeCount += 1; | |
} | |
} | |
}); | |
return ( activeCount !== 0 ); | |
} | |
}); | |
})( jQuery ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if ( class_exists( 'WP_Customize_Panel' ) ) { | |
class PE_WP_Customize_Panel extends WP_Customize_Panel { | |
public $panel; | |
public $type = 'pe_panel'; | |
public function json() { | |
$array = wp_array_slice_assoc( (array) $this, array( 'id', 'description', 'priority', 'type', 'panel', ) ); | |
$array['title'] = html_entity_decode( $this->title, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
$array['content'] = $this->get_content(); | |
$array['active'] = $this->active(); | |
$array['instanceNumber'] = $this->instance_number; | |
return $array; | |
} | |
} | |
} | |
if ( class_exists( 'WP_Customize_Section' ) ) { | |
class PE_WP_Customize_Section extends WP_Customize_Section { | |
public $section; | |
public $type = 'pe_section'; | |
public function json() { | |
$array = wp_array_slice_assoc( (array) $this, array( 'id', 'description', 'priority', 'panel', 'type', 'description_hidden', 'section', ) ); | |
$array['title'] = html_entity_decode( $this->title, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
$array['content'] = $this->get_content(); | |
$array['active'] = $this->active(); | |
$array['instanceNumber'] = $this->instance_number; | |
if ( $this->panel ) { | |
$array['customizeAction'] = sprintf( 'Customizing ▸ %s', esc_html( $this->manager->get_panel( $this->panel )->title ) ); | |
} else { | |
$array['customizeAction'] = 'Customizing'; | |
} | |
return $array; | |
} | |
} | |
} | |
// Enqueue our scripts and styles | |
function pe_customize_controls_scripts() { | |
wp_enqueue_script( 'pe-customize-controls', get_theme_file_uri( '/assets/js/pe-customize-controls.js' ), array(), '1.0', true ); | |
} | |
add_action( 'customize_controls_enqueue_scripts', 'pe_customize_controls_scripts' ); | |
function pe_customize_controls_styles() { | |
wp_enqueue_style( 'pe-customize-controls', get_theme_file_uri( '/assets/css/pe-customize-controls.css' ), array(), '1.0' ); | |
} | |
add_action( 'customize_controls_print_styles', 'pe_customize_controls_styles' ); | |
function pe_customize_register( $wp_customize ) { | |
// Has to be at the top | |
$wp_customize->register_panel_type( 'PE_WP_Customize_Panel' ); | |
$wp_customize->register_section_type( 'PE_WP_Customize_Section' ); | |
// Below this there is only demo code, safe to delete and add your own | |
// panels/sections/controls | |
// Add three levels on panels | |
$lvl1ParentPanel = new PE_WP_Customize_Panel( $wp_customize, 'lvl_1_parent_panel', array( | |
'title' => 'Level 1', | |
'priority' => 131, | |
)); | |
$wp_customize->add_panel( $lvl1ParentPanel ); | |
$lvl2ParentPanel = new PE_WP_Customize_Panel( $wp_customize, 'lvl_2_parent_panel', array( | |
'title' => 'Level 2', | |
'panel' => 'lvl_1_parent_panel', | |
)); | |
$wp_customize->add_panel( $lvl2ParentPanel ); | |
$lvl3ParentPanel = new PE_WP_Customize_Panel( $wp_customize, 'lvl_3_parent_panel', array( | |
'title' => 'Level 3', | |
'panel' => 'lvl_2_parent_panel', | |
'priority' => 1, | |
)); | |
$wp_customize->add_panel( $lvl3ParentPanel ); | |
// Add example section and controls to the final (third) panel | |
$wp_customize->add_section( 'pe_section', array( | |
'title' => 'Section Test', | |
'panel' => 'lvl_3_parent_panel', | |
)); | |
$wp_customize->add_setting( 'pe_test', array( | |
'default' => 'default value here', | |
'sanitize_callback' => 'wp_kses_post', | |
'transport' => 'postMessage', | |
)); | |
$wp_customize->add_control( 'pe_test', array( | |
'type' => 'text', | |
'label' => 'Some text control', | |
'section' => 'pe_section', | |
)); | |
// Add example section and controls to the middle (second) panel | |
$wp_customize->add_section( 'pe_section_2', array( | |
'title' => 'Section 2 Test', | |
'panel' => 'lvl_2_parent_panel', | |
'priority' => 2, | |
)); | |
$wp_customize->add_setting( 'pe_test_2', array( | |
'default' => 'default value here', | |
'sanitize_callback' => 'wp_kses_post', | |
'transport' => 'postMessage', | |
)); | |
$wp_customize->add_control( 'pe_test_2', array( | |
'type' => 'text', | |
'label' => 'Some text control 2', | |
'section' => 'pe_section_2', | |
)); | |
// Add example section and controls to another section | |
$lvl1ParentSection = new PE_WP_Customize_Section( $wp_customize, 'lvl_1_parent_section', array( | |
'title' => 'Level 1 Section', | |
'panel' => 'lvl_3_parent_panel', | |
)); | |
$wp_customize->add_section( $lvl1ParentSection ); | |
$lv21ParentSection = new PE_WP_Customize_Section( $wp_customize, 'lvl_2_parent_section', array( | |
'title' => 'Level 2 Section', | |
'section' => 'lvl_1_parent_section', | |
'panel' => 'lvl_3_parent_panel', | |
)); | |
$wp_customize->add_section( $lv21ParentSection ); | |
$wp_customize->add_setting( 'pe_test_3', array( | |
'default' => 'default value here', | |
'sanitize_callback' => 'wp_kses_post', | |
'transport' => 'postMessage', | |
)); | |
$wp_customize->add_control( 'pe_test_3', array( | |
'type' => 'text', | |
'label' => 'Some text control 3', | |
'section' => 'lvl_2_parent_section', | |
)); | |
} | |
add_action( 'customize_register', 'pe_customize_register' ); |
Hi @OriginalEXE and @DannyCooper
First of all, thanks for the amazing and clean code to extend the customizer. Unfortunately, I'm running in the same issue where the Panel always shows first of sections, is there a simple way for Panel to respect the priority given ?
Thanks in advance
Hi, apologies for a late reply.
I am no longer working with WordPress (for a long time now) so I, unfortunately, can't help with answering this. My rough guess would be to try playing with the first ~50 lines of js where the sorting is done, perhaps there you can manage to sort the panels properly.
Sorry I could not be more useful.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @originalex,
When Setup like this:
It appears that on the second level the Panel always comes before the sections, regardless of priority or the order the code is initiated. Is there a way around that?
Thanks!