possible to make sections in theme customizer sortable and saveable at publish button clicked?

Simple tutorial – How to sort customizer sections in the panel and add order to theme.

1. You must use prefixes to do this because customizer don’t support custom classes.

  • ctmss_panel_ – for panels
  • ctmss_section_ – for sections
  • ctmss_hidden_ – for hidden sections that has a input with values

2. Add panel to the Customizer – add_panel()

$wp_customize->add_panel( 'ctmss_panel_panelname1', array(
    'title'    => esc_html__( 'My Panel', 'textdomain' ),
    'priority' => 150
) );

3. Add section with text input to save new sections order. Hide this section with CSS. add_section(), add_setting(), add_control()

$wp_customize->add_section( 'ctmss_hidden_sectionname1', array(
    'title'       => esc_html__( 'Section hidden', 'textdomain' ),
    'panel'       => 'ctmss_panel_panelname1',
    'priority'    => 1
) );

$wp_customize->add_setting(
    'ctmss_sections_order', array(
        'sanitize_callback' => 'wp_kses_post'
    )
);
$wp_customize->add_control(
    new WP_Customize_Control(
        $wp_customize,
        'sections_order',
        array(
            'settings' => 'ctmss_sections_order',
            'type'     => 'text',
            'label'    => esc_html__( 'Section layout', 'textdomain' ),
            'section'  => 'ctmss_hidden_sectionname1',
        )
    )
);

4. Create array() with available sections. If saved values is available get it, else set default sections and order.

$default_sections = array (
    'ctmss_section_sectionname1' => array (
        'title'       => esc_html__( 'Section 1', 'textdomain' ),
        'description' => esc_html__( 'Section 1 Description', 'textdomain' ),
    ),
    'ctmss_section_sectionname2' => array (
        'title'       => esc_html__( 'Section 2', 'textdomain' ),
        'description' => esc_html__( 'Section 2 Description', 'textdomain' ),
    ),
    'ctmss_section_sectionname3' => array (
        'title'       => esc_html__( 'Section 3', 'textdomain' ),
        'description' => esc_html__( 'Section 3 Description', 'textdomain' ),
    ),
    'ctmss_section_sectionname4' => array (
        'title'       => esc_html__( 'Section 4', 'textdomain' ),
        'description' => esc_html__( 'Section 4 Description', 'textdomain' ),
    ),
);

$sortable_sections = get_theme_mod('ctmss_sections_order');
if( !isset( $sortable_sections ) || empty( $sortable_sections ) ){
    set_theme_mod( 'ctmss_sections_order', implode(',', array_keys( $default_sections ) ) );
}
$sortable_sections = explode(',', $sortable_sections );

foreach( $sortable_sections as $sortable_section ){
    $wp_customize->add_section( $sortable_section, array(
        'title'       => $default_sections[$sortable_section]['title'],
        'description' => $default_sections[$sortable_section]['description'],
        'panel'       => 'ctmss_panel_panelname1'
    ) );
}

5. Add controls to the sections. Example:

$wp_customize->add_setting(
    'myprefix_section1_layout', array(
        'default'           => 'classic',
        'sanitize_callback' => 'wp_kses_post'
    )
);
$wp_customize->add_control(
    new WP_Customize_Control(
        $wp_customize,
        'section1_layout',
        array(
            'settings' => 'myprefix_section1_layout',
            'type'     => 'radio',
            'label'    => esc_html__( 'Section layout', 'textdomain' ),
            'section'  => 'ctmss_section_sectionname1',
            'choices'  => array(
                'classic'          => esc_html__( 'Classic', 'textdomain' ),
                'grid'             => esc_html__( 'Grid', 'textdomain' ),
                'list'             => esc_html__( 'List', 'textdomain' ),
            )
        )
    )
);

$wp_customize->add_setting(
    'myprefix_section2_layout', array(
        'default'           => 'classic',
        'sanitize_callback' => 'wp_kses_post'
    )
);
$wp_customize->add_control(
    new WP_Customize_Control(
        $wp_customize,
        'section2_layout',
        array(
            'settings' => 'myprefix_section2_layout',
            'type'     => 'radio',
            'label'    => esc_html__( 'Section layout', 'textdomain' ),
            'section'  => 'ctmss_section_sectionname2',
            'choices'  => array(
                'classic'          => esc_html__( 'Classic', 'textdomain' ),
                'grid'             => esc_html__( 'Grid', 'textdomain' ),
                'list'             => esc_html__( 'List', 'textdomain' ),
            )
        )
    )
);

$wp_customize->add_setting(
    'myprefix_section3_layout', array(
        'default'           => 'classic',
        'sanitize_callback' => 'wp_kses_post'
    )
);
$wp_customize->add_control(
    new WP_Customize_Control(
        $wp_customize,
        'section3_layout',
        array(
            'settings' => 'myprefix_section3_layout',
            'type'     => 'radio',
            'label'    => esc_html__( 'Section layout', 'textdomain' ),
            'section'  => 'ctmss_section_sectionname3',
            'choices'  => array(
                'classic'          => esc_html__( 'Classic', 'textdomain' ),
                'grid'             => esc_html__( 'Grid', 'textdomain' ),
                'list'             => esc_html__( 'List', 'textdomain' ),
            )
        )
    )
);

$wp_customize->add_setting(
    'myprefix_section4_layout', array(
        'default'           => 'classic',
        'sanitize_callback' => 'wp_kses_post'
    )
);
$wp_customize->add_control(
    new WP_Customize_Control(
        $wp_customize,
        'section4_layout',
        array(
            'settings' => 'myprefix_section4_layout',
            'type'     => 'radio',
            'label'    => esc_html__( 'Section layout', 'textdomain' ),
            'section'  => 'ctmss_section_sectionname4',
            'choices'  => array(
                'classic'          => esc_html__( 'Classic', 'textdomain' ),
                'grid'             => esc_html__( 'Grid', 'textdomain' ),
                'list'             => esc_html__( 'List', 'textdomain' ),
            )
        )
    )
);

6. Add styles and scripts.

function my_customizer_scripts() {
    wp_enqueue_script( 'my_customizer_js', trailingslashit( get_template_directory_uri() ) . 'assets/js/my-customizer.js', array(), '1.0', 'all' );
}
add_action('customize_controls_print_scripts', 'my_customizer_scripts');

function my_customizer_styles() {
    wp_enqueue_style( 'my_customizer_css', trailingslashit( get_template_directory_uri() ) . 'assets/css/my-customizer.css', array(), '1.0', 'all' );
}
add_action('customize_controls_print_styles', 'my_customizer_styles');

my-customizer.css

li[id*="ctmss_hidden_"],
ul[id*="ctmss_hidden_"],
.ctmss_hidden {
    display: none !important;
}

.ctmss_section .accordion-section-title:active {
    cursor: move !important;
}

my-customizer.js

jQuery( document ).ready( function($) {

    $('ul[id*="ctmss_panel_"]').addClass('ctmss_panel');
    $('ul.ctmss_panel').each( function() {
        if( 0 === $(this).length ){
            return true;
        }

        var panel = $(this),
            panelSectionHidden = panel.find('li[id*="ctmss_hidden_"]').attr('aria-owns');

        panel.find('li[id*="ctmss_section_"]').addClass('ctmss_section');
        panel.find('li[id*="ctmss_hidden_"]').addClass('ctmss_hidden');

        // Init sortable.
        panel.sortable( {
            item: 'li.ctmss_section',
            axis: 'y',

            // Update value when we stop sorting.
            stop: function() {
                updateValue();
            }
        });

        // Updates the sorting list.
        function updateValue() {
            var inputValues = panel.find( '.ctmss_section' ).map( function() {
                var id = $(this).attr('id'),
                    id = id.replace('accordion-section-','');

                return id;
            }).get().join(',');

            // Add the value to the hidden field
            $( '#' + panelSectionHidden ).find( '.customize-control-text input' ).prop( 'value', inputValues );

            // Important! Make sure to trigger change event so Customizer knows it has to save the field
            $( '#' + panelSectionHidden ).find( '.customize-control-text input' ).trigger('change');

            console.log( inputValues );
        }

    });

});

7. Add function to display sections.

if ( !function_exists( 'ctmss_get_sections' ) ):
    function ctmss_get_sections( $sections ) {

        $sections = explode(',', $sections);

        $output="";

        if ( empty( $sections ) ) {
            return $output;
        }

        foreach( $sections as $section ) {

            switch ( $section ) {

            case 'ctmss_section_sectionname1':
                $output .= '<div style="width: 100%; height: 200px; padding: 40px; background: #e1e1e1;">Section 1</div>';
                break;

            case 'ctmss_section_sectionname2':
                $output .= '<div style="width: 100%; height: 200px; padding: 40px; background: #e3e3e3;">Section 2</div>';
                break;

            case 'ctmss_section_sectionname3':
                $output .= '<div style="width: 100%; height: 200px; padding: 40px; background: #e5e5e5;">Section 3</div>';
                break;

            case 'ctmss_section_sectionname4':
                $output .= '<div style="width: 100%; height: 200px; padding: 40px; background: #e7e7e7;">Section 4</div>';
                break;

            default:
                break;
            }

        }

        return $output;

    }
endif;

8. Run your code where you want.

echo ctmss_get_sections( get_theme_mod('ctmss_sections_order') );

This solution works without Kirki but is easy to customize it.

Leave a Comment