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