From 9d89733132e89f61d2bb09e9844543b60c38f213 Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Thu, 14 Jan 2010 05:35:23 +0000 Subject: [PATCH] javascript-dock MDL-21329 Several major changes to the underlying navigation and dock code The following changes have been made in this commit: 1. Renamed the side bar thing to dock, this is consistent through all code/css now. 2. Converted everything to YUI 3 except the panel which docked items are shown in, this iwll be converted once YUI 3 overlay is no longer beta. 3 Redisigned the dock, all blocks can now make use of it, and theme code within JS can override it so they can make it look as they want. More changes are coming --- blocks/blocks.js | 591 +++++++++++ .../block_global_navigation_tree.php | 42 +- blocks/global_navigation_tree/navigation.js | 298 ++++++ blocks/moodleblock.class.php | 33 +- .../block_settings_navigation_tree.php | 36 +- lang/en_utf8/block.php | 3 + lang/en_utf8/block_global_navigation_tree.php | 2 - .../block_settings_navigation_tree.php | 2 - lang/en_utf8/moodle.php | 1 - lib/ajax/ajaxlib.php | 16 +- lib/navigationlib.php | 1 - pix/t/block_to_dock.png | Bin 0 -> 246 bytes pix/t/dock_to_block.png | Bin 0 -> 248 bytes theme/base/config.php | 4 +- theme/base/javascript/navigation.js | 117 ++- theme/base/style/blocks.css | 2 +- theme/standardold/config.php | 4 +- theme/standardold/javascript/navigation.js | 928 +----------------- theme/standardold/style/styles_layout.css | 50 +- 19 files changed, 1152 insertions(+), 978 deletions(-) create mode 100644 blocks/blocks.js create mode 100644 blocks/global_navigation_tree/navigation.js create mode 100644 pix/t/block_to_dock.png create mode 100644 pix/t/dock_to_block.png diff --git a/blocks/blocks.js b/blocks/blocks.js new file mode 100644 index 0000000000..22e2753f70 --- /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 61d54342bf..fc790b658a 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 0000000000..39bd0f2f0f --- /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 03db833cd2..595405c58f 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 70b576f73e..e06c51d3dd 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 0000000000000000000000000000000000000000..d72690c8701624caec7f00726cc7c1fbb2a1056a GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G`DAk4@xYmNj^kiEpy*OmPyhXk{jMb?vqXFwsz64!_l=ltB< z)VvY~=c3falGGH1^30M91$R&1fbd2>aiF3=PZ!4!jq}L~3W5ek20$P(=QqFj%!DI1 zu3h^kKl{e^D>lDNX-TQ7vg&%YxB5F+slmioO9FY1FnHK>EIiiO)x~9V cN0W_V{au057w6gqfQB=8y85}Sb4q9e0Ib`F|Nf7`5 literal 0 HcmV?d00001 diff --git a/theme/base/config.php b/theme/base/config.php index 4b0ef7ca73..03070d7e5b 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 e8b872f056..7e0fbffa33 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 f37f39bcb4..7a2646ca6a 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 884fc9d1fc..80cf7a4277 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 b23832b021..e9ee0e4b0c 100644 --- a/theme/standardold/javascript/navigation.js +++ b/theme/standardold/javascript/navigation.js @@ -1,882 +1,48 @@ -/* legacy standard: javascript needed for navbar manipulations */ - -// 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; - } - 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... +// 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)); + }); +}); + +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 (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; - } - 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