Customization

Creating User Friendly Custom Fields by Modifying the Post Page

March 30, 2009   ·   By   ·   19 Comments   ·   Posted in Customization, Functions, Wordpress, Writing Plugins

Ralph over at ForTheLose.org wrote up a great post on how to use custom fields in wordpress a few days ago, and I thought I’d expand on it with another feature that I use for clients quite often.  Custom fields are a great way to store extra post information, and they can really make for some more interesting layouts.

Sometimes, however, they can be a little much for a non-tech savvy client or user to handle – they’ve got to choose the right custom field from the dropdown, or type it in properly, or it won’t work – and what if you’ve got a few specific option values you’d like them to choose from for the custom field?  Sometimes it’s just too risky to expect the user to be able to handle it.

With this in mind, I started looking into how to modify the Add Post and Edit Post pages (ok, they’re really the same page).  As it turns out, its not too terribly difficult to add in a few options of your own on the post page, giving the user attractive, easy to use access to more advanced features of your theme or plugin.  A few examples:

Allowing the user to choose his own sidebars

picture-3This one was a fun project for me – I had a client who wanted to  have a different sidebar on his “testimonials” page (he was a consultant).  Now, I started to just set up a new post template for him, but I realized that he really wasnt going to want to call me every time he wanted to add a new testimonial – that would be ridiculous.  I had really helped sell him on the idea of using wordpress so he wouldn’t need to call me a few times a month to make small changes to his website – so forcing him to do so to change his testimonials wouldnt be acceptable.

After giving that some thought, I decided to make another widgetized sidebar for use with the testimonials page template.  This way, he could add text widgets to just that sidebar.  I got to work, and got it set up, and decided that wasn’t quite right either – what if down the road he decided he wanted to show those testimonials on a sales page, or some other page I hadn’t thought of?  He’d be out of luck.  I decided what I really wanted to do was set up a number of sidebars (I ended up with 6), and allow him to choose the sidebars (it was a 3 column theme) for each page.  Custom Fields were the perfect fit for the job, but that meant he’d have to remember the name of each of the sidebars when he wanted to change them.  A custom post options box seemed just the right fit.  And it did – he was really excited about the functionality, and I was excited to have learned something new.

Required Post Info for a Theme Site

picture-22Another project tasked me with creating a wordpress theme site for a client.  He wanted each theme post to have a number of fields relating to its layout and structure – things like number of columns, if it was widgetized, etc.  I decided that the easy way would be to just put the info into the post body, maybe in some custom tags or something – but wouldn’t it be cool if users could search by this info?  I got to work.  What I ended up with was a custom post options box with all the relevant theme info – dropdowns and checkboxes, which removed any possibility of error from user input.  The client was happy, and as always, I had a good time doing it.

Implementation

Before we get to how to actually implement this, a word of warning:  with great power comes great responsibility.  In this case, you’ve got great power to annoy your users – if you’re releasing a plugin with minor functionality that probably doesnt need a decision made on a per-post basis, it’s probably best to leave it off of the post page.  There is no sense in cluttering and slowing down the post page if it isn’t completely necessary.

The actual implementation of this isn’t too hairy – we’ve basically got 4 parts:

  1. Tell WordPress what we want
  2. Display the options box
  3. Retrieve the options
  4. Insert them into custom fields

Finding Room on the Post Page with add_meta_box()

First things first – we’ve got to reserve a spot on the oh-so-exclusive edit post page.  Here’s how we do it:

add_action('admin_menu', 'my_post_options_box');

function my_post_options_box() {
add_meta_box('post_info', 'Post Information', 'custom_post_info', 'post', 'side', 'high');
}

First things first – we hook into the admin_menu action, with our callback function my_post_options_box().  With this callback function, we initiate the actual box – with the add_meta_box() call.  Add meta box works like this:

add_meta_box(‘id’, ‘title’, ‘callback’, ‘page’, ‘context’, ‘priority’)

  • id – This is the identifier for your box.  Choose wisely.
  • title – This holds the string that will display to the user, so make it informative, and properly formatted.
  • callback – This references the function that is actually going to display your  option box.
  • page – This determines where your post box shows up and your options are post, page, or link.    As far as I know, its not possible to call more than a single option here – so if you want your box on posts and pages, you’ll need to make the call twice.
  • context – Where your post box will end up – your options are normal, advanced, and side.  Side shows up on the right side (only available since 2.7), obviously, and, as far as I can tell, both normal and advanced show up below the post box.
  • priority – Where (vertically) your box will show up.  Your options are high and low, low putting your option at the bottom of the other boxes, and high putting it at the top.  Both screenshots shown above are set to side and high priority.

Displaying Your Options Box

Now we’re going to actually display the options on the page:

function custom_post_info() {
global $post;
?>
<fieldset id="mycustom-div">
<div>
<p>
<label for="cpi_dropdown_options" >Dropdown Options:</label><br />
<select name="cpi_dropdown_options" id="cpi_dropdown_options">
<option<?php selected( get_post_meta($post->ID, 'cpi_dropdown_options', true), 'Option 1' ); ?>>Option 1</option>
<option<?php selected( get_post_meta($post->ID, 'cpi_dropdown_options', true), 'Option 2' ); ?>>Option 2</option>
<option<?php selected( get_post_meta($post->ID, 'cpi_dropdown_options', true), 'Option 3' ); ?>>Option 3</option>
</select>
<br />
<br />
<label for="cpi_text_option">Text Option:</label><br />
<input type="text" name="cpi_text_option" id="cpi_text_option" value="<?php echo get_post_meta($post->ID, 'cpi_text_option', true); ?>">
</p>
</div>
</fieldset>
<?php
}

Nothing too fancy going on here – but a couple of important points – we grab the $post global at the top – we do this so we can check against already stored values in the database (for editing posts instead of creating them).   The entire section is wrapped in a fieldset, a div, and a paragraph – this ensures that the display matchces the rest of the boxes.  Other than that, its a pretty standard form, we’ve got a dropdown menu and a text box waiting to be populated.

Retrieving the Options and Inserting Them Into Custom Fields

We’ve had the user set the options, all that is left is to retrieve them on “Save Draft” or “Publish”, and put them where they really belong – in custom fields.  This is a little trickier than I expected it to be (and maybe I’ve made it too difficult.  If somebody can think of a way to simplify this, please let me know).

add_action('save_post', 'custom_add_save');
function custom_add_save($postID){
// called after a post or page is saved
if($parent_id = wp_is_post_revision($postID))
{
$postID = $parent_id;
}

if ($_POST['cpi_dropdown_options']) {
update_custom_meta($postID, $_POST['cpi_dropdown_options'], 'cpi_dropdown_options');
}
if ($_POST['cpi_text_option']) {
update_custom_meta($postID, $_POST['cpi_text_option'], 'cpi_text_option');
}
}

The first thing we do is hook into the save_post action.  This is called on both saving and publishing, so we should be covered.  Our custom_add_save takes a post id as input from the save_post action, and we need it to tell wordpress where to save our options.

Next, we need to figure out if the post id we got matches a real post, or just a revision.  Revisions are stored in the database right alongside posts, with their own id and all – so we use the wp_is_post_revision function to determine if we’re working with a revision.  If we are, wp_is_post_revision conveniently hands off the id of the parent post for us to use.

Now we’ll check to see if our options were posted.    If we find a $_POST option matching one of our fields, we use that to update the custom field relating to it – but since we have to do this a few times, I’ve sent the actual work of updating the custom fields to another function – update_custom_meta().  Here, we check if the post_meta (custom field) is already set – if it is, we just update it.  If it isn’t, we add a new one.

All Together Now


// ===================
// = POST OPTION BOX =
// ===================

add_action('admin_menu', 'my_post_options_box');

function my_post_options_box() {
add_meta_box('post_info', 'Post Information', 'custom_post_info', 'post', 'side', 'high');
}

//Adds the actual option box
function custom_post_info() {
global $post;
?>
<fieldset id="mycustom-div">
<div>
<p>
<label for="cpi_dropdown_options" >Dropdown Options:</label><br />
<select name="cpi_dropdown_options" id="cpi_dropdown_options">
<option<?php selected( get_post_meta($post->ID, 'cpi_dropdown_options', true), 'Option 1' ); ?>>Option 1</option>
<option<?php selected( get_post_meta($post->ID, 'cpi_dropdown_options', true), 'Option 2' ); ?>>Option 2</option>
<option<?php selected( get_post_meta($post->ID, 'cpi_dropdown_options', true), 'Option 3' ); ?>>Option 3</option>
</select>
<br />
<br />
<label for="cpi_text_option">Text Option:</label><br />
<input type="text" name="cpi_text_option" id="cpi_text_option" value="<?php echo get_post_meta($post->ID, 'cpi_text_option', true); ?>">
</p>
</div>
</fieldset>
<?php
}

add_action('save_post', 'custom_add_save');
function custom_add_save($postID){
// called after a post or page is saved
if($parent_id = wp_is_post_revision($postID))
{
$postID = $parent_id;
}

if ($_POST['cpi_dropdown_options']) {
update_custom_meta($postID, $_POST['cpi_dropdown_options'], 'cpi_dropdown_options');
}
if ($_POST['cpi_text_option']) {
update_custom_meta($postID, $_POST['cpi_text_option'], 'cpi_text_option');
}
}

function update_custom_meta($postID, $newvalue, $field_name) {
// To create new meta
if(!get_post_meta($postID, $field_name)){
add_post_meta($postID, $field_name, $newvalue);
}else{
// or to update existing meta
update_post_meta($postID, $field_name, $newvalue);
}
}
?&gt;

picture-5

And there you have it.  Quick, go make something interesting!

19 Comments
  1. Hi. Congratulations, you have written the most comprehensive tutorial about the add_meta_box().

    However, and please excuse me if I am wrong, it looks that it is missing something in the final part of your code. For example, where “$parent_id” and “$postID” is coming from?

    function custom_add_save($postID){
    // called after a post or page is saved
    if($parent_id = wp_is_post_revision($postID)){
    $postID = $parent_id;
    }

    if($_POST['columns']){
    update_custom_meta($postID, $_POST['columns'], ‘columns’);
    }
    }

  2. Hi Rogério –
    Thanks for the kind words, I’m glad you liked the post. To answer your question:

    $postID is passed in via this line:

    add_action(‘save_post’, ‘custom_add_save’);

    The save_post action hook (see a complete list of action hooks here) passes in the post id of the post that is being saved. So whenever you save a post, this action will call your function (in this case, custom_add_save), with the post id as the first parameter.

    We then use the post id that has been passed in to find the parent id with the function wp_is_post_revision(). wp_is_post_revision will return a post id (which will evaluate to true in an if statement) if the post is a revision, or false if it is the original post. So, with this line:
    if($parent_id = wp_is_post_revision($postID)){
    $postID = $parent_id;
    }
    we check if the post is a revision, and if it is, we set the $postID variable to the post id of the parent post, because this is the one we want.

    At this point, however, something is definitely wrong – I forgot to change the $_POST variables that get checked to match the rest of the code. At this point, you want to check each of your options to see if they’ve been posted – instead of if($_POST['columns']), it should be
    if ($_POST['cpi_dropdown_options'])
    and
    if ($_POST['cpi_text_option']) {

    I’ve changed the code in the post to be accurate.

    Good luck!

  3. Peter, thank you for you prompt reply.

    It looks there’s a missing “<?” on line 25, among “” and “}”

    The explanation about “add meta box function” in the WordPress Codex http://codex.wordpress.org/Function_Reference/add_meta_box
    is much more complicated, though there is a security verification we are not doing.

    Well, I have changed your code a bit in order to achieve my needs and added all reference I could find about the needed functions: http://pastie.org/440128

    Once more, thank you.

  4. Thanks Rogério –
    I have an enormous amount of trouble getting source code to format properly: blocks of php were being removed from my code. Hopefully I’ll get better at it soon.

    In the meantime, I’ve updated the code, and tested the posted version – it should be working now.

    Thanks for the reference you posted – should be a great resource for anyone who needs more help with this.

  5. Have you ever tried pastie.org? I found it the best solution for pasting the code on the net.

  6. I guess the css of the site is not loading…

  7. Hey Rogerio –

    The CSS disappearing is no accident – read about it here: Annual CSS Naked Day. Should be back to normal after tomorrow.

    Thanks!

  8. a good tip.
    if you don’t like that the meta is automatically retrieved in the postedior then set the meta_key with underscore as prefix (“_metakeyname”) and it is not visible. :)

  9. Very cool Havard – I noticed in the core that if the meta value is an array, it won’t be displayed, but I had no idea you could do it this way as well. Thanks for the tip!

  10. Darrell

    Hello, I am trying to add a page to my website that would allow visitors to post things like their name and school, and have this info to post right away…

    Do you know where I can get this info????????

  11. Hi, Peter, almost a year later and here I am with this subject again. It’s because something odd just happened. I don’t know why but all the data from the custom fields were gone forever to blank. Would you by any chance be kind to just take a quick look on the code? It must be something very fool of mine but I just can’t find out…

    http://pastie.org/888229

    I can’t thank you enough.

  12. I discovered that after an error caused by a php all the data in the Custom Fields went blank again, just as the plugin was first activated… Any tips?

  13. The last line in the code should be ?> not ?> (that’s about the limit of my php)

    Am looking for similar code to allow a guest blogger to insert a URL without them knowing HTML.

  14. Thanks for the great guide.
    Mentioning which files you’re editing may save people some time.

  15. Hi. This tutorial helped me a lot. I had some trouble creating a dropdown list for displaying custom post_type taxonomy names… It took a while but here’s what ended up going into the functions.php file:


    Choose Slideshow:

    'slideshow' ) ); ?>
    <option value="ID, 'slideshow_slug', true); ?>" selected="selected">ID, 'slideshow_slug', true); echo $meta_values; ?>

    <option value="slug; ?>">slug; ?>

    ID, 'slideshow_slug', true); echo $meta_values; ?>

    Maybe this will help anyone who is looking to do something similar with the CPI template.

  16. perhaps this will display properly… i did not know that putting PHP code between functions would not display the input.

    Choose Slideshow:

    ‘slideshow’ ) ); ?>
    <option value="ID, ‘slideshow_slug’, true); ?>” selected=”selected”>ID, ‘slideshow_slug’, true); echo $meta_values; ?>

    <option value="slug; ?>”>slug; ?>

    ID, ‘slideshow_slug’, true); echo $meta_values; ?>

  17. Hi. This tutorial helped me a lot. I had some trouble creating a dropdown list for displaying custom post_type taxonomy names…

    /** Choose Slideshow:
    *
    * ‘slideshow’ ) ); ?>
    * <option value="ID, ‘slideshow_slug’, true); ?>” selected=”selected”>ID, ‘slideshow_slug’, true); echo $meta_values; ?>
    *
    * <option value="slug; ?>”>slug; ?>
    *
    *
    * /

  18. Hi Peter,

    I just added your code to a theme i’m working on. I see it’s creating the custom field but the drop down value isn’t displaying the selected option when editing the page. For example if I publish a post using Option 3 when I come back to edit the post it’s default option is Option 1. Though I do see option 3 in the custom meta field. How can I have it display the selected option when editing the post?

    Thanks
    Pete

Submit a Comment