blocks-navigation MDL-25596 Fixed up redundant AJAX calls when navigation was limited...
[moodle.git] / blocks / navigation / yui / navigation / navigation.js
1 YUI.add('moodle-block_navigation-navigation', function(Y){
3 var EXPANSIONLIMIT_EVERYTHING = 0,
4     EXPANSIONLIMIT_COURSE     = 20,
5     EXPANSIONLIMIT_SECTION    = 30,
6     EXPANSIONLIMIT_ACTIVITY   = 40;
9 /**
10  * Navigation tree class.
11  *
12  * This class establishes the tree initially, creating expandable branches as
13  * required, and delegating the expand/collapse event.
14  */
15 var TREE = function(config) {
16     TREE.superclass.constructor.apply(this, arguments);
17 }
18 TREE.prototype = {
19     /**
20      * The tree's ID, normally its block instance id.
21      */
22     id : null,
23     /**
24      * Initialise the tree object when its first created.
25      */
26     initializer : function(config) {
27         this.id = config.id;
29         var node = Y.one('#inst'+config.id);
31         // Can't find the block instance within the page
32         if (node === null) {
33             return;
34         }
36         // Delegate event to toggle expansion
37         var self = this;
38         Y.delegate('click', function(e){self.toggleExpansion(e);}, node.one('.block_tree'), '.tree_item.branch');
40         // Gather the expandable branches ready for initialisation.
41         var expansions = [];
42         if (config.expansions) {
43             expansions = config.expansions;
44         } else if (window['navtreeexpansions'+config.id]) {
45             expansions = window['navtreeexpansions'+config.id];
46         }
47         // Establish each expandable branch as a tree branch.
48         for (var i in expansions) {
49             new BRANCH({
50                 tree:this,
51                 branchobj:expansions[i],
52                 overrides : {
53                     expandable : true,
54                     children : [],
55                     haschildren : true
56                 }
57             }).wire();
58             M.block_navigation.expandablebranchcount++;
59         }
61         // Call the generic blocks init method to add all the generic stuff
62         if (this.get('candock')) {
63             this.initialise_block(Y, node);
64         }
65     },
66     /**
67      * This is a callback function responsible for expanding and collapsing the
68      * branches of the tree. It is delegated to rather than multiple event handles.
69      */
70     toggleExpansion : function(e) {
71         // First check if they managed to click on the li iteslf, then find the closest
72         // LI ancestor and use that
74         if (e.target.test('a')) {
75             // A link has been clicked don't fire any more events just do the default.
76             e.stopPropagation();
77             return;
78         }
80         // Makes sure we can get to the LI containing the branch.
81         var target = e.target;
82         if (!target.test('li')) {
83             target = target.ancestor('li')
84         }
85         if (!target) {
86             return;
87         }
89         // Toggle expand/collapse providing its not a root level branch.
90         if (!target.hasClass('depth_1')) {
91             target.toggleClass('collapsed');
92         }
94         // If the accordian feature has been enabled collapse all siblings.
95         if (this.get('accordian')) {
96             target.siblings('li').each(function(){
97                 if (this.get('id') !== target.get('id') && !this.hasClass('collapsed')) {
98                     this.addClass('collapsed');
99                 }
100             });
101         }
103         // If this block can dock tell the dock to resize if required and check
104         // the width on the dock panel in case it is presently in use.
105         if (this.get('candock')) {
106             M.core_dock.resize();
107             var panel = M.core_dock.getPanel();
108             if (panel.visible) {
109                 panel.correctWidth();
110             }
111         }
112     }
114 // The tree extends the YUI base foundation.
115 Y.extend(TREE, Y.Base, TREE.prototype, {
116     NAME : 'navigation-tree',
117     ATTRS : {
118         instance : {
119             value : null
120         },
121         candock : {
122             validator : Y.Lang.isBool,
123             value : false
124         },
125         accordian : {
126             validator : Y.Lang.isBool,
127             value : false
128         },
129         expansionlimit : {
130             value : 0,
131             setter : function(val) {
132                 return parseInt(val);
133             }
134         }
135     }
136 });
137 if (M.core_dock && M.core_dock.genericblock) {
138     Y.augment(TREE, M.core_dock.genericblock);
141 /**
142  * The tree branch class.
143  * This class is used to manage a tree branch, in particular its ability to load
144  * its contents by AJAX.
145  */
146 var BRANCH = function(config) {
147     BRANCH.superclass.constructor.apply(this, arguments);
149 BRANCH.prototype = {
150     /**
151      * The node for this branch (p)
152      */
153     node : null,
154     /**
155      * A reference to the ajax load event handle when created.
156      */
157     event_ajaxload : null,
158     /**
159      * Initialises the branch when it is first created.
160      */
161     initializer : function(config) {
162         if (config.branchobj !== null) {
163             // Construct from the provided xml
164             for (var i in config.branchobj) {
165                 this.set(i, config.branchobj[i]);
166             }
167             var children = this.get('children');
168             this.set('haschildren', (children.length > 0));
169         }
170         if (config.overrides !== null) {
171             // Construct from the provided xml
172             for (var i in config.overrides) {
173                 this.set(i, config.overrides[i]);
174             }
175         }
176         // Get the node for this branch
177         this.node = Y.one('#', this.get('id'));
178         // Now check whether the branch is not expandable because of the expansionlimit
179         var expansionlimit = this.get('tree').get('expansionlimit');
180         var type = this.get('type');
181         if (expansionlimit != EXPANSIONLIMIT_EVERYTHING &&  type >= expansionlimit && type <= EXPANSIONLIMIT_ACTIVITY) {
182             this.set('expandable', false);
183             this.set('haschildren', false);
184         }
185     },
186     /**
187      * Draws the branch within the tree.
188      *
189      * This function creates a DOM structure for the branch and then injects
190      * it into the navigation tree at the correct point.
191      */
192     draw : function(element) {
194         var isbranch = (this.get('expandable') || this.get('haschildren'));
195         var branchli = Y.Node.create('<li></li>');
196         var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
198         if (isbranch) {
199             branchli.addClass('collapsed').addClass('contains_branch');
200             branchp.addClass('branch');
201         }
203         // Prepare the icon, should be an object representing a pix_icon
204         var branchicon = false;
205         var icon = this.get('icon');
206         if (icon && (!isbranch || this.get('type') == 40)) {
207             branchicon = Y.Node.create('<img alt="" />');
208             branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
209             branchli.addClass('item_with_icon');
210             if (icon.alt) {
211                 branchicon.setAttribute('alt', icon.alt);
212             }
213             if (icon.title) {
214                 branchicon.setAttribute('title', icon.title);
215             }
216             if (icon.classes) {
217                 for (var i in icon.classes) {
218                     branchicon.addClass(icon.classes[i]);
219                 }
220             }
221         }
223         var link = this.get('link');
224         if (!link) {
225             if (branchicon) {
226                 branchp.appendChild(branchicon);
227             }
228             branchp.append(this.get('name'));
229         } else {
230             var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
231             if (branchicon) {
232                 branchlink.appendChild(branchicon);
233             }
234             branchlink.append(this.get('name'));
235             if (this.get('hidden')) {
236                 branchlink.addClass('dimmed');
237             }
238             branchp.appendChild(branchlink);
239         }
241         branchli.appendChild(branchp);
242         element.appendChild(branchli);
243         this.node = branchp;
244         return this;
245     },
246     /**
247      * Attaches required events to the branch structure.
248      */
249     wire : function() {
250         this.node = this.node || Y.one('#'+this.get('id'));
251         if (!this.node) {
252             return false;
253         }
254         if (this.get('expandable')) {
255             this.event_ajaxload = this.node.on('ajaxload|click', this.ajaxLoad, this);
256         }
257         return this;
258     },
259     /**
260      * Gets the UL element that children for this branch should be inserted into.
261      */
262     getChildrenUL : function() {
263         var ul = this.node.next('ul');
264         if (!ul) {
265             ul = Y.Node.create('<ul></ul>');
266             this.node.ancestor().append(ul);
267         }
268         return ul;
269     },
270     /**
271      * Load the content of the branch via AJAX.
272      *
273      * This function calls ajaxProcessResponse with the result of the AJAX
274      * request made here.
275      */
276     ajaxLoad : function(e) {
277         e.stopPropagation();
279         if (this.node.hasClass('loadingbranch')) {
280             return true;
281         }
283         this.node.addClass('loadingbranch');
285         var params = {
286             elementid : this.get('id'),
287             id : this.get('key'),
288             type : this.get('type'),
289             sesskey : M.cfg.sesskey,
290             instance : this.get('tree').get('instance')
291         };
293         Y.io(M.cfg.wwwroot+'/lib/ajax/getnavbranch.php', {
294             method:'POST',
295             data:  build_querystring(params),
296             on: {
297                 complete: this.ajaxProcessResponse
298             },
299             context:this
300         });
301         return true;
302     },
303     /**
304      * Processes an AJAX request to load the content of this branch through
305      * AJAX.
306      */
307     ajaxProcessResponse : function(tid, outcome) {
308         this.node.removeClass('loadingbranch');
309         this.event_ajaxload.detach();
310         try {
311             var object = Y.JSON.parse(outcome.responseText);
312             if (object.children && object.children.length > 0) {
313                 for (var i in object.children) {
314                     if (typeof(object.children[i])=='object') {
315                         this.addChild(object.children[i]);
316                     }
317                 }
318                 this.get('tree').toggleExpansion({target:this.node});
319                 return true;
320             }
321         } catch (ex) {
322             // If we got here then there was an error parsing the result
323         }
324         // The branch is empty so class it accordingly
325         this.node.replaceClass('branch', 'emptybranch');
326         return true;
327     },
328     /**
329      * Turns the branch object passed to the method into a proper branch object
330      * and then adds it as a child of this branch.
331      */
332     addChild : function(branchobj) {
333         // Make the new branch into an object
334         var branch = new BRANCH({tree:this.get('tree'), branchobj:branchobj});
335         if (branch.draw(this.getChildrenUL())) {
336             branch.wire();
337             var count = 0, i, children = branch.get('children');
338             for (i in children) {
339                 // Add each branch to the tree
340                 if (children[i].type == 20) {
341                     count++;
342                 }
343                 if (typeof(children[i])=='object') {
344                     branch.addChild(children[i]);
345                 }
346             }
347             if (branch.get('type') == 10 && count >= M.block_navigation.courselimit) {
348                 branch.addChild({
349                     name : M.str.moodle.viewallcourses,
350                     title : M.str.moodle.viewallcourses,
351                     link : M.cfg.wwwroot+'/course/category.php?id='+branch.get('key'),
352                     haschildren : false,
353                     icon : {'pix':"i/navigationitem",'component':'moodle'}
354                 }, branch);
355             }
356         }
357         return true;
358     }
360 Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
361     NAME : 'navigation-branch',
362     ATTRS : {
363         tree : {
364             validator : Y.Lang.isObject
365         },
366         name : {
367             value : '',
368             validator : Y.Lang.isString,
369             setter : function(val) {
370                 return val.replace(/\n/g, '<br />');
371             }
372         },
373         title : {
374             value : '',
375             validator : Y.Lang.isString
376         },
377         id : {
378             value : '',
379             validator : Y.Lang.isString,
380             getter : function(val) {
381                 if (val == '') {
382                     val = 'expandable_branch_'+M.block_navigation.expandablebranchcount;
383                     M.block_navigation.expandablebranchcount++;
384                 }
385                 return val;
386             }
387         },
388         key : {
389             value : null
390         },
391         type : {
392             value : null
393         },
394         link : {
395             value : false
396         },
397         icon : {
398             value : false,
399             validator : Y.Lang.isObject
400         },
401         expandable : {
402             value : false,
403             validator : Y.Lang.isBool
404         },
405         hidden : {
406             value : false,
407             validator : Y.Lang.isBool
408         },
409         haschildren : {
410             value : false,
411             validator : Y.Lang.isBool
412         },
413         children : {
414             value : [],
415             validator : Y.Lang.isArray
416         }
417     }
418 });
420 /**
421  * This namespace will contain all of the contents of the navigation blocks
422  * global navigation and settings.
423  * @namespace
424  */
425 M.block_navigation = M.block_navigation || {
426     /** The number of expandable branches in existence */
427     expandablebranchcount:1,
428     courselimit : 20,
429     instance : null,
430     /**
431      * Add new instance of navigation tree to tree collection
432      */
433     init_add_tree:function(properties) {
434         if (properties.courselimit) {
435             this.courselimit = properties.courselimit;
436         }
437         if (M.core_dock) {
438             M.core_dock.init(Y);
439         }
440         new TREE(properties);
441     }
442 };
444 }, '@VERSION@', {requires:['base', 'core_dock', 'io', 'node', 'dom', 'event-custom', 'event-delegate', 'json-parse']});