Dynamic Page Templates in WordPress, Part 3

In the first two parts of this tutorial series, we covered what dynamic page templates were and why they were needed. We also looked at the code required to implement them.

Dynamic Page Templates in WordPress, Part 3

In this third and final tutorial in the series, I’ll be creating two examples of fully working dynamic page templates you can use in your own projects. These were specifically chosen to be easily extendable to suit your own needs, and are intended as inspiration for any other type of dynamic page templates you can think of.

The two dynamic page templates we’ll be taking a look at shortly are:

  • Simple Contact Form
  • Blog Post Archive

As well as implementing our page templates, I’ll also show you how to add extra polish, via custom CSS and JavaScript, to make interaction much more intuitive to end users.

Plus, we’ll take a look at how you can use page templates for any post type. Since WordPress 4.7, you can specify the post type a page template is associated with. We’ll see how you can modify an existing dynamic page template to take advantage of this new feature so it works with any post type.

We’ve a lot to cover in this tutorial, so let’s get started!

Theme Setup

We’ll be using a WordPress Twenty Seventeen child theme again, just as we did in part 2 of this tutorial series, to add our dynamic page template code. Let’s begin with a blank child theme.

Create a child theme folder called twentyseventeen-child and add the following files:

  • functions.php
  • style.css

Inside style.css, add:

/*
 Theme Name:   Twenty Seventeen Child
 Description:  Twenty Seventeen Child Theme
 Author:       David Gwyer
 Template:     twentyseventeen
 Version:      0.1
 License:      GNU General Public License v2 or later
 License URI:  http://www.gnu.org/licenses/gpl-2.0.html
 Text Domain:  twenty-seventeen-child
*/

And inside functions.php, add:

<?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' ) );
    }

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

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

Add the child theme to your WordPress theme directory as we did before. If you’re not sure about how to do this, please refer back to part 2 in this tutorial series.

We now have a working (blank) child theme ready for us to add our dynamic page template code to.

Dynamic Form Page Template

Our first real implementation of a dynamic page template is a simple contact form. We’ll be adding the following fields:

  • Heading
  • Name
  • Subject
  • Email
  • Phone number

These are text input fields, apart from the heading, which is a standard HTML heading tag.

Before we implement the actual page template, though, we need to add custom controls to the page editor that will allow us to modify page template output. Then, when we create the page template, it will be rendered according to the page editor control settings.

In part 2 of this tutorial series, I mentioned that there’s no easy way to add custom controls directly to the ‘Page Attributes’ meta box, where the page template drop-down is located.

Dynamic Page Templates in WordPress, Part 3

This means that we have to add our dynamic page template controls elsewhere for now. I’ll show you how to get around this limitation a little later on, with a little CSS and JavaScript magic. But for now, we’ll have to make do with adding our custom controls to a separate meta box.

In the DPT_Twenty_Seventeen_Child class, register two new action hooks in the init method, and a new method called page_template_meta_boxes.

<?php

/**
 * 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_boxes' ) );
    add_action( 'load-post-new.php', array( $this, 'page_template_meta_boxes' ) );
}

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

The load-post.php and load-post-new.php action hooks run whenever a post (of any type) is edited or created. When this happens, we register another action hook add_meta_boxes that will trigger the creation of our custom meta box, which is done via the add_page_template_meta_boxes callback function. Let’s implement that function now.

<?php

/* Register meta box. */
public function add_page_template_meta_boxes() {
    // Create meta box for our form dynamic page template
    add_meta_box(
        'form-page-template-meta-box',
        esc_html__( 'Form Page Template Meta Box', 'twenty-seventeen-child' ),
        array( $this, 'display_form_page_template_meta_box' ),
        'page',
        'side',
        'default'
    );
}

The actual rendering of the meta box controls will be handled via the display_form_page_template_meta_box callback function, which was specified above as one of the arguments to add_meta_box().

<?php

/* Render form meta box on the page editor. */
public function display_form_page_template_meta_box( $object ) {
    ?><p>Add controls here...</p><?php
}

For now, I’ve added some placeholder text so we can see our new meta box on the page editor.

Dynamic Page Templates in WordPress, Part 3

Remember from earlier that our form page template will have a heading and four text fields. There are many ways we could choose to customize the form output, but in our case let’s add check boxes for each field that will allow us to toggle their visibility. Update display_form_page_template_meta_box() to include the following code.

<?php

public function display_form_page_template_meta_box( $object ) {
    wp_nonce_field( basename( __FILE__ ), 'page_template_meta_box_nonce' );
    $heading = get_post_meta( $object->ID, 'pt_chk_form_heading', true );
    $name = get_post_meta( $object->ID, 'pt_chk_form_name', true );
    $subject = get_post_meta( $object->ID, 'pt_chk_form_subject', true );
    $email = get_post_meta( $object->ID, 'pt_chk_form_email', true );
    $phone = get_post_meta( $object->ID, 'pt_chk_form_phone', true );
    ?>

    <div id="form_pt_wrapper">
        <p>
            <input type="checkbox" name="pt_chk_form_heading" id="pt_chk_form_heading" value="1" <?php checked( $heading, true ); ?> />&nbsp;<label for="pt_chk_form_heading"><?php _e( "Display Heading", 'twenty-seventeen-child' ); ?></label><br>
        </p>
        <p>
            <input type="checkbox" name="pt_chk_form_name" id="pt_chk_form_name" value="1" <?php checked( $name, true ); ?> />&nbsp;<label for="pt_chk_form_name"><?php _e( "Display Name", 'twenty-seventeen-child' ); ?></label><br>
        </p>
        <p>
            <input type="checkbox" name="pt_chk_form_subject" id="pt_chk_form_subject" value="1" <?php checked( $subject, true ); ?> />&nbsp;<label for="pt_chk_form_subject"><?php _e( "Display Subject", 'twenty-seventeen-child' ); ?></label><br>
        </p>
        <p>
            <input type="checkbox" name="pt_chk_form_email" id="pt_chk_form_email" value="1" <?php checked( $email, true ); ?> />&nbsp;<label for="pt_chk_form_email"><?php _e( "Display Email", 'twenty-seventeen-child' ); ?></label><br>
        </p>
        <p>
            <input type="checkbox" name="pt_chk_form_phone" id="pt_chk_form_phone" value="1" <?php checked( $phone, true ); ?> />&nbsp;<label for="pt_chk_form_phone"><?php _e( "Display Phone Number", 'twenty-seventeen-child' ); ?></label><br>
        </p>
    </div>
    <?php
}

We include a nonce field for security which will be verified later on, just before we save the form values into the database.

Note: If for any reason the nonce value cannot be verified then the settings won’t be saved.

Then, current form values are retrieved from the database before the custom form fields are outputted inside the meta box.

Dynamic Page Templates in WordPress, Part 3

Currently, our check boxes won’t be saved when the post is updated. To make the form settings persist, we need to register a new hook in the init() method that triggers during a save_post action, and then implement the callback to manually update post meta settings.

<php

/**
 * 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_boxes' ) );
    add_action( 'load-post-new.php', array( $this, 'page_template_meta_boxes' ) );
    add_action( 'save_post', array( $this, 'save_page_template_meta' ), 10, 2 );
}
<?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;
    }

    $heading = isset( $_POST[ 'pt_chk_form_heading' ] ) ? $_POST[ 'pt_chk_form_heading' ] : '';
    update_post_meta( $post_id, 'pt_chk_form_heading', $heading );

    $name = isset( $_POST[ 'pt_chk_form_name' ] ) ? $_POST[ 'pt_chk_form_name' ] : '';
    update_post_meta( $post_id, 'pt_chk_form_name', $name );

    $subject = isset( $_POST[ 'pt_chk_form_subject' ] ) ? $_POST[ 'pt_chk_form_subject' ] : '';
    update_post_meta( $post_id, 'pt_chk_form_subject', $subject );

    $email = isset( $_POST[ 'pt_chk_form_email' ] ) ? $_POST[ 'pt_chk_form_email' ] : '';
    update_post_meta( $post_id, 'pt_chk_form_email', $email );

    $phone = isset( $_POST[ 'pt_chk_form_phone' ] ) ? $_POST[ 'pt_chk_form_phone' ] : '';
    update_post_meta( $post_id, 'pt_chk_form_phone', $phone );
}

Once the form nonce value and user permissions have been verified, along with a check to make sure we are on the correct post type, we can test for the posted form values and safely save the values to the database.

Our check boxes are now fully functional, so we can go ahead and implement the actual page template! Inside the root child theme folder, add a new folder called page-templates, and add to it a new file called form-page-template.php.

Add the following code to the new file to create a blank page template.

<?php

/**
 * Template Name: Form 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">

            <!-- Add page template code here. -->

        </main><!-- #main -->

    </div><!-- #primary -->

</div><!-- .wrap -->

<?php get_footer();

To reduce code complexity, our contact form doesn’t validate user input, and we’ve committed the usual form checks and validation, as we want to focus purely on making the form output dynamic without extraneous code.

First, we need to retrieve the dynamic contact form check box values.

<?php

$heading = get_post_meta( get_the_ID(), 'pt_chk_form_heading', true );
$name = get_post_meta( get_the_ID(), 'pt_chk_form_name', true );
$subject = get_post_meta( get_the_ID(), 'pt_chk_form_subject', true );
$email = get_post_meta( get_the_ID(), 'pt_chk_form_email', true );
$phone = get_post_meta( get_the_ID(), 'pt_chk_form_phone', true );

Then we can add in the form code. This is very similar for each form field. Let’s take a look at the name field code.

<?php if ( ! empty ( $name ) ) : ?>
    <label for="cf_name"><?php _e( 'Name', 'twenty-seventeen-child' ); ?></label>
    <input type="text" name="cf_name" id="cf_name"><br>
<?php endif; ?>

We test the value of the check box from the page template settings and only output the form field if it’s checked. Otherwise, nothing gets outputted. This is repeated for each form field.

Once the form is submitted, we send an email to the site admin and display a message on screen. Putting this all together, we have our final page template code.

<?php

/**
 * Template Name: Form 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();

            $heading = get_post_meta( get_the_ID(), 'pt_chk_form_heading', true );
            $name = get_post_meta( get_the_ID(), 'pt_chk_form_name', true );
            $subject = get_post_meta( get_the_ID(), 'pt_chk_form_subject', true );
            $email = get_post_meta( get_the_ID(), 'pt_chk_form_email', true );
            $phone = get_post_meta( get_the_ID(), 'pt_chk_form_phone', true );

            // Form submission handler
            if ( isset( $_POST['cf_submitted'] ) ) {
                $admin_email = get_bloginfo( 'admin_email' );
                $cf_subject = trim( $_POST['cf_subject'] );
                $headers = "From: [email protected]";
                mail( $admin_email, $subject, 'Dynamic contact form submitted!', $headers );
                echo "<p>Mail sent successfully!</p>";
            }
            ?>

            <form action="<?php the_permalink(); ?>" method="post">

                <?php if ( ! empty( $heading ) ) : ?>
                    <h2>Dynamic Form Page Template!</h2>
                <?php endif; ?>

                <?php if ( ! empty( $name ) ) : ?>
                    <label for="cf_name"><?php _e( 'Name', 'twenty-seventeen-child' ); ?></label>
                    <input type="text" name="cf_name" id="cf_name"><br>
                <?php endif; ?>

                <?php if ( ! empty( $subject ) ) : ?>
                    <label for="cf_subject"><?php _e( 'Subject', 'twenty-seventeen-child' ); ?></label>
                    <input type="text" name="cf_subject" id="cf_subject"><br>
                <?php endif; ?>

                <?php if ( ! empty( $email ) ) : ?>
                    <label for="cf_email"><?php _e( 'Email', 'twenty-seventeen-child' ); ?></label>
                    <input type="email" name="cf_email" id="cf_email"><br>
                <?php endif; ?>

                <?php if ( ! empty( $phone ) ) : ?>
                    <label for="cf_phone"><?php _e( 'Phone Number', 'twenty-seventeen-child' ); ?></label>
                    <input type="tel" name="cf_phone" id="cf_phone"><br>
                <?php endif; ?>

            <p><input type="hidden" name="cf_submitted" id="cf_submitted" value="true"><input class="submit button" type="submit" value="<?php _e( 'Submit Form', 'twenty-seventeen-child' ); ?>"></p>
        
            </form>
        
        <?php
        endwhile; // End of the loop.
        ?>

        </main><!-- #main -->

    </div><!-- #primary -->

</div><!-- .wrap -->

<?php get_footer();

To test everything’s working correctly, make sure all the form page template check boxes are checked and update the post. Then take a look at the page template on the front end.

Dynamic Page Templates in WordPress, Part 3

Now try unchecking some of the form page template check boxes. Only the fields specified are outputted. You have total control over how the form is displayed! In the screen shot below, I unchecked just the email and phone check boxes.

Dynamic Page Templates in WordPress, Part 3

Note: If you’re working on a local WordPress environment then the mail function may not actually send the email. It will only work if you have a mail server set up and running.

The form could easily be extended to add any number of controls of any type. For example, you could add an optional CAPTCHA field to your form, or be able to specify the order of fields outputted, or even the text for the form heading/labels. The point here is that you can use dynamic page templates to customize your form however you want. The possibilities are literally endless!

Organizing Our Page Template Controls

You might have noticed that there are a couple of usability issues with the admin controls for our dynamic page template. Functionally it’s fine, but ideally the dynamic page template controls should be in the same meta box as the page template drop-down.

Dynamic Page Templates in WordPress, Part 3

Remember that the reason we had to add our page template controls to a separate meta box in the first place was because there’s currently no WordPress hook available to add custom controls directly to the page template meta box.

Also, when a dynamic page template is selected, we only want the controls associated with that template to be visible. We can complete both requirements by adding some custom CSS and JavaScript to the page editor.

Specifically, we need to:

  • Hide the form meta box.
  • Wait for the admin editor page to fully load.
  • Move the form controls to the ‘Page Attributes’ meta box.
  • Only display the admin form controls if the associated page template is selected.

Start by adding css and js folders to your child theme root folder. Inside the css folder create a style.css file, and in the js folder create a script.js file. You can call these anything you want, though. Just remember to make a note of the filenames if so, and replace them in the enqueue scripts code.

Then, we need to enqueue both files only on the page editor screen. We don’t want them added to all admin pages. Register a new action hook in the init() method to load scripts on admin pages, and add the callback function to enqueue the script files.

<?php

/**
 * 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_boxes' ) );
    add_action( 'load-post-new.php', array( $this, 'page_template_meta_boxes' ) );
    add_action( 'save_post', array( $this, 'save_page_template_meta' ), 10, 2 );
    add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_editor_scripts' ) );
}

public function enqueue_editor_scripts($hook) {
    global $post_type;

    if ( 'page' != $post_type ) {
        return;
    }


    if ( 'post-new.php' != $hook && 'post.php' != $hook) {
        return;
    }

    wp_enqueue_script( 'dpt_script', get_stylesheet_directory_uri() . '/js/script.js', array( 'jquery' ) );
    wp_enqueue_style( 'dpt_styles', get_stylesheet_directory_uri() . '/css/style.css' );
}

Notice how we’re targeting the page post type and then either the post-new.php or post.php admin pages. So, basically, unless we’re on the page editor, our scripts will not get loaded, which is what we want.

Let’s go ahead now and start adding CSS and JavaScript to customize the form page template controls. Firstly, hide the whole form meta box with CSS by adding this to style.css:

#form-page-template-meta-box {
    display: none;
}

We could have done this with JavaScript, but we want the form meta box to be hidden immediately. If we did it via JavaScript, we’d have to wait until the page loaded, and you’d see a small flash as the meta box rendered on screen and then was hidden with JavaScript. So using CSS in this case is better.

Now for the JavaScript. Add this to script.js.

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

    var pt = $( "#page_template" );
    var form_controls = $( "#form_pt_wrapper" );

    // Move form controls to 'Page Attributes' meta box and hide them by default
    form_controls.insertAfter( '#page_template' ).hide();

    function displayControls( ptStr, sel ) {
        if ( ptStr !== pt.val() ) {
            sel.hide();
        } else {
            sel.toggle();
        }
    }

    // Call on page load
    displayControls( 'page-templates/form-page-template.php', form_controls );

    // Call every time drop down changes
    pt.on( 'change', function () {
        displayControls( this.value, form_controls );
    });

});

I’m not going to go into a huge amount of detail regarding the JavaScript, but here’s the overview.

We first cache a couple of CSS selectors and move the admin form controls to the Page Attributes meta box. Then, we have a displayControls() function that either hides or displays the form controls depending on the current value of the page template drop-down. We call displayControls() on page load, and then every time the drop-down is changed, to make sure we’re always in sync.

With the CSS and JavaScript added, the form page template controls are now displayed in the correct meta box, and only show if the associated page template is selected.

Dynamic Page Templates in WordPress, Part 3

This looks much better and is way more intuitive to the user. Because meta boxes can be moved around WordPress admin screens, our dynamic page template controls would not necessarily have been anywhere near the page template drop-down! We’ve solved this problem in an elegant way to ensure our controls always appear directly underneath the page template drop-down!

Blog Posts Dynamic Page Template

Our next dynamic page template displays a list of your latest blog posts. But rather than just list all posts, we’ll implement a list box (similar to a drop-down) to allow you to choose the post category. Not only that, you’ll also be able to select multiple post categories.

Start by adding a new meta box in add_page_template_meta_boxes().

<?php

// Create meta box for our blog posts dynamic page template
add_meta_box(
    'blog-page-template-meta-box',
    esc_html__( 'Blog Page Template Meta Box', 'twenty-seventeen-child' ),
    array( $this, 'display_blog_page_template_meta_box' ),
    'page',
    'side',
    'default'
);

And now we need to implement the callback function to render our meta box.

<?php

/* Render form meta box on the page editor. */
public function display_blog_page_template_meta_box( $object ) {
 
    $blog_category = get_post_meta( $object->ID, 'blog_category', true );
    $categories = get_categories();
    ?>

    <div id="blog_pt_wrapper">
        <label for="blog_category[]"><?php _e( 'Choose categories', 'twenty-seventeen-child' ); ?>:</label>
        <select style="height:inherit;" name="blog_category[]" multiple="multiple" size="10">
        <?php
            foreach ( $categories as $category ) {
                printf(
                    '<option value="%1$s" %2$s>%3$s (%4$s)</option>',
                    esc_attr( $category->cat_ID ),
                    esc_attr( selected( $category->cat_ID, $this->q($category->cat_ID, $blog_category) ) ),
                    esc_html( $category->cat_name ),
                    esc_html( $category->category_count )
                );
            }
        ?>
        </select>
    </div>

<?php
}

Let’s break this down. We first define a variable to hold the list of post categories selected (if any) from the last time the post was updated. Another variable stores an array of all existing categories.

Note: We already have a nonce field from our previous form page template, so we don’t need to use another one here, as we are on the same admin page.

We then loop over the list of the site categories, populating a drop-down control as we go. Any category that was previously selected is selected again to keep everything in sync.

You might have noticed, though, that one of the arguments to selected() is a function call. Normally we just use selected() to compare two values to determine whether to mark the current item as selected. However, because we can select more than one category, our database setting is always an array of values (even if we actually only select one category).

The function q() is a helper function which allows us to check the current list item against the array of saved categories.

<?php

public function q($sel, $opt) {
    if( is_array($opt) && in_array($sel, $opt) ) {
        return $sel;
    }
}

For each category, the category ID is passed into q() along with the saved category array. If the current category is in the list of saved categories then the current category is returned to selected() and will match the first argument. This will cause selected() to mark the current category as selected. This is an elegant way of handling multiple options for a single control.

All we need to do now is update save_page_template_meta() to handle saving blog post categories. Add this code to do just that.

<?php

// Save blog page template controls
$category = isset( $_POST[ 'blog_category' ] ) ? $_POST[ 'blog_category' ] : '';
update_post_meta( $post_id, 'blog_category', $category );

Now, we need to create the blog posts page template. Inside your child themes page-templates folder, create a new file called blog-page-template.php, and add the following code.

<?php

/**
 * Template Name: Blog 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
        $paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
        $category = get_post_meta( get_the_id(), 'blog_category', true );

        if( is_array($category) ) {
            $category = implode(",", $category);
        }

        if( !empty($category) ) {
            $cat = $category;
        } else {
            $cat = 0;
        }

        $query_args = array(
            'paged' => $paged,
            'cat' => $cat,
            'orderby' => 'date',
            'order' => 'DESC',
            'post_type' => 'post'
        );

        $blog_posts = new WP_Query($query_args);
        ?>

        <?php if ( $blog_posts->have_posts() ) : ?>

            <?php while ( $blog_posts->have_posts() ) : $blog_posts->the_post(); ?>
                <?php get_template_part( 'template-parts/post/content', get_post_format() ); ?>
            <?php endwhile; // end of the loop. ?>

        <?php endif; ?>

        </main><!-- #main -->

    </div><!-- #primary -->

</div><!-- .wrap -->

<?php get_footer();

The only real difference from our previous dynamic page template is the code inside the <main> HTML tag, so let’s take a closer look at that now.

We first set the value of the paged query variable, which is used to display posts over multiple pages, depending on the number of pages returned from our WordPress query. Then, we get the all the categories selected on the page editor meta box. The category array is converted to a string and given a default value if empty. A new WordPress query is then created and the results outputted in a standard loop.

The key thing here is that we’re able to control exactly which categories are passed to the query object, via the selections made on the page editor meta box.

All we have to do now is hide the blog categories meta box and move the list control to the Page Attributes meta box. Just like we did before.

Inside style.css update the styles to hide the blog posts meta box:

#form-page-template-meta-box, #blog-page-template-meta-box {
    display: none;
}

The script.js file needs a little more code to be added. Here is the fully updated file.

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

    var pt = $( "#page_template" );
    var form_controls = $( "#form_pt_wrapper" );
    var blog_post_controls = $( "#blog_pt_wrapper" );

    // Move form controls to 'Page Attributes' meta box and hide them by default
    form_controls.insertAfter( '#page_template' ).hide();
    blog_post_controls.insertAfter( '#page_template' ).hide();

    function displayControls( ptStr, sel ) {
        if ( ptStr !== pt.val() ) {
            sel.hide();
        } else {
            sel.toggle();
        }
    }   

    // Call on page load
    displayControls( 'page-templates/form-page-template.php', form_controls );

    displayControls( 'page-templates/blog-page-template.php', blog_post_controls );

    // Call every time drop down changes
    pt.on( 'change', function () {

        var controls;

        if( this.value === 'page-templates/form-page-template.php' ) {
            controls = form_controls;
            blog_post_controls.hide();
        } else if( this.value === 'page-templates/blog-page-template.php' ) {
            controls = blog_post_controls;
            form_controls.hide()
        } else {
            // hide all
            blog_post_controls.hide()
            form_controls.hide();
        }

        displayControls( this.value, controls );
    });

});

Most of the changes worth noting are in the .on('change') function. Because we now have more than one dynamic page template, we have to test to see which one was selected from the drop-down, and then pass this in the corresponding element selector to displayControls().

We also need to hide all other page template controls apart from the one selected. And if the default page template is displayed, we hide all page template controls. The JavaScript code could be optimized further, but as we only have two dynamic page templates active, it does a good enough job for our needs.

Dynamic Page Templates in WordPress, Part 3

With these modifications in place, we now have two functioning dynamic page templates with each of their associated controls displayed directly underneath the page template drop-down.

Page Templates for Everyone

Earlier I alluded to how, in WordPress 4.7+, you can now assign page templates to any post type. Prior to WordPress 4.7, you could only assign them to pages, but not anymore!

All you have to do is add an extra line to the comment block in the page template header, and specify a comma-separated list of post types you want the page template to be available on.

<?php

Template Post Type: page, movie, notes

The post type name needs to be the same as the slug entered when the post type was first registered, otherwise it will be ignored.

So, we can display page templates for any post type, but what about dynamic page templates? With just a few modifications, these can be supported too. Let’s take a look at what’s needed.

Luckily, apart from adding a line of code the top of your page template, all the necessary changes are in one file: functions.php.

Firstly, we need to enqueue the dynamic page template CSS and JavaScript not just on pages but for all post types we want to support dynamic page templates. So, in enqueue_editor_scripts(), we can do something like this.

<?php

if ( 'page' != $post_type && 'movie' != $post_type ) {
    return;
    }

Now, the dynamic page template scripts will be loaded on pages and the movie custom post type.

Next, in add_page_template_meta_boxes(), update each instance of add_meta_box() you want to show on a custom post type. Instead of just specifying page, we can pass in an array of required post types.

<?php

add_meta_box(
    'form-page-template-meta-box',
    esc_html__( 'Form Page Template Meta Box', 'twenty-seventeen-child' ),
    array( $this, 'display_form_page_template_meta_box' ),
    Array( 'page', 'movie' ),
    'side',
    'default'
);

Finally, update save_page_template_meta() to support multiple post types just as we did for enqueue_editor_scripts(). And that’s it!

By just following these few short steps, you can modify your dynamic page templates to work for any post type.

Note: For any WordPress sites running less than version 4.7, the Template Post Type header text will simply be ignored, and all page templates will be displayed for pages by default. If this is not desirable, you can add custom code to make your page templates backwards compatible.

<?php

function makewp_exclude_page_templates( $post_templates ) {

    if ( version_compare( $GLOBALS['wp_version'], '4.7', '<' ) ) {
        unset( $post_templates['templates/my-full-width-post-template.php'] );
    }

    return $post_templates;
}
add_filter( 'theme_page_templates', 'makewp_exclude_page_templates' );

This snippet is taken from the Make WordPress blog, where you can find more information about backwards compatibility and the new page templates feature in more detail.

Conclusion

We’ve covered quite a bit of ground in the final part of this tutorial series. Specifically, we’ve implemented two fully working dynamic page templates and used custom CSS and JavaScript to add some polish to the user experience.

Even though the dynamic post types introduced in this tutorial series have been relatively simple, it would be very easy to extend them to create powerful and flexible page templates. There’s just so much scope to add some next-level functionality. And, from WordPress 4.7, you’re not limited to developing them just for pages either.

If you’re looking for other utilities to help you build out your growing set of tools for WordPress or for code to study and become more well-versed in WordPress, don’t forget to see what we have available in ThemeKeeper Market.

Has this tutorial series inspired you to create dynamic page templates? If so, let me know in the comments below. I’d love to hear your ideas and how you might use them in your own projects.