While rebuilding the posts plugins for WordPress 2.5 I became frustrated by the excessive length of their settings pages and more than a little envious of other plugins which managed to split their settings into subpages accessible from a submenu.
I pored through more opaque code than is comfortable and eventually decided I should try and build an easy, reusable solution. Here’s the end result (download code):
class PluginSubpages {
var $pages = array();
var $parent_page = '';
var $current_page = array();
function PluginSubpages() {
$this->__construct();
}
function __construct() {
$this->parent_page = $_SERVER['QUERY_STRING'];
$p1 = strpos($this->parent_page, 'page=');
$p2 = strpos($this->parent_page, '&');
if ($p2 === false) {
$this->parent_page = substr($this->parent_page, $p1+5);
} else {
$this->parent_page = substr($this->parent_page, $p1+5, $p2-$p1-5);
}
}
function add_subpage( $title, $slug, $view ) {
$this->pages[] = array('title' => $title, 'slug' => $slug, 'view' => $view);
}
function page_from_slug( $slug ) {
if (!isset($slug)) {
return $this->pages[0];
}
foreach ($this->pages as $page) {
if ($page['slug'] === $slug) {
return $page;
}
}
die('non-existent slug');
}
function display() {
echo "\n<ul id=\"submenu\" style=\"display: block\">\n";
$base = $_SERVER['PHP_SELF'] . '?page=' . $this->parent_page . '&subpage=';
$this->current_page = $this->page_from_slug($_GET['subpage']);
foreach($this->pages as $page) {
if($page === $this->current_page) {
echo "<li><a href=\"$base{$page['slug']}\" class=\"current\">{$page['title']}</a></li>\n";
} else {
echo "<li><a href=\"$base{$page['slug']}\">{$page['title']}</a></li>\n";
}
}
echo "</ul>\n";
$this->current_page['view']();
}
}
There’s one nasty trick in there which I’ll come to but first … how to use it.
You’ll already have the code to register your admin settings page:
function plugin_option_menu() {
add_options_page('Plugin Options', 'Plugin', 8,'plugin-file', 'plugin_options_page');
}
add_action('admin_menu', 'plugin_option_menu', 1);
function plugin_options_page(){
...
}
Instead of filling ‘plugin_options_page’ with all your settings code you use PluginSubpages to delegate the hard work.
function plugin_options_page(){
echo '<div class="wrap"><h2>Plugin</h2></div>';
$m = new PluginSubpages();
// register the subpages
$m->add_subpage('General', 'general', 'plugin_general_options_subpage');
...
$m->add_subpage('Other', 'other', 'plugin_other_options_subpage');
// show them!
$m->display();
}
Each of the subpage functions behaves independently, e.g.,
function plugin_general_options_subpage(){
$options = get_option('plugin-general');
if (isset($_POST['update_options'])) {
...
}
?>
<div class="wrap">
<h2>General Settings</h2>
<form method="post" action="">
...
<div class="submit">
<input type="submit" name="update_options" value="Save General Settings" />
</div>
</form>
</div>
<?php
}
Now about that ‘nasty trick’… PluginSubpages->display() spits out the HTML for the menu as an unordered list. How should the menu list be styled? I could include specific inline CSS styles for the menu but I thought it would be better if the subpage menu looked like the preexisting admin menu.
This is the evil bit. I found that giving the subpage menu the id of ‘submenu’, the id of the admin menu I could steal all its styles and the subpage menu would fit into its surrounding seamlessly. The only catch is that CSS ids cannot be duplicated — each must be unique. In practice, though, every browser I tried coped without a squeak. Just don’t expect your admin pages to validate if you use this technique.
Good stuff…
Indeed, an exceptional time saver!
Regards.