Skip to content

Instantly share code, notes, and snippets.

@OriginalEXE
Last active June 26, 2023 05:07
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 25 You must be signed in to fork a gist
  • Save OriginalEXE/9a6183e09f4cae2f30b006232bb154af to your computer and use it in GitHub Desktop.
Save OriginalEXE/9a6183e09f4cae2f30b006232bb154af to your computer and use it in GitHub Desktop.
Extending WordPress Customizer Panels and Sections to allow nesting
.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%);
}
( 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 );
<?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 &#9656; %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' );
@DannyCooper
Copy link

Hi @originalex,

When Setup like this:

Panel
- Panel
-- Section
-- Section
- Section
- Section
- Section

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!

@fearlex
Copy link

fearlex commented Feb 19, 2019

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

@OriginalEXE
Copy link
Author

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