Dynamic Page Templates in WordPress, Part 2

In part 1 of this tutorial series I introduced the basic concept of dynamic page templates and created a standard page template as a foundation for future work. A WordPress child theme based on the Twenty Seventeen parent theme was used to implement the page template.

Dynamic Page Templates in WordPress, Part 2

In this tutorial, you’ll learn more specific details about dynamic page templates and how you can use them in your own WordPress projects.

I’ll also show you step by step how to extend the page template from the first tutorial and turn it into your very first working dynamic page template!

Dynamic Page Templates: A More Flexible Approach

So how can we make page templates more flexible, and why would this be useful anyway?

Let’s say that you have a portfolio page template that outputs a gallery of individual portfolio items. Each item would be a fixed size, and only so many could fit onto each row.

We could, of course, add another page template to display portfolios at different sizes. But what if we wanted to show small, medium, or large portfolios? For this, we would need three separate page templates, with each one having different sizes for the portfolio items.

This could instantly be made more flexible by adding a drop-down control to select the portfolio size (i.e. Small, Medium, or Large). This is more convenient to the user as the page template box is less cluttered with unnecessary choices.

It makes more sense for the developer too, who only has one page template to maintain rather than three! This follows the Don’t Repeat Yourself (DRY) software development principle.

Should All Page Templates Be Dynamic?

It’s worth noting that not every page template would necessarily benefit from being dynamic. If you have a page template that does one thing, and one thing only, then that’s fine. It wouldn’t make sense to add custom controls, and extra logic, to a page template in this case.

But, as you’ll see by the end of this tutorial, many page templates would benefit greatly from being more flexible.

Another useful example for a dynamic page template would be a contact form. There are many custom controls that could be added to make this page template highly flexible.

For example, rather than output a fixed set of form fields, page template controls could be added to allow you to omit certain fields. Or maybe a Captcha field could be optionally displayed to prevent spam form submissions? There are so just many ways you could customize a contact form.

I’ll be creating a dynamic form page template in part 3 of this tutorial series. But first let’s get started with creating a general purpose dynamic page template.

Our First Dynamic Page Template

To begin with, we’ll be creating a basic dynamic page template to demonstrate how all the component parts fit together. Custom controls will be added to the page editor which will be used later on to control page template output.

The custom page template controls we’ll be adding shortly are:

  • Text box
  • Textarea
  • Check box
  • Radio buttons
  • Drop-down select box

Ideally, the controls should be added directly below the page template drop-down box to make it obvious they’re related to a page template.

However, WordPress doesn’t provide any hooks for this, so you’ll have to make do (for now) with adding custom page template controls to a separate custom meta box. In part three of this tutorial series, I’ll show you how to get round this problem.

Hooks are fundamental to WordPress development. They allow developers to extend the code base without having to resort to editing core files, which is generally considered a bad idea. This is because any custom code would be wiped out whenever a WordPress update is performed (which can happen quite regularly).

To display our meta box on the page editor screen, add the load-post.php and load-post-new.php hooks to the child theme init() method we created in part 1.

<?php
add_action( 'load-post.php', array( $this, 'page_template_meta_box' ) );
add_action( 'load-post-new.php', array( $this, 'page_template_meta_box' ) );
add_action( 'save_post', 'save_page_template_meta', 10, 2 );
}

We use two WordPress hooks to ensure the meta box displays on the page editor, whether you’re creating a new page or editing an existing one. There is also a save_post hook that handles saving of post meta data, which I’ll cover a bit later on.

Add the following four class methods to create, display, and save data for the meta box.

/* Add meta box hook. */
public function page_template_meta_box() {
  add_action( 'add_meta_boxes', array( $this, 'add_page_template_meta_box' ) );
}
/* Register meta box. */
public function add_page_template_meta_box() {
  add_meta_box(
    'page-template-meta-box',
	esc_html__( 'Page Template Meta Box', 'twenty-seventeen-child' ),
	array( $this, 'display_page_template_meta_box' ),
	'page',
	'side',
	'default'
  );
}
/* Render meta box on the page editor. */
public function display_page_template_meta_box($object) {  
  wp_nonce_field( basename( __FILE__ ), 'page_template_meta_box_nonce' );
}

I won’t go into too much detail here about WordPress meta boxes, as that could be a whole tutorial on its own, but note the following points about the code I added above:

  • The page_template_meta_box() and add_page_template_meta_box() class methods register the meta box with WordPress.
  • In add_page_template_meta_box(), the 'page' parameter specifies that this meta box will only be displayed on the ‘page’ post type editor in the WordPress admin.
  • The display_page_template_meta_box() class method renders the meta box and sets up a nonce to make the form controls more secure.

If all is well, you should now have a meta box displayed on the page editor, as shown below.

Dynamic Page Templates in WordPress, Part 2

It’s a bit empty at the moment, though, so let’s add some controls.

Adding Custom Controls

If you’ll recall from above, we’re going to add a text box, text area, checkbox, radio button, and select box controls to the meta box. Start by adding the following code to the display_page_template_meta_box() method underneath the nonce function.

<?php
$text = get_post_meta( $object->ID, 'page_template_text', true );
$textarea = get_post_meta( $object->ID, 'page_template_textarea', true );
$checkbox = get_post_meta( $object->ID, 'page_template_chk', true );
$radio = get_post_meta( $object->ID, 'page_template_radio', true );
$select = get_post_meta( $object->ID, 'page_template_select', true );

This retrieves the current values of our meta box controls and stores them in local variables. Now add the following HTML directly after, to render the meta box controls.

  ?>
  <div>
	<p>
	    <label for="page-template-text"><?php _e( "Text Control", 'twenty-seventeen-child' ); ?></label><br>
	    <input class="widefat" type="text" name="page-template-text" id="page-template-text" value="<?php echo esc_attr( $text ); ?>" />
	</p>

	<p>
	    <label for="page-template-textarea"><?php _e( "Textarea Control", 'twenty-seventeen-child' ); ?></label><br>
	    <textarea rows="5" class="widefat" name="page-template-textarea" id="page-template-textarea"><?php echo esc_attr( $textarea ); ?></textarea>
	</p>

	<p>
	    <input type="checkbox" name="page-template-chk" id="page-template-chk" value="1" <?php checked($checkbox, true); ?> />&nbsp;<label for="page-template-chk"><?php _e( "Checkbox Control", 'twenty-seventeen-child' ); ?></label><br>
	</p>

	<p>
	    <label for="page-template-align"><?php _e( "Radio Button Control", 'twenty-seventeen-child' ); ?></label><br>
	    <input type="radio" name="page-template-align" id="rdo-left" value="left" <?php checked( $radio, 'left' ); ?> ><label for="rdo-left"><?php _e( 'Left', 'twenty-seventeen-child' ); ?></label><br>
	    <input type="radio" name="page-template-align" id="rdo-right" value="right" <?php checked( $radio, 'right' ); ?> ><label for="rdo-right"><?php _e( 'Right', 'twenty-seventeen-child' ); ?></label><br>
	    <input type="radio" name="page-template-align" id="rdo-center" value="center" <?php checked( $radio, 'center' ); ?> ><label for="rdo-center"><?php _e( 'Center', 'twenty-seventeen-child' ); ?></label><br>
	</p>

	<p>
	    <label for="page-template-select">Dropdown</label>
	    <select name="page-template-select" class="widefat">
		    <option	value='one' <?php selected( 'one', $select ); ?>><?php _e( 'One', 'twenty-seventeen-child' ); ?></option>
		    <option	value='two' <?php selected( 'two', $select ); ?>><?php _e( 'Two', 'twenty-seventeen-child' ); ?></option>
		    <option	value='three' <?php selected( 'three', $select ); ?>><?php _e( 'Three', 'twenty-seventeen-child' ); ?></option>
		    <option	value='four' <?php selected( 'four', $select ); ?>><?php _e( 'Four', 'twenty-seventeen-child' ); ?></option>
	    </select>
	</p>
</div><?php

Each control is contained inside a paragraph tag, and its current value is updated via the local variable we created earlier. This ensures that the meta box controls always display the correct settings.

However, this won’t happen unless we save the current meta box control data to the WordPress database.

A little earlier, I registered a hook to execute a class method every time the page editor was updated. Let’s add that method to our child theme class now.

<?php
/* Save meta box data. */
public function save_page_template_meta( $post_id, $post ) {

    if ( ! ( isset( $_POST[ 'page_template_meta_box_nonce' ] ) && wp_verify_nonce( $_POST[ 'page_template_meta_box_nonce' ], basename( __FILE__ ) ) ) ) {
	    return $post_id;
    }

    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return $post_id;
    }

    if( 'page' != $post->post_type ) {
	    return $post_id;
    }

    $page_template_text_value = isset( $_POST[ 'page-template-text' ] ) ? $_POST[ 'page-template-text' ] : '';
    update_post_meta( $post_id, 'page_template_text', $page_template_text_value );

    $page_template_textarea_value = isset( $_POST[ 'page-template-textarea' ] ) ? $_POST[ 'page-template-textarea' ] : '';
    update_post_meta( $post_id, 'page_template_textarea', $page_template_textarea_value );

    $page_template_chk_value = isset( $_POST[ 'page-template-chk' ] ) ? $_POST[ 'page-template-chk' ] : '';
    update_post_meta( $post_id, 'page_template_chk', $page_template_chk_value );

    $page_template_radio_value = isset( $_POST[ 'page-template-align' ] ) ? $_POST[ 'page-template-align' ] : '';
    update_post_meta( $post_id, 'page_template_radio', $page_template_radio_value );

    $page_template_select_value = isset( $_POST[ 'page-template-select' ]) ? $_POST[ 'page-template-select' ] : '';
    update_post_meta( $post_id, 'page_template_select', $page_template_select_value );
}

The save_page_template_meta() class method handles saving the meta box control data. It only saves the meta box data if the nonce is verified, the current users can edit posts, and we are on the page editor admin screen.

If those conditions are satisfied, then we extract the data for each control which is stored in the global $_POST variable. This variable is set every time a form is submitted.

Finally, the meta box control data is saved to the WordPress database as meta data for the current page.

With the custom page template controls added, our meta box should look like this.

Dynamic Page Templates in WordPress, Part 2

Enter some text for the text box and textarea, and make selections for the check box, radio button, and select box. Hit update to save your changes, and when the page editor reloads, your meta box controls should show the data you just entered.

Dynamic Page Templates in WordPress, Part 2

The full source code for the child theme functions.php file is shown below.

<?php

/**
 * Twenty Seventeen child theme class.
 *
 * DPT = D[ynamic] P[age] T[emplates].
 */
class DPT_Twenty_Seventeen_Child {

    /**
	 * Register hooks.
	 */
	public function init() {
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_parent_theme_styles' ) );
		add_action( 'load-post.php', array( $this, 'page_template_meta_box' ) );
		add_action( 'load-post-new.php', array( $this, 'page_template_meta_box' ) );
		add_action( 'save_post', array( $this, 'save_page_template_meta' ), 10, 2 );
	}

	/* Enqueue parent theme styles. */
	public function enqueue_parent_theme_styles() {
		wp_enqueue_style( 'twenty-seventeen-css', get_template_directory_uri() . '/style.css' );
	}

	/* Add meta box hook. */
	public function page_template_meta_box() {
	  add_action( 'add_meta_boxes', array( $this, 'add_page_template_meta_box' ) );
	}

	/* Register meta box. */
	public function add_page_template_meta_box() {
	  add_meta_box(
		'page-template-meta-box',
		esc_html__( 'Page Template Meta Box', 'twenty-seventeen-child' ),
		array( $this, 'display_page_template_meta_box' ),
		'page',
		'side',
		'default'
	  );
	}

	/* Render meta box on the page editor. */
	public function display_page_template_meta_box( $object ) {

	  wp_nonce_field( basename( __FILE__ ), 'page_template_meta_box_nonce' );

	  $text = get_post_meta( $object->ID, 'page_template_text', true );
	  $textarea = get_post_meta( $object->ID, 'page_template_textarea', true );
      $checkbox = get_post_meta( $object->ID, 'page_template_chk', true );
      $radio = get_post_meta( $object->ID, 'page_template_radio', true );
      $select = get_post_meta( $object->ID, 'page_template_select', true );
	  ?>
	  <div>
		<p>
			<label for="page-template-text"><?php _e( "Text Control", 'twenty-seventeen-child' ); ?></label><br>
			<input class="widefat" type="text" name="page-template-text" id="page-template-text" value="<?php echo esc_attr( $text ); ?>" />
		</p>

		<p>
			<label for="page-template-textarea"><?php _e( "Textarea Control", 'twenty-seventeen-child' ); ?></label><br>
			<textarea rows="5" class="widefat" name="page-template-textarea" id="page-template-textarea"><?php echo esc_attr( $textarea ); ?></textarea>
		</p>

		<p>
			<input type="checkbox" name="page-template-chk" id="page-template-chk" value="1" <?php checked($checkbox, true); ?> />&nbsp;<label for="page-template-chk"><?php _e( "Checkbox Control", 'twenty-seventeen-child' ); ?></label><br>
		</p>

		<p>
			<label for="page-template-align"><?php _e( "Radio Button Control", 'twenty-seventeen-child' ); ?></label><br>
			<input type="radio" name="page-template-align" id="rdo-left" value="left" <?php checked( $radio, 'left' ); ?> ><label for="rdo-left"><?php _e( 'Left', 'twenty-seventeen-child' ); ?></label><br>
			<input type="radio" name="page-template-align" id="rdo-right" value="right" <?php checked( $radio, 'right' ); ?> ><label for="rdo-right"><?php _e( 'Right', 'twenty-seventeen-child' ); ?></label><br>
			<input type="radio" name="page-template-align" id="rdo-center" value="center" <?php checked( $radio, 'center' ); ?> ><label for="rdo-center"><?php _e( 'Center', 'twenty-seventeen-child' ); ?></label><br>
		</p>

		<p>
			<label for="page-template-select">Dropdown</label>
			<select name="page-template-select" class="widefat">
				<option	value='one' <?php selected( 'one', $select ); ?>><?php _e( 'One', 'twenty-seventeen-child' ); ?></option>
				<option	value='two' <?php selected( 'two', $select ); ?>><?php _e( 'Two', 'twenty-seventeen-child' ); ?></option>
				<option	value='three' <?php selected( 'three', $select ); ?>><?php _e( 'Three', 'twenty-seventeen-child' ); ?></option>
				<option	value='four' <?php selected( 'four', $select ); ?>><?php _e( 'Four', 'twenty-seventeen-child' ); ?></option>
			</select>
		</p>
	  </div><?php
	}

	/* Save meta box data. */
	public function save_page_template_meta( $post_id, $post ) {

	  if ( ! ( isset( $_POST[ 'page_template_meta_box_nonce' ] ) && wp_verify_nonce( $_POST[ 'page_template_meta_box_nonce' ], basename( __FILE__ ) ) ) ) {
		return $post_id;
	  }

	  if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return $post_id;
	  }

	  if( 'page' != $post->post_type ) {
		return $post_id;
	  }

	  $page_template_text_value = isset( $_POST[ 'page-template-text' ] ) ? $_POST[ 'page-template-text' ] : '';
	  update_post_meta( $post_id, 'page_template_text', $page_template_text_value );

  	  $page_template_textarea_value = isset( $_POST[ 'page-template-textarea' ] ) ? $_POST[ 'page-template-textarea' ] : '';
	  update_post_meta( $post_id, 'page_template_textarea', $page_template_textarea_value );

  	  $page_template_chk_value = isset( $_POST[ 'page-template-chk' ] ) ? $_POST[ 'page-template-chk' ] : '';
	  update_post_meta( $post_id, 'page_template_chk', $page_template_chk_value );

   	  $page_template_radio_value = isset( $_POST[ 'page-template-align' ] ) ? $_POST[ 'page-template-align' ] : '';
	  update_post_meta( $post_id, 'page_template_radio', $page_template_radio_value );

   	  $page_template_select_value = isset( $_POST[ 'page-template-select' ]) ? $_POST[ 'page-template-select' ] : '';
	  update_post_meta( $post_id, 'page_template_select', $page_template_select_value );
	}
}

$ts_child_theme = new DPT_Twenty_Seventeen_Child();
$ts_child_theme->init();

The final piece of the puzzle is to use the meta box control data in our page template on the front end. Open up the test-page-template.php file we created in part 1 and replace the contents with this updated code.

<?php
/**
 * Template Name: Test Page Template
 *
 * @package WordPress
 * @subpackage Twenty_Seventeen
 * @since 1.0
 */

get_header(); ?>

<div class="wrap">
    <div id="primary" class="content-area">
		<main id="main" class="site-main" role="main">

			<?php
			while ( have_posts() ) : the_post();

				$text = get_post_meta( get_the_ID(), 'page_template_text', true );
				$textarea = get_post_meta( get_the_ID(), 'page_template_textarea', true );
				$checkbox = get_post_meta( get_the_ID(), 'page_template_chk', true );
				$radio = get_post_meta( get_the_ID(), 'page_template_radio', true );
				$select = get_post_meta( get_the_ID(), 'page_template_select', true );

				echo "<p>Text Box: " . $text . "</p>";
				echo "<p>Text Area: " . $textarea . "</p>";
				echo "<p>Checkbox: " . $checkbox . "</p>";
				echo "<p>Radio Buttons: " . $radio . "</p>";
				echo "<p>Dropdown: " . $select . "</p>";

				echo "<h2>Sitemap</h2>";
				echo "<ul>" . wp_list_pages( array( 'title_li' => '' ) ) . "</ul>";

			endwhile; // End of the loop.
			?>

		</main><!-- #main -->
	</div><!-- #primary -->
</div><!-- .wrap -->

<?php get_footer();

Make sure the ‘Test Page Template’ is the currently selected page template and view the page on the front end.

Dynamic Page Templates in WordPress, Part 2

As you can see, the page template now includes the values just set for the meta box controls on the page editor. This is core to the rest of the tutorial as we’ll build on this basic example and create various examples of fully working dynamic page templates that you can use in your own WordPress projects.

Conclusion

In this tutorial, we covered how to build a functioning dynamic page template. At the moment, even though it’s functional, our basic page template isn’t terribly useful.

In the third and final part of this tutorial series, I’ll show you how to build various dynamic page templates, from start to finish, that you can use (and expand upon) in your own WordPress projects.

If you have any questions, please leave me a message in the comments below. I’d love to hear your thoughts on the tutorial.