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