A drag-and-drop feature is needed in the Customizer to take theme customizations to the next level. I had been searching through the Web for anything I can find regarding this. The Default Controls in Customizer, including the Widget Panel and the Menu Panel, employ Javascript in a big way but were not exactly was I was looking for. It seems like there has not been anything major done in this direction. Then I stumbled upon the Custom Controls designed by Anthony Hortin. He has designed a neat set of Custom Controls but the one that got my attention was the Sortable Repeater. It boasts of a drag-and-drop functionality for the various fields that are created using Javascript (which in itself is a pretty nifty feature). I looked through the code for the Sortable Repeater and found that it fits perfectly as the landing pad for a drag-and-drop control in the customizer. So, let’s jump right into it!
Here is the code for the custom control –
[php]
class Plum_Sortable_Repeater_Custom_Control extends WP_Customize_Control {
/**
* The type of control being rendered
*/
public $type = ‘sortable_repeater’;
/**
* Button labels
*/
public $button_labels = array();
/**
* Constructor
*/
public function __construct( $manager, $id, $args = array(), $options = array() ) {
parent::__construct( $manager, $id, $args );
// Merge the passed button labels with our default labels
$this->button_labels = wp_parse_args( $this->button_labels,
array(
‘add’ => __( ‘Add’, ‘plum’ ),
)
);
}
/**
* Enqueue our scripts and styles
*/
public function enqueue() {
wp_enqueue_script( ‘skyrocket_custom_controls_js’, trailingslashit( get_template_directory_uri() ) . ‘js/customizer-rep.js’, array( ‘jquery’ ), ‘1.0’, true );
wp_enqueue_style( ‘skyrocket_custom_controls_css’, trailingslashit( get_template_directory_uri() ) . ‘assets/css/customizer-rep.css’, array(), ‘1.0’, ‘all’ );
}
/**
* Render the control in the customizer
*/
public function render_content() {
?>
<div class="drag_and_drop_control">
<?php if( !empty( $this->label ) ) { ?>
<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
<?php } ?>
<?php if( !empty( $this->description ) ) { ?>
<span class="customize-control-description"><?php echo esc_html( $this->description ); ?></span>
<?php } ?>
<input type="hidden" id="<?php echo esc_attr( $this->id ); ?>" name="<?php echo esc_attr( $this->id ); ?>" value="<?php echo esc_attr( $this->value() ); ?>" class="customize-control-drag-and-drop" <?php $this->link(); ?> />
<ul class="sortable">
<li class="repeater" value="1">
<div class="repeater-input">a</div>
</li>
<li class="repeater" value="2">
<div class="repeater-input">b</div>
</li>
<li class="repeater" value="3">
<div class="repeater-input">c</div>
</li>
<li class="repeater" value="4">
<div class="repeater-input">d</div>
</li>
</div>
</div>
<?php
}
}
[/php]
Pardon the lack of indentation. For someone who deals in WordPress Themes, I surely suck at the TinyMCE Editor. This code creates the Drag-and-Drop custom control of 4 Fields whose positions can be changed using drag-and-drop. Let’s take a look at the code on a step-by-step basis.
Initially, we extend the class WP_Customize_Control
to create a custom control. Then, we define the type of the control. After that, we create a function enqueue()
to enqueue the required CSS and JS files. Here, we have enqueued control.js and control.css. The code for enqueuing is pretty straight-forward with files getting enqueued from their respective places and with the dependency of jQuery in case of JS file.
After that, we move ahead to rendering the control. In the function render_content(), we insert the actual HTML that is going to be rendered at the front-end in the customizer. Initially, we check for the ‘label’ and ‘description’ values during the addition of the control in customize_register using add_control(). Then, we insert a hidden input element. This is where the value of the control is going to be saved in the value attribute. We’ll come back to it later. Also note that this input element contains $this->link()
. This is the piece of code which tells the Customizer that a change has been made and the new settings need to be saved triggering the ‘Publish’ button. Without this code working properly, the Customizer will not know when to save the settings.
Now, let’s take a look at the CSS and jQuery code for the Custom Control-
[css]
.drag_and_drop_control .repeater {
background: #eedfdb;
text-align: center;
margin: 10px auto;
font-weight: 700;
color: #555d66;
}
.drag_and_drop_control .repeater .repeater-input {
padding-top: 5px;
padding-bottom: 5px;
width: 80%;
display: inline-block;
}
[/css]
[js]
$(‘.drag_and_drop_control’).each(function() {
// If there is an existing customizer value, populate our rows
var defaultValuesArray = $(this).find(‘.customize-control-sortable-repeater’).val().split(‘,’);
var defaultTextArray = $.map(defaultValuesArray, function(i, val) {
return $(‘.drag_and_drop_control’).find(‘li:eq(‘ + (i – 1) + ‘)’).text();
});
var numRepeaterItems = defaultValuesArray.length;
var i;
for (i=0; i<=numRepeaterItems; i++) {
$(this).find(‘.repeater:eq(‘ + i + ‘)’).val(defaultValuesArray[i]).html(‘<div class="repeater-input">’ + defaultTextArray[i] + ‘</div><i class="fa fa-minus"></i><i class="fa fa-plus"></i>’);
}
});
// Make our Repeater fields sortable
$(this).find(‘.sortable’).sortable({
update: function(event, ui) {
skyrocketGetAllInputs($(this).parent());
}
});
// Toggle the Sortability of the Fields
$(this).find(‘.repeater’).on(‘click’, ‘i.fa-minus’, function() {
$(this).parents(‘li’).addClass(‘disabled’);
});
$(this).find(‘.repeater’).on(‘click’, ‘i.fa-plus’, function() {
$(this).parents(‘li’).removeClass(‘disabled’);
});
// Get the values from the repeater input fields and add to our hidden field
function skyrocketGetAllInputs($element) {
var inputValues = $element.find(‘.repeater’).map(function() {
return $(this).val();
}).toArray();
// Add all the values from our repeater fields to the hidden field (which is the one that actually gets saved)
$element.find(‘.customize-control-sortable-repeater’).val(inputValues);
// Important! Make sure to trigger change event so Customizer knows it has to save the field
$element.find(‘.customize-control-sortable-repeater’).trigger(‘change’);
}
[/js]
CSS is pretty self-explanatory. I have made added some custom colors apart from the regular customizer.
JS is where the magic happens. I have picked up the code from the Sortable Repeater and modified as per my requirements. Initially, we check for pre-existing value in the hidden input element. The fields are created in the customizer using those values. During first load, the default values are loaded which are defined in the Custom Control extended class.
After that, we make the fields sortable. For that, we have used jQuery sortable widget. If you are not familiar with the sortable() widget, I suggest you take a look at it. Here, I have used the ‘update’ event which is triggered only when the item is dragged and a change is made. Every time a change is made, the callback function loads.
In the callback function, we call the function dvtGetAllInputs() and pass $(this).parent() as its parameter. I’ll explain it in a moment. What this function does is iterates through every li element, gets its value and stores it in the variable ‘inputValues’ in the form of an array. Then, this value is stored in the hidden input element. After that, we need to trigger the change event so that customizer know than changes have been made and they need to be saved.
This was the draggable functionality of the control. The value stored is in the form of a string of values of the draggable elements separated by commas. In the example above, we have 4 fields with values 1, 2, 3 and 4. So, the default value is going to be 1,2,3,4. If we, suppose, drag item C to the top, the new value is going to be 3,1,2,4.
This control is currently under development. It has great potential and can even be used as a localized page builder for Customizer.