MDL-64506 templates: Move BS2 labels to BS4 badges
[moodle.git] / lib / yui / build / moodle-core-dock / moodle-core-dock-debug.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 {
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 */
140M.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 */
236M.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 255DOCK = function() {
84192d78
SH
256 DOCK.superclass.constructor.apply(this, arguments);
257};
258DOCK.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
1058Y.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});
1160Y.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 1180DOCKPANEL = function() {
84192d78
SH
1181 DOCKPANEL.superclass.constructor.apply(this, arguments);
1182};
1183DOCKPANEL.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};
1344Y.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});
1398Y.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 1418TABHEIGHTMANAGER = function() {
84192d78
SH
1419 TABHEIGHTMANAGER.superclass.constructor.apply(this, arguments);
1420};
1421TABHEIGHTMANAGER.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};
1498Y.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 */
1542Y.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 1659BLOCK = function() {
84192d78
SH
1660 BLOCK.superclass.constructor.apply(this, arguments);
1661};
1662BLOCK.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};
1861Y.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 1906DOCKEDITEM = function() {
84192d78
SH
1907 DOCKEDITEM.superclass.constructor.apply(this, arguments);
1908};
1909DOCKEDITEM.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};
2096Y.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});
2210Y.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});