This is one of the topics I like to talk about. I’ve not only written about it once, but I followed that up about a year later. But, here I am again, with new thoughts on things, and a different perspective.
The Old Way
The first time I wrote about the panels, I suggested setting up an array of information, that would be used to create the actual box. Then each piece of information would be saved into the database. That was a pretty good solution. Then, the second time, I thought “Hey, why not just store everything one big serialized array” That would stop me from having to define each variable before I could use the meta box information. Well, that worked out for a while, but then I started seeing some faults. When you imported a WordPress database, all of your custom field information would be totally borked. I’m sure there was a way to properly re-import the arrays, but not with my current setup (I think…). So there was the fault in storing the information in an array in the database. There was also a fault in storing the fields in a huge array. It was bulky, limited, and didn’t allow for very expansive boxes.
A Semi-Fix for using an Array
To try and fix the limitations of creating the meta boxes via a huge array, I created a switch loop that would loop through and check for different types. I had a few generic ones like text, textarea, select, etc (the ones you see in most places). But then I also added a case for something called “custom.”
<?php break; case 'custom' : include_once( TEMPLATEPATH . $callback ); ?>
So what that allowed me to do was define the metabox as being “custom”, and then I could create any custom fields, group of fields, in the $callback file I defined. Okay… that kind of fixes it. But then I realized, why not just do everything like that? Why limit the other options as well.
The New Way
My new way ditches the array of boxes entirely, and saves each piece of information in a separate field (which is still easily accessible (more on that later)), and allows for completely custom meta boxes.
All of the code posted below will go in
functions.phpin your theme. Also, I’m not going to explain each section line by line. Most of the code is re-used from the previous tutorials, so go check those out if you are confused.
Defining the meta box
Let’s create a function where we can initiate and add our meta boxes. We are using add_meta_box. I won’t go over what each parameter means, because I went over it in the other tutorials, and the codex are very helpful.
/**
* Create custom meta boxes.
*
* @since 1.0.0
*/
function sf_create_meta_box()
{
add_meta_box(
'sf-meta-box-subtitle',
__( 'Subtitle', 'sf' ),
'sf_meta_box_subtitle',
'post',
'normal',
'high'
);
}
So that function will add a meta box that I’m going to use for a subtitle. Now we need to define the callback function that will be called (sf_meta_box_subtitle). Unlike the old way, we now are building the HTML structure by hand. It may seem like more work, but really it’s just a few lines.
function sf_meta_box_subtitle()
{
global $meta; sf_post_meta( $post->ID );
?>
<input type="text" name="sf_meta[subtitle]" value="<?php echo $meta[ 'subtitle' ]; ?>" style="width:99%;" /><br />
<p><?php _e( 'Use this to add a subtitle to the post.', 'sf' ); ?></p>
<?php
}
You’ll see a function in there, sf_post_meta that we haven’t gone over yet. That will be used to get the the meta information on the front-end, as well as the back-end. More on that later.
We need to now call the original function in the admin panel, so it knows to load in the meta boxes. A simple one-liner will do that:
add_action( 'admin_menu', 'sf_create_meta_box' );
Saving the Data
Now we need to save the data. First we do a few checks to see if we need to save the information. We don’t want to do anything if no data has been submitted, if a revision is being saved, or the current user can’t edit posts (someone being sneaky).
Next I added an action so if you ever need to do anything right before the meta information is saved (maybe add something more to the array), you can easily do that.
Because we have created an array of fields by using sf_meta[input_name], we can loop through those fields specifically. Then we just go through a few checks to see if we need to add, update, or delete the post meta.
And one final do_action so we can do anything extra if we need to afterwords.
/**
* Verify and save meta. Don't save if there is no specific meta, it is a revision,
* or the current user can't edit posts.
*/
function sf_save_meta_box( $post_id, $post )
{
global $post, $type;
$post = get_post( $post_id );
if( !isset( $_POST[ "sf_meta" ] ) )
return;
if( $post->post_type == 'revision' )
return;
if( !current_user_can( 'edit_post', $post_id ))
return;
$meta = apply_filters( 'sf_post_meta', $_POST[ "sf_meta" ] );
foreach( $meta as $key => $meta_box )
{
$key = 'meta_' . $key;
$curdata = $meta_box;
$olddata = get_post_meta( $post_id, $key, true );
if( $olddata == "" && $curdata != "" )
add_post_meta( $post_id, $key, $curdata );
elseif( $curdata != $olddata )
update_post_meta( $post_id, $key, $curdata, $olddata );
elseif( $curdata == "" )
delete_post_meta( $post_id, $key );
}
do_action( 'sf_saved_meta', $post );
}
add_action( 'save_post', 'sf_save_meta_box', 1, 2 );
Using the Data
I said before that it was a pain defining a variable for each field of meta information you wanted to use. Well it is. However, this time around I have a solution. The following function will do a few things: Define a post ID if one doesn’t exist (tries to get one from the loop), and gets a list of meta keys associated with that post.
Then here is where it gets cool: WordPress adds an underscore to all of their internal information. So we know we don’t need to grab any of that. So as we loop through, anything that starts with an _, we simply ignore. Otherwise, we add it to our $meta array. Then we cut out our prefixed meta_, and add just the simple part of the key to our array.
/**
* Gathers all meta objects attached to a certain posts.
* Excludes WordPress internal meta and creates an array of data.
*/
function sf_post_meta( $post_id = '' )
{
global $meta, $post, $wpdb;
if( empty( $post_id ) )
$post_id = $post->ID;
$meta = array();
$custom_field_keys = get_post_custom_keys( $post_id );
if( $custom_field_keys )
{
foreach( $custom_field_keys as $key => $value )
{
$valuet = trim( $value );
if ( '_' == $valuet{0} )
continue;
$value_short = str_replace( 'meta_', "", $valuet );
$meta[ $value_short ] = get_post_meta( $post_id, $value, true );
}
}
return $meta;
}
Now in our page, we can just do this after our while loop.
<?php while( have_posts() ) : the_post(); sf_post_meta(); ?>
Notice the call of sf_post_meta? That is what’s going to call all the meta info we need. Then we can just do the following when we need to access the data.
<div class="subtitle"><?php echo $meta[ 'subtitle' ]; ?></div>
If this does not work, try adding
global $meta;before the loop.
Conclusion
It’s as easy as that. Now you can create beautiful meta boxes that are integrated directly into the writing pages, without having to deal with clunky arrays, or worrying about defining a ton of variables to access your data.
WPBundle
This is the type of code that will be powering the themes on WPBundle. The code that will be behind these themes (HTML/PHP, excluding CSS), will be licensed under the GPL, which means that you will be able to freely use and access the code whether you have paid for the themes or not. (You will still need to buy the bundle to get the images, CSS, support, and PSD files.) I urge you to subscribe to WPBundle so you can stay up to date on all of the going-ons.
Pingback: Custom Write Panels Re-Redux — Spencer Finnell | Design Blog
Pingback: Tweets that mention Custom Write Panels Re-Redux — Spencer Finnell -- Topsy.com
Thanks for putting the time into this, its really made my life a lot easier for a project Im working on- and thanks for breaking the code into easy-to-understand chunks. Learning > Copy+Paste!
After experimenting for an hour or so, I have 2 questions:
1) Using the meta box to save HTML: I want to create a meta box on the post edit screen for my end user to paste an embed code from Vimeo, YouTube, etc and then be able to echo that meta data on the post page. Following your tutorial above, I setup a box that works perfectly at first: I enter the embed code and save the post- all is well until I go back to that post later, make an edit to he content and Update it. Then I am left with just <object width= in my meta box. Suggestions for how to handle raw HTML here?
2) Category-specific meta box: I know it was brought up in previous tutorials for doing this, but I'll be honest Im still a bit fuzzy. If I would like a meta box to appear only for posts of a specific custom post type (hello, WP 3.0!!), is there a way to do that within this code?
Thanks in advance!
Hey Andrew,
I’ve run into your first problem before, and I’ll be honest, I forgot how I fixed it. I think I used htmlspecialchars to filter the existing data that appears in the input field.
To have the meta box only show on a certain post type is as easy as this:
* Create custom meta boxes. * * @since 1.0.0 */ function sf_create_meta_box() { add_meta_box( 'sf-meta-box-subtitle', __( 'Subtitle', 'sf' ), 'sf_meta_box_subtitle', 'post-type', 'normal', 'high' ); }Where
post-typeis your custom post type.Looks like I need to fix some CSS on my end… :P
Perfect, thanks! For future comment-readers, putting in the htmlspecialchars was really easy (note that this is exactly the code in step 2 above, but with my custom variable ‘vid_embed’):
<input type="text" name="sf_meta[vid_embed]" value="” style=”width:99%;” />
Works perfectly! Ive also put it on http://pastie.org/1034172
Looks like that previous comment stripped out the php echo statement so you cant actually see the part that is important! Use the pastie.org link above to see the full code snippet.
Nice post and this post helped me alot in my college assignement. Say thank you you seeking your information.
I want to quote your post in my blog. It can?
And you et an account on Twitter?
Yes you certainly can.
My twitter account is: http://twitter.com/spencerfinnell
I just worked on a project that needed a conditional wrapped around the data I was echoing from the custom post meta boxes- this is the solution I created to hide a field if it is empty:
<?php if( get_post_meta($post->ID, 'meta_twitter', true) ) { ?> <div class="profile_link"> <h3>twitter:</h3> <?php echo $meta[ 'twitter' ]; ?> </div> <?php } ?>Here, my metabox was for a set of posts about client projects. Some clients have relevant twitter accounts, but others do not. So, this allows me to check and see if the twitter username meta box has a value. If it does, it shows uses the same code from the tutorial to show the @username. If that meta box is empty, it wont show the heading or the (non-existent) @username.
Hopefully this helps out others who might want to do this- note that in the first line, you must add ‘meta_’ before your variable name- Spencer explains this in the ‘Using the Data’ section above.
Are there other/simpler ways to do the same thing?
You should just be able to do
Hi! I just love these tutorials! I just wonder how can i have more custom fields in the same meta box? do i need to put it in arrays like in the other tutorials?
Lets say i have a produkts metabox, and i want to include price, weight, shipping etc under that same metabox..
Any suggestions apreciated!
Just add more input boxes in the same function as before.
Thank you, that worked perfekt! Now there is one issue i can’t solve..
Trying to make a multible checkboxes for sizes(XS,S,M,L,XL), but cannot get them to save and be checked when i get back to the post. Any idea how i can manage this?
Thank you..
To clearify the question above.
I would like to save more values to 1 key.
[language-name]
[/language-name]
i tried something like this:
[language-name]
[/language-name]
but that did not work
apretiate some help,
please :)
well that code wont sho up.. will try to show like this.
tried this, but did not work
damn it.. here it is: http://pastie.org/1097298
Try something like:
<label>Sizes</label> <ul> <li><input type="checkbox" name="sf_meta[sizes][s]" value="S" />S</li> <li><input type="checkbox" name="sf_meta[sizes][m]" value="M" />M</li> <li><input type="checkbox" name="sf_meta[sizes][l]" value="L" />L</li> </ul>Tried that, but that did not work. Did not save the custom field. Is it something with the saving in functions code?
Wrong post. Look at the correct page’s comments, and you will find the answer.
Man,
thanks to quality-control.
I have a little problem:
When click on some state the link gimme a page like:
/status/complete
But, when click, page not found.
How to fix this?
Thanks,
–
Helton
aprizion@gmail.com
Wrong post dude. Check out the Quality Control site: http://getqualitycontrol.com/
I’ve been using this technique with a few projects and it has been awesome- once you understand the code the first time, it is incredibly easy to customize and employ in a variety of situations.
I have been working on one use that I havent been able to solve yet. I am working on a very basic directory where my authors enter data in a variety of fields and taxonomies (seriously with custom taxonomies, I’m going to use WordPress to organize everything in my life!). But I’d also like to provide a field for them to enter their own commentary which will only be displayed to logged-in users. But it would be very helpful if this field was a full text area but it every way I can think of to change the text input type to textarea fails.
Any suggestions for how to add multi-line support to one of these custom meta boxes? Bonus points would be adding TinyMCE support but I would be stoked just to get it to accept textarea.
Thanks!
Pingback: WPBundle and WordPress | Spencer Finnell