How to Give Your Plugin Settings a Submenu

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.

One reply on “How to Give Your Plugin Settings a Submenu”

Comments are closed.