';
- sb[sb.length] = this.getNodeHtml();
- sb[sb.length] = this.getChildrenHtml();
- sb[sb.length] = '
';
- return sb.join("");
+ return ['';
+
// Don't render the actual child node HTML unless this node is expanded.
if ( (this.hasChildren(true) && this.expanded) ||
(this.renderHidden && !this.isDynamic()) ) {
@@ -1616,7 +2354,7 @@
this.getChildrenEl().innerHTML = this.completeRender();
this.dynamicLoadComplete = true;
this.isLoading = false;
- this.expand();
+ this.expand(true);
this.tree.locked = false;
},
@@ -1655,14 +2393,54 @@
},
/**
- * Get the markup for the node. This is designed to be overrided so that we can
+ * Get the markup for the node. This may be overrided so that we can
* support different types of nodes.
* @method getNodeHtml
* @return {string} The HTML that will render this node.
*/
getNodeHtml: function() {
- return "";
+ var sb = [];
+
+ sb[sb.length] = '
';
+
+ for (var i=0;i';
+ }
+
+ if (this.hasIcon) {
+ sb[sb.length] = ' | ';
+ }
+
+ sb[sb.length] = '';
+ sb[sb.length] = this.getContentHtml();
+ sb[sb.length] = ' |
';
+
+ return sb.join("");
+
},
+ /**
+ * Get the markup for the contents of the node. This is designed to be overrided so that we can
+ * support different types of nodes.
+ * @method getContentHtml
+ * @return {string} The HTML that will render the content of this node.
+ */
+ getContentHtml: function () {
+ return "";
+ },
/**
* Regenerates the html for this node and its children. To be used when the
@@ -1676,7 +2454,7 @@
if (this.hasIcon) {
var el = this.getToggleEl();
if (el) {
- el.className = this.getStyle();
+ el.className = el.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());
}
}
},
@@ -1687,32 +2465,434 @@
* @return {string} string representation of the node
*/
toString: function() {
- return "Node (" + this.index + ")";
- }
+ return this._type + " (" + this.index + ")";
+ },
+ /**
+ * array of items that had the focus set on them
+ * so that they can be cleaned when focus is lost
+ * @property _focusHighlightedItems
+ * @type Array of DOM elements
+ * @private
+ */
+ _focusHighlightedItems: [],
+ /**
+ * DOM element that actually got the browser focus
+ * @property _focusedItem
+ * @type DOM element
+ * @private
+ */
+ _focusedItem: null,
+
+ /**
+ * Returns true if there are any elements in the node that can
+ * accept the real actual browser focus
+ * @method _canHaveFocus
+ * @return {boolean} success
+ * @private
+ */
+ _canHaveFocus: function() {
+ return this.getEl().getElementsByTagName('a').length > 0;
+ },
+ /**
+ * Removes the focus of previously selected Node
+ * @method _removeFocus
+ * @private
+ */
+ _removeFocus:function () {
+ if (this._focusedItem) {
+ Event.removeListener(this._focusedItem,'blur');
+ this._focusedItem = null;
+ }
+ var el;
+ while ((el = this._focusHighlightedItems.shift())) { // yes, it is meant as an assignment, really
+ Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
+ }
+ },
+ /**
+ * Sets the focus on the node element.
+ * It will only be able to set the focus on nodes that have anchor elements in it.
+ * Toggle or branch icons have anchors and can be focused on.
+ * If will fail in nodes that have no anchor
+ * @method focus
+ * @return {boolean} success
+ */
+ focus: function () {
+ var focused = false, self = this;
+ if (this.tree.currentFocus) {
+ this.tree.currentFocus._removeFocus();
+ }
+
+ var expandParent = function (node) {
+ if (node.parent) {
+ expandParent(node.parent);
+ node.parent.expand();
+ }
+ };
+ expandParent(this);
+
+ Dom.getElementsBy (
+ function (el) {
+ return /ygtv(([tl][pmn]h?)|(content))/.test(el.className);
+ } ,
+ 'td' ,
+ self.getEl().firstChild ,
+ function (el) {
+ Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
+ if (!focused) {
+ var aEl = el.getElementsByTagName('a');
+ if (aEl.length) {
+ aEl = aEl[0];
+ aEl.focus();
+ self._focusedItem = aEl;
+ Event.on(aEl,'blur',function () {
+ //console.log('f1');
+ self.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
+ self.tree.currentFocus = null;
+ self._removeFocus();
+ });
+ focused = true;
+ }
+ }
+ self._focusHighlightedItems.push(el);
+ }
+ );
+ if (focused) {
+ //console.log('f2');
+ this.tree.fireEvent('focusChanged',{oldNode:this.tree.currentFocus,newNode:this});
+ this.tree.currentFocus = this;
+ } else {
+ //console.log('f3');
+ this.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
+ this.tree.currentFocus = null;
+ this._removeFocus();
+ }
+ return focused;
+ },
+
+ /**
+ * Count of nodes in a branch
+ * @method getNodeCount
+ * @return {int} number of nodes in the branch
+ */
+ getNodeCount: function() {
+ for (var i = 0, count = 0;i< this.children.length;i++) {
+ count += this.children[i].getNodeCount();
+ }
+ return count + 1;
+ },
+
+ /**
+ * Returns an object which could be used to build a tree out of this node and its children.
+ * It can be passed to the tree constructor to reproduce this node as a tree.
+ * It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
+ * @method getNodeDefinition
+ * @return {Object | false} definition of the tree or false if the node or any children is defined as dynamic
+ */
+ getNodeDefinition: function() {
+
+ if (this.isDynamic()) { return false; }
+
+ var def, defs = Lang.merge(this.data), children = [];
+
+
+
+ if (this.expanded) {defs.expanded = this.expanded; }
+ if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
+ if (!this.renderHidden) { defs.renderHidden = this.renderHidden; }
+ if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
+ if (this.nowrap) { defs.nowrap = this.nowrap; }
+ if (this.className) { defs.className = this.className; }
+ if (this.editable) { defs.editable = this.editable; }
+ if (this.enableHighlight) { defs.enableHighlight = this.enableHighlight; }
+ if (this.highlightState) { defs.highlightState = this.highlightState; }
+ if (this.propagateHighlightUp) { defs.propagateHighlightUp = this.propagateHighlightUp; }
+ if (this.propagateHighlightDown) { defs.propagateHighlightDown = this.propagateHighlightDown; }
+ defs.type = this._type;
+
+
+
+ for (var i = 0; i < this.children.length;i++) {
+ def = this.children[i].getNodeDefinition();
+ if (def === false) { return false;}
+ children.push(def);
+ }
+ if (children.length) { defs.children = children; }
+ return defs;
+ },
+
+
+ /**
+ * Generates the link that will invoke this node's toggle method
+ * @method getToggleLink
+ * @return {string} the javascript url for toggling this node
+ */
+ getToggleLink: function() {
+ return 'return false;';
+ },
+
+ /**
+ * Sets the value of property for this node and all loaded descendants.
+ * Only public and defined properties can be set, not methods.
+ * Values for unknown properties will be assigned to the refNode.data object
+ * @method setNodesProperty
+ * @param name {string} Name of the property to be set
+ * @param value {any} value to be set
+ * @param refresh {boolean} if present and true, it does a refresh
+ */
+ setNodesProperty: function(name, value, refresh) {
+ if (name.charAt(0) != '_' && !Lang.isUndefined(this[name]) && !Lang.isFunction(this[name]) ) {
+ this[name] = value;
+ } else {
+ this.data[name] = value;
+ }
+ for (var i = 0; i < this.children.length;i++) {
+ this.children[i].setNodesProperty(name,value);
+ }
+ if (refresh) {
+ this.refresh();
+ }
+ },
+ /**
+ * Toggles the highlighted state of a Node
+ * @method toggleHighlight
+ */
+ toggleHighlight: function() {
+ if (this.enableHighlight) {
+ // unhighlights only if fully highligthed. For not or partially highlighted it will highlight
+ if (this.highlightState == 1) {
+ this.unhighlight();
+ } else {
+ this.highlight();
+ }
+ }
+ },
+
+ /**
+ * Turns highlighting on node.
+ * @method highlight
+ * @param _silent {boolean} optional, don't fire the highlightEvent
+ */
+ highlight: function(_silent) {
+ if (this.enableHighlight) {
+ if (this.tree.singleNodeHighlight) {
+ if (this.tree._currentlyHighlighted) {
+ this.tree._currentlyHighlighted.unhighlight();
+ }
+ this.tree._currentlyHighlighted = this;
+ }
+ this.highlightState = 1;
+ this._setHighlightClassName();
+ if (this.propagateHighlightDown) {
+ for (var i = 0;i < this.children.length;i++) {
+ this.children[i].highlight(true);
+ }
+ }
+ if (this.propagateHighlightUp) {
+ if (this.parent) {
+ this.parent._childrenHighlighted();
+ }
+ }
+ if (!_silent) {
+ this.tree.fireEvent('highlightEvent',this);
+ }
+ }
+ },
+ /**
+ * Turns highlighting off a node.
+ * @method unhighlight
+ * @param _silent {boolean} optional, don't fire the highlightEvent
+ */
+ unhighlight: function(_silent) {
+ if (this.enableHighlight) {
+ this.highlightState = 0;
+ this._setHighlightClassName();
+ if (this.propagateHighlightDown) {
+ for (var i = 0;i < this.children.length;i++) {
+ this.children[i].unhighlight(true);
+ }
+ }
+ if (this.propagateHighlightUp) {
+ if (this.parent) {
+ this.parent._childrenHighlighted();
+ }
+ }
+ if (!_silent) {
+ this.tree.fireEvent('highlightEvent',this);
+ }
+ }
+ },
+ /**
+ * Checks whether all or part of the children of a node are highlighted and
+ * sets the node highlight to full, none or partial highlight.
+ * If set to propagate it will further call the parent
+ * @method _childrenHighlighted
+ * @private
+ */
+ _childrenHighlighted: function() {
+ var yes = false, no = false;
+ if (this.enableHighlight) {
+ for (var i = 0;i < this.children.length;i++) {
+ switch(this.children[i].highlightState) {
+ case 0:
+ no = true;
+ break;
+ case 1:
+ yes = true;
+ break;
+ case 2:
+ yes = no = true;
+ break;
+ }
+ }
+ if (yes && no) {
+ this.highlightState = 2;
+ } else if (yes) {
+ this.highlightState = 1;
+ } else {
+ this.highlightState = 0;
+ }
+ this._setHighlightClassName();
+ if (this.propagateHighlightUp) {
+ if (this.parent) {
+ this.parent._childrenHighlighted();
+ }
+ }
+ }
+ },
+
+ /**
+ * Changes the classNames on the toggle and content containers to reflect the current highlighting
+ * @method _setHighlightClassName
+ * @private
+ */
+ _setHighlightClassName: function() {
+ var el = Dom.get('ygtvtableel' + this.index);
+ if (el) {
+ el.className = el.className.replace(/\bygtv-highlight\d\b/gi,'ygtv-highlight' + this.highlightState);
+ }
+ }
+
};
YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
+})();
+/**
+ * A custom YAHOO.widget.Node that handles the unique nature of
+ * the virtual, presentationless root node.
+ * @namespace YAHOO.widget
+ * @class RootNode
+ * @extends YAHOO.widget.Node
+ * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
+ * @constructor
+ */
+YAHOO.widget.RootNode = function(oTree) {
+ // Initialize the node with null params. The root node is a
+ // special case where the node has no presentation. So we have
+ // to alter the standard properties a bit.
+ this.init(null, null, true);
+
+ /*
+ * For the root node, we get the tree reference from as a param
+ * to the constructor instead of from the parent element.
+ */
+ this.tree = oTree;
+};
+YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
+
+ /**
+ * The node type
+ * @property _type
+ * @type string
+ * @private
+ * @default "RootNode"
+ */
+ _type: "RootNode",
+
+ // overrides YAHOO.widget.Node
+ getNodeHtml: function() {
+ return "";
+ },
+
+ toString: function() {
+ return this._type;
+ },
+
+ loadComplete: function() {
+ this.tree.draw();
+ },
+
+ /**
+ * Count of nodes in tree.
+ * It overrides Nodes.getNodeCount because the root node should not be counted.
+ * @method getNodeCount
+ * @return {int} number of nodes in the tree
+ */
+ getNodeCount: function() {
+ for (var i = 0, count = 0;i< this.children.length;i++) {
+ count += this.children[i].getNodeCount();
+ }
+ return count;
+ },
+
+ /**
+ * Returns an object which could be used to build a tree out of this node and its children.
+ * It can be passed to the tree constructor to reproduce this node as a tree.
+ * Since the RootNode is automatically created by treeView,
+ * its own definition is excluded from the returned node definition
+ * which only contains its children.
+ * @method getNodeDefinition
+ * @return {Object | false} definition of the tree or false if any child node is defined as dynamic
+ */
+ getNodeDefinition: function() {
+
+ for (var def, defs = [], i = 0; i < this.children.length;i++) {
+ def = this.children[i].getNodeDefinition();
+ if (def === false) { return false;}
+ defs.push(def);
+ }
+ return defs;
+ },
+
+ collapse: function() {},
+ expand: function() {},
+ getSiblings: function() { return null; },
+ focus: function () {}
+
+});
+(function () {
+ var Dom = YAHOO.util.Dom,
+ Lang = YAHOO.lang,
+ Event = YAHOO.util.Event;
/**
* The default node presentation. The first parameter should be
* either a string that will be used as the node's label, or an object
- * that has a string propery called label. By default, the clicking the
+ * that has at least a string property called label. By default, clicking the
* label will toggle the expanded/collapsed state of the node. By
- * changing the href property of the instance, this behavior can be
+ * setting the href property of the instance, this behavior can be
* changed so that the label will go to the specified href.
* @namespace YAHOO.widget
* @class TextNode
* @extends YAHOO.widget.Node
* @constructor
* @param oData {object} a string or object containing the data that will
- * be used to render this node
+ * be used to render this node.
+ * Providing a string is the same as providing an object with a single property named label.
+ * All values in the oData will be used to set equally named properties in the node
+ * as long as the node does have such properties, they are not undefined, private or functions.
+ * All attributes are made available in noderef.data, which
+ * can be used to store custom attributes. TreeView.getNode(s)ByProperty
+ * can be used to retrieve a node by one of the attributes.
* @param oParent {YAHOO.widget.Node} this node's parent node
- * @param expanded {boolean} the initial expanded/collapsed state
+ * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
*/
YAHOO.widget.TextNode = function(oData, oParent, expanded) {
if (oData) {
+ if (Lang.isString(oData)) {
+ oData = { label: oData };
+ }
this.init(oData, oParent, expanded);
this.setUpLabel(oData);
}
@@ -1745,56 +2925,59 @@
*/
label: null,
- textNodeParentChange: function() {
-
- /**
- * Custom event that is fired when the text node label is clicked. The
- * custom event is defined on the tree instance, so there is a single
- * event that handles all nodes in the tree. The node clicked is
- * provided as an argument
- *
- * @event labelClick
- * @for YAHOO.widget.TreeView
- * @param {YAHOO.widget.Node} node the node clicked
- */
- if (this.tree && !this.tree.hasEvent("labelClick")) {
- this.tree.createEvent("labelClick", this.tree);
- }
-
- },
+ /**
+ * The text for the title (tooltip) for the label element
+ * @property title
+ * @type string
+ */
+ title: null,
+
+ /**
+ * The href for the node's label. If one is not specified, the href will
+ * be set so that it toggles the node.
+ * @property href
+ * @type string
+ */
+ href: null,
/**
+ * The label href target, defaults to current window
+ * @property target
+ * @type string
+ */
+ target: "_self",
+
+ /**
+ * The node type
+ * @property _type
+ * @private
+ * @type string
+ * @default "TextNode"
+ */
+ _type: "TextNode",
+
+
+ /**
* Sets up the node label
* @method setUpLabel
* @param oData string containing the label, or an object with a label property
*/
setUpLabel: function(oData) {
- // set up the custom event on the tree
- this.textNodeParentChange();
- this.subscribe("parentChange", this.textNodeParentChange);
-
- if (typeof oData == "string") {
- oData = { label: oData };
+ if (Lang.isString(oData)) {
+ oData = {
+ label: oData
+ };
+ } else {
+ if (oData.style) {
+ this.labelStyle = oData.style;
+ }
}
+
this.label = oData.label;
- this.data.label = oData.label;
-
- // update the link
- if (oData.href) {
- this.href = oData.href;
- }
- // set the target
- if (oData.target) {
- this.target = oData.target;
- }
-
- if (oData.style) {
- this.labelStyle = oData.style;
- }
-
this.labelElId = "ygtvlabelel" + this.index;
+
},
/**
@@ -1804,156 +2987,145 @@
* @return {object} the element
*/
getLabelEl: function() {
- return document.getElementById(this.labelElId);
+ return Dom.get(this.labelElId);
},
// overrides YAHOO.widget.Node
- getNodeHtml: function() {
+ getContentHtml: function() {
var sb = [];
-
- sb[sb.length] = '
';
- sb[sb.length] = '';
-
- for (var i=0;i
';
- //sb[sb.length] = ' | ';
- sb[sb.length] = ' | ';
- }
-
- var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
- this.tree.id + '\',' + this.index + ')';
-
- sb[sb.length] = '';
-
- sb[sb.length] = '';
-
- /*
- sb[sb.length] = ' ';
- */
-
- //sb[sb.length] = ' ';
-
- sb[sb.length] = ' ';
- sb[sb.length] = ' | ';
- sb[sb.length] = '';
- sb[sb.length] = '';
sb[sb.length] = this.label;
- sb[sb.length] = '';
- sb[sb.length] = ' | ';
- sb[sb.length] = '
';
- sb[sb.length] = '
';
-
+ sb[sb.length] = this.href?'':'';
return sb.join("");
},
- /**
- * Executed when the label is clicked. Fires the labelClick custom event.
- * @method onLabelClick
- * @param me {Node} this node
- * @scope the anchor tag clicked
- * @return false to cancel the anchor click
+
+ /**
+ * Returns an object which could be used to build a tree out of this node and its children.
+ * It can be passed to the tree constructor to reproduce this node as a tree.
+ * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
+ * @method getNodeDefinition
+ * @return {Object | false} definition of the tree or false if this node or any descendant is defined as dynamic
*/
- onLabelClick: function(me) {
- return me.tree.fireEvent("labelClick", me);
- //return true;
+ getNodeDefinition: function() {
+ var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
+ if (def === false) { return false; }
+
+ // Node specific properties
+ def.label = this.label;
+ if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
+ if (this.title) { def.title = this.title; }
+ if (this.href) { def.href = this.href; }
+ if (this.target != '_self') { def.target = this.target; }
+
+ return def;
+
},
toString: function() {
- return "TextNode (" + this.index + ") " + this.label;
+ return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
+ },
+
+ // deprecated
+ onLabelClick: function() {
+ return false;
+ },
+ refresh: function() {
+ YAHOO.widget.TextNode.superclass.refresh.call(this);
+ var label = this.getLabelEl();
+ label.innerHTML = this.label;
+ if (label.tagName.toUpperCase() == 'A') {
+ label.href = this.href;
+ label.target = this.target;
+ }
}
+
+
+
});
+})();
/**
- * A custom YAHOO.widget.Node that handles the unique nature of
- * the virtual, presentationless root node.
+ * A menu-specific implementation that differs from TextNode in that only
+ * one sibling can be expanded at a time.
* @namespace YAHOO.widget
- * @class RootNode
- * @extends YAHOO.widget.Node
- * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
+ * @class MenuNode
+ * @extends YAHOO.widget.TextNode
+ * @param oData {object} a string or object containing the data that will
+ * be used to render this node.
+ * Providing a string is the same as providing an object with a single property named label.
+ * All values in the oData will be used to set equally named properties in the node
+ * as long as the node does have such properties, they are not undefined, private or functions.
+ * All attributes are made available in noderef.data, which
+ * can be used to store custom attributes. TreeView.getNode(s)ByProperty
+ * can be used to retrieve a node by one of the attributes.
+ * @param oParent {YAHOO.widget.Node} this node's parent node
+ * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
* @constructor
*/
-YAHOO.widget.RootNode = function(oTree) {
- // Initialize the node with null params. The root node is a
- // special case where the node has no presentation. So we have
- // to alter the standard properties a bit.
- this.init(null, null, true);
-
- /*
- * For the root node, we get the tree reference from as a param
- * to the constructor instead of from the parent element.
- */
- this.tree = oTree;
-};
+YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
+ YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
-YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
-
- // overrides YAHOO.widget.Node
- getNodeHtml: function() {
- return "";
- },
+ /*
+ * Menus usually allow only one branch to be open at a time.
+ */
+ this.multiExpand = false;
- toString: function() {
- return "RootNode";
- },
+};
- loadComplete: function() {
- this.tree.draw();
- },
+YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
- collapse: function() {},
- expand: function() {}
+ /**
+ * The node type
+ * @property _type
+ * @private
+ * @default "MenuNode"
+ */
+ _type: "MenuNode"
});
+(function () {
+ var Dom = YAHOO.util.Dom,
+ Lang = YAHOO.lang,
+ Event = YAHOO.util.Event;
+
/**
* This implementation takes either a string or object for the
- * oData argument. If is it a string, we will use it for the display
+ * oData argument. If is it a string, it will use it for the display
* of this node (and it can contain any html code). If the parameter
- * is an object, we look for a parameter called "html" that will be
+ * is an object,it looks for a parameter called "html" that will be
* used for this node's display.
* @namespace YAHOO.widget
* @class HTMLNode
* @extends YAHOO.widget.Node
* @constructor
* @param oData {object} a string or object containing the data that will
- * be used to render this node
+ * be used to render this node.
+ * Providing a string is the same as providing an object with a single property named html.
+ * All values in the oData will be used to set equally named properties in the node
+ * as long as the node does have such properties, they are not undefined, private or functions.
+ * All other attributes are made available in noderef.data, which
+ * can be used to store custom attributes. TreeView.getNode(s)ByProperty
+ * can be used to retrieve a node by one of the attributes.
* @param oParent {YAHOO.widget.Node} this node's parent node
- * @param expanded {boolean} the initial expanded/collapsed state
+ * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
* @param hasIcon {boolean} specifies whether or not leaf nodes should
- * have an icon
+ * be rendered with or without a horizontal line line and/or toggle icon. If the icon
+ * is not displayed, the content fills the space it would have occupied.
+ * This option operates independently of the leaf node presentation logic
+ * for dynamic nodes.
+ * (deprecated; use oData.hasIcon)
*/
YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
if (oData) {
@@ -1972,298 +3144,523 @@
*/
contentStyle: "ygtvhtml",
- /**
- * The generated id that will contain the data passed in by the implementer.
- * @property contentElId
- * @type string
- */
- contentElId: null,
/**
* The HTML content to use for this node's display
- * @property content
+ * @property html
* @type string
*/
- content: null,
+ html: null,
+
+/**
+ * The node type
+ * @property _type
+ * @private
+ * @type string
+ * @default "HTMLNode"
+ */
+ _type: "HTMLNode",
/**
* Sets up the node label
* @property initContent
- * @param {object} An html string or object containing an html property
- * @param {boolean} hasIcon determines if the node will be rendered with an
+ * @param oData {object} An html string or object containing an html property
+ * @param hasIcon {boolean} determines if the node will be rendered with an
* icon or not
*/
initContent: function(oData, hasIcon) {
- if (typeof oData == "string") {
- oData = { html: oData };
- }
-
- this.html = oData.html;
+ this.setHtml(oData);
this.contentElId = "ygtvcontentel" + this.index;
- this.hasIcon = hasIcon;
-
+ if (!Lang.isUndefined(hasIcon)) { this.hasIcon = hasIcon; }
+
},
/**
- * Returns the outer html element for this node's content
- * @method getContentEl
- * @return {HTMLElement} the element
+ * Synchronizes the node.data, node.html, and the node's content
+ * @property setHtml
+ * @param o {object} An html string or object containing an html property
*/
- getContentEl: function() {
- return document.getElementById(this.contentElId);
- },
+ setHtml: function(o) {
- // overrides YAHOO.widget.Node
- getNodeHtml: function() {
- var sb = [];
+ this.html = (typeof o === "string") ? o : o.html;
- sb[sb.length] = '
';
- sb[sb.length] = '';
-
- for (var i=0;i ';
- sb[sb.length] = ' | ';
+ var el = this.getContentEl();
+ if (el) {
+ el.innerHTML = this.html;
}
- if (this.hasIcon) {
- sb[sb.length] = ' | ';
- sb[sb.length] = '>';
- }
-
- sb[sb.length] = '';
- sb[sb.length] = this.html;
- sb[sb.length] = ' | ';
- sb[sb.length] = '
';
- sb[sb.length] = '
';
-
- return sb.join("");
},
- toString: function() {
- return "HTMLNode (" + this.index + ")";
+ // overrides YAHOO.widget.Node
+ getContentHtml: function() {
+ return this.html;
+ },
+
+ /**
+ * Returns an object which could be used to build a tree out of this node and its children.
+ * It can be passed to the tree constructor to reproduce this node as a tree.
+ * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
+ * @method getNodeDefinition
+ * @return {Object | false} definition of the tree or false if any node is defined as dynamic
+ */
+ getNodeDefinition: function() {
+ var def = YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this);
+ if (def === false) { return false; }
+ def.html = this.html;
+ return def;
+
}
-
});
+})();
+(function () {
+ var Dom = YAHOO.util.Dom,
+ Lang = YAHOO.lang,
+ Event = YAHOO.util.Event,
+ Calendar = YAHOO.widget.Calendar;
+
/**
- * A menu-specific implementation that differs from TextNode in that only
- * one sibling can be expanded at a time.
+ * A Date-specific implementation that differs from TextNode in that it uses
+ * YAHOO.widget.Calendar as an in-line editor, if available
+ * If Calendar is not available, it behaves as a plain TextNode.
* @namespace YAHOO.widget
- * @class MenuNode
+ * @class DateNode
* @extends YAHOO.widget.TextNode
* @param oData {object} a string or object containing the data that will
- * be used to render this node
+ * be used to render this node.
+ * Providing a string is the same as providing an object with a single property named label.
+ * All values in the oData will be used to set equally named properties in the node
+ * as long as the node does have such properties, they are not undefined, private nor functions.
+ * All attributes are made available in noderef.data, which
+ * can be used to store custom attributes. TreeView.getNode(s)ByProperty
+ * can be used to retrieve a node by one of the attributes.
* @param oParent {YAHOO.widget.Node} this node's parent node
- * @param expanded {boolean} the initial expanded/collapsed state
+ * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
* @constructor
*/
-YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
- if (oData) {
- this.init(oData, oParent, expanded);
- this.setUpLabel(oData);
- }
+YAHOO.widget.DateNode = function(oData, oParent, expanded) {
+ YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded);
+};
- /*
- * Menus usually allow only one branch to be open at a time.
+YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, {
+
+ /**
+ * The node type
+ * @property _type
+ * @type string
+ * @private
+ * @default "DateNode"
*/
- this.multiExpand = false;
+ _type: "DateNode",
+
+ /**
+ * Configuration object for the Calendar editor, if used.
+ * See
http://developer.yahoo.com/yui/calendar/#internationalization
+ * @property calendarConfig
+ */
+ calendarConfig: null,
+
+
+
+ /**
+ * If YAHOO.widget.Calendar is available, it will pop up a Calendar to enter a new date. Otherwise, it falls back to a plain <input> textbox
+ * @method fillEditorContainer
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @return void
+ */
+ fillEditorContainer: function (editorData) {
+
+ var cal, container = editorData.inputContainer;
+
+ if (Lang.isUndefined(Calendar)) {
+ Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode');
+ YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData);
+ return;
+ }
+
+ if (editorData.nodeType != this._type) {
+ editorData.nodeType = this._type;
+ editorData.saveOnEnter = false;
+
+ editorData.node.destroyEditorContents(editorData);
+ editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div')));
+ if (this.calendarConfig) {
+ cal.cfg.applyConfig(this.calendarConfig,true);
+ cal.cfg.fireQueue();
+ }
+ cal.selectEvent.subscribe(function () {
+ this.tree._closeEditor(true);
+ },this,true);
+ } else {
+ cal = editorData.inputObject;
+ }
-};
+ cal.cfg.setProperty("selected",this.label, false);
-YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
+ var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER');
+ var pageDate = this.label.split(delim);
+ cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]);
+ cal.cfg.fireQueue();
- toString: function() {
- return "MenuNode (" + this.index + ") " + this.label;
+ cal.render();
+ cal.oDomContainer.focus();
+ },
+ /**
+ * Saves the date entered in the editor into the DateNode label property and displays it.
+ * Overrides Node.saveEditorValue
+ * @method saveEditorValue
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ */
+ saveEditorValue: function (editorData) {
+ var node = editorData.node,
+ validator = node.tree.validator,
+ value;
+ if (Lang.isUndefined(Calendar)) {
+ value = editorData.inputElement.value;
+ } else {
+ var cal = editorData.inputObject,
+ date = cal.getSelectedDates()[0],
+ dd = [];
+
+ dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate();
+ dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1;
+ dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear();
+ value = dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER'));
+ }
+ if (Lang.isFunction(validator)) {
+ value = validator(value,node.label,node);
+ if (Lang.isUndefined(value)) { return false; }
+ }
+
+ node.label = value;
+ node.getLabelEl().innerHTML = value;
+ },
+ /**
+ * Returns an object which could be used to build a tree out of this node and its children.
+ * It can be passed to the tree constructor to reproduce this node as a tree.
+ * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
+ * @method getNodeDefinition
+ * @return {Object | false} definition of the node or false if this node or any descendant is defined as dynamic
+ */
+ getNodeDefinition: function() {
+ var def = YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);
+ if (def === false) { return false; }
+ if (this.calendarConfig) { def.calendarConfig = this.calendarConfig; }
+ return def;
}
+
});
-/**
- * A static factory class for tree view expand/collapse animations
- * @class TVAnim
- * @static
- */
-YAHOO.widget.TVAnim = function() {
- return {
- /**
- * Constant for the fade in animation
- * @property FADE_IN
- * @type string
- * @static
- */
- FADE_IN: "TVFadeIn",
+})();
+(function () {
+ var Dom = YAHOO.util.Dom,
+ Lang = YAHOO.lang,
+ Event = YAHOO.util.Event,
+ TV = YAHOO.widget.TreeView,
+ TVproto = TV.prototype;
- /**
- * Constant for the fade out animation
- * @property FADE_OUT
- * @type string
- * @static
- */
- FADE_OUT: "TVFadeOut",
+ /**
+ * An object to store information used for in-line editing
+ * for all Nodes of all TreeViews. It contains:
+ *
+ * - active {boolean}, whether there is an active cell editor
+ * - whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor
+ * - nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.
+ * - editorPanel {HTMLelement (<div>)} element holding the in-line editor
+ * - inputContainer {HTMLelement (<div>)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method
+ * - buttonsContainer {HTMLelement (<div>)} element which holds the <button> elements for Ok/Cancel. If you don't want any of the buttons, hide it via CSS styles, don't destroy it
+ * - node {YAHOO.widget.Node} reference to the Node being edited
+ * - saveOnEnter {boolean}, whether the Enter key should be accepted as a Save command (Esc. is always taken as Cancel), disable for multi-line input elements
+ *
+ * Editors are free to use this object to store additional data.
+ * @property editorData
+ * @static
+ * @for YAHOO.widget.TreeView
+ */
+ TV.editorData = {
+ active:false,
+ whoHasIt:null, // which TreeView has it
+ nodeType:null,
+ editorPanel:null,
+ inputContainer:null,
+ buttonsContainer:null,
+ node:null, // which Node is being edited
+ saveOnEnter:true
+ // Each node type is free to add its own properties to this as it sees fit.
+ };
+
+ /**
+ * Validator function for edited data, called from the TreeView instance scope,
+ * receives the arguments (newValue, oldValue, nodeInstance)
+ * and returns either the validated (or type-converted) value or undefined.
+ * An undefined return will prevent the editor from closing
+ * @property validator
+ * @default null
+ * @for YAHOO.widget.TreeView
+ */
+ TVproto.validator = null;
+
+ /**
+ * Entry point of the editing plug-in.
+ * TreeView will call this method if it exists when a node label is clicked
+ * @method _nodeEditing
+ * @param node {YAHOO.widget.Node} the node to be edited
+ * @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
+ * @for YAHOO.widget.TreeView
+ * @private
+ */
+
+
+ TVproto._nodeEditing = function (node) {
+ if (node.fillEditorContainer && node.editable) {
+ var ed, topLeft, buttons, button, editorData = TV.editorData;
+ editorData.active = true;
+ editorData.whoHasIt = this;
+ if (!editorData.nodeType) {
+ editorData.editorPanel = ed = document.body.appendChild(document.createElement('div'));
+ Dom.addClass(ed,'ygtv-label-editor');
- /**
- * Returns a ygAnim instance of the given type
- * @method getAnim
- * @param type {string} the type of animation
- * @param el {HTMLElement} the element to element (probably the children div)
- * @param callback {function} function to invoke when the animation is done.
- * @return {YAHOO.util.Animation} the animation instance
- * @static
- */
- getAnim: function(type, el, callback) {
- if (YAHOO.widget[type]) {
- return new YAHOO.widget[type](el, callback);
+ buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
+ Dom.addClass(buttons,'ygtv-button-container');
+ button = buttons.appendChild(document.createElement('button'));
+ Dom.addClass(button,'ygtvok');
+ button.innerHTML = ' ';
+ button = buttons.appendChild(document.createElement('button'));
+ Dom.addClass(button,'ygtvcancel');
+ button.innerHTML = ' ';
+ Event.on(buttons, 'click', function (ev) {
+ var target = Event.getTarget(ev);
+ var node = TV.editorData.node;
+ if (Dom.hasClass(target,'ygtvok')) {
+ Event.stopEvent(ev);
+ this._closeEditor(true);
+ }
+ if (Dom.hasClass(target,'ygtvcancel')) {
+ Event.stopEvent(ev);
+ this._closeEditor(false);
+ }
+ }, this, true);
+
+ editorData.inputContainer = ed.appendChild(document.createElement('div'));
+ Dom.addClass(editorData.inputContainer,'ygtv-input');
+
+ Event.on(ed,'keydown',function (ev) {
+ var editorData = TV.editorData,
+ KEY = YAHOO.util.KeyListener.KEY;
+ switch (ev.keyCode) {
+ case KEY.ENTER:
+ Event.stopEvent(ev);
+ if (editorData.saveOnEnter) {
+ this._closeEditor(true);
+ }
+ break;
+ case KEY.ESCAPE:
+ Event.stopEvent(ev);
+ this._closeEditor(false);
+ break;
+ }
+ },this,true);
+
+
+
} else {
- return null;
+ ed = editorData.editorPanel;
}
- },
+ editorData.node = node;
+ if (editorData.nodeType) {
+ Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
+ }
+ Dom.addClass(ed,' ygtv-edit-' + node._type);
+ topLeft = Dom.getXY(node.getContentEl());
+ Dom.setStyle(ed,'left',topLeft[0] + 'px');
+ Dom.setStyle(ed,'top',topLeft[1] + 'px');
+ Dom.setStyle(ed,'display','block');
+ ed.focus();
+ node.fillEditorContainer(editorData);
- /**
- * Returns true if the specified animation class is available
- * @method isValid
- * @param type {string} the type of animation
- * @return {boolean} true if valid, false if not
- * @static
- */
- isValid: function(type) {
- return (YAHOO.widget[type]);
+ return true; // If inline editor available, don't do anything else.
}
};
-} ();
-
-/**
- * A 1/2 second fade-in animation.
- * @class TVFadeIn
- * @constructor
- * @param el {HTMLElement} the element to animate
- * @param callback {function} function to invoke when the animation is finished
- */
-YAHOO.widget.TVFadeIn = function(el, callback) {
+
/**
- * The element to animate
- * @property el
- * @type HTMLElement
- */
- this.el = el;
+ * Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
+ * It calls the corresponding node editNode method.
+ * @method onEventEditNode
+ * @param oArgs {object} Object passed as arguments to TreeView event listeners
+ * @for YAHOO.widget.TreeView
+ */
+ TVproto.onEventEditNode = function (oArgs) {
+ if (oArgs instanceof YAHOO.widget.Node) {
+ oArgs.editNode();
+ } else if (oArgs.node instanceof YAHOO.widget.Node) {
+ oArgs.node.editNode();
+ }
+ };
+
/**
- * the callback to invoke when the animation is complete
- * @property callback
- * @type function
- */
- this.callback = callback;
-
-};
-
-YAHOO.widget.TVFadeIn.prototype = {
+ * Method to be called when the inline editing is finished and the editor is to be closed
+ * @method _closeEditor
+ * @param save {Boolean} true if the edited value is to be saved, false if discarded
+ * @private
+ * @for YAHOO.widget.TreeView
+ */
+
+ TVproto._closeEditor = function (save) {
+ var ed = TV.editorData,
+ node = ed.node,
+ close = true;
+ if (save) {
+ close = ed.node.saveEditorValue(ed) !== false;
+ }
+ if (close) {
+ Dom.setStyle(ed.editorPanel,'display','none');
+ ed.active = false;
+ node.focus();
+ }
+ };
+
/**
- * Performs the animation
- * @method animate
- */
- animate: function() {
- var tvanim = this;
+ * Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
+ * @method _destroyEditor
+ * @private
+ * @for YAHOO.widget.TreeView
+ */
+ TVproto._destroyEditor = function() {
+ var ed = TV.editorData;
+ if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
+ Event.removeListener(ed.editorPanel,'keydown');
+ Event.removeListener(ed.buttonContainer,'click');
+ ed.node.destroyEditorContents(ed);
+ document.body.removeChild(ed.editorPanel);
+ ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
+ ed.active = false;
+ }
+ };
+
+ var Nproto = YAHOO.widget.Node.prototype;
+
+ /**
+ * Signals if the label is editable. (Ignored on TextNodes with href set.)
+ * @property editable
+ * @type boolean
+ * @for YAHOO.widget.Node
+ */
+ Nproto.editable = false;
+
+ /**
+ * pops up the contents editor, if there is one and the node is declared editable
+ * @method editNode
+ * @for YAHOO.widget.Node
+ */
+
+ Nproto.editNode = function () {
+ this.tree._nodeEditing(this);
+ };
+
+
- var s = this.el.style;
- s.opacity = 0.1;
- s.filter = "alpha(opacity=10)";
- s.display = "";
- var dur = 0.4;
- var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
- a.onComplete.subscribe( function() { tvanim.onComplete(); } );
- a.animate();
- },
-
- /**
- * Clean up and invoke callback
- * @method onComplete
+ /** Placeholder for a function that should provide the inline node label editor.
+ * Leaving it set to null will indicate that this node type is not editable.
+ * It should be overridden by nodes that provide inline editing.
+ * The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
+ * @method fillEditorContainer
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @return void
+ * @for YAHOO.widget.Node
*/
- onComplete: function() {
- this.callback();
- },
+ Nproto.fillEditorContainer = null;
+
/**
- * toString
- * @method toString
- * @return {string} the string representation of the instance
+ * Node-specific destroy function to empty the contents of the inline editor panel
+ * This function is the worst case alternative that will purge all possible events and remove the editor contents
+ * Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
+ * @method destroyEditorContents
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @for YAHOO.widget.Node
*/
- toString: function() {
- return "TVFadeIn";
- }
-};
+ Nproto.destroyEditorContents = function (editorData) {
+ // In the worst case, if the input editor (such as the Calendar) has no destroy method
+ // we can only try to remove all possible events on it.
+ Event.purgeElement(editorData.inputContainer,true);
+ editorData.inputContainer.innerHTML = '';
+ };
-/**
- * A 1/2 second fade out animation.
- * @class TVFadeOut
- * @constructor
- * @param el {HTMLElement} the element to animate
- * @param callback {Function} function to invoke when the animation is finished
- */
-YAHOO.widget.TVFadeOut = function(el, callback) {
/**
- * The element to animate
- * @property el
- * @type HTMLElement
+ * Saves the value entered into the editor.
+ * Should be overridden by each node type
+ * @method saveEditorValue
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @return a return of exactly false will prevent the editor from closing
+ * @for YAHOO.widget.Node
*/
- this.el = el;
+ Nproto.saveEditorValue = function (editorData) {
+ };
+
+ var TNproto = YAHOO.widget.TextNode.prototype;
+
- /**
- * the callback to invoke when the animation is complete
- * @property callback
- * @type function
+
+ /**
+ * Places an <input> textbox in the input container and loads the label text into it
+ * @method fillEditorContainer
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @return void
+ * @for YAHOO.widget.TextNode
*/
- this.callback = callback;
+ TNproto.fillEditorContainer = function (editorData) {
+
+ var input;
+ // If last node edited is not of the same type as this one, delete it and fill it with our editor
+ if (editorData.nodeType != this._type) {
+ editorData.nodeType = this._type;
+ editorData.saveOnEnter = true;
+ editorData.node.destroyEditorContents(editorData);
-};
+ editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
+
+ } else {
+ // if the last node edited was of the same time, reuse the input element.
+ input = editorData.inputElement;
+ }
-YAHOO.widget.TVFadeOut.prototype = {
+ input.value = this.label;
+ input.focus();
+ input.select();
+ };
+
/**
- * Performs the animation
- * @method animate
+ * Saves the value entered in the editor into the TextNode label property and displays it
+ * Overrides Node.saveEditorValue
+ * @method saveEditorValue
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @for YAHOO.widget.TextNode
*/
- animate: function() {
- var tvanim = this;
- var dur = 0.4;
- var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
- a.onComplete.subscribe( function() { tvanim.onComplete(); } );
- a.animate();
- },
+ TNproto.saveEditorValue = function (editorData) {
+ var node = editorData.node,
+ value = editorData.inputElement.value,
+ validator = node.tree.validator;
+
+ if (Lang.isFunction(validator)) {
+ value = validator(value,node.label,node);
+ if (Lang.isUndefined(value)) { return false; }
+ }
+ node.label = value;
+ node.getLabelEl().innerHTML = value;
+ };
/**
- * Clean up and invoke callback
- * @method onComplete
+ * Destroys the contents of the inline editor panel
+ * Overrides Node.destroyEditorContent
+ * Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node
+ * @method destroyEditorContents
+ * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
+ * @for YAHOO.widget.TextNode
*/
- onComplete: function() {
- var s = this.el.style;
- s.display = "none";
- // s.opacity = 1;
- s.filter = "alpha(opacity=100)";
- this.callback();
- },
-
- /**
- * toString
- * @method toString
- * @return {string} the string representation of the instance
- */
- toString: function() {
- return "TVFadeOut";
- }
-};
-
-YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.3.0", build: "442"});
+ TNproto.destroyEditorContents = function (editorData) {
+ editorData.inputContainer.innerHTML = '';
+ };
+})();
+YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.7.0", build: "1799"});