Commit | Line | Data |
---|---|---|
84192d78 SH |
1 | YUI.add('moodle-core-dock', function (Y, NAME) { |
2 | ||
3 | /** | |
4 | * Dock JS. | |
5 | * | |
6 | * This file contains the DOCK object and all dock related global namespace methods and properties. | |
7 | * | |
8 | * @module moodle-core-dock | |
9 | */ | |
10 | ||
a69a7e89 SH |
11 | var LOGNS = 'moodle-core-dock', |
12 | BODY = Y.one(Y.config.doc.body), | |
13 | CSS = { | |
14 | dock: 'dock', // CSS Class applied to the dock box | |
15 | dockspacer: 'dockspacer', // CSS class applied to the dockspacer | |
16 | controls: 'controls', // CSS class applied to the controls box | |
17 | body: 'has_dock', // CSS class added to the body when there is a dock | |
18 | buttonscontainer: 'buttons_container', | |
19 | dockeditem: 'dockeditem', // CSS class added to each item in the dock | |
20 | dockeditemcontainer: 'dockeditem_container', | |
21 | dockedtitle: 'dockedtitle', // CSS class added to the item's title in each dock | |
22 | activeitem: 'activeitem', // CSS class added to the active item | |
a69a7e89 SH |
23 | dockonload: 'dock_on_load' |
24 | }, | |
25 | SELECTOR = { | |
26 | dockableblock: '.block[data-instanceid][data-dockable]', | |
27 | blockmoveto: '.block[data-instanceid][data-dockable] .moveto', | |
28 | panelmoveto: '#dockeditempanel .commands a.moveto', | |
3a0bc0fd | 29 | dockonload: '.block.' + CSS.dockonload, |
a69a7e89 SH |
30 | blockregion: '[data-blockregion]' |
31 | }, | |
32 | DOCK, | |
33 | DOCKPANEL, | |
34 | TABHEIGHTMANAGER, | |
35 | BLOCK, | |
ad3f8cd1 | 36 | DOCKEDITEM; // eslint-disable-line no-unused-vars |
84192d78 | 37 | |
84192d78 | 38 | M.core = M.core || {}; |
84192d78 SH |
39 | M.core.dock = M.core.dock || {}; |
40 | ||
41 | /** | |
42 | * The dock - once initialised. | |
43 | * | |
44 | * @private | |
45 | * @property _dock | |
46 | * @type DOCK | |
47 | */ | |
48 | M.core.dock._dock = null; | |
49 | ||
50 | /** | |
51 | * An associative array of dockable blocks. | |
52 | * @property _dockableblocks | |
53 | * @type {Array} An array of BLOCK objects organised by instanceid. | |
54 | * @private | |
55 | */ | |
56 | M.core.dock._dockableblocks = {}; | |
57 | ||
58 | /** | |
59 | * Initialises the dock. | |
60 | * This method registers dockable blocks, and creates delegations to dock them. | |
61 | * @static | |
62 | * @method init | |
63 | */ | |
64 | M.core.dock.init = function() { | |
65 | Y.all(SELECTOR.dockableblock).each(M.core.dock.registerDockableBlock); | |
fd5466af CB |
66 | Y.Global.on(M.core.globalEvents.BLOCK_CONTENT_UPDATED, function(e) { |
67 | M.core.dock.notifyBlockChange(e.instanceid); | |
68 | }, this); | |
84192d78 SH |
69 | BODY.delegate('click', M.core.dock.dockBlock, SELECTOR.blockmoveto); |
70 | BODY.delegate('key', M.core.dock.dockBlock, SELECTOR.blockmoveto, 'enter'); | |
71 | }; | |
72 | ||
73 | /** | |
74 | * Returns an instance of the dock. | |
75 | * Initialises one if one hasn't already being initialised. | |
76 | * | |
77 | * @static | |
78 | * @method get | |
79 | * @return DOCK | |
80 | */ | |
81 | M.core.dock.get = function() { | |
82 | if (this._dock === null) { | |
83 | this._dock = new DOCK(); | |
84 | } | |
85 | return this._dock; | |
86 | }; | |
87 | ||
88 | /** | |
89 | * Registers a dockable block with the dock. | |
90 | * | |
91 | * @static | |
92 | * @method registerDockableBlock | |
93 | * @param {int} id The block instance ID. | |
94 | * @return void | |
95 | */ | |
96 | M.core.dock.registerDockableBlock = function(id) { | |
97 | if (typeof id === 'object' && typeof id.getData === 'function') { | |
98 | id = id.getData('instanceid'); | |
99 | } | |
3a0bc0fd | 100 | M.core.dock._dockableblocks[id] = new BLOCK({id: id}); |
84192d78 SH |
101 | }; |
102 | ||
103 | /** | |
104 | * Docks a block given either its instanceid, its node, or an event fired from within the block. | |
105 | * @static | |
106 | * @method dockBlockByInstanceID | |
107 | * @param id | |
108 | * @return void | |
109 | */ | |
110 | M.core.dock.dockBlock = function(id) { | |
111 | if (typeof id === 'object' && id.target !== 'undefined') { | |
112 | id = id.target; | |
113 | } | |
114 | if (typeof id === "object") { | |
115 | if (!id.test(SELECTOR.dockableblock)) { | |
116 | id = id.ancestor(SELECTOR.dockableblock); | |
117 | } | |
3a0bc0fd | 118 | if (typeof id === 'object' && typeof id.getData === 'function' && !id.ancestor('.' + CSS.dock)) { |
84192d78 SH |
119 | id = id.getData('instanceid'); |
120 | } else { | |
557f44d9 | 121 | Y.log('Invalid instanceid given to dockBlockByInstanceID', 'warn', LOGNS); |
84192d78 SH |
122 | return; |
123 | } | |
124 | } | |
125 | var block = M.core.dock._dockableblocks[id]; | |
126 | if (block) { | |
127 | block.moveToDock(); | |
128 | } | |
129 | }; | |
130 | ||
131 | /** | |
132 | * Fixes the title orientation. Rotating it if required. | |
133 | * | |
134 | * @static | |
135 | * @method fixTitleOrientation | |
136 | * @param {Node} title The title node we are looking at. | |
137 | * @param {String} text The string to use as the title. | |
138 | * @return {Node} The title node to use. | |
139 | */ | |
140 | M.core.dock.fixTitleOrientation = function(title, text) { | |
a69a7e89 SH |
141 | var dock = M.core.dock.get(), |
142 | fontsize = '11px', | |
143 | transform = 'rotate(270deg)', | |
144 | test, | |
145 | width, | |
146 | height, | |
64e7aa4d AN |
147 | container, |
148 | verticaldirection = M.util.get_string('thisdirectionvertical', 'langconfig'); | |
84192d78 SH |
149 | title = Y.one(title); |
150 | ||
151 | if (dock.get('orientation') !== 'vertical') { | |
152 | // If the dock isn't vertical don't adjust it! | |
a69a7e89 | 153 | title.set('innerHTML', text); |
84192d78 SH |
154 | return title; |
155 | } | |
156 | ||
157 | if (Y.UA.ie > 0 && Y.UA.ie < 8) { | |
158 | // IE 6/7 can't rotate text so force ver | |
64e7aa4d | 159 | verticaldirection = 'ver'; |
84192d78 SH |
160 | } |
161 | ||
64e7aa4d | 162 | switch (verticaldirection) { |
84192d78 SH |
163 | case 'ver': |
164 | // Stacked is easy | |
a69a7e89 | 165 | return title.set('innerHTML', text.split('').join('<br />')); |
84192d78 | 166 | case 'ttb': |
a69a7e89 | 167 | transform = 'rotate(90deg)'; |
84192d78 SH |
168 | break; |
169 | case 'btt': | |
a69a7e89 | 170 | // Nothing to do here. transform default is good. |
84192d78 SH |
171 | break; |
172 | } | |
173 | ||
174 | if (Y.UA.ie === 8) { | |
175 | // IE8 can flip the text via CSS but not handle transform. IE9+ can handle the CSS3 transform attribute. | |
a69a7e89 | 176 | title.set('innerHTML', text); |
84192d78 SH |
177 | title.setAttribute('style', 'writing-mode: tb-rl; filter: flipV flipH;display:inline;'); |
178 | title.addClass('filterrotate'); | |
179 | return title; | |
180 | } | |
181 | ||
182 | // We need to fix a font-size - sorry theme designers. | |
557f44d9 AN |
183 | test = Y.Node.create('<h2 class="transform-test-heading"><span class="transform-test-node" style="font-size:' + |
184 | fontsize + ';">' + text + '</span></h2>'); | |
84192d78 | 185 | BODY.insert(test, 0); |
a69a7e89 SH |
186 | width = test.one('span').get('offsetWidth') * 1.2; |
187 | height = test.one('span').get('offsetHeight'); | |
84192d78 SH |
188 | test.remove(); |
189 | ||
a69a7e89 | 190 | title.set('innerHTML', text); |
84192d78 SH |
191 | title.addClass('css3transform'); |
192 | ||
193 | // Move the title into position | |
194 | title.setStyles({ | |
3a0bc0fd DP |
195 | 'position': 'relative', |
196 | 'fontSize': fontsize, | |
197 | 'width': width, | |
198 | 'top': (width - height) / 2 | |
84192d78 SH |
199 | }); |
200 | ||
201 | // Positioning is different when in RTL mode. | |
557f44d9 | 202 | if (window.right_to_left()) { |
3a0bc0fd | 203 | title.setStyle('left', width / 2 - height); |
84192d78 | 204 | } else { |
3a0bc0fd | 205 | title.setStyle('right', width / 2 - height); |
84192d78 SH |
206 | } |
207 | ||
208 | // Rotate the text | |
209 | title.setStyles({ | |
3a0bc0fd DP |
210 | 'transform': transform, |
211 | '-ms-transform': transform, | |
212 | '-moz-transform': transform, | |
213 | '-webkit-transform': transform, | |
214 | '-o-transform': transform | |
84192d78 SH |
215 | }); |
216 | ||
a69a7e89 | 217 | container = Y.Node.create('<div></div>'); |
84192d78 | 218 | container.append(title); |
a69a7e89 | 219 | container.setStyles({ |
3a0bc0fd DP |
220 | height: width + (width / 4), |
221 | position: 'relative' | |
a69a7e89 | 222 | }); |
84192d78 SH |
223 | return container; |
224 | }; | |
225 | ||
226 | /** | |
227 | * Informs the dock that the content of the block has changed. | |
228 | * This should be called by the blocks JS code if its content has been updated dynamically. | |
229 | * This method ensure the dock resizes if need be. | |
230 | * | |
231 | * @static | |
232 | * @method notifyBlockChange | |
233 | * @param {Number} instanceid | |
234 | * @return void | |
235 | */ | |
236 | M.core.dock.notifyBlockChange = function(instanceid) { | |
237 | if (this._dock !== null) { | |
238 | var dock = M.core.dock.get(), | |
239 | activeitem = dock.getActiveItem(); | |
240 | if (activeitem && activeitem.get('blockinstanceid') === parseInt(instanceid, 10)) { | |
241 | dock.resizePanelIfRequired(); | |
242 | } | |
243 | } | |
244 | }; | |
245 | ||
246 | /** | |
247 | * The Dock. | |
248 | * | |
249 | * @namespace M.core.dock | |
250 | * @class Dock | |
251 | * @constructor | |
1f777e5c AN |
252 | * @extends Base |
253 | * @uses EventTarget | |
84192d78 | 254 | */ |
a69a7e89 | 255 | DOCK = function() { |
84192d78 SH |
256 | DOCK.superclass.constructor.apply(this, arguments); |
257 | }; | |
258 | DOCK.prototype = { | |
259 | /** | |
260 | * Tab height manager used to ensure tabs are always visible. | |
261 | * @protected | |
262 | * @property tabheightmanager | |
263 | * @type TABHEIGHTMANAGER | |
264 | */ | |
3a0bc0fd | 265 | tabheightmanager: null, |
84192d78 SH |
266 | /** |
267 | * Will be an eventtype if there is an eventype to prevent. | |
268 | * @protected | |
269 | * @property preventevent | |
270 | * @type String | |
271 | */ | |
3a0bc0fd | 272 | preventevent: null, |
84192d78 SH |
273 | /** |
274 | * Will be an object if there is a delayed event in effect. | |
275 | * @protected | |
276 | * @property delayedevent | |
277 | * @type {Object} | |
278 | */ | |
3a0bc0fd | 279 | delayedevent: null, |
84192d78 SH |
280 | /** |
281 | * An array of currently docked items. | |
282 | * @protected | |
283 | * @property dockeditems | |
284 | * @type Array | |
285 | */ | |
3a0bc0fd | 286 | dockeditems: [], |
84192d78 SH |
287 | /** |
288 | * Set to true once the dock has been drawn. | |
289 | * @protected | |
290 | * @property dockdrawn | |
291 | * @type Boolean | |
292 | */ | |
3a0bc0fd | 293 | dockdrawn: false, |
84192d78 SH |
294 | /** |
295 | * The number of blocks that are currently docked. | |
296 | * @protected | |
297 | * @property count | |
298 | * @type Number | |
299 | */ | |
3a0bc0fd | 300 | count: 0, |
84192d78 SH |
301 | /** |
302 | * The total number of blocks that have been docked. | |
303 | * @protected | |
304 | * @property totalcount | |
305 | * @type Number | |
306 | */ | |
3a0bc0fd | 307 | totalcount: 0, |
84192d78 SH |
308 | /** |
309 | * A hidden node used as a holding area for DOM objects used by blocks that have been docked. | |
310 | * @protected | |
311 | * @property holdingareanode | |
312 | * @type Node | |
313 | */ | |
3a0bc0fd | 314 | holdingareanode: null, |
84192d78 SH |
315 | /** |
316 | * Called during the initialisation process of the object. | |
317 | * @method initializer | |
318 | */ | |
3a0bc0fd | 319 | initializer: function() { |
82955480 | 320 | Y.log('Dock initialising', 'debug', LOGNS); |
a69a7e89 | 321 | |
84192d78 SH |
322 | // Publish the events the dock has |
323 | /** | |
324 | * Fired when the dock first starts initialising. | |
325 | * @event dock:starting | |
326 | */ | |
3a0bc0fd | 327 | this.publish('dock:starting', {prefix: 'dock', broadcast: 2, emitFacade: true, fireOnce: true}); |
84192d78 SH |
328 | /** |
329 | * Fired after the dock is initialised for the first time. | |
330 | * @event dock:initialised | |
331 | */ | |
3a0bc0fd | 332 | this.publish('dock:initialised', {prefix: 'dock', broadcast: 2, emitFacade: true, fireOnce: true}); |
84192d78 SH |
333 | /** |
334 | * Fired before the dock structure and content is first created. | |
335 | * @event dock:beforedraw | |
336 | */ | |
3a0bc0fd | 337 | this.publish('dock:beforedraw', {prefix: 'dock', fireOnce: true}); |
84192d78 SH |
338 | /** |
339 | * Fired before the dock is changed from hidden to visible. | |
340 | * @event dock:beforeshow | |
341 | */ | |
3a0bc0fd | 342 | this.publish('dock:beforeshow', {prefix: 'dock'}); |
84192d78 SH |
343 | /** |
344 | * Fires after the dock has been changed from hidden to visible. | |
345 | * @event dock:shown | |
346 | */ | |
3a0bc0fd | 347 | this.publish('dock:shown', {prefix: 'dock', broadcast: 2}); |
84192d78 SH |
348 | /** |
349 | * Fired after the dock has been changed from visible to hidden. | |
350 | * @event dock:hidden | |
351 | */ | |
3a0bc0fd | 352 | this.publish('dock:hidden', {prefix: 'dock', broadcast: 2}); |
84192d78 SH |
353 | /** |
354 | * Fires when an item is added to the dock. | |
355 | * @event dock:itemadded | |
356 | */ | |
3a0bc0fd | 357 | this.publish('dock:itemadded', {prefix: 'dock'}); |
84192d78 SH |
358 | /** |
359 | * Fires when an item is removed from the dock. | |
360 | * @event dock:itemremoved | |
361 | */ | |
3a0bc0fd | 362 | this.publish('dock:itemremoved', {prefix: 'dock'}); |
84192d78 SH |
363 | /** |
364 | * Fires when a block is added or removed from the dock. | |
365 | * This happens after the itemadded and itemremoved events have been called. | |
366 | * @event dock:itemschanged | |
367 | */ | |
3a0bc0fd | 368 | this.publish('dock:itemschanged', {prefix: 'dock', broadcast: 2}); |
84192d78 SH |
369 | /** |
370 | * Fires once when the docks panel is first initialised. | |
371 | * @event dock:panelgenerated | |
372 | */ | |
3a0bc0fd | 373 | this.publish('dock:panelgenerated', {prefix: 'dock', fireOnce: true}); |
84192d78 SH |
374 | /** |
375 | * Fires when the dock panel is about to be resized. | |
376 | * @event dock:panelresizestart | |
377 | */ | |
3a0bc0fd | 378 | this.publish('dock:panelresizestart', {prefix: 'dock'}); |
84192d78 SH |
379 | /** |
380 | * Fires after the dock panel has been resized. | |
381 | * @event dock:resizepanelcomplete | |
382 | */ | |
3a0bc0fd | 383 | this.publish('dock:resizepanelcomplete', {prefix: 'dock'}); |
84192d78 SH |
384 | |
385 | // Apply theme customisations here before we do any real work. | |
386 | this._applyThemeCustomisation(); | |
387 | // Inform everyone we are now about to initialise. | |
388 | this.fire('dock:starting'); | |
389 | this._ensureDockDrawn(); | |
390 | // Inform everyone the dock has been initialised | |
391 | this.fire('dock:initialised'); | |
392 | }, | |
393 | /** | |
394 | * Ensures that the dock has been drawn. | |
395 | * @private | |
396 | * @method _ensureDockDrawn | |
397 | * @return {Boolean} | |
398 | */ | |
3a0bc0fd | 399 | _ensureDockDrawn: function() { |
84192d78 SH |
400 | if (this.dockdrawn === true) { |
401 | return true; | |
402 | } | |
a69a7e89 SH |
403 | var dock = this._initialiseDockNode(), |
404 | clickargs = { | |
3a0bc0fd DP |
405 | cssselector: '.' + CSS.dockedtitle, |
406 | delay: 0 | |
a69a7e89 SH |
407 | }, |
408 | mouseenterargs = { | |
3a0bc0fd DP |
409 | cssselector: '.' + CSS.dockedtitle, |
410 | delay: 0.5, | |
411 | iscontained: true, | |
412 | preventevent: 'click', | |
413 | preventdelay: 3 | |
a69a7e89 | 414 | }; |
84192d78 SH |
415 | if (Y.UA.ie > 0 && Y.UA.ie < 7) { |
416 | // Adjust for IE 6 (can't handle fixed pos) | |
3a0bc0fd | 417 | dock.setStyle('height', dock.get('winHeight') + 'px'); |
84192d78 SH |
418 | } |
419 | ||
420 | this.fire('dock:beforedraw'); | |
421 | ||
422 | this._initialiseDockControls(); | |
423 | ||
3a0bc0fd | 424 | this.tabheightmanager = new TABHEIGHTMANAGER({dock: this}); |
84192d78 | 425 | |
84192d78 SH |
426 | // Attach the required event listeners |
427 | // We use delegate here as that way a handful of events are created for the dock | |
428 | // and all items rather than the same number for the dock AND every item individually | |
3a0bc0fd DP |
429 | Y.delegate('click', this.handleEvent, this.get('dockNode'), '.' + CSS.dockedtitle, this, clickargs); |
430 | Y.delegate('mouseenter', this.handleEvent, this.get('dockNode'), '.' + CSS.dockedtitle, this, mouseenterargs); | |
431 | this.get('dockNode').on('mouseleave', this.handleEvent, this, {cssselector: '#dock', delay: 0.5, iscontained: false}); | |
84192d78 | 432 | |
84192d78 | 433 | Y.delegate('click', this.handleReturnToBlock, this.get('dockNode'), SELECTOR.panelmoveto, this); |
3a0bc0fd | 434 | Y.delegate('dock:actionkey', this.handleDockedItemEvent, this.get('dockNode'), '.' + CSS.dockeditem, this); |
84192d78 | 435 | |
3a0bc0fd | 436 | BODY.on('click', this.handleEvent, this, {cssselector: 'body', delay: 0}); |
84192d78 SH |
437 | this.on('dock:itemschanged', this.resizeBlockSpace, this); |
438 | this.on('dock:itemschanged', this.checkDockVisibility, this); | |
439 | this.on('dock:itemschanged', this.resetFirstItem, this); | |
440 | this.dockdrawn = true; | |
441 | return true; | |
442 | }, | |
443 | /** | |
444 | * Handles an actionkey event on the dock. | |
445 | * @param {EventFacade} e | |
446 | * @method handleDockedItemEvent | |
447 | * @return {Boolean} | |
448 | */ | |
3a0bc0fd | 449 | handleDockedItemEvent: function(e) { |
84192d78 SH |
450 | if (e.type !== 'dock:actionkey') { |
451 | return false; | |
452 | } | |
453 | var target = e.target, | |
3a0bc0fd | 454 | dockeditem = '.' + CSS.dockeditem; |
84192d78 SH |
455 | if (!target.test(dockeditem)) { |
456 | target = target.ancestor(dockeditem); | |
457 | } | |
458 | if (!target) { | |
459 | return false; | |
460 | } | |
461 | e.halt(); | |
a69a7e89 | 462 | this.dockeditems[target.getAttribute('rel')].toggle(e.action); |
84192d78 SH |
463 | }, |
464 | /** | |
465 | * Call the theme customisation method "customise_dock_for_theme" if it exists. | |
466 | * @private | |
467 | * @method _applyThemeCustomisation | |
468 | */ | |
3a0bc0fd | 469 | _applyThemeCustomisation: function() { |
84192d78 | 470 | // Check if there is a customisation function |
3a0bc0fd | 471 | if (typeof (customise_dock_for_theme) === 'function') { |
84192d78 SH |
472 | // First up pre the legacy object. |
473 | M.core_dock = this; | |
474 | M.core_dock.cfg = { | |
3a0bc0fd DP |
475 | buffer: null, |
476 | orientation: null, | |
477 | position: null, | |
478 | spacebeforefirstitem: null, | |
479 | removeallicon: null | |
84192d78 SH |
480 | }; |
481 | M.core_dock.css = { | |
3a0bc0fd DP |
482 | dock: null, |
483 | dockspacer: null, | |
484 | controls: null, | |
485 | body: null, | |
486 | buttonscontainer: null, | |
487 | dockeditem: null, | |
488 | dockeditemcontainer: null, | |
489 | dockedtitle: null, | |
490 | activeitem: null | |
84192d78 SH |
491 | }; |
492 | try { | |
493 | // Run the customisation function | |
557f44d9 | 494 | window.customise_dock_for_theme(this); |
84192d78 SH |
495 | } catch (exception) { |
496 | // Do nothing at the moment. | |
497 | Y.log('Exception while attempting to apply theme customisations.', 'error', LOGNS); | |
498 | } | |
499 | // Now to work out what they did. | |
500 | var key, value, | |
501 | warned = false, | |
502 | cfgmap = { | |
3a0bc0fd DP |
503 | buffer: 'bufferPanel', |
504 | orientation: 'orientation', | |
505 | position: 'position', | |
506 | spacebeforefirstitem: 'bufferBeforeFirstItem', | |
507 | removeallicon: 'undockAllIconUrl' | |
84192d78 SH |
508 | }; |
509 | // Check for and apply any legacy configuration. | |
510 | for (key in M.core_dock.cfg) { | |
a69a7e89 SH |
511 | if (Y.Lang.isString(key) && cfgmap[key]) { |
512 | value = M.core_dock.cfg[key]; | |
513 | if (value === null) { | |
514 | continue; | |
515 | } | |
516 | if (!warned) { | |
517 | Y.log('Warning: customise_dock_for_theme has changed. Please update your code.', 'warn', LOGNS); | |
518 | warned = true; | |
519 | } | |
520 | // Damn, the've set something. | |
557f44d9 AN |
521 | Y.log('Note for customise_dock_for_theme code: M.core_dock.cfg.' + key + |
522 | ' is now dock.set(\'' + key + '\', value)', | |
523 | 'debug', LOGNS); | |
a69a7e89 | 524 | this.set(cfgmap[key], value); |
84192d78 | 525 | } |
84192d78 SH |
526 | } |
527 | // Check for and apply any legacy CSS changes.. | |
528 | for (key in M.core_dock.css) { | |
a69a7e89 SH |
529 | if (Y.Lang.isString(key)) { |
530 | value = M.core_dock.css[key]; | |
531 | if (value === null) { | |
532 | continue; | |
533 | } | |
534 | if (!warned) { | |
535 | Y.log('Warning: customise_dock_for_theme has changed. Please update your code.', 'warn', LOGNS); | |
536 | warned = true; | |
537 | } | |
538 | // Damn, they've set something. | |
557f44d9 AN |
539 | Y.log('Note for customise_dock_for_theme code: M.core_dock.css.' + key + ' is now CSS.' + key + ' = value', |
540 | 'debug', LOGNS); | |
a69a7e89 | 541 | CSS[key] = value; |
84192d78 | 542 | } |
84192d78 SH |
543 | } |
544 | } | |
545 | }, | |
546 | /** | |
547 | * Initialises the dock node, creating it and its content if required. | |
548 | * | |
549 | * @private | |
550 | * @method _initialiseDockNode | |
551 | * @return {Node} The dockNode | |
552 | */ | |
3a0bc0fd | 553 | _initialiseDockNode: function() { |
84192d78 | 554 | var dock = this.get('dockNode'), |
3a0bc0fd DP |
555 | positionorientationclass = CSS.dock + '_' + this.get('position') + '_' + this.get('orientation'), |
556 | holdingarea = Y.Node.create('<div></div>').setStyles({display: 'none'}), | |
84192d78 SH |
557 | buttons = this.get('buttonsNode'), |
558 | container = this.get('itemContainerNode'); | |
a69a7e89 | 559 | |
84192d78 | 560 | if (!dock) { |
3a0bc0fd | 561 | dock = Y.one('#' + CSS.dock); |
84192d78 SH |
562 | } |
563 | if (!dock) { | |
3a0bc0fd | 564 | dock = Y.Node.create('<div id="' + CSS.dock + '"></div>'); |
84192d78 SH |
565 | BODY.append(dock); |
566 | } | |
567 | dock.setAttribute('role', 'menubar').addClass(positionorientationclass); | |
568 | if (Y.all(SELECTOR.dockonload).size() === 0) { | |
569 | // Nothing on the dock... hide it using CSS | |
570 | dock.addClass('nothingdocked'); | |
571 | } else { | |
3a0bc0fd | 572 | positionorientationclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation'); |
84192d78 SH |
573 | BODY.addClass(CSS.body).addClass(); |
574 | } | |
575 | ||
576 | if (!buttons) { | |
3a0bc0fd | 577 | buttons = dock.one('.' + CSS.buttonscontainer); |
84192d78 SH |
578 | } |
579 | if (!buttons) { | |
3a0bc0fd | 580 | buttons = Y.Node.create('<div class="' + CSS.buttonscontainer + '"></div>'); |
84192d78 SH |
581 | dock.append(buttons); |
582 | } | |
583 | ||
584 | if (!container) { | |
3a0bc0fd | 585 | container = dock.one('.' + CSS.dockeditemcontainer); |
84192d78 SH |
586 | } |
587 | if (!container) { | |
3a0bc0fd | 588 | container = Y.Node.create('<div class="' + CSS.dockeditemcontainer + '"></div>'); |
84192d78 SH |
589 | buttons.append(container); |
590 | } | |
591 | ||
592 | BODY.append(holdingarea); | |
593 | this.holdingareanode = holdingarea; | |
594 | ||
595 | this.set('dockNode', dock); | |
596 | this.set('buttonsNode', buttons); | |
597 | this.set('itemContainerNode', container); | |
598 | ||
599 | return dock; | |
600 | }, | |
601 | /** | |
602 | * Initialises the dock controls. | |
603 | * | |
604 | * @private | |
605 | * @method _initialiseDockControls | |
606 | */ | |
3a0bc0fd | 607 | _initialiseDockControls: function() { |
84192d78 SH |
608 | // Add a removeall button |
609 | // Must set the image src seperatly of we get an error with XML strict headers | |
610 | ||
3a0bc0fd DP |
611 | var removeall = Y.Node.create('<img alt="' + M.util.get_string('undockall', 'block') + '" tabindex="0" />'); |
612 | removeall.setAttribute('src', this.get('undockAllIconUrl')); | |
84192d78 | 613 | removeall.on('removeall|click', this.removeAll, this); |
3a0bc0fd DP |
614 | removeall.on('dock:actionkey', this.removeAll, this, {actions: {enter: true}}); |
615 | this.get('buttonsNode').append(Y.Node.create('<div class="' + CSS.controls + '"></div>').append(removeall)); | |
84192d78 SH |
616 | }, |
617 | /** | |
618 | * Returns the dock panel. Initialising it if it hasn't already been initialised. | |
619 | * @method getPanel | |
620 | * @return {DOCKPANEL} | |
621 | */ | |
3a0bc0fd | 622 | getPanel: function() { |
84192d78 SH |
623 | var panel = this.get('panel'); |
624 | if (!panel) { | |
3a0bc0fd | 625 | panel = new DOCKPANEL({dock: this}); |
84192d78 SH |
626 | panel.on('panel:visiblechange', this.resize, this); |
627 | Y.on('windowresize', this.resize, this); | |
628 | // Initialise the dockpanel .. should only happen once | |
629 | this.set('panel', panel); | |
630 | this.fire('dock:panelgenerated'); | |
631 | } | |
632 | return panel; | |
633 | }, | |
634 | /** | |
635 | * Resizes the dock panel if required. | |
636 | * @method resizePanelIfRequired | |
637 | */ | |
3a0bc0fd | 638 | resizePanelIfRequired: function() { |
84192d78 SH |
639 | this.resize(); |
640 | var panel = this.get('panel'); | |
641 | if (panel) { | |
642 | panel.correctWidth(); | |
643 | } | |
644 | }, | |
645 | /** | |
646 | * Handles a dock event sending it to the right place. | |
647 | * | |
648 | * @method handleEvent | |
649 | * @param {EventFacade} e | |
650 | * @param {Object} options | |
651 | * @return {Boolean} | |
652 | */ | |
3a0bc0fd | 653 | handleEvent: function(e, options) { |
a69a7e89 SH |
654 | var item = this.getActiveItem(), |
655 | target, | |
656 | targetid, | |
657 | regex = /^dock_item_(\d+)_title$/, | |
658 | self = this; | |
84192d78 SH |
659 | if (options.cssselector === 'body') { |
660 | if (!this.get('dockNode').contains(e.target)) { | |
661 | if (item) { | |
662 | item.hide(); | |
663 | } | |
664 | } | |
665 | } else { | |
84192d78 SH |
666 | if (e.target.test(options.cssselector)) { |
667 | target = e.target; | |
668 | } else { | |
669 | target = e.target.ancestor(options.cssselector); | |
670 | } | |
671 | if (!target) { | |
672 | return true; | |
673 | } | |
674 | if (this.preventevent !== null && e.type === this.preventevent) { | |
675 | return true; | |
676 | } | |
677 | if (options.preventevent) { | |
678 | this.preventevent = options.preventevent; | |
679 | if (options.preventdelay) { | |
3a0bc0fd | 680 | setTimeout(function() { |
84192d78 SH |
681 | self.preventevent = null; |
682 | }, options.preventdelay * 1000); | |
683 | } | |
684 | } | |
685 | if (this.delayedevent && this.delayedevent.timeout) { | |
686 | clearTimeout(this.delayedevent.timeout); | |
687 | this.delayedevent.event.detach(); | |
688 | this.delayedevent = null; | |
689 | } | |
690 | if (options.delay > 0) { | |
691 | return this.delayEvent(e, options, target); | |
692 | } | |
a69a7e89 | 693 | targetid = target.get('id'); |
84192d78 SH |
694 | if (targetid.match(regex)) { |
695 | item = this.dockeditems[targetid.replace(regex, '$1')]; | |
696 | if (item.active) { | |
697 | item.hide(); | |
698 | } else { | |
699 | item.show(); | |
700 | } | |
701 | } else if (item) { | |
702 | item.hide(); | |
703 | } | |
704 | } | |
705 | return true; | |
706 | }, | |
707 | /** | |
708 | * Delays an event. | |
709 | * | |
710 | * @method delayEvent | |
711 | * @param {EventFacade} event | |
712 | * @param {Object} options | |
713 | * @param {Node} target | |
714 | * @return {Boolean} | |
715 | */ | |
3a0bc0fd | 716 | delayEvent: function(event, options, target) { |
84192d78 | 717 | var self = this; |
3a0bc0fd | 718 | self.delayedevent = (function() { |
84192d78 | 719 | return { |
3a0bc0fd DP |
720 | target: target, |
721 | event: BODY.on('mousemove', function(e) { | |
84192d78 SH |
722 | self.delayedevent.target = e.target; |
723 | }), | |
3a0bc0fd | 724 | timeout: null |
84192d78 SH |
725 | }; |
726 | })(self); | |
3a0bc0fd | 727 | self.delayedevent.timeout = setTimeout(function() { |
84192d78 SH |
728 | self.delayedevent.timeout = null; |
729 | self.delayedevent.event.detach(); | |
730 | if (options.iscontained === self.get('dockNode').contains(self.delayedevent.target)) { | |
3a0bc0fd | 731 | self.handleEvent(event, {cssselector: options.cssselector, delay: 0, iscontained: options.iscontained}); |
84192d78 | 732 | } |
3a0bc0fd | 733 | }, options.delay * 1000); |
84192d78 SH |
734 | return true; |
735 | }, | |
736 | /** | |
737 | * Resizes block spaces. | |
738 | * @method resizeBlockSpace | |
739 | */ | |
3a0bc0fd | 740 | resizeBlockSpace: function() { |
84192d78 SH |
741 | if (Y.all(SELECTOR.dockonload).size() > 0) { |
742 | // Do not resize during initial load | |
743 | return; | |
744 | } | |
eb67d5a6 AN |
745 | |
746 | var populatedRegionCount = 0, | |
747 | populatedBlockRegions = [], | |
748 | unpopulatedBlockRegions = [], | |
749 | isMoving = false, | |
750 | populatedLegacyRegions = [], | |
751 | containsLegacyRegions = false, | |
752 | classesToAdd = [], | |
753 | classesToRemove = []; | |
754 | ||
84192d78 | 755 | // First look for understood regions. |
3a0bc0fd | 756 | Y.all(SELECTOR.blockregion).each(function(region) { |
84192d78 SH |
757 | var regionname = region.getData('blockregion'); |
758 | if (region.all('.block').size() > 0) { | |
eb67d5a6 AN |
759 | populatedBlockRegions.push(regionname); |
760 | populatedRegionCount++; | |
34df4e4b | 761 | } else if (region.all('.block_dock_placeholder').size() > 0) { |
eb67d5a6 | 762 | unpopulatedBlockRegions.push(regionname); |
84192d78 SH |
763 | } |
764 | }); | |
eb67d5a6 | 765 | |
84192d78 | 766 | // Next check for legacy regions. |
3a0bc0fd | 767 | Y.all('.block-region').each(function(region) { |
84192d78 SH |
768 | if (region.test(SELECTOR.blockregion)) { |
769 | // This is a new region, we've already processed it. | |
770 | return; | |
771 | } | |
eb67d5a6 AN |
772 | |
773 | // Sigh - there are legacy regions. | |
774 | containsLegacyRegions = true; | |
775 | ||
776 | var regionname = region.get('id').replace(/^region\-/, 'side-'), | |
777 | hasblocks = (region.all('.block').size() > 0); | |
778 | ||
84192d78 | 779 | if (hasblocks) { |
eb67d5a6 AN |
780 | populatedLegacyRegions.push(regionname); |
781 | populatedRegionCount++; | |
782 | } else { | |
783 | // This legacy region has no blocks so cannot have the -only body tag. | |
784 | classesToRemove.push( | |
785 | regionname + '-only' | |
786 | ); | |
84192d78 | 787 | } |
84192d78 | 788 | }); |
eb67d5a6 | 789 | |
84192d78 | 790 | if (BODY.hasClass('blocks-moving')) { |
eb67d5a6 AN |
791 | // When we're moving blocks, we do not want to collapse. |
792 | isMoving = true; | |
84192d78 | 793 | } |
eb67d5a6 AN |
794 | |
795 | Y.each(unpopulatedBlockRegions, function(regionname) { | |
796 | classesToAdd.push( | |
797 | // This block region is empty. | |
798 | 'empty-region-' + regionname, | |
799 | ||
800 | // Which has the same effect as being docked. | |
801 | 'docked-region-' + regionname | |
802 | ); | |
803 | classesToRemove.push( | |
804 | // It is no-longer used. | |
805 | 'used-region-' + regionname, | |
806 | ||
807 | // It cannot be the only region on screen if it is empty. | |
808 | regionname + '-only' | |
809 | ); | |
810 | }, this); | |
811 | ||
812 | Y.each(populatedBlockRegions, function(regionname) { | |
813 | classesToAdd.push( | |
814 | // This block region is in use. | |
815 | 'used-region-' + regionname | |
816 | ); | |
817 | classesToRemove.push( | |
818 | // It is not empty. | |
819 | 'empty-region-' + regionname, | |
820 | ||
821 | // Is it not docked. | |
822 | 'docked-region-' + regionname | |
823 | ); | |
824 | ||
825 | if (populatedRegionCount === 1 && isMoving === false) { | |
826 | // There was only one populated region, and we are not moving blocks. | |
827 | classesToAdd.push(regionname + '-only'); | |
828 | } else { | |
829 | // There were multiple block regions visible - remove any 'only' classes. | |
830 | classesToRemove.push(regionname + '-only'); | |
831 | } | |
832 | }, this); | |
833 | ||
834 | if (containsLegacyRegions) { | |
835 | // Handle the classing for legacy blocks. These have slightly different class names for the body. | |
836 | if (isMoving || populatedRegionCount !== 1) { | |
837 | Y.each(populatedLegacyRegions, function(regionname) { | |
838 | classesToRemove.push(regionname + '-only'); | |
839 | }); | |
840 | } else { | |
841 | Y.each(populatedLegacyRegions, function(regionname) { | |
842 | classesToAdd.push(regionname + '-only'); | |
843 | }); | |
844 | } | |
84192d78 SH |
845 | } |
846 | ||
eb67d5a6 AN |
847 | if (!BODY.hasClass('has-region-content')) { |
848 | // This page does not have a content region, therefore content-only is implied when all block regions are docked. | |
849 | if (populatedRegionCount === 0 && isMoving === false) { | |
850 | // If all blocks are docked, ensure that the content-only class is added anyway. | |
851 | classesToAdd.push('content-only'); | |
84192d78 | 852 | } else { |
eb67d5a6 AN |
853 | // Otherwise remove it. |
854 | classesToRemove.push('content-only'); | |
84192d78 SH |
855 | } |
856 | } | |
eb67d5a6 AN |
857 | |
858 | // Modify the body clases. | |
859 | Y.each(classesToRemove, function(className) { | |
860 | BODY.removeClass(className); | |
861 | }); | |
862 | Y.each(classesToAdd, function(className) { | |
863 | BODY.addClass(className); | |
864 | }); | |
84192d78 SH |
865 | }, |
866 | /** | |
867 | * Adds an item to the dock. | |
868 | * @method add | |
869 | * @param {DOCKEDITEM} item | |
870 | */ | |
3a0bc0fd | 871 | add: function(item) { |
84192d78 SH |
872 | // Set the dockitem id to the total count and then increment it. |
873 | item.set('id', this.totalcount); | |
3a0bc0fd | 874 | Y.log('Adding block ' + item._getLogDescription() + ' to the dock.', 'debug', LOGNS); |
84192d78 SH |
875 | this.count++; |
876 | this.totalcount++; | |
877 | this.dockeditems[item.get('id')] = item; | |
878 | this.dockeditems[item.get('id')].draw(); | |
879 | this.fire('dock:itemadded', item); | |
880 | this.fire('dock:itemschanged', item); | |
881 | }, | |
882 | /** | |
883 | * Appends an item to the dock (putting it in the item container. | |
884 | * @method append | |
885 | * @param {Node} docknode | |
886 | */ | |
3a0bc0fd | 887 | append: function(docknode) { |
84192d78 SH |
888 | this.get('itemContainerNode').append(docknode); |
889 | }, | |
890 | /** | |
891 | * Handles events that require a docked block to be returned to the page./ | |
892 | * @method handleReturnToBlock | |
893 | * @param {EventFacade} e | |
894 | */ | |
3a0bc0fd | 895 | handleReturnToBlock: function(e) { |
84192d78 SH |
896 | e.halt(); |
897 | this.remove(this.getActiveItem().get('id')); | |
898 | }, | |
899 | /** | |
900 | * Removes a docked item from the dock. | |
901 | * @method remove | |
902 | * @param {Number} id The docked item id. | |
903 | * @return {Boolean} | |
904 | */ | |
3a0bc0fd | 905 | remove: function(id) { |
84192d78 SH |
906 | if (!this.dockeditems[id]) { |
907 | return false; | |
908 | } | |
3a0bc0fd | 909 | Y.log('Removing block ' + this.dockeditems[id]._getLogDescription() + ' from the dock.', 'debug', LOGNS); |
84192d78 SH |
910 | this.dockeditems[id].remove(); |
911 | delete this.dockeditems[id]; | |
912 | this.count--; | |
913 | this.fire('dock:itemremoved', id); | |
914 | this.fire('dock:itemschanged', id); | |
915 | return true; | |
916 | }, | |
917 | /** | |
918 | * Ensures the the first item in the dock has the correct class. | |
919 | * @method resetFirstItem | |
920 | */ | |
3a0bc0fd DP |
921 | resetFirstItem: function() { |
922 | this.get('dockNode').all('.' + CSS.dockeditem + '.firstdockitem').removeClass('firstdockitem'); | |
923 | if (this.get('dockNode').one('.' + CSS.dockeditem)) { | |
924 | this.get('dockNode').one('.' + CSS.dockeditem).addClass('firstdockitem'); | |
84192d78 SH |
925 | } |
926 | }, | |
927 | /** | |
928 | * Removes all docked blocks returning them to the page. | |
929 | * @method removeAll | |
930 | * @return {Boolean} | |
931 | */ | |
3a0bc0fd DP |
932 | removeAll: function() { |
933 | Y.log('Undocking all ' + this.dockeditems.length + ' blocks', 'debug', LOGNS); | |
a69a7e89 SH |
934 | var i; |
935 | for (i in this.dockeditems) { | |
936 | if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) { | |
937 | this.remove(i); | |
938 | } | |
84192d78 SH |
939 | } |
940 | return true; | |
941 | }, | |
942 | /** | |
943 | * Hides the active item. | |
944 | * @method hideActive | |
945 | */ | |
3a0bc0fd | 946 | hideActive: function() { |
84192d78 SH |
947 | var item = this.getActiveItem(); |
948 | if (item) { | |
949 | item.hide(); | |
950 | } | |
951 | }, | |
952 | /** | |
953 | * Checks wether the dock should be shown or hidden | |
954 | * @method checkDockVisibility | |
955 | */ | |
3a0bc0fd DP |
956 | checkDockVisibility: function() { |
957 | var bodyclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation'); | |
84192d78 SH |
958 | if (!this.count) { |
959 | this.get('dockNode').addClass('nothingdocked'); | |
960 | BODY.removeClass(CSS.body).removeClass(); | |
961 | this.fire('dock:hidden'); | |
962 | } else { | |
963 | this.fire('dock:beforeshow'); | |
964 | this.get('dockNode').removeClass('nothingdocked'); | |
965 | BODY.addClass(CSS.body).addClass(bodyclass); | |
966 | this.fire('dock:shown'); | |
967 | } | |
968 | }, | |
969 | /** | |
970 | * This function checks the size and position of the panel and moves/resizes if | |
971 | * required to keep it within the bounds of the window. | |
972 | * @method resize | |
973 | * @return {Boolean} | |
974 | */ | |
3a0bc0fd | 975 | resize: function() { |
a69a7e89 SH |
976 | var panel = this.getPanel(), |
977 | item = this.getActiveItem(), | |
978 | buffer, | |
979 | screenh, | |
980 | docky, | |
981 | titletop, | |
982 | containery, | |
983 | containerheight, | |
984 | scrolltop, | |
985 | panelheight, | |
986 | dockx, | |
987 | titleleft; | |
84192d78 SH |
988 | if (!panel.get('visible') || !item) { |
989 | return true; | |
990 | } | |
991 | ||
992 | this.fire('dock:panelresizestart'); | |
993 | if (this.get('orientation') === 'vertical') { | |
a69a7e89 | 994 | buffer = this.get('bufferPanel'); |
3a0bc0fd | 995 | screenh = parseInt(BODY.get('winHeight'), 10) - (buffer * 2); |
a69a7e89 | 996 | docky = this.get('dockNode').getY(); |
3a0bc0fd | 997 | titletop = item.get('dockTitleNode').getY() - docky - buffer; |
a69a7e89 | 998 | containery = this.get('itemContainerNode').getY(); |
3a0bc0fd | 999 | containerheight = containery - docky + this.get('buttonsNode').get('offsetHeight'); |
a69a7e89 | 1000 | scrolltop = panel.get('bodyNode').get('scrollTop'); |
84192d78 SH |
1001 | panel.get('bodyNode').setStyle('height', 'auto'); |
1002 | panel.get('node').removeClass('oversized_content'); | |
a69a7e89 | 1003 | panelheight = panel.get('node').get('offsetHeight'); |
84192d78 SH |
1004 | |
1005 | if (Y.UA.ie > 0 && Y.UA.ie < 7) { | |
1006 | panel.setTop(item.get('dockTitleNode').getY()); | |
a69a7e89 | 1007 | } else if (panelheight > screenh) { |
3a0bc0fd DP |
1008 | panel.setTop(buffer - containerheight); |
1009 | panel.get('bodyNode').setStyle('height', (screenh - panel.get('headerNode').get('offsetHeight')) + 'px'); | |
84192d78 | 1010 | panel.get('node').addClass('oversized_content'); |
3a0bc0fd DP |
1011 | } else if (panelheight > (screenh - (titletop - buffer))) { |
1012 | panel.setTop(titletop - containerheight - (panelheight - (screenh - titletop)) + buffer); | |
84192d78 | 1013 | } else { |
3a0bc0fd | 1014 | panel.setTop(titletop - containerheight + buffer); |
84192d78 SH |
1015 | } |
1016 | ||
1017 | if (scrolltop) { | |
1018 | panel.get('bodyNode').set('scrollTop', scrolltop); | |
1019 | } | |
1020 | } | |
1021 | ||
1022 | if (this.get('position') === 'right') { | |
0f5e439e | 1023 | panel.get('node').setStyle('left', '-' + panel.get('node').get('offsetWidth') + 'px'); |
84192d78 SH |
1024 | |
1025 | } else if (this.get('position') === 'top') { | |
a69a7e89 | 1026 | dockx = this.get('dockNode').getX(); |
3a0bc0fd DP |
1027 | titleleft = item.get('dockTitleNode').getX() - dockx; |
1028 | panel.get('node').setStyle('left', titleleft + 'px'); | |
84192d78 SH |
1029 | } |
1030 | ||
1031 | this.fire('dock:resizepanelcomplete'); | |
1032 | return true; | |
1033 | }, | |
1034 | /** | |
1035 | * Returns the currently active dock item or false | |
1036 | * @method getActiveItem | |
1037 | * @return {DOCKEDITEM} | |
1038 | */ | |
3a0bc0fd | 1039 | getActiveItem: function() { |
a69a7e89 SH |
1040 | var i; |
1041 | for (i in this.dockeditems) { | |
84192d78 SH |
1042 | if (this.dockeditems[i].active) { |
1043 | return this.dockeditems[i]; | |
1044 | } | |
1045 | } | |
1046 | return false; | |
1047 | }, | |
1048 | /** | |
1049 | * Adds an item to the holding area. | |
1050 | * @method addToHoldingArea | |
1051 | * @param {Node} node | |
1052 | */ | |
3a0bc0fd | 1053 | addToHoldingArea: function(node) { |
84192d78 SH |
1054 | this.holdingareanode.append(node); |
1055 | } | |
1056 | }; | |
1057 | ||
1058 | Y.extend(DOCK, Y.Base, DOCK.prototype, { | |
3a0bc0fd DP |
1059 | NAME: 'moodle-core-dock', |
1060 | ATTRS: { | |
84192d78 SH |
1061 | /** |
1062 | * The dock itself. #dock. | |
1063 | * @attribute dockNode | |
1064 | * @type Node | |
1065 | * @writeOnce | |
1066 | */ | |
3a0bc0fd DP |
1067 | dockNode: { |
1068 | writeOnce: true | |
84192d78 SH |
1069 | }, |
1070 | /** | |
1071 | * The docks panel. | |
1072 | * @attribute panel | |
1073 | * @type DOCKPANEL | |
1074 | * @writeOnce | |
1075 | */ | |
3a0bc0fd DP |
1076 | panel: { |
1077 | writeOnce: true | |
84192d78 SH |
1078 | }, |
1079 | /** | |
1080 | * A container within the dock used for buttons. | |
1081 | * @attribute buttonsNode | |
1082 | * @type Node | |
1083 | * @writeOnce | |
1084 | */ | |
3a0bc0fd DP |
1085 | buttonsNode: { |
1086 | writeOnce: true | |
84192d78 SH |
1087 | }, |
1088 | /** | |
1089 | * A container within the dock used for docked blocks. | |
1090 | * @attribute itemContainerNode | |
1091 | * @type Node | |
1092 | * @writeOnce | |
1093 | */ | |
3a0bc0fd DP |
1094 | itemContainerNode: { |
1095 | writeOnce: true | |
84192d78 SH |
1096 | }, |
1097 | ||
1098 | /** | |
1099 | * Buffer used when containing a panel. | |
1100 | * @attribute bufferPanel | |
1101 | * @type Number | |
1102 | * @default 10 | |
1103 | */ | |
3a0bc0fd DP |
1104 | bufferPanel: { |
1105 | value: 10, | |
1106 | validator: Y.Lang.isNumber | |
84192d78 SH |
1107 | }, |
1108 | ||
1109 | /** | |
1110 | * Position of the dock. | |
1111 | * @attribute position | |
1112 | * @type String | |
1113 | * @default left | |
1114 | */ | |
3a0bc0fd DP |
1115 | position: { |
1116 | value: 'left', | |
1117 | validator: Y.Lang.isString | |
84192d78 SH |
1118 | }, |
1119 | ||
1120 | /** | |
1121 | * vertical || horizontal determines if we change the title | |
1122 | * @attribute orientation | |
1123 | * @type String | |
1124 | * @default vertical | |
1125 | */ | |
3a0bc0fd DP |
1126 | orientation: { |
1127 | value: 'vertical', | |
1128 | validator: Y.Lang.isString, | |
1129 | setter: function(value) { | |
84192d78 SH |
1130 | if (value.match(/^vertical$/i)) { |
1131 | return 'vertical'; | |
1132 | } | |
1133 | return 'horizontal'; | |
1134 | } | |
1135 | }, | |
1136 | ||
1137 | /** | |
1138 | * Space between the top of the dock and the first item. | |
1139 | * @attribute bufferBeforeFirstItem | |
1140 | * @type Number | |
1141 | * @default 10 | |
1142 | */ | |
3a0bc0fd DP |
1143 | bufferBeforeFirstItem: { |
1144 | value: 10, | |
1145 | validator: Y.Lang.isNumber | |
84192d78 SH |
1146 | }, |
1147 | ||
1148 | /** | |
1149 | * Icon URL for the icon to undock all blocks | |
1150 | * @attribute undockAllIconUrl | |
1151 | * @type String | |
1152 | * @default t/dock_to_block | |
1153 | */ | |
557f44d9 | 1154 | undockAllIconUrl: { |
3a0bc0fd DP |
1155 | value: M.util.image_url((window.right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block', 'moodle'), |
1156 | validator: Y.Lang.isString | |
84192d78 SH |
1157 | } |
1158 | } | |
1159 | }); | |
1160 | Y.augment(DOCK, Y.EventTarget); | |
ad3f8cd1 DP |
1161 | /* global DOCKPANEL, LOGNS */ |
1162 | ||
84192d78 SH |
1163 | /** |
1164 | * Dock JS. | |
1165 | * | |
1166 | * This file contains the panel class used by the dock to display the content of docked blocks. | |
1167 | * | |
1168 | * @module moodle-core-dock | |
1169 | */ | |
1170 | ||
1171 | /** | |
1172 | * Panel. | |
1173 | * | |
1174 | * @namespace M.core.dock | |
1175 | * @class Panel | |
1176 | * @constructor | |
1f777e5c AN |
1177 | * @extends Base |
1178 | * @uses EventTarget | |
84192d78 | 1179 | */ |
a69a7e89 | 1180 | DOCKPANEL = function() { |
84192d78 SH |
1181 | DOCKPANEL.superclass.constructor.apply(this, arguments); |
1182 | }; | |
1183 | DOCKPANEL.prototype = { | |
1184 | /** | |
1185 | * True once the panel has been created. | |
1186 | * @property created | |
1187 | * @protected | |
1188 | * @type {Boolean} | |
1189 | */ | |
3a0bc0fd | 1190 | created: false, |
84192d78 SH |
1191 | /** |
1192 | * Called during the initialisation process of the object. | |
1193 | * @method initializer | |
1194 | */ | |
3a0bc0fd | 1195 | initializer: function() { |
82955480 | 1196 | Y.log('Panel initialising', 'debug', LOGNS); |
84192d78 SH |
1197 | /** |
1198 | * Fired before the panel is shown. | |
1199 | * @event dockpane::beforeshow | |
1200 | */ | |
3a0bc0fd | 1201 | this.publish('dockpanel:beforeshow', {prefix: 'dockpanel'}); |
84192d78 SH |
1202 | /** |
1203 | * Fired after the panel is shown. | |
1204 | * @event dockpanel:shown | |
1205 | */ | |
3a0bc0fd | 1206 | this.publish('dockpanel:shown', {prefix: 'dockpanel'}); |
84192d78 SH |
1207 | /** |
1208 | * Fired before the panel is hidden. | |
1209 | * @event dockpane::beforehide | |
1210 | */ | |
3a0bc0fd | 1211 | this.publish('dockpanel:beforehide', {prefix: 'dockpanel'}); |
84192d78 SH |
1212 | /** |
1213 | * Fired after the panel is hidden. | |
1214 | * @event dockpanel:hidden | |
1215 | */ | |
3a0bc0fd | 1216 | this.publish('dockpanel:hidden', {prefix: 'dockpanel'}); |
84192d78 SH |
1217 | /** |
1218 | * Fired when ever the dock panel is either hidden or shown. | |
1219 | * Always fired after the shown or hidden events. | |
1220 | * @event dockpanel:visiblechange | |
1221 | */ | |
3a0bc0fd | 1222 | this.publish('dockpanel:visiblechange', {prefix: 'dockpanel'}); |
84192d78 SH |
1223 | }, |
1224 | /** | |
1225 | * Creates the Panel if it has not already been created. | |
1226 | * @method create | |
1227 | * @return {Boolean} | |
1228 | */ | |
3a0bc0fd | 1229 | create: function() { |
84192d78 SH |
1230 | if (this.created) { |
1231 | return true; | |
1232 | } | |
1233 | this.created = true; | |
1234 | var dock = this.get('dock'), | |
1235 | node = dock.get('dockNode'); | |
1236 | this.set('node', Y.Node.create('<div id="dockeditempanel" class="dockitempanel_hidden"></div>')); | |
1237 | this.set('contentNode', Y.Node.create('<div class="dockeditempanel_content"></div>')); | |
1238 | this.set('headerNode', Y.Node.create('<div class="dockeditempanel_hd"></div>')); | |
1239 | this.set('bodyNode', Y.Node.create('<div class="dockeditempanel_bd"></div>')); | |
1240 | node.append( | |
1241 | this.get('node').append(this.get('contentNode').append(this.get('headerNode')).append(this.get('bodyNode'))) | |
1242 | ); | |
1243 | }, | |
1244 | /** | |
1245 | * Displays the panel. | |
1246 | * @method show | |
1247 | */ | |
3a0bc0fd | 1248 | show: function() { |
84192d78 SH |
1249 | this.create(); |
1250 | this.fire('dockpanel:beforeshow'); | |
1251 | this.set('visible', true); | |
1252 | this.get('node').removeClass('dockitempanel_hidden'); | |
1253 | this.fire('dockpanel:shown'); | |
1254 | this.fire('dockpanel:visiblechange'); | |
1255 | }, | |
1256 | /** | |
1257 | * Hides the panel | |
1258 | * @method hide | |
1259 | */ | |
3a0bc0fd | 1260 | hide: function() { |
84192d78 SH |
1261 | this.fire('dockpanel:beforehide'); |
1262 | this.set('visible', false); | |
1263 | this.get('node').addClass('dockitempanel_hidden'); | |
1264 | this.fire('dockpanel:hidden'); | |
1265 | this.fire('dockpanel:visiblechange'); | |
1266 | }, | |
1267 | /** | |
1268 | * Sets the panel header. | |
1269 | * @method setHeader | |
1270 | * @param {Node|String} content | |
1271 | */ | |
3a0bc0fd | 1272 | setHeader: function(content) { |
84192d78 | 1273 | this.create(); |
a69a7e89 SH |
1274 | var header = this.get('headerNode'), |
1275 | i; | |
84192d78 SH |
1276 | header.setContent(content); |
1277 | if (arguments.length > 1) { | |
a69a7e89 SH |
1278 | for (i = 1; i < arguments.length; i++) { |
1279 | if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) { | |
1280 | header.append(arguments[i]); | |
1281 | } | |
84192d78 SH |
1282 | } |
1283 | } | |
1284 | }, | |
1285 | /** | |
1286 | * Sets the panel body. | |
1287 | * @method setBody | |
1288 | * @param {Node|String} content | |
1289 | */ | |
3a0bc0fd | 1290 | setBody: function(content) { |
84192d78 SH |
1291 | this.create(); |
1292 | this.get('bodyNode').setContent(content); | |
1293 | }, | |
1294 | /** | |
1295 | * Sets the new top mark of the panel. | |
1296 | * | |
1297 | * @method setTop | |
1298 | * @param {Number} newtop | |
1299 | */ | |
3a0bc0fd | 1300 | setTop: function(newtop) { |
84192d78 SH |
1301 | if (Y.UA.ie > 0 && Y.UA.ie < 7) { |
1302 | this.get('node').setY(newtop); | |
1303 | } else { | |
3a0bc0fd | 1304 | this.get('node').setStyle('top', newtop.toString() + 'px'); |
84192d78 SH |
1305 | } |
1306 | }, | |
1307 | /** | |
1308 | * Corrects the width of the panel. | |
1309 | * @method correctWidth | |
1310 | */ | |
3a0bc0fd | 1311 | correctWidth: function() { |
84192d78 SH |
1312 | var bodyNode = this.get('bodyNode'), |
1313 | // Width of content. | |
1314 | width = bodyNode.get('clientWidth'), | |
1315 | // Scrollable width of content. | |
1316 | scroll = bodyNode.get('scrollWidth'), | |
1317 | // Width of content container with overflow. | |
1318 | offsetWidth = bodyNode.get('offsetWidth'), | |
1319 | // The new width - defaults to the current width. | |
1320 | newWidth = width, | |
1321 | // The max width (80% of screen). | |
1322 | maxWidth = Math.round(bodyNode.get('winWidth') * 0.8); | |
1323 | ||
1324 | // If the scrollable width is more than the visible width | |
1325 | if (scroll > width) { | |
1326 | // Content width | |
1327 | // + the difference | |
1328 | // + any rendering difference (borders, padding) | |
1329 | // + 10px to make it look nice. | |
3a0bc0fd | 1330 | newWidth = width + (scroll - width) + ((offsetWidth - width) * 2) + 10; |
84192d78 SH |
1331 | } |
1332 | ||
1333 | // Make sure its not more then the maxwidth | |
1334 | if (newWidth > maxWidth) { | |
1335 | newWidth = maxWidth; | |
1336 | } | |
1337 | ||
1338 | // Set the new width if its more than the old width. | |
1339 | if (newWidth > offsetWidth) { | |
3a0bc0fd | 1340 | this.get('node').setStyle('width', newWidth + 'px'); |
84192d78 SH |
1341 | } |
1342 | } | |
1343 | }; | |
1344 | Y.extend(DOCKPANEL, Y.Base, DOCKPANEL.prototype, { | |
3a0bc0fd DP |
1345 | NAME: 'moodle-core-dock-panel', |
1346 | ATTRS: { | |
84192d78 SH |
1347 | /** |
1348 | * The dock itself. | |
1349 | * @attribute dock | |
1350 | * @type DOCK | |
1351 | * @writeonce | |
1352 | */ | |
3a0bc0fd DP |
1353 | dock: { |
1354 | writeOnce: 'initOnly' | |
84192d78 SH |
1355 | }, |
1356 | /** | |
1357 | * The node that contains the whole panel. | |
1358 | * @attribute node | |
1359 | * @type Node | |
1360 | */ | |
3a0bc0fd DP |
1361 | node: { |
1362 | value: null | |
84192d78 SH |
1363 | }, |
1364 | /** | |
1365 | * The node that contains the header, body and footer. | |
1366 | * @attribute contentNode | |
1367 | * @type Node | |
1368 | */ | |
3a0bc0fd DP |
1369 | contentNode: { |
1370 | value: null | |
84192d78 SH |
1371 | }, |
1372 | /** | |
1373 | * The node that contains the header | |
1374 | * @attribute headerNode | |
1375 | * @type Node | |
1376 | */ | |
3a0bc0fd DP |
1377 | headerNode: { |
1378 | value: null | |
84192d78 SH |
1379 | }, |
1380 | /** | |
1381 | * The node that contains the body | |
1382 | * @attribute bodyNode | |
1383 | * @type Node | |
1384 | */ | |
3a0bc0fd DP |
1385 | bodyNode: { |
1386 | value: null | |
84192d78 SH |
1387 | }, |
1388 | /** | |
1389 | * True if the panel is currently visible. | |
1390 | * @attribute visible | |
1391 | * @type Boolean | |
1392 | */ | |
3a0bc0fd DP |
1393 | visible: { |
1394 | value: false | |
84192d78 SH |
1395 | } |
1396 | } | |
1397 | }); | |
1398 | Y.augment(DOCKPANEL, Y.EventTarget); | |
ad3f8cd1 DP |
1399 | /* global TABHEIGHTMANAGER, LOGNS */ |
1400 | ||
84192d78 SH |
1401 | /** |
1402 | * Dock JS. | |
1403 | * | |
1404 | * This file contains the tab height manager. | |
1405 | * The tab height manager is responsible for ensure all tabs are visible all the time. | |
1406 | * | |
1407 | * @module moodle-core-dock | |
1408 | */ | |
1409 | ||
1410 | /** | |
1411 | * Tab height manager. | |
1412 | * | |
1413 | * @namespace M.core.dock | |
1414 | * @class TabHeightManager | |
1415 | * @constructor | |
1f777e5c | 1416 | * @extends Base |
84192d78 | 1417 | */ |
a69a7e89 | 1418 | TABHEIGHTMANAGER = function() { |
84192d78 SH |
1419 | TABHEIGHTMANAGER.superclass.constructor.apply(this, arguments); |
1420 | }; | |
1421 | TABHEIGHTMANAGER.prototype = { | |
1422 | /** | |
1423 | * Initialises the dock sizer which then attaches itself to the required | |
1424 | * events in order to monitor the dock | |
1425 | * @method initializer | |
1426 | */ | |
3a0bc0fd | 1427 | initializer: function() { |
84192d78 SH |
1428 | var dock = this.get('dock'); |
1429 | dock.on('dock:itemschanged', this.checkSizing, this); | |
1430 | Y.on('windowresize', this.checkSizing, this); | |
1431 | }, | |
1432 | /** | |
1433 | * Check if the size dock items needs to be adjusted | |
1434 | * @method checkSizing | |
1435 | */ | |
3a0bc0fd | 1436 | checkSizing: function() { |
84192d78 SH |
1437 | var dock = this.get('dock'), |
1438 | node = dock.get('dockNode'), | |
1439 | items = dock.dockeditems, | |
abaae2a5 SH |
1440 | containermargin = parseInt(node.one('.dockeditem_container').getStyle('marginTop').replace('/[^0-9]+$/', ''), 10), |
1441 | dockheight = node.get('offsetHeight') - containermargin, | |
1442 | controlheight = node.one('.controls').get('offsetHeight'), | |
1443 | buffer = (dock.get('bufferPanel') * 3), | |
3a0bc0fd | 1444 | possibleheight = dockheight - controlheight - buffer - (items.length * 2), |
84192d78 SH |
1445 | totalheight = 0, |
1446 | id, dockedtitle; | |
1447 | if (items.length > 0) { | |
1448 | for (id in items) { | |
a69a7e89 | 1449 | if (Y.Lang.isNumber(id) || Y.Lang.isString(id)) { |
3a0bc0fd | 1450 | dockedtitle = Y.one(items[id].get('title')).ancestor('.' + CSS.dockedtitle); |
a69a7e89 SH |
1451 | if (dockedtitle) { |
1452 | if (this.get('enabled')) { | |
1453 | dockedtitle.setStyle('height', 'auto'); | |
1454 | } | |
1455 | totalheight += dockedtitle.get('offsetHeight') || 0; | |
84192d78 | 1456 | } |
84192d78 SH |
1457 | } |
1458 | } | |
1459 | if (totalheight > possibleheight) { | |
1460 | this.enable(possibleheight); | |
1461 | } | |
1462 | } | |
1463 | }, | |
1464 | /** | |
1465 | * Enables the dock sizer and resizes where required. | |
1466 | * @method enable | |
1467 | * @param {Number} possibleheight | |
1468 | */ | |
3a0bc0fd | 1469 | enable: function(possibleheight) { |
84192d78 SH |
1470 | var dock = this.get('dock'), |
1471 | items = dock.dockeditems, | |
1472 | count = dock.count, | |
1473 | runningcount = 0, | |
1474 | usedheight = 0, | |
1475 | id, itemtitle, itemheight, offsetheight; | |
82955480 | 1476 | Y.log('Enabling the dock tab sizer.', 'debug', LOGNS); |
84192d78 SH |
1477 | this.set('enabled', true); |
1478 | for (id in items) { | |
a69a7e89 | 1479 | if (Y.Lang.isNumber(id) || Y.Lang.isString(id)) { |
3a0bc0fd | 1480 | itemtitle = Y.one(items[id].get('title')).ancestor('.' + CSS.dockedtitle); |
a69a7e89 SH |
1481 | if (!itemtitle) { |
1482 | continue; | |
1483 | } | |
3a0bc0fd | 1484 | itemheight = Math.floor((possibleheight - usedheight) / (count - runningcount)); |
a69a7e89 SH |
1485 | offsetheight = itemtitle.get('offsetHeight'); |
1486 | itemtitle.setStyle('overflow', 'hidden'); | |
1487 | if (offsetheight > itemheight) { | |
3a0bc0fd | 1488 | itemtitle.setStyle('height', itemheight + 'px'); |
a69a7e89 SH |
1489 | usedheight += itemheight; |
1490 | } else { | |
1491 | usedheight += offsetheight; | |
1492 | } | |
1493 | runningcount++; | |
84192d78 | 1494 | } |
84192d78 SH |
1495 | } |
1496 | } | |
1497 | }; | |
1498 | Y.extend(TABHEIGHTMANAGER, Y.Base, TABHEIGHTMANAGER.prototype, { | |
3a0bc0fd DP |
1499 | NAME: 'moodle-core-tabheightmanager', |
1500 | ATTRS: { | |
84192d78 SH |
1501 | /** |
1502 | * The dock. | |
1503 | * @attribute dock | |
1504 | * @type DOCK | |
1505 | * @writeOnce | |
1506 | */ | |
3a0bc0fd DP |
1507 | dock: { |
1508 | writeOnce: 'initOnly' | |
84192d78 SH |
1509 | }, |
1510 | /** | |
1511 | * True if the item_sizer is being used, false otherwise. | |
1512 | * @attribute enabled | |
1513 | * @type Bool | |
1514 | */ | |
3a0bc0fd DP |
1515 | enabled: { |
1516 | value: false | |
84192d78 SH |
1517 | } |
1518 | } | |
1519 | }); | |
1520 | /** | |
1521 | * Dock JS. | |
1522 | * | |
1523 | * This file contains the action key event definition that is used for accessibility handling within the Dock. | |
1524 | * | |
1525 | * @module moodle-core-dock | |
1526 | */ | |
1527 | ||
1528 | /** | |
1529 | * A 'dock:actionkey' Event. | |
1530 | * The event consists of the left arrow, right arrow, enter and space keys. | |
1531 | * More keys can be mapped to action meanings. | |
1532 | * actions: collapse , expand, toggle, enter. | |
1533 | * | |
1534 | * This event is subscribed to by dockitems. | |
1535 | * The on() method to subscribe allows specifying the desired trigger actions as JSON. | |
1536 | * | |
1537 | * This event can also be delegated if needed. | |
1538 | * | |
1539 | * @namespace M.core.dock | |
1540 | * @class ActionKey | |
1541 | */ | |
1542 | Y.Event.define("dock:actionkey", { | |
a69a7e89 SH |
1543 | // Webkit and IE repeat keydown when you hold down arrow keys. |
1544 | // Opera links keypress to page scroll; others keydown. | |
1545 | // Firefox prevents page scroll via preventDefault() on either | |
1546 | // keydown or keypress. | |
1547 | _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress', | |
84192d78 SH |
1548 | |
1549 | /** | |
1550 | * The keys to trigger on. | |
a69a7e89 SH |
1551 | * @property _keys |
1552 | */ | |
1553 | _keys: { | |
3a0bc0fd | 1554 | // arrows |
a69a7e89 SH |
1555 | '37': 'collapse', |
1556 | '39': 'expand', | |
3a0bc0fd | 1557 | // (@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings) |
a69a7e89 SH |
1558 | '32': 'toggle', |
1559 | '13': 'enter' | |
1560 | }, | |
84192d78 SH |
1561 | |
1562 | /** | |
1563 | * Handles key events | |
1564 | * @method _keyHandler | |
1565 | * @param {EventFacade} e | |
1566 | * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers | |
1567 | * @param {Object} args | |
1568 | */ | |
3a0bc0fd | 1569 | _keyHandler: function(e, notifier, args) { |
a69a7e89 SH |
1570 | var actObj; |
1571 | if (!args.actions) { | |
1572 | actObj = {collapse: true, expand: true, toggle: true, enter: true}; | |
1573 | } else { | |
1574 | actObj = args.actions; | |
1575 | } | |
1576 | if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) { | |
1577 | e.action = this._keys[e.keyCode]; | |
1578 | notifier.fire(e); | |
1579 | } | |
1580 | }, | |
84192d78 SH |
1581 | |
1582 | /** | |
1583 | * Subscribes to events. | |
1584 | * @method on | |
1585 | * @param {Node} node The node this subscription was applied to. | |
1586 | * @param {Subscription} sub The object tracking this subscription. | |
1587 | * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers | |
1588 | */ | |
3a0bc0fd | 1589 | on: function(node, sub, notifier) { |
a69a7e89 SH |
1590 | // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions). |
1591 | if (sub.args === null) { | |
3a0bc0fd | 1592 | // no actions given |
a69a7e89 SH |
1593 | sub._detacher = node.on(this._event, this._keyHandler, this, notifier, {actions: false}); |
1594 | } else { | |
1595 | sub._detacher = node.on(this._event, this._keyHandler, this, notifier, sub.args[0]); | |
1596 | } | |
1597 | }, | |
84192d78 SH |
1598 | |
1599 | /** | |
1600 | * Detaches an event listener | |
1601 | * @method detach | |
a69a7e89 SH |
1602 | * @param {Node} node The node this subscription was applied to. |
1603 | * @param {Subscription} sub The object tracking this subscription. | |
1604 | * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers | |
84192d78 | 1605 | */ |
3a0bc0fd DP |
1606 | detach: function(node, sub) { |
1607 | // detach our _detacher handle of the subscription made in on() | |
a69a7e89 SH |
1608 | sub._detacher.detach(); |
1609 | }, | |
84192d78 SH |
1610 | |
1611 | /** | |
1612 | * Creates a delegated event listener. | |
1613 | * @method delegate | |
1614 | * @param {Node} node The node this subscription was applied to. | |
1615 | * @param {Subscription} sub The object tracking this subscription. | |
1616 | * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers | |
1617 | * @param {String|function} filter Selector string or function that accpets an event object and returns null. | |
1618 | */ | |
3a0bc0fd | 1619 | delegate: function(node, sub, notifier, filter) { |
a69a7e89 SH |
1620 | // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions). |
1621 | if (sub.args === null) { | |
3a0bc0fd DP |
1622 | // no actions given |
1623 | sub._delegateDetacher = node.delegate(this._event, this._keyHandler, filter, this, notifier, {actions: false}); | |
a69a7e89 SH |
1624 | } else { |
1625 | sub._delegateDetacher = node.delegate(this._event, this._keyHandler, filter, this, notifier, sub.args[0]); | |
1626 | } | |
1627 | }, | |
84192d78 SH |
1628 | |
1629 | /** | |
1630 | * Detaches a delegated event listener. | |
1631 | * @method detachDelegate | |
1632 | * @param {Node} node The node this subscription was applied to. | |
1633 | * @param {Subscription} sub The object tracking this subscription. | |
1634 | * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers | |
1635 | * @param {String|function} filter Selector string or function that accpets an event object and returns null. | |
1636 | */ | |
3a0bc0fd | 1637 | detachDelegate: function(node, sub) { |
a69a7e89 SH |
1638 | sub._delegateDetacher.detach(); |
1639 | } | |
84192d78 | 1640 | }); |
ad3f8cd1 DP |
1641 | /* global BLOCK, LOGNS, DOCKEDITEM */ |
1642 | ||
84192d78 SH |
1643 | /** |
1644 | * Dock JS. | |
1645 | * | |
1646 | * This file contains the block class used to manage blocks (both docked and not) for the dock. | |
1647 | * | |
1648 | * @module moodle-core-dock | |
1649 | */ | |
1650 | ||
1651 | /** | |
1652 | * Block. | |
1653 | * | |
1654 | * @namespace M.core.dock | |
1655 | * @class Block | |
1656 | * @constructor | |
1f777e5c | 1657 | * @extends Base |
84192d78 | 1658 | */ |
a69a7e89 | 1659 | BLOCK = function() { |
84192d78 SH |
1660 | BLOCK.superclass.constructor.apply(this, arguments); |
1661 | }; | |
1662 | BLOCK.prototype = { | |
1663 | /** | |
1664 | * A content place holder used when the block has been docked. | |
1665 | * @property contentplaceholder | |
1666 | * @protected | |
1667 | * @type Node | |
1668 | */ | |
3a0bc0fd | 1669 | contentplaceholder: null, |
84192d78 SH |
1670 | /** |
1671 | * The skip link associated with this block. | |
1672 | * @property contentskipanchor | |
1673 | * @protected | |
1674 | * @type Node | |
1675 | */ | |
3a0bc0fd | 1676 | contentskipanchor: null, |
84192d78 SH |
1677 | /** |
1678 | * The cached content node for the actual block | |
1679 | * @property cachedcontentnode | |
1680 | * @protected | |
1681 | * @type Node | |
1682 | */ | |
3a0bc0fd | 1683 | cachedcontentnode: null, |
84192d78 SH |
1684 | /** |
1685 | * If true the user preference isn't updated | |
1686 | * @property skipsetposition | |
1687 | * @protected | |
1688 | * @type Boolean | |
1689 | */ | |
3a0bc0fd | 1690 | skipsetposition: true, |
84192d78 SH |
1691 | /** |
1692 | * The dock item associated with this block | |
1693 | * @property dockitem | |
1694 | * @protected | |
a69a7e89 | 1695 | * @type DOCKEDITEM |
84192d78 | 1696 | */ |
3a0bc0fd | 1697 | dockitem: null, |
84192d78 SH |
1698 | /** |
1699 | * Called during the initialisation process of the object. | |
1700 | * @method initializer | |
1701 | */ | |
3a0bc0fd DP |
1702 | initializer: function() { |
1703 | var node = Y.one('#inst' + this.get('id')); | |
84192d78 SH |
1704 | if (!node) { |
1705 | return false; | |
1706 | } | |
1707 | ||
3a0bc0fd | 1708 | Y.log('Initialised block with instance id:' + this.get('id'), 'debug', LOGNS); |
84192d78 SH |
1709 | |
1710 | M.core.dock.ensureMoveToIconExists(node); | |
1711 | ||
1712 | // Move the block straight to the dock if required | |
1713 | if (node.hasClass(CSS.dockonload)) { | |
1714 | node.removeClass(CSS.dockonload); | |
e3554c1c | 1715 | this.moveToDock(); |
84192d78 SH |
1716 | } |
1717 | this.skipsetposition = false; | |
1718 | return true; | |
1719 | }, | |
1720 | /** | |
1721 | * Returns the class associated with this block. | |
1722 | * @method _getBlockClass | |
1723 | * @private | |
1724 | * @param {Node} node | |
1725 | * @return String | |
1726 | */ | |
3a0bc0fd | 1727 | _getBlockClass: function(node) { |
a69a7e89 SH |
1728 | var block = node.getData('block'), |
1729 | classes, | |
1730 | matches; | |
1731 | if (Y.Lang.isString(block) && block !== '') { | |
1732 | return block; | |
1733 | } | |
1734 | classes = node.getAttribute('className').toString(); | |
1735 | matches = /(^| )block_([^ ]+)/.exec(classes); | |
84192d78 SH |
1736 | if (matches) { |
1737 | return matches[2]; | |
1738 | } | |
1739 | return matches; | |
1740 | }, | |
1741 | ||
1742 | /** | |
a69a7e89 | 1743 | * This function is responsible for moving a block from the page structure onto the dock. |
84192d78 SH |
1744 | * @method moveToDock |
1745 | * @param {EventFacade} e | |
1746 | */ | |
3a0bc0fd | 1747 | moveToDock: function(e) { |
84192d78 SH |
1748 | if (e) { |
1749 | e.halt(true); | |
1750 | } | |
1751 | ||
1752 | var dock = M.core.dock.get(), | |
1753 | id = this.get('id'), | |
3a0bc0fd | 1754 | blockcontent = Y.one('#inst' + id).one('.content'), |
557f44d9 | 1755 | icon = (window.right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block', |
a69a7e89 SH |
1756 | breakchar = (location.href.match(/\?/)) ? '&' : '?', |
1757 | blocktitle, | |
1758 | blockcommands, | |
1759 | movetoimg, | |
1760 | moveto; | |
84192d78 SH |
1761 | |
1762 | if (!blockcontent) { | |
1763 | return; | |
1764 | } | |
1765 | ||
3a0bc0fd | 1766 | Y.log('Moving block to the dock:' + this.get('id'), 'debug', LOGNS); |
84192d78 | 1767 | |
84192d78 SH |
1768 | this.recordBlockState(); |
1769 | ||
1770 | blocktitle = this.cachedcontentnode.one('.title h2').cloneNode(true); | |
84192d78 | 1771 | |
e3554c1c AN |
1772 | // Build up the block commands. |
1773 | // These should not actually added to the DOM. | |
1774 | blockcommands = this.cachedcontentnode.one('.title .commands'); | |
1775 | if (blockcommands) { | |
1776 | blockcommands = blockcommands.cloneNode(true); | |
1777 | } else { | |
1778 | blockcommands = Y.Node.create('<div class="commands"></div>'); | |
1779 | } | |
a69a7e89 | 1780 | movetoimg = Y.Node.create('<img />').setAttrs({ |
3a0bc0fd DP |
1781 | alt: Y.Escape.html(M.util.get_string('undockitem', 'block')), |
1782 | title: Y.Escape.html(M.util.get_string('undockblock', 'block', blocktitle.get('innerHTML'))), | |
1783 | src: M.util.image_url(icon, 'moodle') | |
a69a7e89 SH |
1784 | }); |
1785 | moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>').setAttrs({ | |
3a0bc0fd | 1786 | href: Y.config.win.location.href + breakchar + 'dock=' + id |
a69a7e89 SH |
1787 | }); |
1788 | moveto.append(movetoimg); | |
1789 | blockcommands.append(moveto.append(movetoimg)); | |
84192d78 SH |
1790 | |
1791 | // Create a new dock item for the block | |
1792 | this.dockitem = new DOCKEDITEM({ | |
3a0bc0fd DP |
1793 | block: this, |
1794 | dock: dock, | |
1795 | blockinstanceid: id, | |
1796 | title: blocktitle, | |
1797 | contents: blockcontent, | |
1798 | commands: blockcommands, | |
1799 | blockclass: this._getBlockClass(Y.one('#inst' + id)) | |
84192d78 SH |
1800 | }); |
1801 | // Register an event so that when it is removed we can put it back as a block | |
1802 | dock.add(this.dockitem); | |
1803 | ||
1804 | if (!this.skipsetposition) { | |
1805 | // save the users preference | |
3a0bc0fd | 1806 | M.util.set_user_preference('docked_block_instance_' + id, 1); |
84192d78 SH |
1807 | } |
1808 | ||
a69a7e89 | 1809 | this.set('isDocked', true); |
84192d78 SH |
1810 | }, |
1811 | /** | |
1812 | * Records the block state and adds it to the docks holding area. | |
1813 | * @method recordBlockState | |
1814 | */ | |
3a0bc0fd | 1815 | recordBlockState: function() { |
84192d78 SH |
1816 | var id = this.get('id'), |
1817 | dock = M.core.dock.get(), | |
3a0bc0fd | 1818 | node = Y.one('#inst' + id), |
84192d78 SH |
1819 | skipanchor = node.previous(); |
1820 | // Disable the skip anchor when docking | |
1821 | if (skipanchor.hasClass('skip-block')) { | |
1822 | this.contentskipanchor = skipanchor; | |
1823 | this.contentskipanchor.hide(); | |
1824 | } | |
1825 | this.cachedcontentnode = node; | |
1826 | this.contentplaceholder = Y.Node.create('<div class="block_dock_placeholder"></div>'); | |
1827 | node.replace(this.contentplaceholder); | |
1828 | dock.addToHoldingArea(node); | |
1829 | node = null; | |
84192d78 SH |
1830 | }, |
1831 | ||
1832 | /** | |
1833 | * This function removes a block from the dock and puts it back into the page structure. | |
a69a7e89 | 1834 | * @method returnToPage |
84192d78 SH |
1835 | * @return {Boolean} |
1836 | */ | |
3a0bc0fd | 1837 | returnToPage: function() { |
e3554c1c | 1838 | var id = this.get('id'); |
84192d78 | 1839 | |
3a0bc0fd | 1840 | Y.log('Moving block out of the dock:' + this.get('id'), 'debug', LOGNS); |
84192d78 SH |
1841 | |
1842 | // Enable the skip anchor when going back to block mode | |
1843 | if (this.contentskipanchor) { | |
1844 | this.contentskipanchor.show(); | |
1845 | } | |
1846 | ||
1847 | if (this.cachedcontentnode.one('.header')) { | |
1848 | this.cachedcontentnode.one('.header').insert(this.dockitem.get('contents'), 'after'); | |
1849 | } else { | |
1850 | this.cachedcontentnode.insert(this.dockitem.get('contents')); | |
1851 | } | |
1852 | ||
1853 | this.contentplaceholder.replace(this.cachedcontentnode); | |
84192d78 | 1854 | this.cachedcontentnode = null; |
e3554c1c | 1855 | |
3a0bc0fd | 1856 | M.util.set_user_preference('docked_block_instance_' + id, 0); |
a69a7e89 | 1857 | this.set('isDocked', false); |
84192d78 SH |
1858 | return true; |
1859 | } | |
1860 | }; | |
1861 | Y.extend(BLOCK, Y.Base, BLOCK.prototype, { | |
3a0bc0fd DP |
1862 | NAME: 'moodle-core-dock-block', |
1863 | ATTRS: { | |
84192d78 SH |
1864 | /** |
1865 | * The block instance ID | |
1866 | * @attribute id | |
1867 | * @writeOnce | |
1868 | * @type Number | |
1869 | */ | |
3a0bc0fd DP |
1870 | id: { |
1871 | writeOnce: 'initOnly', | |
1872 | setter: function(value) { | |
84192d78 SH |
1873 | return parseInt(value, 10); |
1874 | } | |
1875 | }, | |
1876 | /** | |
1877 | * True if the block has been docked. | |
1878 | * @attribute isDocked | |
1879 | * @default false | |
1880 | * @type Boolean | |
1881 | */ | |
3a0bc0fd DP |
1882 | isDocked: { |
1883 | value: false | |
84192d78 SH |
1884 | } |
1885 | } | |
1886 | }); | |
ad3f8cd1 DP |
1887 | /* global LOGNS, DOCKEDITEM */ |
1888 | ||
84192d78 SH |
1889 | /** |
1890 | * Dock JS. | |
1891 | * | |
1892 | * This file contains the docked item class. | |
1893 | * | |
1894 | * @module moodle-core-dock | |
1895 | */ | |
1896 | ||
1897 | /** | |
1898 | * Docked item. | |
1899 | * | |
1900 | * @namespace M.core.dock | |
1901 | * @class DockedItem | |
1902 | * @constructor | |
1f777e5c AN |
1903 | * @extends Base |
1904 | * @uses EventTarget | |
84192d78 | 1905 | */ |
a69a7e89 | 1906 | DOCKEDITEM = function() { |
84192d78 SH |
1907 | DOCKEDITEM.superclass.constructor.apply(this, arguments); |
1908 | }; | |
1909 | DOCKEDITEM.prototype = { | |
1910 | /** | |
1911 | * Set to true if this item is currently being displayed. | |
1912 | * @property active | |
1913 | * @protected | |
1914 | * @type Boolean | |
1915 | */ | |
3a0bc0fd | 1916 | active: false, |
84192d78 SH |
1917 | /** |
1918 | * Called during the initialisation process of the object. | |
1919 | * @method initializer | |
1920 | */ | |
3a0bc0fd | 1921 | initializer: function() { |
84192d78 | 1922 | var title = this.get('title'), |
a69a7e89 SH |
1923 | titlestring, |
1924 | type; | |
84192d78 SH |
1925 | /** |
1926 | * Fired before the docked item has been drawn. | |
1927 | * @event dockeditem:drawstart | |
1928 | */ | |
3a0bc0fd | 1929 | this.publish('dockeditem:drawstart', {prefix: 'dockeditem'}); |
84192d78 SH |
1930 | /** |
1931 | * Fired after the docked item has been drawn. | |
1932 | * @event dockeditem:drawcomplete | |
1933 | */ | |
3a0bc0fd | 1934 | this.publish('dockeditem:drawcomplete', {prefix: 'dockeditem'}); |
84192d78 SH |
1935 | /** |
1936 | * Fired before the docked item is to be shown. | |
1937 | * @event dockeditem:showstart | |
1938 | */ | |
3a0bc0fd | 1939 | this.publish('dockeditem:showstart', {prefix: 'dockeditem'}); |
84192d78 SH |
1940 | /** |
1941 | * Fired after the docked item has been shown. | |
1942 | * @event dockeditem:showcomplete | |
1943 | */ | |
3a0bc0fd | 1944 | this.publish('dockeditem:showcomplete', {prefix: 'dockeditem'}); |
84192d78 SH |
1945 | /** |
1946 | * Fired before the docked item has been hidden. | |
1947 | * @event dockeditem:hidestart | |
1948 | */ | |
3a0bc0fd | 1949 | this.publish('dockeditem:hidestart', {prefix: 'dockeditem'}); |
84192d78 SH |
1950 | /** |
1951 | * Fired after the docked item has been hidden. | |
1952 | * @event dockeditem:hidecomplete | |
1953 | */ | |
3a0bc0fd | 1954 | this.publish('dockeditem:hidecomplete', {prefix: 'dockeditem'}); |
84192d78 SH |
1955 | /** |
1956 | * Fired when the docked item is removed from the dock. | |
1957 | * @event dockeditem:itemremoved | |
1958 | */ | |
3a0bc0fd | 1959 | this.publish('dockeditem:itemremoved', {prefix: 'dockeditem'}); |
84192d78 | 1960 | if (title) { |
a69a7e89 | 1961 | type = title.get('nodeName'); |
84192d78 | 1962 | titlestring = title.cloneNode(true); |
3a0bc0fd | 1963 | title = Y.Node.create('<' + type + '></' + type + '>'); |
84192d78 SH |
1964 | title = M.core.dock.fixTitleOrientation(title, titlestring.get('text')); |
1965 | this.set('title', title); | |
1966 | this.set('titlestring', titlestring); | |
1967 | } | |
3a0bc0fd | 1968 | Y.log('Initialised dockeditem for block with title "' + this._getLogDescription(), 'debug', LOGNS); |
84192d78 SH |
1969 | }, |
1970 | /** | |
1971 | * This function draws the item on the dock. | |
1972 | * @method draw | |
1973 | * @return Boolean | |
1974 | */ | |
3a0bc0fd | 1975 | draw: function() { |
84192d78 SH |
1976 | var create = Y.Node.create, |
1977 | dock = this.get('dock'), | |
1978 | count = dock.count, | |
1979 | docktitle, | |
1980 | dockitem, | |
1981 | closeicon, | |
1982 | closeiconimg, | |
1983 | id = this.get('id'); | |
1984 | ||
1985 | this.fire('dockeditem:drawstart'); | |
1986 | ||
3a0bc0fd DP |
1987 | docktitle = create('<div id="dock_item_' + id + '_title" role="menu" aria-haspopup="true" class="' |
1988 | + CSS.dockedtitle + '"></div>'); | |
84192d78 | 1989 | docktitle.append(this.get('title')); |
3a0bc0fd | 1990 | dockitem = create('<div id="dock_item_' + id + '" class="' + CSS.dockeditem + '" tabindex="0" rel="' + id + '"></div>'); |
84192d78 SH |
1991 | if (count === 1) { |
1992 | dockitem.addClass('firstdockitem'); | |
1993 | } | |
1994 | dockitem.append(docktitle); | |
1995 | dock.append(dockitem); | |
1996 | ||
557f44d9 AN |
1997 | closeiconimg = create('<img alt="' + M.util.get_string('hidepanel', 'block') + |
1998 | '" title="' + M.util.get_string('hidedockpanel', 'block') + '" />'); | |
84192d78 SH |
1999 | closeiconimg.setAttribute('src', M.util.image_url('t/dockclose', 'moodle')); |
2000 | closeicon = create('<span class="hidepanelicon" tabindex="0"></span>').append(closeiconimg); | |
2001 | closeicon.on('forceclose|click', this.hide, this); | |
3a0bc0fd | 2002 | closeicon.on('dock:actionkey', this.hide, this, {actions: {enter: true, toggle: true}}); |
84192d78 SH |
2003 | this.get('commands').append(closeicon); |
2004 | ||
2005 | this.set('dockTitleNode', docktitle); | |
2006 | this.set('dockItemNode', dockitem); | |
2007 | ||
2008 | this.fire('dockeditem:drawcomplete'); | |
2009 | return true; | |
2010 | }, | |
2011 | /** | |
2012 | * This function toggles makes the item active and shows it. | |
2013 | * @method show | |
2014 | * @return Boolean | |
2015 | */ | |
3a0bc0fd | 2016 | show: function() { |
84192d78 SH |
2017 | var dock = this.get('dock'), |
2018 | panel = dock.getPanel(), | |
2019 | docktitle = this.get('dockTitleNode'); | |
2020 | ||
2021 | dock.hideActive(); | |
2022 | this.fire('dockeditem:showstart'); | |
3a0bc0fd | 2023 | Y.log('Showing ' + this._getLogDescription(), 'debug', LOGNS); |
84192d78 | 2024 | panel.setHeader(this.get('titlestring'), this.get('commands')); |
557f44d9 AN |
2025 | panel.setBody(Y.Node.create('<div class="block_' + this.get('blockclass') + ' block_docked"></div>') |
2026 | .append(this.get('contents'))); | |
09869dd5 AO |
2027 | if (M.core.actionmenu !== undefined) { |
2028 | M.core.actionmenu.newDOMNode(panel.get('node')); | |
2029 | } | |
84192d78 SH |
2030 | panel.show(); |
2031 | panel.correctWidth(); | |
2032 | ||
2033 | this.active = true; | |
2034 | // Add active item class first up | |
2035 | docktitle.addClass(CSS.activeitem); | |
2036 | // Set aria-exapanded property to true. | |
2037 | docktitle.set('aria-expanded', "true"); | |
2038 | this.fire('dockeditem:showcomplete'); | |
2039 | dock.resize(); | |
2040 | return true; | |
2041 | }, | |
2042 | /** | |
2043 | * This function hides the item and makes it inactive. | |
2044 | * @method hide | |
2045 | */ | |
3a0bc0fd | 2046 | hide: function() { |
84192d78 | 2047 | this.fire('dockeditem:hidestart'); |
3a0bc0fd | 2048 | Y.log('Hiding "' + this._getLogDescription(), 'debug', LOGNS); |
84192d78 SH |
2049 | if (this.active) { |
2050 | // No longer active | |
2051 | this.active = false; | |
2052 | // Hide the panel | |
2053 | this.get('dock').getPanel().hide(); | |
2054 | } | |
2055 | // Remove the active class | |
2056 | // Set aria-exapanded property to false | |
2057 | this.get('dockTitleNode').removeClass(CSS.activeitem).set('aria-expanded', "false"); | |
2058 | this.fire('dockeditem:hidecomplete'); | |
2059 | }, | |
2060 | /** | |
2061 | * A toggle between calling show and hide functions based on css.activeitem | |
2062 | * Applies rules to key press events (dock:actionkey) | |
2063 | * @method toggle | |
2064 | * @param {String} action | |
2065 | */ | |
3a0bc0fd | 2066 | toggle: function(action) { |
84192d78 SH |
2067 | var docktitle = this.get('dockTitleNode'); |
2068 | if (docktitle.hasClass(CSS.activeitem) && action !== 'expand') { | |
2069 | this.hide(); | |
3a0bc0fd | 2070 | } else if (!docktitle.hasClass(CSS.activeitem) && action !== 'collapse') { |
84192d78 SH |
2071 | this.show(); |
2072 | } | |
2073 | }, | |
2074 | /** | |
2075 | * This function removes the node and destroys it's bits. | |
2076 | * @method remove. | |
2077 | */ | |
3a0bc0fd | 2078 | remove: function() { |
84192d78 SH |
2079 | this.hide(); |
2080 | // Return the block to its original position. | |
a69a7e89 | 2081 | this.get('block').returnToPage(); |
84192d78 SH |
2082 | // Remove the dock item node. |
2083 | this.get('dockItemNode').remove(); | |
2084 | this.fire('dockeditem:itemremoved'); | |
2085 | }, | |
2086 | /** | |
2087 | * Returns the description of this item to use for log calls. | |
2088 | * @method _getLogDescription | |
2089 | * @private | |
2090 | * @return {String} | |
2091 | */ | |
3a0bc0fd DP |
2092 | _getLogDescription: function() { |
2093 | return this.get('titlestring').get('innerHTML') + ' (' + this.get('blockinstanceid') + ')'; | |
84192d78 SH |
2094 | } |
2095 | }; | |
2096 | Y.extend(DOCKEDITEM, Y.Base, DOCKEDITEM.prototype, { | |
3a0bc0fd DP |
2097 | NAME: 'moodle-core-dock-dockeditem', |
2098 | ATTRS: { | |
84192d78 SH |
2099 | /** |
2100 | * The block this docked item is associated with. | |
2101 | * @attribute block | |
2102 | * @type BLOCK | |
2103 | * @writeOnce | |
2104 | * @required | |
2105 | */ | |
3a0bc0fd DP |
2106 | block: { |
2107 | writeOnce: 'initOnly' | |
84192d78 SH |
2108 | }, |
2109 | /** | |
2110 | * The dock itself. | |
2111 | * @attribute dock | |
2112 | * @type DOCK | |
2113 | * @writeOnce | |
2114 | * @required | |
2115 | */ | |
3a0bc0fd DP |
2116 | dock: { |
2117 | writeOnce: 'initOnly' | |
84192d78 SH |
2118 | }, |
2119 | /** | |
2120 | * The docked item ID. This will be given by the dock. | |
2121 | * @attribute id | |
2122 | * @type Number | |
2123 | */ | |
3a0bc0fd | 2124 | id: {}, |
84192d78 SH |
2125 | /** |
2126 | * Block instance id.Taken from the associated block. | |
2127 | * @attribute blockinstanceid | |
2128 | * @type Number | |
2129 | * @writeOnce | |
2130 | */ | |
3a0bc0fd DP |
2131 | blockinstanceid: { |
2132 | writeOnce: 'initOnly', | |
2133 | setter: function(value) { | |
84192d78 SH |
2134 | return parseInt(value, 10); |
2135 | } | |
2136 | }, | |
2137 | /** | |
2138 | * The title nodeof the docked item. | |
2139 | * @attribute title | |
2140 | * @type Node | |
2141 | * @default null | |
2142 | */ | |
3a0bc0fd DP |
2143 | title: { |
2144 | value: null | |
84192d78 SH |
2145 | }, |
2146 | /** | |
2147 | * The title string. | |
2148 | * @attribute titlestring | |
2149 | * @type String | |
2150 | */ | |
3a0bc0fd DP |
2151 | titlestring: { |
2152 | value: null | |
84192d78 SH |
2153 | }, |
2154 | /** | |
2155 | * The contents of the docked item | |
2156 | * @attribute contents | |
2157 | * @type Node | |
2158 | * @writeOnce | |
2159 | * @required | |
2160 | */ | |
3a0bc0fd DP |
2161 | contents: { |
2162 | writeOnce: 'initOnly' | |
84192d78 SH |
2163 | }, |
2164 | /** | |
2165 | * Commands associated with the block. | |
2166 | * @attribute commands | |
2167 | * @type Node | |
2168 | * @writeOnce | |
2169 | * @required | |
2170 | */ | |
3a0bc0fd DP |
2171 | commands: { |
2172 | writeOnce: 'initOnly' | |
84192d78 SH |
2173 | }, |
2174 | /** | |
2175 | * The block class. | |
2176 | * @attribute blockclass | |
2177 | * @type String | |
2178 | * @writeOnce | |
2179 | * @required | |
2180 | */ | |
3a0bc0fd DP |
2181 | blockclass: { |
2182 | writeOnce: 'initOnly' | |
84192d78 SH |
2183 | }, |
2184 | /** | |
2185 | * The title node for the docked block. | |
2186 | * @attribute dockTitleNode | |
2187 | * @type Node | |
2188 | */ | |
3a0bc0fd DP |
2189 | dockTitleNode: { |
2190 | value: null | |
84192d78 SH |
2191 | }, |
2192 | /** | |
2193 | * The item node for the docked block. | |
2194 | * @attribute dockItemNode | |
2195 | * @type Node | |
2196 | */ | |
3a0bc0fd DP |
2197 | dockItemNode: { |
2198 | value: null | |
84192d78 SH |
2199 | }, |
2200 | /** | |
2201 | * The container for the docked item (will contain the block contents when visible) | |
2202 | * @attribute dockcontainerNode | |
2203 | * @type Node | |
2204 | */ | |
3a0bc0fd DP |
2205 | dockcontainerNode: { |
2206 | value: null | |
84192d78 SH |
2207 | } |
2208 | } | |
2209 | }); | |
2210 | Y.augment(DOCKEDITEM, Y.EventTarget); | |
2211 | ||
82955480 | 2212 | |
84192d78 SH |
2213 | }, '@VERSION@', { |
2214 | "requires": [ | |
2215 | "base", | |
2216 | "node", | |
2217 | "event-custom", | |
2218 | "event-mouseenter", | |
2219 | "event-resize", | |
2220 | "escape", | |
fd5466af CB |
2221 | "moodle-core-dock-loader", |
2222 | "moodle-core-event" | |
84192d78 SH |
2223 | ] |
2224 | }); |