Meta box controls — datepicker and slider UI

In the previous article I covered drop down controls in our meta box. Today we’ll move forward to more custom solutions. Instead of image and gallery meta box controls, we’ll add a datepicker — a calendar, and slider UI. They can be used for various things. I used datepicker for a custom reservation plugin, and in another project, I used it to schedule the custom post type expiration date using wp_cron().

Preparing the functions and layout

We’ll be using the same template for post meta box as in the previous articles, but with some modifications. First we’ll ‘split’ the meta box to left and right side, so that everything is not crammed in. Remember you can set any kind of HTML layout you like. Our HTML will look like this

<style type="text/css">
.post_meta_extras p{margin: 20px;}
.post_meta_extras label{display:block; margin-bottom: 10px;}
.post_meta_extras .left_part{display: inline-block;width: 45%;margin-right: 30px;vertical-align: top;}
.post_meta_extras .right_part{display: inline-block; width: 46%;vertical-align: top;}
</style>
<div class="post_meta_extras">
<div class="left_part">
...
</div>
<div class="right_part">

</div>
</div>

In the left part we’ll put our existing meta box controls, and in the right part we’ll put the new ones.

Before we start with the php code, we’ll need to add our JavaScript, otherwise we won’t be able to use our date picker and slider. Luckily for us, WordPress got us covered. If you take a look in the wp_enqueue_script() function developers page, you’ll notice that you can load dependencies that will be loaded with your enqueued script. We’ll need an admin.js file that will be enqueued only on our post/page page in the admin. In our functions.php we’ll add

/**
* Load admin scripts
*
* @param string $hook Page hook.
* @since 1.0.0.
*/
function mytheme_load_admin_script( $hook ) {
if ( 'post.php' === $hook || 'post-new.php' === $hook ) {
wp_enqueue_script( 'admin_js', get_template_directory_uri() . '/js/admin.js', array( 'jquery', 'jquery-ui-datepicker', 'jquery-ui-slider' ) );
}
}

add_action( 'admin_enqueue_scripts', 'mytheme_load_admin_script' );

We only want our admin.js to load on the post.php admin page, that’s why we will add the check if ( ‘post.php’ === $hook || ‘post-new.php’ === $hook ) to check for new or existing post. Next, notice that we’ve added jquery, jquery-ui-datepicker and jquery-ui-slider in our dependency array. This way we don’t need to download separate files or call them from CDN since they are already included in WordPress.

Adding the controls

Now that we have our JavaScript file ready, and our layout pre made, we can add our custom controls. Inside our mytheme_metabox_controls( $post ) function we can add variables

$mytheme_datepicker = ( isset( $meta['mytheme_datepicker'][0] ) && '' !== $meta['mytheme_datepicker'][0] ) ? $meta['mytheme_datepicker'][0] : '';
$mytheme_select = ( isset( $meta['mytheme_select'][0] ) && '' !== $meta['mytheme_select'][0] ) ? $meta['mytheme_select'][0] : '';

Inside the right_part div we’ll add

<div class="right_part">
<p>
<label for="mytheme_datepicker">
<?php esc_html_e( 'Pick a date', 'mytheme' ); ?>
</label>
<input type="text" id="mytheme_datepicker" name="mytheme_datepicker" value="<?php echo esc_attr( $mytheme_datepicker ); ?>" />
</p>
<p>
<label for="mytheme_slider"><?php esc_html_e( 'Choose a value:', 'mytheme' ); ?></label>
<input type="text" id="mytheme_slider" name="mytheme_slider" readonly value="<?php echo esc_attr( $mytheme_slider ); ?>"/>
<div id="slider-range"></div>
</p>
</div>

We’ll create a slider that has a range (to make it more interesting). For our custom controls to work, we need to add some JavaScript. In admin.js add

jQuery(document).ready(function($){
'use strict';

$( '#mytheme_datepicker' ).datepicker();

var slider_val = $('#mytheme_slider').val();

if ( slider_val.length <= 0 ) {
$('#mytheme_slider').val('75 - 300');
} else {
var matches = slider_val.match(/(\d+)/g);
var initial_slider_val = matches[0];
var final_slider_val = matches[1];
}

$( '#slider-range' ).slider({
range: true,
min: 0,
max: 500,
values: ( slider_val.length <= 0 ) ? [ 75, 300 ] : [matches[0], matches[1]],
slide: function( event, ui ) {
$( '#mytheme_slider' ).val( ui.values[ 0 ] + ' - ' + ui.values[ 1 ] );
}
});

});

Notice that we had to add some tests that will make the slider see the ranges it has in its values (if there are any). You can customize this as you wish of course. The same goes for datepicker. You can make it always present on screen, instead of calling it on input click for instance.

And we need to save our values, so in mytheme_save_metaboxes( $post_id ) function add

if ( isset( $_POST['mytheme_datepicker'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_datepicker', sanitize_text_field( wp_unslash( $_POST['mytheme_datepicker'] ) ) ); // Input var okay.
}

if ( isset( $_POST['mytheme_slider'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_slider', sanitize_text_field( wp_unslash( $_POST['mytheme_slider'] ) ) ); // Input var okay.
}

after the last if condition.

If you look at our meta box controls you’ll notice that they don’t have any styling set. WordPress will load JavaScript files, but won’t load any accompanied css styles with jQuery UI. That means that we either need to load it via locally downloaded files, set our own styling inline or pull the style from CDN network. I’ll show you how you can add the default style by calling it from the CDN. To do so, in our admin enqueue function you’ll add

/**
* Load admin scripts
*
* @param string $hook Page hook.
* @since 1.0.0.
*/
function mytheme_load_admin_script( $hook ) {
if ( 'post.php' === $hook || 'post-new.php' === $hook ) {

wp_enqueue_style( 'admin-ui-css', 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css', false, '', false );

wp_enqueue_script( 'admin_js', get_template_directory_uri() . '/js/admin.js', array( 'jquery', 'jquery-ui-datepicker', 'jquery-ui-slider' ) );
}
}

add_action( 'admin_enqueue_scripts', 'mytheme_load_admin_script' );

We’ve used google’s CDN here.

Now that we’ve got that going we can see how it all looks like.

Datepicker and slider meta box control

Datepicker and slider meta box control.

Active datepicker and slider meta box control

Active datepicker and slider meta box control.

Note that you can add as many sliders as you want, but the JavaScript should be modified a bit to suit your needs (either duplicate the slider function, or use $.each function to apply the slider UI to each slider element — just be careful not to duplicate element id’s). You can also make datepicker always present on screen, in which case you’ll use a div instead of input field. But there is a small catch — since you need to save values, you’ll still need to place your selected values in an input field so that you can fetch it via $_POST variable. This can be circumvented by making your calendar input field type=”hidden” and then placing the picked value in it using JavaScript. It takes a bit more coding, but nothing you can’t do ;)

Our full php code for the metabox now looks like this:

add_action( 'add_meta_boxes', 'mytheme_add_meta_box' );

if ( ! function_exists( 'mytheme_add_meta_box' ) ) {
/**
* Add meta box to page screen
*
* This function handles the addition of variuos meta boxes to your page or post screens.
* You can add as many meta boxes as you want, but as a rule of thumb it's better to add
* only what you need. If you can logically fit everything in a single metabox then add
* it in a single meta box, rather than putting each control in a separate meta box.
*
* @since 1.0.0
*/
function mytheme_add_meta_box() {
add_meta_box( 'additional-page-metabox-options', esc_html__( 'Metabox Controls', 'mytheme' ), 'mytheme_metabox_controls', 'page', 'normal', 'low' );
}
}

if ( ! function_exists( 'mytheme_metabox_controls' ) ) {
/**
* Meta box render function
*
* @param object $post Post object.
* @since 1.0.0
*/
function mytheme_metabox_controls( $post ) {
$meta = get_post_meta( $post->ID );
$mytheme_input_field = ( isset( $meta['mytheme_input_field'][0] ) && '' !== $meta['mytheme_input_field'][0] ) ? $meta['mytheme_input_field'][0] : '';
$mytheme_radio_value = ( isset( $meta['mytheme_radio_value'][0] ) && '' !== $meta['mytheme_radio_value'][0] ) ? $meta['mytheme_radio_value'][0] : '';
$mytheme_checkbox_value = ( isset( $meta['mytheme_checkbox_value'][0] ) && '1' === $meta['mytheme_checkbox_value'][0] ) ? 1 : 0;
$mytheme_custom_select = ( isset( $meta['mytheme_custom_select'][0] ) && '' !== $meta['mytheme_custom_select'][0] ) ? $meta['mytheme_custom_select'][0] : '';
$mytheme_page_select = ( isset( $meta['mytheme_page_select'][0] ) && '' !== $meta['mytheme_page_select'][0] ) ? $meta['mytheme_page_select'][0] : '';
$mytheme_category_select = ( isset( $meta['mytheme_category_select'][0] ) && '' !== $meta['mytheme_category_select'][0] ) ? $meta['mytheme_category_select'][0] : '';
$mytheme_datepicker = ( isset( $meta['mytheme_datepicker'][0] ) && '' !== $meta['mytheme_datepicker'][0] ) ? $meta['mytheme_datepicker'][0] : '';
$mytheme_slider = ( isset( $meta['mytheme_slider'][0] ) && '' !== $meta['mytheme_slider'][0] ) ? $meta['mytheme_slider'][0] : '';
wp_nonce_field( 'mytheme_control_meta_box', 'mytheme_control_meta_box_nonce' ); // Always add nonce to your meta boxes!
?>
<style type="text/css">
.post_meta_extras p{margin: 20px;}
.post_meta_extras label{display:block; margin-bottom: 10px;}
.post_meta_extras .left_part{display: inline-block;width: 45%;margin-right: 30px; vertical-align: top;}
.post_meta_extras .right_part{display: inline-block; width: 46%; vertical-align: top;}
</style>
<div class="post_meta_extras">
<div class="left_part">
<p>
<label><?php esc_attr_e( 'Input text', 'mytheme' ); ?></label>
<input type="text" name="mytheme_input_field" value="<?php echo esc_attr( $mytheme_input_field ); ?>">
</p>
<p>
<label>
<input type="radio" name="mytheme_radio_value" value="value_1" <?php checked( $mytheme_radio_value, 'value_1' ); ?>>
<?php esc_attr_e( 'Radio value 1', 'mytheme' ); ?>
</label>
<label>
<input type="radio" name="mytheme_radio_value" value="value_2" <?php checked( $mytheme_radio_value, 'value_2' ); ?>>
<?php esc_attr_e( 'Radio value 2', 'mytheme' ); ?>
</label>
<label>
<input type="radio" name="mytheme_radio_value" value="value_3" <?php checked( $mytheme_radio_value, 'value_3' ); ?>>
<?php esc_attr_e( 'Radio value 3', 'mytheme' ); ?>
</label>
</p>
<p>
<label><input type="checkbox" name="mytheme_checkbox_value" value="1" <?php checked( $mytheme_checkbox_value, 1 ); ?> /><?php esc_attr_e( 'Checkbox value', 'mytheme' ); ?></label>
</p>
<p>
<label for="mytheme_custom_select"><?php esc_attr_e( 'Custom Select', 'mytheme' ); ?></label>
<select id="mytheme_custom_select" name="mytheme_custom_select">
<option value=""><?php esc_html_e( '&ndash; Select &ndash;', 'mytheme' ); ?></option>
<option value="<?php echo esc_attr( 'myvalue_1' ); ?>" <?php selected( $mytheme_custom_select, 'myvalue_1', true ); ?>><?php esc_html_e( 'My custom value 1', 'mytheme' ); ?></option>
<option value="<?php echo esc_attr( 'myvalue_2' ); ?>" <?php selected( $mytheme_custom_select, 'myvalue_2', true ); ?>><?php esc_html_e( 'My custom value 2', 'mytheme' ); ?></option>
<option value="<?php echo esc_attr( 'myvalue_3' ); ?>" <?php selected( $mytheme_custom_select, 'myvalue_3', true ); ?>><?php esc_html_e( 'My custom value 3', 'mytheme' ); ?></option>
</select>
</p>
<p>
<?php
$args_pages = array(
'depth' => 0,
'child_of' => 0,
'selected' => $mytheme_page_select,
'echo' => 1,
'name' => 'mytheme_page_select',
'id' => 'mytheme_page_select',
'class' => null,
'show_option_none' => null,
'show_option_no_change' => null,
'option_none_value' => esc_html__( '&ndash; Select &ndash;', 'mytheme' ),
);
?>
<label for="mytheme_page_select"><?php esc_attr_e( 'Pages Select', 'mytheme' ); ?></label>
<?php wp_dropdown_pages( $args_pages ); ?>
</p>
<p>
<?php $args_cat = array(
'show_option_all' => '',
'show_option_none' => '',
'option_none_value' => '-1',
'orderby' => 'ID',
'order' => 'ASC',
'show_count' => 0,
'hide_empty' => 1,
'child_of' => 0,
'exclude' => '',
'include' => '',
'echo' => 1,
'selected' => $mytheme_category_select,
'hierarchical' => 0,
'name' => 'mytheme_category_select',
'id' => 'mytheme_category_select',
'class' => 'postform',
'depth' => 0,
'tab_index' => 0,
'taxonomy' => 'category',
'hide_if_empty' => false,
'value_field' => 'term_id',
); ?>
<label for="mytheme_category_select"><?php esc_attr_e( 'Category Select', 'mytheme' ); ?></label>
<?php
wp_dropdown_categories( $args_cat );
?>
</p>
</div>
<div class="right_part">
<p>
<label for="mytheme_datepicker">
<?php esc_html_e( 'Pick a date', 'mytheme' ); ?>
</label>
<input type="text" id="mytheme_datepicker" name="mytheme_datepicker" value="<?php echo esc_attr( $mytheme_datepicker ); ?>" />
</p>
<p>
<label for="mytheme_slider"><?php esc_html_e( 'Choose a value:', 'mytheme' ); ?></label>
<input type="text" id="mytheme_slider" name="mytheme_slider" readonly value="<?php echo esc_attr( $mytheme_slider ); ?>"/>
<div id="slider-range"></div>
</p>
</div>
</div>
<?php
}
}

add_action( 'save_post', 'mytheme_save_metaboxes' );

if ( ! function_exists( 'mytheme_save_metaboxes' ) ) {
/**
* Save controls from the meta boxes
*
* @param int $post_id Current post id.
* @since 1.0.0
*/
function mytheme_save_metaboxes( $post_id ) {
/*
* We need to verify this came from the our screen and with proper authorization,
* because save_post can be triggered at other times. Add as many nonces, as you
* have metaboxes.
*/
if ( ! isset( $_POST['mytheme_control_meta_box_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['mytheme_control_meta_box_nonce'] ), 'mytheme_control_meta_box' ) ) { // Input var okay.
return $post_id;
}

// Check the user's permissions.
if ( isset( $_POST['post_type'] ) && 'page' === $_POST['post_type'] ) { // Input var okay.
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}

/*
* If this is an autosave, our form has not been submitted,
* so we don't want to do anything.
*/
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}

/* Ok to save */

if ( isset( $_POST['mytheme_input_field'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_input_field', sanitize_text_field( wp_unslash( $_POST['mytheme_input_field'] ) ) ); // Input var okay.
}

if ( isset( $_POST['mytheme_radio_value'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_radio_value', sanitize_text_field( wp_unslash( $_POST['mytheme_radio_value'] ) ) ); // Input var okay.
}

$mytheme_checkbox_value = ( isset( $_POST['mytheme_checkbox_value'] ) && '1' === $_POST['mytheme_checkbox_value'] ) ? 1 : 0; // Input var okay.
update_post_meta( $post_id, 'mytheme_checkbox_value', esc_attr( $mytheme_checkbox_value ) );

if ( isset( $_POST['mytheme_custom_select'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_custom_select', sanitize_text_field( wp_unslash( $_POST['mytheme_custom_select'] ) ) ); // Input var okay.
}

if ( isset( $_POST['mytheme_page_select'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_page_select', sanitize_text_field( wp_unslash( $_POST['mytheme_page_select'] ) ) ); // Input var okay.
}

if ( isset( $_POST['mytheme_category_select'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_category_select', sanitize_text_field( wp_unslash( $_POST['mytheme_category_select'] ) ) ); // Input var okay.
}

if ( isset( $_POST['mytheme_datepicker'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_datepicker', sanitize_text_field( wp_unslash( $_POST['mytheme_datepicker'] ) ) ); // Input var okay.
}

if ( isset( $_POST['mytheme_slider'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_slider', sanitize_text_field( wp_unslash( $_POST['mytheme_slider'] ) ) ); // Input var okay.
}

}
}

Usage

The way you’ll use this on the front page is analogous to the previous cases

$date = get_post_meta( get_the_ID(), 'mytheme_datepicker', true );
$slider_values = get_post_meta( get_the_ID(), 'mytheme_slider', true );

Next time I’ll cover the promised image and gallery upload meta box controls, so stay tuned for that.

I hope you’ll find this tutorial interesting and useful. If you have any questions feel free to post them below, and as always, happy coding :)


Originally published at Made by Denis.