Commit | Line | Data |
---|---|---|
1ce15fda SH |
1 | /** |
2 | * The dock namespace: Contains all things dock related | |
3 | * @namespace | |
4 | */ | |
53fc3e70 | 5 | M.core_dock = { |
7e4617f7 SH |
6 | count : 0, // The number of dock items currently |
7 | totalcount : 0, // The number of dock items through the page life | |
8 | items : [], // An array of dock items | |
9 | earlybinds : [], // Events added before the dock was augmented to support events | |
10 | Y : null, // The YUI instance to use with dock related code | |
11 | initialised : false, // True once thedock has been initialised | |
12 | delayedevent : null, // Will be an object if there is a delayed event in effect | |
673835d4 SH |
13 | preventevent : null, // Will be an eventtype if there is an eventyoe to prevent |
14 | holdingarea : null | |
edc858dc | 15 | }; |
7e4617f7 SH |
16 | /** |
17 | * Namespace containing the nodes that relate to the dock | |
18 | * @namespace | |
19 | */ | |
20 | M.core_dock.nodes = { | |
21 | dock : null, // The dock itself | |
22 | body : null, // The body of the page | |
23 | panel : null // The docks panel | |
edc858dc | 24 | }; |
7e4617f7 SH |
25 | /** |
26 | * Configuration parameters used during the initialisation and setup | |
27 | * of dock and dock items. | |
28 | * This is here specifically so that themers can override core parameters and | |
29 | * design aspects without having to re-write navigation | |
30 | * @namespace | |
31 | */ | |
32 | M.core_dock.cfg = { | |
33 | buffer:10, // Buffer used when containing a panel | |
34 | position:'left', // position of the dock | |
35 | orientation:'vertical', // vertical || horizontal determines if we change the title | |
36 | spacebeforefirstitem: 10, // Space between the top of the dock and the first item | |
37 | removeallicon: M.util.image_url('t/dock_to_block', 'moodle') | |
edc858dc | 38 | }; |
7e4617f7 SH |
39 | /** |
40 | * CSS classes to use with the dock | |
41 | * @namespace | |
42 | */ | |
43 | M.core_dock.css = { | |
44 | dock:'dock', // CSS Class applied to the dock box | |
45 | dockspacer:'dockspacer', // CSS class applied to the dockspacer | |
46 | controls:'controls', // CSS class applied to the controls box | |
47 | body:'has_dock', // CSS class added to the body when there is a dock | |
48 | dockeditem:'dockeditem', // CSS class added to each item in the dock | |
49 | dockeditemcontainer:'dockeditem_container', | |
50 | dockedtitle:'dockedtitle', // CSS class added to the item's title in each dock | |
51 | activeitem:'activeitem' // CSS class added to the active item | |
edc858dc | 52 | }; |
7e4617f7 SH |
53 | /** |
54 | * Augments the classes as required and processes early bindings | |
55 | */ | |
56 | M.core_dock.init = function(Y) { | |
57 | if (this.initialised) { | |
1ce15fda | 58 | return true; |
7e4617f7 SH |
59 | } |
60 | var css = this.css; | |
61 | this.initialised = true; | |
62 | this.Y = Y; | |
63 | this.nodes.body = Y.one(document.body); | |
64 | ||
65 | // Give the dock item class the event properties/methods | |
66 | Y.augment(this.item, Y.EventTarget); | |
67 | Y.augment(this, Y.EventTarget, true); | |
68 | ||
69 | // Publish the events the dock has | |
70 | this.publish('dock:beforedraw', {prefix:'dock'}); | |
71 | this.publish('dock:beforeshow', {prefix:'dock'}); | |
72 | this.publish('dock:shown', {prefix:'dock'}); | |
73 | this.publish('dock:hidden', {prefix:'dock'}); | |
74 | this.publish('dock:initialised', {prefix:'dock'}); | |
75 | this.publish('dock:itemadded', {prefix:'dock'}); | |
76 | this.publish('dock:itemremoved', {prefix:'dock'}); | |
77 | this.publish('dock:itemschanged', {prefix:'dock'}); | |
78 | this.publish('dock:panelgenerated', {prefix:'dock'}); | |
79 | this.publish('dock:panelresizestart', {prefix:'dock'}); | |
80 | this.publish('dock:resizepanelcomplete', {prefix:'dock'}); | |
81 | this.publish('dock:starting', {prefix: 'dock',broadcast: 2,emitFacade: true}); | |
82 | this.fire('dock:starting'); | |
83 | // Re-apply early bindings properly now that we can | |
84 | this.applyBinds(); | |
85 | // Check if there is a customisation function | |
86 | if (typeof(customise_dock_for_theme) === 'function') { | |
87 | try { | |
88 | // Run the customisation function | |
89 | customise_dock_for_theme(); | |
90 | } catch (exception) { | |
91 | // Do nothing at the moment | |
1ce15fda | 92 | } |
7e4617f7 SH |
93 | } |
94 | ||
a6338a13 SH |
95 | var dock = Y.one('#dock'); |
96 | if (!dock) { | |
97 | // Start the construction of the dock | |
98 | dock = Y.Node.create('<div id="dock" class="'+css.dock+' '+css.dock+'_'+this.cfg.position+'_'+this.cfg.orientation+'"></div>') | |
99 | .append(Y.Node.create('<div class="'+css.dockeditemcontainer+'"></div>')); | |
100 | this.nodes.body.append(dock); | |
101 | } else { | |
102 | dock.addClass(css.dock+'_'+this.cfg.position+'_'+this.cfg.orientation); | |
103 | } | |
ea9ad1f3 | 104 | this.holdingarea = Y.Node.create('<div></div>').setStyles({display:'none'}); |
673835d4 | 105 | this.nodes.body.append(this.holdingarea); |
a6338a13 SH |
106 | if (Y.UA.ie > 0 && Y.UA.ie < 7) { |
107 | // Adjust for IE 6 (can't handle fixed pos) | |
108 | dock.setStyle('height', dock.get('winHeight')+'px'); | |
109 | } | |
110 | // Store the dock | |
111 | this.nodes.dock = dock; | |
112 | this.nodes.container = dock.one('.'+css.dockeditemcontainer); | |
113 | ||
7e4617f7 SH |
114 | if (Y.all('.block.dock_on_load').size() == 0) { |
115 | // Nothing on the dock... hide it using CSS | |
116 | dock.addClass('nothingdocked'); | |
117 | } else { | |
118 | this.nodes.body.addClass(this.css.body); | |
119 | } | |
a6338a13 | 120 | |
7e4617f7 | 121 | this.fire('dock:beforedraw'); |
a6338a13 | 122 | |
7e4617f7 | 123 | // Add a removeall button |
8c29a17d SH |
124 | // Must set the image src seperatly of we get an error with XML strict headers |
125 | var removeall = Y.Node.create('<img alt="'+M.str.block.undockall+'" title="'+M.str.block.undockall+'" />'); | |
126 | removeall.setAttribute('src',this.cfg.removeallicon); | |
7e4617f7 SH |
127 | removeall.on('removeall|click', this.remove_all, this); |
128 | dock.appendChild(Y.Node.create('<div class="'+css.controls+'"></div>').append(removeall)); | |
129 | ||
130 | // Create a manager for the height of the tabs. Once set this can be forgotten about | |
131 | new (function(Y){ | |
132 | return { | |
133 | enabled : false, // True if the item_sizer is being used, false otherwise | |
134 | /** | |
135 | * Initialises the dock sizer which then attaches itself to the required | |
136 | * events in order to monitor the dock | |
137 | * @param {YUI} Y | |
138 | */ | |
139 | init : function() { | |
140 | M.core_dock.on('dock:itemschanged', this.checkSizing, this); | |
141 | Y.on('windowresize', this.checkSizing, this); | |
142 | }, | |
143 | /** | |
144 | * Check if the size dock items needs to be adjusted | |
145 | */ | |
146 | checkSizing : function() { | |
147 | var dock = M.core_dock; | |
148 | var possibleheight = dock.nodes.dock.get('offsetHeight') - dock.nodes.dock.one('.controls').get('offsetHeight') - (dock.cfg.buffer*3) - (dock.items.length*2); | |
149 | var totalheight = 0; | |
150 | for (var id in dock.items) { | |
151 | var dockedtitle = Y.one(dock.items[id].title).ancestor('.'+dock.css.dockedtitle); | |
152 | if (dockedtitle) { | |
153 | if (this.enabled) { | |
154 | dockedtitle.setStyle('height', 'auto'); | |
155 | } | |
156 | totalheight += dockedtitle.get('offsetHeight') || 0; | |
157 | } | |
158 | } | |
159 | if (totalheight > possibleheight) { | |
160 | this.enable(possibleheight); | |
161 | } | |
162 | }, | |
163 | /** | |
164 | * Enables the dock sizer and resizes where required. | |
165 | */ | |
166 | enable : function(possibleheight) { | |
167 | var dock = M.core_dock; | |
168 | var runningcount = 0; | |
169 | var usedheight = 0; | |
170 | this.enabled = true; | |
171 | for (var id in dock.items) { | |
172 | var itemtitle = Y.one(dock.items[id].title).ancestor('.'+dock.css.dockedtitle); | |
173 | if (!itemtitle) { | |
174 | continue; | |
175 | } | |
176 | var itemheight = Math.floor((possibleheight-usedheight) / (dock.count - runningcount)); | |
177 | var offsetheight = itemtitle.get('offsetHeight'); | |
178 | itemtitle.setStyle('overflow', 'hidden'); | |
179 | if (offsetheight > itemheight) { | |
180 | itemtitle.setStyle('height', itemheight+'px'); | |
181 | usedheight += itemheight; | |
182 | } else { | |
183 | usedheight += offsetheight; | |
184 | } | |
185 | runningcount++; | |
186 | } | |
1ce15fda | 187 | } |
edc858dc | 188 | }; |
7e4617f7 SH |
189 | })(Y).init(); |
190 | ||
191 | // Attach the required event listeners | |
192 | // We use delegate here as that way a handful of events are created for the dock | |
193 | // and all items rather than the same number for the dock AND every item individually | |
194 | Y.delegate('click', this.handleEvent, this.nodes.dock, '.'+this.css.dockedtitle, this, {cssselector:'.'+this.css.dockedtitle, delay:0}); | |
195 | Y.delegate('mouseenter', this.handleEvent, this.nodes.dock, '.'+this.css.dockedtitle, this, {cssselector:'.'+this.css.dockedtitle, delay:0.5, iscontained:true, preventevent:'click', preventdelay:3}); | |
8c29a17d SH |
196 | //Y.delegate('mouseleave', this.handleEvent, this.nodes.body, '#dock', this, {cssselector:'#dock', delay:0.5, iscontained:false}); |
197 | this.nodes.dock.on('mouseleave', this.handleEvent, this, {cssselector:'#dock', delay:0.5, iscontained:false}); | |
198 | ||
7e4617f7 SH |
199 | this.nodes.body.on('click', this.handleEvent, this, {cssselector:'body', delay:0}); |
200 | this.on('dock:itemschanged', this.resizeBlockSpace, this); | |
201 | this.on('dock:itemschanged', this.checkDockVisibility, this); | |
4e2e816a | 202 | this.on('dock:itemschanged', this.resetFirstItem, this); |
7e4617f7 SH |
203 | // Inform everyone the dock has been initialised |
204 | this.fire('dock:initialised'); | |
205 | return true; | |
edc858dc | 206 | }; |
7e4617f7 SH |
207 | /** |
208 | * Get the panel docked blocks will be shown in and initialise it if we havn't already. | |
209 | */ | |
210 | M.core_dock.getPanel = function() { | |
211 | if (this.nodes.panel === null) { | |
212 | // Initialise the dockpanel .. should only happen once | |
213 | this.nodes.panel = (function(Y, parent){ | |
214 | var dockpanel = Y.Node.create('<div id="dockeditempanel" class="dockitempanel_hidden"><div class="dockeditempanel_content"><div class="dockeditempanel_hd"></div><div class="dockeditempanel_bd"></div></div></div>'); | |
215 | // Give the dockpanel event target properties and methods | |
216 | Y.augment(dockpanel, Y.EventTarget); | |
217 | // Publish events for the dock panel | |
218 | dockpanel.publish('dockpanel:beforeshow', {prefix:'dockpanel'}); | |
219 | dockpanel.publish('dockpanel:shown', {prefix:'dockpanel'}); | |
220 | dockpanel.publish('dockpanel:beforehide', {prefix:'dockpanel'}); | |
221 | dockpanel.publish('dockpanel:hidden', {prefix:'dockpanel'}); | |
222 | dockpanel.publish('dockpanel:visiblechange', {prefix:'dockpanel'}); | |
223 | // Cache the content nodes | |
224 | dockpanel.contentNode = dockpanel.one('.dockeditempanel_content'); | |
225 | dockpanel.contentHeader = dockpanel.contentNode.one('.dockeditempanel_hd'); | |
226 | dockpanel.contentBody = dockpanel.contentNode.one('.dockeditempanel_bd'); | |
227 | // Set the x position of the panel | |
228 | //dockpanel.setX(parent.get('offsetWidth')); | |
229 | dockpanel.visible = false; | |
230 | // Add a show event | |
231 | dockpanel.show = function() { | |
232 | this.fire('dockpanel:beforeshow'); | |
233 | this.visible = true; | |
234 | this.removeClass('dockitempanel_hidden'); | |
235 | this.fire('dockpanel:shown'); | |
236 | this.fire('dockpanel:visiblechange'); | |
edc858dc | 237 | }; |
7e4617f7 SH |
238 | // Add a hide event |
239 | dockpanel.hide = function() { | |
240 | this.fire('dockpanel:beforehide'); | |
241 | this.visible = false; | |
242 | this.addClass('dockitempanel_hidden'); | |
243 | this.fire('dockpanel:hidden'); | |
244 | this.fire('dockpanel:visiblechange'); | |
edc858dc | 245 | }; |
7e4617f7 SH |
246 | // Add a method to set the header content |
247 | dockpanel.setHeader = function(content) { | |
248 | this.contentHeader.setContent(content); | |
249 | if (arguments.length > 1) { | |
250 | for (var i=1;i < arguments.length;i++) { | |
251 | this.contentHeader.append(arguments[i]); | |
1ce15fda | 252 | } |
1ce15fda | 253 | } |
edc858dc | 254 | }; |
7e4617f7 SH |
255 | // Add a method to set the body content |
256 | dockpanel.setBody = function(content) { | |
257 | this.contentBody.setContent(content); | |
edc858dc | 258 | }; |
7e4617f7 SH |
259 | // Add a method to set the top of the panel position |
260 | dockpanel.setTop = function(newtop) { | |
261 | this.setY(newtop); | |
262 | return; | |
263 | if (Y.UA.ie > 0) { | |
264 | this.setY(newtop); | |
265 | return true; | |
1ce15fda | 266 | } |
7e4617f7 | 267 | this.setStyle('top', newtop+'px'); |
edc858dc | 268 | }; |
7e4617f7 SH |
269 | // Put the dockpanel in the body |
270 | parent.append(dockpanel); | |
271 | // Return it | |
272 | return dockpanel; | |
273 | })(this.Y, this.nodes.dock); | |
274 | this.nodes.panel.on('panel:visiblechange', this.resize, this); | |
275 | this.Y.on('windowresize', this.resize, this); | |
276 | this.fire('dock:panelgenerated'); | |
277 | } | |
278 | return this.nodes.panel; | |
edc858dc | 279 | }; |
7e4617f7 SH |
280 | /** |
281 | * Handles a generic event within the dock | |
282 | * @param {Y.Event} e | |
283 | * @param {object} options Event configuration object | |
284 | */ | |
285 | M.core_dock.handleEvent = function(e, options) { | |
286 | var item = this.getActiveItem(); | |
7e4617f7 SH |
287 | if (options.cssselector == 'body') { |
288 | if (!this.nodes.dock.contains(e.target)) { | |
289 | if (item) { | |
290 | item.hide(); | |
291 | } | |
292 | } | |
b110d3c6 SH |
293 | } else { |
294 | var target; | |
295 | if (e.target.test(options.cssselector)) { | |
296 | target = e.target; | |
297 | } else { | |
298 | target = e.target.ancestor(options.cssselector); | |
299 | } | |
300 | if (!target) { | |
301 | return true; | |
302 | } | |
7e4617f7 SH |
303 | if (this.preventevent !== null && e.type === this.preventevent) { |
304 | return true; | |
305 | } | |
306 | if (options.preventevent) { | |
307 | this.preventevent = options.preventevent; | |
308 | if (options.preventdelay) { | |
309 | setTimeout(function(){M.core_dock.preventevent = null;}, options.preventdelay*1000); | |
310 | } | |
311 | } | |
312 | if (this.delayedevent && this.delayedevent.timeout) { | |
313 | clearTimeout(this.delayedevent.timeout); | |
314 | this.delayedevent.event.detach(); | |
315 | this.delayedevent = null; | |
316 | } | |
317 | if (options.delay > 0) { | |
318 | return this.delayEvent(e, options, target); | |
319 | } | |
320 | var targetid = target.get('id'); | |
321 | if (targetid.match(/^dock_item_(\d+)_title$/)) { | |
322 | item = this.items[targetid.replace(/^dock_item_(\d+)_title$/, '$1')]; | |
323 | if (item.active) { | |
324 | item.hide(); | |
325 | } else { | |
326 | item.show(); | |
1ce15fda | 327 | } |
7e4617f7 SH |
328 | } else if (item) { |
329 | item.hide(); | |
1ce15fda | 330 | } |
6d5f8015 | 331 | } |
7e4617f7 | 332 | return true; |
edc858dc | 333 | }; |
7e4617f7 SH |
334 | /** |
335 | * This function delays an event and then fires it providing the cursor if either | |
336 | * within or outside of the original target (options.iscontained=true|false) | |
337 | * @param {Y.Event} event | |
338 | * @param {object} options | |
339 | * @param {Y.Node} target | |
340 | * @return bool | |
341 | */ | |
342 | M.core_dock.delayEvent = function(event, options, target) { | |
343 | var self = this; | |
344 | self.delayedevent = (function(){ | |
345 | return { | |
346 | target : target, | |
347 | event : self.nodes.body.on('mousemove', function(e){ | |
348 | self.delayedevent.target = e.target; | |
349 | }), | |
350 | timeout : null | |
edc858dc | 351 | }; |
7e4617f7 SH |
352 | })(self); |
353 | self.delayedevent.timeout = setTimeout(function(){ | |
354 | self.delayedevent.timeout = null; | |
355 | self.delayedevent.event.detach(); | |
356 | if (options.iscontained == self.nodes.dock.contains(self.delayedevent.target)) { | |
357 | self.handleEvent(event, {cssselector:options.cssselector, delay:0, iscontained:options.iscontained}); | |
358 | } | |
359 | }, options.delay*1000); | |
360 | return true; | |
edc858dc | 361 | }; |
7e4617f7 SH |
362 | /** |
363 | * Corrects the orientation of the title, which for the default | |
364 | * dock just means making it vertical | |
365 | * The orientation is determined by M.str.langconfig.thisdirectionvertical: | |
366 | * ver : Letters are stacked rather than rotated | |
367 | * ttb : Title is rotated clockwise so the first letter is at the top | |
368 | * btt : Title is rotated counterclockwise so the first letter is at the bottom. | |
369 | * @param {string} title | |
370 | */ | |
10a995c1 | 371 | M.core_dock.fixTitleOrientation = function(item, title, text) { |
7e4617f7 SH |
372 | var Y = this.Y; |
373 | ||
374 | var title = Y.one(title); | |
6d5f8015 | 375 | |
7e4617f7 SH |
376 | if (Y.UA.ie > 0 && Y.UA.ie < 8) { |
377 | // IE 6/7 can't rotate text so force ver | |
378 | M.str.langconfig.thisdirectionvertical = 'ver'; | |
379 | } | |
380 | ||
381 | var clockwise = false; | |
382 | switch (M.str.langconfig.thisdirectionvertical) { | |
383 | case 'ver': | |
384 | // Stacked is easy | |
385 | return title.setContent(text.split('').join('<br />')); | |
386 | case 'ttb': | |
387 | clockwise = true; | |
388 | break; | |
389 | case 'btt': | |
390 | clockwise = false; | |
391 | break; | |
392 | } | |
393 | ||
394 | if (Y.UA.ie > 7) { | |
395 | // IE8 can flip the text via CSS but not handle SVG | |
edc858dc | 396 | title.setContent(text); |
7e4617f7 SH |
397 | title.setAttribute('style', 'writing-mode: tb-rl; filter: flipV flipH;display:inline;'); |
398 | title.addClass('filterrotate'); | |
399 | return title; | |
400 | } | |
401 | ||
402 | // Cool, we can use SVG! | |
0999f864 | 403 | var test = Y.Node.create('<h2><span style="font-size:10px;">'+text+'</span></h2>'); |
7e4617f7 SH |
404 | this.nodes.body.append(test); |
405 | var height = test.one('span').get('offsetWidth')+4; | |
406 | var width = test.one('span').get('offsetHeight')*2; | |
407 | var qwidth = width/4; | |
408 | test.remove(); | |
409 | ||
410 | // Create the text for the SVG | |
411 | var txt = document.createElementNS('http://www.w3.org/2000/svg', 'text'); | |
412 | txt.setAttribute('font-size','10px'); | |
413 | if (clockwise) { | |
414 | txt.setAttribute('transform','rotate(90 '+(qwidth/2)+' '+qwidth+')'); | |
415 | } else { | |
416 | txt.setAttribute('y', height); | |
417 | txt.setAttribute('transform','rotate(270 '+qwidth+' '+(height-qwidth)+')'); | |
418 | } | |
419 | txt.appendChild(document.createTextNode(text)); | |
420 | ||
421 | var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | |
422 | svg.setAttribute('version', '1.1'); | |
423 | svg.setAttribute('height', height); | |
424 | svg.setAttribute('width', width); | |
425 | svg.appendChild(txt); | |
426 | ||
edc858dc | 427 | title.append(svg); |
10a995c1 SH |
428 | |
429 | item.on('dockeditem:drawcomplete', function(txt, title){ | |
430 | txt.setAttribute('fill', Y.one(title).getStyle('color')); | |
431 | }, item, txt, title); | |
432 | ||
7e4617f7 | 433 | return title; |
edc858dc | 434 | }; |
7e4617f7 SH |
435 | /** |
436 | * Resizes the space that contained blocks if there were no blocks left in | |
437 | * it. e.g. if all blocks have been moved to the dock | |
438 | * @param {Y.Node} node | |
439 | */ | |
440 | M.core_dock.resizeBlockSpace = function(node) { | |
441 | ||
442 | if (this.Y.all('.block.dock_on_load').size()>0) { | |
443 | // Do not resize during initial load | |
444 | return; | |
445 | } | |
446 | var blockregions = []; | |
447 | var populatedblockregions = 0; | |
448 | this.Y.all('.block-region').each(function(region){ | |
449 | var hasblocks = (region.all('.block').size() > 0); | |
450 | if (hasblocks) { | |
451 | populatedblockregions++; | |
452 | } | |
453 | blockregions[region.get('id')] = {hasblocks: hasblocks, bodyclass: region.get('id').replace(/^region\-/, 'side-')+'-only'}; | |
454 | }); | |
455 | var bodynode = M.core_dock.nodes.body; | |
456 | var noblocksbodyclass = 'content-only'; | |
457 | var i = null; | |
458 | if (populatedblockregions==0) { | |
459 | bodynode.addClass(noblocksbodyclass); | |
460 | for (i in blockregions) { | |
461 | bodynode.removeClass(blockregions[i].bodyclass); | |
462 | } | |
463 | } else if (populatedblockregions==1) { | |
464 | bodynode.removeClass(noblocksbodyclass); | |
465 | for (i in blockregions) { | |
466 | if (!blockregions[i].hasblocks) { | |
467 | bodynode.removeClass(blockregions[i].bodyclass); | |
468 | } else { | |
469 | bodynode.addClass(blockregions[i].bodyclass); | |
470 | } | |
471 | } | |
472 | } else { | |
473 | bodynode.removeClass(noblocksbodyclass); | |
474 | for (i in blockregions) { | |
475 | bodynode.removeClass(blockregions[i].bodyclass); | |
476 | } | |
477 | } | |
edc858dc | 478 | }; |
7e4617f7 SH |
479 | /** |
480 | * Adds a dock item into the dock | |
481 | * @function | |
482 | * @param {M.core_dock.item} item | |
483 | */ | |
484 | M.core_dock.add = function(item) { | |
485 | item.id = this.totalcount; | |
486 | this.count++; | |
487 | this.totalcount++; | |
488 | this.items[item.id] = item; | |
489 | this.items[item.id].draw(); | |
490 | this.fire('dock:itemadded', item); | |
491 | this.fire('dock:itemschanged', item); | |
edc858dc | 492 | }; |
7e4617f7 SH |
493 | /** |
494 | * Appends a dock item to the dock | |
495 | * @param {YUI.Node} docknode | |
496 | */ | |
497 | M.core_dock.append = function(docknode) { | |
498 | this.nodes.container.append(docknode); | |
edc858dc | 499 | }; |
7e4617f7 SH |
500 | /** |
501 | * Initialises a generic block object | |
502 | * @param {YUI} Y | |
503 | * @param {int} id | |
504 | */ | |
505 | M.core_dock.init_genericblock = function(Y, id) { | |
506 | if (!this.initialised) { | |
507 | this.init(Y); | |
508 | } | |
509 | new this.genericblock(id).init(Y, Y.one('#inst'+id)); | |
edc858dc | 510 | }; |
7e4617f7 SH |
511 | /** |
512 | * Removes the node at the given index and puts it back into conventional page sturcture | |
513 | * @function | |
514 | * @param {int} uid Unique identifier for the block | |
515 | * @return {boolean} | |
516 | */ | |
517 | M.core_dock.remove = function(uid) { | |
518 | if (!this.items[uid]) { | |
519 | return false; | |
520 | } | |
521 | this.items[uid].remove(); | |
522 | delete this.items[uid]; | |
523 | this.count--; | |
524 | this.fire('dock:itemremoved', uid); | |
525 | this.fire('dock:itemschanged', uid); | |
526 | return true; | |
edc858dc | 527 | }; |
4e2e816a SH |
528 | /** |
529 | * Ensures the the first item in the dock has the correct class | |
530 | */ | |
531 | M.core_dock.resetFirstItem = function() { | |
532 | this.nodes.dock.all('.'+this.css.dockeditem+'.firstdockitem').removeClass('firstdockeditem'); | |
533 | if (this.nodes.dock.one('.'+this.css.dockeditem)) { | |
534 | this.nodes.dock.one('.'+this.css.dockeditem).addClass('firstdockitem'); | |
535 | } | |
edc858dc | 536 | }; |
7e4617f7 SH |
537 | /** |
538 | * Removes all nodes and puts them back into conventional page sturcture | |
539 | * @function | |
540 | * @return {boolean} | |
541 | */ | |
542 | M.core_dock.remove_all = function() { | |
543 | for (var i in this.items) { | |
544 | this.remove(i); | |
545 | } | |
546 | return true; | |
edc858dc | 547 | }; |
7e4617f7 SH |
548 | /** |
549 | * Hides the active item | |
550 | */ | |
551 | M.core_dock.hideActive = function() { | |
552 | var item = this.getActiveItem(); | |
553 | if (item) { | |
554 | item.hide(); | |
555 | } | |
edc858dc | 556 | }; |
7e4617f7 SH |
557 | /** |
558 | * Checks wether the dock should be shown or hidden | |
559 | */ | |
560 | M.core_dock.checkDockVisibility = function() { | |
561 | if (!this.count) { | |
562 | this.nodes.dock.addClass('nothingdocked'); | |
563 | this.nodes.body.removeClass(this.css.body); | |
564 | this.fire('dock:hidden'); | |
565 | } else { | |
566 | this.fire('dock:beforeshow'); | |
567 | this.nodes.dock.removeClass('nothingdocked'); | |
568 | this.nodes.body.addClass(this.css.body); | |
569 | this.fire('dock:shown'); | |
570 | } | |
edc858dc | 571 | }; |
7e4617f7 SH |
572 | /** |
573 | * This smart little function allows developers to attach event listeners before | |
574 | * the dock has been augmented to allows event listeners. | |
575 | * Once the augmentation is complete this function will be replaced with the proper | |
576 | * on method for handling event listeners. | |
577 | * Finally applyBinds needs to be called in order to properly bind events. | |
578 | * @param {string} event | |
579 | * @param {function} callback | |
580 | */ | |
581 | M.core_dock.on = function(event, callback) { | |
582 | this.earlybinds.push({event:event,callback:callback}); | |
edc858dc | 583 | }; |
7e4617f7 SH |
584 | /** |
585 | * This function takes all early binds and attaches them as listeners properly | |
586 | * This should only be called once augmentation is complete. | |
587 | */ | |
588 | M.core_dock.applyBinds = function() { | |
589 | for (var i in this.earlybinds) { | |
590 | var bind = this.earlybinds[i]; | |
591 | this.on(bind.event, bind.callback); | |
592 | } | |
593 | this.earlybinds = []; | |
edc858dc | 594 | }; |
7e4617f7 SH |
595 | /** |
596 | * This function checks the size and position of the panel and moves/resizes if | |
597 | * required to keep it within the bounds of the window. | |
598 | */ | |
599 | M.core_dock.resize = function() { | |
600 | this.fire('dock:panelresizestart'); | |
601 | var panel = this.getPanel(); | |
602 | var item = this.getActiveItem(); | |
603 | if (!panel.visible || !item) { | |
604 | return; | |
605 | } | |
606 | var buffer = this.cfg.buffer; | |
607 | var screenheight = parseInt(this.nodes.body.get('winHeight'))-(buffer*2); | |
b1c8c40e SH |
608 | var docky = this.nodes.dock.getY(); |
609 | var titletop = item.nodes.docktitle.getY()-docky-buffer; | |
610 | var containery = this.nodes.container.getY(); | |
611 | var containerheight = containery-docky+this.nodes.container.get('offsetHeight'); | |
0999f864 SH |
612 | panel.contentBody.setStyle('height', 'auto'); |
613 | panel.removeClass('oversized_content'); | |
7e4617f7 SH |
614 | var panelheight = panel.get('offsetHeight'); |
615 | ||
616 | if (panelheight > screenheight) { | |
617 | panel.setStyle('top', (buffer-containerheight)+'px'); | |
0999f864 SH |
618 | panel.contentBody.setStyle('height', (screenheight-panel.contentHeader.get('offsetHeight'))+'px'); |
619 | panel.addClass('oversized_content'); | |
7e4617f7 SH |
620 | } else if (panelheight > (screenheight-(titletop-buffer))) { |
621 | var difference = panelheight - (screenheight-titletop); | |
622 | panel.setStyle('top', (titletop-containerheight-difference+buffer)+'px'); | |
623 | } else { | |
624 | panel.setStyle('top', (titletop-containerheight+buffer)+'px'); | |
625 | } | |
626 | this.fire('dock:resizepanelcomplete'); | |
627 | return; | |
edc858dc | 628 | }; |
7e4617f7 SH |
629 | /** |
630 | * Returns the currently active dock item or false | |
631 | */ | |
632 | M.core_dock.getActiveItem = function() { | |
633 | for (var i in this.items) { | |
634 | if (this.items[i].active) { | |
635 | return this.items[i]; | |
636 | } | |
637 | } | |
638 | return false; | |
edc858dc | 639 | }; |
6d5f8015 SH |
640 | /** |
641 | * This class represents a generic block | |
7e4617f7 | 642 | * @class M.core_dock.genericblock |
6d5f8015 SH |
643 | * @constructor |
644 | */ | |
7e4617f7 | 645 | M.core_dock.genericblock = function(id) { |
6d5f8015 | 646 | // Nothing to actually do here but it needs a constructor! |
7e4617f7 SH |
647 | if (id) { |
648 | this.id = id; | |
649 | } | |
6d5f8015 SH |
650 | }; |
651 | M.core_dock.genericblock.prototype = { | |
652 | Y : null, // A YUI instance to use with the block | |
653 | id : null, // The block instance id | |
654 | cachedcontentnode : null, // The cached content node for the actual block | |
655 | blockspacewidth : null, // The width of the block's original container | |
656 | skipsetposition : false, // If true the user preference isn't updated | |
7e4617f7 | 657 | isdocked : false, // True if it is docked |
1ce15fda | 658 | /** |
6d5f8015 SH |
659 | * This function should be called within the block's constructor and is used to |
660 | * set up the initial controls for swtiching block position as well as an initial | |
661 | * moves that may be required. | |
662 | * | |
663 | * @param {YUI} Y | |
664 | * @param {YUI.Node} node The node that contains all of the block's content | |
7e4617f7 | 665 | * @return {M.core_dock.genericblock} |
1ce15fda | 666 | */ |
6d5f8015 | 667 | init : function(Y, node) { |
7e4617f7 SH |
668 | M.core_dock.init(Y); |
669 | ||
6d5f8015 SH |
670 | this.Y = Y; |
671 | if (!node) { | |
7e4617f7 | 672 | return false; |
6d5f8015 | 673 | } |
53fc3e70 | 674 | |
6d5f8015 SH |
675 | var commands = node.one('.header .title .commands'); |
676 | if (!commands) { | |
677 | commands = this.Y.Node.create('<div class="commands"></div>'); | |
678 | if (node.one('.header .title')) { | |
679 | node.one('.header .title').append(commands); | |
1ce15fda | 680 | } |
6d5f8015 | 681 | } |
1ce15fda | 682 | |
8c29a17d SH |
683 | // Must set the image src seperatly of we get an error with XML strict headers |
684 | var moveto = Y.Node.create('<input type="image" class="moveto customcommand requiresjs" alt="'+M.str.block.addtodock+'" title="'+M.str.block.addtodock+'" />'); | |
685 | moveto.setAttribute('src', M.util.image_url('t/block_to_dock', 'moodle')); | |
6d5f8015 | 686 | moveto.on('movetodock|click', this.move_to_dock, this, commands); |
1ce15fda | 687 | |
6d5f8015 SH |
688 | var blockaction = node.one('.block_action'); |
689 | if (blockaction) { | |
690 | blockaction.prepend(moveto); | |
691 | } else { | |
692 | commands.append(moveto); | |
693 | } | |
46de77b6 | 694 | |
6d5f8015 SH |
695 | // Move the block straight to the dock if required |
696 | if (node.hasClass('dock_on_load')) { | |
edc858dc | 697 | node.removeClass('dock_on_load'); |
6d5f8015 SH |
698 | this.skipsetposition = true; |
699 | this.move_to_dock(null, commands); | |
700 | } | |
7e4617f7 | 701 | return this; |
6d5f8015 | 702 | }, |
1ce15fda | 703 | |
6d5f8015 SH |
704 | /** |
705 | * This function is reponsible for moving a block from the page structure onto the | |
706 | * dock | |
707 | * @param {event} | |
708 | */ | |
709 | move_to_dock : function(e, commands) { | |
710 | if (e) { | |
711 | e.halt(true); | |
712 | } | |
1ce15fda | 713 | |
7e4617f7 | 714 | var Y = this.Y; |
edc858dc | 715 | var dock = M.core_dock; |
7e4617f7 SH |
716 | |
717 | var node = Y.one('#inst'+this.id); | |
6d5f8015 SH |
718 | var blockcontent = node.one('.content'); |
719 | if (!blockcontent) { | |
720 | return; | |
721 | } | |
1ce15fda | 722 | |
6d5f8015 SH |
723 | var blockclass = (function(classes){ |
724 | var r = /(^|\s)(block_[a-zA-Z0-9_]+)(\s|$)/; | |
725 | var m = r.exec(classes); | |
726 | return (m)?m[2]:m; | |
727 | })(node.getAttribute('className').toString()); | |
1ce15fda | 728 | |
6d5f8015 | 729 | this.cachedcontentnode = node; |
90723839 | 730 | |
7e4617f7 | 731 | node.replace(Y.Node.getDOMNode(Y.Node.create('<div id="content_placeholder_'+this.id+'" class="block_dock_placeholder"></div>'))); |
673835d4 | 732 | M.core_dock.holdingarea.append(node); |
6d5f8015 | 733 | node = null; |
1ce15fda | 734 | |
7e4617f7 | 735 | var blocktitle = Y.Node.getDOMNode(this.cachedcontentnode.one('.title h2')).cloneNode(true); |
1ce15fda | 736 | |
6d5f8015 SH |
737 | var blockcommands = this.cachedcontentnode.one('.title .commands'); |
738 | if (!blockcommands) { | |
7e4617f7 | 739 | blockcommands = Y.Node.create('<div class="commands"></div>'); |
6d5f8015 SH |
740 | this.cachedcontentnode.one('.title').append(blockcommands); |
741 | } | |
8c29a17d SH |
742 | |
743 | // Must set the image src seperatly of we get an error with XML strict headers | |
744 | var movetoimg = Y.Node.create('<img alt="'+M.str.block.undockitem+'" title="'+M.str.block.undockitem+'" />'); | |
745 | movetoimg.setAttribute('src', M.util.image_url('t/dock_to_block', 'moodle')); | |
746 | var moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>').append(movetoimg); | |
6d5f8015 SH |
747 | if (location.href.match(/\?/)) { |
748 | moveto.set('href', location.href+'&dock='+this.id); | |
749 | } else { | |
750 | moveto.set('href', location.href+'?dock='+this.id); | |
751 | } | |
752 | blockcommands.append(moveto); | |
1ce15fda | 753 | |
6d5f8015 | 754 | // Create a new dock item for the block |
7e4617f7 | 755 | var dockitem = new dock.item(Y, this.id, blocktitle, blockcontent, blockcommands, blockclass); |
6d5f8015 SH |
756 | // Wire the draw events to register remove events |
757 | dockitem.on('dockeditem:drawcomplete', function(e){ | |
758 | // check the contents block [editing=off] | |
759 | this.contents.all('.moveto').on('returntoblock|click', function(e){ | |
760 | e.halt(); | |
edc858dc | 761 | dock.remove(this.id); |
6d5f8015 SH |
762 | }, this); |
763 | // check the commands block [editing=on] | |
764 | this.commands.all('.moveto').on('returntoblock|click', function(e){ | |
765 | e.halt(); | |
edc858dc | 766 | dock.remove(this.id); |
6d5f8015 SH |
767 | }, this); |
768 | // Add a close icon | |
8c29a17d SH |
769 | // Must set the image src seperatly of we get an error with XML strict headers |
770 | var closeicon = Y.Node.create('<span class="hidepanelicon"><img alt="" style="width:11px;height:11px;cursor:pointer;" /></span>'); | |
771 | closeicon.one('img').setAttribute('src', M.util.image_url('t/dockclose', 'moodle')); | |
7e4617f7 | 772 | closeicon.on('forceclose|click', this.hide, this); |
6d5f8015 SH |
773 | this.commands.append(closeicon); |
774 | }, dockitem); | |
6d5f8015 | 775 | // Register an event so that when it is removed we can put it back as a block |
7e4617f7 SH |
776 | dockitem.on('dockeditem:itemremoved', this.return_to_block, this, dockitem); |
777 | dock.add(dockitem); | |
778 | ||
6d5f8015 SH |
779 | if (!this.skipsetposition) { |
780 | // save the users preference | |
781 | M.util.set_user_preference('docked_block_instance_'+this.id, 1); | |
782 | } else { | |
783 | this.skipsetposition = false; | |
784 | } | |
1ce15fda | 785 | |
7e4617f7 | 786 | this.isdocked = true; |
1ce15fda SH |
787 | }, |
788 | /** | |
6d5f8015 SH |
789 | * This function removes a block from the dock and puts it back into the page |
790 | * structure. | |
791 | * @param {M.core_dock.class.item} | |
1ce15fda | 792 | */ |
6d5f8015 SH |
793 | return_to_block : function(dockitem) { |
794 | var placeholder = this.Y.one('#content_placeholder_'+this.id); | |
1ce15fda | 795 | |
6d5f8015 SH |
796 | if (this.cachedcontentnode.one('.header')) { |
797 | this.cachedcontentnode.one('.header').insert(dockitem.contents, 'after'); | |
798 | } else { | |
799 | this.cachedcontentnode.insert(dockitem.contents); | |
800 | } | |
1ce15fda | 801 | |
6d5f8015 SH |
802 | placeholder.replace(this.Y.Node.getDOMNode(this.cachedcontentnode)); |
803 | this.cachedcontentnode = this.Y.one('#'+this.cachedcontentnode.get('id')); | |
d2e68385 | 804 | |
7e4617f7 | 805 | var commands = this.cachedcontentnode.one('.title .commands'); |
6d5f8015 SH |
806 | if (commands) { |
807 | commands.all('.hidepanelicon').remove(); | |
808 | commands.all('.moveto').remove(); | |
809 | commands.remove(); | |
1ce15fda | 810 | } |
6d5f8015 SH |
811 | this.cachedcontentnode.one('.title').append(commands); |
812 | this.cachedcontentnode = null; | |
813 | M.util.set_user_preference('docked_block_instance_'+this.id, 0); | |
7e4617f7 | 814 | this.isdocked = false; |
6d5f8015 | 815 | return true; |
1ce15fda | 816 | } |
edc858dc | 817 | }; |
1ce15fda SH |
818 | |
819 | /** | |
820 | * This class represents an item in the dock | |
7e4617f7 | 821 | * @class M.core_dock.item |
1ce15fda | 822 | * @constructor |
d2e68385 | 823 | * @param {YUI} Y The YUI instance to use for this item |
1ce15fda | 824 | * @param {int} uid The unique ID for the item |
53fc3e70 SH |
825 | * @param {this.Y.Node} title |
826 | * @param {this.Y.Node} contents | |
827 | * @param {this.Y.Node} commands | |
3406acde | 828 | * @param {string} blockclass |
1ce15fda | 829 | */ |
90723839 | 830 | M.core_dock.item = function(Y, uid, title, contents, commands, blockclass){ |
53fc3e70 | 831 | this.Y = Y; |
10a995c1 SH |
832 | this.publish('dockeditem:drawstart', {prefix:'dockeditem'}); |
833 | this.publish('dockeditem:drawcomplete', {prefix:'dockeditem'}); | |
834 | this.publish('dockeditem:showstart', {prefix:'dockeditem'}); | |
835 | this.publish('dockeditem:showcomplete', {prefix:'dockeditem'}); | |
836 | this.publish('dockeditem:hidestart', {prefix:'dockeditem'}); | |
837 | this.publish('dockeditem:hidecomplete', {prefix:'dockeditem'}); | |
838 | this.publish('dockeditem:itemremoved', {prefix:'dockeditem'}); | |
53fc3e70 SH |
839 | if (uid && this.id==null) { |
840 | this.id = uid; | |
841 | } | |
842 | if (title && this.title==null) { | |
7e4617f7 SH |
843 | this.titlestring = title.cloneNode(true); |
844 | this.title = document.createElement(title.nodeName); | |
10a995c1 | 845 | M.core_dock.fixTitleOrientation(this, this.title, this.titlestring.firstChild.nodeValue); |
53fc3e70 SH |
846 | } |
847 | if (contents && this.contents==null) { | |
848 | this.contents = contents; | |
849 | } | |
850 | if (commands && this.commands==null) { | |
851 | this.commands = commands; | |
852 | } | |
90723839 | 853 | if (blockclass && this.blockclass==null) { |
edc858dc | 854 | this.blockclass = blockclass; |
90723839 | 855 | } |
7e4617f7 | 856 | this.nodes = (function(){ |
edc858dc | 857 | return {docktitle : null, dockitem : null, container: null}; |
7e4617f7 | 858 | })(); |
edc858dc | 859 | }; |
6d5f8015 SH |
860 | /** |
861 | * | |
862 | */ | |
863 | M.core_dock.item.prototype = { | |
864 | Y : null, // The YUI instance to use with this dock item | |
865 | id : null, // The unique id for the item | |
866 | name : null, // The name of the item | |
867 | title : null, // The title of the item | |
7e4617f7 | 868 | titlestring : null, // The title as a plain string |
6d5f8015 SH |
869 | contents : null, // The content of the item |
870 | commands : null, // The commands for the item | |
871 | active : false, // True if the item is being shown | |
7e4617f7 SH |
872 | blockclass : null, // The class of the block this item relates to |
873 | nodes : null, | |
6d5f8015 SH |
874 | /** |
875 | * This function draws the item on the dock | |
876 | */ | |
877 | draw : function() { | |
878 | this.fire('dockeditem:drawstart'); | |
6d5f8015 | 879 | |
7e4617f7 SH |
880 | var Y = this.Y; |
881 | var css = M.core_dock.css; | |
882 | ||
883 | this.nodes.docktitle = Y.Node.create('<div id="dock_item_'+this.id+'_title" class="'+css.dockedtitle+'"></div>'); | |
884 | this.nodes.docktitle.append(this.title); | |
885 | this.nodes.dockitem = Y.Node.create('<div id="dock_item_'+this.id+'" class="'+css.dockeditem+'"></div>'); | |
886 | if (M.core_dock.count === 1) { | |
887 | this.nodes.dockitem.addClass('firstdockitem'); | |
6d5f8015 | 888 | } |
7e4617f7 SH |
889 | this.nodes.dockitem.append(this.nodes.docktitle); |
890 | M.core_dock.append(this.nodes.dockitem); | |
6d5f8015 | 891 | this.fire('dockeditem:drawcomplete'); |
7e4617f7 | 892 | return true; |
6d5f8015 SH |
893 | }, |
894 | /** | |
895 | * This function toggles makes the item active and shows it | |
6d5f8015 | 896 | */ |
7e4617f7 SH |
897 | show : function() { |
898 | M.core_dock.hideActive(); | |
899 | var Y = this.Y; | |
900 | var css = M.core_dock.css; | |
901 | var panel = M.core_dock.getPanel(); | |
6d5f8015 | 902 | this.fire('dockeditem:showstart'); |
7e4617f7 SH |
903 | panel.setHeader(this.titlestring, this.commands); |
904 | panel.setBody(Y.Node.create('<div class="'+this.blockclass+' block_docked"></div>').append(this.contents)); | |
905 | panel.show(); | |
906 | ||
6d5f8015 SH |
907 | this.active = true; |
908 | // Add active item class first up | |
7e4617f7 | 909 | this.nodes.docktitle.addClass(css.activeitem); |
6d5f8015 | 910 | this.fire('dockeditem:showcomplete'); |
7e4617f7 | 911 | M.core_dock.resize(); |
6d5f8015 SH |
912 | return true; |
913 | }, | |
914 | /** | |
915 | * This function hides the item and makes it inactive | |
6d5f8015 | 916 | */ |
7e4617f7 SH |
917 | hide : function() { |
918 | var css = M.core_dock.css; | |
919 | this.fire('dockeditem:hidestart'); | |
920 | // No longer active | |
921 | this.active = false; | |
922 | // Remove the active class | |
923 | this.nodes.docktitle.removeClass(css.activeitem); | |
924 | // Hide the panel | |
925 | M.core_dock.getPanel().hide(); | |
926 | this.fire('dockeditem:hidecomplete'); | |
6d5f8015 SH |
927 | }, |
928 | /** | |
7e4617f7 | 929 | * This function removes the node and destroys it's bits |
6d5f8015 SH |
930 | * @param {Event} e |
931 | */ | |
7e4617f7 SH |
932 | remove : function () { |
933 | this.hide(); | |
934 | this.nodes.dockitem.remove(); | |
935 | this.fire('dockeditem:itemremoved'); | |
6d5f8015 | 936 | } |
edc858dc | 937 | }; |