Creating a Customizer control with JavaScript

This post is about creating a control, from A to Z, in WordPress Customizer with JavaScript.

Did you know that you can create panels, sections and controls in the Customizer with JavaScript ?
"The PHP API for their registration is essentially a wrapper for the underlying JS API" as mentioned by Weston Ruter in Improvements to the Customize JS API in 4.9 

He also says "you can also avoid statically registering settings and partials in PHP by instead adding filters to dynamically recognize settings and partials, allowing them to be registered on demand", meaning that you can create settings and partials in JS but will have to register them in PHP via a filter because they must be sanitized and validated by the server for security reasons !

For this task, we need to :
- access the Customizer Pane to create a Panel, a Section, a Setting and a Control with JS
- access the Customizer Preview to define the related Partial of the Control with JS
- add a filter to validate and sanitize the Setting with PHP
For more information about how things are related in the Customizer, please read my post "Listen for changes in Customizer Preview !"

I'll explain it as if the reader is a beginner so she/he can understand and follow along.

My tree looks like this:

- theme ( main folder)
-- inc (folder)
--- customizer.php
-- js (folder)
--- controls.js
--- customizer.js
--- main.js
-- functions.php

The functions.php file is used to :
- enqueue and localize main.js where we will render on the front end our choice(s) from the Customizer
- require the customizer.php file where we hook controls.js (for Customizer Pane) and customizer.js (for Customizer Preview)
We can of course hook controls.js and customizer.js from functions.php but it's better to keep things separated.

// File functions.php
/**
 * Load and localize main.js.
 */
function main_js() {
    wp_enqueue_script( 'main-js', get_theme_file_uri( '/js/main.js' ), array(), '1.0', true );
    // Localize the script with new data and pass php variables to JS.
    $main_js_data = array(
        /** FOR LATER USE. */
        'siteTitleColor' => get_theme_mod('siteTitleColorSetting', '#fff'),
    );
    wp_localize_script( 'main-js', 'main_vars', $main_js_data );
}
add_action( 'wp_enqueue_scripts', 'main_js' );

/**
 * Customizer additions.
 */
require get_template_directory() . '/inc/customizer.php';
// File customizer.php
/**
 * Hooking in JS code to affect the controls in the Customizer.
 */
function midday_customize_controls_js() {
    wp_enqueue_script( 'midday-controls', get_template_directory_uri() . '/js/controls.js', array( 'customize-controls' ), filemtime( get_theme_file_path( '/js/controls.js' ) ), true );
}
add_action( 'customize_controls_enqueue_scripts', 'midday_customize_controls_js' );

/**
 * Binds JS handlers to make Theme Customizer preview reload changes asynchronously.
 */
function midday_customize_preview_js() {
    wp_enqueue_script( 'midday-customizer', get_template_directory_uri() . '/js/customizer.js', array( 'customize-preview' ), '20151215', true );
}
add_action( 'customize_preview_init', 'midday_customize_preview_js' );

If you don't know what is wp_localize_script(), think of it as a bridge between PHP and JS where we can pass variables from PHP to JS.
Just to let you know, there is also another function wp_add_inline_script() to add JS code to a registered JS file.

Now that we are all set, let's create a Panel, a Section, a Setting and a Control in controls.js to change the site title color.

/**
 * File controls.js
 *
 * Access Theme Customizer Controls for a better user experience.
 */
(function (api) {
    api.bind('ready', function () {
        // Create panel.
    api.panel.add(
        new api.Panel('myPanel', {
        title: 'Theme Options',
                description: 'Customize Theme',
                priority: 5 // Optional default is 160.
        })
    );
        // Site Title Color Section.
        api.section.add(
            new api.Section('siteTitleColorSection', {
                title: 'Site Title Color Section',
                panel: 'myPanel',
                customizeAction: 'Customizing ▸ Site Title Color', // String above title's Section.
                priority: 5 // The order of this section in the panel.
            })
        );
        // Site Title Color Setting.
        api.add(
            new api.Setting('siteTitleColorSetting', '#fff', {
                transport: 'postMessage'
            })
        );
        // Site Title Color Control.
        api.control.add(
        new wp.customize.ColorControl('siteTitleColorControl', {
                section: 'siteTitleColorSection',
                label : 'Site Title Color Control',
        setting: 'siteTitleColorSetting',
                priority: 5 // The order of this control in the section.
        })
    );
    });
}) (wp.customize);

Please note that the pattern for the Setting is different from the others !
For the Setting, we type api.add(setting) and define in the setting an id, a defaultValue, then we pass the desired call type (here we are using postMessage).

Now, let's access the Customizer Preview in customizer.js and define how the Setting joins the Control to the Partial.

/**
 * File customizer.js.
 *
 * Theme Customizer enhancements for a better user experience.
 *
 * Contains handlers to make Theme Customizer preview reload changes asynchronously.
 */

(function ($) {
    // Site Title Color.
    wp.customize("siteTitleColorSetting", function (value) {
        value.bind(function (new_value) {
            $(".site-title a").css("color", new_value);
        });
    });
}(jQuery));

Now, let's add in customizer.php the filter to validate and sanitize our Setting, preferably (for logic) after the code that hooks controls.js

// File customizer.php
add_filter(
    'customize_dynamic_setting_args',
    function( $setting_args, $setting_id ) {
        if ( 'siteTitleColorSetting' === $setting_id ) {
            $setting_args = array(
                'sanitize_callback' => 'sanitize_hex_color',
            );
        }
        return $setting_args;
    },
    10,
    2
);

As you can see, the above function uses the customize_dynamic_setting_args filter to tell the server to validate/recognize the Setting and to sanitize it.
The number 10 is the priority of execution of the function, and the number 2 is the number of arguments the function accepts.
More info about add_filter() in the Code Reference.

Now, if you go to the Customizer, you'll find the created panel, section, setting and control.
If you try to change the site title it will change according to your choice.
But if you publish it, nothing will happen on the front end 🤔
So, our final step is to render/reflect our choice in the Customizer on the front end.
Remember the beginning of this post ?
We have loaded and localized main.js for this purpose 😉
Open main.js and add the following code

/**
 * File main.js.
 *
 * Handles theme's JS functions.
 */
"use strict";

const siteTitleColor = midday_vars.siteTitleColor; // Retriving the passed variable from PHP to JS.
const siteTitleAnchor = document.querySelector('.site-title a'); // Select the site title anchor.
if (siteTitleColor) { // If their is any value.
    siteTitleAnchor.style.color = siteTitleColor; // Add this value as a color to the site title anchor.
}

The code is explained and easy to understand, if you have any question don't hesitate !

As a final note, I would like to let you know that I've created the same control with PHP along with the JS one.
When we change the site title color with the PHP control (let's say #000) and the JS control (let's say #81d742) and publish our changes, it's the JS control that takes over 💪 

Hope this post will help you begin using the Customizer's JS API instead of it's PHP API 😊

SYA,
LebCit.