diff --git a/blocks/blocks.js b/blocks/blocks.js new file mode 100644 index 00000000000..22e2753f70b --- /dev/null +++ b/blocks/blocks.js @@ -0,0 +1,591 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * This file contains classes used to manage the navigation structures in Moodle + * and was introduced as part of the changes occuring in Moodle 2.0 + * + * @since 2.0 + * @package javascript + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * 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(); + }); +} + +/** + * @namespace + */ +blocks.dock = { + count: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:[], + 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:{ + spacebeforefirstitem: 10 // Space between the top of the dock and the first item + }, + 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 + }, + 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 + } + }, + /** + * Adds a dock item into the dock + * @function + */ + add:function(item) { + item.id = this.count; + this.count++; + this.items[item.id] = item; + this.draw(); + this.items[item.id].draw(); + this.fire('dock:itemadded', item); + }, + /** + * Draws the dock + * @function + */ + draw:function() { + if (this.node !== null) { + return true; + } + this.fire('dock:drawstarted'); + this.node = Y.Node.create('
'); + this.node.appendChild(Y.Node.create('
')); + if (Y.UA.ie > 0 && Y.UA.ie < 7) { + this.node.setStyle('height', this.node.get('winHeight')+'px'); + } + + var dockcontrol = Y.Node.create('
'); + var removeall = Y.Node.create(''+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 + */ + remove:function(uid) { + this.items[uid].remove(); + this.fire('dock:itemremoved', uid); + this.count--; + if (this.count===0) { + this.fire('dock:toberemoved'); + this.items = []; + this.node.remove(); + this.node = null; + this.fire('dock:removed'); + } + }, + /** + * Removes all nodes and puts them back into conventional page sturcture + * @function + */ + remove_all:function() { + for (var i in this.items) { + this.items[i].remove(); + this.items[i] = null; + } + Y.fire('dock:toberemoved'); + this.items = []; + this.node.remove(); + this.node = null; + Y.fire('dock:removed'); + }, + /** + * Resizes the active item + * @function + */ + resize:function(e){ + for (var i in this.items) { + if (this.items[i].active) { + this.items[i].resize_panel(e); + } + } + }, + /** + * Hides all (the active) item + * @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 = []; + }, + /** + * 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, + cachedcontentnode : null, + blockspacewidth : null, + skipsetposition : false, + + /** + * 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} The node that contains all of the block's content + */ + init : function(node) { + if (!node) { + node = Y.one('#inst'+this.id); + } + + var commands = node.one('.header .title .commands'); + if (!commands) { + commands = Y.Node.create('
'); + if (node.one('.header .title')) { + node.one('.header .title').append(commands); + } + } + + var moveto = Y.Node.create(''); + moveto.append(Y.Node.create(''+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); + + var customcommands = node.all('.customcommand'); + if (customcommands.size() > 0) { + customcommands.each(function(){ + this.remove(); + commands.appendChild(this); + }); + } + + 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'); + + 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('
'); + node.replace(Y.Node.getDOMNode(placeholder)); + node = null; + + 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
"); + + var commands = this.cachedcontentnode.all('.title .commands'); + var blockcommands = Y.Node.create('
'); + if (commands.size() > 0) { + blockcommands = commands.item(0); + } + + var dockitem = new blocks.dock.item(this.id, blocktitle, blockcontent, blockcommands); + 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); + dockitem.on('dock:itemremoved', this.return_to_block, this, dockitem); + blocks.dock.add(dockitem); + + if (!this.skipsetposition) { + 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 + */ + resize_block_space : function(node) { + node = node.ancestor('.block-region'); + if (node) { + if (node.all('.sideblock').size() === 0 && this.blockspacewidth === null) { + this.blockspacewidth = node.getStyle('width'); + node.setStyle('width', '0px'); + } else if (this.blockspacewidth !== null) { + node.setStyle('width', this.blockspacewidth); + this.blockspacewidth = 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; + } + }, + + abstract_item_class : { + id : null, + name : null, + title : null, + contents : null, + commands : null, + events : null, + active : false, + panel : null, + preventhide : false, + cfg : null, + + 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('
'); + dockitemtitle.append(this.title); + var dockitem = Y.Node.create('
'); + if (blocks.dock.count === 1) { + dockitem.addClass('firstdockitem'); + } + dockitem.append(dockitemtitle); + if (this.commands.hasChildNodes) { + this.contents.appendChild(this.commands); + } + blocks.dock.node.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); + dockitem.on('showitem|mouseover', this.show, this); + this.fire('dockeditem:drawcomplete'); + }, + /** + * This function removes the node and destroys it's bits + */ + remove : function (e) { + this.hide(e); + Y.one('#dock_item_'+this.id).remove(); + this.panel.destroy(); + this.fire('dock: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) { + 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 screenheight || panelbody.hasClass('oversized_content'))) { + this.panel.cfg.setProperty('height', screenheight-(buffer*2)); + panelbody.setStyle('height', (screenheight-(buffer*3))+'px'); + panelbody.addClass('oversized_content'); + } + this.fire('dockeditem:resizecomplete'); + } + } +}; + +/** + * This class represents a generic block + * @class genericblock + * @constructor + */ +blocks.genericblock = function(uid){ + 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 + */ +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.events = blocks.dock.abstract_item_class.events; +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; + +YUI({base: moodle_cfg.yui3loaderBase}).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); + blocks.dock.apply_binds(); +}); \ No newline at end of file diff --git a/blocks/global_navigation_tree/block_global_navigation_tree.php b/blocks/global_navigation_tree/block_global_navigation_tree.php index 61d54342bf0..fc790b658a2 100644 --- a/blocks/global_navigation_tree/block_global_navigation_tree.php +++ b/blocks/global_navigation_tree/block_global_navigation_tree.php @@ -79,6 +79,12 @@ class block_global_navigation_tree extends block_tree { return true; } + function get_required_javascript() { + $this->_initialise_dock(); + $this->page->requires->js('blocks/global_navigation_tree/navigation.js'); + user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT); + } + /** * Gets the content for this block by grabbing it from $this->page */ @@ -142,21 +148,14 @@ class block_global_navigation_tree extends block_tree { $this->showmyhistory(); } - $togglesidetabdisplay = get_string('togglesidetabdisplay', $this->blockname); - $toggleblockdisplay = get_string('toggleblockdisplay', $this->blockname); - - // Get the expandable items so we can pass them to JS $expandable = array(); $this->page->navigation->find_expandable($expandable); - $args = array('expansions'=>$expandable,'instance'=>$this->instance->id); - $args['togglesidetabdisplay'] = $togglesidetabdisplay; - $args['toggleblockdisplay'] = $toggleblockdisplay; - // Give JS some information we will use within the JS tree object - $this->page->requires->data_for_js('globalnav'.block_global_navigation_tree::$navcount, $args); + // Initialise the JS tree object - $this->id = 'globalnav'.block_global_navigation_tree::$navcount; - $this->page->requires->js_function_call('setup_new_navtree', array($this->id))->on_dom_ready(); + $args = array($this->instance->id,array('expansions'=>$expandable,'instance'=>$this->instance->id)); + $this->page->requires->js_function_call('blocks.navigation.setup_new_tree', $args)->on_dom_ready(); + // Grab the items to display $this->content->items = array($this->page->navigation); @@ -165,21 +164,6 @@ class block_global_navigation_tree extends block_tree { $this->content->footer .= $OUTPUT->action_icon($reloadlink, get_string('reload'), 't/reload'); - if (empty($this->config->enablesidebarpopout) || $this->config->enablesidebarpopout == 'yes') { - user_preference_allow_ajax_update('nav_in_tab_panel_globalnav'.block_global_navigation_tree::$navcount, PARAM_INT); - - $movelink = new html_link($this->page->url); - $movelink->add_classes('moveto customcommand requiresjs'); - if ($this->docked) { - $movelink->url->param('undock', $this->instance->id); - $moveicon = $OUTPUT->action_icon($movelink, $toggleblockdisplay, 't/movetoblock'); - } else { - $movelink->url->param('dock', $this->instance->id); - $moveicon = $OUTPUT->action_icon($movelink, $toggleblockdisplay, 't/movetosidetab'); - } - $this->content->footer .= $moveicon; - } - // Set content generated to true so that we know it has been done $this->contentgenerated = true; return true; @@ -200,14 +184,14 @@ class block_global_navigation_tree extends block_tree { $attributes = parent::html_attributes(); if ($this->docked===null) { - $this->docked = get_user_preferences('nav_in_tab_panel_globalnav'.block_global_navigation_tree::$navcount, 0); + $this->docked = get_user_preferences('docked_block_instance_'.$this->instance->id, 0); } if (!empty($this->config->enablehoverexpansion) && $this->config->enablehoverexpansion == 'yes') { - $attributes['class'] .= ' sideblock_js_expansion'; + $attributes['class'] .= ' block_js_expansion'; } if ($this->docked) { - $attributes['class'] .= ' sideblock_js_sidebarpopout'; + $attributes['class'] .= ' dock_on_load'; } return $attributes; } diff --git a/blocks/global_navigation_tree/navigation.js b/blocks/global_navigation_tree/navigation.js new file mode 100644 index 00000000000..39bd0f2f0f7 --- /dev/null +++ b/blocks/global_navigation_tree/navigation.js @@ -0,0 +1,298 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * This file contains classes used to manage the navigation structures in Moodle + * and was introduced as part of the changes occuring in Moodle 2.0 + * + * @since 2.0 + * @package javascript + * @copyright 2009 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * @namespace + */ +var blocks = blocks || {}; + +/** + * This namespace will contain all of the contents of the navigation blocks + * global navigation and settings. + * @namespace + */ +blocks.navigation = { + /** The number of expandable branches in existence */ + expandablebranchcount:0, + /** An array of initialised trees */ + treecollection:[], + /** + * @namespace + */ + classes:{}, + /** + * @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); + }); + } +}; + +/** + * @class tree + * @constructor + * @base 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) { + this.id = id; + this.key = key; + this.type = 'blocks.navigation.classes.tree'; + this.errorlog = []; + this.ajaxbranches = 0; + this.expansions = []; + this.instance = null; + this.cachedcontentnode = null; + this.cachedfooter = null; + this.position = 'block'; + this.skipsetposition = false; + if (properties) { + if (properties.expansions) { + this.expansions = properties.expansions; + } + if (properties.instance) { + this.instance = properties.instance; + } + } + + if (Y.one('#inst'+this.id) === null) { + return; + } + + 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++; + } + + var node = Y.one('#inst'+this.id); + node.all('.tree_item.branch').on('click', this.toggleexpansion , this); + + this.init(node); + + if (node.hasClass('block_js_expansion')) { + node.on('mouseover', function(e){this.toggleClass('mouseover');}, node); + node.on('mouseout', function(e){this.toggleClass('mouseover');}, node); + } +} + +/** + * Loads a branch via AJAX + * @param {event} The event object + * @param {object} A branch to load via ajax + */ +blocks.navigation.classes.tree.prototype.init_load_ajax = function(e, branch) { + e.stopPropagation(); + if (e.target.get('nodeName').toUpperCase() != 'P') { + return true; + } + var cfginstance = (this.instance != null)?'&instance='+this.instance:''; + Y.io(moodle_cfg.wwwroot+'/lib/ajax/getnavbranch.php', { + method:'POST', + data:'elementid='+branch.id+'&id='+branch.branchid+'&type='+branch.type+'&sesskey='+moodle_cfg.sesskey+cfginstance, + on: { + complete:this.load_ajax, + success:function() {Y.detach('click', this.init_load_ajax, e.target);} + }, + context:this, + arguments:{ + target:e.target + } + }); + return true; +} + +/** + * Takes an branch provided through ajax and loads it into the tree + */ +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 + blocks.dock.resize(); + return true; + } + } + args.target.replaceClass('branch', 'emptybranch'); + return true; +} + +/** + * Adds a branch into the tree provided with some XML + */ +blocks.navigation.classes.tree.prototype.add_branch = function(branchxml, target, depth) { + + var branch = new blocks.navigation.classes.branch(this); + branch.construct_from_xml(branchxml); + + var childrenul = false; + if (depth === 1) { + if (!branch.haschildren) { + return false; + } + childrenul = Y.Node.create('
    '); + target.appendChild(childrenul); + } else { + childrenul = branch.inject_into_dom(target); + } + + if (childrenul) { + for (var i=0;i'); + var branchp = Y.Node.create('

    '); + + if ((this.expandable !== null || this.haschildren) && this.expansionceiling===null) { + branchli.addClass('collapsed'); + branchp.addClass('branch'); + branchp.on('click', this.tree.toggleexpansion, this.tree); + if (this.expandable) { + branchp.on('ajaxload|click', this.tree.init_load_ajax, this.tree, {branchid:this.key,id:this.id,type:this.type}); + } + } + + if (this.myclass !== null) { + branchp.addClass(this.myclass); + } + if (this.id !== null) { + branchp.setAttribute('id', this.id); + } + + var branchicon = false; + if (this.icon != null) { + branchicon = Y.Node.create(''); + this.name = ' '+this.name; + } + if (this.link === null) { + if (branchicon) { + branchp.appendChild(branchicon); + } + branchp.append(this.name.replace(/\n/g, '
    ')); + } else { + var branchlink = Y.Node.create(''+this.name.replace(/\n/g, '
    ')+'
    '); + if (branchicon) { + branchlink.appendChild(branchicon); + } + if (this.hidden) { + branchlink.addClass('dimmed'); + } + branchp.appendChild(branchlink); + } + + branchli.appendChild(branchp); + if (this.haschildren) { + var childrenul = Y.Node.create('
      '); + branchli.appendChild(childrenul); + element.appendChild(branchli); + return childrenul + } else { + element.appendChild(branchli); + return false; + } +} + +YUI({base: moodle_cfg.yui3loaderBase}).use('event-custom', 'node', function(Y){ + // Give the tree class the dock block properties + Y.augment(blocks.navigation.classes.tree, blocks.genericblock); +}); \ No newline at end of file diff --git a/blocks/moodleblock.class.php b/blocks/moodleblock.class.php index 03db833cd23..595405c58fd 100644 --- a/blocks/moodleblock.class.php +++ b/blocks/moodleblock.class.php @@ -113,6 +113,8 @@ class block_base { var $cron = NULL; + static $dockinitialised = false; + /// Class Functions /** @@ -541,10 +543,14 @@ class block_base { * @return array attribute name => value. */ function html_attributes() { - return array( + $attributes = array( 'id' => 'inst' . $this->instance->id, 'class' => 'block_' . $this->name() ); + if (get_user_preferences('docked_block_instance_'.$this->instance->id, 0)) { + $attributes['class'] .= ' dock_on_load'; + } + return $attributes; } /** @@ -566,6 +572,15 @@ class block_base { } $this->page = $page; $this->specialization(); + $this->get_required_javascript(); + } + + function get_required_javascript() { + if ($this->instance_can_dock_with_dock()) { + $this->_initialise_dock(); + $this->page->requires->js_function_call('blocks.setup_generic_block', array($this->instance->id))->on_dom_ready(); + user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT); + } } /** @@ -728,6 +743,22 @@ class block_base { function config_print() { throw new coding_exception('config_print() can no longer be used. Blocks should use a settings.php file.'); } + + public function instance_can_dock_with_dock() { + return true; + } + + public function _initialise_dock() { + if (!self::$dockinitialised) { + $this->page->requires->js('blocks/blocks.js'); + $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')); + self::$dockinitialised = true; + } + + } + /** @callback callback functions for comments api */ public static function comment_template($options) { $ret = <<_initialise_dock(); + $this->page->requires->js('blocks/global_navigation_tree/navigation.js'); + $this->page->requires->js_function_call('blocks.navigation.setup_new_tree', array($this->instance->id))->on_dom_ready(); + user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT); + } + /** * Gets the content for this block by grabbing it from $this->page */ @@ -110,17 +116,6 @@ class block_settings_navigation_tree extends block_tree { redirect($url); } - $togglesidetabdisplay = get_string('togglesidetabdisplay', $this->blockname); - $toggleblockdisplay = get_string('toggleblockdisplay', $this->blockname); - $args = array('instance'=>$this->instance->id); - $args['togglesidetabdisplay'] = $togglesidetabdisplay; - $args['toggleblockdisplay'] = $toggleblockdisplay; - // Give JS some information we will use within the JS tree object - $this->page->requires->data_for_js('settingsnav'.block_settings_navigation_tree::$navcount, $args); - - - $this->id = 'settingsnav'.block_settings_navigation_tree::$navcount; - $this->page->requires->js_function_call('setup_new_navtree', array($this->id))->on_dom_ready(); // Grab the children from settings nav, we have more than one root node // and we dont want to show the site node $this->content->items = $this->page->settingsnav->children; @@ -147,17 +142,6 @@ class block_settings_navigation_tree extends block_tree { if (!empty($this->config->enablesidebarpopout) && $this->config->enablesidebarpopout == 'yes') { user_preference_allow_ajax_update('nav_in_tab_panel_settingsnav'.block_settings_navigation_tree::$navcount, PARAM_INT); - - $movelink = new html_link($this->page->url); - $movelink->add_classes('moveto customcommand requiresjs'); - if ($this->docked) { - $movelink->url->param('undock', $this->instance->id); - $moveicon = $OUTPUT->action_icon($movelink, $toggleblockdisplay, 't/movetoblock'); - } else { - $movelink->url->param('dock', $this->instance->id); - $moveicon = $OUTPUT->action_icon($movelink, $toggleblockdisplay, 't/movetosidetab'); - } - $this->content->footer .= $moveicon; } } @@ -170,14 +154,14 @@ class block_settings_navigation_tree extends block_tree { // Check if this block has been docked if ($this->docked === null) { - $this->docked = get_user_preferences('nav_in_tab_panel_settingsnav'.block_settings_navigation_tree::$navcount, 0); + $this->docked = get_user_preferences('docked_block_instance_'.$this->instance->id, 0); } if (!empty($this->config->enablehoverexpansion) && $this->config->enablehoverexpansion == 'yes') { - $attributes['class'] .= ' sideblock_js_expansion'; + $attributes['class'] .= ' block_js_expansion'; } if ($this->docked) { - $attributes['class'] .= ' sideblock_js_sidebarpopout'; + $attributes['class'] .= ' dock_on_load'; } return $attributes; } diff --git a/lang/en_utf8/block.php b/lang/en_utf8/block.php index 70b576f73e0..e06c51d3dd0 100644 --- a/lang/en_utf8/block.php +++ b/lang/en_utf8/block.php @@ -1,6 +1,7 @@ headdone) { - throw new coding_exception('YUI3 libraries can be preloaded by PHP only from HEAD, please use YUI autoloading instead: ', $stylesheet); + throw new coding_exception('YUI3 libraries can be preloaded by PHP only from HEAD, please use YUI autoloading instead: ', $libname); } $libnames = (array)$libname; foreach ($libnames as $lib) { @@ -591,6 +591,7 @@ class page_requirements_manager { * @return string the HTML code to to at the end of the page. */ public function get_end_code() { + global $CFG; $output = $this->get_yui2lib_code(); $output .= $this->get_linked_resources_code(self::WHEN_AT_END); @@ -601,9 +602,16 @@ class page_requirements_manager { $js = $this->get_javascript_code(self::WHEN_AT_END); $ondomreadyjs = $this->get_javascript_code(self::WHEN_ON_DOM_READY, ' '); - if ($ondomreadyjs) { - $js .= "YAHOO.util.Event.onDOMReady(function() {\n" . $ondomreadyjs . "});\n"; - } + + $js .= <<forceopen = true; $this->action = new moodle_url($CFG->wwwroot); $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME); - $PAGE->requires->string_for_js('moveallsidetabstoblock','moodle'); $regenerate = optional_param('regenerate', null, PARAM_TEXT); if ($regenerate==='navigation') { $this->cache->clear(); diff --git a/pix/t/block_to_dock.png b/pix/t/block_to_dock.png new file mode 100644 index 00000000000..d72690c8701 Binary files /dev/null and b/pix/t/block_to_dock.png differ diff --git a/pix/t/dock_to_block.png b/pix/t/dock_to_block.png new file mode 100644 index 00000000000..5ace5af1c62 Binary files /dev/null and b/pix/t/dock_to_block.png differ diff --git a/theme/base/config.php b/theme/base/config.php index 4b0ef7ca73e..03070d7e5ba 100644 --- a/theme/base/config.php +++ b/theme/base/config.php @@ -150,5 +150,5 @@ $THEME->layouts = array( ); /** List of javascript files that need to included on each page */ -$THEME->javascripts = array('navigation'); -//$THEME->javascripts_footer = array(); \ No newline at end of file +$THEME->javascripts = array(); +$THEME->javascripts_footer = array('navigation'); \ No newline at end of file diff --git a/theme/base/javascript/navigation.js b/theme/base/javascript/navigation.js index e8b872f0562..7e0fbffa335 100644 --- a/theme/base/javascript/navigation.js +++ b/theme/base/javascript/navigation.js @@ -1 +1,116 @@ -/* base: javascript needed for navbar manipulations */ +/** + * So you want to override the navigation huh ?? + * Make it look like your own and totally customise it to be way cool !! + * + * Well now you can by following the instructions in this file. + * + * It will be essential to have a clear idea about what it is you want to acheive, + * whilst it is possible to override nearly all of the navbar settings/methods it's + * not nesecarily going to be an easy task. + * + * To begin you must understand the structure of the blocks and particually the navbar + * object. The following outlines the basic structure: + * + * - Namespace: blocks + * - Func: setup_generic_block Creates a new generic block instance + * - Class: genericblock Generic block class + * - Namespace: navbar + * - Var: count The # of items that have EVER existed on the navbar + * - Var: exists True if the navbar exists + * - Var: items An array of items on the navbar + * - Var: node The node that is the navbar + * - Var: strings An object containing strings for the navbar + * - Namespace: cfg + * - Var: buffer The space buffer around panels + * - Var: position The position of the navbar + * - Var: orientation The orientation of the navbar + * - Namespace: display + * ............ A series of display parameters + * - Namespace: css + * ............ A series of CSS class names + * - Namespace: panel + * ............ A series of conf options for YUI panels + * - Func: add Adds an item to the navbar + * - Func: draw Creates the navbar and adds it to the page + * - Func: remove Removes an item from the navbar + * - Func: remove_all Removes all items from the navbar + * - Func: resize Calls the navbar to resize its active item + * - Func: hide_all Calls the navbar to hide all active items + * - Class: item A navbar item class + * - Namespace: abstract_block_class A namespace containing all of the properties + * ............. and methods that will be used as the default + * ............. methods for the generic block class. + * - Namespace: abstract_item_class A namespace containing all of the properties + * ............. and methods for the navbar item class + * + * From the structure above you are able to immediatly override any of the vars + * that are associated with the navigation by simply assigning them a value as + * shown below: + * + * blocks.navbar.cfg.buffer = 20; // or + * + * You are also able to override all of the properties and methods of the two + * abstract classes that manage all of the interaction for the blocks and navbar + * items thanks to the prototyping method that is being used to build the classes. + * + * To override a method simply copy the following style of coding: + * + * blocks.genericblock.prototype.init = function(uid) { + * // The code for the new init method which will be executed in the + * // objects scope and override the old init method. + * } + * + * // OR if the following is easier for you to understand + * + * function new_init_method(uid) { + * // The code for the new init method which will be executed in the + * // objects scope and override the old init method. + * } + * blocks.genericblock.prototype.init = new_init_method() + * + * Alternativily for the navbar items class the there are a series of actions that + * get fired that you may want to listen to. The events defined are as follows: + * + * navbaritem:drawstart draw is called + * navbaritem:drawcomplete draw is complete + * navbaritem:showstart show is called + * navbaritem:showcomplete show is complete + * navbaritem:hidestart hide is called + * navbaritem:hidecomplete hide is complete + * navbaritem:resizestart resize is called + * navbaritem:resizecomplete resize is complete + * navbaritem:itemremoved item is removed from the navbar + * + * You can listen to any of these events by first finding the appropriate item within + * the navbar.items array and then calling the following on it: + * + * var uid = x; + * blocks.navbar.items[uid].on('navbaritem:showstart', callback, scope); + * function callback(navbaritem) { + * // What ever you want to do can go here + * } + * + */ + +// If this isn't set we don't need an override at all +if (blocks.genericblock) { + + /** + * Override the default resize_block_space method so that we can ensure + * it works for this template + * @param {Y.Node} blocknode + */ + blocks.genericblock.prototype.resize_block_space = function(blocknode) { + var blockregion = blocknode.ancestor('#block-region'); + if (blockregion) { + if (blockregion.all('.sideblock').size() === 0 && this.blockspacewidth === null) { + // Some spiffy code to reduce the template sideblock to 0 width + this.blockspacewidth = blockregion.getStyle('width'); + } else if (this.blockspacewidth !== null) { + // Some spiffy code to set the sideblock width back to the original width + this.blockspacewidth = null; + } + } + } + +} \ No newline at end of file diff --git a/theme/base/style/blocks.css b/theme/base/style/blocks.css index f37f39bcb4f..7a2646ca6aa 100644 --- a/theme/base/style/blocks.css +++ b/theme/base/style/blocks.css @@ -111,7 +111,7 @@ font-weight:bold; } -.jsenabled .sideblock_js_sidebarpopout, +.jsenabled .dock_on_load, .jsenabled .block_tree .collapsed ul { display: none; } diff --git a/theme/standardold/config.php b/theme/standardold/config.php index 884fc9d1fc8..80cf7a4277c 100644 --- a/theme/standardold/config.php +++ b/theme/standardold/config.php @@ -177,5 +177,5 @@ $THEME->layouts = array( ); /** List of javascript files that need to included on each page */ -$THEME->javascripts = array('navigation'); - +$THEME->javascripts = array(); +$THEME->javascripts_footer = array('navigation'); \ No newline at end of file diff --git a/theme/standardold/javascript/navigation.js b/theme/standardold/javascript/navigation.js index b23832b0219..e9ee0e4b0ce 100644 --- a/theme/standardold/javascript/navigation.js +++ b/theme/standardold/javascript/navigation.js @@ -1,882 +1,48 @@ -/* legacy standard: javascript needed for navbar manipulations */ +// This attaches a shadow to the dock when it has been drawn (added to the page) +blocks.dock.on('dock:drawcompleted', function() { + blocks.dock.node.append(shadow.create(true, true, true, true)); +}); +blocks.dock.on('dock:itemadded', function(item) { + item.on('dockeditem:showcomplete', function() { + Y.one('#dock_item_panel_'+this.id).append(shadow.create(true, true, true, false)); + }); + item.on('dockeditem:hidestart', function() { + shadow.remove(Y.one('#dock_item_panel_'+this.id)); + }); +}); -// content of this file was originally in lib/javascript-naigation.php, -// it was moved here for two reasons - testing of themes JS and second it may need -// to use different tricks in other themes with CSS column layouts - - - -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * This file contains classes used to manage the navigation structures in Moodle - * and was introduced as part of the changes occuring in Moodle 2.0 - * - * @since 2.0 - * @package javascript - * @copyright 2009 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * Some very important general namespaces to act as containers for the general - * objects required to manage the navigation. - * - * For anyone looking to improve this javascript taking a little time to turn - * the classes into namespaced classes, and giving the class structure in this file - * a similar structure to YUI on a moodle namespace would be AWESOME - */ -YAHOO.namespace('moodle.navigation'); -YAHOO.namespace('moodle.navigation.sideblockwidth'); -YAHOO.namespace('moodle.navigation.tabpanel'); -YAHOO.namespace('moodle.navigation.treecollection'); - -/** - * Instatiate some very important variables that allow us to manage the navigaiton - * objects without having to hit my arch enemy `undefined` - */ -YAHOO.moodle.navigation.sideblockwidth = null; -YAHOO.moodle.navigation.tabpanel = null; -YAHOO.moodle.navigation.treecollection = Array(); -YAHOO.moodle.navigation.expandablebranchcount = 0; - -/** - * Navigation Tree object (function) used to control a global navigation tree - * handling things such as collapse, expand, and AJAX requests for more branches - * - * You should never call this directly.. you should use {@link start_new_navtree()} - * which will create the class and make it accessible in a smart way - * - * @class navigation_tree - * @constructor - * @param {string} treename - * @param {string} key - */ -function navigation_tree (treename, key) { - this.name = treename; - this.key = key; - this.errorlog = ''; - this.ajaxbranches = 0; - this.expansions = Array(); - this.instance = null - this.cachedcontent = null; - this.cachedfooter = null; - this.position = 'block'; - this.skipsetposition = false; - this.togglesidetabdisplay = '[[togglesidetabdisplay]]'; - this.toggleblockdisplay = '[[toggleblockdisplay]]'; - this.sideblockwidth = null; - if (window[this.name]) { - if (window[this.name].expansions) { - this.expansions = window[this.name].expansions; +var shadow = { + /** + * This function create a series of DIV's appended to an element to give it a + * shadow + * @param {bool} top Displays a top shadow if true + * @param {bool} right Displays a right shadow if true + * @param {bool} bottom Displays a bottom shadow if true + * @param {bool} left Displays a left shadow if true + * @return {Y.Node} + */ + create : function(top, right, bottom, left) { + var shadow = Y.Node.create('
      '); + if (Y.UA.ie > 0 && Y.UA.ie < 7) { + // IE 6 doesn't like this shadow method + return shadow; } - if (window[this.name].instance) { - this.instance = window[this.name].instance; - } - if (window[this.name].togglesidetabdisplay) { - this.togglesidetabdisplay = window[this.name].togglesidetabdisplay; - } - if (window[this.name].toggleblockdisplay) { - this.toggleblockdisplay = window[this.name].toggleblockdisplay; - } - } -} -/** - * Initialise function used to attach the initial events to the navigation tree - * This function attachs toggles and ajax calls - */ -navigation_tree.prototype.initialise = function() { - if (!document.getElementById(this.name)) { - return; - } - var e = document.getElementById(this.name); - var i = 0; - while (!YAHOO.util.Dom.hasClass(e, 'sideblock') && e.nodeName.toUpperCase()!='BODY') { - e = e.parentNode; - } - var movetos = YAHOO.util.Dom.getElementsByClassName('moveto', 'a', e); - if (movetos !== null && movetos.length > 0) { - for (i = 0;i0) { - for (i = 0; i 0) { - for (i = 0; i < customcommands.length; i++) { - customcommands[i].parentNode.removeChild(customcommands[i]); - commands[0].appendChild(customcommands[i]); - } - } - - if (YAHOO.util.Dom.hasClass(e, 'sideblock_js_sidebarpopout')) { - YAHOO.util.Dom.removeClass(e, 'sideblock_js_sidebarpopout'); - this.skipsetposition = true; - this.toggle_block_display(e, this); - } else if (YAHOO.util.Dom.hasClass(e, 'sideblock_js_expansion')) { - YAHOO.util.Event.addListener(e, 'mouseover', this.togglesize, e, this); - YAHOO.util.Event.addListener(e, 'mouseout', this.togglesize, e, this); - } -} -/** - * Toogle a branch either collapsed or expanded... CSS styled - * @param {object} e Event object - */ -navigation_tree.prototype.toggleexpansion = function(e) { - YAHOO.util.Event.stopPropagation(e); - var target = YAHOO.util.Event.getTarget(e); - var parent = target.parentNode; - while (parent.nodeName.toUpperCase()!='LI') { - parent = parent.parentNode; - } - if (YAHOO.util.Dom.hasClass(parent, 'collapsed')) { - YAHOO.util.Dom.removeClass(parent, 'collapsed'); - } else { - YAHOO.util.Dom.addClass(parent, 'collapsed'); - } - if (this.position === 'sidebar') { - YAHOO.moodle.navigation.tabpanel.resize_tab(); - } -} -/** - * Toggles the size on an element by adding/removing the mouseover class - * @param {object} e Event object - * @param {element} element The element to add/remove the class from - */ -navigation_tree.prototype.togglesize = function(e, element) { - if (e.type == 'mouseout') { - var mp = YAHOO.util.Event.getXY(e); - if (mp[0] == -1) { - return true; - } - var ep = YAHOO.util.Dom.getXY(element); - ep[2] = ep[0]+element.offsetWidth; - ep[3] = ep[1]+element.offsetHeight; - var withinrealm = (mp[0] > ep[0] && mp[0] < ep[2] && mp[1] > ep[1] && mp[1] < ep[3]); - if (!withinrealm) { - YAHOO.util.Event.stopEvent(e); - YAHOO.util.Dom.removeClass(element, 'mouseover'); - } - } else { - YAHOO.util.Event.stopEvent(e); - element.style.width = element.offsetWidth +'px'; - YAHOO.util.Dom.addClass(element, 'mouseover'); - } - return true; -} -/** - * This function makes the initial call to load a branch of the navigation - * tree by AJAX - * @param {object} e Event object - * @param {object} branch The branch object from navigation_tree::expansions - * @return {bool} - */ -navigation_tree.prototype.init_load_ajax = function(e, branch) { - YAHOO.util.Event.stopPropagation(e); - if (YAHOO.util.Event.getTarget(e).nodeName.toUpperCase() != 'P') { - return true; - } - var postargs = 'elementid='+branch.id+'&id='+branch.branchid+'&type='+branch.type+'&sesskey='+moodle_cfg.sesskey; - if (this.instance != null) { - postargs += '&instance='+this.instance; - } - YAHOO.util.Connect.asyncRequest('POST', moodle_cfg.wwwroot+'/lib/ajax/getnavbranch.php', callback={ - success:function(o) {this.load_ajax(o);}, - failure:function(o) {this.load_ajax(o);}, - argument: {gntinstance:this,branch:branch,event:e, target:YAHOO.util.Event.getTarget(e)}, - scope: this - }, postargs); - return true; -} -/** - * This function loads a branch returned by AJAX into the XHTML tree structure - * @param {object} outcome The AJAX response - * @return {bool} - */ -navigation_tree.prototype.load_ajax = function(outcome) { - // Check the status - if (outcome.status!=0 && outcome.responseXML!=null) { - var branch = outcome.responseXML.documentElement; - if (branch!=null && this.add_branch(branch,outcome.argument.target ,1)) { - // If we get here everything worked perfectly - YAHOO.util.Event.removeListener(outcome.argument.branch.element, 'click', navigation_tree.prototype.init_load_ajax); - if (this.position === 'sidebar') { - YAHOO.moodle.navigation.tabpanel.resize_tab(); - } - return true; - } - } - // Something went wrong or there simply wasn't anything more to display - // add the emptybranch css class so we can flag it - YAHOO.util.Dom.replaceClass(outcome.argument.target, 'branch', 'emptybranch'); - return false; -} -/** - * This recursive function takes an XML branch and includes it in the tree - * @param {xmlnode} branchxml The XML node for the branch - * @param {element} target The target node to add to - * @param {int} depth The depth we have delved (recusive counter) - * @return {bool} - */ -navigation_tree.prototype.add_branch = function(branchxml, target, depth) { - var branch = new navigation_tree_branch(this.name); - branch.load_from_xml_node(branchxml); - if (depth>1) { - target = branch.inject_into_dom(target,this); - } - var dropcount = 5; - while (target.nodeName.toUpperCase() !== 'LI') { - target = target.parentNode; - if (dropcount==0 && moodle_cfg.developerdebug) { - return alert("dropped because of exceeding dropcount"); - } - dropcount--; - } - if (branch.haschildren && branch.mychildren && branch.mychildren.childNodes) { - for (var i=0;i 0) { - for (var i=0;i0) { - for (var j=0;j"); - var commands = YAHOO.util.Dom.getElementsByClassName('commands', 'div', this.cachedcontent); - var tabcommands = null; - if (commands.length > 0) { - tabcommands = commands[0]; - } else { - tabcommands = document.createElement('div'); - YAHOO.util.Dom.addClass(tabcommands, 'commands'); - } - - if (YAHOO.util.Dom.hasClass(sideblocknode, 'block-region')) { - var blocks = YAHOO.util.Dom.getElementsByClassName('sideblock', 'div', sideblocknode); - if (blocks.length === 0) { - YAHOO.moodle.navigation.sideblockwidth = YAHOO.util.Dom.getStyle(sideblocknode, 'width'); - YAHOO.util.Dom.setStyle(sideblocknode, 'width', '0px'); - } - } - - if (YAHOO.moodle.navigation.tabpanel === null) { - YAHOO.moodle.navigation.tabpanel = new navigation_tab_panel(); - } - YAHOO.moodle.navigation.tabpanel.add_to_tab_panel(this.name, tabtitle, tabcontent, tabcommands); - if (!this.skipsetposition) { - set_user_preference('nav_in_tab_panel_'+this.name, 1); - } else { - this.skipsetposition = false; - } - return true; -} -/** - * This function gets called from {@link navigation_tree.toggle_block_display()} - * and is responsible for moving the block from the sidebar to the block position - * @return {bool} - */ -navigation_tree.prototype.move_to_block_position = function(e) { - - YAHOO.util.Event.stopEvent(e); - - if (this.sideblockwidth !== null) { - YAHOO.util.Dom.setStyle(sideblocknode, 'width', this.sideblockwidth); - this.sideblockwidth = null; - } - - var placeholder = document.getElementById(this.name+'_content_placeholder'); - if (!placeholder || YAHOO.moodle.navigation.tabpanel == null) { - return false; - } - - if (YAHOO.moodle.navigation.tabpanel.showntab !== null) { - YAHOO.moodle.navigation.tabpanel.hide_tab(e, YAHOO.moodle.navigation.tabpanel.showntab.tabname); - } - - var tabcontent = YAHOO.moodle.navigation.tabpanel.get_tab_panel_contents(this.name); - this.cachedcontent.appendChild(tabcontent); - placeholder.parentNode.replaceChild(this.cachedcontent, placeholder); - - if (YAHOO.moodle.navigation.sideblockwidth !== null) { - var sideblocknode = this.cachedcontent; - while (sideblocknode && !YAHOO.util.Dom.hasClass(sideblocknode, 'block-region')) { - sideblocknode = sideblocknode.parentNode; - } - if (YAHOO.util.Dom.hasClass(sideblocknode, 'block-region')) { - YAHOO.util.Dom.setStyle(sideblocknode, 'width', YAHOO.moodle.navigation.sideblockwidth); - } - } - - var moveto = YAHOO.util.Dom.getElementsByClassName('moveto customcommand', 'a', this.cachedcontent); - if (moveto.length > 0) { - for (var i=0;i0) { - for (var j=0;j 0 && YAHOO.env.ua.ie < 7) { - YAHOO.util.Dom.setStyle(navbar, 'height', YAHOO.util.Dom.getViewportHeight()+'px'); - } - - var navbarcontrol = document.createElement('div'); - YAHOO.util.Dom.addClass(navbarcontrol, 'controls'); - var removeall = document.createElement('img'); - removeall.setAttribute('src', get_image_url('t/movetoblock', 'moodle')); - removeall.setAttribute('title', mstr.moodle.moveallsidetabstoblock); - removeall.setAttribute('alt', mstr.moodle.moveallsidetabstoblock); - navbarcontrol.appendChild(removeall); - navbar.appendChild(navbarcontrol); - - document.getElementsByTagName('body')[0].appendChild(navbar); - navbar.appendChild(create_shadow(false, true, true, false)); - YAHOO.util.Dom.addClass(document.getElementsByTagName('body')[0], 'has_navigation_bar'); - this.navigationpanel = navbar; - this.tabpanelexists = true; - navbar.style.display = 'block'; - - YAHOO.util.Event.addListener(removeall, 'click', move_all_sidetabs_to_block_position); - - return true; -} -/** - * This removes the tab panel element from the page - * @method remove_tab_panel - * @return {bool} - */ -navigation_tab_panel.prototype.remove_tab_panel = function () { - var panel = document.getElementById('sidebarpopup'); - if (!panel) { - return false; - } - this.tabpanel = null; - panel.parentNode.removeChild(panel); - this.tabpanelexists = false; - this.navigationpanel = null; - if (YAHOO.util.Dom.hasClass(document.getElementsByTagName('body')[0], 'has_navigation_bar')) { - YAHOO.util.Dom.removeClass(document.getElementsByTagName('body')[0], 'has_navigation_bar') - } - return true; -} -/** - * This function retrieves the content of a tab in the navigation tab panel - * @method get_tab_panel_contents - * @param {string} tabname The name of the tab - * @return {element} The content element - */ -navigation_tab_panel.prototype.get_tab_panel_contents = function(tabname) { - remove_shadow(this.tabpanelelementcontents[tabname]); - return this.tabpanelelementcontents[tabname]; -} -/** - * This function adds a tab to the navigation tab panel - * - * If you find that it takes a long time to make the initial transaction then I - * would first check the time that set_user_preference is taking, during development - * the code needed to be re-jigged because it was taking a very long time to execute - * - * @method add_to_tab_panel - * @param {string} tabname The string name of the tab - * @param {element} tabtitle The title of the tab - * @param {element} tabcontent The content for the tab - * @param {element} tabcommands The commands for the tab - */ -navigation_tab_panel.prototype.add_to_tab_panel = function (tabname, tabtitle, tabcontent, tabcommands) { - if (!this.tabpanelexists) { - this.create_tab_panel(); - } - - var firsttab = (this.tabcount==0); - - var sidetab = document.createElement('div'); - sidetab.setAttribute('id', tabname+'_sidebarpopup'); - YAHOO.util.Dom.addClass(sidetab, 'sideblock_tab'); - - if (firsttab) { - YAHOO.util.Dom.addClass(sidetab, 'firsttab'); - } - var sidetabtitle = document.createElement('div'); - sidetabtitle.appendChild(tabtitle); - sidetabtitle.setAttribute('id', tabname+'_title'); - YAHOO.util.Dom.addClass(sidetabtitle, 'title'); - tabcontent.appendChild(create_shadow(true, true, true, false)); - sidetab.appendChild(sidetabtitle); - - if (tabcommands.childNodes.length>0) { - tabcontent.appendChild(tabcommands); - } - - this.navigationpanel.appendChild(sidetab); - - var position = YAHOO.util.Dom.getXY(sidetabtitle); - position[0] += sidetabtitle.offsetWidth; - if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) { - position[0] -= 2; - } - - this.tabpanels[tabname] = new YAHOO.widget.Panel('navigation_tab_panel_'+tabname, { - close:false, - draggable:false, - constraintoviewport: false, - underlay:"none", - visible:false, - monitorresize:false, - /*context:[tabname+'_title','tl','tr',['configChanged','beforeShow','changeBody']],*/ - xy:position, - autofillheight:'body'}); - this.tabpanels[tabname].showEvent.subscribe(this.resize_tab, this, true); - this.tabpanels[tabname].setBody(tabcontent); - this.tabpanels[tabname].render(this.navigationpanel); - - this.tabpanelelementnames[this.tabpanelelementnames.length] = tabname; - this.tabpanelelementcontents[tabname] = tabcontent; - this.tabcount++; - - YAHOO.util.Event.addListener(sidetab, "mouseover", this.show_tab, tabname, this); -} -/** - * This function handles checking the size, and positioning of the navigaiton - * panel when expansion events occur, or when the panel is shown, or if the window - * is resized - * - * There are undoubtably some bugs in this little bit of code. For one it relies - * on the padding set in CSS by the YUI:sam skin, if you are hitting a problem - * whereby the navigation extends beyond its border, or doesn't fill to its own - * border check the value assigned to padding for the panel body `.yui_panel .bd` - * - * @return {bool} - */ -navigation_tab_panel.prototype.resize_tab = function () { - var screenheight = YAHOO.util.Dom.getViewportHeight(); - var tabheight = parseInt(this.tabpanels[this.showntab.tabname].body.offsetHeight); - var tabtop = parseInt(this.tabpanels[this.showntab.tabname].cfg.getProperty('y')); - var titletop = YAHOO.util.Dom.getY(this.showntab.tabname+'_title'); - var scrolltop = (document.all)?document.body.scrollTop:window.pageYOffset; - // This makes sure that the panel is the same height as the tab title to - // begin with - if (tabtop > (10+scrolltop) && tabtop > (titletop+scrolltop)) { - this.tabpanels[this.showntab.tabname].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 ((tabtop+tabheight)>(screenheight+scrolltop) && tabtop > 10) { - tabtop = (screenheight-tabheight-10); - if (tabtop<10) { - tabtop = 10; - } - this.tabpanels[this.showntab.tabname].cfg.setProperty('y', tabtop+scrolltop); - } - - // This makes the panel constrain to the screen's height if the panel is big - if (tabtop <= 10 && ((tabheight+tabtop*2) > screenheight || YAHOO.util.Dom.hasClass(this.tabpanels[this.showntab.tabname].body, 'oversized_content'))) { - this.tabpanels[this.showntab.tabname].cfg.setProperty('height', (screenheight-39)); - YAHOO.util.Dom.setStyle(this.tabpanels[this.showntab.tabname].body, 'height', (screenheight-59)+'px'); - YAHOO.util.Dom.addClass(this.tabpanels[this.showntab.tabname].body, 'oversized_content'); - } -} -/** - * This function sets everything up for the show even and then calls the panel's - * show event once we are happy. - * - * This function is responsible for closing any open panels, removing show events - * so we don't refresh unnessecarily and adding events to trap closing, and resizing - * events - * - * @param {event} e The event that fired to get us here - * @param {string} tabname The tabname to open - * @return {bool} - */ -navigation_tab_panel.prototype.show_tab = function (e, tabname) { - if (this.showntab !== null) { - this.hide_tab(e, this.showntab.tabname); - } - this.showntab = {event:e, tabname:tabname}; - this.tabpanels[tabname].show(e, this.tabpanel); - YAHOO.util.Dom.addClass(tabname+'_title', 'active_tab'); - YAHOO.util.Event.removeListener(tabname+'_sidebarpopup', "mouseover", this.show_tab); - YAHOO.util.Event.addListener('navigation_tab_panel_'+tabname, "click", function (e){this.preventhide = true}, this, true); - YAHOO.util.Event.addListener(tabname+'_sidebarpopup', "click", this.hide_tab, tabname, this); - YAHOO.util.Event.addListener(window, 'resize', this.resize_tab, this, true); - YAHOO.util.Event.addListener(document.body, "click", this.hide_tab, tabname, this); - return true; -} -/** - * This function closes the open tab and sets the listeners up to handle the show - * event again - * - * @param {event} e The event that fired to get us here - * @param {string} tabname The tabname to close - * @return {bool} - */ -navigation_tab_panel.prototype.hide_tab = function(e, tabname) { - if (this.preventhide===true) { - this.preventhide = false; - } else { - this.showntab = null; - YAHOO.util.Event.addListener(tabname+'_sidebarpopup', "mouseover", this.show_tab, tabname, this); - YAHOO.util.Event.removeListener(window, 'resize', this.resize_tab); - YAHOO.util.Event.removeListener(document.body, "click", this.hide_tab); - YAHOO.util.Dom.removeClass(tabname+'_title', 'active_tab'); - this.tabpanels[tabname].hide(e, this.tabpanel); - } -} -/** - * This function removes a tab from the navigation tab panel - * @param {string} tabname - * @return {bool} - */ -navigation_tab_panel.prototype.remove_from_tab_panel = function(tabname) { - var tab = document.getElementById(tabname+'_sidebarpopup'); - if (!tab) { - return false; - } - tab.parentNode.removeChild(tab); - this.tabpanels[tabname].destroy(); - this.tabpanels[tabname] = null; - this.tabcount--; - if (this.tabcount === 0) { - this.remove_tab_panel(); - } - return true; -} - -/** - * Global navigation tree branch object used to parse an XML branch - * into a usable object, and then to inject it into the DOM - * @class navigation_tree_branch - * @constructor - */ -function navigation_tree_branch(treename) { - this.treename = treename; - this.myname = null; - this.mytitle = null; - this.myclass = null; - this.myid = null; - this.mykey = null; - this.mytype = null; - this.mylink = null; - this.myicon = null; - this.myexpandable = null; - this.expansionceiling = null; - this.myhidden = false; - this.haschildren = false; - this.mychildren = false; -} -/** - * This function populates the object from an XML branch - * @param {xmlnode} branch The XML branch to turn into an object - */ -navigation_tree_branch.prototype.load_from_xml_node = function (branch) { - this.myname = null; - this.mytitle = branch.getAttribute('title'); - this.myclass = branch.getAttribute('class'); - this.myid = branch.getAttribute('id'); - this.mylink = branch.getAttribute('link'); - this.myicon = branch.getAttribute('icon'); - this.mykey = branch.getAttribute('key'); - this.mytype = branch.getAttribute('type'); - this.myexpandable = branch.getAttribute('expandable'); - this.expansionceiling = branch.getAttribute('expansionceiling'); - this.myhidden = (branch.getAttribute('hidden')=='true'); - this.haschildren = (branch.getAttribute('haschildren')=='true'); - - if (this.myid && this.myid.match(/^expandable_branch_\d+$/)) { - YAHOO.moodle.navigation.expandablebranchcount++; - this.myid = 'expandable_branch_'+YAHOO.moodle.navigation.expandablebranchcount; - } - - for (var i=0; i'))); - } else { - var branchlink = document.createElement('a'); - branchlink.setAttribute('title', this.mytitle); - branchlink.setAttribute('href', this.mylink); - if (branchicon !== false) { - branchlink.appendChild(branchicon); - } - branchlink.appendChild(document.createTextNode(this.myname.replace(/\n/g, '
      '))); - if (this.myhidden) { - YAHOO.util.Dom.addClass(branchlink, 'dimmed'); - } - branchp.appendChild(branchlink); - } - branchli.appendChild(branchp); - element.appendChild(branchli); - return branchli; -} - -/** - * Creates a new JS instance of a global navigation tree and kicks it into gear - * @param {string} treename The name of the tree - */ -function setup_new_navtree(treename) { - var key = YAHOO.moodle.navigation.treecollection.length; - YAHOO.moodle.navigation.treecollection[key] = new navigation_tree(treename, key); - YAHOO.moodle.navigation.treecollection[key].initialise(); -} - -/** - * This function moves all navigation tree instances that are currently - * displayed in the sidebar back into their block positions - */ -function move_all_sidetabs_to_block_position(e) { - for (var i=0; i 0 && YAHOO.env.ua.ie < 7) { - // IE6 just doest like my shadow... + if (top) shadow.append(Y.Node.create('
      ')); + if (right) shadow.append(Y.Node.create('
      ')); + if (bottom) shadow.append(Y.Node.create('
      ')); + if (left) shadow.append(Y.Node.create('
      ')); + if (top && left) shadow.append(Y.Node.create('
      ')); + if (top && right) shadow.append(Y.Node.create('
      ')); + if (bottom && left) shadow.append(Y.Node.create('
      ')); + if (bottom && right) shadow.append(Y.Node.create('
      ')); return shadow; + }, + /** + * This function removes any shadows that a node and its children may have + * @param {Y.Node} node The element to remove the shadow from + * @return {bool} + */ + remove : function(node) { + node.all('.divshadow').remove(); } - var createShadowDiv = function(cname) { - var shadowdiv = document.createElement('div'); - YAHOO.util.Dom.addClass(shadowdiv, cname); - if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 7) { - // IE version less than 7 doesnt support alpha - YAHOO.util.Dom.setStyle(shadowdiv, 'opacity', 0.3); - } - return shadowdiv; - } - if (top) shadow.appendChild(createShadowDiv('shadow_top')); - if (right) shadow.appendChild(createShadowDiv('shadow_right')); - if (bottom) shadow.appendChild(createShadowDiv('shadow_bottom')); - if (left) shadow.appendChild(createShadowDiv('shadow_left')); - if (top && left) shadow.appendChild(createShadowDiv('shadow_top_left')); - if (bottom && left) shadow.appendChild(createShadowDiv('shadow_bottom_left')); - if (top && right) shadow.appendChild(createShadowDiv('shadow_top_right')); - if (bottom && right) shadow.appendChild(createShadowDiv('shadow_bottom_right')); - return shadow; -} -/** - * This function removes any shadows that a node and its children may have - * @param {element} el The element to remove the shadow from - * @return {bool} - */ -function remove_shadow(el) { - var shadows = YAHOO.util.Dom.getElementsByClassName('divshadow', 'div', el); - if (shadows == null || shadows.length == 0) return true; - for (var i=0;i