navigation MDL-25596 Improvements for the navigation blocks JS
authorSam Hemelryk <sam@moodle.com>
Thu, 23 Dec 2010 03:21:07 +0000 (11:21 +0800)
committerSam Hemelryk <sam@moodle.com>
Thu, 23 Dec 2010 03:21:07 +0000 (11:21 +0800)
The biggest change is that the navigation block has been converted to a proper YUI module.
The following are the other changes made at the same time:
 * A loading icon is displayed when a branch is being loaded by AJAX.
 * Fixed a bug where you could trigger multiple AJAX requests by rapidly clicking an unloaded branch.
 * Fixed a bug where empty branches weren't being marked as such after a successful AJAX load.
 * When docked the width of the blocks dock panel is now inspected an increased if required to try avoid horizontal scrolling.
 * Removed the no longer needed inclusion of the YUI2 dom library from the navigation and settings block.
 * Expandable nodes are now passed as JS data allowing the navigation JS to be initialised through block_navigation::get_required_javascript.
 * AJAX is now focused around the branch in question rather than the tree in general.
 * Expansion of branches is now delegated to the tree rather than being an individual event on all branches.
 * Tidied up the code in general removing unneeded-unused parameters.

blocks/dock.js
blocks/navigation/block_navigation.php
blocks/navigation/navigation.js [deleted file]
blocks/navigation/styles.css
blocks/navigation/yui/navigation/navigation.js [new file with mode: 0644]
blocks/settings/block_settings.php
lib/navigationlib.php

index 5a2d56b..225c163 100644 (file)
@@ -268,6 +268,43 @@ M.core_dock.getPanel = function() {
                 }
                 return;
             };
+            /**
+             * Increases the width of the panel to avoid horizontal scrolling
+             * if possible.
+             */
+            dockpanel.correctWidth = function() {
+                var bd = this.one('.dockeditempanel_bd');
+
+                // Width of content
+                var w = bd.get('clientWidth');
+                // Scrollable width of content
+                var s = bd.get('scrollWidth');
+                // Width of content container with overflow
+                var ow = this.get('offsetWidth');
+                // The new width
+                var nw = w;
+                // The max width (80% of screen)
+                var mw = Math.round(this.get('winWidth') * 0.8);
+
+                // If the scrollable width is more than the visible width
+                if (s > w) {
+                    //   Content width
+                    // + the difference
+                    // + any rendering difference (borders, padding)
+                    // + 10px to make it look nice.
+                    nw = w + (s-w) + ((ow-w)*2) + 10;
+                }
+
+                // Make sure its not more then the maxwidth
+                if (nw > mw) {
+                    nw = mw;
+                }
+
+                // Set the new width if its more than the old width.
+                if (nw > ow) {
+                    this.setStyle('width', nw+'px');
+                }
+            }
             // Put the dockpanel in the body
             parent.append(dockpanel);
             // Return it
@@ -520,7 +557,7 @@ M.core_dock.init_genericblock = function(Y, id) {
     if (!this.initialised) {
         this.init(Y);
     }
-    new this.genericblock(id).init(Y, Y.one('#inst'+id));
+    new this.genericblock(id).initialise_block(Y, Y.one('#inst'+id));
 };
 /**
  * Removes the node at the given index and puts it back into conventional page sturcture
@@ -700,7 +737,7 @@ M.core_dock.genericblock.prototype = {
      * @param {YUI.Node} node The node that contains all of the block's content
      * @return {M.core_dock.genericblock}
      */
-    init : function(Y, node) {
+    initialise_block : function(Y, node) {
         M.core_dock.init(Y);
         
         this.Y = Y;
@@ -939,6 +976,7 @@ M.core_dock.item.prototype = {
         panel.setHeader(this.titlestring, this.commands);
         panel.setBody(Y.Node.create('<div class="'+this.blockclass+' block_docked"></div>').append(this.contents));
         panel.show();
+        panel.correctWidth();
 
         this.active = true;
         // Add active item class first up
index 73522fe..8d67196 100644 (file)
@@ -102,6 +102,13 @@ class block_navigation extends block_base {
     function get_required_javascript() {
         global $CFG;
         user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
+        $this->page->requires->js_module('core_dock');
+        $limit = 20;
+        if (!empty($CFG->navcourselimit)) {
+            $limit = $CFG->navcourselimit;
+    }
+        $arguments = array('id'=>$this->instance->id, 'instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked(), 'courselimit'=>$limit);
+        $this->page->requires->yui_module(array('core_dock', 'moodle-block_navigation-navigation'), 'M.block_navigation.init_add_tree', array($arguments));
     }
 
     /**
@@ -113,7 +120,6 @@ class block_navigation extends block_base {
         if ($this->contentgenerated === true) {
             return $this->content;
         }
-        $this->page->requires->yui2_lib('dom');
         // JS for navigation moved to the standard theme, the code will probably have to depend on the actual page structure
         // $this->page->requires->js('/lib/javascript-navigation.js');
         // Navcount is used to allow us to have multiple trees although I dont' know why
@@ -171,14 +177,7 @@ class block_navigation extends block_base {
             }
         }
 
-        // Initialise the JS tree object
-        $module = array('name'=>'block_navigation', 'fullpath'=>'/blocks/navigation/navigation.js', 'requires'=>array('core_dock', 'io', 'node', 'dom', 'event-custom', 'json-parse'), 'strings'=>array(array('viewallcourses','moodle')));
-        $limit = 20;
-        if (!empty($CFG->navcourselimit)) {
-            $limit = $CFG->navcourselimit;
-        }
-        $arguments = array($this->instance->id, array('expansions'=>$expandable, 'instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked(), 'courselimit'=>$limit));
-        $this->page->requires->js_init_call('M.block_navigation.init_add_tree', $arguments, false, $module);
+        $this->page->requires->data_for_js('navtreeexpansions'.$this->instance->id, $expandable);
 
         $options = array();
         $options['linkcategories'] = (!empty($this->config->linkcategories) && $this->config->linkcategories == 'yes');
diff --git a/blocks/navigation/navigation.js b/blocks/navigation/navigation.js
deleted file mode 100644 (file)
index e5e4fae..0000000
+++ /dev/null
@@ -1,393 +0,0 @@
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * This file contains classes used to manage the navigation structures in Moodle
- * and was introduced as part of the changes occuring in Moodle 2.0
- *
- * @since 2.0
- * @package javascript
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-/**
- * This namespace will contain all of the contents of the navigation blocks
- * global navigation and settings.
- * @namespace
- */
-M.block_navigation = M.block_navigation || {
-    /** The number of expandable branches in existence */
-    expandablebranchcount:0,
-    /** An array of initialised trees */
-    treecollection:[],
-    /**
-     * Will contain all of the classes for the navigation blocks
-     * @namespace
-     */
-    classes:{},
-    courselimit : 20,
-    /**
-     * This function gets called when the module is first loaded as required by
-     * the YUI.add statement at the bottom of the page.
-     *
-     * NOTE: This will only be executed ONCE
-     * @function
-     */
-    init:function(Y) {
-        M.core_dock.init(Y);
-        if (M.core_dock.genericblock) {
-            // Give the tree class the dock block properties
-            Y.augment(M.block_navigation.classes.tree, M.core_dock.genericblock);
-        }
-    },
-    /**
-     * Add new instance of navigation tree to tree collection
-     */
-    init_add_tree:function(Y, id, properties) {
-        if (properties.courselimit) {
-            this.courselimit = properties.courselimit;
-        }
-       M.block_navigation.treecollection[id] = new M.block_navigation.classes.tree(Y, id, properties);
-    }
-};
-
-/**
- * @class tree
- * @constructor
- * @base M.core_dock.genericblock
- * @param {YUI} Y A yui instance to use with the navigation
- * @param {string} id The name of the tree
- * @param {object} properties Object containing tree properties
- */
-M.block_navigation.classes.tree = function(Y, id, properties) {
-    this.Y = Y;
-    this.id = id;
-    this.key = id;
-    this.errorlog = [];
-    this.ajaxbranches = 0;
-    this.expansions = [];
-    this.instance = id;
-    this.cachedcontentnode = null;
-    this.cachedfooter = null;
-    this.position = 'block';
-    this.skipsetposition = false;
-    this.candock = false;
-
-    if (properties.expansions) {
-        this.expansions = properties.expansions;
-    }
-    if (properties.instance) {
-        this.instance = properties.instance;
-    }
-    if (properties.candock) {
-        this.candock = true;
-    }
-
-    var node = this.Y.one('#inst'+this.id);
-
-    // Can't find the block instance within the page
-    if (node === null) {
-        return;
-    }
-
-    // Attach event to toggle expansion
-    node.all('.tree_item.branch').on('click', this.toggleexpansion , this);
-
-    // Attach events to expand by AJAX
-    //var expandablenode;
-    for (var i in this.expansions) {
-        var expandablenode = Y.one('#'+this.expansions[i].id);
-        if (expandablenode) {
-            expandablenode.on('ajaxload|click', this.init_load_ajax, this, this.expansions[i]);
-            M.block_navigation.expandablebranchcount++;
-        } else if (M.cfg.debug) {
-            Y.one(document.body).append(Y.Node.create('<div class="notification" style="font-size:6pt;">Expandable node within navigation was missing [#'+this.expansions[i].id+']</div>'));
-        } else {
-            // Failing over silently
-        }
-    }
-
-    if (node.hasClass('block_js_expansion')) {
-        node.on('mouseover', function(e){this.toggleClass('mouseover');}, node);
-        node.on('mouseout', function(e){this.toggleClass('mouseover');}, node);
-    }
-
-    // Call the generic blocks init method to add all the generic stuff
-    if (this.candock) {
-        this.init(Y, node);
-    }
-};
-
-/**
- * Loads a branch via AJAX
- * @param {event} e The event object
- * @param {object} branch A branch to load via ajax
- */
-M.block_navigation.classes.tree.prototype.init_load_ajax = function(e, branch) {
-    e.stopPropagation();
-    var target = e.target;
-    if (target.test('span')) {
-        target = target.ancestor('p');
-    }
-    if (!target || !target.test('p')) {
-        return true;
-    }
-    var cfginstance = '', Y = this.Y;
-    if (this.instance != null) {
-        cfginstance = '&instance='+this.instance
-    }
-    Y.io(M.cfg.wwwroot+'/lib/ajax/getnavbranch.php', {
-        method:'POST',
-        data:'elementid='+branch.id+'&id='+branch.branchid+'&type='+branch.type+'&sesskey='+M.cfg.sesskey+cfginstance,
-        on: {
-            complete:this.load_ajax,
-            success:function() {Y.detach('click', this.init_load_ajax, target);}
-        },
-        context:this,
-        arguments:{
-            target:target
-        }
-    });
-    return true;
-};
-
-/**
- * Takes an branch provided through ajax and loads it into the tree
- * @param {int} tid The transaction id
- * @param {object} outcome
- * @param {mixed} args
- * @return bool
- */
-M.block_navigation.classes.tree.prototype.load_ajax = function(tid, outcome, args) {
-    try {
-        var object = this.Y.JSON.parse(outcome.responseText);
-        if (this.add_branch(object, args.target.ancestor('li') ,1)) {
-            if (this.candock) {
-                M.core_dock.resize();
-            }
-            return true;
-        }
-    } catch (e) {
-        // If we got here then there was an error parsing the result
-    }
-    // The branch is empty so class it accordingly
-    args.target.replaceClass('branch', 'emptybranch');
-    return true;
-};
-
-/**
- * Adds a branch into the tree provided with some XML
- * @param {object} branchobj
- * @param {Y.Node} target
- * @param {int} depth
- * @return bool
- */
-M.block_navigation.classes.tree.prototype.add_branch = function(branchobj, target, depth) {
-
-    // Make the new branch into an object
-    var branch = new M.block_navigation.classes.branch(this, branchobj);
-    var childrenul = false, Y = this.Y;
-    if (depth === 1) {
-        if (!branch.children) {
-            return false;
-        }
-        childrenul = Y.Node.create('<ul></ul>');
-        target.appendChild(childrenul);
-    } else {
-        childrenul = branch.inject_into_dom(target);
-    }
-    if (childrenul) {
-        var count = 0;
-        for (var i in branch.children) {
-            // Add each branch to the tree
-            if (branch.children[i].type == 20) {
-                count++;
-            }
-            if (typeof(branch.children[i])=='object') {
-                this.add_branch(branch.children[i], childrenul, depth+1);
-            }
-        }
-        if (branch.type == 10 && count >= M.block_navigation.courselimit) {
-            var properties = Array();
-            properties['name'] = M.str.moodle.viewallcourses;
-            properties['title'] = M.str.moodle.viewallcourses;
-            properties['link'] = M.cfg.wwwroot+'/course/category.php?id='+branch.key;
-            properties['haschildren'] = false;
-            properties['icon'] = {'pix':"i/navigationitem",'component':'moodle'};
-            this.add_branch(properties, childrenul, depth+1);
-        }
-    }
-    return true;
-};
-/**
- * Toggle a branch as expanded or collapsed
- * @param {Event} e
- */
-M.block_navigation.classes.tree.prototype.toggleexpansion = function(e) {
-    // First check if they managed to click on the li iteslf, then find the closest
-    // LI ancestor and use that
-
-    if (e.target.get('nodeName').toUpperCase() == 'A') {
-        // A link has been clicked don't fire any more events just do the default.
-        e.stopPropagation();
-        return;
-    }
-
-    var target = e.target;
-    if (!target.test('li')) {
-        target = target.ancestor('li')
-    }
-
-    if (target && !target.hasClass('depth_1')) {
-        target.toggleClass('collapsed');
-    }
-
-    if (this.candock) {
-        M.core_dock.resize();
-    }
-};
-
-/**
- * This class represents a branch for a tree
- * @class branch
- * @constructor
- * @param {M.block_navigation.classes.tree} tree
- * @param {object|null} obj
- */
-M.block_navigation.classes.branch = function(tree, obj) {
-    this.tree = tree;
-    this.name = null;
-    this.title = null;
-    this.classname = null;
-    this.id = null;
-    this.key = null;
-    this.type = null;
-    this.link = null;
-    this.icon = null;
-    this.expandable = null;
-    this.expansionceiling = null;
-    this.hidden = false;
-    this.haschildren = false;
-    this.children = false;
-    if (obj !== null) {
-        // Construct from the provided xml
-        this.construct_from_json(obj);
-    }
-};
-/**
- * Populates this branch from a JSON object
- * @param {object} obj
- */
-M.block_navigation.classes.branch.prototype.construct_from_json = function(obj) {
-    for (var i in obj) {
-        this[i] = obj[i];
-    }
-    if (this.children && this.children.length > 0) {
-        this.haschildren = true;
-    } else {
-        this.children = [];
-    }
-    if (this.id && this.id.match(/^expandable_branch_\d+$/)) {
-        // Assign a new unique id for this new expandable branch
-        M.block_navigation.expandablebranchcount++;
-        this.id = 'expandable_branch_'+M.block_navigation.expandablebranchcount;
-    }
-};
-/**
- * Injects a branch into the tree at the given location
- * @param {element} element
- */
-M.block_navigation.classes.branch.prototype.inject_into_dom = function(element) {
-
-    var Y = this.tree.Y;
-
-    var isbranch = ((this.expandable !== null || this.haschildren) && this.expansionceiling===null);
-    var branchli = Y.Node.create('<li></li>');
-    var branchp = Y.Node.create('<p class="tree_item"></p>');
-
-    if (isbranch) {
-        branchli.addClass('collapsed');
-        branchli.addClass('contains_branch');
-        branchp.addClass('branch');
-        branchp.on('click', this.tree.toggleexpansion, this.tree);
-        if (this.expandable) {
-            branchp.on('ajaxload|click', this.tree.init_load_ajax, this.tree, {branchid:this.key,id:this.id,type:this.type});
-        }
-    }
-
-    if (this.myclass !== null) {
-        branchp.addClass(this.myclass);
-    }
-    if (this.id !== null) {
-        branchp.setAttribute('id', this.id);
-    }
-
-    // Prepare the icon, should be an object representing a pix_icon
-    var branchicon = false;
-    if (this.icon != null && (!isbranch || this.type == 40)) {
-        branchicon = Y.Node.create('<img alt="" />');
-        branchicon.setAttribute('src', M.util.image_url(this.icon.pix, this.icon.component));
-        branchli.addClass('item_with_icon');
-        if (this.icon.alt) {
-            branchicon.setAttribute('alt', this.icon.alt);
-        }
-        if (this.icon.title) {
-            branchicon.setAttribute('alt', this.icon.title);
-        }
-        if (this.icon.classes) {
-            for (var i in this.icon.classes) {
-                branchicon.addClass(this.icon.classes[i]);
-            }
-        }
-    }
-
-    if (this.link === null) {
-        if (branchicon) {
-            branchp.appendChild(branchicon);
-        }
-        branchp.append(this.name.replace(/\n/g, '<br />'));
-    } else {
-        var branchlink = Y.Node.create('<a title="'+this.title+'" href="'+this.link+'"></a>');
-        if (branchicon) {
-            branchlink.appendChild(branchicon);
-        }
-        branchlink.append(this.name.replace(/\n/g, '<br />'));
-        if (this.hidden) {
-            branchlink.addClass('dimmed');
-        }
-        branchp.appendChild(branchlink);
-    }
-
-    branchli.appendChild(branchp);
-    if (this.haschildren) {
-        var childrenul = Y.Node.create('<ul></ul>');
-        branchli.appendChild(childrenul);
-        element.appendChild(branchli);
-        return childrenul
-    } else {
-        element.appendChild(branchli);
-        return false;
-    }
-};
-
-/**
- * Causes the navigation block module to initalise the first time the module
- * is used!
- *
- * NOTE: Never convert the second argument to a function reference...
- * doing so causes scoping issues
- */
-YUI.add('block_navigation', function(Y){M.block_navigation.init(Y);}, '0.0.0.1', M.yui.loader.modules.block_navigation.requires);
\ No newline at end of file
index c6aecd3..8e204dd 100644 (file)
 .block_navigation .block_tree .active_tree_node {font-weight:bold;}
 .block_navigation .block_tree .depth_1.current_branch ul {font-weight:normal;}
 
+.dock .block_navigation .tree_item {white-space: nowrap;}
+
 .jsenabled .block_navigation .block_tree .tree_item.branch {cursor:pointer;}
 .jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0% 5%;background-repeat: no-repeat;}
 .jsenabled .block_navigation .block_tree .collapsed ul {display: none;}
 .jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
+.jsenabled .block_navigation .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
 
 /** JavaScript state rules **/
 .jsenabled .block_navigation.dock_on_load,
diff --git a/blocks/navigation/yui/navigation/navigation.js b/blocks/navigation/yui/navigation/navigation.js
new file mode 100644 (file)
index 0000000..d35d8d7
--- /dev/null
@@ -0,0 +1,424 @@
+YUI.add('moodle-block_navigation-navigation', function(Y){
+
+/**
+ * Navigation tree class.
+ *
+ * This class establishes the tree initially, creating expandable branches as
+ * required, and delegating the expand/collapse event.
+ */
+var TREE = function(config) {
+    TREE.superclass.constructor.apply(this, arguments);
+}
+TREE.prototype = {
+    /**
+     * The tree's ID, normally its block instance id.
+     */
+    id : null,
+    /**
+     * Initialise the tree object when its first created.
+     */
+    initializer : function(config) {
+        this.id = config.id;
+
+        var node = Y.one('#inst'+config.id);
+
+        // Can't find the block instance within the page
+        if (node === null) {
+            return;
+        }
+
+        // Delegate event to toggle expansion
+        var self = this;
+        Y.delegate('click', function(e){self.toggleExpansion(e);}, node.one('.block_tree'), '.tree_item.branch');
+
+        // Gather the expandable branches ready for initialisation.
+        var expansions = [];
+        if (config.expansions) {
+            expansions = config.expansions;
+        } else if (window['navtreeexpansions'+config.id]) {
+            expansions = window['navtreeexpansions'+config.id];
+        }
+        // Establish each expandable branch as a tree branch.
+        for (var i in expansions) {
+            new BRANCH({
+                tree:this,
+                branchobj:expansions[i],
+                overrides : {
+                    expandable : true,
+                    children : [],
+                    haschildren : true
+                }
+            }).wire();
+            M.block_navigation.expandablebranchcount++;
+        }
+
+        // Call the generic blocks init method to add all the generic stuff
+        if (this.get('candock')) {
+            this.initialise_block(Y, node);
+        }
+    },
+    /**
+     * This is a callback function responsible for expanding and collapsing the
+     * branches of the tree. It is delegated to rather than multiple event handles.
+     */
+    toggleExpansion : function(e) {
+        // First check if they managed to click on the li iteslf, then find the closest
+        // LI ancestor and use that
+
+        if (e.target.test('a')) {
+            // A link has been clicked don't fire any more events just do the default.
+            e.stopPropagation();
+            return;
+        }
+
+        // Makes sure we can get to the LI containing the branch.
+        var target = e.target;
+        if (!target.test('li')) {
+            target = target.ancestor('li')
+        }
+        if (!target) {
+            return;
+        }
+
+        // Toggle expand/collapse providing its not a root level branch.
+        if (!target.hasClass('depth_1')) {
+            target.toggleClass('collapsed');
+        }
+
+        // If the accordian feature has been enabled collapse all siblings.
+        if (this.get('accordian')) {
+            target.siblings('li').each(function(){
+                if (this.get('id') !== target.get('id') && !this.hasClass('collapsed')) {
+                    this.addClass('collapsed');
+                }
+            });
+        }
+
+        // If this block can dock tell the dock to resize if required and check
+        // the width on the dock panel in case it is presently in use.
+        if (this.get('candock')) {
+            M.core_dock.resize();
+            var panel = M.core_dock.getPanel();
+            if (panel.visible) {
+                panel.correctWidth();
+            }
+        }
+    }
+}
+// The tree extends the YUI base foundation.
+Y.extend(TREE, Y.Base, TREE.prototype, {
+    NAME : 'navigation-tree',
+    ATTRS : {
+        instance : {
+            value : null
+        },
+        candock : {
+            validator : Y.Lang.isBool,
+            value : false
+        },
+        accordian : {
+            validator : Y.Lang.isBool,
+            value : false
+        }
+    }
+});
+if (M.core_dock && M.core_dock.genericblock) {
+    Y.augment(TREE, M.core_dock.genericblock);
+}
+
+/**
+ * The tree branch class.
+ * This class is used to manage a tree branch, in particular its ability to load
+ * its contents by AJAX.
+ */
+var BRANCH = function(config) {
+    BRANCH.superclass.constructor.apply(this, arguments);
+}
+BRANCH.prototype = {
+    /**
+     * The node for this branch (p)
+     */
+    node : null,
+    /**
+     * A reference to the ajax load event handle when created.
+     */
+    event_ajaxload : null,
+    /**
+     * Initialises the branch when it is first created.
+     */
+    initializer : function(config) {
+        if (config.branchobj !== null) {
+            // Construct from the provided xml
+            for (var i in config.branchobj) {
+                this.set(i, config.branchobj[i]);
+            }
+            var children = this.get('children');
+            this.set('haschildren', (children.length > 0));
+        }
+        if (config.overrides !== null) {
+            // Construct from the provided xml
+            for (var i in config.overrides) {
+                this.set(i, config.overrides[i]);
+            }
+        }
+        this.node = Y.one('#', this.get('id'));
+    },
+    /**
+     * Draws the branch within the tree.
+     *
+     * This function creates a DOM structure for the branch and then injects
+     * it into the navigation tree at the correct point.
+     */
+    draw : function(element) {
+
+        var isbranch = (this.get('expandable') || this.get('haschildren'));
+        var branchli = Y.Node.create('<li></li>');
+        var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
+
+        if (isbranch) {
+            branchli.addClass('collapsed').addClass('contains_branch');
+            branchp.addClass('branch');
+        }
+
+        // Prepare the icon, should be an object representing a pix_icon
+        var branchicon = false;
+        var icon = this.get('icon');
+        if (icon && (!isbranch || this.get('type') == 40)) {
+            branchicon = Y.Node.create('<img alt="" />');
+            branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
+            branchli.addClass('item_with_icon');
+            if (icon.alt) {
+                branchicon.setAttribute('alt', icon.alt);
+            }
+            if (icon.title) {
+                branchicon.setAttribute('title', icon.title);
+            }
+            if (icon.classes) {
+                for (var i in icon.classes) {
+                    branchicon.addClass(icon.classes[i]);
+                }
+            }
+        }
+
+        var link = this.get('link');
+        if (!link) {
+            if (branchicon) {
+                branchp.appendChild(branchicon);
+            }
+            branchp.append(this.get('name'));
+        } else {
+            var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
+            if (branchicon) {
+                branchlink.appendChild(branchicon);
+            }
+            branchlink.append(this.get('name'));
+            if (this.get('hidden')) {
+                branchlink.addClass('dimmed');
+            }
+            branchp.appendChild(branchlink);
+        }
+
+        branchli.appendChild(branchp);
+        element.appendChild(branchli);
+        this.node = branchp;
+        return this;
+    },
+    /**
+     * Attaches required events to the branch structure.
+     */
+    wire : function() {
+        this.node = this.node || Y.one('#'+this.get('id'));
+        if (!this.node) {
+            return false;
+        }
+        if (this.get('expandable')) {
+            this.event_ajaxload = this.node.on('ajaxload|click', this.ajaxLoad, this);
+        }
+        return this;
+    },
+    /**
+     * Gets the UL element that children for this branch should be inserted into.
+     */
+    getChildrenUL : function() {
+        var ul = this.node.next('ul');
+        if (!ul) {
+            ul = Y.Node.create('<ul></ul>');
+            this.node.ancestor().append(ul);
+        }
+        return ul;
+    },
+    /**
+     * Load the content of the branch via AJAX.
+     *
+     * This function calls ajaxProcessResponse with the result of the AJAX
+     * request made here.
+     */
+    ajaxLoad : function(e) {
+        e.stopPropagation();
+
+        if (this.node.hasClass('loadingbranch')) {
+            return true;
+        }
+
+        this.node.addClass('loadingbranch');
+
+        var params = {
+            elementid : this.get('id'),
+            id : this.get('key'),
+            type : this.get('type'),
+            sesskey : M.cfg.sesskey,
+            instance : this.get('tree').get('instance')
+        };
+
+        Y.io(M.cfg.wwwroot+'/lib/ajax/getnavbranch.php', {
+            method:'POST',
+            data:  build_querystring(params),
+            on: {
+                complete: this.ajaxProcessResponse
+            },
+            context:this
+        });
+        return true;
+    },
+    /**
+     * Processes an AJAX request to load the content of this branch through
+     * AJAX.
+     */
+    ajaxProcessResponse : function(tid, outcome) {
+        this.node.removeClass('loadingbranch');
+        this.event_ajaxload.detach();
+        try {
+            var object = Y.JSON.parse(outcome.responseText);
+            if (object.children && object.children.length > 0) {
+                for (var i in object.children) {
+                    if (typeof(object.children[i])=='object') {
+                        this.addChild(object.children[i]);
+                    }
+                }
+                this.get('tree').toggleExpansion({target:this.node});
+                return true;
+            }
+        } catch (ex) {
+            // If we got here then there was an error parsing the result
+        }
+        // The branch is empty so class it accordingly
+        this.node.replaceClass('branch', 'emptybranch');
+        return true;
+    },
+    /**
+     * Turns the branch object passed to the method into a proper branch object
+     * and then adds it as a child of this branch.
+     */
+    addChild : function(branchobj) {
+        // Make the new branch into an object
+        var branch = new BRANCH({tree:this.get('tree'), branchobj:branchobj});
+        if (branch.draw(this.getChildrenUL())) {
+            branch.wire();
+            var count = 0, i, children = branch.get('children');
+            for (i in children) {
+                // Add each branch to the tree
+                if (children[i].type == 20) {
+                    count++;
+                }
+                if (typeof(children[i])=='object') {
+                    branch.addChild(children[i]);
+                }
+            }
+            if (branch.get('type') == 10 && count >= M.block_navigation.courselimit) {
+                branch.addChild({
+                    name : M.str.moodle.viewallcourses,
+                    title : M.str.moodle.viewallcourses,
+                    link : M.cfg.wwwroot+'/course/category.php?id='+branch.get('key'),
+                    haschildren : false,
+                    icon : {'pix':"i/navigationitem",'component':'moodle'}
+                }, branch);
+            }
+        }
+        return true;
+    }
+}
+Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
+    NAME : 'navigation-branch',
+    ATTRS : {
+        tree : {
+            validator : Y.Lang.isObject
+        },
+        name : {
+            value : '',
+            validator : Y.Lang.isString,
+            setter : function(val) {
+                return val.replace(/\n/g, '<br />');
+            }
+        },
+        title : {
+            value : '',
+            validator : Y.Lang.isString
+        },
+        id : {
+            value : '',
+            validator : Y.Lang.isString,
+            getter : function(val) {
+                if (val == '') {
+                    val = 'expandable_branch_'+M.block_navigation.expandablebranchcount;
+                    M.block_navigation.expandablebranchcount++;
+                }
+                return val;
+            }
+        },
+        key : {
+            value : null
+        },
+        type : {
+            value : null
+        },
+        link : {
+            value : false
+        },
+        icon : {
+            value : false,
+            validator : Y.Lang.isObject
+        },
+        expandable : {
+            value : false,
+            validator : Y.Lang.isBool
+        },
+        hidden : {
+            value : false,
+            validator : Y.Lang.isBool
+        },
+        haschildren : {
+            value : false,
+            validator : Y.Lang.isBool
+        },
+        children : {
+            value : [],
+            validator : Y.Lang.isArray
+        }
+    }
+});
+
+/**
+ * This namespace will contain all of the contents of the navigation blocks
+ * global navigation and settings.
+ * @namespace
+ */
+M.block_navigation = M.block_navigation || {
+    /** The number of expandable branches in existence */
+    expandablebranchcount:1,
+    courselimit : 20,
+    instance : null,
+    /**
+     * Add new instance of navigation tree to tree collection
+     */
+    init_add_tree:function(properties) {
+        if (properties.courselimit) {
+            this.courselimit = properties.courselimit;
+        }
+        if (M.core_dock) {
+            M.core_dock.init(Y);
+        }
+        new TREE(properties);
+    }
+};
+
+}, '@VERSION@', {requires:['base', 'core_dock', 'io', 'node', 'dom', 'event-custom', 'event-delegate', 'json-parse']});
\ No newline at end of file
index bab697d..675200e 100644 (file)
@@ -92,9 +92,8 @@ class block_settings extends block_base {
 
     function get_required_javascript() {
         global $CFG;
-        $module = array('name'=>'block_navigation', 'fullpath'=>'/blocks/navigation/navigation.js', 'requires'=>array('core_dock', 'io', 'node', 'dom', 'event-custom', 'json-parse'));
-        $arguments = array($this->instance->id, array('instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked()));
-        $this->page->requires->js_init_call('M.block_navigation.init_add_tree', $arguments, false, $module);
+        $arguments = array('id'=>$this->instance->id, 'instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked());
+        $this->page->requires->yui_module(array('core_dock', 'moodle-block_navigation-navigation'), 'M.block_navigation.init_add_tree', array($arguments));
         user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
     }
 
@@ -107,7 +106,6 @@ class block_settings extends block_base {
         if ($this->contentgenerated === true) {
             return true;
         }
-        $this->page->requires->yui2_lib('dom');
         // JS for navigation moved to the standard theme, the code will probably have to depend on the actual page structure
         // $this->page->requires->js('/lib/javascript-navigation.js');
         block_settings::$navcount++;
index 67e5241..31d2a37 100644 (file)
@@ -540,7 +540,7 @@ class navigation_node implements renderable {
             if ($child->nodetype == self::NODETYPE_BRANCH && $child->children->count()==0 && $child->display) {
                 $child->id = 'expandable_branch_'.(count($expandable)+1);
                 $this->add_class('canexpand');
-                $expandable[] = array('id'=>$child->id,'branchid'=>$child->key,'type'=>$child->type);
+                $expandable[] = array('id'=>$child->id,'key'=>$child->key,'type'=>$child->type);
             }
             $child->find_expandable($expandable);
         }
@@ -3699,7 +3699,7 @@ class navigation_json {
      */
     public function set_expandable($expandable) {
         foreach ($expandable as $node) {
-            $this->expandable[$node['branchid'].':'.$node['type']] = $node;
+            $this->expandable[$node['key'].':'.$node['type']] = $node;
         }
     }
     /**