MDL-40167 dock: converted the dock JS to a YUI shifted module.

The patch converts the dock into a YUI shifter module and at
the same time improves several aspects of its operation.
The features of this patch include:
* Dock Module conversion.
* A loader that ensures we don't include the dock JS or its
  requirements unless actually required.
* We no longer include the dock JS for themes that don't
  enable it.
* Blocks no longer add registration events to the page
  instead a dockable attribute is added to the html and the
  loader looks for that.
* The dock module is properly documented and running YUIDoc
  gives good quality documentation.
* We no longer need the dock module registration or
  subcomponent.
* All events that can be delegated are now delegated.
* Removed unused variables and code left over after fixes.
* Support for docking blocks renderered using the new blocks
  render method. Better support for custom block regions.
This commit is contained in:
Sam Hemelryk 2013-06-20 18:12:28 +12:00
parent 07bbbcf174
commit 84192d7836
30 changed files with 7579 additions and 1421 deletions

File diff suppressed because it is too large Load Diff

View File

@ -271,6 +271,10 @@ class block_base {
$bc->collapsible = block_contents::VISIBLE;
}
if ($this->instance_can_be_docked() && !$this->hide_header()) {
$bc->dockable = true;
}
$bc->annotation = ''; // TODO MDL-19398 need to work out what to say here.
return $bc;
@ -435,9 +439,13 @@ class block_base {
$this->specialization();
}
/**
* Allows the block to load any JS it requires into the page.
*
* By default this function simply permits the user to dock the block if it is dockable.
*/
function get_required_javascript() {
if ($this->instance_can_be_docked() && !$this->hide_header()) {
$this->page->requires->js_init_call('M.core_dock.init_genericblock', array($this->instance->id));
user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
}
}

View File

@ -56,7 +56,6 @@ class block_navigation extends block_base {
* Set the initial properties for the block
*/
function init() {
global $CFG;
$this->blockname = get_class($this);
$this->title = get_string('pluginname', $this->blockname);
}
@ -109,8 +108,7 @@ class block_navigation extends block_base {
*/
function get_required_javascript() {
global $CFG;
user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
$this->page->requires->js_module('core_dock');
parent::get_required_javascript();
$limit = 20;
if (!empty($CFG->navcourselimit)) {
$limit = $CFG->navcourselimit;
@ -127,7 +125,7 @@ class block_navigation extends block_base {
'expansionlimit' => $expansionlimit
);
$this->page->requires->string_for_js('viewallcourses', 'moodle');
$this->page->requires->yui_module(array('core_dock', 'moodle-block_navigation-navigation'), 'M.block_navigation.init_add_tree', array($arguments));
$this->page->requires->yui_module('moodle-block_navigation-navigation', 'M.block_navigation.init_add_tree', array($arguments));
}
/**
@ -136,7 +134,6 @@ class block_navigation extends block_base {
* @return object $this->content
*/
function get_content() {
global $CFG, $OUTPUT;
// First check if we have already generated, don't waste cycles
if ($this->contentgenerated === true) {
return $this->content;

View File

@ -1,5 +1,51 @@
YUI.add('moodle-block_navigation-navigation', function (Y, NAME) {
/**
* Navigation block JS.
*
* This file contains the Navigation block JS..
*
* @module moodle-block_navigation-navigation
*/
/**
* This namespace will contain all of the contents of the navigation blocks
* global navigation and settings.
* @namespace M
* @class block_navigation
* @static
*/
M.block_navigation = M.block_navigation || {};
/**
* The number of expandable branches in existence.
*
* @property expandablebranchcount
* @protected
* @static
*/
M.block_navigation.expandablebranchcount = 1;
/**
* The maximum number of courses to show as part of a branch.
*
* @property courselimit
* @protected
* @static
*/
M.block_navigation.courselimit = 20;
/**
* Add new instance of navigation tree to tree collection
*
* @method init_add_tree
* @static
* @param {Object} properties
*/
M.block_navigation.init_add_tree = function(properties) {
if (properties.courselimit) {
this.courselimit = properties.courselimit;
}
new TREE(properties);
};
/**
* A 'actionkey' Event to help with Y.delegate().
* The event consists of the left arrow, right arrow, enter and space keys.
@ -9,24 +55,35 @@ YUI.add('moodle-block_navigation-navigation', function (Y, NAME) {
* This event is delegated to branches in the navigation tree.
* The on() method to subscribe allows specifying the desired trigger actions as JSON.
*
* Todo: This could be centralised, a similar Event is defined in blocks/dock.js
* @namespace M.block_navigation
* @class ActionKey
*/
Y.Event.define("actionkey", {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
/**
* The keys to trigger on.
* @method _keys
*/
_keys: {
//arrows
'37': 'collapse',
'39': 'expand',
//(@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
'32': 'toggle',
'13': 'enter'
},
/**
* Handles key events
* @method _keyHandler
* @param {EventFacade} e
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {Object} args
*/
_keyHandler: function (e, notifier, args) {
var actObj;
if (!args.actions) {
@ -40,6 +97,13 @@ Y.Event.define("actionkey", {
}
},
/**
* Subscribes to events.
* @method on
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
*/
on: function (node, sub, notifier) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
@ -50,11 +114,23 @@ Y.Event.define("actionkey", {
}
},
/**
* Detaches an event listener
* @method detach
*/
detach: function (node, sub) {
//detach our _detacher handle of the subscription made in on()
sub._detacher.detach();
},
/**
* Creates a delegated event listener.
* @method delegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
delegate: function (node, sub, notifier, filter) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
@ -65,45 +141,50 @@ Y.Event.define("actionkey", {
}
},
/**
* Detaches a delegated event listener.
* @method detachDelegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
detachDelegate: function (node, sub) {
sub._delegateDetacher.detach();
}
});
var EXPANSIONLIMIT_EVERYTHING = 0,
//EXPANSIONLIMIT_COURSE = 20,
//EXPANSIONLIMIT_SECTION = 30,
EXPANSIONLIMIT_COURSE = 20,
EXPANSIONLIMIT_SECTION = 30,
EXPANSIONLIMIT_ACTIVITY = 40;
/**
* Mappings for the different types of nodes coming from the navigation.
* Copied from lib/navigationlib.php navigation_node constants.
* @type object
*/
// Mappings for the different types of nodes coming from the navigation.
// Copied from lib/navigationlib.php navigation_node constants.
var NODETYPE = {
/** @type int Root node = 0 */
// @type int Root node = 0
ROOTNODE : 0,
/** @type int System context = 1 */
// @type int System context = 1
SYSTEM : 1,
/** @type int Course category = 10 */
// @type int Course category = 10
CATEGORY : 10,
/** @type int MYCATEGORY = 11 */
// @type int MYCATEGORY = 11
MYCATEGORY : 11,
/** @type int Course = 20 */
// @type int Course = 20
COURSE : 20,
/** @type int Course section = 30 */
// @type int Course section = 30
SECTION : 30,
/** @type int Activity (course module) = 40 */
// @type int Activity (course module) = 40
ACTIVITY : 40,
/** @type int Resource (course module = 50 */
// @type int Resource (course module = 50
RESOURCE : 50,
/** @type int Custom node (could be anything) = 60 */
// @type int Custom node (could be anything) = 60
CUSTOM : 60,
/** @type int Setting = 70 */
// @type int Setting = 70
SETTING : 70,
/** @type int User context = 80 */
// @type int User context = 80
USER : 80,
/** @type int Container = 90 */
// @type int Container = 90
CONTAINER : 90
};
@ -112,6 +193,11 @@ var NODETYPE = {
*
* This class establishes the tree initially, creating expandable branches as
* required, and delegating the expand/collapse event.
*
* @namespace M.block_navigation
* @class Tree
* @constructor
* @extends Y.Base
*/
var TREE = function() {
TREE.superclass.constructor.apply(this, arguments);
@ -119,17 +205,27 @@ var TREE = function() {
TREE.prototype = {
/**
* The tree's ID, normally its block instance id.
* @property id
* @type Int
* @protected
*/
id : null,
/**
* An array of initialised branches.
* @property branches
* @type Array
* @protected
*/
branches : [],
/**
* Initialise the tree object when its first created.
* @method initializer
* @param {Object} config
*/
initializer : function(config) {
this.id = config.id;
Y.log('Initialising navigation block tree', 'note', 'moodle-block_navigation');
this.id = parseInt(config.id, 10);
var node = Y.one('#inst'+config.id);
@ -168,14 +264,11 @@ TREE.prototype = {
Y.delegate('click', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
Y.delegate('actionkey', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
}
// Call the generic blocks init method to add all the generic stuff
if (this.get('candock')) {
this.initialise_block(Y, node);
}
},
/**
* Fire actions for a branch when an event occurs.
* @method fire_branch_action
* @param {EventFacade} event
*/
fire_branch_action : function(event) {
var id = event.currentTarget.getAttribute('id');
@ -185,6 +278,9 @@ TREE.prototype = {
/**
* This is a callback function responsible for expanding and collapsing the
* branches of the tree. It is delegated to rather than multiple event handles.
* @method toggleExpansion
* @param {EventFacade} e
* @return Boolean
*/
toggleExpansion : function(e) {
// First check if they managed to click on the li iteslf, then find the closest
@ -240,46 +336,66 @@ TREE.prototype = {
// If this block can dock tell the dock to resize if required and check
// the width on the dock panel in case it is presently in use.
if (this.get('candock')) {
M.core_dock.resize();
var panel = M.core_dock.getPanel();
if (panel.visible) {
panel.correctWidth();
}
if (this.get('candock') && M.core.dock.notifyBlockChange) {
M.core.dock.notifyBlockChange(this.id);
}
return true;
}
};
// The tree extends the YUI base foundation.
Y.extend(TREE, Y.Base, TREE.prototype, {
NAME : 'navigation-tree',
ATTRS : {
instance : {
value : null
},
/**
* True if the block can dock.
* @attribute candock
* @type Boolean
*/
candock : {
validator : Y.Lang.isBool,
value : false
},
/**
* If set to true nodes will be opened/closed in an accordian fashion.
* @attribute accordian
* @type Boolean
*/
accordian : {
validator : Y.Lang.isBool,
value : false
},
/**
* The nodes that get shown.
* @attribute expansionlimit
* @type Integer
*/
expansionlimit : {
value : 0,
setter : function(val) {
return parseInt(val, 10);
val = parseInt(val, 10);
if (val !== EXPANSIONLIMIT_EVERYTHING &&
val !== EXPANSIONLIMIT_COURSE &&
val !== EXPANSIONLIMIT_SECTION &&
val !== EXPANSIONLIMIT_ACTIVITY) {
val = EXPANSIONLIMIT_EVERYTHING;
}
return val;
}
}
}
});
if (M.core_dock && M.core_dock.genericblock) {
Y.augment(TREE, M.core_dock.genericblock);
}
/**
* The tree branch class.
* The Branch class.
*
* This class is used to manage a tree branch, in particular its ability to load
* its contents by AJAX.
*
* @namespace M.block_navigation
* @class Branch
* @constructor
* @extends Y.Base
*/
BRANCH = function() {
BRANCH.superclass.constructor.apply(this, arguments);
@ -287,10 +403,15 @@ BRANCH = function() {
BRANCH.prototype = {
/**
* The node for this branch (p)
* @property node
* @type Node
* @protected
*/
node : null,
/**
* Initialises the branch when it is first created.
* @method initializer
* @param {Object} config
*/
initializer : function(config) {
var i,
@ -310,8 +431,7 @@ BRANCH.prototype = {
}
}
// Get the node for this branch
this.node = Y.one('#', this.get('id'));
// Now check whether the branch is not expandable because of the expansionlimit
this.node = Y.one('#'+this.get('id'));
var expansionlimit = this.get('tree').get('expansionlimit');
var type = this.get('type');
if (expansionlimit !== EXPANSIONLIMIT_EVERYTHING && type >= expansionlimit && type <= EXPANSIONLIMIT_ACTIVITY) {
@ -324,6 +444,11 @@ BRANCH.prototype = {
*
* This function creates a DOM structure for the branch and then injects
* it into the navigation tree at the correct point.
*
* @method draw
* @chainable
* @param {Node} element
* @return Branch
*/
draw : function(element) {
@ -345,7 +470,7 @@ BRANCH.prototype = {
// Prepare the icon, should be an object representing a pix_icon
var branchicon = false;
var icon = this.get('icon');
if (icon && (!isbranch || this.get('type') == NODETYPE.ACTIVITY)) {
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
branchicon = Y.Node.create('<img alt="" />');
branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
branchli.addClass('item_with_icon');
@ -409,6 +534,8 @@ BRANCH.prototype = {
},
/**
* Gets the UL element that children for this branch should be inserted into.
* @method getChildrenUL
* @return Node
*/
getChildrenUL : function() {
var ul = this.node.next('ul');
@ -423,6 +550,10 @@ BRANCH.prototype = {
*
* This function calls ajaxProcessResponse with the result of the AJAX
* request made here.
*
* @method ajaxLoad
* @param {EventFacade} e
* @return Bool
*/
ajaxLoad : function(e) {
if (e.type === 'actionkey' && e.action !== 'enter') {
@ -446,6 +577,7 @@ BRANCH.prototype = {
// We've already loaded this stuff.
return true;
}
Y.log('Loading navigation branch via AJAX: '+this.get('key'), 'note', 'moodle-block_navigation');
this.node.addClass('loadingbranch');
var params = {
@ -469,6 +601,11 @@ BRANCH.prototype = {
/**
* Processes an AJAX request to load the content of this branch through
* AJAX.
*
* @method ajaxProcessResponse
* @param {Int} tid The transaction id.
* @param {Object} outcome
* @return Boolean
*/
ajaxProcessResponse : function(tid, outcome) {
this.node.removeClass('loadingbranch');
@ -479,20 +616,28 @@ BRANCH.prototype = {
var coursecount = 0;
for (var i in object.children) {
if (typeof(object.children[i])==='object') {
if (object.children[i].type == NODETYPE.COURSE) {
if (object.children[i].type === NODETYPE.COURSE) {
coursecount++;
}
this.addChild(object.children[i]);
}
}
if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE || this.get('type') == NODETYPE.MYCATEGORY)
if ((this.get('type') === NODETYPE.CATEGORY || this.get('type') === NODETYPE.ROOTNODE || this.get('type') === NODETYPE.MYCATEGORY)
&& coursecount >= M.block_navigation.courselimit) {
this.addViewAllCoursesChild(this);
}
Y.log('AJAX loading complete.', 'note', 'moodle-block_navigation');
// If this block can dock tell the dock to resize if required and check
// the width on the dock panel in case it is presently in use.
if (this.get('tree').get('candock') && M.core.dock.notifyBlockChange) {
M.core.dock.notifyBlockChange(this.get('tree').id);
}
return true;
}
Y.log('AJAX loading complete but there were no children.', 'note', 'moodle-block_navigation');
} catch (ex) {
// If we got here then there was an error parsing the result
// If we got here then there was an error parsing the result.
Y.log('Error parsing AJAX response or adding branches to the navigation tree', 'error', 'moodle-block_navigation');
}
// The branch is empty so class it accordingly
this.node.replaceClass('branch', 'emptybranch');
@ -501,6 +646,10 @@ BRANCH.prototype = {
/**
* Turns the branch object passed to the method into a proper branch object
* and then adds it as a child of this branch.
*
* @method addChild
* @param {Object} branchobj
* @return Boolean
*/
addChild : function(branchobj) {
// Make the new branch into an object
@ -511,14 +660,14 @@ BRANCH.prototype = {
var count = 0, i, children = branch.get('children');
for (i in children) {
// Add each branch to the tree
if (children[i].type == NODETYPE.COURSE) {
if (children[i].type === NODETYPE.COURSE) {
count++;
}
if (typeof(children[i]) === 'object') {
branch.addChild(children[i]);
}
}
if ((branch.get('type') == NODETYPE.CATEGORY || branch.get('type') == NODETYPE.MYCATEGORY)
if ((branch.get('type') === NODETYPE.CATEGORY || branch.get('type') === NODETYPE.MYCATEGORY)
&& count >= M.block_navigation.courselimit) {
this.addViewAllCoursesChild(branch);
}
@ -528,10 +677,13 @@ BRANCH.prototype = {
/**
* Add a link to view all courses in a category
*
* @method addViewAllCoursesChild
* @param {BRANCH} branch
*/
addViewAllCoursesChild: function(branch) {
var url = null;
if (branch.get('type') == NODETYPE.ROOTNODE) {
if (branch.get('type') === NODETYPE.ROOTNODE) {
if (branch.get('key') === 'mycourses') {
url = M.cfg.wwwroot + '/my';
} else {
@ -552,9 +704,22 @@ BRANCH.prototype = {
Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
NAME : 'navigation-branch',
ATTRS : {
/**
* The Tree this branch belongs to.
* @attribute tree
* @type TREE
* @required
* @writeOnce
*/
tree : {
writeOnce : 'initOnly',
validator : Y.Lang.isObject
},
/**
* The name of this branch.
* @attribute name
* @type String
*/
name : {
value : '',
validator : Y.Lang.isString,
@ -562,10 +727,21 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
return val.replace(/\n/g, '<br />');
}
},
/**
* The title to use for this branch.
* @attribute title
* @type String
*/
title : {
value : '',
validator : Y.Lang.isString
},
/**
* The ID of this branch.
* The ID and Type should always form a unique pair.
* @attribute id
* @type String
*/
id : {
value : '',
validator : Y.Lang.isString,
@ -577,31 +753,74 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
return val;
}
},
/**
* The key used to identify this branch easily if there is one.
* @attribute key
* @type String
*/
key : {
value : null
},
/**
* The type of this branch.
* @attribute type
* @type Int
*/
type : {
value : null
value : null,
setter : function(value) {
return parseInt(value, 10);
}
},
/**
* The link to use for this branch.
* @attribute link
* @type String
*/
link : {
value : false
},
/**
* The Icon to add when displaying this branch.
* @attribute icon
* @type Object
*/
icon : {
value : false,
validator : Y.Lang.isObject
},
/**
* True if this branch is expandable.
* @attribute expandable
* @type Boolean
*/
expandable : {
value : false,
validator : Y.Lang.isBool
},
/**
* True if this branch is hidden and should be displayed greyed out.
* @attribute hidden
* @type Boolean
*/
hidden : {
value : false,
validator : Y.Lang.isBool
},
/**
* True if this branch has any children.
* @attribute haschildren
* @type Boolean
*/
haschildren : {
value : false,
validator : Y.Lang.isBool
},
/**
* An array of other branches that appear as children of this branch.
* @attribute children
* @type Array
*/
children : {
value : [],
validator : Y.Lang.isArray
@ -609,40 +828,5 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
}
});
/**
* This namespace will contain all of the contents of the navigation blocks
* global navigation and settings.
* @namespace
*/
M.block_navigation = M.block_navigation || {
/** The number of expandable branches in existence */
expandablebranchcount:1,
courselimit : 20,
instance : null,
/**
* Add new instance of navigation tree to tree collection
*/
init_add_tree:function(properties) {
if (properties.courselimit) {
this.courselimit = properties.courselimit;
}
if (M.core_dock) {
M.core_dock.init(Y);
}
new TREE(properties);
}
};
}, '@VERSION@', {
"requires": [
"base",
"core_dock",
"io-base",
"node",
"dom",
"event-custom",
"event-delegate",
"json-parse"
]
});
}, '@VERSION@', {"requires": ["base", "io-base", "node", "event-synthetic", "event-delegate", "json-parse"]});

View File

@ -1,5 +1,51 @@
YUI.add('moodle-block_navigation-navigation', function (Y, NAME) {
/**
* Navigation block JS.
*
* This file contains the Navigation block JS..
*
* @module moodle-block_navigation-navigation
*/
/**
* This namespace will contain all of the contents of the navigation blocks
* global navigation and settings.
* @namespace M
* @class block_navigation
* @static
*/
M.block_navigation = M.block_navigation || {};
/**
* The number of expandable branches in existence.
*
* @property expandablebranchcount
* @protected
* @static
*/
M.block_navigation.expandablebranchcount = 1;
/**
* The maximum number of courses to show as part of a branch.
*
* @property courselimit
* @protected
* @static
*/
M.block_navigation.courselimit = 20;
/**
* Add new instance of navigation tree to tree collection
*
* @method init_add_tree
* @static
* @param {Object} properties
*/
M.block_navigation.init_add_tree = function(properties) {
if (properties.courselimit) {
this.courselimit = properties.courselimit;
}
new TREE(properties);
};
/**
* A 'actionkey' Event to help with Y.delegate().
* The event consists of the left arrow, right arrow, enter and space keys.
@ -9,24 +55,35 @@ YUI.add('moodle-block_navigation-navigation', function (Y, NAME) {
* This event is delegated to branches in the navigation tree.
* The on() method to subscribe allows specifying the desired trigger actions as JSON.
*
* Todo: This could be centralised, a similar Event is defined in blocks/dock.js
* @namespace M.block_navigation
* @class ActionKey
*/
Y.Event.define("actionkey", {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
/**
* The keys to trigger on.
* @method _keys
*/
_keys: {
//arrows
'37': 'collapse',
'39': 'expand',
//(@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
'32': 'toggle',
'13': 'enter'
},
/**
* Handles key events
* @method _keyHandler
* @param {EventFacade} e
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {Object} args
*/
_keyHandler: function (e, notifier, args) {
var actObj;
if (!args.actions) {
@ -40,6 +97,13 @@ Y.Event.define("actionkey", {
}
},
/**
* Subscribes to events.
* @method on
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
*/
on: function (node, sub, notifier) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
@ -50,11 +114,23 @@ Y.Event.define("actionkey", {
}
},
/**
* Detaches an event listener
* @method detach
*/
detach: function (node, sub) {
//detach our _detacher handle of the subscription made in on()
sub._detacher.detach();
},
/**
* Creates a delegated event listener.
* @method delegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
delegate: function (node, sub, notifier, filter) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
@ -65,45 +141,50 @@ Y.Event.define("actionkey", {
}
},
/**
* Detaches a delegated event listener.
* @method detachDelegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
detachDelegate: function (node, sub) {
sub._delegateDetacher.detach();
}
});
var EXPANSIONLIMIT_EVERYTHING = 0,
//EXPANSIONLIMIT_COURSE = 20,
//EXPANSIONLIMIT_SECTION = 30,
EXPANSIONLIMIT_COURSE = 20,
EXPANSIONLIMIT_SECTION = 30,
EXPANSIONLIMIT_ACTIVITY = 40;
/**
* Mappings for the different types of nodes coming from the navigation.
* Copied from lib/navigationlib.php navigation_node constants.
* @type object
*/
// Mappings for the different types of nodes coming from the navigation.
// Copied from lib/navigationlib.php navigation_node constants.
var NODETYPE = {
/** @type int Root node = 0 */
// @type int Root node = 0
ROOTNODE : 0,
/** @type int System context = 1 */
// @type int System context = 1
SYSTEM : 1,
/** @type int Course category = 10 */
// @type int Course category = 10
CATEGORY : 10,
/** @type int MYCATEGORY = 11 */
// @type int MYCATEGORY = 11
MYCATEGORY : 11,
/** @type int Course = 20 */
// @type int Course = 20
COURSE : 20,
/** @type int Course section = 30 */
// @type int Course section = 30
SECTION : 30,
/** @type int Activity (course module) = 40 */
// @type int Activity (course module) = 40
ACTIVITY : 40,
/** @type int Resource (course module = 50 */
// @type int Resource (course module = 50
RESOURCE : 50,
/** @type int Custom node (could be anything) = 60 */
// @type int Custom node (could be anything) = 60
CUSTOM : 60,
/** @type int Setting = 70 */
// @type int Setting = 70
SETTING : 70,
/** @type int User context = 80 */
// @type int User context = 80
USER : 80,
/** @type int Container = 90 */
// @type int Container = 90
CONTAINER : 90
};
@ -112,6 +193,11 @@ var NODETYPE = {
*
* This class establishes the tree initially, creating expandable branches as
* required, and delegating the expand/collapse event.
*
* @namespace M.block_navigation
* @class Tree
* @constructor
* @extends Y.Base
*/
var TREE = function() {
TREE.superclass.constructor.apply(this, arguments);
@ -119,17 +205,26 @@ var TREE = function() {
TREE.prototype = {
/**
* The tree's ID, normally its block instance id.
* @property id
* @type Int
* @protected
*/
id : null,
/**
* An array of initialised branches.
* @property branches
* @type Array
* @protected
*/
branches : [],
/**
* Initialise the tree object when its first created.
* @method initializer
* @param {Object} config
*/
initializer : function(config) {
this.id = config.id;
this.id = parseInt(config.id, 10);
var node = Y.one('#inst'+config.id);
@ -168,14 +263,11 @@ TREE.prototype = {
Y.delegate('click', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
Y.delegate('actionkey', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
}
// Call the generic blocks init method to add all the generic stuff
if (this.get('candock')) {
this.initialise_block(Y, node);
}
},
/**
* Fire actions for a branch when an event occurs.
* @method fire_branch_action
* @param {EventFacade} event
*/
fire_branch_action : function(event) {
var id = event.currentTarget.getAttribute('id');
@ -185,6 +277,9 @@ TREE.prototype = {
/**
* This is a callback function responsible for expanding and collapsing the
* branches of the tree. It is delegated to rather than multiple event handles.
* @method toggleExpansion
* @param {EventFacade} e
* @return Boolean
*/
toggleExpansion : function(e) {
// First check if they managed to click on the li iteslf, then find the closest
@ -240,46 +335,66 @@ TREE.prototype = {
// If this block can dock tell the dock to resize if required and check
// the width on the dock panel in case it is presently in use.
if (this.get('candock')) {
M.core_dock.resize();
var panel = M.core_dock.getPanel();
if (panel.visible) {
panel.correctWidth();
}
if (this.get('candock') && M.core.dock.notifyBlockChange) {
M.core.dock.notifyBlockChange(this.id);
}
return true;
}
};
// The tree extends the YUI base foundation.
Y.extend(TREE, Y.Base, TREE.prototype, {
NAME : 'navigation-tree',
ATTRS : {
instance : {
value : null
},
/**
* True if the block can dock.
* @attribute candock
* @type Boolean
*/
candock : {
validator : Y.Lang.isBool,
value : false
},
/**
* If set to true nodes will be opened/closed in an accordian fashion.
* @attribute accordian
* @type Boolean
*/
accordian : {
validator : Y.Lang.isBool,
value : false
},
/**
* The nodes that get shown.
* @attribute expansionlimit
* @type Integer
*/
expansionlimit : {
value : 0,
setter : function(val) {
return parseInt(val, 10);
val = parseInt(val, 10);
if (val !== EXPANSIONLIMIT_EVERYTHING &&
val !== EXPANSIONLIMIT_COURSE &&
val !== EXPANSIONLIMIT_SECTION &&
val !== EXPANSIONLIMIT_ACTIVITY) {
val = EXPANSIONLIMIT_EVERYTHING;
}
return val;
}
}
}
});
if (M.core_dock && M.core_dock.genericblock) {
Y.augment(TREE, M.core_dock.genericblock);
}
/**
* The tree branch class.
* The Branch class.
*
* This class is used to manage a tree branch, in particular its ability to load
* its contents by AJAX.
*
* @namespace M.block_navigation
* @class Branch
* @constructor
* @extends Y.Base
*/
BRANCH = function() {
BRANCH.superclass.constructor.apply(this, arguments);
@ -287,10 +402,15 @@ BRANCH = function() {
BRANCH.prototype = {
/**
* The node for this branch (p)
* @property node
* @type Node
* @protected
*/
node : null,
/**
* Initialises the branch when it is first created.
* @method initializer
* @param {Object} config
*/
initializer : function(config) {
var i,
@ -310,8 +430,7 @@ BRANCH.prototype = {
}
}
// Get the node for this branch
this.node = Y.one('#', this.get('id'));
// Now check whether the branch is not expandable because of the expansionlimit
this.node = Y.one('#'+this.get('id'));
var expansionlimit = this.get('tree').get('expansionlimit');
var type = this.get('type');
if (expansionlimit !== EXPANSIONLIMIT_EVERYTHING && type >= expansionlimit && type <= EXPANSIONLIMIT_ACTIVITY) {
@ -324,6 +443,11 @@ BRANCH.prototype = {
*
* This function creates a DOM structure for the branch and then injects
* it into the navigation tree at the correct point.
*
* @method draw
* @chainable
* @param {Node} element
* @return Branch
*/
draw : function(element) {
@ -345,7 +469,7 @@ BRANCH.prototype = {
// Prepare the icon, should be an object representing a pix_icon
var branchicon = false;
var icon = this.get('icon');
if (icon && (!isbranch || this.get('type') == NODETYPE.ACTIVITY)) {
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
branchicon = Y.Node.create('<img alt="" />');
branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
branchli.addClass('item_with_icon');
@ -409,6 +533,8 @@ BRANCH.prototype = {
},
/**
* Gets the UL element that children for this branch should be inserted into.
* @method getChildrenUL
* @return Node
*/
getChildrenUL : function() {
var ul = this.node.next('ul');
@ -423,6 +549,10 @@ BRANCH.prototype = {
*
* This function calls ajaxProcessResponse with the result of the AJAX
* request made here.
*
* @method ajaxLoad
* @param {EventFacade} e
* @return Bool
*/
ajaxLoad : function(e) {
if (e.type === 'actionkey' && e.action !== 'enter') {
@ -469,6 +599,11 @@ BRANCH.prototype = {
/**
* Processes an AJAX request to load the content of this branch through
* AJAX.
*
* @method ajaxProcessResponse
* @param {Int} tid The transaction id.
* @param {Object} outcome
* @return Boolean
*/
ajaxProcessResponse : function(tid, outcome) {
this.node.removeClass('loadingbranch');
@ -479,20 +614,25 @@ BRANCH.prototype = {
var coursecount = 0;
for (var i in object.children) {
if (typeof(object.children[i])==='object') {
if (object.children[i].type == NODETYPE.COURSE) {
if (object.children[i].type === NODETYPE.COURSE) {
coursecount++;
}
this.addChild(object.children[i]);
}
}
if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE || this.get('type') == NODETYPE.MYCATEGORY)
if ((this.get('type') === NODETYPE.CATEGORY || this.get('type') === NODETYPE.ROOTNODE || this.get('type') === NODETYPE.MYCATEGORY)
&& coursecount >= M.block_navigation.courselimit) {
this.addViewAllCoursesChild(this);
}
// If this block can dock tell the dock to resize if required and check
// the width on the dock panel in case it is presently in use.
if (this.get('tree').get('candock') && M.core.dock.notifyBlockChange) {
M.core.dock.notifyBlockChange(this.get('tree').id);
}
return true;
}
} catch (ex) {
// If we got here then there was an error parsing the result
// If we got here then there was an error parsing the result.
}
// The branch is empty so class it accordingly
this.node.replaceClass('branch', 'emptybranch');
@ -501,6 +641,10 @@ BRANCH.prototype = {
/**
* Turns the branch object passed to the method into a proper branch object
* and then adds it as a child of this branch.
*
* @method addChild
* @param {Object} branchobj
* @return Boolean
*/
addChild : function(branchobj) {
// Make the new branch into an object
@ -511,14 +655,14 @@ BRANCH.prototype = {
var count = 0, i, children = branch.get('children');
for (i in children) {
// Add each branch to the tree
if (children[i].type == NODETYPE.COURSE) {
if (children[i].type === NODETYPE.COURSE) {
count++;
}
if (typeof(children[i]) === 'object') {
branch.addChild(children[i]);
}
}
if ((branch.get('type') == NODETYPE.CATEGORY || branch.get('type') == NODETYPE.MYCATEGORY)
if ((branch.get('type') === NODETYPE.CATEGORY || branch.get('type') === NODETYPE.MYCATEGORY)
&& count >= M.block_navigation.courselimit) {
this.addViewAllCoursesChild(branch);
}
@ -528,10 +672,13 @@ BRANCH.prototype = {
/**
* Add a link to view all courses in a category
*
* @method addViewAllCoursesChild
* @param {BRANCH} branch
*/
addViewAllCoursesChild: function(branch) {
var url = null;
if (branch.get('type') == NODETYPE.ROOTNODE) {
if (branch.get('type') === NODETYPE.ROOTNODE) {
if (branch.get('key') === 'mycourses') {
url = M.cfg.wwwroot + '/my';
} else {
@ -552,9 +699,22 @@ BRANCH.prototype = {
Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
NAME : 'navigation-branch',
ATTRS : {
/**
* The Tree this branch belongs to.
* @attribute tree
* @type TREE
* @required
* @writeOnce
*/
tree : {
writeOnce : 'initOnly',
validator : Y.Lang.isObject
},
/**
* The name of this branch.
* @attribute name
* @type String
*/
name : {
value : '',
validator : Y.Lang.isString,
@ -562,10 +722,21 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
return val.replace(/\n/g, '<br />');
}
},
/**
* The title to use for this branch.
* @attribute title
* @type String
*/
title : {
value : '',
validator : Y.Lang.isString
},
/**
* The ID of this branch.
* The ID and Type should always form a unique pair.
* @attribute id
* @type String
*/
id : {
value : '',
validator : Y.Lang.isString,
@ -577,31 +748,74 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
return val;
}
},
/**
* The key used to identify this branch easily if there is one.
* @attribute key
* @type String
*/
key : {
value : null
},
/**
* The type of this branch.
* @attribute type
* @type Int
*/
type : {
value : null
value : null,
setter : function(value) {
return parseInt(value, 10);
}
},
/**
* The link to use for this branch.
* @attribute link
* @type String
*/
link : {
value : false
},
/**
* The Icon to add when displaying this branch.
* @attribute icon
* @type Object
*/
icon : {
value : false,
validator : Y.Lang.isObject
},
/**
* True if this branch is expandable.
* @attribute expandable
* @type Boolean
*/
expandable : {
value : false,
validator : Y.Lang.isBool
},
/**
* True if this branch is hidden and should be displayed greyed out.
* @attribute hidden
* @type Boolean
*/
hidden : {
value : false,
validator : Y.Lang.isBool
},
/**
* True if this branch has any children.
* @attribute haschildren
* @type Boolean
*/
haschildren : {
value : false,
validator : Y.Lang.isBool
},
/**
* An array of other branches that appear as children of this branch.
* @attribute children
* @type Array
*/
children : {
value : [],
validator : Y.Lang.isArray
@ -609,40 +823,5 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
}
});
/**
* This namespace will contain all of the contents of the navigation blocks
* global navigation and settings.
* @namespace
*/
M.block_navigation = M.block_navigation || {
/** The number of expandable branches in existence */
expandablebranchcount:1,
courselimit : 20,
instance : null,
/**
* Add new instance of navigation tree to tree collection
*/
init_add_tree:function(properties) {
if (properties.courselimit) {
this.courselimit = properties.courselimit;
}
if (M.core_dock) {
M.core_dock.init(Y);
}
new TREE(properties);
}
};
}, '@VERSION@', {
"requires": [
"base",
"core_dock",
"io-base",
"node",
"dom",
"event-custom",
"event-delegate",
"json-parse"
]
});
}, '@VERSION@', {"requires": ["base", "io-base", "node", "event-synthetic", "event-delegate", "json-parse"]});

View File

@ -1,3 +1,49 @@
/**
* Navigation block JS.
*
* This file contains the Navigation block JS..
*
* @module moodle-block_navigation-navigation
*/
/**
* This namespace will contain all of the contents of the navigation blocks
* global navigation and settings.
* @namespace M
* @class block_navigation
* @static
*/
M.block_navigation = M.block_navigation || {};
/**
* The number of expandable branches in existence.
*
* @property expandablebranchcount
* @protected
* @static
*/
M.block_navigation.expandablebranchcount = 1;
/**
* The maximum number of courses to show as part of a branch.
*
* @property courselimit
* @protected
* @static
*/
M.block_navigation.courselimit = 20;
/**
* Add new instance of navigation tree to tree collection
*
* @method init_add_tree
* @static
* @param {Object} properties
*/
M.block_navigation.init_add_tree = function(properties) {
if (properties.courselimit) {
this.courselimit = properties.courselimit;
}
new TREE(properties);
};
/**
* A 'actionkey' Event to help with Y.delegate().
* The event consists of the left arrow, right arrow, enter and space keys.
@ -7,24 +53,35 @@
* This event is delegated to branches in the navigation tree.
* The on() method to subscribe allows specifying the desired trigger actions as JSON.
*
* Todo: This could be centralised, a similar Event is defined in blocks/dock.js
* @namespace M.block_navigation
* @class ActionKey
*/
Y.Event.define("actionkey", {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
/**
* The keys to trigger on.
* @method _keys
*/
_keys: {
//arrows
'37': 'collapse',
'39': 'expand',
//(@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
'32': 'toggle',
'13': 'enter'
},
/**
* Handles key events
* @method _keyHandler
* @param {EventFacade} e
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {Object} args
*/
_keyHandler: function (e, notifier, args) {
var actObj;
if (!args.actions) {
@ -38,6 +95,13 @@ Y.Event.define("actionkey", {
}
},
/**
* Subscribes to events.
* @method on
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
*/
on: function (node, sub, notifier) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
@ -48,11 +112,23 @@ Y.Event.define("actionkey", {
}
},
/**
* Detaches an event listener
* @method detach
*/
detach: function (node, sub) {
//detach our _detacher handle of the subscription made in on()
sub._detacher.detach();
},
/**
* Creates a delegated event listener.
* @method delegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
delegate: function (node, sub, notifier, filter) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
@ -63,45 +139,50 @@ Y.Event.define("actionkey", {
}
},
/**
* Detaches a delegated event listener.
* @method detachDelegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
detachDelegate: function (node, sub) {
sub._delegateDetacher.detach();
}
});
var EXPANSIONLIMIT_EVERYTHING = 0,
//EXPANSIONLIMIT_COURSE = 20,
//EXPANSIONLIMIT_SECTION = 30,
EXPANSIONLIMIT_COURSE = 20,
EXPANSIONLIMIT_SECTION = 30,
EXPANSIONLIMIT_ACTIVITY = 40;
/**
* Mappings for the different types of nodes coming from the navigation.
* Copied from lib/navigationlib.php navigation_node constants.
* @type object
*/
// Mappings for the different types of nodes coming from the navigation.
// Copied from lib/navigationlib.php navigation_node constants.
var NODETYPE = {
/** @type int Root node = 0 */
// @type int Root node = 0
ROOTNODE : 0,
/** @type int System context = 1 */
// @type int System context = 1
SYSTEM : 1,
/** @type int Course category = 10 */
// @type int Course category = 10
CATEGORY : 10,
/** @type int MYCATEGORY = 11 */
// @type int MYCATEGORY = 11
MYCATEGORY : 11,
/** @type int Course = 20 */
// @type int Course = 20
COURSE : 20,
/** @type int Course section = 30 */
// @type int Course section = 30
SECTION : 30,
/** @type int Activity (course module) = 40 */
// @type int Activity (course module) = 40
ACTIVITY : 40,
/** @type int Resource (course module = 50 */
// @type int Resource (course module = 50
RESOURCE : 50,
/** @type int Custom node (could be anything) = 60 */
// @type int Custom node (could be anything) = 60
CUSTOM : 60,
/** @type int Setting = 70 */
// @type int Setting = 70
SETTING : 70,
/** @type int User context = 80 */
// @type int User context = 80
USER : 80,
/** @type int Container = 90 */
// @type int Container = 90
CONTAINER : 90
};
@ -110,6 +191,11 @@ var NODETYPE = {
*
* This class establishes the tree initially, creating expandable branches as
* required, and delegating the expand/collapse event.
*
* @namespace M.block_navigation
* @class Tree
* @constructor
* @extends Y.Base
*/
var TREE = function() {
TREE.superclass.constructor.apply(this, arguments);
@ -117,17 +203,27 @@ var TREE = function() {
TREE.prototype = {
/**
* The tree's ID, normally its block instance id.
* @property id
* @type Int
* @protected
*/
id : null,
/**
* An array of initialised branches.
* @property branches
* @type Array
* @protected
*/
branches : [],
/**
* Initialise the tree object when its first created.
* @method initializer
* @param {Object} config
*/
initializer : function(config) {
this.id = config.id;
Y.log('Initialising navigation block tree', 'note', 'moodle-block_navigation');
this.id = parseInt(config.id, 10);
var node = Y.one('#inst'+config.id);
@ -166,14 +262,11 @@ TREE.prototype = {
Y.delegate('click', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
Y.delegate('actionkey', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
}
// Call the generic blocks init method to add all the generic stuff
if (this.get('candock')) {
this.initialise_block(Y, node);
}
},
/**
* Fire actions for a branch when an event occurs.
* @method fire_branch_action
* @param {EventFacade} event
*/
fire_branch_action : function(event) {
var id = event.currentTarget.getAttribute('id');
@ -183,6 +276,9 @@ TREE.prototype = {
/**
* This is a callback function responsible for expanding and collapsing the
* branches of the tree. It is delegated to rather than multiple event handles.
* @method toggleExpansion
* @param {EventFacade} e
* @return Boolean
*/
toggleExpansion : function(e) {
// First check if they managed to click on the li iteslf, then find the closest
@ -238,46 +334,66 @@ TREE.prototype = {
// If this block can dock tell the dock to resize if required and check
// the width on the dock panel in case it is presently in use.
if (this.get('candock')) {
M.core_dock.resize();
var panel = M.core_dock.getPanel();
if (panel.visible) {
panel.correctWidth();
}
if (this.get('candock') && M.core.dock.notifyBlockChange) {
M.core.dock.notifyBlockChange(this.id);
}
return true;
}
};
// The tree extends the YUI base foundation.
Y.extend(TREE, Y.Base, TREE.prototype, {
NAME : 'navigation-tree',
ATTRS : {
instance : {
value : null
},
/**
* True if the block can dock.
* @attribute candock
* @type Boolean
*/
candock : {
validator : Y.Lang.isBool,
value : false
},
/**
* If set to true nodes will be opened/closed in an accordian fashion.
* @attribute accordian
* @type Boolean
*/
accordian : {
validator : Y.Lang.isBool,
value : false
},
/**
* The nodes that get shown.
* @attribute expansionlimit
* @type Integer
*/
expansionlimit : {
value : 0,
setter : function(val) {
return parseInt(val, 10);
val = parseInt(val, 10);
if (val !== EXPANSIONLIMIT_EVERYTHING &&
val !== EXPANSIONLIMIT_COURSE &&
val !== EXPANSIONLIMIT_SECTION &&
val !== EXPANSIONLIMIT_ACTIVITY) {
val = EXPANSIONLIMIT_EVERYTHING;
}
return val;
}
}
}
});
if (M.core_dock && M.core_dock.genericblock) {
Y.augment(TREE, M.core_dock.genericblock);
}
/**
* The tree branch class.
* The Branch class.
*
* This class is used to manage a tree branch, in particular its ability to load
* its contents by AJAX.
*
* @namespace M.block_navigation
* @class Branch
* @constructor
* @extends Y.Base
*/
BRANCH = function() {
BRANCH.superclass.constructor.apply(this, arguments);
@ -285,10 +401,15 @@ BRANCH = function() {
BRANCH.prototype = {
/**
* The node for this branch (p)
* @property node
* @type Node
* @protected
*/
node : null,
/**
* Initialises the branch when it is first created.
* @method initializer
* @param {Object} config
*/
initializer : function(config) {
var i,
@ -308,8 +429,7 @@ BRANCH.prototype = {
}
}
// Get the node for this branch
this.node = Y.one('#', this.get('id'));
// Now check whether the branch is not expandable because of the expansionlimit
this.node = Y.one('#'+this.get('id'));
var expansionlimit = this.get('tree').get('expansionlimit');
var type = this.get('type');
if (expansionlimit !== EXPANSIONLIMIT_EVERYTHING && type >= expansionlimit && type <= EXPANSIONLIMIT_ACTIVITY) {
@ -322,6 +442,11 @@ BRANCH.prototype = {
*
* This function creates a DOM structure for the branch and then injects
* it into the navigation tree at the correct point.
*
* @method draw
* @chainable
* @param {Node} element
* @return Branch
*/
draw : function(element) {
@ -343,7 +468,7 @@ BRANCH.prototype = {
// Prepare the icon, should be an object representing a pix_icon
var branchicon = false;
var icon = this.get('icon');
if (icon && (!isbranch || this.get('type') == NODETYPE.ACTIVITY)) {
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
branchicon = Y.Node.create('<img alt="" />');
branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
branchli.addClass('item_with_icon');
@ -407,6 +532,8 @@ BRANCH.prototype = {
},
/**
* Gets the UL element that children for this branch should be inserted into.
* @method getChildrenUL
* @return Node
*/
getChildrenUL : function() {
var ul = this.node.next('ul');
@ -421,6 +548,10 @@ BRANCH.prototype = {
*
* This function calls ajaxProcessResponse with the result of the AJAX
* request made here.
*
* @method ajaxLoad
* @param {EventFacade} e
* @return Bool
*/
ajaxLoad : function(e) {
if (e.type === 'actionkey' && e.action !== 'enter') {
@ -444,6 +575,7 @@ BRANCH.prototype = {
// We've already loaded this stuff.
return true;
}
Y.log('Loading navigation branch via AJAX: '+this.get('key'), 'note', 'moodle-block_navigation');
this.node.addClass('loadingbranch');
var params = {
@ -467,6 +599,11 @@ BRANCH.prototype = {
/**
* Processes an AJAX request to load the content of this branch through
* AJAX.
*
* @method ajaxProcessResponse
* @param {Int} tid The transaction id.
* @param {Object} outcome
* @return Boolean
*/
ajaxProcessResponse : function(tid, outcome) {
this.node.removeClass('loadingbranch');
@ -477,20 +614,28 @@ BRANCH.prototype = {
var coursecount = 0;
for (var i in object.children) {
if (typeof(object.children[i])==='object') {
if (object.children[i].type == NODETYPE.COURSE) {
if (object.children[i].type === NODETYPE.COURSE) {
coursecount++;
}
this.addChild(object.children[i]);
}
}
if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE || this.get('type') == NODETYPE.MYCATEGORY)
if ((this.get('type') === NODETYPE.CATEGORY || this.get('type') === NODETYPE.ROOTNODE || this.get('type') === NODETYPE.MYCATEGORY)
&& coursecount >= M.block_navigation.courselimit) {
this.addViewAllCoursesChild(this);
}
Y.log('AJAX loading complete.', 'note', 'moodle-block_navigation');
// If this block can dock tell the dock to resize if required and check
// the width on the dock panel in case it is presently in use.
if (this.get('tree').get('candock') && M.core.dock.notifyBlockChange) {
M.core.dock.notifyBlockChange(this.get('tree').id);
}
return true;
}
Y.log('AJAX loading complete but there were no children.', 'note', 'moodle-block_navigation');
} catch (ex) {
// If we got here then there was an error parsing the result
// If we got here then there was an error parsing the result.
Y.log('Error parsing AJAX response or adding branches to the navigation tree', 'error', 'moodle-block_navigation');
}
// The branch is empty so class it accordingly
this.node.replaceClass('branch', 'emptybranch');
@ -499,6 +644,10 @@ BRANCH.prototype = {
/**
* Turns the branch object passed to the method into a proper branch object
* and then adds it as a child of this branch.
*
* @method addChild
* @param {Object} branchobj
* @return Boolean
*/
addChild : function(branchobj) {
// Make the new branch into an object
@ -509,14 +658,14 @@ BRANCH.prototype = {
var count = 0, i, children = branch.get('children');
for (i in children) {
// Add each branch to the tree
if (children[i].type == NODETYPE.COURSE) {
if (children[i].type === NODETYPE.COURSE) {
count++;
}
if (typeof(children[i]) === 'object') {
branch.addChild(children[i]);
}
}
if ((branch.get('type') == NODETYPE.CATEGORY || branch.get('type') == NODETYPE.MYCATEGORY)
if ((branch.get('type') === NODETYPE.CATEGORY || branch.get('type') === NODETYPE.MYCATEGORY)
&& count >= M.block_navigation.courselimit) {
this.addViewAllCoursesChild(branch);
}
@ -526,10 +675,13 @@ BRANCH.prototype = {
/**
* Add a link to view all courses in a category
*
* @method addViewAllCoursesChild
* @param {BRANCH} branch
*/
addViewAllCoursesChild: function(branch) {
var url = null;
if (branch.get('type') == NODETYPE.ROOTNODE) {
if (branch.get('type') === NODETYPE.ROOTNODE) {
if (branch.get('key') === 'mycourses') {
url = M.cfg.wwwroot + '/my';
} else {
@ -550,9 +702,22 @@ BRANCH.prototype = {
Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
NAME : 'navigation-branch',
ATTRS : {
/**
* The Tree this branch belongs to.
* @attribute tree
* @type TREE
* @required
* @writeOnce
*/
tree : {
writeOnce : 'initOnly',
validator : Y.Lang.isObject
},
/**
* The name of this branch.
* @attribute name
* @type String
*/
name : {
value : '',
validator : Y.Lang.isString,
@ -560,10 +725,21 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
return val.replace(/\n/g, '<br />');
}
},
/**
* The title to use for this branch.
* @attribute title
* @type String
*/
title : {
value : '',
validator : Y.Lang.isString
},
/**
* The ID of this branch.
* The ID and Type should always form a unique pair.
* @attribute id
* @type String
*/
id : {
value : '',
validator : Y.Lang.isString,
@ -575,58 +751,77 @@ Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
return val;
}
},
/**
* The key used to identify this branch easily if there is one.
* @attribute key
* @type String
*/
key : {
value : null
},
/**
* The type of this branch.
* @attribute type
* @type Int
*/
type : {
value : null
value : null,
setter : function(value) {
return parseInt(value, 10);
}
},
/**
* The link to use for this branch.
* @attribute link
* @type String
*/
link : {
value : false
},
/**
* The Icon to add when displaying this branch.
* @attribute icon
* @type Object
*/
icon : {
value : false,
validator : Y.Lang.isObject
},
/**
* True if this branch is expandable.
* @attribute expandable
* @type Boolean
*/
expandable : {
value : false,
validator : Y.Lang.isBool
},
/**
* True if this branch is hidden and should be displayed greyed out.
* @attribute hidden
* @type Boolean
*/
hidden : {
value : false,
validator : Y.Lang.isBool
},
/**
* True if this branch has any children.
* @attribute haschildren
* @type Boolean
*/
haschildren : {
value : false,
validator : Y.Lang.isBool
},
/**
* An array of other branches that appear as children of this branch.
* @attribute children
* @type Array
*/
children : {
value : [],
validator : Y.Lang.isArray
}
}
});
/**
* This namespace will contain all of the contents of the navigation blocks
* global navigation and settings.
* @namespace
*/
M.block_navigation = M.block_navigation || {
/** The number of expandable branches in existence */
expandablebranchcount:1,
courselimit : 20,
instance : null,
/**
* Add new instance of navigation tree to tree collection
*/
init_add_tree:function(properties) {
if (properties.courselimit) {
this.courselimit = properties.courselimit;
}
if (M.core_dock) {
M.core_dock.init(Y);
}
new TREE(properties);
}
};

View File

@ -2,11 +2,9 @@
"moodle-block_navigation-navigation": {
"requires": [
"base",
"core_dock",
"io-base",
"node",
"dom",
"event-custom",
"event-synthetic",
"event-delegate",
"json-parse"
]

View File

@ -91,10 +91,13 @@ class block_settings extends block_base {
}
function get_required_javascript() {
global $CFG;
$arguments = array('id' => $this->instance->id, 'instance' => $this->instance->id, 'candock' => $this->instance_can_be_docked());
$this->page->requires->yui_module(array('core_dock', 'moodle-block_navigation-navigation'), 'M.block_navigation.init_add_tree', array($arguments));
user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
parent::get_required_javascript();
$arguments = array(
'id' => $this->instance->id,
'instance' => $this->instance->id,
'candock' => $this->instance_can_be_docked()
);
$this->page->requires->yui_module('moodle-block_navigation-navigation', 'M.block_navigation.init_add_tree', array($arguments));
}
/**

View File

@ -231,7 +231,6 @@ $cache = '.var_export($cache, true).';
'currencies' => null,
'dbtransfer' => null,
'debug' => null,
'dock' => null,
'editor' => $CFG->dirroot.'/lib/editor',
'edufields' => null,
'enrol' => $CFG->dirroot.'/enrol',

View File

@ -2438,6 +2438,12 @@ class block_contents {
*/
public $collapsible = self::NOT_HIDEABLE;
/**
* Set this to true if the block is dockable.
* @var bool
*/
public $dockable = false;
/**
* @var array A (possibly empty) array of editing controls. Each element of
* this array should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption).

View File

@ -1070,12 +1070,18 @@ class core_renderer extends renderer_base {
if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
$bc->collapsible = block_contents::NOT_HIDEABLE;
}
if (!empty($bc->blockinstanceid)) {
$bc->attributes['data-instanceid'] = $bc->blockinstanceid;
}
$skiptitle = strip_tags($bc->title);
if ($bc->blockinstanceid && !empty($skiptitle)) {
$bc->attributes['aria-labelledby'] = 'instance-'.$bc->blockinstanceid.'-header';
} else if (!empty($bc->arialabel)) {
$bc->attributes['aria-label'] = $bc->arialabel;
}
if ($bc->dockable) {
$bc->attributes['data-dockable'] = 1;
}
if ($bc->collapsible == block_contents::HIDDEN) {
$bc->add_class('hidden');
}

View File

@ -245,7 +245,6 @@ class page_requirements_manager {
// Every page should include definition of following modules.
$this->js_module($this->find_module('core_filepicker'));
$this->js_module($this->find_module('core_dock'));
}
/**
@ -673,15 +672,6 @@ class page_requirements_manager {
$module = array('name' => 'core_completion',
'fullpath' => '/course/completion.js');
break;
case 'core_dock':
$module = array('name' => 'core_dock',
'fullpath' => '/blocks/dock.js',
'requires' => array('base', 'node', 'event-custom', 'event-mouseenter', 'event-resize', 'escape'),
'strings' => array(array('addtodock', 'block'),array('undockitem', 'block'),array('dockblock', 'block'),
array('undockblock', 'block'),array('undockall', 'block'),array('thisdirectionvertical', 'langconfig'),
array('hidedockpanel', 'block'),array('hidepanel', 'block')
));
break;
case 'core_message':
$module = array('name' => 'core_message',
'requires' => array('base', 'node', 'event', 'node-event-simulate'),

View File

@ -1444,7 +1444,7 @@ class moodle_page {
* @return void
*/
public function initialise_theme_and_output() {
global $OUTPUT, $PAGE, $SITE;
global $OUTPUT, $PAGE, $SITE, $CFG;
if (!empty($this->_wherethemewasinitialised)) {
return;
@ -1465,6 +1465,11 @@ class moodle_page {
}
$this->_theme->setup_blocks($this->pagelayout, $this->blocks);
if ($this->_theme->enable_dock && !empty($CFG->allowblockstodock)) {
$this->requires->strings_for_js(array('addtodock', 'undockitem', 'dockblock', 'undockblock', 'undockall', 'hidedockpanel', 'hidepanel'), 'block');
$this->requires->string_for_js('thisdirectionvertical', 'langconfig');
$this->requires->yui_module('moodle-core-dockloader', 'M.core.dock.loader.initLoader');
}
if ($this === $PAGE) {
$OUTPUT = $this->get_renderer('core');

View File

@ -47,7 +47,7 @@ class core_component_testcase extends advanced_testcase {
foreach($subsystems as $subsystem => $fulldir) {
$this->assertFalse(strpos($subsystem, '_'), 'Core subsystems must be one work without underscores');
if ($fulldir === null) {
if ($subsystem === 'dock' or $subsystem === 'filepicker' or $subsystem === 'help') {
if ($subsystem === 'filepicker' or $subsystem === 'help') {
// Arrgghh, let's not introduce more subsystems for no real reason...
} else {
// Lang strings.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,126 @@
YUI.add('moodle-core-dockloader', function (Y, NAME) {
var LOADERNAME = 'moodle-core-dock-loader';
/**
* Core namespace.
*
* @static
* @namespace M
* @class core
*/
M.core = M.core || {};
/**
* Dock namespace.
*
* @static
* @namespace M.core
* @class dock
*/
M.core.dock = M.core.dock || {};
/**
* Creates the move to dock icon for dockable blocks if it doesn't already exist.
*
* @static
* @method ensureMoveToIconExists
* @param {Node} blocknode The Blocks node (.block[data-instanceid])
*/
M.core.dock.ensureMoveToIconExists = function(blocknode) {
if (blocknode.one('.moveto')) {
return true;
}
var commands,
moveto = Y.Node.create('<input type="image" class="moveto customcommand requiresjs" />'),
blockaction = blocknode.one('.block_action'),
icon = 't/block_to_dock';
// Must set the image src seperatly of we get an error with XML strict headers
if (right_to_left()) {
icon = icon + '_rtl';
}
moveto.setAttribute('alt', M.util.get_string('addtodock', 'block'));
moveto.setAttribute('title', Y.Escape.html(M.util.get_string('dockblock', 'block', blocknode.one('.header .title h2').getHTML())));
moveto.setAttribute('src', M.util.image_url(icon, 'moodle'));
if (blockaction) {
blockaction.prepend(moveto);
} else {
commands = blocknode.one('.header .title .commands');
if (!commands && blocknode.one('.header .title')) {
commands = Y.Node.create('<div class="commands"></div>');
blocknode.one('.header .title').append(commands);
}
commands.append(moveto);
}
return true;
};
/**
* Dock loader.
*
* The dock loader is repsponsible for loading and initialising the dock only when required.
* By doing this we avoid the need to load unnecessary JavaScript into the page for the dock just incase
* it is being used.
*
* @static
* @namespace M.core.dock
* @class Loader
*/
M.core.dock.loader = M.core.dock.loader || {};
/**
* Delegation events
* @property delegationEvents
* @protected
* @type {Array}
*/
M.core.dock.loader.delegationEvents = [];
/**
* Initialises the dock loader.
*
* The dock loader works by either firing the dock immediately if there are already docked blocks.
* Or if there are not any docked blocks delegating two events and then loading and firing the dock when one of
* those delegated events is triggered.
*
* @method initLoader
*/
M.core.dock.loader.initLoader = function() {
Y.log('Dock loader initialising', 'note', LOADERNAME);
var dockedblocks = Y.all('.block[data-instanceid][data-dockable]'),
body = Y.one(document.body);
dockedblocks.each(function() {
var id = parseInt(this.getData('instanceid'), 10);
Y.log('Dock loader watching block with instance id: '+id, 'note', LOADERNAME);
M.core.dock.ensureMoveToIconExists(this);
});
if (dockedblocks.some(function(node){return node.hasClass('dock_on_load');})) {
Y.log('Loading dock module', 'note', LOADERNAME);
Y.use('moodle-core-dock', function() {
M.core.dock.init();
});
} else {
var callback = function(e) {
var i,
block = this.ancestor('.block[data-instanceid]'),
instanceid = block.getData('instanceid');
e.halt();
for (i in M.core.dock.loader.delegationEvents) {
M.core.dock.loader.delegationEvents[i].detach();
}
block.addClass('dock_on_load');
Y.log('Loading dock module', 'note', LOADERNAME);
Y.use('moodle-core-dock', function(){
M.util.set_user_preference('docked_block_instance_'+instanceid, 1);
M.core.dock.init();
});
};
M.core.dock.loader.delegationEvents.push(body.delegate('click', callback, '.moveto'));
M.core.dock.loader.delegationEvents.push(body.delegate('key', callback, '.moveto', 'enter'));
}
};
}, '@VERSION@', {"requires": ["escape"]});

View File

@ -0,0 +1 @@
YUI.add("moodle-core-dockloader",function(e,t){var n="moodle-core-dock-loader";M.core=M.core||{},M.core.dock=M.core.dock||{},M.core.dock.ensureMoveToIconExists=function(t){if(t.one(".moveto"))return!0;var n,r=e.Node.create('<input type="image" class="moveto customcommand requiresjs" />'),i=t.one(".block_action"),s="t/block_to_dock";return right_to_left()&&(s+="_rtl"),r.setAttribute("alt",M.util.get_string("addtodock","block")),r.setAttribute("title",e.Escape.html(M.util.get_string("dockblock","block",t.one(".header .title h2").getHTML()))),r.setAttribute("src",M.util.image_url(s,"moodle")),i?i.prepend(r):(n=t.one(".header .title .commands"),!n&&t.one(".header .title")&&(n=e.Node.create('<div class="commands"></div>'),t.one(".header .title").append(n)),n.append(r)),!0},M.core.dock.loader=M.core.dock.loader||{},M.core.dock.loader.delegationEvents=[],M.core.dock.loader.initLoader=function(){var t=e.all(".block[data-instanceid][data-dockable]"),n=e.one(document.body);t.each(function(){var e=parseInt(this.getData("instanceid"),10);M.core.dock.ensureMoveToIconExists(this)});if(t.some(function(e){return e.hasClass("dock_on_load")}))e.use("moodle-core-dock",function(){M.core.dock.init()});else{var r=function(t){var n,r=this.ancestor(".block[data-instanceid]"),i=r.getData("instanceid");t.halt();for(n in M.core.dock.loader.delegationEvents)M.core.dock.loader.delegationEvents[n].detach();r.addClass("dock_on_load"),e.use("moodle-core-dock",function(){M.util.set_user_preference("docked_block_instance_"+i,1),M.core.dock.init()})};M.core.dock.loader.delegationEvents.push(n.delegate("click",r,".moveto")),M.core.dock.loader.delegationEvents.push(n.delegate("key",r,".moveto","enter"))}}},"@VERSION@",{requires:["escape"]});

View File

@ -0,0 +1,122 @@
YUI.add('moodle-core-dockloader', function (Y, NAME) {
var LOADERNAME = 'moodle-core-dock-loader';
/**
* Core namespace.
*
* @static
* @namespace M
* @class core
*/
M.core = M.core || {};
/**
* Dock namespace.
*
* @static
* @namespace M.core
* @class dock
*/
M.core.dock = M.core.dock || {};
/**
* Creates the move to dock icon for dockable blocks if it doesn't already exist.
*
* @static
* @method ensureMoveToIconExists
* @param {Node} blocknode The Blocks node (.block[data-instanceid])
*/
M.core.dock.ensureMoveToIconExists = function(blocknode) {
if (blocknode.one('.moveto')) {
return true;
}
var commands,
moveto = Y.Node.create('<input type="image" class="moveto customcommand requiresjs" />'),
blockaction = blocknode.one('.block_action'),
icon = 't/block_to_dock';
// Must set the image src seperatly of we get an error with XML strict headers
if (right_to_left()) {
icon = icon + '_rtl';
}
moveto.setAttribute('alt', M.util.get_string('addtodock', 'block'));
moveto.setAttribute('title', Y.Escape.html(M.util.get_string('dockblock', 'block', blocknode.one('.header .title h2').getHTML())));
moveto.setAttribute('src', M.util.image_url(icon, 'moodle'));
if (blockaction) {
blockaction.prepend(moveto);
} else {
commands = blocknode.one('.header .title .commands');
if (!commands && blocknode.one('.header .title')) {
commands = Y.Node.create('<div class="commands"></div>');
blocknode.one('.header .title').append(commands);
}
commands.append(moveto);
}
return true;
};
/**
* Dock loader.
*
* The dock loader is repsponsible for loading and initialising the dock only when required.
* By doing this we avoid the need to load unnecessary JavaScript into the page for the dock just incase
* it is being used.
*
* @static
* @namespace M.core.dock
* @class Loader
*/
M.core.dock.loader = M.core.dock.loader || {};
/**
* Delegation events
* @property delegationEvents
* @protected
* @type {Array}
*/
M.core.dock.loader.delegationEvents = [];
/**
* Initialises the dock loader.
*
* The dock loader works by either firing the dock immediately if there are already docked blocks.
* Or if there are not any docked blocks delegating two events and then loading and firing the dock when one of
* those delegated events is triggered.
*
* @method initLoader
*/
M.core.dock.loader.initLoader = function() {
var dockedblocks = Y.all('.block[data-instanceid][data-dockable]'),
body = Y.one(document.body);
dockedblocks.each(function() {
var id = parseInt(this.getData('instanceid'), 10);
M.core.dock.ensureMoveToIconExists(this);
});
if (dockedblocks.some(function(node){return node.hasClass('dock_on_load');})) {
Y.use('moodle-core-dock', function() {
M.core.dock.init();
});
} else {
var callback = function(e) {
var i,
block = this.ancestor('.block[data-instanceid]'),
instanceid = block.getData('instanceid');
e.halt();
for (i in M.core.dock.loader.delegationEvents) {
M.core.dock.loader.delegationEvents[i].detach();
}
block.addClass('dock_on_load');
Y.use('moodle-core-dock', function(){
M.util.set_user_preference('docked_block_instance_'+instanceid, 1);
M.core.dock.init();
});
};
M.core.dock.loader.delegationEvents.push(body.delegate('click', callback, '.moveto'));
M.core.dock.loader.delegationEvents.push(body.delegate('key', callback, '.moveto', 'enter'));
}
};
}, '@VERSION@', {"requires": ["escape"]});

View File

@ -0,0 +1,20 @@
{
"name": "moodle-core-dock",
"builds": {
"moodle-core-dock": {
"jsfiles": [
"dock.js",
"panel.js",
"tabheightmanager.js",
"actionkey.js",
"block.js",
"dockeditem.js"
]
},
"moodle-core-dockloader": {
"jsfiles": [
"dockloader.js"
]
}
}
}

118
lib/yui/src/dock/js/actionkey.js vendored Normal file
View File

@ -0,0 +1,118 @@
/**
* Dock JS.
*
* This file contains the action key event definition that is used for accessibility handling within the Dock.
*
* @module moodle-core-dock
*/
/**
* A 'dock:actionkey' Event.
* The event consists of the left arrow, right arrow, enter and space keys.
* More keys can be mapped to action meanings.
* actions: collapse , expand, toggle, enter.
*
* This event is subscribed to by dockitems.
* The on() method to subscribe allows specifying the desired trigger actions as JSON.
*
* This event can also be delegated if needed.
*
* @namespace M.core.dock
* @class ActionKey
*/
Y.Event.define("dock:actionkey", {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
/**
* The keys to trigger on.
* @method _keys
*/
_keys: {
//arrows
'37': 'collapse',
'39': 'expand',
//(@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
'32': 'toggle',
'13': 'enter'
},
/**
* Handles key events
* @method _keyHandler
* @param {EventFacade} e
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {Object} args
*/
_keyHandler: function (e, notifier, args) {
var actObj;
if (!args.actions) {
actObj = {collapse:true, expand:true, toggle:true, enter:true};
} else {
actObj = args.actions;
}
if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) {
e.action = this._keys[e.keyCode];
notifier.fire(e);
}
},
/**
* Subscribes to events.
* @method on
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
*/
on: function (node, sub, notifier) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
//no actions given
sub._detacher = node.on(this._event, this._keyHandler,this, notifier, {actions:false});
} else {
sub._detacher = node.on(this._event, this._keyHandler,this, notifier, sub.args[0]);
}
},
/**
* Detaches an event listener
* @method detach
*/
detach: function (node, sub, notifier) {
//detach our _detacher handle of the subscription made in on()
sub._detacher.detach();
},
/**
* Creates a delegated event listener.
* @method delegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
delegate: function (node, sub, notifier, filter) {
// subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
if (sub.args === null) {
//no actions given
sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, {actions:false});
} else {
sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, sub.args[0]);
}
},
/**
* Detaches a delegated event listener.
* @method detachDelegate
* @param {Node} node The node this subscription was applied to.
* @param {Subscription} sub The object tracking this subscription.
* @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
* @param {String|function} filter Selector string or function that accpets an event object and returns null.
*/
detachDelegate: function (node, sub, notifier, filter) {
sub._delegateDetacher.detach();
}
});

248
lib/yui/src/dock/js/block.js vendored Normal file
View File

@ -0,0 +1,248 @@
/**
* Dock JS.
*
* This file contains the block class used to manage blocks (both docked and not) for the dock.
*
* @module moodle-core-dock
*/
/**
* Block.
*
* @namespace M.core.dock
* @class Block
* @constructor
* @extends Y.Base
*/
var BLOCK = function() {
BLOCK.superclass.constructor.apply(this, arguments);
};
BLOCK.prototype = {
/**
* A content place holder used when the block has been docked.
* @property contentplaceholder
* @protected
* @type Node
*/
contentplaceholder : null,
/**
* The skip link associated with this block.
* @property contentskipanchor
* @protected
* @type Node
*/
contentskipanchor : null,
/**
* The cached content node for the actual block
* @property cachedcontentnode
* @protected
* @type Node
*/
cachedcontentnode : null,
/**
* If true the user preference isn't updated
* @property skipsetposition
* @protected
* @type Boolean
*/
skipsetposition : true,
/**
* The dock item associated with this block
* @property dockitem
* @protected
* @type DOCKITEM
*/
dockitem : null,
/**
* Called during the initialisation process of the object.
* @method initializer
*/
initializer : function() {
var node = Y.one('#inst'+this.get('id'));
if (!node) {
return false;
}
Y.log('Initialised block with instance id:'+this.get('id'), 'note', LOGNS);
M.core.dock.ensureMoveToIconExists(node);
// Move the block straight to the dock if required
if (node.hasClass(CSS.dockonload)) {
node.removeClass(CSS.dockonload);
var commands = node.one('.header .title .commands');
if (!commands) {
commands = Y.Node.create('<div class="commands"></div>');
if (node.one('.header .title')) {
node.one('.header .title').append(commands);
}
}
this.moveToDock(null, commands);
}
this.skipsetposition = false;
return true;
},
/**
* Returns the class associated with this block.
* @method _getBlockClass
* @private
* @param {Node} node
* @return String
*/
_getBlockClass : function(node) {
var classes = node.getAttribute('className').toString(),
regex = /(^|\s)(block_[a-zA-Z0-9_]+)(\s|$)/,
matches = regex.exec(classes);
if (matches) {
return matches[2];
}
return matches;
},
/**
* This function is reponsible for moving a block from the page structure onto the dock.
* @method moveToDock
* @param {EventFacade} e
*/
moveToDock : function(e) {
if (e) {
e.halt(true);
}
var dock = M.core.dock.get(),
id = this.get('id'),
blockcontent = Y.one('#inst'+id).one('.content');
if (!blockcontent) {
return;
}
Y.log('Moving block to the dock:'+this.get('id'), 'note', LOGNS);
var icon = (right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block',
char = (location.href.match(/\?/)) ? '&' : '?',
blocktitle,
blockcommands,
movetoimg,
moveto;
this.recordBlockState();
blocktitle = this.cachedcontentnode.one('.title h2').cloneNode(true);
blockcommands = this.cachedcontentnode.one('.title .commands').cloneNode(true);
// Must set the image src seperatly of we get an error with XML strict headers
movetoimg = Y.Node.create('<img alt="'+Y.Escape.html(M.str.block.undockitem)+'" title="'+
Y.Escape.html(M.util.get_string('undockblock', 'block', blocktitle.innerHTML)) +'" />');
movetoimg.setAttribute('src', M.util.image_url(icon, 'moodle'));
moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>').append(movetoimg);
moveto.set('href', location.href + char + 'dock='+id);
blockcommands.append(moveto);
// Create a new dock item for the block
this.dockitem = new DOCKEDITEM({
block : this,
dock : dock,
blockinstanceid : id,
title : blocktitle,
contents : blockcontent,
commands : blockcommands,
blockclass : this._getBlockClass(Y.one('#inst'+id))
});
// Register an event so that when it is removed we can put it back as a block
dock.add(this.dockitem);
if (!this.skipsetposition) {
// save the users preference
M.util.set_user_preference('docked_block_instance_'+id, 1);
}
this.set('idDocked', true);
},
/**
* Records the block state and adds it to the docks holding area.
* @method recordBlockState
*/
recordBlockState : function() {
var id = this.get('id'),
dock = M.core.dock.get(),
node = Y.one('#inst'+id),
skipanchor = node.previous();
// Disable the skip anchor when docking
if (skipanchor.hasClass('skip-block')) {
this.contentskipanchor = skipanchor;
this.contentskipanchor.hide();
}
this.cachedcontentnode = node;
this.contentplaceholder = Y.Node.create('<div class="block_dock_placeholder"></div>');
node.replace(this.contentplaceholder);
dock.addToHoldingArea(node);
node = null;
if (!this.cachedcontentnode.one('.title .commands')) {
this.cachedcontentnode.one('.title').append(Y.Node.create('<div class="commands"></div>'));
}
},
/**
* This function removes a block from the dock and puts it back into the page structure.
* @method returnToBlock
* @return {Boolean}
*/
returnToBlock : function() {
var id = this.get('id');
Y.log('Moving block out of the dock:'+this.get('id'), 'note', LOGNS);
// Enable the skip anchor when going back to block mode
if (this.contentskipanchor) {
this.contentskipanchor.show();
}
if (this.cachedcontentnode.one('.header')) {
this.cachedcontentnode.one('.header').insert(this.dockitem.get('contents'), 'after');
} else {
this.cachedcontentnode.insert(this.dockitem.get('contents'));
}
this.contentplaceholder.replace(this.cachedcontentnode);
this.cachedcontentnode = Y.one('#'+this.cachedcontentnode.get('id'));
var commands = this.dockitem.get('commands');
if (commands) {
commands.all('.hidepanelicon').remove();
commands.all('.moveto').remove();
commands.remove();
}
this.cachedcontentnode.one('.title').append(commands);
this.cachedcontentnode = null;
M.util.set_user_preference('docked_block_instance_'+id, 0);
this.set('idDocked', false);
return true;
}
};
Y.extend(BLOCK, Y.Base, BLOCK.prototype, {
NAME : 'moodle-core-dock-block',
ATTRS : {
/**
* The block instance ID
* @attribute id
* @writeOnce
* @type Number
*/
id : {
writeOnce : 'initOnly',
setter : function(value) {
return parseInt(value, 10);
}
},
/**
* True if the block has been docked.
* @attribute isDocked
* @default false
* @type Boolean
*/
isDocked : {
value : false
}
}
});

1077
lib/yui/src/dock/js/dock.js vendored Normal file

File diff suppressed because it is too large Load Diff

315
lib/yui/src/dock/js/dockeditem.js vendored Normal file
View File

@ -0,0 +1,315 @@
/**
* Dock JS.
*
* This file contains the docked item class.
*
* @module moodle-core-dock
*/
/**
* Docked item.
*
* @namespace M.core.dock
* @class DockedItem
* @constructor
* @extends Y.Base
* @uses Y.EventTarget
*/
var DOCKEDITEM = function() {
DOCKEDITEM.superclass.constructor.apply(this, arguments);
};
DOCKEDITEM.prototype = {
/**
* Set to true if this item is currently being displayed.
* @property active
* @protected
* @type Boolean
*/
active : false,
/**
* Called during the initialisation process of the object.
* @method initializer
*/
initializer : function() {
var title = this.get('title'),
titlestring;
/**
* Fired before the docked item has been drawn.
* @event dockeditem:drawstart
*/
this.publish('dockeditem:drawstart', {prefix:'dockeditem'});
/**
* Fired after the docked item has been drawn.
* @event dockeditem:drawcomplete
*/
this.publish('dockeditem:drawcomplete', {prefix:'dockeditem'});
/**
* Fired before the docked item is to be shown.
* @event dockeditem:showstart
*/
this.publish('dockeditem:showstart', {prefix:'dockeditem'});
/**
* Fired after the docked item has been shown.
* @event dockeditem:showcomplete
*/
this.publish('dockeditem:showcomplete', {prefix:'dockeditem'});
/**
* Fired before the docked item has been hidden.
* @event dockeditem:hidestart
*/
this.publish('dockeditem:hidestart', {prefix:'dockeditem'});
/**
* Fired after the docked item has been hidden.
* @event dockeditem:hidecomplete
*/
this.publish('dockeditem:hidecomplete', {prefix:'dockeditem'});
/**
* Fired when the docked item is removed from the dock.
* @event dockeditem:itemremoved
*/
this.publish('dockeditem:itemremoved', {prefix:'dockeditem'});
if (title) {
var type = title.get('nodeName');
titlestring = title.cloneNode(true);
title = Y.Node.create('<'+type+'></'+type+'>');
title = M.core.dock.fixTitleOrientation(title, titlestring.get('text'));
this.set('title', title);
this.set('titlestring', titlestring);
}
Y.log('Initialised dockeditem for block with title "'+this._getLogDescription(), 'note', LOGNS);
},
/**
* This function draws the item on the dock.
* @method draw
* @return Boolean
*/
draw : function() {
var create = Y.Node.create,
dock = this.get('dock'),
count = dock.count,
docktitle,
dockitem,
closeicon,
closeiconimg,
id = this.get('id');
this.fire('dockeditem:drawstart');
docktitle = create('<div id="dock_item_'+id+'_title" role="menu" aria-haspopup="true" class="'+CSS.dockedtitle+'"></div>');
docktitle.append(this.get('title'));
dockitem = create('<div id="dock_item_'+id+'" class="'+CSS.dockeditem+'" tabindex="0" rel="'+id+'"></div>');
if (count === 1) {
dockitem.addClass('firstdockitem');
}
dockitem.append(docktitle);
dock.append(dockitem);
closeiconimg = create('<img alt="'+M.str.block.hidepanel+'" title="'+M.str.block.hidedockpanel+'" />');
closeiconimg.setAttribute('src', M.util.image_url('t/dockclose', 'moodle'));
closeicon = create('<span class="hidepanelicon" tabindex="0"></span>').append(closeiconimg);
closeicon.on('forceclose|click', this.hide, this);
closeicon.on('dock:actionkey',this.hide, this, {actions:{enter:true,toggle:true}});
this.get('commands').append(closeicon);
this.set('dockTitleNode', docktitle);
this.set('dockItemNode', dockitem);
this.fire('dockeditem:drawcomplete');
return true;
},
/**
* This function toggles makes the item active and shows it.
* @method show
* @return Boolean
*/
show : function() {
var dock = this.get('dock'),
panel = dock.getPanel(),
docktitle = this.get('dockTitleNode');
dock.hideActive();
this.fire('dockeditem:showstart');
Y.log('Showing '+this._getLogDescription(), 'info', LOGNS);
panel.setHeader(this.get('titlestring'), this.get('commands'));
panel.setBody(Y.Node.create('<div class="'+this.get('blockclass')+' block_docked"></div>').append(this.get('contents')));
panel.show();
panel.correctWidth();
this.active = true;
// Add active item class first up
docktitle.addClass(CSS.activeitem);
// Set aria-exapanded property to true.
docktitle.set('aria-expanded', "true");
this.fire('dockeditem:showcomplete');
dock.resize();
return true;
},
/**
* This function hides the item and makes it inactive.
* @method hide
*/
hide : function() {
this.fire('dockeditem:hidestart');
Y.log('Hiding "'+this._getLogDescription(), 'info', LOGNS);
if (this.active) {
// No longer active
this.active = false;
// Hide the panel
this.get('dock').getPanel().hide();
}
// Remove the active class
// Set aria-exapanded property to false
this.get('dockTitleNode').removeClass(CSS.activeitem).set('aria-expanded', "false");
this.fire('dockeditem:hidecomplete');
},
/**
* A toggle between calling show and hide functions based on css.activeitem
* Applies rules to key press events (dock:actionkey)
* @method toggle
* @param {String} action
*/
toggle : function(action) {
var docktitle = this.get('dockTitleNode');
if (docktitle.hasClass(CSS.activeitem) && action !== 'expand') {
this.hide();
} else if (!docktitle.hasClass(CSS.activeitem) && action !== 'collapse') {
this.show();
}
},
/**
* This function removes the node and destroys it's bits.
* @method remove.
*/
remove : function () {
this.hide();
// Return the block to its original position.
this.get('block').returnToBlock();
// Remove the dock item node.
this.get('dockItemNode').remove();
this.fire('dockeditem:itemremoved');
},
/**
* Returns the description of this item to use for log calls.
* @method _getLogDescription
* @private
* @return {String}
*/
_getLogDescription : function() {
return this.get('titlestring').get('innerHTML')+' ('+this.get('blockinstanceid')+')';
}
};
Y.extend(DOCKEDITEM, Y.Base, DOCKEDITEM.prototype, {
NAME : 'moodle-core-dock-dockeditem',
ATTRS : {
/**
* The block this docked item is associated with.
* @attribute block
* @type BLOCK
* @writeOnce
* @required
*/
block : {
writeOnce : 'initOnly'
},
/**
* The dock itself.
* @attribute dock
* @type DOCK
* @writeOnce
* @required
*/
dock : {
writeOnce : 'initOnly'
},
/**
* The docked item ID. This will be given by the dock.
* @attribute id
* @type Number
*/
id : {},
/**
* Block instance id.Taken from the associated block.
* @attribute blockinstanceid
* @type Number
* @writeOnce
*/
blockinstanceid : {
writeOnce : 'initOnly',
setter : function(value) {
return parseInt(value, 10);
}
},
/**
* The title nodeof the docked item.
* @attribute title
* @type Node
* @default null
*/
title : {
value : null
},
/**
* The title string.
* @attribute titlestring
* @type String
*/
titlestring : {
value : null
},
/**
* The contents of the docked item
* @attribute contents
* @type Node
* @writeOnce
* @required
*/
contents : {
writeOnce : 'initOnly'
},
/**
* Commands associated with the block.
* @attribute commands
* @type Node
* @writeOnce
* @required
*/
commands : {
writeOnce : 'initOnly'
},
/**
* The block class.
* @attribute blockclass
* @type String
* @writeOnce
* @required
*/
blockclass : {
writeOnce : 'initOnly'
},
/**
* The title node for the docked block.
* @attribute dockTitleNode
* @type Node
*/
dockTitleNode : {
value : null
},
/**
* The item node for the docked block.
* @attribute dockItemNode
* @type Node
*/
dockItemNode : {
value : null
},
/**
* The container for the docked item (will contain the block contents when visible)
* @attribute dockcontainerNode
* @type Node
*/
dockcontainerNode : {
value : null
}
}
});
Y.augment(DOCKEDITEM, Y.EventTarget);

122
lib/yui/src/dock/js/dockloader.js vendored Normal file
View File

@ -0,0 +1,122 @@
var LOADERNAME = 'moodle-core-dock-loader';
/**
* Core namespace.
*
* @static
* @namespace M
* @class core
*/
M.core = M.core || {};
/**
* Dock namespace.
*
* @static
* @namespace M.core
* @class dock
*/
M.core.dock = M.core.dock || {};
/**
* Creates the move to dock icon for dockable blocks if it doesn't already exist.
*
* @static
* @method ensureMoveToIconExists
* @param {Node} blocknode The Blocks node (.block[data-instanceid])
*/
M.core.dock.ensureMoveToIconExists = function(blocknode) {
if (blocknode.one('.moveto')) {
return true;
}
var commands,
moveto = Y.Node.create('<input type="image" class="moveto customcommand requiresjs" />'),
blockaction = blocknode.one('.block_action'),
icon = 't/block_to_dock';
// Must set the image src seperatly of we get an error with XML strict headers
if (right_to_left()) {
icon = icon + '_rtl';
}
moveto.setAttribute('alt', M.util.get_string('addtodock', 'block'));
moveto.setAttribute('title', Y.Escape.html(M.util.get_string('dockblock', 'block', blocknode.one('.header .title h2').getHTML())));
moveto.setAttribute('src', M.util.image_url(icon, 'moodle'));
if (blockaction) {
blockaction.prepend(moveto);
} else {
commands = blocknode.one('.header .title .commands');
if (!commands && blocknode.one('.header .title')) {
commands = Y.Node.create('<div class="commands"></div>');
blocknode.one('.header .title').append(commands);
}
commands.append(moveto);
}
return true;
};
/**
* Dock loader.
*
* The dock loader is repsponsible for loading and initialising the dock only when required.
* By doing this we avoid the need to load unnecessary JavaScript into the page for the dock just incase
* it is being used.
*
* @static
* @namespace M.core.dock
* @class Loader
*/
M.core.dock.loader = M.core.dock.loader || {};
/**
* Delegation events
* @property delegationEvents
* @protected
* @type {Array}
*/
M.core.dock.loader.delegationEvents = [];
/**
* Initialises the dock loader.
*
* The dock loader works by either firing the dock immediately if there are already docked blocks.
* Or if there are not any docked blocks delegating two events and then loading and firing the dock when one of
* those delegated events is triggered.
*
* @method initLoader
*/
M.core.dock.loader.initLoader = function() {
Y.log('Dock loader initialising', 'note', LOADERNAME);
var dockedblocks = Y.all('.block[data-instanceid][data-dockable]'),
body = Y.one(document.body);
dockedblocks.each(function() {
var id = parseInt(this.getData('instanceid'), 10);
Y.log('Dock loader watching block with instance id: '+id, 'note', LOADERNAME);
M.core.dock.ensureMoveToIconExists(this);
});
if (dockedblocks.some(function(node){return node.hasClass('dock_on_load');})) {
Y.log('Loading dock module', 'note', LOADERNAME);
Y.use('moodle-core-dock', function() {
M.core.dock.init();
});
} else {
var callback = function(e) {
var i,
block = this.ancestor('.block[data-instanceid]'),
instanceid = block.getData('instanceid');
e.halt();
for (i in M.core.dock.loader.delegationEvents) {
M.core.dock.loader.delegationEvents[i].detach();
}
block.addClass('dock_on_load');
Y.log('Loading dock module', 'note', LOADERNAME);
Y.use('moodle-core-dock', function(){
M.util.set_user_preference('docked_block_instance_'+instanceid, 1);
M.core.dock.init();
});
};
M.core.dock.loader.delegationEvents.push(body.delegate('click', callback, '.moveto'));
M.core.dock.loader.delegationEvents.push(body.delegate('key', callback, '.moveto', 'enter'));
}
};

233
lib/yui/src/dock/js/panel.js vendored Normal file
View File

@ -0,0 +1,233 @@
/**
* Dock JS.
*
* This file contains the panel class used by the dock to display the content of docked blocks.
*
* @module moodle-core-dock
*/
/**
* Panel.
*
* @namespace M.core.dock
* @class Panel
* @constructor
* @extends Y.Base
* @uses Y.EventTarget
*/
var DOCKPANEL = function() {
DOCKPANEL.superclass.constructor.apply(this, arguments);
};
DOCKPANEL.prototype = {
/**
* True once the panel has been created.
* @property created
* @protected
* @type {Boolean}
*/
created : false,
/**
* Called during the initialisation process of the object.
* @method initializer
*/
initializer : function() {
Y.log('Panel initialising', 'note', LOGNS);
/**
* Fired before the panel is shown.
* @event dockpane::beforeshow
*/
this.publish('dockpanel:beforeshow', {prefix:'dockpanel'});
/**
* Fired after the panel is shown.
* @event dockpanel:shown
*/
this.publish('dockpanel:shown', {prefix:'dockpanel'});
/**
* Fired before the panel is hidden.
* @event dockpane::beforehide
*/
this.publish('dockpanel:beforehide', {prefix:'dockpanel'});
/**
* Fired after the panel is hidden.
* @event dockpanel:hidden
*/
this.publish('dockpanel:hidden', {prefix:'dockpanel'});
/**
* Fired when ever the dock panel is either hidden or shown.
* Always fired after the shown or hidden events.
* @event dockpanel:visiblechange
*/
this.publish('dockpanel:visiblechange', {prefix:'dockpanel'});
},
/**
* Creates the Panel if it has not already been created.
* @method create
* @return {Boolean}
*/
create : function() {
if (this.created) {
return true;
}
this.created = true;
var dock = this.get('dock'),
node = dock.get('dockNode');
this.set('node', Y.Node.create('<div id="dockeditempanel" class="dockitempanel_hidden"></div>'));
this.set('contentNode', Y.Node.create('<div class="dockeditempanel_content"></div>'));
this.set('headerNode', Y.Node.create('<div class="dockeditempanel_hd"></div>'));
this.set('bodyNode', Y.Node.create('<div class="dockeditempanel_bd"></div>'));
node.append(
this.get('node').append(this.get('contentNode').append(this.get('headerNode')).append(this.get('bodyNode')))
);
},
/**
* Displays the panel.
* @method show
*/
show : function() {
this.create();
this.fire('dockpanel:beforeshow');
this.set('visible', true);
this.get('node').removeClass('dockitempanel_hidden');
this.fire('dockpanel:shown');
this.fire('dockpanel:visiblechange');
},
/**
* Hides the panel
* @method hide
*/
hide : function() {
this.fire('dockpanel:beforehide');
this.set('visible', false);
this.get('node').addClass('dockitempanel_hidden');
this.fire('dockpanel:hidden');
this.fire('dockpanel:visiblechange');
},
/**
* Sets the panel header.
* @method setHeader
* @param {Node|String} content
*/
setHeader : function(content) {
this.create();
var header = this.get('headerNode');
header.setContent(content);
if (arguments.length > 1) {
for (var i=1;i < arguments.length;i++) {
header.append(arguments[i]);
}
}
},
/**
* Sets the panel body.
* @method setBody
* @param {Node|String} content
*/
setBody : function(content) {
this.create();
this.get('bodyNode').setContent(content);
},
/**
* Sets the new top mark of the panel.
*
* @method setTop
* @param {Number} newtop
*/
setTop : function(newtop) {
if (Y.UA.ie > 0 && Y.UA.ie < 7) {
this.get('node').setY(newtop);
} else {
this.get('node').setStyle('top', newtop.toString()+'px');
}
},
/**
* Corrects the width of the panel.
* @method correctWidth
*/
correctWidth : function() {
var bodyNode = this.get('bodyNode'),
// Width of content.
width = bodyNode.get('clientWidth'),
// Scrollable width of content.
scroll = bodyNode.get('scrollWidth'),
// Width of content container with overflow.
offsetWidth = bodyNode.get('offsetWidth'),
// The new width - defaults to the current width.
newWidth = width,
// The max width (80% of screen).
maxWidth = Math.round(bodyNode.get('winWidth') * 0.8);
// If the scrollable width is more than the visible width
if (scroll > width) {
// Content width
// + the difference
// + any rendering difference (borders, padding)
// + 10px to make it look nice.
newWidth = width + (scroll - width) + ((offsetWidth - width)*2) + 10;
}
// Make sure its not more then the maxwidth
if (newWidth > maxWidth) {
newWidth = maxWidth;
}
// Set the new width if its more than the old width.
if (newWidth > offsetWidth) {
this.get('node').setStyle('width', newWidth+'px');
}
}
};
Y.extend(DOCKPANEL, Y.Base, DOCKPANEL.prototype, {
NAME : 'moodle-core-dock-panel',
ATTRS : {
/**
* The dock itself.
* @attribute dock
* @type DOCK
* @writeonce
*/
dock : {
writeOnce : 'initOnly'
},
/**
* The node that contains the whole panel.
* @attribute node
* @type Node
*/
node : {
value : null
},
/**
* The node that contains the header, body and footer.
* @attribute contentNode
* @type Node
*/
contentNode : {
value : null
},
/**
* The node that contains the header
* @attribute headerNode
* @type Node
*/
headerNode : {
value : null
},
/**
* The node that contains the body
* @attribute bodyNode
* @type Node
*/
bodyNode : {
value : null
},
/**
* True if the panel is currently visible.
* @attribute visible
* @type Boolean
*/
visible : {
value : false
}
}
});
Y.augment(DOCKPANEL, Y.EventTarget);

111
lib/yui/src/dock/js/tabheightmanager.js vendored Normal file
View File

@ -0,0 +1,111 @@
/**
* Dock JS.
*
* This file contains the tab height manager.
* The tab height manager is responsible for ensure all tabs are visible all the time.
*
* @module moodle-core-dock
*/
/**
* Tab height manager.
*
* @namespace M.core.dock
* @class TabHeightManager
* @constructor
* @extends Y.Base
*/
var TABHEIGHTMANAGER = function() {
TABHEIGHTMANAGER.superclass.constructor.apply(this, arguments);
};
TABHEIGHTMANAGER.prototype = {
/**
* Initialises the dock sizer which then attaches itself to the required
* events in order to monitor the dock
* @method initializer
*/
initializer : function() {
var dock = this.get('dock');
dock.on('dock:itemschanged', this.checkSizing, this);
Y.on('windowresize', this.checkSizing, this);
},
/**
* Check if the size dock items needs to be adjusted
* @method checkSizing
*/
checkSizing : function() {
var dock = this.get('dock'),
node = dock.get('dockNode'),
items = dock.dockeditems,
possibleheight = node.get('offsetHeight') - node.one('.controls').get('offsetHeight') - (dock.get('bufferPanel')*3) - (items.length*2),
totalheight = 0,
id, dockedtitle;
if (items.length > 0) {
for (id in items) {
dockedtitle = Y.one(items[id].get('title')).ancestor('.'+CSS.dockedtitle);
if (dockedtitle) {
if (this.get('enabled')) {
dockedtitle.setStyle('height', 'auto');
}
totalheight += dockedtitle.get('offsetHeight') || 0;
}
}
if (totalheight > possibleheight) {
this.enable(possibleheight);
}
}
},
/**
* Enables the dock sizer and resizes where required.
* @method enable
* @param {Number} possibleheight
*/
enable : function(possibleheight) {
var dock = this.get('dock'),
items = dock.dockeditems,
count = dock.count,
runningcount = 0,
usedheight = 0,
id, itemtitle, itemheight, offsetheight;
Y.log('Enabling the dock tab sizer.', 'note', LOGNS);
this.set('enabled', true);
for (id in items) {
itemtitle = Y.one(items[id].get('title')).ancestor('.'+CSS.dockedtitle);
if (!itemtitle) {
continue;
}
itemheight = Math.floor((possibleheight-usedheight) / (count - runningcount));
offsetheight = itemtitle.get('offsetHeight');
itemtitle.setStyle('overflow', 'hidden');
if (offsetheight > itemheight) {
itemtitle.setStyle('height', itemheight+'px');
usedheight += itemheight;
} else {
usedheight += offsetheight;
}
runningcount++;
}
}
};
Y.extend(TABHEIGHTMANAGER, Y.Base, TABHEIGHTMANAGER.prototype, {
NAME : 'moodle-core-tabheightmanager',
ATTRS : {
/**
* The dock.
* @attribute dock
* @type DOCK
* @writeOnce
*/
dock : {
writeOnce : 'initOnly'
},
/**
* True if the item_sizer is being used, false otherwise.
* @attribute enabled
* @type Bool
*/
enabled : {
value : false
}
}
});

View File

@ -0,0 +1,18 @@
{
"moodle-core-dock": {
"requires": [
"base",
"node",
"event-custom",
"event-mouseenter",
"event-resize",
"escape",
"moodle-core-dockloader"
]
},
"moodle-core-dockloader": {
"requires": [
"escape"
]
}
}