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). 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; } } );