navigation MDL-21366 Converted dock and navigation to YUI3 modules and added some supporting structures to outputcomponents and ajaxlib

This commit is contained in:
Sam Hemelryk 2010-01-21 08:38:50 +00:00
parent fc9d196454
commit 1ce15fdac8
9 changed files with 897 additions and 770 deletions

700
blocks/dock.js Normal file
View File

@ -0,0 +1,700 @@
/**
* START OF BLOCKS CODE
* This code can be included in the footer instead of the header if we ever
* have a static JS file that will be loaded in the footer.
* Once this is done we will then also be able to remove the blocks.dock.init
* function and call
*/
/**
* This namespace will contain all of content (functions, classes, properties)
* for the block system
* @namespace
*/
M.blocks = M.blocks || {};
/**
* The dock namespace: Contains all things dock related
* @namespace
*/
M.blocks.dock = {
count:0, // The number of dock items currently
totalcount:0, // The number of dock items through the page life
exists:false, // True if the dock exists
items:[], // An array of dock items
node:null, // The YUI node for the dock itself
earlybinds:[], // Events added before the dock was augmented to support events
/**
* Strings used by the dock/dockitems
* @namespace
*/
strings:{
addtodock : '[[addtodock]]',
undockitem : '[[undockitem]]',
undockall : '[[undockall]]'
},
/**
* Configuration parameters used during the initialisation and setup
* of dock and dock items.
* This is here specifically so that themers can override core parameters and
* design aspects without having to re-write navigation
* @namespace
*/
cfg:{
buffer:10, // Buffer used when containing a panel
position:'left', // position of the dock
orientation:'vertical', // vertical || horizontal determines if we change the title
/**
* Display parameters for the dock
* @namespace
*/
display:{
spacebeforefirstitem: 10, // Space between the top of the dock and the first item
mindisplaywidth: null // Minimum width for the display of dock items
},
/**
* CSS classes to use with the dock
* @namespace
*/
css: {
dock:'dock', // CSS Class applied to the dock box
dockspacer:'dockspacer', // CSS class applied to the dockspacer
controls:'controls', // CSS class applied to the controls box
body:'has_dock', // CSS class added to the body when there is a dock
dockeditem:'dockeditem', // CSS class added to each item in the dock
dockedtitle:'dockedtitle', // CSS class added to the item's title in each dock
activeitem:'activeitem' // CSS class added to the active item
},
/**
* Configuration options for the panel that items are shown in
* @namespace
*/
panel: {
close:false, // Show a close button on the panel
draggable:false, // Make the panel draggable
underlay:"none", // Use a special underlay
modal:false, // Throws a lightbox if set to true
keylisteners:null, // An array of keylisterners to attach
visible:false, // Visible by default
effect: null, // An effect that should be used with the panel
monitorresize:false, // Monitor the resize of the panel
context:null, // Sets up contexts for the panel
fixedcenter:false, // Always displays the panel in the center of the screen
zIndex:null, // Sets a specific z index for the panel
constraintoviewport: false, // Constrain the panel to the viewport
autofillheight:'body' // Which container element should fill out empty space
}
},
/**
* Augments the classes as required and processes early bindings
*/
init:function() {
// Give the dock item class the event properties/methods
Y.augment(M.blocks.dock.item, Y.EventTarget);
Y.augment(M.blocks.dock, Y.EventTarget, true);
// Re-apply early bindings properly now that we can
M.blocks.dock.apply_binds();
},
/**
* Adds a dock item into the dock
* @function
* @param {M.blocks.dock.item} item
*/
add:function(item) {
item.id = this.totalcount;
this.count++;
this.totalcount++;
this.items[item.id] = item;
this.draw();
this.items[item.id].draw();
this.fire('dock:itemadded', item);
},
append : function(docknode) {
M.blocks.dock.node.one('#dock_item_container').append(docknode);
},
/**
* Draws the dock
* @function
* @return bool
*/
draw:function() {
if (this.node !== null) {
return true;
}
this.fire('dock:drawstarted');
this.item_sizer.init();
this.node = Y.Node.create('<div id="dock" class="'+M.blocks.dock.cfg.css.dock+' '+M.blocks.dock.cfg.css.dock+'_'+M.blocks.dock.cfg.position+'_'+M.blocks.dock.cfg.orientation+'"></div>');
this.node.appendChild(Y.Node.create('<div class="'+M.blocks.dock.cfg.css.dockspacer+'" style="height:'+M.blocks.dock.cfg.display.spacebeforefirstitem+'px"></div>'));
this.node.appendChild(Y.Node.create('<div id="dock_item_container"></div>'));
if (Y.UA.ie > 0 && Y.UA.ie < 7) {
this.node.setStyle('height', this.node.get('winHeight')+'px');
}
var dockcontrol = Y.Node.create('<div class="'+M.blocks.dock.cfg.css.controls+'"></div>');
var removeall = Y.Node.create('<img src="'+get_image_url('t/dock_to_block', 'moodle')+'" alt="'+mstr.block.undockall+'" title="'+mstr.block.undockall+'" />');
removeall.on('removeall|click', this.remove_all, this);
dockcontrol.appendChild(removeall);
this.node.appendChild(dockcontrol);
Y.one(document.body).appendChild(this.node);
Y.one(document.body).addClass(M.blocks.dock.cfg.css.body);
this.fire('dock:drawcompleted');
return true;
},
/**
* Removes the node at the given index and puts it back into conventional page sturcture
* @function
* @param {int} uid Unique identifier for the block
* @return {boolean}
*/
remove:function(uid) {
if (!this.items[uid]) {
return false;
}
this.items[uid].remove();
delete this.items[uid];
this.count--;
this.fire('dock:itemremoved', uid);
if (this.count===0) {
this.fire('dock:toberemoved');
this.items = [];
this.node.remove();
this.node = null;
this.fire('dock:removed');
}
return true;
},
/**
* Removes all nodes and puts them back into conventional page sturcture
* @function
* @return {boolean}
*/
remove_all:function() {
for (var i in this.items) {
this.items[i].remove();
this.count--;
delete this.items[i];
}
Y.fire('dock:toberemoved');
this.items = [];
this.node.remove();
this.node = null;
Y.fire('dock:removed');
return true;
},
/**
* Resizes the active item
* @function
* @param {Event} e
*/
resize:function(e){
for (var i in this.items) {
if (this.items[i].active) {
this.items[i].resize_panel(e);
}
}
},
/**
* Hides all [the active] items
* @function
*/
hide_all:function() {
for (var i in this.items) {
this.items[i].hide();
}
},
/**
* This smart little function allows developers to attach event listeners before
* the dock has been augmented to allows event listeners.
* Once the augmentation is complete this function will be replaced with the proper
* on method for handling event listeners.
* Finally apply_binds needs to be called in order to properly bind events.
* @param {string} event
* @param {function} callback
*/
on : function(event, callback) {
this.earlybinds.push({event:event,callback:callback});
},
/**
* This function takes all early binds and attaches them as listeners properly
* This should only be called once augmentation is complete.
*/
apply_binds : function() {
for (var i in this.earlybinds) {
var bind = this.earlybinds[i];
this.on(bind.event, bind.callback);
}
this.earlybinds = [];
},
item_sizer : {
enabled : false,
init : function() {
M.blocks.dock.on('dock:itemadded', this.check_if_required, this);
M.blocks.dock.on('dock:itemremoved', this.check_if_required, this);
Y.on('windowresize', this.check_if_required, this);
},
check_if_required : function() {
var possibleheight = M.blocks.dock.node.get('offsetHeight') - M.blocks.dock.node.one('.controls').get('offsetHeight') - (M.blocks.dock.cfg.buffer*3) - (M.blocks.dock.items.length*2);
var totalheight = 0;
for (var id in M.blocks.dock.items) {
var dockedtitle = Y.get(M.blocks.dock.items[id].title).ancestor('.'+M.blocks.dock.cfg.css.dockedtitle);
if (dockedtitle) {
if (this.enabled) {
dockedtitle.setStyle('height', 'auto');
}
totalheight += dockedtitle.get('offsetHeight') || 0;
}
}
if (totalheight > possibleheight) {
this.enable(possibleheight);
}
},
enable : function(possibleheight) {
this.enabled = true;
var runningcount = 0;
var usedheight = 0;
for (var id in M.blocks.dock.items) {
var itemtitle = Y.get(M.blocks.dock.items[id].title).ancestor('.'+M.blocks.dock.cfg.css.dockedtitle);
if (!itemtitle) {
continue;
}
var itemheight = Math.floor((possibleheight-usedheight) / (M.blocks.dock.count - runningcount));
Y.log("("+possibleheight+"-"+usedheight+") / ("+M.blocks.dock.count+" - "+runningcount+") = "+itemheight);
var offsetheight = itemtitle.get('offsetHeight');
itemtitle.setStyle('overflow', 'hidden');
if (offsetheight > itemheight) {
itemtitle.setStyle('height', itemheight+'px');
usedheight += itemheight;
} else {
usedheight += offsetheight;
}
runningcount++;
}
Y.log('possible: '+possibleheight+' - used height: '+usedheight);
}
},
/**
* Namespace containing methods and properties that will be prototyped
* to the generic block class and possibly overriden by themes
* @namespace
*/
abstract_block_class : {
id : null, // The block instance id
cachedcontentnode : null, // The cached content node for the actual block
blockspacewidth : null, // The width of the block's original container
skipsetposition : false, // If true the user preference isn't updated
/**
* This function should be called within the block's constructor and is used to
* set up the initial controls for swtiching block position as well as an initial
* moves that may be required.
*
* @param {YUI.Node} node The node that contains all of the block's content
*/
init : function(node) {
if (!node) {
node = Y.one('#inst'+this.id);
if (!node) {
return;
}
}
var commands = node.one('.header .title .commands');
if (!commands) {
commands = Y.Node.create('<div class="commands"></div>');
if (node.one('.header .title')) {
node.one('.header .title').append(commands);
}
}
var moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>');
moveto.append(Y.Node.create('<img src="'+get_image_url('t/dock_to_block', 'moodle')+'" alt="'+mstr.block.undockitem+'" title="'+mstr.block.undockitem+'" />'));
if (location.href.match(/\?/)) {
moveto.set('href', location.href+'&dock='+this.id);
} else {
moveto.set('href', location.href+'?dock='+this.id);
}
commands.append(moveto);
commands.all('a.moveto').on('movetodock|click', this.move_to_dock, this);
node.all('.customcommand').each(function(){
this.remove();
commands.appendChild(this);
});
// Move the block straight to the dock if required
if (node.hasClass('dock_on_load')) {
node.removeClass('dock_on_load')
this.skipsetposition = true;
this.move_to_dock();
}
},
/**
* This function is reponsible for moving a block from the page structure onto the
* dock
* @param {event}
*/
move_to_dock : function(e) {
if (e) {
e.halt(true);
}
var node = Y.one('#inst'+this.id);
var blockcontent = node.one('.content');
if (!blockcontent) {
return;
}
this.cachedcontentnode = node;
node.all('a.moveto').each(function(moveto){
Y.Event.purgeElement(Y.Node.getDOMNode(moveto), false, 'click');
if (moveto.hasClass('customcommand')) {
moveto.all('img').each(function(movetoimg){
movetoimg.setAttribute('src', get_image_url('t/dock_to_block', 'moodle'));
movetoimg.setAttribute('alt', mstr.block.undockitem);
movetoimg.setAttribute('title', mstr.block.undockitem);
}, this);
}
}, this);
var placeholder = Y.Node.create('<div id="content_placeholder_'+this.id+'"></div>');
node.replace(Y.Node.getDOMNode(placeholder));
node = null;
var spacewidth = this.resize_block_space(placeholder);
var blocktitle = Y.Node.getDOMNode(this.cachedcontentnode.one('.title h2')).cloneNode(true);
blocktitle.innerHTML = blocktitle.innerHTML.replace(/([a-zA-Z0-9])/g, "$1<br />");
var commands = this.cachedcontentnode.all('.title .commands');
var blockcommands = Y.Node.create('<div class="commands"></div>');
if (commands.size() > 0) {
blockcommands = commands.item(0);
}
// Create a new dock item for the block
var dockitem = new M.blocks.dock.item(this.id, blocktitle, blockcontent, blockcommands);
if (spacewidth !== null && M.blocks.dock.cfg.display.mindisplaywidth == null) {
dockitem.cfg.display.mindisplaywidth = spacewidth;
}
// Wire the draw events to register remove events
dockitem.on('dockeditem:drawcomplete', function(e){
// check the contents block [editing=off]
this.contents.all('a.moveto').on('returntoblock|click', function(e){
e.halt();
M.blocks.dock.remove(this.id)
}, this);
// check the commands block [editing=on]
this.commands.all('a.moveto').on('returntoblock|click', function(e){
e.halt();
M.blocks.dock.remove(this.id)
}, this);
}, dockitem);
// Register an event so that when it is removed we can put it back as a block
dockitem.on('dockitem:itemremoved', this.return_to_block, this, dockitem);
M.blocks.dock.add(dockitem);
if (!this.skipsetposition) {
// save the users preference
set_user_preference('docked_block_instance_'+this.id, 1);
} else {
this.skipsetposition = false;
}
},
/**
* Resizes the space that contained blocks if there were no blocks left in
* it. e.g. if all blocks have been moved to the dock
* @param {Y.Node} node
*/
resize_block_space : function(node) {
node = node.ancestor('.block-region');
if (node) {
var width = node.getStyle('width');
if (node.all('.sideblock').size() === 0 && this.blockspacewidth === null) {
// If the node has no children then we can shrink it
this.blockspacewidth = width;
node.setStyle('width', '0px');
} else if (this.blockspacewidth !== null) {
// Otherwise if it contains children and we have saved a width
// we can reapply the width
node.setStyle('width', this.blockspacewidth);
this.blockspacewidth = null;
}
return width;
}
return null;
},
/**
* This function removes a block from the dock and puts it back into the page
* structure.
* @param {M.blocks.dock.class.item}
*/
return_to_block : function(dockitem) {
var placeholder = Y.one('#content_placeholder_'+this.id);
this.cachedcontentnode.appendChild(dockitem.contents);
placeholder.replace(Y.Node.getDOMNode(this.cachedcontentnode));
this.cachedcontentnode = Y.one('#'+this.cachedcontentnode.get('id'));
this.resize_block_space(this.cachedcontentnode);
this.cachedcontentnode.all('a.moveto').each(function(moveto){
Y.Event.purgeElement(Y.Node.getDOMNode(moveto), false, 'click');
moveto.on('movetodock|click', this.move_to_dock, this);
if (moveto.hasClass('customcommand')) {
moveto.all('img').each(function(movetoimg){
movetoimg.setAttribute('src', get_image_url('t/block_to_dock', 'moodle'));
movetoimg.setAttribute('alt', mstr.block.addtodock);
movetoimg.setAttribute('title', mstr.block.addtodock);
}, this);
}
}, this);
var commands = this.cachedcontentnode.all('.commands');
var blocktitle = this.cachedcontentnode.all('.title');
if (commands.size() === 1 && blocktitle.size() === 1) {
commands.item(0).remove();
blocktitle.item(0).append(commands.item(0));
}
this.cachedcontentnode = null;
set_user_preference('docked_block_instance_'+this.id, 0);
return true;
}
},
/**
* This namespace contains the generic properties, methods and events
* that will be bound to the M.blocks.dock.item class.
* These can then be overriden to customise the way dock items work/display
* @namespace
*/
abstract_item_class : {
id : null, // The unique id for the item
name : null, // The name of the item
title : null, // The title of the item
contents : null, // The content of the item
commands : null, // The commands for the item
active : false, // True if the item is being shown
panel : null, // The YUI2 panel the item will be shown in
preventhide : false, // If true the next call to hide will be ignored
cfg : null, // The config options for this item by default M.blocks.cfg
/**
* Initialises all of the items events
* @function
*/
init_events : function() {
this.publish('dockeditem:drawstart', {prefix:'dockeditem'});
this.publish('dockeditem:drawcomplete', {prefix:'dockeditem'});
this.publish('dockeditem:showstart', {prefix:'dockeditem'});
this.publish('dockeditem:showcomplete', {prefix:'dockeditem'});
this.publish('dockeditem:hidestart', {prefix:'dockeditem'});
this.publish('dockeditem:hidecomplete', {prefix:'dockeditem'});
this.publish('dockeditem:resizestart', {prefix:'dockeditem'});
this.publish('dockeditem:resizecomplete', {prefix:'dockeditem'});
this.publish('dockeditem:itemremoved', {prefix:'dockeditem'});
},
/**
* This function draws the item on the dock
*/
draw : function() {
this.fire('dockeditem:drawstart');
var dockitemtitle = Y.Node.create('<div id="dock_item_'+this.id+'_title" class="'+this.cfg.css.dockedtitle+'"></div>');
dockitemtitle.append(this.title);
var dockitem = Y.Node.create('<div id="dock_item_'+this.id+'" class="'+this.cfg.css.dockeditem+'"></div>');
if (M.blocks.dock.count === 1) {
dockitem.addClass('firstdockitem');
}
dockitem.append(dockitemtitle);
if (this.commands.hasChildNodes) {
this.contents.appendChild(this.commands);
}
M.blocks.dock.append(dockitem);
var position = dockitemtitle.getXY();
position[0] += parseInt(dockitemtitle.get('offsetWidth'));
if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) {
position[0] -= 2;
}
this.panel = new YAHOO.widget.Panel('dock_item_panel_'+this.id, {
close:this.cfg.panel.close,
draggable:this.cfg.panel.draggable,
underlay:this.cfg.panel.underlay,
modal: this.cfg.panel.modal,
keylisteners: this.cfg.panel.keylisteners,
visible:this.cfg.panel.visible,
effect:this.cfg.panel.effect,
monitorresize:this.cfg.panel.monitorresize,
context: this.cfg.panel.context,
fixedcenter: this.cfg.panel.fixedcenter,
zIndex: this.cfg.panel.zIndex,
constraintoviewport: this.cfg.panel.constraintoviewport,
xy:position,
autofillheight:this.cfg.panel.autofillheight});
this.panel.showEvent.subscribe(this.resize_panel, this, true);
this.panel.setBody(Y.Node.getDOMNode(this.contents));
this.panel.render(M.blocks.dock.node);
if (this.cfg.display.mindisplaywidth !== null && Y.one(this.panel.body).getStyle('minWidth') == '0px') {
Y.one(this.panel.body).setStyle('minWidth', this.cfg.display.mindisplaywidth);
Y.one(this.panel.body).setStyle('minHeight', dockitemtitle.get('offsetHeight')+'px');
}
dockitem.on('showitem|mouseover', this.show, this);
this.fire('dockeditem:drawcomplete');
},
/**
* This function removes the node and destroys it's bits
* @param {Event} e
*/
remove : function (e) {
this.hide(e);
Y.one('#dock_item_'+this.id).remove();
this.panel.destroy();
this.fire('dockitem:itemremoved');
},
/**
* This function toggles makes the item active and shows it
* @param {event}
*/
show : function(e) {
M.blocks.dock.hide_all();
this.fire('dockeditem:showstart');
this.panel.show(e, this);
this.active = true;
Y.one('#dock_item_'+this.id+'_title').addClass(this.cfg.css.activeitem);
Y.detach('mouseover', this.show, Y.one('#dock_item_'+this.id));
Y.one('#dock_item_panel_'+this.id).on('dockpreventhide|click', function(){this.preventhide=true;}, this);
Y.one('#dock_item_'+this.id).on('dockhide|click', this.hide, this);
Y.get(window).on('dockresize|resize', this.resize_panel, this);
Y.get(document.body).on('dockhide|click', this.hide, this);
this.fire('dockeditem:showcomplete');
return true;
},
/**
* This function hides the item and makes it inactive
* @param {event}
*/
hide : function(e) {
// Ignore this call is preventhide is true
if (this.preventhide===true) {
this.preventhide = false;
} else if (this.active) {
this.fire('dockeditem:hidestart');
this.active = false;
Y.one('#dock_item_'+this.id+'_title').removeClass(this.cfg.css.activeitem);
Y.one('#dock_item_'+this.id).on('showitem|mouseover', this.show, this);
Y.get(window).detach('dockresize|resize');
Y.get(document.body).detach('dockhide|click');
this.panel.hide(e, this);
this.fire('dockeditem:hidecomplete');
}
},
/**
* This function checks the size and position of the panel and moves/resizes if
* required to keep it within the bounds of the window.
*/
resize_panel : function() {
this.fire('dockeditem:resizestart');
var panelbody = Y.one(this.panel.body);
var buffer = this.cfg.buffer;
var screenheight = parseInt(Y.get(document.body).get('winHeight'));
var panelheight = parseInt(panelbody.get('offsetHeight'));
var paneltop = parseInt(this.panel.cfg.getProperty('y'));
var titletop = parseInt(Y.one('#dock_item_'+this.id+'_title').getY());
var scrolltop = window.pageYOffset || document.body.scrollTop || 0;
// This makes sure that the panel is the same height as the dock title to
// begin with
if (paneltop > (buffer+scrolltop) && paneltop > (titletop+scrolltop)) {
this.panel.cfg.setProperty('y', titletop+scrolltop);
}
// This makes sure that if the panel is big it is moved up to ensure we don't
// have wasted space above the panel
if ((paneltop+panelheight)>(screenheight+scrolltop) && paneltop > buffer) {
paneltop = (screenheight-panelheight-buffer);
if (paneltop<buffer) {
paneltop = buffer;
}
this.panel.cfg.setProperty('y', paneltop+scrolltop);
}
// This makes the panel constrain to the screen's height if the panel is big
if (paneltop <= buffer && ((panelheight+paneltop*2) > screenheight || panelbody.hasClass('oversized_content'))) {
this.panel.cfg.setProperty('height', screenheight-(buffer*3));
panelbody.setStyle('height', (screenheight-(buffer*3)-10)+'px');
panelbody.addClass('oversized_content');
}
this.fire('dockeditem:resizecomplete');
}
}
};
/**
* This class represents a generic block
* @class genericblock
* @constructor
* @param {int} uid
*/
M.blocks.genericblock = function(uid){
// Save the unique id as the blocks id
if (uid && this.id==null) {
this.id = uid;
}
if (this instanceof M.blocks.genericblock) {
this.init();
}
};
/** Properties */
M.blocks.genericblock.prototype.name = M.blocks.dock.abstract_block_class.name;
M.blocks.genericblock.prototype.cachedcontentnode = M.blocks.dock.abstract_block_class.cachedcontentnode;
M.blocks.genericblock.prototype.blockspacewidth = M.blocks.dock.abstract_block_class.blockspacewidth;
M.blocks.genericblock.prototype.skipsetposition = M.blocks.dock.abstract_block_class.skipsetposition;
/** Methods **/
M.blocks.genericblock.prototype.init = M.blocks.dock.abstract_block_class.init;
M.blocks.genericblock.prototype.move_to_dock = M.blocks.dock.abstract_block_class.move_to_dock;
M.blocks.genericblock.prototype.resize_block_space = M.blocks.dock.abstract_block_class.resize_block_space;
M.blocks.genericblock.prototype.return_to_block = M.blocks.dock.abstract_block_class.return_to_block;
/**
* This class represents an item in the dock
* @class item
* @constructor
* @param {int} uid The unique ID for the item
* @param {Y.Node} title
* @param {Y.Node} contents
* @param {Y.Node} commands
*/
M.blocks.dock.item = function(uid, title, contents, commands){
if (uid && this.id==null) this.id = uid;
if (title && this.title==null) this.title = title;
if (contents && this.contents==null) this.contents = contents;
if (commands && this.commands==null) this.commands = commands;
this.init_events();
}
/** Properties */
M.blocks.dock.item.prototype.id = M.blocks.dock.abstract_item_class.id;
M.blocks.dock.item.prototype.name = M.blocks.dock.abstract_item_class.name;
M.blocks.dock.item.prototype.title = M.blocks.dock.abstract_item_class.title;
M.blocks.dock.item.prototype.contents = M.blocks.dock.abstract_item_class.contents;
M.blocks.dock.item.prototype.commands = M.blocks.dock.abstract_item_class.commands;
M.blocks.dock.item.prototype.active = M.blocks.dock.abstract_item_class.active;
M.blocks.dock.item.prototype.panel = M.blocks.dock.abstract_item_class.panel;
M.blocks.dock.item.prototype.preventhide = M.blocks.dock.abstract_item_class.preventhide;
M.blocks.dock.item.prototype.cfg = M.blocks.dock.cfg;
/** Methods **/
M.blocks.dock.item.prototype.init_events = M.blocks.dock.abstract_item_class.init_events;
M.blocks.dock.item.prototype.draw = M.blocks.dock.abstract_item_class.draw;
M.blocks.dock.item.prototype.remove = M.blocks.dock.abstract_item_class.remove;
M.blocks.dock.item.prototype.show = M.blocks.dock.abstract_item_class.show;
M.blocks.dock.item.prototype.hide = M.blocks.dock.abstract_item_class.hide;
M.blocks.dock.item.prototype.resize_panel = M.blocks.dock.abstract_item_class.resize_panel;
YUI.add('blocks_dock', M.blocks.dock.init, '0.0.0.1', 'requires', yui3loader.modules['blocks_dock'].requires);

View File

@ -80,8 +80,9 @@ class block_global_navigation_tree extends block_tree {
}
function get_required_javascript() {
global $CFG;
$this->_initialise_dock();
$this->page->requires->js('/blocks/global_navigation_tree/navigation.js');
$this->page->requires->js_module('blocks_navigation', array('fullpath'=>$CFG->wwwroot.'/blocks/global_navigation_tree/navigation.js', 'requires'=>array('blocks_dock', 'io', 'node', 'dom', 'event-custom')));
user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
}
@ -153,8 +154,8 @@ class block_global_navigation_tree extends block_tree {
$this->page->navigation->find_expandable($expandable);
// Initialise the JS tree object
$args = array($this->instance->id,array('expansions'=>$expandable,'instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked()));
$this->page->requires->js_function_call('blocks.navigation.setup_new_tree', $args)->on_dom_ready();
$args = array($this->instance->id, array('expansions'=>$expandable,'instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked()));
$this->page->requires->js_object_init("M.blocks.navigation.treecollection[".$this->instance->id."]", 'M.blocks.navigation.classes.tree', $args, array('blocks_navigation'));
// Grab the items to display
$this->content->items = array($this->page->navigation);

View File

@ -34,7 +34,7 @@ var blocks = blocks || {};
* global navigation and settings.
* @namespace
*/
blocks.navigation = {
M.blocks.navigation = {
/** The number of expandable branches in existence */
expandablebranchcount:0,
/** An array of initialised trees */
@ -45,35 +45,36 @@ blocks.navigation = {
*/
classes:{},
/**
* This function gets called when the module is first loaded as required by
* the YUI.add statement at the bottom of the page.
*
* NOTE: This will only be executed ONCE
* @function
* @static
* @param {int} uid The id of the block within the page
* @param {object} properties
*/
setup_new_tree:function(uid, properties) {
Y.use('base','dom','io','node', function() {
properties = properties || {'instance':uid};
blocks.navigation.treecollection[uid] = new blocks.navigation.classes.tree(uid, uid, properties);
});
init:function() {
if (M.blocks.genericblock) {
// Give the tree class the dock block properties
Y.augment(M.blocks.navigation.classes.tree, M.blocks.genericblock);
}
}
};
/**
* @class tree
* @constructor
* @base blocks.dock.abstractblock
* @base M.blocks.dock.abstractblock
* @param {string} id The name of the tree
* @param {int} key The internal id within the tree store
* @param {object} properties Object containing tree properties
*/
blocks.navigation.classes.tree = function(id, key, properties) {
M.blocks.navigation.classes.tree = function(id, properties) {
this.id = id;
this.key = key;
this.type = 'blocks.navigation.classes.tree';
this.key = id;
this.type = 'M.blocks.navigation.classes.tree';
this.errorlog = [];
this.ajaxbranches = 0;
this.expansions = [];
this.instance = null;
this.instance = id;
this.cachedcontentnode = null;
this.cachedfooter = null;
this.position = 'block';
@ -102,7 +103,7 @@ blocks.navigation.classes.tree = function(id, key, properties) {
// Attache events to expand by AJAX
for (var i in this.expansions) {
Y.one('#'+this.expansions[i].id).on('ajaxload|click', this.init_load_ajax, this, this.expansions[i]);
blocks.navigation.expandablebranchcount++;
M.blocks.navigation.expandablebranchcount++;
}
if (node.hasClass('block_js_expansion')) {
@ -121,7 +122,7 @@ blocks.navigation.classes.tree = function(id, key, properties) {
* @param {event} e The event object
* @param {object} branch A branch to load via ajax
*/
blocks.navigation.classes.tree.prototype.init_load_ajax = function(e, branch) {
M.blocks.navigation.classes.tree.prototype.init_load_ajax = function(e, branch) {
e.stopPropagation();
if (e.target.get('nodeName').toUpperCase() != 'P') {
return true;
@ -152,14 +153,14 @@ blocks.navigation.classes.tree.prototype.init_load_ajax = function(e, branch) {
* @param {mixed} args
* @return bool
*/
blocks.navigation.classes.tree.prototype.load_ajax = function(tid, outcome, args) {
M.blocks.navigation.classes.tree.prototype.load_ajax = function(tid, outcome, args) {
// Check the status
if (outcome.status!=0 && outcome.responseXML!=null) {
var branch = outcome.responseXML.documentElement;
if (branch!=null && this.add_branch(branch, args.target.ancestor('LI') ,1)) {
// If we get here everything worked perfectly
if (this.candock) {
blocks.dock.resize();
M.blocks.dock.resize();
}
return true;
}
@ -176,10 +177,10 @@ blocks.navigation.classes.tree.prototype.load_ajax = function(tid, outcome, args
* @param {int} depth
* @return bool
*/
blocks.navigation.classes.tree.prototype.add_branch = function(branchxml, target, depth) {
M.blocks.navigation.classes.tree.prototype.add_branch = function(branchxml, target, depth) {
// Make the new branch into an object
var branch = new blocks.navigation.classes.branch(this, branchxml);
var branch = new M.blocks.navigation.classes.branch(this, branchxml);
var childrenul = false;
if (depth === 1) {
@ -203,7 +204,7 @@ blocks.navigation.classes.tree.prototype.add_branch = function(branchxml, target
* Toggle a branch as expanded or collapsed
* @param {Event} e
*/
blocks.navigation.classes.tree.prototype.toggleexpansion = function(e) {
M.blocks.navigation.classes.tree.prototype.toggleexpansion = function(e) {
// First check if they managed to click on the li iteslf, then find the closest
// LI ancestor and use that
if (e.target.get('nodeName').toUpperCase() == 'LI') {
@ -212,7 +213,7 @@ blocks.navigation.classes.tree.prototype.toggleexpansion = function(e) {
e.target.ancestor('LI').toggleClass('collapsed');
}
if (this.candock) {
blocks.dock.resize();
M.blocks.dock.resize();
}
}
@ -220,10 +221,10 @@ blocks.navigation.classes.tree.prototype.toggleexpansion = function(e) {
* This class represents a branch for a tree
* @class branch
* @constructor
* @param {blocks.navigation.classes.tree} tree
* @param {M.blocks.navigation.classes.tree} tree
* @param {xmldoc|null} xml
*/
blocks.navigation.classes.branch = function(tree, xml) {
M.blocks.navigation.classes.branch = function(tree, xml) {
this.tree = tree;
this.name = null;
this.title = null;
@ -247,7 +248,7 @@ blocks.navigation.classes.branch = function(tree, xml) {
* Constructs a branch from XML
* @param {xmldoc} xml
*/
blocks.navigation.classes.branch.prototype.construct_from_xml = function(xml) {
M.blocks.navigation.classes.branch.prototype.construct_from_xml = function(xml) {
// Get required attributes
this.title = xml.getAttribute('title');
this.classname = xml.getAttribute('class');
@ -264,8 +265,8 @@ blocks.navigation.classes.branch.prototype.construct_from_xml = function(xml) {
if (this.id && this.id.match(/^expandable_branch_\d+$/)) {
// Assign a new unique id for this new expandable branch
blocks.navigation.expandablebranchcount++;
this.id = 'expandable_branch_'+blocks.navigation.expandablebranchcount;
M.blocks.navigation.expandablebranchcount++;
this.id = 'expandable_branch_'+M.blocks.navigation.expandablebranchcount;
}
// Retrieve any additional information
@ -284,7 +285,7 @@ blocks.navigation.classes.branch.prototype.construct_from_xml = function(xml) {
* Injects a branch into the tree at the given location
* @param {element} element
*/
blocks.navigation.classes.branch.prototype.inject_into_dom = function(element) {
M.blocks.navigation.classes.branch.prototype.inject_into_dom = function(element) {
var branchli = Y.Node.create('<li></li>');
var branchp = Y.Node.create('<p class="tree_item"></p>');
@ -338,9 +339,4 @@ blocks.navigation.classes.branch.prototype.inject_into_dom = function(element) {
}
}
YUI(yui3loader).use('event-custom', 'node', function(Y){
if (blocks.genericblock) {
// Give the tree class the dock block properties
Y.augment(blocks.navigation.classes.tree, blocks.genericblock);
}
});
YUI.add('blocks_navigation', M.blocks.navigation.init, '0.0.0.1', yui3loader.modules.blocks_navigation.requires);

View File

@ -578,7 +578,8 @@ class block_base {
function get_required_javascript() {
$this->_initialise_dock();
if ($this->instance_can_be_docked()) {
$this->page->requires->js_function_call('blocks.setup_generic_block', array($this->instance->id))->on_dom_ready();
$this->page->requires->js('/blocks/dock.js');
$this->page->requires->js_object_init(null, 'M.blocks.genericblock', array($this->instance->id), array('blocks_dock'));
user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
}
}
@ -750,11 +751,10 @@ class block_base {
}
public function _initialise_dock() {
global $CFG;
if (!self::$dockinitialised) {
$this->page->requires->js_function_call('blocks.dock.init')->on_dom_ready();
$this->page->requires->data_for_js('blocks.dock.strings.addtodock', get_string('addtodock', 'block'));
$this->page->requires->data_for_js('blocks.dock.strings.undockitem', get_string('undockitem', 'block'));
$this->page->requires->data_for_js('blocks.dock.strings.undockall', get_string('undockall', 'block'));
$this->page->requires->js_module('blocks_dock', array('fullpath'=>$CFG->wwwroot.'/blocks/dock.js', 'requires'=>array('base','dom','io','node', 'event-custom')));
$this->page->requires->strings_for_js(array('addtodock','undockitem','undockall'), 'block');
self::$dockinitialised = true;
}
}
@ -861,7 +861,7 @@ class block_tree extends block_list {
$this->content->items = array();
}
$this->get_content();
$content = $output->tree_block_contents($this->content->items,array('class'=>'block_tree'));
$content = $output->tree_block_contents($this->content->items,array('class'=>'block_tree list'));
if (isset($this->id) && !is_numeric($this->id)) {
$content = $output->box($content, 'block_tree_box', $this->id);
}

View File

@ -78,11 +78,12 @@ class block_settings_navigation_tree extends block_tree {
}
function get_required_javascript() {
global $CFG;
$this->_initialise_dock();
$this->page->requires->js('/blocks/global_navigation_tree/navigation.js');
$args = array($this->instance->id, array('instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked()));
$this->page->requires->js_function_call('blocks.navigation.setup_new_tree', $args)->on_dom_ready();
user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
$this->page->requires->js_module('blocks_navigation', array('fullpath'=>$CFG->wwwroot.'/blocks/global_navigation_tree/navigation.js', 'requires'=>array('blocks_dock', 'io', 'node', 'dom', 'event-custom')));
$arguments = array($this->instance->id, array('instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked()));
$this->page->requires->js_object_init("M.blocks.navigation.treecollection[".$this->instance->id."]", 'M.blocks.navigation.classes.tree', $arguments, array('blocks_navigation'));
user_preference_allow_ajax_update('M.docked_block_instance_'.$this->instance->id, PARAM_INT);
}
/**

View File

@ -472,7 +472,10 @@ class page_requirements_manager {
* Ideally the JS code fragment should be stored in plugin renderer so that themes
* may override it.
*
* Example: "Y.use('mod_mymod'); M.mod_mymod.init_view();"
* Example:
* Y.use('mod_mymod', function(){
* M.mod_mymod.init_view();"
* });
*
* @param string $jscode
* @return void
@ -481,6 +484,21 @@ class page_requirements_manager {
$this->jsinitcode[] = trim($jscode, " ;\n");
}
/**
* Adds a required JavaScript object initialisation to the page.
*
* @param string|null $var If null the object is not assigned to any variable
* @param string $class
* @param array $arguments
* @param array $requirements
* @return required_js_object_init
*/
public function js_object_init($var, $class, array $arguments = null, array $requirements = null) {
$requirement = new required_js_object_init($this, $var, $class, $arguments, $requirements);
$this->requiredjscode[] = $requirement;
return $requirement;
}
/**
* Make a language string available to JavaScript.
*
@ -647,7 +665,10 @@ class page_requirements_manager {
* @return unknown_type
*/
protected function get_javascript_init_code() {
return implode(";\n", $this->jsinitcode) . ";\n";
if (count($this->jsinitcode)) {
return implode(";\n", $this->jsinitcode) . ";\n";
}
return '';
}
/**
@ -816,19 +837,14 @@ class page_requirements_manager {
$ondomreadyjs = $this->get_javascript_code(self::WHEN_ON_DOM_READY, ' ');
$jsinit = $this->get_javascript_init_code();
$handlersjs = $this->get_event_handler_code();
if (!empty($ondomreadyjs)) {
$ondomreadyjs = " Y.on('domready', function() {\n$ondomreadyjs\n });";
}
// the global Y can be used only after it is fully loaded, that means
// from code executed from the following block
$js .= <<<EOD
Y = YUI(yui3loader).use('node-base', function(Y) {
$inyuijs ;
Y.on('domready', function() {
$ondomreadyjs
});
$jsinit
$handlersjs
});
EOD;
$js .= "Y = YUI(yui3loader).use('node', function(Y) {\n{$inyuijs}{$ondomreadyjs}{$jsinit}{$handlersjs}\n});";
$output .= html_writer::script($js);
@ -1112,6 +1128,89 @@ abstract class required_js_code extends requirement_base {
}
}
/**
* This class is used to manage an object initialisation in JavaScript.
*
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*/
class required_js_object_init extends required_js_code {
/**
* The variable to assign the object to or null for none
* @var string|null
*/
protected $var;
/**
* The class of the object to initialise
* @var string
*/
protected $class;
/**
* Arguments to pass in to the constructor
*/
protected $arguments;
/**
* Required YUI modules
*/
protected $requirements;
protected $delay = 0;
/**
* Constructor. Normally instances of this class should not be created directly.
* Client code should create them via the page_requirements_manager
* method {@link page_requirements_manager::js_object_init()}.
*
* @param page_requirements_manager $manager the page_requirements_manager we are associated with.
* @param string|null $var
* @param string $class
* @param array|null $arguments
* @param array|null $requirements
*/
public function __construct(page_requirements_manager $manager, $var, $class, array $arguments = null, array $requirements = null) {
parent::__construct($manager);
$this->when = page_requirements_manager::WHEN_IN_YUI;
$this->var = $var;
$this->class = $class;
$this->arguments = $arguments;
$this->requirements = $requirements;
}
/**
* Gets the actual JavaScript code for the required object initialisation
* @return string
*/
public function get_js_code() {
return js_writer::object_init($this->var, $this->class, $this->arguments, $this->requirements, $this->delay);
}
/**
* Indicate that this initalisation should be called in YUI's onDomReady event.
*
* Thisis needed mostly for buggy IE browsers because they have problems
* when JS starts modifying DOM structure before the DOM is ready.
*/
public function on_dom_ready() {
if ($this->is_done() || $this->when < page_requirements_manager::WHEN_IN_YUI) {
return;
}
$this->when = page_requirements_manager::WHEN_ON_DOM_READY;
}
/**
* Indicate that this function should be called a certain number of seconds
* after the page has finished loading. (More exactly, a number of seconds
* after the onDomReady event fires.)
*
* @param integer $seconds the number of seconds delay.
*/
public function after_delay($seconds) {
if ($seconds) {
$this->on_dom_ready();
}
$this->delay = $seconds;
}
}
/**
* This class represents a JavaScript function that must be called from the HTML

View File

@ -1413,711 +1413,4 @@ function get_image_url(imagename, component) {
function submitFormById(id) {
submit_form_by_id(null, {id: id});
}
/**
* START OF BLOCKS CODE
* This code can be included in the footer instead of the header if we ever
* have a static JS file that will be loaded in the footer.
* Once this is done we will then also be able to remove the blocks.dock.init
* function and call
*/
/**
* This namespace will contain all of content (functions, classes, properties)
* for the block system
* @namespace
*/
var blocks = blocks || {};
blocks.setup_generic_block = function(uid) {
Y.use('base','dom','io','node', 'event-custom', function() {
var block = new blocks.genericblock(uid);
block.init();
});
}
/**
* The dock namespace: Contains all things dock related
* @namespace
*/
blocks.dock = {
count:0, // The number of dock items currently
totalcount:0, // The number of dock items through the page life
exists:false, // True if the dock exists
items:[], // An array of dock items
node:null, // The YUI node for the dock itself
earlybinds:[], // Events added before the dock was augmented to support events
/**
* Strings used by the dock/dockitems
* @namespace
*/
strings:{
addtodock : '[[addtodock]]',
undockitem : '[[undockitem]]',
undockall : '[[undockall]]'
},
/**
* Configuration parameters used during the initialisation and setup
* of dock and dock items.
* This is here specifically so that themers can override core parameters and
* design aspects without having to re-write navigation
* @namespace
*/
cfg:{
buffer:10, // Buffer used when containing a panel
position:'left', // position of the dock
orientation:'vertical', // vertical || horizontal determines if we change the title
/**
* Display parameters for the dock
* @namespace
*/
display:{
spacebeforefirstitem: 10, // Space between the top of the dock and the first item
mindisplaywidth: null // Minimum width for the display of dock items
},
/**
* CSS classes to use with the dock
* @namespace
*/
css: {
dock:'dock', // CSS Class applied to the dock box
dockspacer:'dockspacer', // CSS class applied to the dockspacer
controls:'controls', // CSS class applied to the controls box
body:'has_dock', // CSS class added to the body when there is a dock
dockeditem:'dockeditem', // CSS class added to each item in the dock
dockedtitle:'dockedtitle', // CSS class added to the item's title in each dock
activeitem:'activeitem' // CSS class added to the active item
},
/**
* Configuration options for the panel that items are shown in
* @namespace
*/
panel: {
close:false, // Show a close button on the panel
draggable:false, // Make the panel draggable
underlay:"none", // Use a special underlay
modal:false, // Throws a lightbox if set to true
keylisteners:null, // An array of keylisterners to attach
visible:false, // Visible by default
effect: null, // An effect that should be used with the panel
monitorresize:false, // Monitor the resize of the panel
context:null, // Sets up contexts for the panel
fixedcenter:false, // Always displays the panel in the center of the screen
zIndex:null, // Sets a specific z index for the panel
constraintoviewport: false, // Constrain the panel to the viewport
autofillheight:'body' // Which container element should fill out empty space
}
},
/**
* Augments the classes as required and processes early bindings
*/
init:function() {
Y.use('event-custom','event', 'node', function(Y){
// Give the dock item class the event properties/methods
Y.augment(blocks.dock.item, Y.EventTarget);
Y.augment(blocks.dock, Y.EventTarget, true);
// Re-apply early bindings properly now that we can
blocks.dock.apply_binds();
});
},
/**
* Adds a dock item into the dock
* @function
* @param {blocks.dock.item} item
*/
add:function(item) {
item.id = this.totalcount;
this.count++;
this.totalcount++;
this.items[item.id] = item;
this.draw();
this.items[item.id].draw();
this.fire('dock:itemadded', item);
},
append : function(docknode) {
blocks.dock.node.one('#dock_item_container').append(docknode);
},
/**
* Draws the dock
* @function
* @return bool
*/
draw:function() {
if (this.node !== null) {
return true;
}
this.fire('dock:drawstarted');
this.item_sizer.init();
this.node = Y.Node.create('<div id="dock" class="'+blocks.dock.cfg.css.dock+' '+blocks.dock.cfg.css.dock+'_'+blocks.dock.cfg.position+'_'+blocks.dock.cfg.orientation+'"></div>');
this.node.appendChild(Y.Node.create('<div class="'+blocks.dock.cfg.css.dockspacer+'" style="height:'+blocks.dock.cfg.display.spacebeforefirstitem+'px"></div>'));
this.node.appendChild(Y.Node.create('<div id="dock_item_container"></div>'));
if (Y.UA.ie > 0 && Y.UA.ie < 7) {
this.node.setStyle('height', this.node.get('winHeight')+'px');
}
var dockcontrol = Y.Node.create('<div class="'+blocks.dock.cfg.css.controls+'"></div>');
var removeall = Y.Node.create('<img src="'+get_image_url('t/dock_to_block', 'moodle')+'" alt="'+blocks.dock.strings.undockall+'" title="'+blocks.dock.strings.undockall+'" />');
removeall.on('removeall|click', this.remove_all, this);
dockcontrol.appendChild(removeall);
this.node.appendChild(dockcontrol);
Y.one(document.body).appendChild(this.node);
Y.one(document.body).addClass(blocks.dock.cfg.css.body);
this.fire('dock:drawcompleted');
return true;
},
/**
* Removes the node at the given index and puts it back into conventional page sturcture
* @function
* @param {int} uid Unique identifier for the block
* @return {boolean}
*/
remove:function(uid) {
if (!this.items[uid]) {
return false;
}
this.items[uid].remove();
delete this.items[uid];
this.count--;
this.fire('dock:itemremoved', uid);
if (this.count===0) {
this.fire('dock:toberemoved');
this.items = [];
this.node.remove();
this.node = null;
this.fire('dock:removed');
}
return true;
},
/**
* Removes all nodes and puts them back into conventional page sturcture
* @function
* @return {boolean}
*/
remove_all:function() {
for (var i in this.items) {
this.items[i].remove();
this.count--;
delete this.items[i];
}
Y.fire('dock:toberemoved');
this.items = [];
this.node.remove();
this.node = null;
Y.fire('dock:removed');
return true;
},
/**
* Resizes the active item
* @function
* @param {Event} e
*/
resize:function(e){
for (var i in this.items) {
if (this.items[i].active) {
this.items[i].resize_panel(e);
}
}
},
/**
* Hides all [the active] items
* @function
*/
hide_all:function() {
for (var i in this.items) {
this.items[i].hide();
}
},
/**
* This smart little function allows developers to attach event listeners before
* the dock has been augmented to allows event listeners.
* Once the augmentation is complete this function will be replaced with the proper
* on method for handling event listeners.
* Finally apply_binds needs to be called in order to properly bind events.
* @param {string} event
* @param {function} callback
*/
on : function(event, callback) {
this.earlybinds.push({event:event,callback:callback});
},
/**
* This function takes all early binds and attaches them as listeners properly
* This should only be called once augmentation is complete.
*/
apply_binds : function() {
for (var i in this.earlybinds) {
var bind = this.earlybinds[i];
this.on(bind.event, bind.callback);
}
this.earlybinds = [];
},
item_sizer : {
enabled : false,
init : function() {
blocks.dock.on('dock:itemadded', this.check_if_required, this);
blocks.dock.on('dock:itemremoved', this.check_if_required, this);
Y.on('windowresize', this.check_if_required, this);
},
check_if_required : function() {
var possibleheight = blocks.dock.node.get('offsetHeight') - blocks.dock.node.one('.controls').get('offsetHeight') - (blocks.dock.cfg.buffer*3) - (blocks.dock.items.length*2);
var totalheight = 0;
for (var id in blocks.dock.items) {
var dockedtitle = Y.get(blocks.dock.items[id].title).ancestor('.'+blocks.dock.cfg.css.dockedtitle);
if (dockedtitle) {
if (this.enabled) {
dockedtitle.setStyle('height', 'auto');
}
totalheight += dockedtitle.get('offsetHeight') || 0;
}
}
if (totalheight > possibleheight) {
this.enable(possibleheight);
}
},
enable : function(possibleheight) {
this.enabled = true;
var runningcount = 0;
var usedheight = 0;
for (var id in blocks.dock.items) {
var itemtitle = Y.get(blocks.dock.items[id].title).ancestor('.'+blocks.dock.cfg.css.dockedtitle);
if (!itemtitle) {
continue;
}
var itemheight = Math.floor((possibleheight-usedheight) / (blocks.dock.count - runningcount));
Y.log("("+possibleheight+"-"+usedheight+") / ("+blocks.dock.count+" - "+runningcount+") = "+itemheight);
var offsetheight = itemtitle.get('offsetHeight');
itemtitle.setStyle('overflow', 'hidden');
if (offsetheight > itemheight) {
itemtitle.setStyle('height', itemheight+'px');
usedheight += itemheight;
} else {
usedheight += offsetheight;
}
runningcount++;
}
Y.log('possible: '+possibleheight+' - used height: '+usedheight);
}
},
/**
* Namespace containing methods and properties that will be prototyped
* to the generic block class and possibly overriden by themes
* @namespace
*/
abstract_block_class : {
id : null, // The block instance id
cachedcontentnode : null, // The cached content node for the actual block
blockspacewidth : null, // The width of the block's original container
skipsetposition : false, // If true the user preference isn't updated
/**
* This function should be called within the block's constructor and is used to
* set up the initial controls for swtiching block position as well as an initial
* moves that may be required.
*
* @param {YUI.Node} node The node that contains all of the block's content
*/
init : function(node) {
if (!node) {
node = Y.one('#inst'+this.id);
if (!node) {
return;
}
}
var commands = node.one('.header .title .commands');
if (!commands) {
commands = Y.Node.create('<div class="commands"></div>');
if (node.one('.header .title')) {
node.one('.header .title').append(commands);
}
}
var moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>');
moveto.append(Y.Node.create('<img src="'+get_image_url('t/dock_to_block', 'moodle')+'" alt="'+blocks.dock.strings.undockitem+'" title="'+blocks.dock.strings.undockitem+'" />'));
if (location.href.match(/\?/)) {
moveto.set('href', location.href+'&dock='+this.id);
} else {
moveto.set('href', location.href+'?dock='+this.id);
}
commands.append(moveto);
commands.all('a.moveto').on('movetodock|click', this.move_to_dock, this);
node.all('.customcommand').each(function(){
this.remove();
commands.appendChild(this);
});
// Move the block straight to the dock if required
if (node.hasClass('dock_on_load')) {
node.removeClass('dock_on_load')
this.skipsetposition = true;
this.move_to_dock();
}
},
/**
* This function is reponsible for moving a block from the page structure onto the
* dock
* @param {event}
*/
move_to_dock : function(e) {
if (e) {
e.halt(true);
}
var node = Y.one('#inst'+this.id);
var blockcontent = node.one('.content');
if (!blockcontent) {
return;
}
this.cachedcontentnode = node;
node.all('a.moveto').each(function(moveto){
Y.Event.purgeElement(Y.Node.getDOMNode(moveto), false, 'click');
if (moveto.hasClass('customcommand')) {
moveto.all('img').each(function(movetoimg){
movetoimg.setAttribute('src', get_image_url('t/dock_to_block', 'moodle'));
movetoimg.setAttribute('alt', blocks.dock.strings.undockitem);
movetoimg.setAttribute('title', blocks.dock.strings.undockitem);
}, this);
}
}, this);
var placeholder = Y.Node.create('<div id="content_placeholder_'+this.id+'"></div>');
node.replace(Y.Node.getDOMNode(placeholder));
node = null;
var spacewidth = this.resize_block_space(placeholder);
var blocktitle = Y.Node.getDOMNode(this.cachedcontentnode.one('.title h2')).cloneNode(true);
blocktitle.innerHTML = blocktitle.innerHTML.replace(/([a-zA-Z0-9])/g, "$1<br />");
var commands = this.cachedcontentnode.all('.title .commands');
var blockcommands = Y.Node.create('<div class="commands"></div>');
if (commands.size() > 0) {
blockcommands = commands.item(0);
}
// Create a new dock item for the block
var dockitem = new blocks.dock.item(this.id, blocktitle, blockcontent, blockcommands);
if (spacewidth !== null && blocks.dock.cfg.display.mindisplaywidth == null) {
dockitem.cfg.display.mindisplaywidth = spacewidth;
}
// Wire the draw events to register remove events
dockitem.on('dockeditem:drawcomplete', function(e){
// check the contents block [editing=off]
this.contents.all('a.moveto').on('returntoblock|click', function(e){
e.halt();
blocks.dock.remove(this.id)
}, this);
// check the commands block [editing=on]
this.commands.all('a.moveto').on('returntoblock|click', function(e){
e.halt();
blocks.dock.remove(this.id)
}, this);
}, dockitem);
// Register an event so that when it is removed we can put it back as a block
dockitem.on('dockitem:itemremoved', this.return_to_block, this, dockitem);
blocks.dock.add(dockitem);
if (!this.skipsetposition) {
// save the users preference
set_user_preference('docked_block_instance_'+this.id, 1);
} else {
this.skipsetposition = false;
}
},
/**
* Resizes the space that contained blocks if there were no blocks left in
* it. e.g. if all blocks have been moved to the dock
* @param {Y.Node} node
*/
resize_block_space : function(node) {
node = node.ancestor('.block-region');
if (node) {
var width = node.getStyle('width');
if (node.all('.sideblock').size() === 0 && this.blockspacewidth === null) {
// If the node has no children then we can shrink it
this.blockspacewidth = width;
node.setStyle('width', '0px');
} else if (this.blockspacewidth !== null) {
// Otherwise if it contains children and we have saved a width
// we can reapply the width
node.setStyle('width', this.blockspacewidth);
this.blockspacewidth = null;
}
return width;
}
return null;
},
/**
* This function removes a block from the dock and puts it back into the page
* structure.
* @param {blocks.dock.class.item}
*/
return_to_block : function(dockitem) {
var placeholder = Y.one('#content_placeholder_'+this.id);
this.cachedcontentnode.appendChild(dockitem.contents);
placeholder.replace(Y.Node.getDOMNode(this.cachedcontentnode));
this.cachedcontentnode = Y.one('#'+this.cachedcontentnode.get('id'));
this.resize_block_space(this.cachedcontentnode);
this.cachedcontentnode.all('a.moveto').each(function(moveto){
Y.Event.purgeElement(Y.Node.getDOMNode(moveto), false, 'click');
moveto.on('movetodock|click', this.move_to_dock, this);
if (moveto.hasClass('customcommand')) {
moveto.all('img').each(function(movetoimg){
movetoimg.setAttribute('src', get_image_url('t/block_to_dock', 'moodle'));
movetoimg.setAttribute('alt', blocks.dock.strings.addtodock);
movetoimg.setAttribute('title', blocks.dock.strings.addtodock);
}, this);
}
}, this);
var commands = this.cachedcontentnode.all('.commands');
var blocktitle = this.cachedcontentnode.all('.title');
if (commands.size() === 1 && blocktitle.size() === 1) {
commands.item(0).remove();
blocktitle.item(0).append(commands.item(0));
}
this.cachedcontentnode = null;
set_user_preference('docked_block_instance_'+this.id, 0);
return true;
}
},
/**
* This namespace contains the generic properties, methods and events
* that will be bound to the blocks.dock.item class.
* These can then be overriden to customise the way dock items work/display
* @namespace
*/
abstract_item_class : {
id : null, // The unique id for the item
name : null, // The name of the item
title : null, // The title of the item
contents : null, // The content of the item
commands : null, // The commands for the item
active : false, // True if the item is being shown
panel : null, // The YUI2 panel the item will be shown in
preventhide : false, // If true the next call to hide will be ignored
cfg : null, // The config options for this item by default blocks.cfg
/**
* Initialises all of the items events
* @function
*/
init_events : function() {
this.publish('dockeditem:drawstart', {prefix:'dockeditem'});
this.publish('dockeditem:drawcomplete', {prefix:'dockeditem'});
this.publish('dockeditem:showstart', {prefix:'dockeditem'});
this.publish('dockeditem:showcomplete', {prefix:'dockeditem'});
this.publish('dockeditem:hidestart', {prefix:'dockeditem'});
this.publish('dockeditem:hidecomplete', {prefix:'dockeditem'});
this.publish('dockeditem:resizestart', {prefix:'dockeditem'});
this.publish('dockeditem:resizecomplete', {prefix:'dockeditem'});
this.publish('dockeditem:itemremoved', {prefix:'dockeditem'});
},
/**
* This function draws the item on the dock
*/
draw : function() {
this.fire('dockeditem:drawstart');
var dockitemtitle = Y.Node.create('<div id="dock_item_'+this.id+'_title" class="'+this.cfg.css.dockedtitle+'"></div>');
dockitemtitle.append(this.title);
var dockitem = Y.Node.create('<div id="dock_item_'+this.id+'" class="'+this.cfg.css.dockeditem+'"></div>');
if (blocks.dock.count === 1) {
dockitem.addClass('firstdockitem');
}
dockitem.append(dockitemtitle);
if (this.commands.hasChildNodes) {
this.contents.appendChild(this.commands);
}
blocks.dock.append(dockitem);
var position = dockitemtitle.getXY();
position[0] += parseInt(dockitemtitle.get('offsetWidth'));
if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) {
position[0] -= 2;
}
this.panel = new YAHOO.widget.Panel('dock_item_panel_'+this.id, {
close:this.cfg.panel.close,
draggable:this.cfg.panel.draggable,
underlay:this.cfg.panel.underlay,
modal: this.cfg.panel.modal,
keylisteners: this.cfg.panel.keylisteners,
visible:this.cfg.panel.visible,
effect:this.cfg.panel.effect,
monitorresize:this.cfg.panel.monitorresize,
context: this.cfg.panel.context,
fixedcenter: this.cfg.panel.fixedcenter,
zIndex: this.cfg.panel.zIndex,
constraintoviewport: this.cfg.panel.constraintoviewport,
xy:position,
autofillheight:this.cfg.panel.autofillheight});
this.panel.showEvent.subscribe(this.resize_panel, this, true);
this.panel.setBody(Y.Node.getDOMNode(this.contents));
this.panel.render(blocks.dock.node);
if (this.cfg.display.mindisplaywidth !== null && Y.one(this.panel.body).getStyle('minWidth') == '0px') {
Y.one(this.panel.body).setStyle('minWidth', this.cfg.display.mindisplaywidth);
Y.one(this.panel.body).setStyle('minHeight', dockitemtitle.get('offsetHeight')+'px');
}
dockitem.on('showitem|mouseover', this.show, this);
this.fire('dockeditem:drawcomplete');
},
/**
* This function removes the node and destroys it's bits
* @param {Event} e
*/
remove : function (e) {
this.hide(e);
Y.one('#dock_item_'+this.id).remove();
this.panel.destroy();
this.fire('dockitem:itemremoved');
},
/**
* This function toggles makes the item active and shows it
* @param {event}
*/
show : function(e) {
blocks.dock.hide_all();
this.fire('dockeditem:showstart');
this.panel.show(e, this);
this.active = true;
Y.one('#dock_item_'+this.id+'_title').addClass(this.cfg.css.activeitem);
Y.detach('mouseover', this.show, Y.one('#dock_item_'+this.id));
Y.one('#dock_item_panel_'+this.id).on('dockpreventhide|click', function(){this.preventhide=true;}, this);
Y.one('#dock_item_'+this.id).on('dockhide|click', this.hide, this);
Y.get(window).on('dockresize|resize', this.resize_panel, this);
Y.get(document.body).on('dockhide|click', this.hide, this);
this.fire('dockeditem:showcomplete');
return true;
},
/**
* This function hides the item and makes it inactive
* @param {event}
*/
hide : function(e) {
// Ignore this call is preventhide is true
if (this.preventhide===true) {
this.preventhide = false;
} else if (this.active) {
this.fire('dockeditem:hidestart');
this.active = false;
Y.one('#dock_item_'+this.id+'_title').removeClass(this.cfg.css.activeitem);
Y.one('#dock_item_'+this.id).on('showitem|mouseover', this.show, this);
Y.get(window).detach('dockresize|resize');
Y.get(document.body).detach('dockhide|click');
this.panel.hide(e, this);
this.fire('dockeditem:hidecomplete');
}
},
/**
* This function checks the size and position of the panel and moves/resizes if
* required to keep it within the bounds of the window.
*/
resize_panel : function() {
this.fire('dockeditem:resizestart');
var panelbody = Y.one(this.panel.body);
var buffer = this.cfg.buffer;
var screenheight = parseInt(Y.get(document.body).get('winHeight'));
var panelheight = parseInt(panelbody.get('offsetHeight'));
var paneltop = parseInt(this.panel.cfg.getProperty('y'));
var titletop = parseInt(Y.one('#dock_item_'+this.id+'_title').getY());
var scrolltop = window.pageYOffset || document.body.scrollTop || 0;
// This makes sure that the panel is the same height as the dock title to
// begin with
if (paneltop > (buffer+scrolltop) && paneltop > (titletop+scrolltop)) {
this.panel.cfg.setProperty('y', titletop+scrolltop);
}
// This makes sure that if the panel is big it is moved up to ensure we don't
// have wasted space above the panel
if ((paneltop+panelheight)>(screenheight+scrolltop) && paneltop > buffer) {
paneltop = (screenheight-panelheight-buffer);
if (paneltop<buffer) {
paneltop = buffer;
}
this.panel.cfg.setProperty('y', paneltop+scrolltop);
}
// This makes the panel constrain to the screen's height if the panel is big
if (paneltop <= buffer && ((panelheight+paneltop*2) > screenheight || panelbody.hasClass('oversized_content'))) {
this.panel.cfg.setProperty('height', screenheight-(buffer*3));
panelbody.setStyle('height', (screenheight-(buffer*3)-10)+'px');
panelbody.addClass('oversized_content');
}
this.fire('dockeditem:resizecomplete');
}
}
};
/**
* This class represents a generic block
* @class genericblock
* @constructor
* @param {int} uid
*/
blocks.genericblock = function(uid){
// Save the unique id as the blocks id
if (uid && this.id==null) {
this.id = uid;
}
};
/** Properties */
blocks.genericblock.prototype.name = blocks.dock.abstract_block_class.name;
blocks.genericblock.prototype.cachedcontentnode = blocks.dock.abstract_block_class.cachedcontentnode;
blocks.genericblock.prototype.blockspacewidth = blocks.dock.abstract_block_class.blockspacewidth;
blocks.genericblock.prototype.skipsetposition = blocks.dock.abstract_block_class.skipsetposition;
/** Methods **/
blocks.genericblock.prototype.init = blocks.dock.abstract_block_class.init;
blocks.genericblock.prototype.move_to_dock = blocks.dock.abstract_block_class.move_to_dock;
blocks.genericblock.prototype.resize_block_space = blocks.dock.abstract_block_class.resize_block_space;
blocks.genericblock.prototype.return_to_block = blocks.dock.abstract_block_class.return_to_block;
/**
* This class represents an item in the dock
* @class item
* @constructor
* @param {int} uid The unique ID for the item
* @param {Y.Node} title
* @param {Y.Node} contents
* @param {Y.Node} commands
*/
blocks.dock.item = function(uid, title, contents, commands){
if (uid && this.id==null) this.id = uid;
if (title && this.title==null) this.title = title;
if (contents && this.contents==null) this.contents = contents;
if (commands && this.commands==null) this.commands = commands;
this.init_events();
}
/** Properties */
blocks.dock.item.prototype.id = blocks.dock.abstract_item_class.id;
blocks.dock.item.prototype.name = blocks.dock.abstract_item_class.name;
blocks.dock.item.prototype.title = blocks.dock.abstract_item_class.title;
blocks.dock.item.prototype.contents = blocks.dock.abstract_item_class.contents;
blocks.dock.item.prototype.commands = blocks.dock.abstract_item_class.commands;
blocks.dock.item.prototype.active = blocks.dock.abstract_item_class.active;
blocks.dock.item.prototype.panel = blocks.dock.abstract_item_class.panel;
blocks.dock.item.prototype.preventhide = blocks.dock.abstract_item_class.preventhide;
blocks.dock.item.prototype.cfg = blocks.dock.cfg;
/** Methods **/
blocks.dock.item.prototype.init_events = blocks.dock.abstract_item_class.init_events;
blocks.dock.item.prototype.draw = blocks.dock.abstract_item_class.draw;
blocks.dock.item.prototype.remove = blocks.dock.abstract_item_class.remove;
blocks.dock.item.prototype.show = blocks.dock.abstract_item_class.show;
blocks.dock.item.prototype.hide = blocks.dock.abstract_item_class.hide;
blocks.dock.item.prototype.resize_panel = blocks.dock.abstract_item_class.resize_panel;
///////////////// END OF BLOCKS CODE \\\\\\\\\\\\\\\\\\\\\\
}

View File

@ -578,6 +578,41 @@ class js_writer {
return $js . "\n";
}
/**
* Returns JavaScript code to initialise a new object
* @param string|null $var If it is null then no var is assigned the new object
* @param string $class
* @param array $arguments
* @param array $requirements
* @param int $delay
* @return string
*/
public function object_init($var, $class, array $arguments = null, array $requirements = null, $delay=0) {
if (is_array($arguments)) {
$arguments = array_map('json_encode', $arguments);
$arguments = implode(', ', $arguments);
}
if ($var === null) {
$js = "new $class($arguments);";
} else if (strpos($var, '.')!==false) {
$js = "$var = new $class($arguments);";
} else {
$js = "var $var = new $class($arguments);";
}
if ($delay) {
$delay = $delay * 1000; // in miliseconds
$js = "setTimeout(function() { $js }, $delay);";
}
if (count($requirements) > 0) {
$requirements = implode("', '", $requirements);
$js = "Y.use('$requirements', function(){ $js });";
}
return $js."\n";
}
/**
* Returns code setting value to variable
* @param string $name

View File

@ -177,7 +177,9 @@ $THEME->layouts = array(
);
/** List of javascript files that need to included on each page */
$THEME->javascripts = array();
$THEME->javascripts_footer = array('navigation');
//$THEME->javascripts_footer = array('navigation');
/**
* This enables the dock on the side of the page as this theme supports it.
*/
$THEME->enable_dock = true;