Integrating With WordPress’ UI 2/3: Meta Boxes on Custom Pages

Integrating With WordPress’ UI: Meta Boxes on Custom Pages | Wptuts+

This is part 2 of a series looking at how your plugin and theme can provide the best user experience by ‘fitting in’ with WordPress’ native UI. This means more than just looking a part of WordPress (which we covered in part one), but where appropriate, mimicking the same workflow that would (hopefully) be familiar to WordPress users. A part of this, is how you structure pages and present information the end user. An incredibly useful tool from both a UI and developer perspective is the meta box. In this tutorial we look at how you can add meta boxes to your own custom admin page. 

The use of meta boxes is common in WordPress. It’s used on the Widgets, Menus and Dashboard pages – and of course, the post edit screen. They can be a fantastic tool for improving the user experience:

  • They provide a natural grouping of information. On the post edit screen there’s a meta box for handling the post’s publication, one for each taxonomy, and another for dealing with the post’s discussions. Meta boxes visually break up the information into easier to handle chunks.
  • The user decides what’s important. The end user can decide which meta boxes appear where, and can completely hide meta boxes that are not relevant to them. Simply put, this allows the user to manipulate the page so that it is arranged in a way that facilitates their workflow.
  • Minimize or remove. A similar point to the one above: irrelevant meta boxes can be minimized or hidden completely.
  • Looks good. On the whole, meta boxes look good. Since they are fairly common in WordPress, other examples of meta boxes (i.e. meta boxes which don’t look like the native meta box) just look out of place.

A final point that shouldn’t go unnoticed: when implemented properly meta boxes also allow third-parties to add or remove content from your admin page, making your plugin or theme readily extendible.

Please note, I’m not advocating the use of meta boxes for everything – only where it makes sense to do so. As discussed in part one, there are times where WordPress’ existing UI is not sufficient or appropriate for what your plugin is trying to do. In these cases, you shouldn’t constrain yourself to the admin UI – but you shouldn’t ignore it either.

 


 

The Page Layout

WordPress is very good at being extended and meta boxes are no exception. The scripts and styles that WordPress uses to position, style, and ‘animate’ meta boxes is also available to us. Using them means that meta boxes (along with all their ‘features’) can be added with relatively little code.

However, to take advantage of this, we need to mimic the layout of a WordPress’ admin page so that the selectors used in the scripts and styles, apply to our page. Now of course, different pages implement meta boxes differently. For instance, the Dashboard has up to 4 evenly-sized columns of meta boxes, while the post edit page allows only 1 or 2, with one acting as a sidebar. Depending on how you want your page to appear, you’ll need to structure your page accordingly. In this tutorial I’ll be going through the post edit screen’s 1/2-meta box layout. So lets take a look at basic wireframe of an admin page.

.wrap

This element wraps your entire admin page. It adds a margin to the top and right sides to keep the admin page away from the sides of the screen. This should be used on all of your admin pages.

<div class=".wrap">
	<!-- Admin page here -->
</div>

Screen Icon

Next is the screen icon. This again should appear on all your admin pages. The mark-up for the screen icon can be generated by using the function screen_icon(). We covered its use in part one of this series. screen_icon('my-id') produces the HTML:

<div id="icon-my-id" class="icon32">
	<br />
</div>

Heading

Next is the page title. The title should be wrapped inside <h2></h2> tags. If appropriate, an ‘add new’ link can be added inside these tags:

<?php
	printf(
		'<h2> %s <a href="%s" > %s </a></h2>',
		esc_html__('Page Title','plugin_domain'),
		esc_url(admin_url(admin.php?page=my-link-to-add-new)),
		esc_html__('Add New','plugin_domain')
	);
?>

Form

Usually, with meta boxes, you are accepting some form of input from the user. To do this you’ll need to wrap the entire page inside a form. In any case it is required to store the meta box preferences (which meta boxes are closed, and the location of the meta boxes).

<form name="my_form" method="post">
	<input type="hidden" name="action" value="some-action">
	<?php wp_nonce_field( 'some-action-nonce' );
	/* Used to save closed meta boxes and their order */
	wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
	wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
	<!-- Rest of admin page here -->
</form>

#poststuff

This element wraps the meta box holder. It’s an important element as WordPress uses this in targeting its styles and scripts.

<div="poststuff">
	<!-- #post-body .metabox-holder goes here -->
</div>

#post-body

This element acts as the meta box holder. It has two important classes:

metabox-holder and columns-*. The second of these specifies the layout of the page (whether it has 1 or 2 columns). The user-specified layout option can be obtained with get_current_screen()->get_columns(). In the following we use this to add the class columns-1 or columns-2 appropriately (with the latter as default).

<div id="post-body" class="metabox-holder columns-<?php echo 1 == get_current_screen()->get_columns() ? '1' : '2'; ?>">
	<!-- meta box containers here -->
</div>

The Meta Box Containers Inside #post-body

There are two meta box containers, which act as the ‘columns’ of meta boxes. The first, .postbox-container-1, acts as the sidebar in the 2-column layout, and in the 1-column layout sits just above the second meta box container. Then there is #post-body-content. This (optional) element doesn’t contain any meta boxes, but contains any content that you want to sit at the top of the page, and not be moveable. In the post edit screen, for example, it contains the post title, and TinyMCE editor.

To print the meta boxes inside the relevant container we use the do_meta_boxes function which takes three arguments:

  • $screen – The screen ID (or we can use an empty string to use the current screen ID).
  • $context – This a string identifier used when registering the meta box. This can be anything, but should be descriptive (for example ‘side’ and ‘normal’). This allows you to define the default position and order of meta boxes.
  • $object – This is passed to the meta box’s callback as the first argument, and is usually the object being edited (for example a post object, on the post edit screen). If this isn’t relevant for your admin page, you can pass null.
	<div id="post-body-content">
		<!-- #post-body-content -->
	</div>
	<div id="postbox-container-1" class="postbox-container">
		<?php do_meta_boxes('','side',$object); ?>
	</div>
	<div id="postbox-container-2" class="postbox-container">
		<?php do_meta_boxes('','normal',$object); ?>
		<?php do_meta_boxes('','advanced',$object); ?>
	</div>

 


 

Example Layout

	<div class="wrap">
		<?php screen_icon(); ?>
		<h2><?php esc_html_e('Page Title','domain'); ?></h2>
		<form name="my_form" method="post">
			<input type="hidden" name="action" value="some-action">
			<?php wp_nonce_field( 'some-action-nonce' );
			/* Used to save closed meta boxes and their order */
			wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
			wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
			<div id="poststuff">
				<div id="post-body" class="metabox-holder columns-<?php echo 1 == get_current_screen()->get_columns() ? '1' : '2'; ?>">
					<div id="post-body-content">
						<!-- #post-body-content -->
					</div>
					<div id="postbox-container-1" class="postbox-container">
						<?php do_meta_boxes('','side',null); ?>
					</div>
					<div id="postbox-container-2" class="postbox-container">
						<?php do_meta_boxes('','normal',null); ?>
						<?php do_meta_boxes('','advanced',null); ?>
					</div>
				</div> <!-- #post-body -->
			</div> <!-- #poststuff -->
		</form>
	</div><!-- .wrap -->

 


 

Adding Meta Boxes & Screen Options

Now we have the page structure we now want us (or any third party) to be able to add meta boxes to the page. We’d also like to load up the necessary JavaScript that allows these meta boxes to be minimised, hidden or moved.

To allow meta boxes to be added we need to fire two hooks. The first:

add_meta_box_{screen_id}

Passes the object being edited (or null). The second:

add_meta_box

Passes two variables: the screen ID and the object being edited. Users can then hook on these actions and add their meta boxes to the page.

Next we want to load the WordPress script postbox.js. This script allows the user to move, minimise or close meta boxes (and saves their preferences). The script needs to be initialised so we’ll need to print one line of javascript in the footer to do this.

Finally, we add a screen option allowing the user to switch between the one and two column layout. Screen options allowing the user to hide meta boxes are automatically added. We’ll use the load-{$pagenow} hook to fire our callback only on the appropriate page. For custom admin pages, $pagenow is the screen ID.

<?php
/* Throughout $screen_id is assumed to hold the screen ID */
/* Add callbacks for this screen only. */
add_action('load-'.$screen_id, 'wptuts_add_screen_meta_boxes');
add_action('admin_footer-'.$screen_id,'wptuts_print_script_in_footer');
/*
 * Actions to be taken prior to page loading. This is after headers have been set.
 * @uses load-$hook
 */
function wptuts_add_screen_meta_boxes() {
	/* Trigger the add_meta_boxes hooks to allow meta boxes to be added */
	do_action('add_meta_boxes_'.$screen_id, null);
	do_action('add_meta_boxes', $screen_id, null);
	/* Enqueue WordPress' script for handling the meta boxes */
	wp_enqueue_script('postbox');
	/* Add screen option: user can choose between 1 or 2 columns (default 2) */
	add_screen_option('layout_columns', array('max' => 2, 'default' => 2) );
}
/* Prints script in footer. This 'initialises' the meta boxes */
function wptuts_print_script_in_footer() {
	?>
	<script>jQuery(document).ready(function(){ postboxes.add_postbox_toggles(pagenow); });</script>
	<?php
}
?>

All that remains is to add the meta boxes.

 


 

Adding Meta Boxes to the Page

Normally meta boxes can be added using the add_meta_boxes or, better yet, the add_meta_boxes_{post_type} hooks. More generally post type can be thought of as the screen ID. We’ve triggered these hooks inside the wptuts_add_screen_meta_boxes() function above. All that remains is to hook onto these actions and use the add_meta_box() function.

/* Throughout $screen_id is assumed to hold the screen ID */
add_action('add_meta_boxes_'.$screen_id,'wptuts_add_my_meta_box');
function wptuts_add_my_meta_box() {
	add_meta_box(
		'my_meta_box_id', //Meta box ID
		__('My Meta Box','plugin_domain'), //Meta box Title
		'wptuts_my_meta_box_callback', //Callback defining the plugin's innards
		$screen_id, // Screen to which to add the meta box
		'normal' // Context
	);
}

 


 

Code

You can download a simple admin page class based on this tutorial from GitHub.