It's better when it's simple

User Tools

Site Tools


navi plugin

Compatible with DokuWiki


plugin Build a navigation menu from a list

Last updated on
Conflicts with

Similar to indexmenu, navilevel, simplenavi

Tagged with menu, navigation

This plugin allows you to create a nested navigation menu based on a list defined in a Wiki page. Lower navigation levels are shown or hidden dependent on the current page. It is intended for the use in the sidebar of a template supporting one (tested on Arctic). A notable feature is that it allows you to create hierarchical menus without the need of a hierarchical namespace structure.

Download and Install

A CosmoCode Plugin

Use the download link above to either manually download the plugin or install it through the plugin manager.



In the page defining the sidebar in your template add the following syntax:


where navigationmenu is any other page containing an unordered list of page links — this page we call the “control page”.


  • The created menu is completely independent of any namespace structure. The hierarchy is created by the list nesting only. Exception: when the ?ns option is used (see below)
  • The navigation menu page should contain exactly one unordered list, other content will be ignored
  • The list items should only contain links, any other syntax will be ignored
  • Each page linked in the list should occur only once

Control Page Example

The following would create a menu with 4 top level entries: “Welcome”, “Products”, “Service” and “Wiki Syntax”. When you are on the “Products” page, the sub entries “Foomatic 2000” and “Foomatic 2010” are visible.

  * [[start|Welcome]]
  * [[Products]]
    * [[Foomatic 2000]]
    * [[Foomatic 2010]]
  * [[Service]]
    * [[about|About Foo Inc.]]
    * [[Contact]]
  * [[syntax|Wiki Syntax]]

This allows you to create hierarchical menus without the need of a hierarchical namespace structure.

Making use of Namespaces

Sometimes the navi plugin is used to create collapsible, editable navigation even though content is structured into nested namespaces. But since the plugin knows nothing about those namespaces by default it will collapse completely as soon as a page is opened that is not defined in the control page.

By adding ?ns to the navi plugin syntax you can make the plugin to be clever about namespaces: When it is called on a page that is not mentioned on the control page, it will have a look at the namespace of the page. It then checks if a startpage for the namespace is to be found in the control page. If it is, it will be used as the current open branch, if not it is checked if there is any other page in the same namespace on the controlpage. If found it's used. This is repeated for each higher namespace until the top is reached or a matching page.


I modified the function render to use it a little bit like a dynamic version, Menuitems will have list items now. If a child is defined and if it is open the Icon will change. If no Child is defined there is no list item. I add to the DIV li an open or closed class which can be read from the template css like following css-code example. — oh-markoh-mark


div.dokuwiki div.navigation li {
  list-style: none;
  margin-left: 5em; /** default if more than defined levels **/ 
div.dokuwiki div.navigation   { list-style-image: url(images/open.gif); }
div.dokuwiki div.navigation li.close  { list-style-image: url(images/closed.gif); }
div.dokuwiki div.navigation li.level1 { margin-left: 0em; }
div.dokuwiki div.navigation li.level2 { margin-left: 1em; }
div.dokuwiki div.navigation li.level3 { margin-left: 2em; }
div.dokuwiki div.navigation li.level4 { margin-left: 3em; }
div.dokuwiki div.navigation li.level5 { margin-left: 4em; }

here is the mod

     * Create output
     * We handle all modes (except meta) because we pass all output creation back to the parent
     * mod by Mark Wolfgruber 20.06.2011
     *      *           
    function render($format, &$R, $data) {
        global $INFO;
        global $ID;
        $fn   = $data[0];
        $opt  = $data[2];
        $data = $data[1];
        if($format == 'metadata'){
            $R->meta['relation']['naviplugin'][] = $fn;
            return true;
        $R->info['cache'] = false; // no cache please
        $parent = array();
            $parent = (array) $data[$INFO['id']]['parents']; // get the "path" of the page we're on currently
            $current = $INFO['id'];
        }elseif($opt == 'ns'){
            $ns   = $INFO['id'];
            // traverse up for matching namespaces
            do {
                $ns = getNS($ns);
                $try = "$ns:";
                    // got a start page
                    $parent = (array) $data[$try]['parents'];
                    $current = $try;
                    // search for the first page matching the namespace
                     foreach($data as $key => $junk){
                        if(getNS($key) == $ns){
                            $parent = (array) $data[$key]['parents'];
                            $current = $key;
                            break 2;
            } while($ns);
        // we need the top ID for the renderer
        $oldid = $ID;
        $ID = $INFO['id'];
        // create a correctly nested list (or so I hope)
//        $open = false; /** deacivated by mark **/
        $lvl  = 1;
        // read if item has childs and if it is open or closed /** mod by mark **/
        foreach((array) $data as $pid => $info){
            $state=(array_diff($info['parents'],$parent)) ? 'close':'';
            if ( $countparents > '0') {
                for($i=0; $i < $countparents; $i++){
                  $upper[$info['parents'][$upperlevel]]=($state=='close')? 'close' : 'open';
        } /** mod by mark **/ 
        unset($pid); // break the reference with the last element /** mod by mark **/
            // read if item has childs if it is open or closed
            foreach((array) $data as $pid => $info){
                // only show if we are in the "path"
                if(array_diff($info['parents'],$parent)) continue;
                if ($upper[$pid]) { /** mod by mark **/
                  $menuitem=($upper[$pid]=='open') ? 'open' : 'close';
                 } else {
                 } /** mod by mark **/
                // skip every non readable page
                if(auth_quickaclcheck(cleanID($info['page'])) < AUTH_READ) continue;
                if($info['lvl'] == $lvl){
//                    if($open) $R->listitem_close(); /** deacivated by mark **/
                    $R->listitem_open($lvl.' '.$menuitem); /** mod by mark **/
//                    $open = true; /** deacivated by mark **/
                }elseif($lvl > $info['lvl']){
                    for($lvl; $lvl > $info['lvl']; $lvl--){
//                      $R->listitem_close(); /** deacivated by mark **/
//                      $R->listu_close();    /** deacivated by mark **/
//                    $R->listitem_close();   /** deacivated by mark **/
                    $R->listitem_open($lvl.' '.$menuitem); /** mod by mark **/
                }elseif($lvl < $info['lvl']){
                    // more than one run is bad nesting!
                    for($lvl; $lvl < $info['lvl']; $lvl++){
//                    $R->listu_open(); /** deacivated by mark **/
                      $R->listitem_open($info['lvl'].' '.$menuitem); /** mod by oh-mark **/
//                    $open = true;  /** deacivated by mark **/
                if(($format == 'xhtml') && ($info['page'] == $current)) $R->doc .= '<span class="current">';
                if(($format == 'xhtml') && ($info['page'] == $current)) $R->doc .= '</span>';
                $R->listitem_close();  /** mod by mark **/
/*        while($lvl > 0){ 
        }  /** deacivated by mark **/
            $R->listu_close();  /** mod by mark **/
        $ID = $oldid;
        return true;


first submenu item level1 instead level2

i tried this plugin and have a problem with the first submenu item. it have to be in the div class level2 but it became level1 after the script did his work — oh-markoh-mark


found the error:
replace $lvl with $info['lvl'] in line ~173 — oh-markoh-mark

2011/06/19 22:50
            }elseif($lvl < $info['lvl']){
                // more than one run is bad nesting!
                for($lvl; $lvl < $info['lvl']; $lvl++){
                    $R->listitem_open($info['lvl']); /** mod by oh-mark **/
                    $open = true;

Rendering Bugs

The closing of open menu entries does not work. The open menu child entries are on the same level like the main menu entries. The user does not see any indicator for child menu items if the main menu is closed.

Tested with the code modifications on this page with Dokuwiki dokuwiki-rc2011-11-10.tgz “Angua”. HH 29.12.2011

plugin/navi.txt · Last modified: 2013/11/05 13:02 by Klap-in