Proper HTML Custom Menu in Moodle 2

If you’re developing a theme for Moodle 2, you probably want to support the new custom menu functionality. Problem is it uses YUI3 to make the menu ‘work’ rather than CSS alone. This means it’s a) Harder to style and b) results in a nasty ‘jump’ as the JS kicks in – you have to style both pre- and post- JS menu. Instead why not just output a normal nested unordered list and use CSS to provide the functionality? Well, you can! You need to add a little PHP first though. Create a file ‘renderers.php’ in your theme directory and add this code (make sure to change <theme name> to the name of your theme):

<?php
class theme_<theme name>_core_renderer extends core_renderer
{
    /**
     * Renders a custom menu object
     *
     * @staticvar int $menucount
     * @param custom_menu $menu
     * @return string
     */
    protected function render_custom_menu(custom_menu $menu) {
        static $menucount = 0;
        // If the menu has no children return an empty string
        if (!$menu->has_children()) {
            return '';
        }
        // Increment the menu count. This is used for ID's that get worked with
        // in JavaScript as is essential
        $menucount++;
        $content .= html_writer::start_tag('ul', array('class'=>'custommenu'));
        // Render each child
        foreach ($menu->get_children() as $item) {
            $content .= $this->render_custom_menu_item($item);
        }
        // Close the open tags
        $content .= html_writer::end_tag('ul');
        // Return the custom menu
        return $content;
    }
    /**
     * Renders a custom menu node as part of a submenu
     *
     * @see render_custom_menu()
     *
     * @staticvar int $submenucount
     * @param custom_menu_item $menunode
     * @return string
     */
    protected function render_custom_menu_item(custom_menu_item $menunode) {
        // Required to ensure we get unique trackable id's
        static $submenucount = 0;
        if ($menunode->has_children()) {
            // If the child has menus render it as a sub menu
            $submenucount++;
            $content = html_writer::start_tag('li');
            if ($menunode->get_url() !== null) {
                $url = $menunode->get_url();
            } else {
                $url = '#cm_submenu_'.$submenucount;
            }
            $content .= html_writer::link($url, $menunode->get_text(), array('title'=>$menunode->get_title()));
            $content .= html_writer::start_tag('ul');
            foreach ($menunode->get_children() as $menunode) {
                $content .= $this->render_custom_menu_item($menunode);
            }
            $content .= html_writer::end_tag('ul');
            $content .= html_writer::end_tag('li');
        } else {
            // The node doesn't have children so produce a final menuitem
            $content = html_writer::start_tag('li');
            if ($menunode->get_url() !== null) {
                $url = $menunode->get_url();
            } else {
                $url = '#';
            }
            $content .= html_writer::link($url, $menunode->get_text(), array('title'=>$menunode->get_title()));
            $content .= html_writer::end_tag('li');
        }
        // Return the sub menu
        return $content;
    }
}
?>

Then go into your config.php file and make sure $THEME->rendererfactory is set like so:

$THEME->rendererfactory = 'theme_overridden_renderer_factory';

And now you should have nice plain HTML output ready for styling the proper way!

Leave a Reply