1 YUI.add('moodle-core-dock', function (Y, NAME) {
6 * This file contains the DOCK object and all dock related global namespace methods and properties.
8 * @module moodle-core-dock
11 var LOGNS = 'moodle-core-dock',
12 BODY = Y.one(Y.config.doc.body),
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
23 dockonload: 'dock_on_load'
26 dockableblock: '.block[data-instanceid][data-dockable]',
27 blockmoveto: '.block[data-instanceid][data-dockable] .moveto',
28 panelmoveto: '#dockeditempanel .commands a.moveto',
29 dockonload: '.block.' + CSS.dockonload,
30 blockregion: '[data-blockregion]'
36 DOCKEDITEM; // eslint-disable-line no-unused-vars
38 M.core = M.core || {};
39 M.core.dock = M.core.dock || {};
42 * The dock - once initialised.
48 M.core.dock._dock = null;
51 * An associative array of dockable blocks.
52 * @property _dockableblocks
53 * @type {Array} An array of BLOCK objects organised by instanceid.
56 M.core.dock._dockableblocks = {};
59 * Initialises the dock.
60 * This method registers dockable blocks, and creates delegations to dock them.
64 M.core.dock.init = function() {
65 Y.all(SELECTOR.dockableblock).each(M.core.dock.registerDockableBlock);
66 Y.Global.on(M.core.globalEvents.BLOCK_CONTENT_UPDATED, function(e) {
67 M.core.dock.notifyBlockChange(e.instanceid);
69 BODY.delegate('click', M.core.dock.dockBlock, SELECTOR.blockmoveto);
70 BODY.delegate('key', M.core.dock.dockBlock, SELECTOR.blockmoveto, 'enter');
74 * Returns an instance of the dock.
75 * Initialises one if one hasn't already being initialised.
81 M.core.dock.get = function() {
82 if (this._dock === null) {
83 this._dock = new DOCK();
89 * Registers a dockable block with the dock.
92 * @method registerDockableBlock
93 * @param {int} id The block instance ID.
96 M.core.dock.registerDockableBlock = function(id) {
97 if (typeof id === 'object' && typeof id.getData === 'function') {
98 id = id.getData('instanceid');
100 M.core.dock._dockableblocks[id] = new BLOCK({id: id});
104 * Docks a block given either its instanceid, its node, or an event fired from within the block.
106 * @method dockBlockByInstanceID
110 M.core.dock.dockBlock = function(id) {
111 if (typeof id === 'object' && id.target !== 'undefined') {
114 if (typeof id === "object") {
115 if (!id.test(SELECTOR.dockableblock)) {
116 id = id.ancestor(SELECTOR.dockableblock);
118 if (typeof id === 'object' && typeof id.getData === 'function' && !id.ancestor('.' + CSS.dock)) {
119 id = id.getData('instanceid');
124 var block = M.core.dock._dockableblocks[id];
131 * Fixes the title orientation. Rotating it if required.
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.
139 M.core.dock.fixTitleOrientation = function(title, text) {
140 var dock = M.core.dock.get(),
142 transform = 'rotate(270deg)',
147 verticaldirection = M.util.get_string('thisdirectionvertical', 'langconfig');
148 title = Y.one(title);
150 if (dock.get('orientation') !== 'vertical') {
151 // If the dock isn't vertical don't adjust it!
152 title.set('innerHTML', text);
156 if (Y.UA.ie > 0 && Y.UA.ie < 8) {
157 // IE 6/7 can't rotate text so force ver
158 verticaldirection = 'ver';
161 switch (verticaldirection) {
164 return title.set('innerHTML', text.split('').join('<br />'));
166 transform = 'rotate(90deg)';
169 // Nothing to do here. transform default is good.
174 // IE8 can flip the text via CSS but not handle transform. IE9+ can handle the CSS3 transform attribute.
175 title.set('innerHTML', text);
176 title.setAttribute('style', 'writing-mode: tb-rl; filter: flipV flipH;display:inline;');
177 title.addClass('filterrotate');
181 // We need to fix a font-size - sorry theme designers.
182 test = Y.Node.create('<h2 class="transform-test-heading"><span class="transform-test-node" style="font-size:' +
183 fontsize + ';">' + text + '</span></h2>');
184 BODY.insert(test, 0);
185 width = test.one('span').get('offsetWidth') * 1.2;
186 height = test.one('span').get('offsetHeight');
189 title.set('innerHTML', text);
190 title.addClass('css3transform');
192 // Move the title into position
194 'position': 'relative',
195 'fontSize': fontsize,
197 'top': (width - height) / 2
200 // Positioning is different when in RTL mode.
201 if (window.right_to_left()) {
202 title.setStyle('left', width / 2 - height);
204 title.setStyle('right', width / 2 - height);
209 'transform': transform,
210 '-ms-transform': transform,
211 '-moz-transform': transform,
212 '-webkit-transform': transform,
213 '-o-transform': transform
216 container = Y.Node.create('<div></div>');
217 container.append(title);
218 container.setStyles({
219 height: width + (width / 4),
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.
231 * @method notifyBlockChange
232 * @param {Number} instanceid
235 M.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();
248 * @namespace M.core.dock
255 DOCK.superclass.constructor.apply(this, arguments);
259 * Tab height manager used to ensure tabs are always visible.
261 * @property tabheightmanager
262 * @type TABHEIGHTMANAGER
264 tabheightmanager: null,
266 * Will be an eventtype if there is an eventype to prevent.
268 * @property preventevent
273 * Will be an object if there is a delayed event in effect.
275 * @property delayedevent
280 * An array of currently docked items.
282 * @property dockeditems
287 * Set to true once the dock has been drawn.
289 * @property dockdrawn
294 * The number of blocks that are currently docked.
301 * The total number of blocks that have been docked.
303 * @property totalcount
308 * A hidden node used as a holding area for DOM objects used by blocks that have been docked.
310 * @property holdingareanode
313 holdingareanode: null,
315 * Called during the initialisation process of the object.
316 * @method initializer
318 initializer: function() {
320 // Publish the events the dock has
322 * Fired when the dock first starts initialising.
323 * @event dock:starting
325 this.publish('dock:starting', {prefix: 'dock', broadcast: 2, emitFacade: true, fireOnce: true});
327 * Fired after the dock is initialised for the first time.
328 * @event dock:initialised
330 this.publish('dock:initialised', {prefix: 'dock', broadcast: 2, emitFacade: true, fireOnce: true});
332 * Fired before the dock structure and content is first created.
333 * @event dock:beforedraw
335 this.publish('dock:beforedraw', {prefix: 'dock', fireOnce: true});
337 * Fired before the dock is changed from hidden to visible.
338 * @event dock:beforeshow
340 this.publish('dock:beforeshow', {prefix: 'dock'});
342 * Fires after the dock has been changed from hidden to visible.
345 this.publish('dock:shown', {prefix: 'dock', broadcast: 2});
347 * Fired after the dock has been changed from visible to hidden.
350 this.publish('dock:hidden', {prefix: 'dock', broadcast: 2});
352 * Fires when an item is added to the dock.
353 * @event dock:itemadded
355 this.publish('dock:itemadded', {prefix: 'dock'});
357 * Fires when an item is removed from the dock.
358 * @event dock:itemremoved
360 this.publish('dock:itemremoved', {prefix: 'dock'});
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
366 this.publish('dock:itemschanged', {prefix: 'dock', broadcast: 2});
368 * Fires once when the docks panel is first initialised.
369 * @event dock:panelgenerated
371 this.publish('dock:panelgenerated', {prefix: 'dock', fireOnce: true});
373 * Fires when the dock panel is about to be resized.
374 * @event dock:panelresizestart
376 this.publish('dock:panelresizestart', {prefix: 'dock'});
378 * Fires after the dock panel has been resized.
379 * @event dock:resizepanelcomplete
381 this.publish('dock:resizepanelcomplete', {prefix: 'dock'});
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');
392 * Ensures that the dock has been drawn.
394 * @method _ensureDockDrawn
397 _ensureDockDrawn: function() {
398 if (this.dockdrawn === true) {
401 var dock = this._initialiseDockNode(),
403 cssselector: '.' + CSS.dockedtitle,
407 cssselector: '.' + CSS.dockedtitle,
410 preventevent: 'click',
413 if (Y.UA.ie > 0 && Y.UA.ie < 7) {
414 // Adjust for IE 6 (can't handle fixed pos)
415 dock.setStyle('height', dock.get('winHeight') + 'px');
418 this.fire('dock:beforedraw');
420 this._initialiseDockControls();
422 this.tabheightmanager = new TABHEIGHTMANAGER({dock: this});
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
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});
431 Y.delegate('click', this.handleReturnToBlock, this.get('dockNode'), SELECTOR.panelmoveto, this);
432 Y.delegate('dock:actionkey', this.handleDockedItemEvent, this.get('dockNode'), '.' + CSS.dockeditem, this);
434 BODY.on('click', this.handleEvent, this, {cssselector: 'body', delay: 0});
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;
442 * Handles an actionkey event on the dock.
443 * @param {EventFacade} e
444 * @method handleDockedItemEvent
447 handleDockedItemEvent: function(e) {
448 if (e.type !== 'dock:actionkey') {
451 var target = e.target,
452 dockeditem = '.' + CSS.dockeditem;
453 if (!target.test(dockeditem)) {
454 target = target.ancestor(dockeditem);
460 this.dockeditems[target.getAttribute('rel')].toggle(e.action);
463 * Call the theme customisation method "customise_dock_for_theme" if it exists.
465 * @method _applyThemeCustomisation
467 _applyThemeCustomisation: function() {
468 // Check if there is a customisation function
469 if (typeof (customise_dock_for_theme) === 'function') {
470 // First up pre the legacy object.
476 spacebeforefirstitem: null,
484 buttonscontainer: null,
486 dockeditemcontainer: null,
491 // Run the customisation function
492 window.customise_dock_for_theme(this);
493 } catch (exception) {
494 // Do nothing at the moment.
496 // Now to work out what they did.
500 buffer: 'bufferPanel',
501 orientation: 'orientation',
502 position: 'position',
503 spacebeforefirstitem: 'bufferBeforeFirstItem',
504 removeallicon: 'undockAllIconUrl'
506 // Check for and apply any legacy configuration.
507 for (key in M.core_dock.cfg) {
508 if (Y.Lang.isString(key) && cfgmap[key]) {
509 value = M.core_dock.cfg[key];
510 if (value === null) {
516 // Damn, the've set something.
517 this.set(cfgmap[key], value);
520 // Check for and apply any legacy CSS changes..
521 for (key in M.core_dock.css) {
522 if (Y.Lang.isString(key)) {
523 value = M.core_dock.css[key];
524 if (value === null) {
530 // Damn, they've set something.
537 * Initialises the dock node, creating it and its content if required.
540 * @method _initialiseDockNode
541 * @return {Node} The dockNode
543 _initialiseDockNode: function() {
544 var dock = this.get('dockNode'),
545 positionorientationclass = CSS.dock + '_' + this.get('position') + '_' + this.get('orientation'),
546 holdingarea = Y.Node.create('<div></div>').setStyles({display: 'none'}),
547 buttons = this.get('buttonsNode'),
548 container = this.get('itemContainerNode');
551 dock = Y.one('#' + CSS.dock);
554 dock = Y.Node.create('<div id="' + CSS.dock + '"></div>');
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');
562 positionorientationclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation');
563 BODY.addClass(CSS.body).addClass();
567 buttons = dock.one('.' + CSS.buttonscontainer);
570 buttons = Y.Node.create('<div class="' + CSS.buttonscontainer + '"></div>');
571 dock.append(buttons);
575 container = dock.one('.' + CSS.dockeditemcontainer);
578 container = Y.Node.create('<div class="' + CSS.dockeditemcontainer + '"></div>');
579 buttons.append(container);
582 BODY.append(holdingarea);
583 this.holdingareanode = holdingarea;
585 this.set('dockNode', dock);
586 this.set('buttonsNode', buttons);
587 this.set('itemContainerNode', container);
592 * Initialises the dock controls.
595 * @method _initialiseDockControls
597 _initialiseDockControls: function() {
598 // Add a removeall button
599 // Must set the image src seperatly of we get an error with XML strict headers
601 var removeall = Y.Node.create('<img alt="' + M.util.get_string('undockall', 'block') + '" tabindex="0" />');
602 removeall.setAttribute('src', this.get('undockAllIconUrl'));
603 removeall.on('removeall|click', this.removeAll, this);
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));
608 * Returns the dock panel. Initialising it if it hasn't already been initialised.
610 * @return {DOCKPANEL}
612 getPanel: function() {
613 var panel = this.get('panel');
615 panel = new DOCKPANEL({dock: this});
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');
625 * Resizes the dock panel if required.
626 * @method resizePanelIfRequired
628 resizePanelIfRequired: function() {
630 var panel = this.get('panel');
632 panel.correctWidth();
636 * Handles a dock event sending it to the right place.
638 * @method handleEvent
639 * @param {EventFacade} e
640 * @param {Object} options
643 handleEvent: function(e, options) {
644 var item = this.getActiveItem(),
647 regex = /^dock_item_(\d+)_title$/,
649 if (options.cssselector === 'body') {
650 if (!this.get('dockNode').contains(e.target)) {
656 if (e.target.test(options.cssselector)) {
659 target = e.target.ancestor(options.cssselector);
664 if (this.preventevent !== null && e.type === this.preventevent) {
667 if (options.preventevent) {
668 this.preventevent = options.preventevent;
669 if (options.preventdelay) {
670 setTimeout(function() {
671 self.preventevent = null;
672 }, options.preventdelay * 1000);
675 if (this.delayedevent && this.delayedevent.timeout) {
676 clearTimeout(this.delayedevent.timeout);
677 this.delayedevent.event.detach();
678 this.delayedevent = null;
680 if (options.delay > 0) {
681 return this.delayEvent(e, options, target);
683 targetid = target.get('id');
684 if (targetid.match(regex)) {
685 item = this.dockeditems[targetid.replace(regex, '$1')];
701 * @param {EventFacade} event
702 * @param {Object} options
703 * @param {Node} target
706 delayEvent: function(event, options, target) {
708 self.delayedevent = (function() {
711 event: BODY.on('mousemove', function(e) {
712 self.delayedevent.target = e.target;
717 self.delayedevent.timeout = setTimeout(function() {
718 self.delayedevent.timeout = null;
719 self.delayedevent.event.detach();
720 if (options.iscontained === self.get('dockNode').contains(self.delayedevent.target)) {
721 self.handleEvent(event, {cssselector: options.cssselector, delay: 0, iscontained: options.iscontained});
723 }, options.delay * 1000);
727 * Resizes block spaces.
728 * @method resizeBlockSpace
730 resizeBlockSpace: function() {
731 if (Y.all(SELECTOR.dockonload).size() > 0) {
732 // Do not resize during initial load
736 var populatedRegionCount = 0,
737 populatedBlockRegions = [],
738 unpopulatedBlockRegions = [],
740 populatedLegacyRegions = [],
741 containsLegacyRegions = false,
743 classesToRemove = [];
745 // First look for understood regions.
746 Y.all(SELECTOR.blockregion).each(function(region) {
747 var regionname = region.getData('blockregion');
748 if (region.all('.block').size() > 0) {
749 populatedBlockRegions.push(regionname);
750 populatedRegionCount++;
751 } else if (region.all('.block_dock_placeholder').size() > 0) {
752 unpopulatedBlockRegions.push(regionname);
756 // Next check for legacy regions.
757 Y.all('.block-region').each(function(region) {
758 if (region.test(SELECTOR.blockregion)) {
759 // This is a new region, we've already processed it.
763 // Sigh - there are legacy regions.
764 containsLegacyRegions = true;
766 var regionname = region.get('id').replace(/^region\-/, 'side-'),
767 hasblocks = (region.all('.block').size() > 0);
770 populatedLegacyRegions.push(regionname);
771 populatedRegionCount++;
773 // This legacy region has no blocks so cannot have the -only body tag.
774 classesToRemove.push(
780 if (BODY.hasClass('blocks-moving')) {
781 // When we're moving blocks, we do not want to collapse.
785 Y.each(unpopulatedBlockRegions, function(regionname) {
787 // This block region is empty.
788 'empty-region-' + regionname,
790 // Which has the same effect as being docked.
791 'docked-region-' + regionname
793 classesToRemove.push(
794 // It is no-longer used.
795 'used-region-' + regionname,
797 // It cannot be the only region on screen if it is empty.
802 Y.each(populatedBlockRegions, function(regionname) {
804 // This block region is in use.
805 'used-region-' + regionname
807 classesToRemove.push(
809 'empty-region-' + regionname,
812 'docked-region-' + regionname
815 if (populatedRegionCount === 1 && isMoving === false) {
816 // There was only one populated region, and we are not moving blocks.
817 classesToAdd.push(regionname + '-only');
819 // There were multiple block regions visible - remove any 'only' classes.
820 classesToRemove.push(regionname + '-only');
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');
831 Y.each(populatedLegacyRegions, function(regionname) {
832 classesToAdd.push(regionname + '-only');
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');
843 // Otherwise remove it.
844 classesToRemove.push('content-only');
848 // Modify the body clases.
849 Y.each(classesToRemove, function(className) {
850 BODY.removeClass(className);
852 Y.each(classesToAdd, function(className) {
853 BODY.addClass(className);
857 * Adds an item to the dock.
859 * @param {DOCKEDITEM} item
861 add: function(item) {
862 // Set the dockitem id to the total count and then increment it.
863 item.set('id', 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);
872 * Appends an item to the dock (putting it in the item container.
874 * @param {Node} docknode
876 append: function(docknode) {
877 this.get('itemContainerNode').append(docknode);
880 * Handles events that require a docked block to be returned to the page./
881 * @method handleReturnToBlock
882 * @param {EventFacade} e
884 handleReturnToBlock: function(e) {
886 this.remove(this.getActiveItem().get('id'));
889 * Removes a docked item from the dock.
891 * @param {Number} id The docked item id.
894 remove: function(id) {
895 if (!this.dockeditems[id]) {
898 this.dockeditems[id].remove();
899 delete this.dockeditems[id];
901 this.fire('dock:itemremoved', id);
902 this.fire('dock:itemschanged', id);
906 * Ensures the the first item in the dock has the correct class.
907 * @method resetFirstItem
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');
916 * Removes all docked blocks returning them to the page.
920 removeAll: function() {
922 for (i in this.dockeditems) {
923 if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) {
930 * Hides the active item.
933 hideActive: function() {
934 var item = this.getActiveItem();
940 * Checks wether the dock should be shown or hidden
941 * @method checkDockVisibility
943 checkDockVisibility: function() {
944 var bodyclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation');
946 this.get('dockNode').addClass('nothingdocked');
947 BODY.removeClass(CSS.body).removeClass();
948 this.fire('dock:hidden');
950 this.fire('dock:beforeshow');
951 this.get('dockNode').removeClass('nothingdocked');
952 BODY.addClass(CSS.body).addClass(bodyclass);
953 this.fire('dock:shown');
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.
963 var panel = this.getPanel(),
964 item = this.getActiveItem(),
975 if (!panel.get('visible') || !item) {
979 this.fire('dock:panelresizestart');
980 if (this.get('orientation') === 'vertical') {
981 buffer = this.get('bufferPanel');
982 screenh = parseInt(BODY.get('winHeight'), 10) - (buffer * 2);
983 docky = this.get('dockNode').getY();
984 titletop = item.get('dockTitleNode').getY() - docky - buffer;
985 containery = this.get('itemContainerNode').getY();
986 containerheight = containery - docky + this.get('buttonsNode').get('offsetHeight');
987 scrolltop = panel.get('bodyNode').get('scrollTop');
988 panel.get('bodyNode').setStyle('height', 'auto');
989 panel.get('node').removeClass('oversized_content');
990 panelheight = panel.get('node').get('offsetHeight');
992 if (Y.UA.ie > 0 && Y.UA.ie < 7) {
993 panel.setTop(item.get('dockTitleNode').getY());
994 } else if (panelheight > screenh) {
995 panel.setTop(buffer - containerheight);
996 panel.get('bodyNode').setStyle('height', (screenh - panel.get('headerNode').get('offsetHeight')) + 'px');
997 panel.get('node').addClass('oversized_content');
998 } else if (panelheight > (screenh - (titletop - buffer))) {
999 panel.setTop(titletop - containerheight - (panelheight - (screenh - titletop)) + buffer);
1001 panel.setTop(titletop - containerheight + buffer);
1005 panel.get('bodyNode').set('scrollTop', scrolltop);
1009 if (this.get('position') === 'right') {
1010 panel.get('node').setStyle('left', '-' + panel.get('node').get('offsetWidth') + 'px');
1012 } else if (this.get('position') === 'top') {
1013 dockx = this.get('dockNode').getX();
1014 titleleft = item.get('dockTitleNode').getX() - dockx;
1015 panel.get('node').setStyle('left', titleleft + 'px');
1018 this.fire('dock:resizepanelcomplete');
1022 * Returns the currently active dock item or false
1023 * @method getActiveItem
1024 * @return {DOCKEDITEM}
1026 getActiveItem: function() {
1028 for (i in this.dockeditems) {
1029 if (this.dockeditems[i].active) {
1030 return this.dockeditems[i];
1036 * Adds an item to the holding area.
1037 * @method addToHoldingArea
1038 * @param {Node} node
1040 addToHoldingArea: function(node) {
1041 this.holdingareanode.append(node);
1045 Y.extend(DOCK, Y.Base, DOCK.prototype, {
1046 NAME: 'moodle-core-dock',
1049 * The dock itself. #dock.
1050 * @attribute dockNode
1067 * A container within the dock used for buttons.
1068 * @attribute buttonsNode
1076 * A container within the dock used for docked blocks.
1077 * @attribute itemContainerNode
1081 itemContainerNode: {
1086 * Buffer used when containing a panel.
1087 * @attribute bufferPanel
1093 validator: Y.Lang.isNumber
1097 * Position of the dock.
1098 * @attribute position
1104 validator: Y.Lang.isString
1108 * vertical || horizontal determines if we change the title
1109 * @attribute orientation
1115 validator: Y.Lang.isString,
1116 setter: function(value) {
1117 if (value.match(/^vertical$/i)) {
1120 return 'horizontal';
1125 * Space between the top of the dock and the first item.
1126 * @attribute bufferBeforeFirstItem
1130 bufferBeforeFirstItem: {
1132 validator: Y.Lang.isNumber
1136 * Icon URL for the icon to undock all blocks
1137 * @attribute undockAllIconUrl
1139 * @default t/dock_to_block
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
1147 Y.augment(DOCK, Y.EventTarget);
1148 /* global DOCKPANEL, LOGNS */
1153 * This file contains the panel class used by the dock to display the content of docked blocks.
1155 * @module moodle-core-dock
1161 * @namespace M.core.dock
1167 DOCKPANEL = function() {
1168 DOCKPANEL.superclass.constructor.apply(this, arguments);
1170 DOCKPANEL.prototype = {
1172 * True once the panel has been created.
1179 * Called during the initialisation process of the object.
1180 * @method initializer
1182 initializer: function() {
1184 * Fired before the panel is shown.
1185 * @event dockpane::beforeshow
1187 this.publish('dockpanel:beforeshow', {prefix: 'dockpanel'});
1189 * Fired after the panel is shown.
1190 * @event dockpanel:shown
1192 this.publish('dockpanel:shown', {prefix: 'dockpanel'});
1194 * Fired before the panel is hidden.
1195 * @event dockpane::beforehide
1197 this.publish('dockpanel:beforehide', {prefix: 'dockpanel'});
1199 * Fired after the panel is hidden.
1200 * @event dockpanel:hidden
1202 this.publish('dockpanel:hidden', {prefix: 'dockpanel'});
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
1208 this.publish('dockpanel:visiblechange', {prefix: 'dockpanel'});
1211 * Creates the Panel if it has not already been created.
1215 create: function() {
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>'));
1227 this.get('node').append(this.get('contentNode').append(this.get('headerNode')).append(this.get('bodyNode')))
1231 * Displays the panel.
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');
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');
1254 * Sets the panel header.
1256 * @param {Node|String} content
1258 setHeader: function(content) {
1260 var header = this.get('headerNode'),
1262 header.setContent(content);
1263 if (arguments.length > 1) {
1264 for (i = 1; i < arguments.length; i++) {
1265 if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) {
1266 header.append(arguments[i]);
1272 * Sets the panel body.
1274 * @param {Node|String} content
1276 setBody: function(content) {
1278 this.get('bodyNode').setContent(content);
1281 * Sets the new top mark of the panel.
1284 * @param {Number} newtop
1286 setTop: function(newtop) {
1287 if (Y.UA.ie > 0 && Y.UA.ie < 7) {
1288 this.get('node').setY(newtop);
1290 this.get('node').setStyle('top', newtop.toString() + 'px');
1294 * Corrects the width of the panel.
1295 * @method correctWidth
1297 correctWidth: function() {
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.
1307 // The max width (80% of screen).
1308 maxWidth = Math.round(bodyNode.get('winWidth') * 0.8);
1310 // If the scrollable width is more than the visible width
1311 if (scroll > width) {
1314 // + any rendering difference (borders, padding)
1315 // + 10px to make it look nice.
1316 newWidth = width + (scroll - width) + ((offsetWidth - width) * 2) + 10;
1319 // Make sure its not more then the maxwidth
1320 if (newWidth > maxWidth) {
1321 newWidth = maxWidth;
1324 // Set the new width if its more than the old width.
1325 if (newWidth > offsetWidth) {
1326 this.get('node').setStyle('width', newWidth + 'px');
1330 Y.extend(DOCKPANEL, Y.Base, DOCKPANEL.prototype, {
1331 NAME: 'moodle-core-dock-panel',
1340 writeOnce: 'initOnly'
1343 * The node that contains the whole panel.
1351 * The node that contains the header, body and footer.
1352 * @attribute contentNode
1359 * The node that contains the header
1360 * @attribute headerNode
1367 * The node that contains the body
1368 * @attribute bodyNode
1375 * True if the panel is currently visible.
1376 * @attribute visible
1384 Y.augment(DOCKPANEL, Y.EventTarget);
1385 /* global TABHEIGHTMANAGER, LOGNS */
1390 * This file contains the tab height manager.
1391 * The tab height manager is responsible for ensure all tabs are visible all the time.
1393 * @module moodle-core-dock
1397 * Tab height manager.
1399 * @namespace M.core.dock
1400 * @class TabHeightManager
1404 TABHEIGHTMANAGER = function() {
1405 TABHEIGHTMANAGER.superclass.constructor.apply(this, arguments);
1407 TABHEIGHTMANAGER.prototype = {
1409 * Initialises the dock sizer which then attaches itself to the required
1410 * events in order to monitor the dock
1411 * @method initializer
1413 initializer: function() {
1414 var dock = this.get('dock');
1415 dock.on('dock:itemschanged', this.checkSizing, this);
1416 Y.on('windowresize', this.checkSizing, this);
1419 * Check if the size dock items needs to be adjusted
1420 * @method checkSizing
1422 checkSizing: function() {
1423 var dock = this.get('dock'),
1424 node = dock.get('dockNode'),
1425 items = dock.dockeditems,
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),
1430 possibleheight = dockheight - controlheight - buffer - (items.length * 2),
1433 if (items.length > 0) {
1435 if (Y.Lang.isNumber(id) || Y.Lang.isString(id)) {
1436 dockedtitle = Y.one(items[id].get('title')).ancestor('.' + CSS.dockedtitle);
1438 if (this.get('enabled')) {
1439 dockedtitle.setStyle('height', 'auto');
1441 totalheight += dockedtitle.get('offsetHeight') || 0;
1445 if (totalheight > possibleheight) {
1446 this.enable(possibleheight);
1451 * Enables the dock sizer and resizes where required.
1453 * @param {Number} possibleheight
1455 enable: function(possibleheight) {
1456 var dock = this.get('dock'),
1457 items = dock.dockeditems,
1461 id, itemtitle, itemheight, offsetheight;
1462 this.set('enabled', true);
1464 if (Y.Lang.isNumber(id) || Y.Lang.isString(id)) {
1465 itemtitle = Y.one(items[id].get('title')).ancestor('.' + CSS.dockedtitle);
1469 itemheight = Math.floor((possibleheight - usedheight) / (count - runningcount));
1470 offsetheight = itemtitle.get('offsetHeight');
1471 itemtitle.setStyle('overflow', 'hidden');
1472 if (offsetheight > itemheight) {
1473 itemtitle.setStyle('height', itemheight + 'px');
1474 usedheight += itemheight;
1476 usedheight += offsetheight;
1483 Y.extend(TABHEIGHTMANAGER, Y.Base, TABHEIGHTMANAGER.prototype, {
1484 NAME: 'moodle-core-tabheightmanager',
1493 writeOnce: 'initOnly'
1496 * True if the item_sizer is being used, false otherwise.
1497 * @attribute enabled
1508 * This file contains the action key event definition that is used for accessibility handling within the Dock.
1510 * @module moodle-core-dock
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.
1519 * This event is subscribed to by dockitems.
1520 * The on() method to subscribe allows specifying the desired trigger actions as JSON.
1522 * This event can also be delegated if needed.
1524 * @namespace M.core.dock
1527 Y.Event.define("dock:actionkey", {
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',
1535 * The keys to trigger on.
1542 // (@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
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
1554 _keyHandler: function(e, notifier, args) {
1556 if (!args.actions) {
1557 actObj = {collapse: true, expand: true, toggle: true, enter: true};
1559 actObj = args.actions;
1561 if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) {
1562 e.action = this._keys[e.keyCode];
1568 * Subscribes to events.
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
1574 on: function(node, sub, notifier) {
1575 // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
1576 if (sub.args === null) {
1578 sub._detacher = node.on(this._event, this._keyHandler, this, notifier, {actions: false});
1580 sub._detacher = node.on(this._event, this._keyHandler, this, notifier, sub.args[0]);
1585 * Detaches an event listener
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
1591 detach: function(node, sub) {
1592 // detach our _detacher handle of the subscription made in on()
1593 sub._detacher.detach();
1597 * Creates a delegated event listener.
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.
1604 delegate: function(node, sub, notifier, filter) {
1605 // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
1606 if (sub.args === null) {
1608 sub._delegateDetacher = node.delegate(this._event, this._keyHandler, filter, this, notifier, {actions: false});
1610 sub._delegateDetacher = node.delegate(this._event, this._keyHandler, filter, this, notifier, sub.args[0]);
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.
1622 detachDelegate: function(node, sub) {
1623 sub._delegateDetacher.detach();
1626 /* global BLOCK, LOGNS, DOCKEDITEM */
1631 * This file contains the block class used to manage blocks (both docked and not) for the dock.
1633 * @module moodle-core-dock
1639 * @namespace M.core.dock
1644 BLOCK = function() {
1645 BLOCK.superclass.constructor.apply(this, arguments);
1649 * A content place holder used when the block has been docked.
1650 * @property contentplaceholder
1654 contentplaceholder: null,
1656 * The skip link associated with this block.
1657 * @property contentskipanchor
1661 contentskipanchor: null,
1663 * The cached content node for the actual block
1664 * @property cachedcontentnode
1668 cachedcontentnode: null,
1670 * If true the user preference isn't updated
1671 * @property skipsetposition
1675 skipsetposition: true,
1677 * The dock item associated with this block
1678 * @property dockitem
1684 * Called during the initialisation process of the object.
1685 * @method initializer
1687 initializer: function() {
1688 var node = Y.one('#inst' + this.get('id'));
1694 M.core.dock.ensureMoveToIconExists(node);
1696 // Move the block straight to the dock if required
1697 if (node.hasClass(CSS.dockonload)) {
1698 node.removeClass(CSS.dockonload);
1701 this.skipsetposition = false;
1705 * Returns the class associated with this block.
1706 * @method _getBlockClass
1708 * @param {Node} node
1711 _getBlockClass: function(node) {
1712 var block = node.getData('block'),
1715 if (Y.Lang.isString(block) && block !== '') {
1718 classes = node.getAttribute('className').toString();
1719 matches = /(^| )block_([^ ]+)/.exec(classes);
1727 * This function is responsible for moving a block from the page structure onto the dock.
1728 * @method moveToDock
1729 * @param {EventFacade} e
1731 moveToDock: function(e) {
1736 var dock = M.core.dock.get(),
1737 id = this.get('id'),
1738 blockcontent = Y.one('#inst' + id).one('.content'),
1739 icon = (window.right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block',
1740 breakchar = (location.href.match(/\?/)) ? '&' : '?',
1746 if (!blockcontent) {
1751 this.recordBlockState();
1753 blocktitle = this.cachedcontentnode.one('.title h2').cloneNode(true);
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);
1761 blockcommands = Y.Node.create('<div class="commands"></div>');
1763 movetoimg = Y.Node.create('<img />').setAttrs({
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')
1768 moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>').setAttrs({
1769 href: Y.config.win.location.href + breakchar + 'dock=' + id
1771 moveto.append(movetoimg);
1772 blockcommands.append(moveto.append(movetoimg));
1774 // Create a new dock item for the block
1775 this.dockitem = new DOCKEDITEM({
1778 blockinstanceid: id,
1780 contents: blockcontent,
1781 commands: blockcommands,
1782 blockclass: this._getBlockClass(Y.one('#inst' + id))
1784 // Register an event so that when it is removed we can put it back as a block
1785 dock.add(this.dockitem);
1787 if (!this.skipsetposition) {
1788 // save the users preference
1789 M.util.set_user_preference('docked_block_instance_' + id, 1);
1792 this.set('isDocked', true);
1795 * Records the block state and adds it to the docks holding area.
1796 * @method recordBlockState
1798 recordBlockState: function() {
1799 var id = this.get('id'),
1800 dock = M.core.dock.get(),
1801 node = Y.one('#inst' + id),
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();
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);
1816 * This function removes a block from the dock and puts it back into the page structure.
1817 * @method returnToPage
1820 returnToPage: function() {
1821 var id = this.get('id');
1824 // Enable the skip anchor when going back to block mode
1825 if (this.contentskipanchor) {
1826 this.contentskipanchor.show();
1829 if (this.cachedcontentnode.one('.header')) {
1830 this.cachedcontentnode.one('.header').insert(this.dockitem.get('contents'), 'after');
1832 this.cachedcontentnode.insert(this.dockitem.get('contents'));
1835 this.contentplaceholder.replace(this.cachedcontentnode);
1836 this.cachedcontentnode = null;
1838 M.util.set_user_preference('docked_block_instance_' + id, 0);
1839 this.set('isDocked', false);
1843 Y.extend(BLOCK, Y.Base, BLOCK.prototype, {
1844 NAME: 'moodle-core-dock-block',
1847 * The block instance ID
1853 writeOnce: 'initOnly',
1854 setter: function(value) {
1855 return parseInt(value, 10);
1859 * True if the block has been docked.
1860 * @attribute isDocked
1869 /* global LOGNS, DOCKEDITEM */
1874 * This file contains the docked item class.
1876 * @module moodle-core-dock
1882 * @namespace M.core.dock
1888 DOCKEDITEM = function() {
1889 DOCKEDITEM.superclass.constructor.apply(this, arguments);
1891 DOCKEDITEM.prototype = {
1893 * Set to true if this item is currently being displayed.
1900 * Called during the initialisation process of the object.
1901 * @method initializer
1903 initializer: function() {
1904 var title = this.get('title'),
1908 * Fired before the docked item has been drawn.
1909 * @event dockeditem:drawstart
1911 this.publish('dockeditem:drawstart', {prefix: 'dockeditem'});
1913 * Fired after the docked item has been drawn.
1914 * @event dockeditem:drawcomplete
1916 this.publish('dockeditem:drawcomplete', {prefix: 'dockeditem'});
1918 * Fired before the docked item is to be shown.
1919 * @event dockeditem:showstart
1921 this.publish('dockeditem:showstart', {prefix: 'dockeditem'});
1923 * Fired after the docked item has been shown.
1924 * @event dockeditem:showcomplete
1926 this.publish('dockeditem:showcomplete', {prefix: 'dockeditem'});
1928 * Fired before the docked item has been hidden.
1929 * @event dockeditem:hidestart
1931 this.publish('dockeditem:hidestart', {prefix: 'dockeditem'});
1933 * Fired after the docked item has been hidden.
1934 * @event dockeditem:hidecomplete
1936 this.publish('dockeditem:hidecomplete', {prefix: 'dockeditem'});
1938 * Fired when the docked item is removed from the dock.
1939 * @event dockeditem:itemremoved
1941 this.publish('dockeditem:itemremoved', {prefix: 'dockeditem'});
1943 type = title.get('nodeName');
1944 titlestring = title.cloneNode(true);
1945 title = Y.Node.create('<' + type + '></' + type + '>');
1946 title = M.core.dock.fixTitleOrientation(title, titlestring.get('text'));
1947 this.set('title', title);
1948 this.set('titlestring', titlestring);
1952 * This function draws the item on the dock.
1957 var create = Y.Node.create,
1958 dock = this.get('dock'),
1964 id = this.get('id');
1966 this.fire('dockeditem:drawstart');
1968 docktitle = create('<div id="dock_item_' + id + '_title" role="menu" aria-haspopup="true" class="'
1969 + CSS.dockedtitle + '"></div>');
1970 docktitle.append(this.get('title'));
1971 dockitem = create('<div id="dock_item_' + id + '" class="' + CSS.dockeditem + '" tabindex="0" rel="' + id + '"></div>');
1973 dockitem.addClass('firstdockitem');
1975 dockitem.append(docktitle);
1976 dock.append(dockitem);
1978 closeiconimg = create('<img alt="' + M.util.get_string('hidepanel', 'block') +
1979 '" title="' + M.util.get_string('hidedockpanel', 'block') + '" />');
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);
1983 closeicon.on('dock:actionkey', this.hide, this, {actions: {enter: true, toggle: true}});
1984 this.get('commands').append(closeicon);
1986 this.set('dockTitleNode', docktitle);
1987 this.set('dockItemNode', dockitem);
1989 this.fire('dockeditem:drawcomplete');
1993 * This function toggles makes the item active and shows it.
1998 var dock = this.get('dock'),
1999 panel = dock.getPanel(),
2000 docktitle = this.get('dockTitleNode');
2003 this.fire('dockeditem:showstart');
2004 panel.setHeader(this.get('titlestring'), this.get('commands'));
2005 panel.setBody(Y.Node.create('<div class="block_' + this.get('blockclass') + ' block_docked"></div>')
2006 .append(this.get('contents')));
2007 if (M.core.actionmenu !== undefined) {
2008 M.core.actionmenu.newDOMNode(panel.get('node'));
2011 panel.correctWidth();
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');
2023 * This function hides the item and makes it inactive.
2027 this.fire('dockeditem:hidestart');
2030 this.active = false;
2032 this.get('dock').getPanel().hide();
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');
2040 * A toggle between calling show and hide functions based on css.activeitem
2041 * Applies rules to key press events (dock:actionkey)
2043 * @param {String} action
2045 toggle: function(action) {
2046 var docktitle = this.get('dockTitleNode');
2047 if (docktitle.hasClass(CSS.activeitem) && action !== 'expand') {
2049 } else if (!docktitle.hasClass(CSS.activeitem) && action !== 'collapse') {
2054 * This function removes the node and destroys it's bits.
2057 remove: function() {
2059 // Return the block to its original position.
2060 this.get('block').returnToPage();
2061 // Remove the dock item node.
2062 this.get('dockItemNode').remove();
2063 this.fire('dockeditem:itemremoved');
2066 * Returns the description of this item to use for log calls.
2067 * @method _getLogDescription
2071 _getLogDescription: function() {
2072 return this.get('titlestring').get('innerHTML') + ' (' + this.get('blockinstanceid') + ')';
2075 Y.extend(DOCKEDITEM, Y.Base, DOCKEDITEM.prototype, {
2076 NAME: 'moodle-core-dock-dockeditem',
2079 * The block this docked item is associated with.
2086 writeOnce: 'initOnly'
2096 writeOnce: 'initOnly'
2099 * The docked item ID. This will be given by the dock.
2105 * Block instance id.Taken from the associated block.
2106 * @attribute blockinstanceid
2111 writeOnce: 'initOnly',
2112 setter: function(value) {
2113 return parseInt(value, 10);
2117 * The title nodeof the docked item.
2127 * @attribute titlestring
2134 * The contents of the docked item
2135 * @attribute contents
2141 writeOnce: 'initOnly'
2144 * Commands associated with the block.
2145 * @attribute commands
2151 writeOnce: 'initOnly'
2155 * @attribute blockclass
2161 writeOnce: 'initOnly'
2164 * The title node for the docked block.
2165 * @attribute dockTitleNode
2172 * The item node for the docked block.
2173 * @attribute dockItemNode
2180 * The container for the docked item (will contain the block contents when visible)
2181 * @attribute dockcontainerNode
2184 dockcontainerNode: {
2189 Y.augment(DOCKEDITEM, Y.EventTarget);
2200 "moodle-core-dock-loader",