themes MDL-21862 Implements base theme and standard theme.
[moodle.git] / blocks / dock.js
1 /**
2  * The dock namespace: Contains all things dock related
3  * @namespace
4  */
5 M.core_dock = {
6     count : 0,        // The number of dock items currently
7     totalcount : 0,   // The number of dock items through the page life
8     exists : false,   // True if the dock exists
9     items : [],       // An array of dock items
10     node : null,      // The YUI node for the dock itself
11     earlybinds : [],  // Events added before the dock was augmented to support events
12     Y : null,         // The YUI instance to use with dock related code
13     /**
14      * Strings used by the dock/dockitems
15      * @namespace
16      */
17     strings : {
18         addtodock : '[[addtodock]]',
19         undockitem : '[[undockitem]]',
20         undockall : '[[undockall]]'
21     },
22     /**
23      * Configuration parameters used during the initialisation and setup
24      * of dock and dock items.
25      * This is here specifically so that themers can override core parameters and
26      * design aspects without having to re-write navigation
27      * @namespace
28      */
29     cfg:{
30         buffer:10,                          // Buffer used when containing a panel
31         position:'left',                    // position of the dock
32         orientation:'vertical',             // vertical || horizontal determines if we change the title
33         /**
34          * Display parameters for the dock
35          * @namespace
36          */
37         display:{
38             spacebeforefirstitem: 10,       // Space between the top of the dock and the first item
39             mindisplaywidth: null,          // Minimum width for the display of dock items
40             removeallicon: M.util.image_url('t/dock_to_block', 'moodle')
41         },
42         /**
43          * CSS classes to use with the dock
44          * @namespace
45          */
46         css: {
47             dock:'dock',                    // CSS Class applied to the dock box
48             dockspacer:'dockspacer',        // CSS class applied to the dockspacer
49             controls:'controls',            // CSS class applied to the controls box
50             body:'has_dock',                // CSS class added to the body when there is a dock
51             dockeditem:'dockeditem',        // CSS class added to each item in the dock
52             dockedtitle:'dockedtitle',      // CSS class added to the item's title in each dock
53             activeitem:'activeitem'         // CSS class added to the active item
54         },
55         /**
56          * Configuration options for the panel that items are shown in
57          * @namespace
58          */
59         panel: {
60             close:false,                    // Show a close button on the panel
61             draggable:false,                // Make the panel draggable
62             underlay:"none",                // Use a special underlay
63             modal:false,                    // Throws a lightbox if set to true
64             keylisteners:null,              // An array of keylisterners to attach
65             visible:false,                  // Visible by default
66             effect: null,                   // An effect that should be used with the panel
67             monitorresize:false,            // Monitor the resize of the panel
68             context:null,                   // Sets up contexts for the panel
69             fixedcenter:false,              // Always displays the panel in the center of the screen
70             zIndex:null,                    // Sets a specific z index for the panel
71             constraintoviewport: false,     // Constrain the panel to the viewport
72             autofillheight:'body'           // Which container element should fill out empty space
73         }
74     },
75     /**
76      * Augments the classes as required and processes early bindings
77      */
78     init:function(Y) {
79         this.Y = Y;
80         // Give the dock item class the event properties/methods
81         this.Y.augment(M.core_dock.item, this.Y.EventTarget);
82         this.Y.augment(M.core_dock, this.Y.EventTarget, true);
83         // Re-apply early bindings properly now that we can
84         M.core_dock.apply_binds();
85         // Check if there is a customisation function
86         if (typeof(customise_dock_for_theme) === 'function') {
87             customise_dock_for_theme();
88         }
89     },
90     /**
91      * Adds a dock item into the dock
92      * @function
93      * @param {M.core_dock.item} item
94      */
95     add:function(item) {
96         item.id = this.totalcount;
97         this.count++;
98         this.totalcount++;
99         this.items[item.id] = item;
100         this.draw();
101         this.items[item.id].draw();
102         this.fire('dock:itemadded', item);
103     },
104     /**
105      * Appends a dock item to the dock
106      * @param {YUI.Node} docknode
107      */
108     append : function(docknode) {
109         M.core_dock.node.one('#dock_item_container').append(docknode);
110     },
111     /**
112      * Initialises a generic block object
113      * @param {YUI} Y
114      * @param {int} id
115      */
116     init_genericblock : function(Y, id) {
117         var genericblock = new this.genericblock();
118         genericblock.id = id;
119         genericblock.init(Y, Y.one('#inst'+id));
120     },
121     /**
122      * Draws the dock
123      * @function
124      * @return bool
125      */
126     draw:function() {
127         if (this.node !== null) {
128             return true;
129         }
130         this.fire('dock:drawstarted');
131         this.item_sizer.init(this.Y);
132         this.node = this.Y.Node.create('<div id="dock" class="'+M.core_dock.cfg.css.dock+' '+this.cfg.css.dock+'_'+this.cfg.position+'_'+this.cfg.orientation+'"></div>');
133         this.node.appendChild(this.Y.Node.create('<div class="'+M.core_dock.cfg.css.dockspacer+'" style="height:'+M.core_dock.cfg.display.spacebeforefirstitem+'px"></div>'));
134         this.node.appendChild(this.Y.Node.create('<div id="dock_item_container"></div>'));
135         if (this.Y.UA.ie > 0 && this.Y.UA.ie < 7) {
136             this.node.setStyle('height', this.node.get('winHeight')+'px');
137         }
138         var dockcontrol = this.Y.Node.create('<div class="'+M.core_dock.cfg.css.controls+'"></div>');
139         var removeall = this.Y.Node.create('<img src="'+this.cfg.display.removeallicon+'" alt="'+M.str.block.undockall+'" title="'+M.str.block.undockall+'" />');
140         removeall.on('removeall|click', this.remove_all, this);
141         dockcontrol.appendChild(removeall);
142         this.node.appendChild(dockcontrol);
144         this.Y.one(document.body).appendChild(this.node);
145         this.Y.one(document.body).addClass(M.core_dock.cfg.css.body);
146         this.fire('dock:drawcompleted');
147         return true;
148     },
149     /**
150      * Removes the node at the given index and puts it back into conventional page sturcture
151      * @function
152      * @param {int} uid Unique identifier for the block
153      * @return {boolean}
154      */
155     remove:function(uid) {
156         if (!this.items[uid]) {
157             return false;
158         }
159         this.items[uid].remove();
160         delete this.items[uid];
161         this.count--;
162         this.fire('dock:itemremoved', uid);
163         if (this.count===0) {
164             this.fire('dock:toberemoved');
165             this.items = [];
166             this.node.remove();
167             this.node = null;
168             this.Y.one(document.body).removeClass(M.core_dock.cfg.css.body);
169             this.fire('dock:removed');
170         }
171         return true;
172     },
173     /**
174      * Removes all nodes and puts them back into conventional page sturcture
175      * @function
176      * @return {boolean}
177      */
178     remove_all:function() {
179         for (var i in this.items) {
180             this.items[i].remove();
181             this.count--;
182             delete this.items[i];
183         }
184         this.Y.fire('dock:toberemoved');
185         this.items = [];
186         this.node.remove();
187         this.node = null;
188         this.Y.one(document.body).removeClass(M.core_dock.cfg.css.body);
189         this.Y.fire('dock:removed');
190         return true;
191     },
192     /**
193      * Resizes the active item
194      * @function
195      * @param {Event} e
196      */
197     resize:function(e){
198         for (var i in this.items) {
199             if (this.items[i].active) {
200                 this.items[i].resize_panel(e);
201             }
202         }
203     },
204     /**
205      * Hides all [the active] items
206      * @function
207      */
208     hide_all:function() {
209         for (var i in this.items) {
210             this.items[i].hide(null, true);
211         }
212     },
213     /**
214      * This smart little function allows developers to attach event listeners before
215      * the dock has been augmented to allows event listeners.
216      * Once the augmentation is complete this function will be replaced with the proper
217      * on method for handling event listeners.
218      * Finally apply_binds needs to be called in order to properly bind events.
219      * @param {string} event
220      * @param {function} callback
221      */
222     on : function(event, callback) {
223         this.earlybinds.push({event:event,callback:callback});
224     },
225     /**
226      * This function takes all early binds and attaches them as listeners properly
227      * This should only be called once augmentation is complete.
228      */
229     apply_binds : function() {
230         for (var i in this.earlybinds) {
231             var bind = this.earlybinds[i];
232             this.on(bind.event, bind.callback);
233         }
234         this.earlybinds = [];
235     },
236     /**
237      * Namespace for the dock sizer which is responsible for ensuring that dock
238      * items are visible at all times, this is required because otherwise when there
239      * were enough dock items to fit on the dock those that ran over the size of
240      * the dock would not be usable
241      * @namespace
242      */
243     item_sizer : {
244         enabled : false,        // True if the item_sizer is being used, false otherwise
245         Y : null,               // The YUI instance
246         /**
247          * Initialises the dock sizer which then attaches itself to the required
248          * events in order to monitor the dock
249          * @param {YUI} Y
250          */
251         init : function(Y) {
252             this.Y = Y;
253             M.core_dock.on('dock:itemadded', this.check_if_required, this);
254             M.core_dock.on('dock:itemremoved', this.check_if_required, this);
255             this.Y.on('windowresize', this.check_if_required, this);
256         },
257         /**
258          * Check if the size dock items needs to be adjusted
259          */
260         check_if_required : function() {
261             var possibleheight = M.core_dock.node.get('offsetHeight') - M.core_dock.node.one('.controls').get('offsetHeight') - (M.core_dock.cfg.buffer*3) - (M.core_dock.items.length*2);
262             var totalheight = 0;
263             for (var id in M.core_dock.items) {
264                 var dockedtitle = this.Y.get(M.core_dock.items[id].title).ancestor('.'+M.core_dock.cfg.css.dockedtitle);
265                 if (dockedtitle) {
266                     if (this.enabled) {
267                         dockedtitle.setStyle('height', 'auto');
268                     }
269                     totalheight += dockedtitle.get('offsetHeight') || 0;
270                 }
271             }
272             if (totalheight > possibleheight) {
273                 this.enable(possibleheight);
274             }
275         },
276         /**
277          * Enables the dock sizer and resizes where required.
278          */
279         enable : function(possibleheight) {
280             this.enabled = true;
281             var runningcount = 0;
282             var usedheight = 0;
283             for (var id in M.core_dock.items) {
284                 var itemtitle = this.Y.get(M.core_dock.items[id].title).ancestor('.'+M.core_dock.cfg.css.dockedtitle);
285                 if (!itemtitle) {
286                     continue;
287                 }
288                 var itemheight = Math.floor((possibleheight-usedheight) / (M.core_dock.count - runningcount));
289                 this.Y.log("("+possibleheight+"-"+usedheight+") / ("+M.core_dock.count+" - "+runningcount+") = "+itemheight);
290                 var offsetheight = itemtitle.get('offsetHeight');
291                 itemtitle.setStyle('overflow', 'hidden');
292                 if (offsetheight > itemheight) {
293                     itemtitle.setStyle('height', itemheight+'px');
294                     usedheight += itemheight;
295                 } else {
296                     usedheight += offsetheight;
297                 }
298                 runningcount++;
299             }
300             this.Y.log('possible: '+possibleheight+' - used height: '+usedheight);
301         }
302     },
303     /**
304      * Namespace containing methods and properties that will be prototyped
305      * to the generic block class and possibly overriden by themes
306      * @namespace
307      */
308     abstract_block_class : {
310         Y : null,                   // A YUI instance to use with the block
311         id : null,                  // The block instance id
312         cachedcontentnode : null,   // The cached content node for the actual block
313         blockspacewidth : null,     // The width of the block's original container
314         skipsetposition : false,    // If true the user preference isn't updated
316         /**
317          * This function should be called within the block's constructor and is used to
318          * set up the initial controls for swtiching block position as well as an initial
319          * moves that may be required.
320          *
321          * @param {YUI.Node} node The node that contains all of the block's content
322          */
323         init : function(Y, node) {
325             this.Y = Y;
326             if (!node) {
327                 return;
328             }
330             var commands = node.one('.header .title .commands');
331             if (!commands) {
332                 commands = this.Y.Node.create('<div class="commands"></div>');
333                 if (node.one('.header .title')) {
334                     node.one('.header .title').append(commands);
335                 }
336             }
338             var moveto = this.Y.Node.create('<input type="image" class="moveto customcommand requiresjs" src="'+M.util.image_url('t/block_to_dock', 'moodle')+'" alt="'+M.str.block.undockitem+'" title="'+M.str.block.undockitem+'" />');
339             moveto.on('movetodock|click', this.move_to_dock, this, commands);
341             var blockaction = node.one('.block_action');
342             if (blockaction) {
343                 blockaction.prepend(moveto);
344             } else {
345                 commands.append(moveto);
346             }
348             // Move the block straight to the dock if required
349             if (node.hasClass('dock_on_load')) {
350                 node.removeClass('dock_on_load')
351                 this.skipsetposition = true;
352                 this.move_to_dock(null, commands);
353             }
354         },
356         /**
357          * This function is reponsible for moving a block from the page structure onto the
358          * dock
359          * @param {event}
360          */
361         move_to_dock : function(e, commands) {
362             if (e) {
363                 e.halt(true);
364             }
366             var node = this.Y.one('#inst'+this.id);
367             var blockcontent = node.one('.content');
368             if (!blockcontent) {
369                 return;
370             }
372             var blockclass = (function(classes){
373                 var r = /(^|\s)(block_[a-zA-Z0-9_]+)(\s|$)/;
374                 var m = r.exec(classes);
375                 return (m)?m[2]:m;
376             })(node.getAttribute('className').toString());
378             this.cachedcontentnode = node;
380             var placeholder = this.Y.Node.create('<div id="content_placeholder_'+this.id+'"></div>');
381             node.replace(this.Y.Node.getDOMNode(placeholder));
382             node = null;
384             var spacewidth = this.resize_block_space(placeholder);
386             var blocktitle = this.Y.Node.getDOMNode(this.cachedcontentnode.one('.title h2')).cloneNode(true);
387             blocktitle = this.fix_title_orientation(blocktitle);
389             var blockcommands = this.cachedcontentnode.one('.title .commands');
390             var moveto = this.Y.Node.create('<a class="moveto customcommand requiresjs"></a>');
391             moveto.append(this.Y.Node.create('<img src="'+M.util.image_url('t/dock_to_block', 'moodle')+'" alt="'+M.str.block.undockitem+'" title="'+M.str.block.undockitem+'" />'));
392             if (location.href.match(/\?/)) {
393                 moveto.set('href', location.href+'&dock='+this.id);
394             } else {
395                 moveto.set('href', location.href+'?dock='+this.id);
396             }
397             blockcommands.append(moveto);
399             // Create a new dock item for the block
400             var dockitem = new M.core_dock.item(this.Y, this.id, blocktitle, blockcontent, blockcommands, blockclass);
401             if (spacewidth !== null && M.core_dock.cfg.display.mindisplaywidth == null) {
402                 dockitem.cfg.display.mindisplaywidth = spacewidth;
403             }
404             // Wire the draw events to register remove events
405             dockitem.on('dockeditem:drawcomplete', function(e){
406                 // check the contents block [editing=off]
407                 this.contents.all('.moveto').on('returntoblock|click', function(e){
408                     e.halt();
409                     M.core_dock.remove(this.id)
410                 }, this);
411                 // check the commands block [editing=on]
412                 this.commands.all('.moveto').on('returntoblock|click', function(e){
413                     e.halt();
414                     M.core_dock.remove(this.id)
415                 }, this);
416                 // Add a close icon
417                 var closeicon = this.Y.Node.create('<span class="hidepanelicon"><img src="'+M.util.image_url('t/delete', 'moodle')+'" alt="" style="width:11px;height:11px;cursor:pointer;" /></span>');
418                 closeicon.on('forceclose|click', M.core_dock.hide_all, M.core_dock);
419                 closeicon.on('forceclose|click', M.core_dock.hide_all, M.core_dock);
420                 this.commands.append(closeicon);
421             }, dockitem);
423             // Register an event so that when it is removed we can put it back as a block
424             dockitem.on('dockitem:itemremoved', this.return_to_block, this, dockitem);
425             M.core_dock.add(dockitem);
427             if (!this.skipsetposition) {
428                 // save the users preference
429                 M.util.set_user_preference('docked_block_instance_'+this.id, 1);
430             } else {
431                 this.skipsetposition = false;
432             }
433         },
434         /**
435          * Corrects the orientation of the title, which for the default
436          * dock just means making it vertical
437          * @param {YUI.Node} node
438          */
439         fix_title_orientation : function(node) {
440             node.innerHTML = node.innerHTML.replace(/(.)/g, "$1<br />");
441             return node;
442         },
443         /**
444          * Resizes the space that contained blocks if there were no blocks left in
445          * it. e.g. if all blocks have been moved to the dock
446          * @param {Y.Node} node
447          */
448         resize_block_space : function(node) {
449             node = node.ancestor('.block-region');
450             if (node) {
451                 var width =  node.getStyle('width');
452                 if (node.all('.sideblock').size() === 0 && this.blockspacewidth === null) {
453                     // If the node has no children then we can shrink it
454                     this.blockspacewidth = width;
455                     node.setStyle('width', '0px');
456                 } else if (this.blockspacewidth !== null) {
457                     // Otherwise if it contains children and we have saved a width
458                     // we can reapply the width
459                     node.setStyle('width', this.blockspacewidth);
460                     this.blockspacewidth = null;
461                 }
462                 return width;
463             }
464             return null;
465         },
466         /**
467          * This function removes a block from the dock and puts it back into the page
468          * structure.
469          * @param {M.core_dock.class.item}
470          */
471         return_to_block : function(dockitem) {
472             var placeholder = this.Y.one('#content_placeholder_'+this.id);
473             
474             if (this.cachedcontentnode.one('.header')) {
475                 this.cachedcontentnode.one('.header').insert(dockitem.contents, 'after');
476             } else {
477                 this.cachedcontentnode.insert(dockitem.contents);
478             }
480             placeholder.replace(this.Y.Node.getDOMNode(this.cachedcontentnode));
481             this.cachedcontentnode = this.Y.one('#'+this.cachedcontentnode.get('id'));
483             this.resize_block_space(this.cachedcontentnode);
485             var commands = this.cachedcontentnode.one('.commands');
486             commands.all('.hidepanelicon').remove();
487             commands.all('.moveto').remove();
488             commands.remove();
489             this.cachedcontentnode.one('.title').append(commands);
490             this.cachedcontentnode = null;
491             M.util.set_user_preference('docked_block_instance_'+this.id, 0);
492             return true;
493         }
494     },
495     /**
496      * This namespace contains the generic properties, methods and events
497      * that will be bound to the M.core_dock.item class.
498      * These can then be overriden to customise the way dock items work/display
499      * @namespace
500      */
501     abstract_item_class : {
503         Y : null,               // The YUI instance to use with this dock item
504         id : null,              // The unique id for the item
505         name : null,            // The name of the item
506         title : null,           // The title of the item
507         contents : null,        // The content of the item
508         commands : null,        // The commands for the item
509         active : false,         // True if the item is being shown
510         panel : null,           // The YUI2 panel the item will be shown in
511         preventhide : false,    // If true the next call to hide will be ignored
512         cfg : null,             // The config options for this item by default M.core_dock.cfg
514         /**
515          * Initialises all of the items events
516          * @function
517          */
518         init_events : function() {
519             this.publish('dockeditem:drawstart', {prefix:'dockeditem'});
520             this.publish('dockeditem:drawcomplete', {prefix:'dockeditem'});
521             this.publish('dockeditem:showstart', {prefix:'dockeditem'});
522             this.publish('dockeditem:showcomplete', {prefix:'dockeditem'});
523             this.publish('dockeditem:hidestart', {prefix:'dockeditem'});
524             this.publish('dockeditem:hidecomplete', {prefix:'dockeditem'});
525             this.publish('dockeditem:resizestart', {prefix:'dockeditem'});
526             this.publish('dockeditem:resizecomplete', {prefix:'dockeditem'});
527             this.publish('dockeditem:itemremoved', {prefix:'dockeditem'});
528         },
530         /**
531          * This function draws the item on the dock
532          */
533         draw : function() {
534             this.fire('dockeditem:drawstart');
535             var dockitemtitle = this.Y.Node.create('<div id="dock_item_'+this.id+'_title" class="'+this.cfg.css.dockedtitle+'"></div>');
536             dockitemtitle.append(this.title);
537             var dockitem = this.Y.Node.create('<div id="dock_item_'+this.id+'" class="'+this.cfg.css.dockeditem+'"></div>');
538             if (M.core_dock.count === 1) {
539                 dockitem.addClass('firstdockitem');
540             }
541             dockitem.append(dockitemtitle);
542             if (this.commands.hasChildNodes) {
543                 if (this.contents.ancestor().one('.footer')) {
544                     this.contents.ancestor().one('.footer').appendChild(this.commands);
545                 } else {
546                     this.contents.appendChild(this.commands);
547                 }
548             }
549             M.core_dock.append(dockitem);
551             var position = dockitemtitle.getXY();
552             position[0] += parseInt(dockitemtitle.get('offsetWidth'));
553             if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) {
554                 position[0] -= 2;
555             }
556             this.panel = new YAHOO.widget.Panel('dock_item_panel_'+this.id, {
557                 close:this.cfg.panel.close,
558                 draggable:this.cfg.panel.draggable,
559                 underlay:this.cfg.panel.underlay,
560                 modal: this.cfg.panel.modal,
561                 keylisteners: this.cfg.panel.keylisteners,
562                 visible:this.cfg.panel.visible,
563                 effect:this.cfg.panel.effect,
564                 monitorresize:this.cfg.panel.monitorresize,
565                 context: this.cfg.panel.context,
566                 fixedcenter: this.cfg.panel.fixedcenter,
567                 zIndex: this.cfg.panel.zIndex,
568                 constraintoviewport: this.cfg.panel.constraintoviewport,
569                 xy:position,
570                 autofillheight:this.cfg.panel.autofillheight});
571             this.panel.showEvent.subscribe(this.resize_panel, this, true);
572             this.panel.renderEvent.subscribe(this.resize_panel, this, true);
573             this.panel.setBody(this.Y.Node.getDOMNode(this.contents));
574             this.panel.render(M.core_dock.node);
575             this.Y.one(this.panel.body).addClass(this.blockclass);
576             if (this.cfg.display.mindisplaywidth !== null && this.Y.one(this.panel.body).getStyle('minWidth') == '0px') {
577                 this.Y.one(this.panel.body).setStyle('minWidth', this.cfg.display.mindisplaywidth);
578                 this.Y.one(this.panel.body).setStyle('minHeight', dockitemtitle.get('offsetHeight')+'px');
579             }
580             dockitem.on('showitem|mouseover', this.show, this);
581             dockitem.on('showitem|click', this.show, this);
582             this.fire('dockeditem:drawcomplete');
583         },
584         /**
585          * This function removes the node and destroys it's bits
586          * @param {Event} e
587          */
588         remove : function (e) {
589             this.hide(e);
590             this.Y.one('#dock_item_'+this.id).remove();
591             this.panel.destroy();
592             this.fire('dockitem:itemremoved');
593         },
594         /**
595          * This function toggles makes the item active and shows it
596          * @param {event}
597          */
598         show : function(e) {
599             M.core_dock.hide_all();
600             this.fire('dockeditem:showstart');
601             this.panel.show(e, this);
602             this.active = true;
603             // Add active item class first up
604             this.Y.one('#dock_item_'+this.id+'_title').addClass(this.cfg.css.activeitem);
605             // Remove the two show event listeners
606             this.Y.detach('mouseover', this.show, this.Y.one('#dock_item_'+this.id));
607             this.Y.detach('click', this.show, this.Y.one('#dock_item_'+this.id));
608             // Add control events to ensure we don't cause annoyance
609             this.Y.one('#dock_item_panel_'+this.id).on('dockpreventhide|click', function(){this.preventhide=true;}, this);
610             // Add resize event so we keep it viewable
611             this.Y.get(window).on('dockresize|resize', this.resize_panel, this);
613             // If the event was fired by mouse over then we also want to hide when
614             // the user moves the mouse out of the area
615             if (e.type == 'mouseover') {
616                 this.Y.one(this.panel.element).on('dockhide|mouseleave', this.delay_hide, this);
617                 this.preventhide = true;
618                 setTimeout(function(obj){
619                     if (obj.preventhide) {
620                         obj.preventhide = false;
621                     }
622                 }, 1000, this);
623             }
625             // Attach the default hide events, clicking the heading or the body
626             this.Y.one('#dock_item_'+this.id).on('dockhide|click', this.hide, this);
627             this.Y.get(document.body).on('dockhide|click', this.hide, this);
628             
629             this.fire('dockeditem:showcomplete');
630             return true;
631         },
632         /**
633          * This function hides the item and makes it inactive
634          * @param {event} e
635          * @param {boolean} ignorepreventhide If true preventhide is ignored
636          */
637         hide : function(e) {
638             // Check whether a second argument has been passed
639             var ignorepreventhide = (arguments.length==2 && arguments[1]);
640             // Ignore this call is preventhide is true
641             if (this.preventhide===true && !ignorepreventhide) {
642                 this.preventhide = false;
643                 if (e) {
644                     // Stop all propagation immediatly or the next element (likely body)
645                     // will fire this event again and the item will hide
646                     e.stopImmediatePropagation();
647                 }
648             } else if (this.active) {
649                 // Display any hide delay running, mouseleave-mouseenter-click
650                 this.delayhiderunning = false;
651                 this.fire('dockeditem:hidestart');
652                 // No longer active
653                 this.active = false;
654                 // Remove the active class
655                 this.Y.one('#dock_item_'+this.id+'_title').removeClass(this.cfg.css.activeitem);
656                 // Add the show event again
657                 this.Y.one('#dock_item_'+this.id).on('showitem|mouseover', this.show, this);
658                 // Remove the hide events
659                 this.Y.detach('mouseleave', this.delayhide, this.Y.one(this.panel.element));
660                 this.Y.get(window).detach('dockresize|resize');
661                 this.Y.get(document.body).detach('dockhide|click');
662                 // Hide the panel
663                 this.panel.hide(e, this);
664                 this.fire('dockeditem:hidecomplete');
665             }
666         },
667         /**
668          * This function sets the item to hide after a specific delay, that delay is
669          * this.delayhidetimeout.
670          * @param {Event} e
671          */
672         delay_hide : function(e) {
673             // The hide delay timeout is running now
674             this.delayhiderunning = true;
675             // Add the re-enter event to cancel the delay timeout
676             var delayhideevent = this.Y.one(this.panel.element).on('delayhide|mouseover', function(){this.delayhiderunning = false;}, this);
677             // Set the timeout + callback and pass the this for scope and the event so
678             // it can be easily detached
679             setTimeout(function(obj, ev){
680                 if (obj.delayhiderunning) {
681                     ev.detach();
682                     obj.hide();
683                 }
684             }, this.delayhidetimeout, this, delayhideevent);
685         },
686         /**
687          * This function checks the size and position of the panel and moves/resizes if
688          * required to keep it within the bounds of the window.
689          */
690         resize_panel : function() {
691             this.fire('dockeditem:resizestart');
692             var panelbody = this.Y.one(this.panel.body);
693             var buffer = this.cfg.buffer;
694             var screenheight = parseInt(this.Y.get(document.body).get('winHeight'));
695             var panelheight = parseInt(panelbody.get('offsetHeight'));
696             var paneltop = parseInt(this.panel.cfg.getProperty('y'));
697             var titletop = parseInt(this.Y.one('#dock_item_'+this.id+'_title').getY());
698             var scrolltop = window.pageYOffset || document.body.scrollTop || 0;
700             // This makes sure that the panel is the same height as the dock title to
701             // begin with
702             if (paneltop > (buffer+scrolltop) && paneltop > (titletop+scrolltop)) {
703                 this.panel.cfg.setProperty('y', titletop+scrolltop);
704             }
706             // This makes sure that if the panel is big it is moved up to ensure we don't
707             // have wasted space above the panel
708             if ((paneltop+panelheight)>(screenheight+scrolltop) && paneltop > buffer) {
709                 paneltop = (screenheight-panelheight-buffer);
710                 if (paneltop<buffer) {
711                     paneltop = buffer;
712                 }
713                 this.panel.cfg.setProperty('y', paneltop+scrolltop);
714             }
716             // This makes the panel constrain to the screen's height if the panel is big
717             if (paneltop <= buffer && ((panelheight+paneltop*2) > screenheight || panelbody.hasClass('oversized_content'))) {
718                 this.panel.cfg.setProperty('height', screenheight-(buffer*3));
719                 panelbody.setStyle('height', (screenheight-(buffer*3)-10)+'px');
720                 panelbody.addClass('oversized_content');
721             }
722             this.fire('dockeditem:resizecomplete');
723         }
724     }
725 };
727 /**
728  * This class represents a generic block
729  * @class genericblock
730  * @constructor
731  */
732 M.core_dock.genericblock = function() {};
733 /** Properties */
734 M.core_dock.genericblock.prototype.cachedcontentnode =       M.core_dock.abstract_block_class.cachedcontentnode;
735 M.core_dock.genericblock.prototype.blockspacewidth =         M.core_dock.abstract_block_class.blockspacewidth;
736 M.core_dock.genericblock.prototype.skipsetposition =         M.core_dock.abstract_block_class.skipsetposition;
737 /** Methods **/
738 M.core_dock.genericblock.prototype.init =                    M.core_dock.abstract_block_class.init;
739 M.core_dock.genericblock.prototype.move_to_dock =            M.core_dock.abstract_block_class.move_to_dock;
740 M.core_dock.genericblock.prototype.resize_block_space =      M.core_dock.abstract_block_class.resize_block_space;
741 M.core_dock.genericblock.prototype.return_to_block =         M.core_dock.abstract_block_class.return_to_block;
742 M.core_dock.genericblock.prototype.fix_title_orientation =   M.core_dock.abstract_block_class.fix_title_orientation;
744 /**
745  * This class represents an item in the dock
746  * @class item
747  * @constructor
748  * @param {YUI} Y The YUI instance to use for this item
749  * @param {int} uid The unique ID for the item
750  * @param {this.Y.Node} title
751  * @param {this.Y.Node} contents
752  * @param {this.Y.Node} commands
753  */
754 M.core_dock.item = function(Y, uid, title, contents, commands, blockclass){
755     this.Y = Y;
756     if (uid && this.id==null) {
757         this.id = uid;
758     }
759     if (title && this.title==null) {
760         this.title = title;
761     }
762     if (contents && this.contents==null) {
763         this.contents = contents;
764     }
765     if (commands && this.commands==null) {
766         this.commands = commands;
767     }
768     if (blockclass && this.blockclass==null) {
769         this.blockclass = blockclass
770     }
771     this.init_events();
773 /** Properties */
774 M.core_dock.item.prototype.id =                 M.core_dock.abstract_item_class.id;
775 M.core_dock.item.prototype.name =               M.core_dock.abstract_item_class.name;
776 M.core_dock.item.prototype.title =              M.core_dock.abstract_item_class.title;
777 M.core_dock.item.prototype.contents =           M.core_dock.abstract_item_class.contents;
778 M.core_dock.item.prototype.commands =           M.core_dock.abstract_item_class.commands;
779 M.core_dock.item.prototype.active =             M.core_dock.abstract_item_class.active;
780 M.core_dock.item.prototype.panel =              M.core_dock.abstract_item_class.panel;
781 M.core_dock.item.prototype.preventhide =        M.core_dock.abstract_item_class.preventhide;
782 M.core_dock.item.prototype.cfg =                M.core_dock.cfg;
783 M.core_dock.item.prototype.blockclass =         null;
784 M.core_dock.item.prototype.delayhiderunning =   false;
785 M.core_dock.item.prototype.delayhidetimeout =   1000; // 1 Second
786 /** Methods **/
787 M.core_dock.item.prototype.init_events =        M.core_dock.abstract_item_class.init_events;
788 M.core_dock.item.prototype.draw =               M.core_dock.abstract_item_class.draw;
789 M.core_dock.item.prototype.remove =             M.core_dock.abstract_item_class.remove;
790 M.core_dock.item.prototype.show =               M.core_dock.abstract_item_class.show;
791 M.core_dock.item.prototype.hide =               M.core_dock.abstract_item_class.hide;
792 M.core_dock.item.prototype.delay_hide =         M.core_dock.abstract_item_class.delay_hide;
793 M.core_dock.item.prototype.resize_panel =       M.core_dock.abstract_item_class.resize_panel;
795 /**
796  * This ensures that the first time the dock module is used it is initiatlised.
797  * 
798  * NOTE: Never convert the second argument to a function reference...
799  * doing so causes scoping issues
800  */
801 YUI.add('core_dock', function(Y) {M.core_dock.init(Y);}, '0.0.0.1', 'requires', M.yui.loader.modules['core_dock'].requires);