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.
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:
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
- 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.
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
.
The
load-post.php
andload-post-new.php
action hooks run whenever a post (of any type) is edited or created. When this happens, we register another action hookadd_meta_boxes
that will trigger the creation of our custom meta box, which is done via theadd_page_template_meta_boxes
callback function. Let's implement that function now.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 toadd_meta_box()
.Add controls here...
For now, I've added some placeholder text so we can see our new meta box on the page editor.
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.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 ); ?>/>
/>
/>
/>
/>
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.
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 asave_post
action, and then implement the callback to manually update post meta settings. 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 calledform-page-template.php
.Add the following code to the new file to create a blank page template.
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.
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.
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.
Mail sent successfully!"; } ?> 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.
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.
Note: If you're working on a local WordPress environment then the
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.
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
andjs
folders to your child theme root folder. Inside thecss
folder create astyle.css
file, and in thejs
folder create ascript.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.Notice how we're targeting the
page
post type and then either thepost-new.php
orpost.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 adisplayControls()
function that either hides or displays the form controls depending on the current value of the page template drop-down. We calldisplayControls()
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.
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()
.And now we need to implement the callback function to render our meta box.
ID, 'blog_category', true ); $categories = get_categories(); ?>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 useselected()
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.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 toselected()
and will match the first argument. This will causeselected()
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.Now, we need to create the blog posts page template. Inside your child themes
page-templates
folder, create a new file calledblog-page-template.php
, and add the following code.$paged, 'cat' => $cat, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => 'post' ); $blog_posts = new WP_Query($query_args); ?> have_posts() ) : ?> have_posts() ) : $blog_posts->the_post(); ?> The only real difference from our previous dynamic page template is the code inside the
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 todisplayControls()
.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.
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.
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.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 ofadd_meta_box()
you want to show on a custom post type. Instead of just specifyingpage
, we can pass in an array of required post types.Finally, update
save_page_template_meta()
to support multiple post types just as we did forenqueue_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.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 Envato 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.