- Author:
- David Nickerson <nickerso@users.sourceforge.net>
- Date:
- 2009-07-16 02:00:03+12:00
- Desc:
- the starting point for the HH tutorial example
- Permanent Source URI:
- https://models.fieldml.org/workspace/a1/rawfile/f6a8f90307388eb4b040ee3566b84d88b59247f7/dojo-presentation/js/dojo/dijit/_Container.js
dojo.provide("dijit._Container");
dojo.declare("dijit._Contained",
null,
{
// summary
// Mixin for widgets that are children of a container widget
//
// example:
// | // make a basic custom widget that knows about it's parents
// | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
//
getParent: function(){
// summary:
// Returns the parent widget of this widget, assuming the parent
// implements dijit._Container
for(var p=this.domNode.parentNode; p; p=p.parentNode){
var id = p.getAttribute && p.getAttribute("widgetId");
if(id){
var parent = dijit.byId(id);
return parent.isContainer ? parent : null;
}
}
return null;
},
_getSibling: function(which){
var node = this.domNode;
do{
node = node[which+"Sibling"];
}while(node && node.nodeType != 1);
if(!node){ return null; } // null
var id = node.getAttribute("widgetId");
return dijit.byId(id);
},
getPreviousSibling: function(){
// summary:
// Returns null if this is the first child of the parent,
// otherwise returns the next element sibling to the "left".
return this._getSibling("previous"); // Mixed
},
getNextSibling: function(){
// summary:
// Returns null if this is the last child of the parent,
// otherwise returns the next element sibling to the "right".
return this._getSibling("next"); // Mixed
},
getIndexInParent: function(){
// summary:
// Returns the index of this widget within its container parent.
// It returns -1 if the parent does not exist, or if the parent
// is not a dijit._Container
var p = this.getParent();
if(!p || !p.getIndexOfChild){
return -1; // int
}
return p.getIndexOfChild(this); // int
}
}
);
dojo.declare("dijit._Container",
null,
{
// summary:
// Mixin for widgets that contain a set of widget children.
// description:
// Use this mixin for widgets that needs to know about and
// keep track of their widget children. Suitable for widgets like BorderContainer
// and TabContainer which contain (only) a set of child widgets.
//
// It's not suitable for widgets like ContentPane
// which contains mixed HTML (plain DOM nodes in addition to widgets),
// and where contained widgets are not necessarily directly below
// this.containerNode. In that case calls like addChild(node, position)
// wouldn't make sense.
// isContainer: Boolean
// Just a flag indicating that this widget descends from dijit._Container
isContainer: true,
buildRendering: function(){
this.inherited(arguments);
if(!this.containerNode){
// all widgets with descendants must set containerNode
this.containerNode = this.domNode;
}
},
addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
// summary:
// Makes the given widget a child of this widget.
// description:
// Inserts specified child widget's dom node as a child of this widget's
// container node, and possibly does otherprocessing (such as layout).
if(insertIndex === undefined){
insertIndex = "last";
}
var refNode = this.containerNode;
if(insertIndex && typeof insertIndex == "number"){
var children = dojo.query("> [widgetId]", refNode);
if(children && children.length >= insertIndex){
refNode = children[insertIndex-1]; insertIndex = "after";
}
}
dojo.place(widget.domNode, refNode, insertIndex);
// If I've been started but the child widget hasn't been started,
// start it now. Make sure to do this after widget has been
// inserted into the DOM tree, so it can see that it's being controlled by me,
// so it doesn't try to size itself.
if(this._started && !widget._started){
widget.startup();
}
},
removeChild: function(/*Widget or int*/ widget){
// summary:
// Removes the passed widget instance from this widget but does
// not destroy it. You can also pass in an integer indicating
// the index within the container to remove
if(typeof widget == "number" && widget > 0){
widget = this.getChildren()[widget];
}
// If we cannot find the widget, just return
if(!widget || !widget.domNode){ return; }
var node = widget.domNode;
node.parentNode.removeChild(node); // detach but don't destroy
},
_nextElement: function(node){
do{
node = node.nextSibling;
}while(node && node.nodeType != 1);
return node;
},
_firstElement: function(node){
node = node.firstChild;
if(node && node.nodeType != 1){
node = this._nextElement(node);
}
return node;
},
getChildren: function(){
// summary:
// Returns array of children widgets.
// description:
// Returns the widgets that are directly under this.containerNode.
return dojo.query("> [widgetId]", this.containerNode).map(dijit.byNode); // Widget[]
},
hasChildren: function(){
// summary:
// Returns true if widget has children, i.e. if this.containerNode contains something.
return !!this._firstElement(this.containerNode); // Boolean
},
destroyDescendants: function(/*Boolean*/ preserveDom){
dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
},
_getSiblingOfChild: function(/*Widget*/ child, /*int*/ dir){
// summary:
// Get the next or previous widget sibling of child
// dir:
// if 1, get the next sibling
// if -1, get the previous sibling
var node = child.domNode;
var which = (dir>0 ? "nextSibling" : "previousSibling");
do{
node = node[which];
}while(node && (node.nodeType != 1 || !dijit.byNode(node)));
return node ? dijit.byNode(node) : null;
},
getIndexOfChild: function(/*Widget*/ child){
// summary:
// Gets the index of the child in this container or -1 if not found
var children = this.getChildren();
for(var i=0, c; c=children[i]; i++){
if(c == child){
return i; // int
}
}
return -1; // int
}
}
);
dojo.declare("dijit._KeyNavContainer",
[dijit._Container],
{
// summary: A _Container with keyboard navigation of its children.
// decscription:
// To use this mixin, call connectKeyNavHandlers() in
// postCreate() and call startupKeyNavChildren() in startup().
// It provides normalized keyboard and focusing code for Container
// widgets.
/*=====
// focusedChild: Widget
// The currently focused child widget, or null if there isn't one
focusedChild: null,
=====*/
_keyNavCodes: {},
connectKeyNavHandlers: function(/*Array*/ prevKeyCodes, /*Array*/ nextKeyCodes){
// summary:
// Call in postCreate() to attach the keyboard handlers
// to the container.
// preKeyCodes: Array
// Key codes for navigating to the previous child.
// nextKeyCodes: Array
// Key codes for navigating to the next child.
var keyCodes = this._keyNavCodes = {};
var prev = dojo.hitch(this, this.focusPrev);
var next = dojo.hitch(this, this.focusNext);
dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev });
dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next });
this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
this.connect(this.domNode, "onfocus", "_onContainerFocus");
},
startupKeyNavChildren: function(){
// summary:
// Call in startup() to set child tabindexes to -1
dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
},
addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
// summary: Add a child to our _Container
dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
this._startupChild(widget);
},
focus: function(){
// summary: Default focus() implementation: focus the first child.
this.focusFirstChild();
},
focusFirstChild: function(){
// summary: Focus the first focusable child in the container.
this.focusChild(this._getFirstFocusableChild());
},
focusNext: function(){
// summary: Focus the next widget or focal node (for widgets
// with multiple focal nodes) within this container.
if(this.focusedChild && this.focusedChild.hasNextFocalNode
&& this.focusedChild.hasNextFocalNode()){
this.focusedChild.focusNext();
return;
}
var child = this._getNextFocusableChild(this.focusedChild, 1);
if(child.getFocalNodes){
this.focusChild(child, child.getFocalNodes()[0]);
}else{
this.focusChild(child);
}
},
focusPrev: function(){
// summary: Focus the previous widget or focal node (for widgets
// with multiple focal nodes) within this container.
if(this.focusedChild && this.focusedChild.hasPrevFocalNode
&& this.focusedChild.hasPrevFocalNode()){
this.focusedChild.focusPrev();
return;
}
var child = this._getNextFocusableChild(this.focusedChild, -1);
if(child.getFocalNodes){
var nodes = child.getFocalNodes();
this.focusChild(child, nodes[nodes.length-1]);
}else{
this.focusChild(child);
}
},
focusChild: function(/*Widget*/ widget, /*Node?*/ node){
// summary: Focus widget. Optionally focus 'node' within widget.
if(widget){
if(this.focusedChild && widget !== this.focusedChild){
this._onChildBlur(this.focusedChild);
}
this.focusedChild = widget;
if(node && widget.focusFocalNode){
widget.focusFocalNode(node);
}else{
widget.focus();
}
}
},
_startupChild: function(/*Widget*/ widget){
// summary:
// Set tabindex="-1" on focusable widgets so that we
// can focus them programmatically and by clicking.
// Connect focus and blur handlers.
if(widget.getFocalNodes){
dojo.forEach(widget.getFocalNodes(), function(node){
dojo.attr(node, "tabindex", -1);
this._connectNode(node);
}, this);
}else{
var node = widget.focusNode || widget.domNode;
if(widget.isFocusable()){
dojo.attr(node, "tabindex", -1);
}
this._connectNode(node);
}
},
_connectNode: function(/*Element*/ node){
this.connect(node, "onfocus", "_onNodeFocus");
this.connect(node, "onblur", "_onNodeBlur");
},
_onContainerFocus: function(evt){
// focus bubbles on Firefox,
// so just make sure that focus has really gone to the container
if(evt.target === this.domNode){
this.focusFirstChild();
}
},
_onContainerKeypress: function(evt){
if(evt.ctrlKey || evt.altKey){ return; }
var func = this._keyNavCodes[evt.charOrCode];
if(func){
func();
dojo.stopEvent(evt);
}
},
_onNodeFocus: function(evt){
// while focus is on a child,
// take the container out of the tab order so that
// we can shift-tab to the element before the container
dojo.attr(this.domNode, "tabindex", -1);
// record the child that has been focused
var widget = dijit.getEnclosingWidget(evt.target);
if(widget && widget.isFocusable()){
this.focusedChild = widget;
}
dojo.stopEvent(evt);
},
_onNodeBlur: function(evt){
// when focus leaves a child,
// reinstate the container's tabindex
if(this.tabIndex){
dojo.attr(this.domNode, "tabindex", this.tabIndex);
}
dojo.stopEvent(evt);
},
_onChildBlur: function(/*Widget*/ widget){
// summary:
// Called when focus leaves a child widget to go
// to a sibling widget.
},
_getFirstFocusableChild: function(){
return this._getNextFocusableChild(null, 1);
},
_getNextFocusableChild: function(child, dir){
if(child){
child = this._getSiblingOfChild(child, dir);
}
var children = this.getChildren();
for(var i=0; i < children.length; i++){
if(!child){
child = children[(dir>0) ? 0 : (children.length-1)];
}
if(child.isFocusable()){
return child;
}
child = this._getSiblingOfChild(child, dir);
}
// no focusable child found
return null;
}
}
);