Navigation 2.0 navbar proposal

Jump to: navigation, search

Implementation of global navigation, settings navigation, and the navbar in Moodle 2.0

To help Tim in his mammoth task of navigation implementation I have undertaken to complete the implementation of the global navigation, settings navigation, and the navbar for him.

The following is the specification for the three, as they are very closely tied in to each other.

Implementation Overview

The purpose of the new navigation is to capture the structure of a Moodle site and present a series of navigation objects that allow the user to easily navigate Moodle 2.0.

The focus is to create a usable navigation structure for Moodle, that is constant in its appearance and usage.

In order to achieve this it was decided that three navigation structures were needed.

  • Global navigation: This is the course structure for the site, starting at the site level, through categories, courses, course sections, and activities. With the ability for an activity to further expand the navigation.
  • Settings navigation: The settings available to the user for their current context. This will contain things such as site administration, course settings, activity settings, and profile settings.
  • Navigation bar: As seen in Moodle, at the same time as implementing the above two navigation structures we will also re-implement the navigation bar in a object oriented method and make it more intuitive by getting it to auto-generate as deep as possible based on the information available through the PAGE object that is now available.

How these tie in: During the creation of mock up's it became apparent that there was a great deal of repetition in code if all three navigation structures we generated individually. In working through the possibilities we decided on the following approach of implementation:

  1. Start with the global navigation object nesting it as a property of the moodle_page ($PAGE) object.
  2. Initialise the global navigation object the first time it is requested, initially we thought that we would do this at the same time as we initialise theme and output, however this proved to be problematic.
  3. When global navigation initialises it will look at the context held by $PAGE and generate its own structure based on that context, ensuring that it adheres to the capabilities of the current user.
  4. Every node that gets added to the global navigation tree is analysed to find out if it is the active node (the node the user is currently viewing)
  5. Next the navigation bar, like global navigation it is initialised when it is first used
  6. When the navigation bar is first called it also calls global navigation to initialise to ensure that the general navigation structure has been generated.
  7. Unlike the global navigation nothing is generated during initialisation, the object is set-up to be used and nothing else.
  8. When called to display the navigation bar will analyse the global navigation object ($PAGE->navigation) and retrieve all nodes that form the path to the active node, these nodes will form the breadcrumb.
  9. After generating the path as above, the navigation bar then looks at itself to check for any children that have been added specifically to the navigation bar.
  10. Finally the settings navigation.
  11. Settings navigation is again only initialised when it is first used. However it is [currently] only used by the settings block which will be implemented at the same time.
  12. When being initialised settings navigation will call global navigation to initialise if it has not already to ensure that it loaded and ready for inspection.
  13. During initialisation the settings navigation inspects the global navigation to find the currently active node, and $PAGE and its properties to find the different elements that can contribute to the settings available for the user.
  14. Settings navigation then proceeds to generate the permitted settings for the current user for each of the elements found above.

As you can see above all three objects are initialised when they are first used, and global navigation is the main structure that the other two objects (settings and navbar) both inspect in order to lessen the amount of information that they must generate.

The following are the points during processing that the tree will likely be initialised:

  • When interacted with directly; All three blocks can be interacted with directly, and independently, when such an interaction occurs the blocks will be initialised as described above.
  • During block processing; If either a global navigation or settings navigation block have been added to the page the relevant objects will be initialised when we start block processing.
  • Within the template call to display the navigation bar; simple as that when the template wants to use the navigation bar we need to initialise the global navigation and then the navbar.

General Interaction

Whilst the three objects will automatically generate as much of the navigation structure as possible there is case for further extending the navigation beyond the what it is able to generate.

The objects themselves are all made available through the $PAGE object in the following manor:

// Don't forget if you are in a function you will need global $PAGE
$PAGE->navigation;  // This will return the navigation object
$PAGE->settingsnav; // This will return the settings navigation object
$PAGE->navbar;      // This will return the navigation bar object

The following method has also been made available through the $OUTPUT object

// This method will return true if the navigation bar object has anything to display
// and has been added to allow template designers to include a navbar only if there is one
$OUTPUT->has_navbar();

Global navigation interaction

The following examples show the common task of adding an item to each node. Within each example you will see several of the methods available to developers for interacting with the navigation objects.

For more information on other methods please refer to the documentation for the objects available in the phpdocs section of moodle.org

Adding items to the navigation structure

The following example illustrates how one would go about adding custom nodes into the main navigation structure along with the additional actions that can be used to further manipulate that newly added nodes.

/**
 * First we must locate the node that we wish to add content for.
 * In this case we will use the current course.
 * The following line asks the global navigation object to return the
 * course node.
 */
$coursenode = $PAGE->navigation->find_child($PAGE->course->id, navigation_node::TYPE_COURSE);
 
/**
 * If $coursenode is still we can't proceed as we don't have a point to add
 * our new node to
 */
if ($coursenode === false) return;
 
/**
 * Now we can add a branch to put our custom nodes into, remember add returns the
 * node that has just been added.
 */
$branch = $coursenode->add('My custom branch');
 
/**
 * Now lets add a node into the branch with my name, and my profile
 * first we need to create moodle_url object to use as the action (or link)
 * for this node, and then we can create the node
 */
$url = new moodle_url($CFG->wwwroot.'/course/view.php', Array('id'=>$coursenode->key);
$mynode = $branch->add('Sam Hemelryk', $url, 'Sam', null, navigation_node::TYPE_CUSTOM);
 
/**
 * With my node created I could then proceed to call/set the following to
 * achieve difference effects
 */
// This will force the branch I added to be expanded (open) when displayed
$branch->forceopen = true;
 
// This will add the CSS class mycustomclass to the node when it is displayed
$mynode->add_class('mycustomclass');
 
// This will make my node active (giving it the active class and forcing it open
// if it becomes a branch
// Remember: If a node is made active and is within the active branch then it is displayed
// as part of the navigation bar as well as the global navigation
$mynode->make_active();

Adding items to the navigation bar but not the main navigation structure

Adding items to just the navigation structure is much easier than adding items to the global navigation. This is because you are not required to find the node you wish to add to, and you don't need to track the keys you add.

When you add to the navigation bar the new node is simply added to the end of the navigation bar.

/**
 * In this example we will add two node to the navigation bar, the first with a link, the second without
 *
 * To start lets create a moodle_url for the action of the first item
 */
$url = new moodle_url($CFG->wwwroot.'/path/to/stuff.php');
 
/**
 * Now lets add the first item with its action
 */
$PAGE->navbar->add('first node', $url);
 
/**
 * And with the first node added we can add the second
 */
$PAGE->navbar->add('second node');
 
/**
 * And with that we are done.
 * Remember though add returns the key so if you want to manipulate
 * the new nodes you can do by capturing the key and using it to retrieve
 * the node you want.
 */


Adding module settings to the settings navigation

Adding nodes to the settings navigation is identical to the global navigation however rather than using $PAGE->navigation you use $PAGE->settingsnav. The only thing to note is that the settings navigation nodes that are made active will only be shown in the navigation bar is there is no active branch within the global navigation.

Module Callbacks

In order to allow modules to extend the navigation as the user expands the tree through a course two callback methods have been defined and are looked for when the tree is being generated. These callback method all the navigation to expand the tree by adding module specific information from the course or course section branch. The callbacks are as follows:

  • /mod/%MODULE%/lib.php :: function %MODULE%_extend_navigation();
  • /mod/%MODULE%/lib.php :: function %MODULE%_extend_settings_navigation();

%MODULE%_extend_navigation

If the module has defined this function within lib.php and the tree has been expanded to a point at which module information can be displayed then this method gets called.

When called the following four arguments are passed to it by reference:

  1. $navigation
    An instance of navigation_node: This is the point in the navigation where the module's information is desired to go. The navigation_node methods should be used to expand from this node.
  2. $course
    A course object: This is the course object for the course that the module instance belongs too.
  3. $module
    A module object: The module for which the course module is of.
  4. $cm
    A course module object: This is the course module instance for which we want the navigation extended.

Worth noting about this callback method is that it can be called both by a conventional page load and by an AJAX request for more information. Hence all of the resources that are needed to gather further information are provided as method arguments.

Also remember from above that any nodes added to the navigation that are marked active are also displayed in the navigation bar. Thus if you would like to extend what is also placed on the navigation bar you simply need to expand the navigation and mark the extension as active by using the make_active() method available for all navigation nodes.

%MODULE%_extend_settings_navigation

If defined this method is called when the settings navigation is being build in order to allow the module to add its own settings.

When called the following two arguments are passed to the method by reference:

  1. $settingsnav
    An instance of settings_navigation: This is the settings navigation tree that module can add to. Because the settings navigation is more rigid in design and cannot be expanded by AJAX the module is passed the entire instance allowing it to add to the settings navigation anywhere it pleases.
  2. $modulenode
    A module navigation_node: The actual module itself can be accessed through the PAGE object in the following manner: $PAGE->cm.

It is important to note that because the settings navigation doesn't expand by AJAX at all it is safe to rely on global objects like $PAGE being completely setup. Thus you can pick up the course and course module through the $PAGE object and there was no need to pass them as method arguments.

Course Format Callbacks

As well as the module callbacks their are also several course format callbacks that allow course formats to define the way in which the navigation is shown for them. It is important to note that generic functions exist and are used should the course format NOT define these callbacks.

The callbacks should all be located in course format lib.php file

  • e.g. moodle/course/format/myformat/lib.php

are as follows:

callback_%FORMAT%_load_content

This method is called when loading the content for the course through a conventional request (a request for the whole navigation structure). It is provided with 3 arguments, the navigation node to add to, the course object, and the an array of keys to located the course node within the larger structure.

It is important to note that unless the course format is doing something VERY specific it is acceptable for the callback to call the generic method of the navigation structure as shown below.

/**
 * Used to display the course structure for a course where format=weeks
 *
 * This is called automatically by {@link load_course()} if the current course
 * format = weeks.
 *
 * @param navigation_node $navigation The course node
 * @param array $path An array of keys to the course node
 * @param stdClass $course The course we are loading the section for
 */
function callback_weeks_load_content(&$course, &$coursenode) {
    $navigation->load_generic_course_sections($keys, $course, get_string('week'), 'week');
}

callback_%FORMAT%_definition

This method should return a string that can be used to describe the course format to the user.

For the topics format this would be `Topic` and for the weeks format this would be `Weeks`

callback_%FORMAT%_request_key

This method should return the string that is used to identify what section of the course the user is looking at. The string should relate to an optional HTTP GET parameter.

Other callback methods available throughout Moodle 2.0

The following callback methods also exist within Moodle 2.0 allowing other types of plugins to extends the navigation.

Course Reports

Course reports can define a callback function with a lib.php in the report directory that can be used to add navigation nodes to the Course->report branch.

The callback methods should be called %REPORT_TYPE%_report_extend_navigation (e.g. log_report_extend_navigation or outline_report_extend_navigation) and takes the following three arguments in the specified order:

  • $navigation : A report branch navigation node to which new nodes should be added.
  • $course : The course object to reference, use this instead of PAGE->course as if we are processing an AJAX extension $PAGE->course may not be the course we are looking to extend.
  • $context : The context to use if you need to check capabilities.
// Within course/report/myreport/lib.php
 
function myreport_report_extend_navigation($navigation, $course, $context) {
    global $CFG, $OUTPUT;
    if (has_capability('coursereport/myreport:view', $context)) {
        $url = new moodle_url($CFG->wwwroot.'/course/report/myreport/index.php', array('course'=>$course->id));
        $navigation->add(get_string('reportname','myreport'), $url, navigation_node::TYPE_SETTING, null, null, $OUTPUT->old_icon_url('i/report'));
    }
}

Usability

Usability of the navigation blocks was a big consideration when planning these changes as such several things have been done to aid the performance, design, and functionality of the navigation blocks.

Some of the notable features that have come from this include:

Loading branches through AJAX when the user digs down the tree

There is potentially a phenomenal amount of information that could be generated by the navigation structure when it initialises. Because of this the amount of information that the tree structure shows is limited to information that is pertinent to the current user, and is within a given depth. Anything that is further-a-field can only be viewed by progressing towards that information within Moodle.

To aid the usability of the navigation tree AJAX loading of branches into the tree has been added as a feature. This enables a user with Javascript enabled to dig down into the tree expanding branches that we not generated initially for the tree by means of requesting those branches with AJAX.

Caching of retrieved information to add generation time

Again to aid in the generation time of the tree particularly in large installations caching has been implemented within the navigation structure using a class that allows easy caching within the user's session. The cache is lasts only for a short period (60 seconds by default). Into it we include things that are used repetitively, or objects that are used in every page load. This greatly speeds up the delivery of the page when the user is navigating to their desired location, whilst ensuring that when changes that would effect the navigation structure occur they are seen in a timely fashion.

JavaScript transformation of blocks

Again the information that is potentially displayed in the navigation could lead to some very negative effects on usability, the biggest of these being horizontal scrolling of the navigation blocks.

Because of this two JavaScript transformation methods for the block have been defined and can be configured by the user adding the blocks.

  1. The first method is JavaScript block expansion that can be turned on through the block configuration panel. When enabled if the user also has JavaScript enabled then when they move the mouse over the navigation block it will grow in width (currently to 200%) allowing much more of the tree to be viewed before having to horizontally scrolled.
  2. The second method involves removing the block from the page and then adding a side-panel for the page, and re-adding the block back into the page as a tab on the side panel. When you mouse over the navigation tab you the navigation block expands out and can take up as much room as required.

Currently both of these options can be enabled within the settings for the block.

Tips & Tricks

The following are tips and tricks that you may be interested in. These have all come about as people have started using the navigation in their code and exploring how far they can take it.

Opening and closing branches by default

This is an easy one, there are a couple of ways to go around this depending on what you are trying to do.

The preferred method of opening a branch by default is to call the make_active() method on the appropriate node. By calling this method the branch is marked as active, given a special CSS class for our design orientated friends, and all parent branches are also forced open.

$PAGE->settingsnav->add('Some branch')->make_active();

This solution of course only opens branches, it doesn't close them if they would have been open.

In order to close a branch you will need to set the forceopen property of the branch to false. This is the property that at display time is used to determine whether the branch should be shown as open. Setting it to false as shown below will close the branch by default if it was otherwise meant to be open.

$PAGE->settingsnav->add('Some branch')->forceopen = false;

Adding to the navigation but not the navbar

There are a couple of instances where you need to add to the main navigation block but do not want the additions to be reflected in the navbar. The navbar by default is constructed of the active node and all of its parents, however nodes can be ignored by simply setting the mainnavonly property to true.

$node = $navigation->add('An active page');
$node->make_active(); // Required so that it gets the right class and if it is a branch is opened
$node->mainnavonly = true; // Ensures that this node is not shown on the navbar

It is important to note that by setting the mainnavonly property to true the navbar will stop it's auto-generation when it reaches this node. If you want anything to be shown on the navbar after the preceeding node you will need to add it manually by calling $PAGE->navbar->add();

Getting to the top of the settings navigation

If you add a new root branch to the settings navigation by default it will appear at the bottom of the stack, unless it is being added through the module callback methods.

If however you want to add something to the top you can do so by using the prepend method of the settingsnav object as shown below.

$PAGE->settingsnav->prepend('A node at the top'); // Will be added to the top of the settings navigation block
$PAGE->settingsnav->add('A node at the bottom'); // Will be added to the end of the settings navigation block

The prepend method is just about identical to the add method the only difference being that it shifts the new node onto the start of its children array rather than simply adding it to the end.

Adding a popup link to the navigation

Sometimes a popup link is preferred of a normal link when adding items to the navigation. Things such as chat windows, and the live course logs are examples of navigation nodes that generate popups rather than redirecting the user.

Achieving a popup link is achieved simply in the following manner.

$livelogs = get_string('livelogs');
$link = html_link::make('/course/report/log/live.php?id='. $course->id, $livelogs);
$link->add_action(new popup_action('click', $link->url, 'livelog', array('height' => 500, 'width' => 800)));
$navigation->add($livelogs, $link, navigation_node::TYPE_SETTING, null, null, $OUTPUT->old_icon_url('i/report'));

You can see in the above code we work directly with an html_link object. Normally when you add a node you pass in arguments and the navigation builds an html_link object for you, however by creating the html_link object by yourself and passing it to the add method as the link argument you can add a popup action if you like.

API

navigation_node

Methods

  • navigation_node::__construct(Array $properties)
  • navigation_node::__construct(String $text)
  • navigation_node::add($text, $action, $shorttext, $key, $type, $icon)
  • navigation_node::add_class($class)
  • navigation_node::check_if_active()
  • navigation_node::contains_active_node()
  • navigation_node::find($key, $type)
  • navigation_node::find_active_node()
  • navigation_node::find_expandable(&$array)
  • navigation_node::force_open()
  • navigation_node::get($key, $type=null)
  • navigation_node::get_content($shorttext)
  • navigation_node::get_css_type()
  • navigation_node::get_title()
  • navigation_node::has_children()
  • navigation_node::make_active()
  • navigation_node::make_inactive()
  • navigation_node::remove()
  • navigation_node::remove_class()
  • navigation_node::title($title)

Properties

  • navigation_node::action
  • navigation_node::children
  • navigation_node::classes
  • navigation_node::collapse
  • navigation_node::display
  • navigation_node::forceopen
  • navigation_node::helpbutton
  • navigation_node::hidden
  • navigation_node::icon
  • navigation_node::id
  • navigation_node::isactive
  • navigation_node::key
  • navigation_node::nodetype
  • navigation_node::preceedwithhr
  • navigation_node::shorttext
  • navigation_node::text
  • navigation_node::type

Constants Node type constants set whether the node is a leaf (no children) or a branch (has children) and is stored in navigation_node::nodetype

  • navigation_node::NODETYPE_LEAF
  • navigation_node::NODETYPE_BRANCH

Type constants are used to identify the moodle structure point that this node represents

  • navigation_node::TYPE_SYSTEM
  • navigation_node::TYPE_CATEGORY
  • navigation_node::TYPE_COURSE
  • navigation_node::TYPE_SECTION
  • navigation_node::TYPE_ACTIVITY
  • navigation_node::TYPE_RESOURCE
  • navigation_node::TYPE_CUSTOM
  • navigation_node::TYPE_SETTING

global_navigation extends navigation_node

Note: global_navigation extends navigation_node thus all navigation node methods and properties not override below will be available.

Methods

  • global_navigation::add_course_section_generic(&$keys, $course, $name, $activeparam)
  • global_navigation::add_courses($courses, $categoryid=null)
  • global_navigation::content()

Properties

  • global_navigation::expandable [property: an array of nodes that could be expanded]
  • global_navigation::expansionlimit [property: used to limit the items displayed]

settings_navigation extends navigation_node

Methods

  • settings_navigation::add($text, $action, $shorttext, $key, $type, $icon)
  • settings_navigation::remove_empty_root_branches()
  • settings_navigation::content()
  • settings_navigation::prepend($text, $action, $shorttext, $key, $type, $icon)

navbar extends navigation_node

Note: navbar extends navigation_node thus all navigation node methods and properties not override below will be available.

Methods

  • navbar::add($text, $action, $shorttext, $key, $type, $icon)
  • navbar::has_items()
  • settings_navigation::content()

Further Information

Navigation 2.0

Navigation 2.0 implementation plan

Forum Discussion

Navigation_2.0_structure