blocks-navigation MDL-25596 Fixed up redundant AJAX calls when navigation was limited...
[moodle.git] / blocks / navigation / yui / navigation / navigation.js
CommitLineData
48d8d090
SH
1YUI.add('moodle-block_navigation-navigation', function(Y){
2
8e5c23e0
SH
3var EXPANSIONLIMIT_EVERYTHING = 0,
4 EXPANSIONLIMIT_COURSE = 20,
5 EXPANSIONLIMIT_SECTION = 30,
6 EXPANSIONLIMIT_ACTIVITY = 40;
7
8
48d8d090
SH
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 */
15var TREE = function(config) {
16 TREE.superclass.constructor.apply(this, arguments);
17}
18TREE.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;
28
29 var node = Y.one('#inst'+config.id);
30
31 // Can't find the block instance within the page
32 if (node === null) {
33 return;
34 }
35
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');
39
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 }
60
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
73
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 }
79
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 }
88
89 // Toggle expand/collapse providing its not a root level branch.
90 if (!target.hasClass('depth_1')) {
91 target.toggleClass('collapsed');
92 }
93
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 }
102
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 }
113}
114// The tree extends the YUI base foundation.
115Y.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
8e5c23e0
SH
128 },
129 expansionlimit : {
130 value : 0,
131 setter : function(val) {
132 return parseInt(val);
133 }
48d8d090
SH
134 }
135 }
136});
137if (M.core_dock && M.core_dock.genericblock) {
138 Y.augment(TREE, M.core_dock.genericblock);
139}
140
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 */
146var BRANCH = function(config) {
147 BRANCH.superclass.constructor.apply(this, arguments);
148}
149BRANCH.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 }
8e5c23e0 176 // Get the node for this branch
48d8d090 177 this.node = Y.one('#', this.get('id'));
8e5c23e0
SH
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 }
48d8d090
SH
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) {
193
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'));
197
198 if (isbranch) {
199 branchli.addClass('collapsed').addClass('contains_branch');
200 branchp.addClass('branch');
201 }
202
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 }
222
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 }
240
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();
278
279 if (this.node.hasClass('loadingbranch')) {
280 return true;
281 }
282
283 this.node.addClass('loadingbranch');
284
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 };
292
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 }
359}
360Y.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});
419
420/**
421 * This namespace will contain all of the contents of the navigation blocks
422 * global navigation and settings.
423 * @namespace
424 */
425M.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};
443
444}, '@VERSION@', {requires:['base', 'core_dock', 'io', 'node', 'dom', 'event-custom', 'event-delegate', 'json-parse']});