Integrating With WordPress’ UI: Meta Boxes on Custom Pages

Integrating With WordPress’ UI: Meta Boxes on Custom Pages

Tutorial Details
  • Program: WordPress
  • Version: 3.3
  • Difficulty: Moderate
  • Estimated Completion Time: 30 minutes
This entry is part 2 of 3 in the series Integrating With WordPress' UI

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 id="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.


Other parts in this series:Integrating With WordPress’ UI: The BasicsIntegrating With WordPress’ UI: Admin Pointers
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Ignacio Cruz

    Fav series!

  • Narre

    Awesome job.

  • Pingback: September's Best WordPress, Magento, and ExpressionEngine Content | @nexcess

  • Pingback: Integrating With WordPress’ UI 2/3: Meta Boxes on Custom Pages | Deep Ocean, Wide Sky

  • LincolnLemos

    I dont understand how i add this on my plugin page…

    • LincolnLemos

      I done now. Nice tutorial ˆˆ

  • LincolnLemos

    How can i change the layout_columns for 4?

  • http://www.facebook.com/eric.andrew.lewis Eric Andrew Lewis

    In your code example for the div for .wrap, your including a “.” within the class attribute.

    • http://wp.tutsplus.com/ Japh

      Great spotting, @facebook-806246:disqus, both mistakes have been corrected now, apologies for the delay.

  • http://www.facebook.com/eric.andrew.lewis Eric Andrew Lewis

    Also div for poststuff has incorrect id assignment (div=”poststuff”)

    • http://stephenharris.info/ Stephen Harris

      Thanks for pointing this out Eric, I’ll get this fixed!

  • Partager

    I wish that these tutorials would Always include screen captures of the code in action. Following a Tutorial involves a commitment of time and close attention. All of these Tutorials should show the “problem” first and the “solution” afterwards as rendered in the browser. That really helps to know which tutorial is closest to our particular needs.

    Pictures really DO equal a thousand words.

    For instance, I have looked through the whole series of Tutorials on using WordPress UI but I have to spend so much time reading each of them all the way through to see if they are addressing something useful to my problem. The code alone, or a sketch in the middle or the start of the process, doesn’t intuitively signal to me what we get when the smoke clears.

    My problem is that I have a variety of Frameworks whose installation produces either a Real Estate site, a Product or Services site, a Classified site, or Business Directory site. These are based on the one blog, multiple users Registered Member model. Each Member posts and edits their own Content.

    But what all of the Frameworks strip away in their User Edit Post or Modify Post Author Admin pages is the use of PLUGINS. The Framework UI is just a glorified form. In exchange for simplifying the Registered User experience so that they don’t see the default WP Admin User Interface for, say, Authors, the User does not get to see or use specific plugins that let Members manage options of a plugin that creates a Calendar for a Rental Property, as an example.

    The developers of all these frameworks don’t intend to change this situation for the sake of me, the Framework purchaser, so that more plugins will work dynamically in any of these Frameworks.

    The Booking Calendar is a plugin that provides settings and options first for me, the Admin. Once I have installed the plugin it creates the Admin menu and its sub pages of settings to create the “master template” for the Calendar.

    Then, when a Member with Author privileges (default role assignment) logs in to the WP Admin area they see their own sub-set of Menu options to create separate instances of this Calendar for each of their posts, which may be a Rental property.

    When the same Author selects their Posts/Listings they will find that the plugin has installed a dropdown selector above the Content Text editor. Choosing one of their own calendars by name then generates a shortcode into their content which produces a specific Calendar for that one post on the public side.

    Frameworks UI’s remove all such plugins from their Post Authors. None of the Frameworks I have bought can give back these capabilities to the Members because they are barebones forms and not true WordPress User Screens.

    So I am wondering if any of these tutorials can help me build a “pretty styled” UI Admin page or template for Post Authors in a Framework that I already have by adding back the full power of the WP default Admin panel to give Plugins options to Content providers who are merely Authors and not Super-Admin or Admin in role.

    Thanks for any helpful comments!