1 |
efrain |
1 |
YUI.add('yui2-treeview', function(Y) {
|
|
|
2 |
var YAHOO = Y.YUI2;
|
|
|
3 |
/*
|
|
|
4 |
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
|
|
|
5 |
Code licensed under the BSD License:
|
|
|
6 |
http://developer.yahoo.com/yui/license.html
|
|
|
7 |
version: 2.9.0
|
|
|
8 |
*/
|
|
|
9 |
(function () {
|
|
|
10 |
var Dom = YAHOO.util.Dom,
|
|
|
11 |
Event = YAHOO.util.Event,
|
|
|
12 |
Lang = YAHOO.lang,
|
|
|
13 |
Widget = YAHOO.widget;
|
|
|
14 |
|
|
|
15 |
|
|
|
16 |
|
|
|
17 |
/**
|
|
|
18 |
* The treeview widget is a generic tree building tool.
|
|
|
19 |
* @module treeview
|
|
|
20 |
* @title TreeView Widget
|
|
|
21 |
* @requires yahoo, dom, event
|
|
|
22 |
* @optional animation, json, calendar
|
|
|
23 |
* @namespace YAHOO.widget
|
|
|
24 |
*/
|
|
|
25 |
|
|
|
26 |
/**
|
|
|
27 |
* Contains the tree view state data and the root node.
|
|
|
28 |
*
|
|
|
29 |
* @class TreeView
|
|
|
30 |
* @uses YAHOO.util.EventProvider
|
|
|
31 |
* @constructor
|
|
|
32 |
* @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into.
|
|
|
33 |
* Existing markup in this element, if valid, will be used to build the tree
|
|
|
34 |
* @param {Array|Object|String} oConfig (optional) If present, it will be used to build the tree via method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>
|
|
|
35 |
*
|
|
|
36 |
*/
|
|
|
37 |
YAHOO.widget.TreeView = function(id, oConfig) {
|
|
|
38 |
if (id) { this.init(id); }
|
|
|
39 |
if (oConfig) {
|
|
|
40 |
this.buildTreeFromObject(oConfig);
|
|
|
41 |
} else if (Lang.trim(this._el.innerHTML)) {
|
|
|
42 |
this.buildTreeFromMarkup(id);
|
|
|
43 |
}
|
|
|
44 |
};
|
|
|
45 |
|
|
|
46 |
var TV = Widget.TreeView;
|
|
|
47 |
|
|
|
48 |
TV.prototype = {
|
|
|
49 |
|
|
|
50 |
/**
|
|
|
51 |
* The id of tree container element
|
|
|
52 |
* @property id
|
|
|
53 |
* @type String
|
|
|
54 |
*/
|
|
|
55 |
id: null,
|
|
|
56 |
|
|
|
57 |
/**
|
|
|
58 |
* The host element for this tree
|
|
|
59 |
* @property _el
|
|
|
60 |
* @private
|
|
|
61 |
* @type HTMLelement
|
|
|
62 |
*/
|
|
|
63 |
_el: null,
|
|
|
64 |
|
|
|
65 |
/**
|
|
|
66 |
* Flat collection of all nodes in this tree. This is a sparse
|
|
|
67 |
* array, so the length property can't be relied upon for a
|
|
|
68 |
* node count for the tree.
|
|
|
69 |
* @property _nodes
|
|
|
70 |
* @type Node[]
|
|
|
71 |
* @private
|
|
|
72 |
*/
|
|
|
73 |
_nodes: null,
|
|
|
74 |
|
|
|
75 |
/**
|
|
|
76 |
* We lock the tree control while waiting for the dynamic loader to return
|
|
|
77 |
* @property locked
|
|
|
78 |
* @type boolean
|
|
|
79 |
*/
|
|
|
80 |
locked: false,
|
|
|
81 |
|
|
|
82 |
/**
|
|
|
83 |
* The animation to use for expanding children, if any
|
|
|
84 |
* @property _expandAnim
|
|
|
85 |
* @type string
|
|
|
86 |
* @private
|
|
|
87 |
*/
|
|
|
88 |
_expandAnim: null,
|
|
|
89 |
|
|
|
90 |
/**
|
|
|
91 |
* The animation to use for collapsing children, if any
|
|
|
92 |
* @property _collapseAnim
|
|
|
93 |
* @type string
|
|
|
94 |
* @private
|
|
|
95 |
*/
|
|
|
96 |
_collapseAnim: null,
|
|
|
97 |
|
|
|
98 |
/**
|
|
|
99 |
* The current number of animations that are executing
|
|
|
100 |
* @property _animCount
|
|
|
101 |
* @type int
|
|
|
102 |
* @private
|
|
|
103 |
*/
|
|
|
104 |
_animCount: 0,
|
|
|
105 |
|
|
|
106 |
/**
|
|
|
107 |
* The maximum number of animations to run at one time.
|
|
|
108 |
* @property maxAnim
|
|
|
109 |
* @type int
|
|
|
110 |
*/
|
|
|
111 |
maxAnim: 2,
|
|
|
112 |
|
|
|
113 |
/**
|
|
|
114 |
* Whether there is any subscriber to dblClickEvent
|
|
|
115 |
* @property _hasDblClickSubscriber
|
|
|
116 |
* @type boolean
|
|
|
117 |
* @private
|
|
|
118 |
*/
|
|
|
119 |
_hasDblClickSubscriber: false,
|
|
|
120 |
|
|
|
121 |
/**
|
|
|
122 |
* Stores the timer used to check for double clicks
|
|
|
123 |
* @property _dblClickTimer
|
|
|
124 |
* @type window.timer object
|
|
|
125 |
* @private
|
|
|
126 |
*/
|
|
|
127 |
_dblClickTimer: null,
|
|
|
128 |
|
|
|
129 |
/**
|
|
|
130 |
* A reference to the Node currently having the focus or null if none.
|
|
|
131 |
* @property currentFocus
|
|
|
132 |
* @type YAHOO.widget.Node
|
|
|
133 |
*/
|
|
|
134 |
currentFocus: null,
|
|
|
135 |
|
|
|
136 |
/**
|
|
|
137 |
* If true, only one Node can be highlighted at a time
|
|
|
138 |
* @property singleNodeHighlight
|
|
|
139 |
* @type boolean
|
|
|
140 |
* @default false
|
|
|
141 |
*/
|
|
|
142 |
|
|
|
143 |
singleNodeHighlight: false,
|
|
|
144 |
|
|
|
145 |
/**
|
|
|
146 |
* A reference to the Node that is currently highlighted.
|
|
|
147 |
* It is only meaningful if singleNodeHighlight is enabled
|
|
|
148 |
* @property _currentlyHighlighted
|
|
|
149 |
* @type YAHOO.widget.Node
|
|
|
150 |
* @default null
|
|
|
151 |
* @private
|
|
|
152 |
*/
|
|
|
153 |
|
|
|
154 |
_currentlyHighlighted: null,
|
|
|
155 |
|
|
|
156 |
/**
|
|
|
157 |
* Sets up the animation for expanding children
|
|
|
158 |
* @method setExpandAnim
|
|
|
159 |
* @param {string} type the type of animation (acceptable values defined
|
|
|
160 |
* in YAHOO.widget.TVAnim)
|
|
|
161 |
*/
|
|
|
162 |
setExpandAnim: function(type) {
|
|
|
163 |
this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null;
|
|
|
164 |
},
|
|
|
165 |
|
|
|
166 |
/**
|
|
|
167 |
* Sets up the animation for collapsing children
|
|
|
168 |
* @method setCollapseAnim
|
|
|
169 |
* @param {string} type of animation (acceptable values defined in
|
|
|
170 |
* YAHOO.widget.TVAnim)
|
|
|
171 |
*/
|
|
|
172 |
setCollapseAnim: function(type) {
|
|
|
173 |
this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null;
|
|
|
174 |
},
|
|
|
175 |
|
|
|
176 |
/**
|
|
|
177 |
* Perform the expand animation if configured, or just show the
|
|
|
178 |
* element if not configured or too many animations are in progress
|
|
|
179 |
* @method animateExpand
|
|
|
180 |
* @param el {HTMLElement} the element to animate
|
|
|
181 |
* @param node {YAHOO.util.Node} the node that was expanded
|
|
|
182 |
* @return {boolean} true if animation could be invoked, false otherwise
|
|
|
183 |
*/
|
|
|
184 |
animateExpand: function(el, node) {
|
|
|
185 |
this.logger.log("animating expand");
|
|
|
186 |
|
|
|
187 |
if (this._expandAnim && this._animCount < this.maxAnim) {
|
|
|
188 |
// this.locked = true;
|
|
|
189 |
var tree = this;
|
|
|
190 |
var a = Widget.TVAnim.getAnim(this._expandAnim, el,
|
|
|
191 |
function() { tree.expandComplete(node); });
|
|
|
192 |
if (a) {
|
|
|
193 |
++this._animCount;
|
|
|
194 |
this.fireEvent("animStart", {
|
|
|
195 |
"node": node,
|
|
|
196 |
"type": "expand"
|
|
|
197 |
});
|
|
|
198 |
a.animate();
|
|
|
199 |
}
|
|
|
200 |
|
|
|
201 |
return true;
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
return false;
|
|
|
205 |
},
|
|
|
206 |
|
|
|
207 |
/**
|
|
|
208 |
* Perform the collapse animation if configured, or just show the
|
|
|
209 |
* element if not configured or too many animations are in progress
|
|
|
210 |
* @method animateCollapse
|
|
|
211 |
* @param el {HTMLElement} the element to animate
|
|
|
212 |
* @param node {YAHOO.util.Node} the node that was expanded
|
|
|
213 |
* @return {boolean} true if animation could be invoked, false otherwise
|
|
|
214 |
*/
|
|
|
215 |
animateCollapse: function(el, node) {
|
|
|
216 |
this.logger.log("animating collapse");
|
|
|
217 |
|
|
|
218 |
if (this._collapseAnim && this._animCount < this.maxAnim) {
|
|
|
219 |
// this.locked = true;
|
|
|
220 |
var tree = this;
|
|
|
221 |
var a = Widget.TVAnim.getAnim(this._collapseAnim, el,
|
|
|
222 |
function() { tree.collapseComplete(node); });
|
|
|
223 |
if (a) {
|
|
|
224 |
++this._animCount;
|
|
|
225 |
this.fireEvent("animStart", {
|
|
|
226 |
"node": node,
|
|
|
227 |
"type": "collapse"
|
|
|
228 |
});
|
|
|
229 |
a.animate();
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
return true;
|
|
|
233 |
}
|
|
|
234 |
|
|
|
235 |
return false;
|
|
|
236 |
},
|
|
|
237 |
|
|
|
238 |
/**
|
|
|
239 |
* Function executed when the expand animation completes
|
|
|
240 |
* @method expandComplete
|
|
|
241 |
*/
|
|
|
242 |
expandComplete: function(node) {
|
|
|
243 |
this.logger.log("expand complete: " + this.id);
|
|
|
244 |
--this._animCount;
|
|
|
245 |
this.fireEvent("animComplete", {
|
|
|
246 |
"node": node,
|
|
|
247 |
"type": "expand"
|
|
|
248 |
});
|
|
|
249 |
// this.locked = false;
|
|
|
250 |
},
|
|
|
251 |
|
|
|
252 |
/**
|
|
|
253 |
* Function executed when the collapse animation completes
|
|
|
254 |
* @method collapseComplete
|
|
|
255 |
*/
|
|
|
256 |
collapseComplete: function(node) {
|
|
|
257 |
this.logger.log("collapse complete: " + this.id);
|
|
|
258 |
--this._animCount;
|
|
|
259 |
this.fireEvent("animComplete", {
|
|
|
260 |
"node": node,
|
|
|
261 |
"type": "collapse"
|
|
|
262 |
});
|
|
|
263 |
// this.locked = false;
|
|
|
264 |
},
|
|
|
265 |
|
|
|
266 |
/**
|
|
|
267 |
* Initializes the tree
|
|
|
268 |
* @method init
|
|
|
269 |
* @parm {string|HTMLElement} id the id of the element that will hold the tree
|
|
|
270 |
* @private
|
|
|
271 |
*/
|
|
|
272 |
init: function(id) {
|
|
|
273 |
this._el = Dom.get(id);
|
|
|
274 |
this.id = Dom.generateId(this._el,"yui-tv-auto-id-");
|
|
|
275 |
|
|
|
276 |
/**
|
|
|
277 |
* When animation is enabled, this event fires when the animation
|
|
|
278 |
* starts
|
|
|
279 |
* @event animStart
|
|
|
280 |
* @type CustomEvent
|
|
|
281 |
* @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
|
|
|
282 |
* @param {String} oArgs.type the type of animation ("expand" or "collapse")
|
|
|
283 |
*/
|
|
|
284 |
this.createEvent("animStart", this);
|
|
|
285 |
|
|
|
286 |
/**
|
|
|
287 |
* When animation is enabled, this event fires when the animation
|
|
|
288 |
* completes
|
|
|
289 |
* @event animComplete
|
|
|
290 |
* @type CustomEvent
|
|
|
291 |
* @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
|
|
|
292 |
* @param {String} oArgs.type the type of animation ("expand" or "collapse")
|
|
|
293 |
*/
|
|
|
294 |
this.createEvent("animComplete", this);
|
|
|
295 |
|
|
|
296 |
/**
|
|
|
297 |
* Fires when a node is going to be collapsed. Return false to stop
|
|
|
298 |
* the collapse.
|
|
|
299 |
* @event collapse
|
|
|
300 |
* @type CustomEvent
|
|
|
301 |
* @param {YAHOO.widget.Node} node the node that is collapsing
|
|
|
302 |
*/
|
|
|
303 |
this.createEvent("collapse", this);
|
|
|
304 |
|
|
|
305 |
/**
|
|
|
306 |
* Fires after a node is successfully collapsed. This event will not fire
|
|
|
307 |
* if the "collapse" event was cancelled.
|
|
|
308 |
* @event collapseComplete
|
|
|
309 |
* @type CustomEvent
|
|
|
310 |
* @param {YAHOO.widget.Node} node the node that was collapsed
|
|
|
311 |
*/
|
|
|
312 |
this.createEvent("collapseComplete", this);
|
|
|
313 |
|
|
|
314 |
/**
|
|
|
315 |
* Fires when a node is going to be expanded. Return false to stop
|
|
|
316 |
* the collapse.
|
|
|
317 |
* @event expand
|
|
|
318 |
* @type CustomEvent
|
|
|
319 |
* @param {YAHOO.widget.Node} node the node that is expanding
|
|
|
320 |
*/
|
|
|
321 |
this.createEvent("expand", this);
|
|
|
322 |
|
|
|
323 |
/**
|
|
|
324 |
* Fires after a node is successfully expanded. This event will not fire
|
|
|
325 |
* if the "expand" event was cancelled.
|
|
|
326 |
* @event expandComplete
|
|
|
327 |
* @type CustomEvent
|
|
|
328 |
* @param {YAHOO.widget.Node} node the node that was expanded
|
|
|
329 |
*/
|
|
|
330 |
this.createEvent("expandComplete", this);
|
|
|
331 |
|
|
|
332 |
/**
|
|
|
333 |
* Fires when the Enter key is pressed on a node that has the focus
|
|
|
334 |
* @event enterKeyPressed
|
|
|
335 |
* @type CustomEvent
|
|
|
336 |
* @param {YAHOO.widget.Node} node the node that has the focus
|
|
|
337 |
*/
|
|
|
338 |
this.createEvent("enterKeyPressed", this);
|
|
|
339 |
|
|
|
340 |
/**
|
|
|
341 |
* Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click.
|
|
|
342 |
* The listener may return false to cancel toggling and focusing on the node.
|
|
|
343 |
* @event clickEvent
|
|
|
344 |
* @type CustomEvent
|
|
|
345 |
* @param oArgs.event {HTMLEvent} The event object
|
|
|
346 |
* @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
|
|
|
347 |
*/
|
|
|
348 |
this.createEvent("clickEvent", this);
|
|
|
349 |
|
|
|
350 |
/**
|
|
|
351 |
* Fires when the focus receives the focus, when it changes from a Node
|
|
|
352 |
* to another Node or when it is completely lost (blurred)
|
|
|
353 |
* @event focusChanged
|
|
|
354 |
* @type CustomEvent
|
|
|
355 |
* @param oArgs.oldNode {YAHOO.widget.Node} Node that had the focus or null if none
|
|
|
356 |
* @param oArgs.newNode {YAHOO.widget.Node} Node that receives the focus or null if none
|
|
|
357 |
*/
|
|
|
358 |
|
|
|
359 |
this.createEvent('focusChanged',this);
|
|
|
360 |
|
|
|
361 |
/**
|
|
|
362 |
* Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click
|
|
|
363 |
* @event dblClickEvent
|
|
|
364 |
* @type CustomEvent
|
|
|
365 |
* @param oArgs.event {HTMLEvent} The event object
|
|
|
366 |
* @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
|
|
|
367 |
*/
|
|
|
368 |
var self = this;
|
|
|
369 |
this.createEvent("dblClickEvent", {
|
|
|
370 |
scope:this,
|
|
|
371 |
onSubscribeCallback: function() {
|
|
|
372 |
self._hasDblClickSubscriber = true;
|
|
|
373 |
}
|
|
|
374 |
});
|
|
|
375 |
|
|
|
376 |
/**
|
|
|
377 |
* Custom event that is fired when the text node label is clicked.
|
|
|
378 |
* The node clicked is provided as an argument
|
|
|
379 |
*
|
|
|
380 |
* @event labelClick
|
|
|
381 |
* @type CustomEvent
|
|
|
382 |
* @param {YAHOO.widget.Node} node the node clicked
|
|
|
383 |
* @deprecated use clickEvent or dblClickEvent
|
|
|
384 |
*/
|
|
|
385 |
this.createEvent("labelClick", this);
|
|
|
386 |
|
|
|
387 |
/**
|
|
|
388 |
* Custom event fired when the highlight of a node changes.
|
|
|
389 |
* The node that triggered the change is provided as an argument:
|
|
|
390 |
* The status of the highlight can be checked in
|
|
|
391 |
* <a href="YAHOO.widget.Node.html#property_highlightState">nodeRef.highlightState</a>.
|
|
|
392 |
* Depending on <a href="YAHOO.widget.Node.html#property_propagateHighlight">nodeRef.propagateHighlight</a>, other nodes might have changed
|
|
|
393 |
* @event highlightEvent
|
|
|
394 |
* @type CustomEvent
|
|
|
395 |
* @param node {YAHOO.widget.Node} the node that started the change in highlighting state
|
|
|
396 |
*/
|
|
|
397 |
this.createEvent("highlightEvent",this);
|
|
|
398 |
|
|
|
399 |
|
|
|
400 |
this._nodes = [];
|
|
|
401 |
|
|
|
402 |
// store a global reference
|
|
|
403 |
TV.trees[this.id] = this;
|
|
|
404 |
|
|
|
405 |
// Set up the root node
|
|
|
406 |
this.root = new Widget.RootNode(this);
|
|
|
407 |
|
|
|
408 |
var LW = Widget.LogWriter;
|
|
|
409 |
|
|
|
410 |
this.logger = (LW) ? new LW(this.toString()) : YAHOO;
|
|
|
411 |
|
|
|
412 |
this.logger.log("tree init: " + this.id);
|
|
|
413 |
|
|
|
414 |
if (this._initEditor) {
|
|
|
415 |
this._initEditor();
|
|
|
416 |
}
|
|
|
417 |
|
|
|
418 |
// YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
|
|
|
419 |
// YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
|
|
|
420 |
},
|
|
|
421 |
|
|
|
422 |
//handleAvailable: function() {
|
|
|
423 |
//var Event = YAHOO.util.Event;
|
|
|
424 |
//Event.on(this.id,
|
|
|
425 |
//},
|
|
|
426 |
/**
|
|
|
427 |
* Builds the TreeView from an object.
|
|
|
428 |
* This is the method called by the constructor to build the tree when it has a second argument.
|
|
|
429 |
* A tree can be described by an array of objects, each object corresponding to a node.
|
|
|
430 |
* Node descriptions may contain values for any property of a node plus the following extra properties: <ul>
|
|
|
431 |
* <li>type: can be one of the following:<ul>
|
|
|
432 |
* <li> A shortname for a node type (<code>'text','menu','html'</code>) </li>
|
|
|
433 |
* <li>The name of a Node class under YAHOO.widget (<code>'TextNode', 'MenuNode', 'DateNode'</code>, etc) </li>
|
|
|
434 |
* <li>a reference to an actual class: <code>YAHOO.widget.DateNode</code></li>
|
|
|
435 |
* </ul></li>
|
|
|
436 |
* <li>children: an array containing further node definitions</li></ul>
|
|
|
437 |
* A string instead of an object will produce a node of type 'text' with the given string as its label.
|
|
|
438 |
* @method buildTreeFromObject
|
|
|
439 |
* @param oConfig {Array|Object|String} array containing a full description of the tree.
|
|
|
440 |
* An object or a string will be turned into an array with the given object or string as its only element.
|
|
|
441 |
*
|
|
|
442 |
*/
|
|
|
443 |
buildTreeFromObject: function (oConfig) {
|
|
|
444 |
var logger = this.logger;
|
|
|
445 |
logger.log('Building tree from object');
|
|
|
446 |
var build = function (parent, oConfig) {
|
|
|
447 |
var i, item, node, children, type, NodeType, ThisType;
|
|
|
448 |
for (i = 0; i < oConfig.length; i++) {
|
|
|
449 |
item = oConfig[i];
|
|
|
450 |
if (Lang.isString(item)) {
|
|
|
451 |
node = new Widget.TextNode(item, parent);
|
|
|
452 |
} else if (Lang.isObject(item)) {
|
|
|
453 |
children = item.children;
|
|
|
454 |
delete item.children;
|
|
|
455 |
type = item.type || 'text';
|
|
|
456 |
delete item.type;
|
|
|
457 |
switch (Lang.isString(type) && type.toLowerCase()) {
|
|
|
458 |
case 'text':
|
|
|
459 |
node = new Widget.TextNode(item, parent);
|
|
|
460 |
break;
|
|
|
461 |
case 'menu':
|
|
|
462 |
node = new Widget.MenuNode(item, parent);
|
|
|
463 |
break;
|
|
|
464 |
case 'html':
|
|
|
465 |
node = new Widget.HTMLNode(item, parent);
|
|
|
466 |
break;
|
|
|
467 |
default:
|
|
|
468 |
if (Lang.isString(type)) {
|
|
|
469 |
NodeType = Widget[type];
|
|
|
470 |
} else {
|
|
|
471 |
NodeType = type;
|
|
|
472 |
}
|
|
|
473 |
if (Lang.isObject(NodeType)) {
|
|
|
474 |
for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {}
|
|
|
475 |
if (ThisType) {
|
|
|
476 |
node = new NodeType(item, parent);
|
|
|
477 |
} else {
|
|
|
478 |
logger.log('Invalid type in node definition: ' + type,'error');
|
|
|
479 |
}
|
|
|
480 |
} else {
|
|
|
481 |
logger.log('Invalid type in node definition: ' + type,'error');
|
|
|
482 |
}
|
|
|
483 |
}
|
|
|
484 |
if (children) {
|
|
|
485 |
build(node,children);
|
|
|
486 |
}
|
|
|
487 |
} else {
|
|
|
488 |
logger.log('Invalid node definition','error');
|
|
|
489 |
}
|
|
|
490 |
}
|
|
|
491 |
};
|
|
|
492 |
if (!Lang.isArray(oConfig)) {
|
|
|
493 |
oConfig = [oConfig];
|
|
|
494 |
}
|
|
|
495 |
|
|
|
496 |
|
|
|
497 |
build(this.root,oConfig);
|
|
|
498 |
},
|
|
|
499 |
/**
|
|
|
500 |
* Builds the TreeView from existing markup. Markup should consist of <UL> or <OL> elements containing <LI> elements.
|
|
|
501 |
* Each <LI> can have one element used as label and a second optional element which is to be a <UL> or <OL>
|
|
|
502 |
* containing nested nodes.
|
|
|
503 |
* Depending on what the first element of the <LI> element is, the following Nodes will be created: <ul>
|
|
|
504 |
* <li>plain text: a regular TextNode</li>
|
|
|
505 |
* <li>anchor <A>: a TextNode with its <code>href</code> and <code>target</code> taken from the anchor</li>
|
|
|
506 |
* <li>anything else: an HTMLNode</li></ul>
|
|
|
507 |
* Only the first outermost (un-)ordered list in the markup and its children will be parsed.
|
|
|
508 |
* Nodes will be collapsed unless an <LI> tag has a className called 'expanded'.
|
|
|
509 |
* All other className attributes will be copied over to the Node className property.
|
|
|
510 |
* If the <LI> element contains an attribute called <code>yuiConfig</code>, its contents should be a JSON-encoded object
|
|
|
511 |
* as the one used in method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>.
|
|
|
512 |
* @method buildTreeFromMarkup
|
|
|
513 |
* @param id {string|HTMLElement} The id of the element that contains the markup or a reference to it.
|
|
|
514 |
*/
|
|
|
515 |
buildTreeFromMarkup: function (id) {
|
|
|
516 |
this.logger.log('Building tree from existing markup');
|
|
|
517 |
var build = function (markup) {
|
|
|
518 |
var el, child, branch = [], config = {}, label, yuiConfig;
|
|
|
519 |
// Dom's getFirstChild and getNextSibling skip over text elements
|
|
|
520 |
for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) {
|
|
|
521 |
switch (el.tagName.toUpperCase()) {
|
|
|
522 |
case 'LI':
|
|
|
523 |
label = '';
|
|
|
524 |
config = {
|
|
|
525 |
expanded: Dom.hasClass(el,'expanded'),
|
|
|
526 |
title: el.title || el.alt || null,
|
|
|
527 |
className: Lang.trim(el.className.replace(/\bexpanded\b/,'')) || null
|
|
|
528 |
};
|
|
|
529 |
// I cannot skip over text elements here because I want them for labels
|
|
|
530 |
child = el.firstChild;
|
|
|
531 |
if (child.nodeType == 3) {
|
|
|
532 |
// nodes with only whitespace, tabs and new lines don't count, they are probably just formatting.
|
|
|
533 |
label = Lang.trim(child.nodeValue.replace(/[\n\t\r]*/g,''));
|
|
|
534 |
if (label) {
|
|
|
535 |
config.type = 'text';
|
|
|
536 |
config.label = label;
|
|
|
537 |
} else {
|
|
|
538 |
child = Dom.getNextSibling(child);
|
|
|
539 |
}
|
|
|
540 |
}
|
|
|
541 |
if (!label) {
|
|
|
542 |
if (child.tagName.toUpperCase() == 'A') {
|
|
|
543 |
config.type = 'text';
|
|
|
544 |
config.label = child.innerHTML;
|
|
|
545 |
config.href = child.href;
|
|
|
546 |
config.target = child.target;
|
|
|
547 |
config.title = child.title || child.alt || config.title;
|
|
|
548 |
} else {
|
|
|
549 |
config.type = 'html';
|
|
|
550 |
var d = document.createElement('div');
|
|
|
551 |
d.appendChild(child.cloneNode(true));
|
|
|
552 |
config.html = d.innerHTML;
|
|
|
553 |
config.hasIcon = true;
|
|
|
554 |
}
|
|
|
555 |
}
|
|
|
556 |
// see if after the label it has a further list which will become children of this node.
|
|
|
557 |
child = Dom.getNextSibling(child);
|
|
|
558 |
switch (child && child.tagName.toUpperCase()) {
|
|
|
559 |
case 'UL':
|
|
|
560 |
case 'OL':
|
|
|
561 |
config.children = build(child);
|
|
|
562 |
break;
|
|
|
563 |
}
|
|
|
564 |
// if there are further elements or text, it will be ignored.
|
|
|
565 |
|
|
|
566 |
if (YAHOO.lang.JSON) {
|
|
|
567 |
yuiConfig = el.getAttribute('yuiConfig');
|
|
|
568 |
if (yuiConfig) {
|
|
|
569 |
yuiConfig = YAHOO.lang.JSON.parse(yuiConfig);
|
|
|
570 |
config = YAHOO.lang.merge(config,yuiConfig);
|
|
|
571 |
}
|
|
|
572 |
}
|
|
|
573 |
|
|
|
574 |
branch.push(config);
|
|
|
575 |
break;
|
|
|
576 |
case 'UL':
|
|
|
577 |
case 'OL':
|
|
|
578 |
this.logger.log('ULs or OLs can only contain LI elements, not other UL or OL. This will not work in some browsers','error');
|
|
|
579 |
config = {
|
|
|
580 |
type: 'text',
|
|
|
581 |
label: '',
|
|
|
582 |
children: build(child)
|
|
|
583 |
};
|
|
|
584 |
branch.push(config);
|
|
|
585 |
break;
|
|
|
586 |
}
|
|
|
587 |
}
|
|
|
588 |
return branch;
|
|
|
589 |
};
|
|
|
590 |
|
|
|
591 |
var markup = Dom.getChildrenBy(Dom.get(id),function (el) {
|
|
|
592 |
var tag = el.tagName.toUpperCase();
|
|
|
593 |
return tag == 'UL' || tag == 'OL';
|
|
|
594 |
});
|
|
|
595 |
if (markup.length) {
|
|
|
596 |
this.buildTreeFromObject(build(markup[0]));
|
|
|
597 |
} else {
|
|
|
598 |
this.logger.log('Markup contains no UL or OL elements','warn');
|
|
|
599 |
}
|
|
|
600 |
},
|
|
|
601 |
/**
|
|
|
602 |
* Returns the TD element where the event has occurred
|
|
|
603 |
* @method _getEventTargetTdEl
|
|
|
604 |
* @private
|
|
|
605 |
*/
|
|
|
606 |
_getEventTargetTdEl: function (ev) {
|
|
|
607 |
var target = Event.getTarget(ev);
|
|
|
608 |
// go up looking for a TD with a className with a ygtv prefix
|
|
|
609 |
while (target && !(target.tagName.toUpperCase() == 'TD' && Dom.hasClass(target.parentNode,'ygtvrow'))) {
|
|
|
610 |
target = Dom.getAncestorByTagName(target,'td');
|
|
|
611 |
}
|
|
|
612 |
if (Lang.isNull(target)) { return null; }
|
|
|
613 |
// If it is a spacer cell, do nothing
|
|
|
614 |
if (/\bygtv(blank)?depthcell/.test(target.className)) { return null;}
|
|
|
615 |
// If it has an id, search for the node number and see if it belongs to a node in this tree.
|
|
|
616 |
if (target.id) {
|
|
|
617 |
var m = target.id.match(/\bygtv([^\d]*)(.*)/);
|
|
|
618 |
if (m && m[2] && this._nodes[m[2]]) {
|
|
|
619 |
return target;
|
|
|
620 |
}
|
|
|
621 |
}
|
|
|
622 |
return null;
|
|
|
623 |
},
|
|
|
624 |
/**
|
|
|
625 |
* Event listener for click events
|
|
|
626 |
* @method _onClickEvent
|
|
|
627 |
* @private
|
|
|
628 |
*/
|
|
|
629 |
_onClickEvent: function (ev) {
|
|
|
630 |
var self = this,
|
|
|
631 |
td = this._getEventTargetTdEl(ev),
|
|
|
632 |
node,
|
|
|
633 |
target,
|
|
|
634 |
toggle = function (force) {
|
|
|
635 |
node.focus();
|
|
|
636 |
if (force || !node.href) {
|
|
|
637 |
node.toggle();
|
|
|
638 |
try {
|
|
|
639 |
Event.preventDefault(ev);
|
|
|
640 |
} catch (e) {
|
|
|
641 |
// @TODO
|
|
|
642 |
// For some reason IE8 is providing an event object with
|
|
|
643 |
// most of the fields missing, but only when clicking on
|
|
|
644 |
// the node's label, and only when working with inline
|
|
|
645 |
// editing. This generates a "Member not found" error
|
|
|
646 |
// in that browser. Determine if this is a browser
|
|
|
647 |
// bug, or a problem with this code. Already checked to
|
|
|
648 |
// see if the problem has to do with access the event
|
|
|
649 |
// in the outer scope, and that isn't the problem.
|
|
|
650 |
// Maybe the markup for inline editing is broken.
|
|
|
651 |
}
|
|
|
652 |
}
|
|
|
653 |
};
|
|
|
654 |
|
|
|
655 |
if (!td) {
|
|
|
656 |
return;
|
|
|
657 |
}
|
|
|
658 |
|
|
|
659 |
node = this.getNodeByElement(td);
|
|
|
660 |
if (!node) {
|
|
|
661 |
return;
|
|
|
662 |
}
|
|
|
663 |
|
|
|
664 |
// exception to handle deprecated event labelClick
|
|
|
665 |
// @TODO take another look at this deprecation. It is common for people to
|
|
|
666 |
// only be interested in the label click, so why make them have to test
|
|
|
667 |
// the node type to figure out whether the click was on the label?
|
|
|
668 |
target = Event.getTarget(ev);
|
|
|
669 |
if (Dom.hasClass(target, node.labelStyle) || Dom.getAncestorByClassName(target,node.labelStyle)) {
|
|
|
670 |
this.logger.log("onLabelClick " + node.label);
|
|
|
671 |
this.fireEvent('labelClick',node);
|
|
|
672 |
}
|
|
|
673 |
// http://yuilibrary.com/projects/yui2/ticket/2528946
|
|
|
674 |
// Ensures that any open editor is closed.
|
|
|
675 |
// Since the editor is in a separate source which might not be included,
|
|
|
676 |
// we first need to ensure we have the _closeEditor method available
|
|
|
677 |
if (this._closeEditor) { this._closeEditor(false); }
|
|
|
678 |
|
|
|
679 |
// If it is a toggle cell, toggle
|
|
|
680 |
if (/\bygtv[tl][mp]h?h?/.test(td.className)) {
|
|
|
681 |
toggle(true);
|
|
|
682 |
} else {
|
|
|
683 |
if (this._dblClickTimer) {
|
|
|
684 |
window.clearTimeout(this._dblClickTimer);
|
|
|
685 |
this._dblClickTimer = null;
|
|
|
686 |
} else {
|
|
|
687 |
if (this._hasDblClickSubscriber) {
|
|
|
688 |
this._dblClickTimer = window.setTimeout(function () {
|
|
|
689 |
self._dblClickTimer = null;
|
|
|
690 |
if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
|
|
|
691 |
toggle();
|
|
|
692 |
}
|
|
|
693 |
}, 200);
|
|
|
694 |
} else {
|
|
|
695 |
if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
|
|
|
696 |
toggle();
|
|
|
697 |
}
|
|
|
698 |
}
|
|
|
699 |
}
|
|
|
700 |
}
|
|
|
701 |
},
|
|
|
702 |
|
|
|
703 |
/**
|
|
|
704 |
* Event listener for double-click events
|
|
|
705 |
* @method _onDblClickEvent
|
|
|
706 |
* @private
|
|
|
707 |
*/
|
|
|
708 |
_onDblClickEvent: function (ev) {
|
|
|
709 |
if (!this._hasDblClickSubscriber) { return; }
|
|
|
710 |
var td = this._getEventTargetTdEl(ev);
|
|
|
711 |
if (!td) {return;}
|
|
|
712 |
|
|
|
713 |
if (!(/\bygtv[tl][mp]h?h?/.test(td.className))) {
|
|
|
714 |
this.fireEvent('dblClickEvent', {event:ev, node:this.getNodeByElement(td)});
|
|
|
715 |
if (this._dblClickTimer) {
|
|
|
716 |
window.clearTimeout(this._dblClickTimer);
|
|
|
717 |
this._dblClickTimer = null;
|
|
|
718 |
}
|
|
|
719 |
}
|
|
|
720 |
},
|
|
|
721 |
/**
|
|
|
722 |
* Event listener for mouse over events
|
|
|
723 |
* @method _onMouseOverEvent
|
|
|
724 |
* @private
|
|
|
725 |
*/
|
|
|
726 |
_onMouseOverEvent:function (ev) {
|
|
|
727 |
var target;
|
|
|
728 |
if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
|
|
|
729 |
target.className = target.className.replace(/\bygtv([lt])([mp])\b/gi,'ygtv$1$2h');
|
|
|
730 |
}
|
|
|
731 |
},
|
|
|
732 |
/**
|
|
|
733 |
* Event listener for mouse out events
|
|
|
734 |
* @method _onMouseOutEvent
|
|
|
735 |
* @private
|
|
|
736 |
*/
|
|
|
737 |
_onMouseOutEvent: function (ev) {
|
|
|
738 |
var target;
|
|
|
739 |
if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
|
|
|
740 |
target.className = target.className.replace(/\bygtv([lt])([mp])h\b/gi,'ygtv$1$2');
|
|
|
741 |
}
|
|
|
742 |
},
|
|
|
743 |
/**
|
|
|
744 |
* Event listener for key down events
|
|
|
745 |
* @method _onKeyDownEvent
|
|
|
746 |
* @private
|
|
|
747 |
*/
|
|
|
748 |
_onKeyDownEvent: function (ev) {
|
|
|
749 |
var target = Event.getTarget(ev),
|
|
|
750 |
node = this.getNodeByElement(target),
|
|
|
751 |
newNode = node,
|
|
|
752 |
KEY = YAHOO.util.KeyListener.KEY;
|
|
|
753 |
|
|
|
754 |
switch(ev.keyCode) {
|
|
|
755 |
case KEY.UP:
|
|
|
756 |
this.logger.log('UP');
|
|
|
757 |
do {
|
|
|
758 |
if (newNode.previousSibling) {
|
|
|
759 |
newNode = newNode.previousSibling;
|
|
|
760 |
} else {
|
|
|
761 |
newNode = newNode.parent;
|
|
|
762 |
}
|
|
|
763 |
} while (newNode && !newNode._canHaveFocus());
|
|
|
764 |
if (newNode) { newNode.focus(); }
|
|
|
765 |
Event.preventDefault(ev);
|
|
|
766 |
break;
|
|
|
767 |
case KEY.DOWN:
|
|
|
768 |
this.logger.log('DOWN');
|
|
|
769 |
do {
|
|
|
770 |
if (newNode.nextSibling) {
|
|
|
771 |
newNode = newNode.nextSibling;
|
|
|
772 |
} else {
|
|
|
773 |
newNode.expand();
|
|
|
774 |
newNode = (newNode.children.length || null) && newNode.children[0];
|
|
|
775 |
}
|
|
|
776 |
} while (newNode && !newNode._canHaveFocus);
|
|
|
777 |
if (newNode) { newNode.focus();}
|
|
|
778 |
Event.preventDefault(ev);
|
|
|
779 |
break;
|
|
|
780 |
case KEY.LEFT:
|
|
|
781 |
this.logger.log('LEFT');
|
|
|
782 |
do {
|
|
|
783 |
if (newNode.parent) {
|
|
|
784 |
newNode = newNode.parent;
|
|
|
785 |
} else {
|
|
|
786 |
newNode = newNode.previousSibling;
|
|
|
787 |
}
|
|
|
788 |
} while (newNode && !newNode._canHaveFocus());
|
|
|
789 |
if (newNode) { newNode.focus();}
|
|
|
790 |
Event.preventDefault(ev);
|
|
|
791 |
break;
|
|
|
792 |
case KEY.RIGHT:
|
|
|
793 |
this.logger.log('RIGHT');
|
|
|
794 |
var self = this,
|
|
|
795 |
moveFocusRight,
|
|
|
796 |
focusOnExpand = function (newNode) {
|
|
|
797 |
self.unsubscribe('expandComplete',focusOnExpand);
|
|
|
798 |
moveFocusRight(newNode);
|
|
|
799 |
};
|
|
|
800 |
moveFocusRight = function (newNode) {
|
|
|
801 |
do {
|
|
|
802 |
if (newNode.isDynamic() && !newNode.childrenRendered) {
|
|
|
803 |
self.subscribe('expandComplete',focusOnExpand);
|
|
|
804 |
newNode.expand();
|
|
|
805 |
newNode = null;
|
|
|
806 |
break;
|
|
|
807 |
} else {
|
|
|
808 |
newNode.expand();
|
|
|
809 |
if (newNode.children.length) {
|
|
|
810 |
newNode = newNode.children[0];
|
|
|
811 |
} else {
|
|
|
812 |
newNode = newNode.nextSibling;
|
|
|
813 |
}
|
|
|
814 |
}
|
|
|
815 |
} while (newNode && !newNode._canHaveFocus());
|
|
|
816 |
if (newNode) { newNode.focus();}
|
|
|
817 |
};
|
|
|
818 |
|
|
|
819 |
moveFocusRight(newNode);
|
|
|
820 |
Event.preventDefault(ev);
|
|
|
821 |
break;
|
|
|
822 |
case KEY.ENTER:
|
|
|
823 |
this.logger.log('ENTER: ' + newNode.href);
|
|
|
824 |
if (node.href) {
|
|
|
825 |
if (node.target) {
|
|
|
826 |
window.open(node.href,node.target);
|
|
|
827 |
} else {
|
|
|
828 |
window.location(node.href);
|
|
|
829 |
}
|
|
|
830 |
} else {
|
|
|
831 |
node.toggle();
|
|
|
832 |
}
|
|
|
833 |
this.fireEvent('enterKeyPressed',node);
|
|
|
834 |
Event.preventDefault(ev);
|
|
|
835 |
break;
|
|
|
836 |
case KEY.HOME:
|
|
|
837 |
this.logger.log('HOME');
|
|
|
838 |
newNode = this.getRoot();
|
|
|
839 |
if (newNode.children.length) {newNode = newNode.children[0];}
|
|
|
840 |
if (newNode._canHaveFocus()) { newNode.focus(); }
|
|
|
841 |
Event.preventDefault(ev);
|
|
|
842 |
break;
|
|
|
843 |
case KEY.END:
|
|
|
844 |
this.logger.log('END');
|
|
|
845 |
newNode = newNode.parent.children;
|
|
|
846 |
newNode = newNode[newNode.length -1];
|
|
|
847 |
if (newNode._canHaveFocus()) { newNode.focus(); }
|
|
|
848 |
Event.preventDefault(ev);
|
|
|
849 |
break;
|
|
|
850 |
// case KEY.PAGE_UP:
|
|
|
851 |
// this.logger.log('PAGE_UP');
|
|
|
852 |
// break;
|
|
|
853 |
// case KEY.PAGE_DOWN:
|
|
|
854 |
// this.logger.log('PAGE_DOWN');
|
|
|
855 |
// break;
|
|
|
856 |
case 107: // plus key
|
|
|
857 |
case 187: // plus key
|
|
|
858 |
if (ev.shiftKey) {
|
|
|
859 |
this.logger.log('Shift-PLUS');
|
|
|
860 |
node.parent.expandAll();
|
|
|
861 |
} else {
|
|
|
862 |
this.logger.log('PLUS');
|
|
|
863 |
node.expand();
|
|
|
864 |
}
|
|
|
865 |
break;
|
|
|
866 |
case 109: // minus key
|
|
|
867 |
case 189: // minus key
|
|
|
868 |
if (ev.shiftKey) {
|
|
|
869 |
this.logger.log('Shift-MINUS');
|
|
|
870 |
node.parent.collapseAll();
|
|
|
871 |
} else {
|
|
|
872 |
this.logger.log('MINUS');
|
|
|
873 |
node.collapse();
|
|
|
874 |
}
|
|
|
875 |
break;
|
|
|
876 |
default:
|
|
|
877 |
break;
|
|
|
878 |
}
|
|
|
879 |
},
|
|
|
880 |
/**
|
|
|
881 |
* Renders the tree boilerplate and visible nodes
|
|
|
882 |
* @method render
|
|
|
883 |
*/
|
|
|
884 |
render: function() {
|
|
|
885 |
var html = this.root.getHtml(),
|
|
|
886 |
el = this.getEl();
|
|
|
887 |
el.innerHTML = html;
|
|
|
888 |
if (!this._hasEvents) {
|
|
|
889 |
Event.on(el, 'click', this._onClickEvent, this, true);
|
|
|
890 |
Event.on(el, 'dblclick', this._onDblClickEvent, this, true);
|
|
|
891 |
Event.on(el, 'mouseover', this._onMouseOverEvent, this, true);
|
|
|
892 |
Event.on(el, 'mouseout', this._onMouseOutEvent, this, true);
|
|
|
893 |
Event.on(el, 'keydown', this._onKeyDownEvent, this, true);
|
|
|
894 |
}
|
|
|
895 |
this._hasEvents = true;
|
|
|
896 |
},
|
|
|
897 |
|
|
|
898 |
/**
|
|
|
899 |
* Returns the tree's host element
|
|
|
900 |
* @method getEl
|
|
|
901 |
* @return {HTMLElement} the host element
|
|
|
902 |
*/
|
|
|
903 |
getEl: function() {
|
|
|
904 |
if (! this._el) {
|
|
|
905 |
this._el = Dom.get(this.id);
|
|
|
906 |
}
|
|
|
907 |
return this._el;
|
|
|
908 |
},
|
|
|
909 |
|
|
|
910 |
/**
|
|
|
911 |
* Nodes register themselves with the tree instance when they are created.
|
|
|
912 |
* @method regNode
|
|
|
913 |
* @param node {Node} the node to register
|
|
|
914 |
* @private
|
|
|
915 |
*/
|
|
|
916 |
regNode: function(node) {
|
|
|
917 |
this._nodes[node.index] = node;
|
|
|
918 |
},
|
|
|
919 |
|
|
|
920 |
/**
|
|
|
921 |
* Returns the root node of this tree
|
|
|
922 |
* @method getRoot
|
|
|
923 |
* @return {Node} the root node
|
|
|
924 |
*/
|
|
|
925 |
getRoot: function() {
|
|
|
926 |
return this.root;
|
|
|
927 |
},
|
|
|
928 |
|
|
|
929 |
/**
|
|
|
930 |
* Configures this tree to dynamically load all child data
|
|
|
931 |
* @method setDynamicLoad
|
|
|
932 |
* @param {function} fnDataLoader the function that will be called to get the data
|
|
|
933 |
* @param iconMode {int} configures the icon that is displayed when a dynamic
|
|
|
934 |
* load node is expanded the first time without children. By default, the
|
|
|
935 |
* "collapse" icon will be used. If set to 1, the leaf node icon will be
|
|
|
936 |
* displayed.
|
|
|
937 |
*/
|
|
|
938 |
setDynamicLoad: function(fnDataLoader, iconMode) {
|
|
|
939 |
this.root.setDynamicLoad(fnDataLoader, iconMode);
|
|
|
940 |
},
|
|
|
941 |
|
|
|
942 |
/**
|
|
|
943 |
* Expands all child nodes. Note: this conflicts with the "multiExpand"
|
|
|
944 |
* node property. If expand all is called in a tree with nodes that
|
|
|
945 |
* do not allow multiple siblings to be displayed, only the last sibling
|
|
|
946 |
* will be expanded.
|
|
|
947 |
* @method expandAll
|
|
|
948 |
*/
|
|
|
949 |
expandAll: function() {
|
|
|
950 |
if (!this.locked) {
|
|
|
951 |
this.root.expandAll();
|
|
|
952 |
}
|
|
|
953 |
},
|
|
|
954 |
|
|
|
955 |
/**
|
|
|
956 |
* Collapses all expanded child nodes in the entire tree.
|
|
|
957 |
* @method collapseAll
|
|
|
958 |
*/
|
|
|
959 |
collapseAll: function() {
|
|
|
960 |
if (!this.locked) {
|
|
|
961 |
this.root.collapseAll();
|
|
|
962 |
}
|
|
|
963 |
},
|
|
|
964 |
|
|
|
965 |
/**
|
|
|
966 |
* Returns a node in the tree that has the specified index (this index
|
|
|
967 |
* is created internally, so this function probably will only be used
|
|
|
968 |
* in html generated for a given node.)
|
|
|
969 |
* @method getNodeByIndex
|
|
|
970 |
* @param {int} nodeIndex the index of the node wanted
|
|
|
971 |
* @return {Node} the node with index=nodeIndex, null if no match
|
|
|
972 |
*/
|
|
|
973 |
getNodeByIndex: function(nodeIndex) {
|
|
|
974 |
var n = this._nodes[nodeIndex];
|
|
|
975 |
return (n) ? n : null;
|
|
|
976 |
},
|
|
|
977 |
|
|
|
978 |
/**
|
|
|
979 |
* Returns a node that has a matching property and value in the data
|
|
|
980 |
* object that was passed into its constructor.
|
|
|
981 |
* @method getNodeByProperty
|
|
|
982 |
* @param {object} property the property to search (usually a string)
|
|
|
983 |
* @param {object} value the value we want to find (usuall an int or string)
|
|
|
984 |
* @return {Node} the matching node, null if no match
|
|
|
985 |
*/
|
|
|
986 |
getNodeByProperty: function(property, value) {
|
|
|
987 |
for (var i in this._nodes) {
|
|
|
988 |
if (this._nodes.hasOwnProperty(i)) {
|
|
|
989 |
var n = this._nodes[i];
|
|
|
990 |
if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
|
|
|
991 |
return n;
|
|
|
992 |
}
|
|
|
993 |
}
|
|
|
994 |
}
|
|
|
995 |
|
|
|
996 |
return null;
|
|
|
997 |
},
|
|
|
998 |
|
|
|
999 |
/**
|
|
|
1000 |
* Returns a collection of nodes that have a matching property
|
|
|
1001 |
* and value in the data object that was passed into its constructor.
|
|
|
1002 |
* @method getNodesByProperty
|
|
|
1003 |
* @param {object} property the property to search (usually a string)
|
|
|
1004 |
* @param {object} value the value we want to find (usuall an int or string)
|
|
|
1005 |
* @return {Array} the matching collection of nodes, null if no match
|
|
|
1006 |
*/
|
|
|
1007 |
getNodesByProperty: function(property, value) {
|
|
|
1008 |
var values = [];
|
|
|
1009 |
for (var i in this._nodes) {
|
|
|
1010 |
if (this._nodes.hasOwnProperty(i)) {
|
|
|
1011 |
var n = this._nodes[i];
|
|
|
1012 |
if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
|
|
|
1013 |
values.push(n);
|
|
|
1014 |
}
|
|
|
1015 |
}
|
|
|
1016 |
}
|
|
|
1017 |
|
|
|
1018 |
return (values.length) ? values : null;
|
|
|
1019 |
},
|
|
|
1020 |
|
|
|
1021 |
|
|
|
1022 |
/**
|
|
|
1023 |
* Returns a collection of nodes that have passed the test function
|
|
|
1024 |
* passed as its only argument.
|
|
|
1025 |
* The function will receive a reference to each node to be tested.
|
|
|
1026 |
* @method getNodesBy
|
|
|
1027 |
* @param {function} a boolean function that receives a Node instance and returns true to add the node to the results list
|
|
|
1028 |
* @return {Array} the matching collection of nodes, null if no match
|
|
|
1029 |
*/
|
|
|
1030 |
getNodesBy: function(fn) {
|
|
|
1031 |
var values = [];
|
|
|
1032 |
for (var i in this._nodes) {
|
|
|
1033 |
if (this._nodes.hasOwnProperty(i)) {
|
|
|
1034 |
var n = this._nodes[i];
|
|
|
1035 |
if (fn(n)) {
|
|
|
1036 |
values.push(n);
|
|
|
1037 |
}
|
|
|
1038 |
}
|
|
|
1039 |
}
|
|
|
1040 |
return (values.length) ? values : null;
|
|
|
1041 |
},
|
|
|
1042 |
/**
|
|
|
1043 |
* Returns the treeview node reference for an ancestor element
|
|
|
1044 |
* of the node, or null if it is not contained within any node
|
|
|
1045 |
* in this tree.
|
|
|
1046 |
* @method getNodeByElement
|
|
|
1047 |
* @param el {HTMLElement} the element to test
|
|
|
1048 |
* @return {YAHOO.widget.Node} a node reference or null
|
|
|
1049 |
*/
|
|
|
1050 |
getNodeByElement: function(el) {
|
|
|
1051 |
|
|
|
1052 |
var p=el, m, re=/ygtv([^\d]*)(.*)/;
|
|
|
1053 |
|
|
|
1054 |
do {
|
|
|
1055 |
|
|
|
1056 |
if (p && p.id) {
|
|
|
1057 |
m = p.id.match(re);
|
|
|
1058 |
if (m && m[2]) {
|
|
|
1059 |
return this.getNodeByIndex(m[2]);
|
|
|
1060 |
}
|
|
|
1061 |
}
|
|
|
1062 |
|
|
|
1063 |
p = p.parentNode;
|
|
|
1064 |
|
|
|
1065 |
if (!p || !p.tagName) {
|
|
|
1066 |
break;
|
|
|
1067 |
}
|
|
|
1068 |
|
|
|
1069 |
}
|
|
|
1070 |
while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
|
|
|
1071 |
|
|
|
1072 |
return null;
|
|
|
1073 |
},
|
|
|
1074 |
|
|
|
1075 |
/**
|
|
|
1076 |
* When in singleNodeHighlight it returns the node highlighted
|
|
|
1077 |
* or null if none. Returns null if singleNodeHighlight is false.
|
|
|
1078 |
* @method getHighlightedNode
|
|
|
1079 |
* @return {YAHOO.widget.Node} a node reference or null
|
|
|
1080 |
*/
|
|
|
1081 |
getHighlightedNode: function() {
|
|
|
1082 |
return this._currentlyHighlighted;
|
|
|
1083 |
},
|
|
|
1084 |
|
|
|
1085 |
|
|
|
1086 |
/**
|
|
|
1087 |
* Removes the node and its children, and optionally refreshes the
|
|
|
1088 |
* branch of the tree that was affected.
|
|
|
1089 |
* @method removeNode
|
|
|
1090 |
* @param {Node} node to remove
|
|
|
1091 |
* @param {boolean} autoRefresh automatically refreshes branch if true
|
|
|
1092 |
* @return {boolean} False is there was a problem, true otherwise.
|
|
|
1093 |
*/
|
|
|
1094 |
removeNode: function(node, autoRefresh) {
|
|
|
1095 |
|
|
|
1096 |
// Don't delete the root node
|
|
|
1097 |
if (node.isRoot()) {
|
|
|
1098 |
return false;
|
|
|
1099 |
}
|
|
|
1100 |
|
|
|
1101 |
// Get the branch that we may need to refresh
|
|
|
1102 |
var p = node.parent;
|
|
|
1103 |
if (p.parent) {
|
|
|
1104 |
p = p.parent;
|
|
|
1105 |
}
|
|
|
1106 |
|
|
|
1107 |
// Delete the node and its children
|
|
|
1108 |
this._deleteNode(node);
|
|
|
1109 |
|
|
|
1110 |
// Refresh the parent of the parent
|
|
|
1111 |
if (autoRefresh && p && p.childrenRendered) {
|
|
|
1112 |
p.refresh();
|
|
|
1113 |
}
|
|
|
1114 |
|
|
|
1115 |
return true;
|
|
|
1116 |
},
|
|
|
1117 |
|
|
|
1118 |
/**
|
|
|
1119 |
* wait until the animation is complete before deleting
|
|
|
1120 |
* to avoid javascript errors
|
|
|
1121 |
* @method _removeChildren_animComplete
|
|
|
1122 |
* @param o the custom event payload
|
|
|
1123 |
* @private
|
|
|
1124 |
*/
|
|
|
1125 |
_removeChildren_animComplete: function(o) {
|
|
|
1126 |
this.unsubscribe(this._removeChildren_animComplete);
|
|
|
1127 |
this.removeChildren(o.node);
|
|
|
1128 |
},
|
|
|
1129 |
|
|
|
1130 |
/**
|
|
|
1131 |
* Deletes this nodes child collection, recursively. Also collapses
|
|
|
1132 |
* the node, and resets the dynamic load flag. The primary use for
|
|
|
1133 |
* this method is to purge a node and allow it to fetch its data
|
|
|
1134 |
* dynamically again.
|
|
|
1135 |
* @method removeChildren
|
|
|
1136 |
* @param {Node} node the node to purge
|
|
|
1137 |
*/
|
|
|
1138 |
removeChildren: function(node) {
|
|
|
1139 |
|
|
|
1140 |
if (node.expanded) {
|
|
|
1141 |
// wait until the animation is complete before deleting to
|
|
|
1142 |
// avoid javascript errors
|
|
|
1143 |
if (this._collapseAnim) {
|
|
|
1144 |
this.subscribe("animComplete",
|
|
|
1145 |
this._removeChildren_animComplete, this, true);
|
|
|
1146 |
Widget.Node.prototype.collapse.call(node);
|
|
|
1147 |
return;
|
|
|
1148 |
}
|
|
|
1149 |
|
|
|
1150 |
node.collapse();
|
|
|
1151 |
}
|
|
|
1152 |
|
|
|
1153 |
this.logger.log("Removing children for " + node);
|
|
|
1154 |
while (node.children.length) {
|
|
|
1155 |
this._deleteNode(node.children[0]);
|
|
|
1156 |
}
|
|
|
1157 |
|
|
|
1158 |
if (node.isRoot()) {
|
|
|
1159 |
Widget.Node.prototype.expand.call(node);
|
|
|
1160 |
}
|
|
|
1161 |
|
|
|
1162 |
node.childrenRendered = false;
|
|
|
1163 |
node.dynamicLoadComplete = false;
|
|
|
1164 |
|
|
|
1165 |
node.updateIcon();
|
|
|
1166 |
},
|
|
|
1167 |
|
|
|
1168 |
/**
|
|
|
1169 |
* Deletes the node and recurses children
|
|
|
1170 |
* @method _deleteNode
|
|
|
1171 |
* @private
|
|
|
1172 |
*/
|
|
|
1173 |
_deleteNode: function(node) {
|
|
|
1174 |
// Remove all the child nodes first
|
|
|
1175 |
this.removeChildren(node);
|
|
|
1176 |
|
|
|
1177 |
// Remove the node from the tree
|
|
|
1178 |
this.popNode(node);
|
|
|
1179 |
},
|
|
|
1180 |
|
|
|
1181 |
/**
|
|
|
1182 |
* Removes the node from the tree, preserving the child collection
|
|
|
1183 |
* to make it possible to insert the branch into another part of the
|
|
|
1184 |
* tree, or another tree.
|
|
|
1185 |
* @method popNode
|
|
|
1186 |
* @param {Node} node to remove
|
|
|
1187 |
*/
|
|
|
1188 |
popNode: function(node) {
|
|
|
1189 |
var p = node.parent;
|
|
|
1190 |
|
|
|
1191 |
// Update the parent's collection of children
|
|
|
1192 |
var a = [];
|
|
|
1193 |
|
|
|
1194 |
for (var i=0, len=p.children.length;i<len;++i) {
|
|
|
1195 |
if (p.children[i] != node) {
|
|
|
1196 |
a[a.length] = p.children[i];
|
|
|
1197 |
}
|
|
|
1198 |
}
|
|
|
1199 |
|
|
|
1200 |
p.children = a;
|
|
|
1201 |
|
|
|
1202 |
// reset the childrenRendered flag for the parent
|
|
|
1203 |
p.childrenRendered = false;
|
|
|
1204 |
|
|
|
1205 |
// Update the sibling relationship
|
|
|
1206 |
if (node.previousSibling) {
|
|
|
1207 |
node.previousSibling.nextSibling = node.nextSibling;
|
|
|
1208 |
}
|
|
|
1209 |
|
|
|
1210 |
if (node.nextSibling) {
|
|
|
1211 |
node.nextSibling.previousSibling = node.previousSibling;
|
|
|
1212 |
}
|
|
|
1213 |
|
|
|
1214 |
if (this.currentFocus == node) {
|
|
|
1215 |
this.currentFocus = null;
|
|
|
1216 |
}
|
|
|
1217 |
if (this._currentlyHighlighted == node) {
|
|
|
1218 |
this._currentlyHighlighted = null;
|
|
|
1219 |
}
|
|
|
1220 |
|
|
|
1221 |
node.parent = null;
|
|
|
1222 |
node.previousSibling = null;
|
|
|
1223 |
node.nextSibling = null;
|
|
|
1224 |
node.tree = null;
|
|
|
1225 |
|
|
|
1226 |
// Update the tree's node collection
|
|
|
1227 |
delete this._nodes[node.index];
|
|
|
1228 |
},
|
|
|
1229 |
|
|
|
1230 |
/**
|
|
|
1231 |
* Nulls out the entire TreeView instance and related objects, removes attached
|
|
|
1232 |
* event listeners, and clears out DOM elements inside the container. After
|
|
|
1233 |
* calling this method, the instance reference should be expliclitly nulled by
|
|
|
1234 |
* implementer, as in myDataTable = null. Use with caution!
|
|
|
1235 |
*
|
|
|
1236 |
* @method destroy
|
|
|
1237 |
*/
|
|
|
1238 |
destroy : function() {
|
|
|
1239 |
// Since the label editor can be separated from the main TreeView control
|
|
|
1240 |
// the destroy method for it might not be there.
|
|
|
1241 |
if (this._destroyEditor) { this._destroyEditor(); }
|
|
|
1242 |
var el = this.getEl();
|
|
|
1243 |
Event.removeListener(el,'click');
|
|
|
1244 |
Event.removeListener(el,'dblclick');
|
|
|
1245 |
Event.removeListener(el,'mouseover');
|
|
|
1246 |
Event.removeListener(el,'mouseout');
|
|
|
1247 |
Event.removeListener(el,'keydown');
|
|
|
1248 |
for (var i = 0 ; i < this._nodes.length; i++) {
|
|
|
1249 |
var node = this._nodes[i];
|
|
|
1250 |
if (node && node.destroy) {node.destroy(); }
|
|
|
1251 |
}
|
|
|
1252 |
el.innerHTML = '';
|
|
|
1253 |
this._hasEvents = false;
|
|
|
1254 |
},
|
|
|
1255 |
|
|
|
1256 |
|
|
|
1257 |
|
|
|
1258 |
|
|
|
1259 |
/**
|
|
|
1260 |
* TreeView instance toString
|
|
|
1261 |
* @method toString
|
|
|
1262 |
* @return {string} string representation of the tree
|
|
|
1263 |
*/
|
|
|
1264 |
toString: function() {
|
|
|
1265 |
return "TreeView " + this.id;
|
|
|
1266 |
},
|
|
|
1267 |
|
|
|
1268 |
/**
|
|
|
1269 |
* Count of nodes in tree
|
|
|
1270 |
* @method getNodeCount
|
|
|
1271 |
* @return {int} number of nodes in the tree
|
|
|
1272 |
*/
|
|
|
1273 |
getNodeCount: function() {
|
|
|
1274 |
return this.getRoot().getNodeCount();
|
|
|
1275 |
},
|
|
|
1276 |
|
|
|
1277 |
/**
|
|
|
1278 |
* Returns an object which could be used to rebuild the tree.
|
|
|
1279 |
* It can be passed to the tree constructor to reproduce the same tree.
|
|
|
1280 |
* It will return false if any node loads dynamically, regardless of whether it is loaded or not.
|
|
|
1281 |
* @method getTreeDefinition
|
|
|
1282 |
* @return {Object | false} definition of the tree or false if any node is defined as dynamic
|
|
|
1283 |
*/
|
|
|
1284 |
getTreeDefinition: function() {
|
|
|
1285 |
return this.getRoot().getNodeDefinition();
|
|
|
1286 |
},
|
|
|
1287 |
|
|
|
1288 |
/**
|
|
|
1289 |
* Abstract method that is executed when a node is expanded
|
|
|
1290 |
* @method onExpand
|
|
|
1291 |
* @param node {Node} the node that was expanded
|
|
|
1292 |
* @deprecated use treeobj.subscribe("expand") instead
|
|
|
1293 |
*/
|
|
|
1294 |
onExpand: function(node) { },
|
|
|
1295 |
|
|
|
1296 |
/**
|
|
|
1297 |
* Abstract method that is executed when a node is collapsed.
|
|
|
1298 |
* @method onCollapse
|
|
|
1299 |
* @param node {Node} the node that was collapsed.
|
|
|
1300 |
* @deprecated use treeobj.subscribe("collapse") instead
|
|
|
1301 |
*/
|
|
|
1302 |
onCollapse: function(node) { },
|
|
|
1303 |
|
|
|
1304 |
/**
|
|
|
1305 |
* Sets the value of a property for all loaded nodes in the tree.
|
|
|
1306 |
* @method setNodesProperty
|
|
|
1307 |
* @param name {string} Name of the property to be set
|
|
|
1308 |
* @param value {any} value to be set
|
|
|
1309 |
* @param refresh {boolean} if present and true, it does a refresh
|
|
|
1310 |
*/
|
|
|
1311 |
setNodesProperty: function(name, value, refresh) {
|
|
|
1312 |
this.root.setNodesProperty(name,value);
|
|
|
1313 |
if (refresh) {
|
|
|
1314 |
this.root.refresh();
|
|
|
1315 |
}
|
|
|
1316 |
},
|
|
|
1317 |
/**
|
|
|
1318 |
* Event listener to toggle node highlight.
|
|
|
1319 |
* Can be assigned as listener to clickEvent, dblClickEvent and enterKeyPressed.
|
|
|
1320 |
* It returns false to prevent the default action.
|
|
|
1321 |
* @method onEventToggleHighlight
|
|
|
1322 |
* @param oArgs {any} it takes the arguments of any of the events mentioned above
|
|
|
1323 |
* @return {false} Always cancels the default action for the event
|
|
|
1324 |
*/
|
|
|
1325 |
onEventToggleHighlight: function (oArgs) {
|
|
|
1326 |
var node;
|
|
|
1327 |
if ('node' in oArgs && oArgs.node instanceof Widget.Node) {
|
|
|
1328 |
node = oArgs.node;
|
|
|
1329 |
} else if (oArgs instanceof Widget.Node) {
|
|
|
1330 |
node = oArgs;
|
|
|
1331 |
} else {
|
|
|
1332 |
return false;
|
|
|
1333 |
}
|
|
|
1334 |
node.toggleHighlight();
|
|
|
1335 |
return false;
|
|
|
1336 |
}
|
|
|
1337 |
|
|
|
1338 |
|
|
|
1339 |
};
|
|
|
1340 |
|
|
|
1341 |
/* Backwards compatibility aliases */
|
|
|
1342 |
var PROT = TV.prototype;
|
|
|
1343 |
/**
|
|
|
1344 |
* Renders the tree boilerplate and visible nodes.
|
|
|
1345 |
* Alias for render
|
|
|
1346 |
* @method draw
|
|
|
1347 |
* @deprecated Use render instead
|
|
|
1348 |
*/
|
|
|
1349 |
PROT.draw = PROT.render;
|
|
|
1350 |
|
|
|
1351 |
/* end backwards compatibility aliases */
|
|
|
1352 |
|
|
|
1353 |
YAHOO.augment(TV, YAHOO.util.EventProvider);
|
|
|
1354 |
|
|
|
1355 |
/**
|
|
|
1356 |
* Running count of all nodes created in all trees. This is
|
|
|
1357 |
* used to provide unique identifies for all nodes. Deleting
|
|
|
1358 |
* nodes does not change the nodeCount.
|
|
|
1359 |
* @property YAHOO.widget.TreeView.nodeCount
|
|
|
1360 |
* @type int
|
|
|
1361 |
* @static
|
|
|
1362 |
*/
|
|
|
1363 |
TV.nodeCount = 0;
|
|
|
1364 |
|
|
|
1365 |
/**
|
|
|
1366 |
* Global cache of tree instances
|
|
|
1367 |
* @property YAHOO.widget.TreeView.trees
|
|
|
1368 |
* @type Array
|
|
|
1369 |
* @static
|
|
|
1370 |
* @private
|
|
|
1371 |
*/
|
|
|
1372 |
TV.trees = [];
|
|
|
1373 |
|
|
|
1374 |
/**
|
|
|
1375 |
* Global method for getting a tree by its id. Used in the generated
|
|
|
1376 |
* tree html.
|
|
|
1377 |
* @method YAHOO.widget.TreeView.getTree
|
|
|
1378 |
* @param treeId {String} the id of the tree instance
|
|
|
1379 |
* @return {TreeView} the tree instance requested, null if not found.
|
|
|
1380 |
* @static
|
|
|
1381 |
*/
|
|
|
1382 |
TV.getTree = function(treeId) {
|
|
|
1383 |
var t = TV.trees[treeId];
|
|
|
1384 |
return (t) ? t : null;
|
|
|
1385 |
};
|
|
|
1386 |
|
|
|
1387 |
|
|
|
1388 |
/**
|
|
|
1389 |
* Global method for getting a node by its id. Used in the generated
|
|
|
1390 |
* tree html.
|
|
|
1391 |
* @method YAHOO.widget.TreeView.getNode
|
|
|
1392 |
* @param treeId {String} the id of the tree instance
|
|
|
1393 |
* @param nodeIndex {String} the index of the node to return
|
|
|
1394 |
* @return {Node} the node instance requested, null if not found
|
|
|
1395 |
* @static
|
|
|
1396 |
*/
|
|
|
1397 |
TV.getNode = function(treeId, nodeIndex) {
|
|
|
1398 |
var t = TV.getTree(treeId);
|
|
|
1399 |
return (t) ? t.getNodeByIndex(nodeIndex) : null;
|
|
|
1400 |
};
|
|
|
1401 |
|
|
|
1402 |
|
|
|
1403 |
/**
|
|
|
1404 |
* Class name assigned to elements that have the focus
|
|
|
1405 |
*
|
|
|
1406 |
* @property TreeView.FOCUS_CLASS_NAME
|
|
|
1407 |
* @type String
|
|
|
1408 |
* @static
|
|
|
1409 |
* @final
|
|
|
1410 |
* @default "ygtvfocus"
|
|
|
1411 |
|
|
|
1412 |
*/
|
|
|
1413 |
TV.FOCUS_CLASS_NAME = 'ygtvfocus';
|
|
|
1414 |
|
|
|
1415 |
|
|
|
1416 |
|
|
|
1417 |
})();
|
|
|
1418 |
(function () {
|
|
|
1419 |
var Dom = YAHOO.util.Dom,
|
|
|
1420 |
Lang = YAHOO.lang,
|
|
|
1421 |
Event = YAHOO.util.Event;
|
|
|
1422 |
/**
|
|
|
1423 |
* The base class for all tree nodes. The node's presentation and behavior in
|
|
|
1424 |
* response to mouse events is handled in Node subclasses.
|
|
|
1425 |
* @namespace YAHOO.widget
|
|
|
1426 |
* @class Node
|
|
|
1427 |
* @uses YAHOO.util.EventProvider
|
|
|
1428 |
* @param oData {object} a string or object containing the data that will
|
|
|
1429 |
* be used to render this node, and any custom attributes that should be
|
|
|
1430 |
* stored with the node (which is available in noderef.data).
|
|
|
1431 |
* All values in oData will be used to set equally named properties in the node
|
|
|
1432 |
* as long as the node does have such properties, they are not undefined, private or functions,
|
|
|
1433 |
* the rest of the values will be stored in noderef.data
|
|
|
1434 |
* @param oParent {Node} this node's parent node
|
|
|
1435 |
* @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded)
|
|
|
1436 |
* @constructor
|
|
|
1437 |
*/
|
|
|
1438 |
YAHOO.widget.Node = function(oData, oParent, expanded) {
|
|
|
1439 |
if (oData) { this.init(oData, oParent, expanded); }
|
|
|
1440 |
};
|
|
|
1441 |
|
|
|
1442 |
YAHOO.widget.Node.prototype = {
|
|
|
1443 |
|
|
|
1444 |
/**
|
|
|
1445 |
* The index for this instance obtained from global counter in YAHOO.widget.TreeView.
|
|
|
1446 |
* @property index
|
|
|
1447 |
* @type int
|
|
|
1448 |
*/
|
|
|
1449 |
index: 0,
|
|
|
1450 |
|
|
|
1451 |
/**
|
|
|
1452 |
* This node's child node collection.
|
|
|
1453 |
* @property children
|
|
|
1454 |
* @type Node[]
|
|
|
1455 |
*/
|
|
|
1456 |
children: null,
|
|
|
1457 |
|
|
|
1458 |
/**
|
|
|
1459 |
* Tree instance this node is part of
|
|
|
1460 |
* @property tree
|
|
|
1461 |
* @type TreeView
|
|
|
1462 |
*/
|
|
|
1463 |
tree: null,
|
|
|
1464 |
|
|
|
1465 |
/**
|
|
|
1466 |
* The data linked to this node. This can be any object or primitive
|
|
|
1467 |
* value, and the data can be used in getNodeHtml().
|
|
|
1468 |
* @property data
|
|
|
1469 |
* @type object
|
|
|
1470 |
*/
|
|
|
1471 |
data: null,
|
|
|
1472 |
|
|
|
1473 |
/**
|
|
|
1474 |
* Parent node
|
|
|
1475 |
* @property parent
|
|
|
1476 |
* @type Node
|
|
|
1477 |
*/
|
|
|
1478 |
parent: null,
|
|
|
1479 |
|
|
|
1480 |
/**
|
|
|
1481 |
* The depth of this node. We start at -1 for the root node.
|
|
|
1482 |
* @property depth
|
|
|
1483 |
* @type int
|
|
|
1484 |
*/
|
|
|
1485 |
depth: -1,
|
|
|
1486 |
|
|
|
1487 |
/**
|
|
|
1488 |
* The node's expanded/collapsed state
|
|
|
1489 |
* @property expanded
|
|
|
1490 |
* @type boolean
|
|
|
1491 |
*/
|
|
|
1492 |
expanded: false,
|
|
|
1493 |
|
|
|
1494 |
/**
|
|
|
1495 |
* Can multiple children be expanded at once?
|
|
|
1496 |
* @property multiExpand
|
|
|
1497 |
* @type boolean
|
|
|
1498 |
*/
|
|
|
1499 |
multiExpand: true,
|
|
|
1500 |
|
|
|
1501 |
/**
|
|
|
1502 |
* Should we render children for a collapsed node? It is possible that the
|
|
|
1503 |
* implementer will want to render the hidden data... @todo verify that we
|
|
|
1504 |
* need this, and implement it if we do.
|
|
|
1505 |
* @property renderHidden
|
|
|
1506 |
* @type boolean
|
|
|
1507 |
*/
|
|
|
1508 |
renderHidden: false,
|
|
|
1509 |
|
|
|
1510 |
/**
|
|
|
1511 |
* This flag is set to true when the html is generated for this node's
|
|
|
1512 |
* children, and set to false when new children are added.
|
|
|
1513 |
* @property childrenRendered
|
|
|
1514 |
* @type boolean
|
|
|
1515 |
*/
|
|
|
1516 |
childrenRendered: false,
|
|
|
1517 |
|
|
|
1518 |
/**
|
|
|
1519 |
* Dynamically loaded nodes only fetch the data the first time they are
|
|
|
1520 |
* expanded. This flag is set to true once the data has been fetched.
|
|
|
1521 |
* @property dynamicLoadComplete
|
|
|
1522 |
* @type boolean
|
|
|
1523 |
*/
|
|
|
1524 |
dynamicLoadComplete: false,
|
|
|
1525 |
|
|
|
1526 |
/**
|
|
|
1527 |
* This node's previous sibling
|
|
|
1528 |
* @property previousSibling
|
|
|
1529 |
* @type Node
|
|
|
1530 |
*/
|
|
|
1531 |
previousSibling: null,
|
|
|
1532 |
|
|
|
1533 |
/**
|
|
|
1534 |
* This node's next sibling
|
|
|
1535 |
* @property nextSibling
|
|
|
1536 |
* @type Node
|
|
|
1537 |
*/
|
|
|
1538 |
nextSibling: null,
|
|
|
1539 |
|
|
|
1540 |
/**
|
|
|
1541 |
* We can set the node up to call an external method to get the child
|
|
|
1542 |
* data dynamically.
|
|
|
1543 |
* @property _dynLoad
|
|
|
1544 |
* @type boolean
|
|
|
1545 |
* @private
|
|
|
1546 |
*/
|
|
|
1547 |
_dynLoad: false,
|
|
|
1548 |
|
|
|
1549 |
/**
|
|
|
1550 |
* Function to execute when we need to get this node's child data.
|
|
|
1551 |
* @property dataLoader
|
|
|
1552 |
* @type function
|
|
|
1553 |
*/
|
|
|
1554 |
dataLoader: null,
|
|
|
1555 |
|
|
|
1556 |
/**
|
|
|
1557 |
* This is true for dynamically loading nodes while waiting for the
|
|
|
1558 |
* callback to return.
|
|
|
1559 |
* @property isLoading
|
|
|
1560 |
* @type boolean
|
|
|
1561 |
*/
|
|
|
1562 |
isLoading: false,
|
|
|
1563 |
|
|
|
1564 |
/**
|
|
|
1565 |
* The toggle/branch icon will not show if this is set to false. This
|
|
|
1566 |
* could be useful if the implementer wants to have the child contain
|
|
|
1567 |
* extra info about the parent, rather than an actual node.
|
|
|
1568 |
* @property hasIcon
|
|
|
1569 |
* @type boolean
|
|
|
1570 |
*/
|
|
|
1571 |
hasIcon: true,
|
|
|
1572 |
|
|
|
1573 |
/**
|
|
|
1574 |
* Used to configure what happens when a dynamic load node is expanded
|
|
|
1575 |
* and we discover that it does not have children. By default, it is
|
|
|
1576 |
* treated as if it still could have children (plus/minus icon). Set
|
|
|
1577 |
* iconMode to have it display like a leaf node instead.
|
|
|
1578 |
* @property iconMode
|
|
|
1579 |
* @type int
|
|
|
1580 |
*/
|
|
|
1581 |
iconMode: 0,
|
|
|
1582 |
|
|
|
1583 |
/**
|
|
|
1584 |
* Specifies whether or not the content area of the node should be allowed
|
|
|
1585 |
* to wrap.
|
|
|
1586 |
* @property nowrap
|
|
|
1587 |
* @type boolean
|
|
|
1588 |
* @default false
|
|
|
1589 |
*/
|
|
|
1590 |
nowrap: false,
|
|
|
1591 |
|
|
|
1592 |
/**
|
|
|
1593 |
* If true, the node will alway be rendered as a leaf node. This can be
|
|
|
1594 |
* used to override the presentation when dynamically loading the entire
|
|
|
1595 |
* tree. Setting this to true also disables the dynamic load call for the
|
|
|
1596 |
* node.
|
|
|
1597 |
* @property isLeaf
|
|
|
1598 |
* @type boolean
|
|
|
1599 |
* @default false
|
|
|
1600 |
*/
|
|
|
1601 |
isLeaf: false,
|
|
|
1602 |
|
|
|
1603 |
/**
|
|
|
1604 |
* The CSS class for the html content container. Defaults to ygtvhtml, but
|
|
|
1605 |
* can be overridden to provide a custom presentation for a specific node.
|
|
|
1606 |
* @property contentStyle
|
|
|
1607 |
* @type string
|
|
|
1608 |
*/
|
|
|
1609 |
contentStyle: "",
|
|
|
1610 |
|
|
|
1611 |
|
|
|
1612 |
/**
|
|
|
1613 |
* The generated id that will contain the data passed in by the implementer.
|
|
|
1614 |
* @property contentElId
|
|
|
1615 |
* @type string
|
|
|
1616 |
*/
|
|
|
1617 |
contentElId: null,
|
|
|
1618 |
|
|
|
1619 |
/**
|
|
|
1620 |
* Enables node highlighting. If true, the node can be highlighted and/or propagate highlighting
|
|
|
1621 |
* @property enableHighlight
|
|
|
1622 |
* @type boolean
|
|
|
1623 |
* @default true
|
|
|
1624 |
*/
|
|
|
1625 |
enableHighlight: true,
|
|
|
1626 |
|
|
|
1627 |
/**
|
|
|
1628 |
* Stores the highlight state. Can be any of:
|
|
|
1629 |
* <ul>
|
|
|
1630 |
* <li>0 - not highlighted</li>
|
|
|
1631 |
* <li>1 - highlighted</li>
|
|
|
1632 |
* <li>2 - some children highlighted</li>
|
|
|
1633 |
* </ul>
|
|
|
1634 |
* @property highlightState
|
|
|
1635 |
* @type integer
|
|
|
1636 |
* @default 0
|
|
|
1637 |
*/
|
|
|
1638 |
|
|
|
1639 |
highlightState: 0,
|
|
|
1640 |
|
|
|
1641 |
/**
|
|
|
1642 |
* Tells whether highlighting will be propagated up to the parents of the clicked node
|
|
|
1643 |
* @property propagateHighlightUp
|
|
|
1644 |
* @type boolean
|
|
|
1645 |
* @default false
|
|
|
1646 |
*/
|
|
|
1647 |
|
|
|
1648 |
propagateHighlightUp: false,
|
|
|
1649 |
|
|
|
1650 |
/**
|
|
|
1651 |
* Tells whether highlighting will be propagated down to the children of the clicked node
|
|
|
1652 |
* @property propagateHighlightDown
|
|
|
1653 |
* @type boolean
|
|
|
1654 |
* @default false
|
|
|
1655 |
*/
|
|
|
1656 |
|
|
|
1657 |
propagateHighlightDown: false,
|
|
|
1658 |
|
|
|
1659 |
/**
|
|
|
1660 |
* User-defined className to be added to the Node
|
|
|
1661 |
* @property className
|
|
|
1662 |
* @type string
|
|
|
1663 |
* @default null
|
|
|
1664 |
*/
|
|
|
1665 |
|
|
|
1666 |
className: null,
|
|
|
1667 |
|
|
|
1668 |
/**
|
|
|
1669 |
* The node type
|
|
|
1670 |
* @property _type
|
|
|
1671 |
* @private
|
|
|
1672 |
* @type string
|
|
|
1673 |
* @default "Node"
|
|
|
1674 |
*/
|
|
|
1675 |
_type: "Node",
|
|
|
1676 |
|
|
|
1677 |
/*
|
|
|
1678 |
spacerPath: "http://l.yimg.com/a/i/space.gif",
|
|
|
1679 |
expandedText: "Expanded",
|
|
|
1680 |
collapsedText: "Collapsed",
|
|
|
1681 |
loadingText: "Loading",
|
|
|
1682 |
*/
|
|
|
1683 |
|
|
|
1684 |
/**
|
|
|
1685 |
* Initializes this node, gets some of the properties from the parent
|
|
|
1686 |
* @method init
|
|
|
1687 |
* @param oData {object} a string or object containing the data that will
|
|
|
1688 |
* be used to render this node
|
|
|
1689 |
* @param oParent {Node} this node's parent node
|
|
|
1690 |
* @param expanded {boolean} the initial expanded/collapsed state
|
|
|
1691 |
*/
|
|
|
1692 |
init: function(oData, oParent, expanded) {
|
|
|
1693 |
|
|
|
1694 |
this.data = {};
|
|
|
1695 |
this.children = [];
|
|
|
1696 |
this.index = YAHOO.widget.TreeView.nodeCount;
|
|
|
1697 |
++YAHOO.widget.TreeView.nodeCount;
|
|
|
1698 |
this.contentElId = "ygtvcontentel" + this.index;
|
|
|
1699 |
|
|
|
1700 |
if (Lang.isObject(oData)) {
|
|
|
1701 |
for (var property in oData) {
|
|
|
1702 |
if (oData.hasOwnProperty(property)) {
|
|
|
1703 |
if (property.charAt(0) != '_' && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) {
|
|
|
1704 |
this[property] = oData[property];
|
|
|
1705 |
} else {
|
|
|
1706 |
this.data[property] = oData[property];
|
|
|
1707 |
}
|
|
|
1708 |
}
|
|
|
1709 |
}
|
|
|
1710 |
}
|
|
|
1711 |
if (!Lang.isUndefined(expanded) ) { this.expanded = expanded; }
|
|
|
1712 |
|
|
|
1713 |
this.logger = new YAHOO.widget.LogWriter(this.toString());
|
|
|
1714 |
|
|
|
1715 |
/**
|
|
|
1716 |
* The parentChange event is fired when a parent element is applied
|
|
|
1717 |
* to the node. This is useful if you need to apply tree-level
|
|
|
1718 |
* properties to a tree that need to happen if a node is moved from
|
|
|
1719 |
* one tree to another.
|
|
|
1720 |
*
|
|
|
1721 |
* @event parentChange
|
|
|
1722 |
* @type CustomEvent
|
|
|
1723 |
*/
|
|
|
1724 |
this.createEvent("parentChange", this);
|
|
|
1725 |
|
|
|
1726 |
// oParent should never be null except when we create the root node.
|
|
|
1727 |
if (oParent) {
|
|
|
1728 |
oParent.appendChild(this);
|
|
|
1729 |
}
|
|
|
1730 |
},
|
|
|
1731 |
|
|
|
1732 |
/**
|
|
|
1733 |
* Certain properties for the node cannot be set until the parent
|
|
|
1734 |
* is known. This is called after the node is inserted into a tree.
|
|
|
1735 |
* the parent is also applied to this node's children in order to
|
|
|
1736 |
* make it possible to move a branch from one tree to another.
|
|
|
1737 |
* @method applyParent
|
|
|
1738 |
* @param {Node} parentNode this node's parent node
|
|
|
1739 |
* @return {boolean} true if the application was successful
|
|
|
1740 |
*/
|
|
|
1741 |
applyParent: function(parentNode) {
|
|
|
1742 |
if (!parentNode) {
|
|
|
1743 |
return false;
|
|
|
1744 |
}
|
|
|
1745 |
|
|
|
1746 |
this.tree = parentNode.tree;
|
|
|
1747 |
this.parent = parentNode;
|
|
|
1748 |
this.depth = parentNode.depth + 1;
|
|
|
1749 |
|
|
|
1750 |
// @todo why was this put here. This causes new nodes added at the
|
|
|
1751 |
// root level to lose the menu behavior.
|
|
|
1752 |
// if (! this.multiExpand) {
|
|
|
1753 |
// this.multiExpand = parentNode.multiExpand;
|
|
|
1754 |
// }
|
|
|
1755 |
|
|
|
1756 |
this.tree.regNode(this);
|
|
|
1757 |
parentNode.childrenRendered = false;
|
|
|
1758 |
|
|
|
1759 |
// cascade update existing children
|
|
|
1760 |
for (var i=0, len=this.children.length;i<len;++i) {
|
|
|
1761 |
this.children[i].applyParent(this);
|
|
|
1762 |
}
|
|
|
1763 |
|
|
|
1764 |
this.fireEvent("parentChange");
|
|
|
1765 |
|
|
|
1766 |
return true;
|
|
|
1767 |
},
|
|
|
1768 |
|
|
|
1769 |
/**
|
|
|
1770 |
* Appends a node to the child collection.
|
|
|
1771 |
* @method appendChild
|
|
|
1772 |
* @param childNode {Node} the new node
|
|
|
1773 |
* @return {Node} the child node
|
|
|
1774 |
* @private
|
|
|
1775 |
*/
|
|
|
1776 |
appendChild: function(childNode) {
|
|
|
1777 |
if (this.hasChildren()) {
|
|
|
1778 |
var sib = this.children[this.children.length - 1];
|
|
|
1779 |
sib.nextSibling = childNode;
|
|
|
1780 |
childNode.previousSibling = sib;
|
|
|
1781 |
}
|
|
|
1782 |
this.children[this.children.length] = childNode;
|
|
|
1783 |
childNode.applyParent(this);
|
|
|
1784 |
|
|
|
1785 |
// part of the IE display issue workaround. If child nodes
|
|
|
1786 |
// are added after the initial render, and the node was
|
|
|
1787 |
// instantiated with expanded = true, we need to show the
|
|
|
1788 |
// children div now that the node has a child.
|
|
|
1789 |
if (this.childrenRendered && this.expanded) {
|
|
|
1790 |
this.getChildrenEl().style.display = "";
|
|
|
1791 |
}
|
|
|
1792 |
|
|
|
1793 |
return childNode;
|
|
|
1794 |
},
|
|
|
1795 |
|
|
|
1796 |
/**
|
|
|
1797 |
* Appends this node to the supplied node's child collection
|
|
|
1798 |
* @method appendTo
|
|
|
1799 |
* @param parentNode {Node} the node to append to.
|
|
|
1800 |
* @return {Node} The appended node
|
|
|
1801 |
*/
|
|
|
1802 |
appendTo: function(parentNode) {
|
|
|
1803 |
return parentNode.appendChild(this);
|
|
|
1804 |
},
|
|
|
1805 |
|
|
|
1806 |
/**
|
|
|
1807 |
* Inserts this node before this supplied node
|
|
|
1808 |
* @method insertBefore
|
|
|
1809 |
* @param node {Node} the node to insert this node before
|
|
|
1810 |
* @return {Node} the inserted node
|
|
|
1811 |
*/
|
|
|
1812 |
insertBefore: function(node) {
|
|
|
1813 |
this.logger.log("insertBefore: " + node);
|
|
|
1814 |
var p = node.parent;
|
|
|
1815 |
if (p) {
|
|
|
1816 |
|
|
|
1817 |
if (this.tree) {
|
|
|
1818 |
this.tree.popNode(this);
|
|
|
1819 |
}
|
|
|
1820 |
|
|
|
1821 |
var refIndex = node.isChildOf(p);
|
|
|
1822 |
//this.logger.log(refIndex);
|
|
|
1823 |
p.children.splice(refIndex, 0, this);
|
|
|
1824 |
if (node.previousSibling) {
|
|
|
1825 |
node.previousSibling.nextSibling = this;
|
|
|
1826 |
}
|
|
|
1827 |
this.previousSibling = node.previousSibling;
|
|
|
1828 |
this.nextSibling = node;
|
|
|
1829 |
node.previousSibling = this;
|
|
|
1830 |
|
|
|
1831 |
this.applyParent(p);
|
|
|
1832 |
}
|
|
|
1833 |
|
|
|
1834 |
return this;
|
|
|
1835 |
},
|
|
|
1836 |
|
|
|
1837 |
/**
|
|
|
1838 |
* Inserts this node after the supplied node
|
|
|
1839 |
* @method insertAfter
|
|
|
1840 |
* @param node {Node} the node to insert after
|
|
|
1841 |
* @return {Node} the inserted node
|
|
|
1842 |
*/
|
|
|
1843 |
insertAfter: function(node) {
|
|
|
1844 |
this.logger.log("insertAfter: " + node);
|
|
|
1845 |
var p = node.parent;
|
|
|
1846 |
if (p) {
|
|
|
1847 |
|
|
|
1848 |
if (this.tree) {
|
|
|
1849 |
this.tree.popNode(this);
|
|
|
1850 |
}
|
|
|
1851 |
|
|
|
1852 |
var refIndex = node.isChildOf(p);
|
|
|
1853 |
this.logger.log(refIndex);
|
|
|
1854 |
|
|
|
1855 |
if (!node.nextSibling) {
|
|
|
1856 |
this.nextSibling = null;
|
|
|
1857 |
return this.appendTo(p);
|
|
|
1858 |
}
|
|
|
1859 |
|
|
|
1860 |
p.children.splice(refIndex + 1, 0, this);
|
|
|
1861 |
|
|
|
1862 |
node.nextSibling.previousSibling = this;
|
|
|
1863 |
this.previousSibling = node;
|
|
|
1864 |
this.nextSibling = node.nextSibling;
|
|
|
1865 |
node.nextSibling = this;
|
|
|
1866 |
|
|
|
1867 |
this.applyParent(p);
|
|
|
1868 |
}
|
|
|
1869 |
|
|
|
1870 |
return this;
|
|
|
1871 |
},
|
|
|
1872 |
|
|
|
1873 |
/**
|
|
|
1874 |
* Returns true if the Node is a child of supplied Node
|
|
|
1875 |
* @method isChildOf
|
|
|
1876 |
* @param parentNode {Node} the Node to check
|
|
|
1877 |
* @return {boolean} The node index if this Node is a child of
|
|
|
1878 |
* supplied Node, else -1.
|
|
|
1879 |
* @private
|
|
|
1880 |
*/
|
|
|
1881 |
isChildOf: function(parentNode) {
|
|
|
1882 |
if (parentNode && parentNode.children) {
|
|
|
1883 |
for (var i=0, len=parentNode.children.length; i<len ; ++i) {
|
|
|
1884 |
if (parentNode.children[i] === this) {
|
|
|
1885 |
return i;
|
|
|
1886 |
}
|
|
|
1887 |
}
|
|
|
1888 |
}
|
|
|
1889 |
|
|
|
1890 |
return -1;
|
|
|
1891 |
},
|
|
|
1892 |
|
|
|
1893 |
/**
|
|
|
1894 |
* Returns a node array of this node's siblings, null if none.
|
|
|
1895 |
* @method getSiblings
|
|
|
1896 |
* @return Node[]
|
|
|
1897 |
*/
|
|
|
1898 |
getSiblings: function() {
|
|
|
1899 |
var sib = this.parent.children.slice(0);
|
|
|
1900 |
for (var i=0;i < sib.length && sib[i] != this;i++) {}
|
|
|
1901 |
sib.splice(i,1);
|
|
|
1902 |
if (sib.length) { return sib; }
|
|
|
1903 |
return null;
|
|
|
1904 |
},
|
|
|
1905 |
|
|
|
1906 |
/**
|
|
|
1907 |
* Shows this node's children
|
|
|
1908 |
* @method showChildren
|
|
|
1909 |
*/
|
|
|
1910 |
showChildren: function() {
|
|
|
1911 |
if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
|
|
|
1912 |
if (this.hasChildren()) {
|
|
|
1913 |
this.getChildrenEl().style.display = "";
|
|
|
1914 |
}
|
|
|
1915 |
}
|
|
|
1916 |
},
|
|
|
1917 |
|
|
|
1918 |
/**
|
|
|
1919 |
* Hides this node's children
|
|
|
1920 |
* @method hideChildren
|
|
|
1921 |
*/
|
|
|
1922 |
hideChildren: function() {
|
|
|
1923 |
this.logger.log("hiding " + this.index);
|
|
|
1924 |
|
|
|
1925 |
if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
|
|
|
1926 |
this.getChildrenEl().style.display = "none";
|
|
|
1927 |
}
|
|
|
1928 |
},
|
|
|
1929 |
|
|
|
1930 |
/**
|
|
|
1931 |
* Returns the id for this node's container div
|
|
|
1932 |
* @method getElId
|
|
|
1933 |
* @return {string} the element id
|
|
|
1934 |
*/
|
|
|
1935 |
getElId: function() {
|
|
|
1936 |
return "ygtv" + this.index;
|
|
|
1937 |
},
|
|
|
1938 |
|
|
|
1939 |
/**
|
|
|
1940 |
* Returns the id for this node's children div
|
|
|
1941 |
* @method getChildrenElId
|
|
|
1942 |
* @return {string} the element id for this node's children div
|
|
|
1943 |
*/
|
|
|
1944 |
getChildrenElId: function() {
|
|
|
1945 |
return "ygtvc" + this.index;
|
|
|
1946 |
},
|
|
|
1947 |
|
|
|
1948 |
/**
|
|
|
1949 |
* Returns the id for this node's toggle element
|
|
|
1950 |
* @method getToggleElId
|
|
|
1951 |
* @return {string} the toggel element id
|
|
|
1952 |
*/
|
|
|
1953 |
getToggleElId: function() {
|
|
|
1954 |
return "ygtvt" + this.index;
|
|
|
1955 |
},
|
|
|
1956 |
|
|
|
1957 |
|
|
|
1958 |
/*
|
|
|
1959 |
* Returns the id for this node's spacer image. The spacer is positioned
|
|
|
1960 |
* over the toggle and provides feedback for screen readers.
|
|
|
1961 |
* @method getSpacerId
|
|
|
1962 |
* @return {string} the id for the spacer image
|
|
|
1963 |
*/
|
|
|
1964 |
/*
|
|
|
1965 |
getSpacerId: function() {
|
|
|
1966 |
return "ygtvspacer" + this.index;
|
|
|
1967 |
},
|
|
|
1968 |
*/
|
|
|
1969 |
|
|
|
1970 |
/**
|
|
|
1971 |
* Returns this node's container html element
|
|
|
1972 |
* @method getEl
|
|
|
1973 |
* @return {HTMLElement} the container html element
|
|
|
1974 |
*/
|
|
|
1975 |
getEl: function() {
|
|
|
1976 |
return Dom.get(this.getElId());
|
|
|
1977 |
},
|
|
|
1978 |
|
|
|
1979 |
/**
|
|
|
1980 |
* Returns the div that was generated for this node's children
|
|
|
1981 |
* @method getChildrenEl
|
|
|
1982 |
* @return {HTMLElement} this node's children div
|
|
|
1983 |
*/
|
|
|
1984 |
getChildrenEl: function() {
|
|
|
1985 |
return Dom.get(this.getChildrenElId());
|
|
|
1986 |
},
|
|
|
1987 |
|
|
|
1988 |
/**
|
|
|
1989 |
* Returns the element that is being used for this node's toggle.
|
|
|
1990 |
* @method getToggleEl
|
|
|
1991 |
* @return {HTMLElement} this node's toggle html element
|
|
|
1992 |
*/
|
|
|
1993 |
getToggleEl: function() {
|
|
|
1994 |
return Dom.get(this.getToggleElId());
|
|
|
1995 |
},
|
|
|
1996 |
/**
|
|
|
1997 |
* Returns the outer html element for this node's content
|
|
|
1998 |
* @method getContentEl
|
|
|
1999 |
* @return {HTMLElement} the element
|
|
|
2000 |
*/
|
|
|
2001 |
getContentEl: function() {
|
|
|
2002 |
return Dom.get(this.contentElId);
|
|
|
2003 |
},
|
|
|
2004 |
|
|
|
2005 |
|
|
|
2006 |
/*
|
|
|
2007 |
* Returns the element that is being used for this node's spacer.
|
|
|
2008 |
* @method getSpacer
|
|
|
2009 |
* @return {HTMLElement} this node's spacer html element
|
|
|
2010 |
*/
|
|
|
2011 |
/*
|
|
|
2012 |
getSpacer: function() {
|
|
|
2013 |
return document.getElementById( this.getSpacerId() ) || {};
|
|
|
2014 |
},
|
|
|
2015 |
*/
|
|
|
2016 |
|
|
|
2017 |
/*
|
|
|
2018 |
getStateText: function() {
|
|
|
2019 |
if (this.isLoading) {
|
|
|
2020 |
return this.loadingText;
|
|
|
2021 |
} else if (this.hasChildren(true)) {
|
|
|
2022 |
if (this.expanded) {
|
|
|
2023 |
return this.expandedText;
|
|
|
2024 |
} else {
|
|
|
2025 |
return this.collapsedText;
|
|
|
2026 |
}
|
|
|
2027 |
} else {
|
|
|
2028 |
return "";
|
|
|
2029 |
}
|
|
|
2030 |
},
|
|
|
2031 |
*/
|
|
|
2032 |
|
|
|
2033 |
/**
|
|
|
2034 |
* Hides this nodes children (creating them if necessary), changes the toggle style.
|
|
|
2035 |
* @method collapse
|
|
|
2036 |
*/
|
|
|
2037 |
collapse: function() {
|
|
|
2038 |
// Only collapse if currently expanded
|
|
|
2039 |
if (!this.expanded) { return; }
|
|
|
2040 |
|
|
|
2041 |
// fire the collapse event handler
|
|
|
2042 |
var ret = this.tree.onCollapse(this);
|
|
|
2043 |
|
|
|
2044 |
if (false === ret) {
|
|
|
2045 |
this.logger.log("Collapse was stopped by the abstract onCollapse");
|
|
|
2046 |
return;
|
|
|
2047 |
}
|
|
|
2048 |
|
|
|
2049 |
ret = this.tree.fireEvent("collapse", this);
|
|
|
2050 |
|
|
|
2051 |
if (false === ret) {
|
|
|
2052 |
this.logger.log("Collapse was stopped by a custom event handler");
|
|
|
2053 |
return;
|
|
|
2054 |
}
|
|
|
2055 |
|
|
|
2056 |
|
|
|
2057 |
if (!this.getEl()) {
|
|
|
2058 |
this.expanded = false;
|
|
|
2059 |
} else {
|
|
|
2060 |
// hide the child div
|
|
|
2061 |
this.hideChildren();
|
|
|
2062 |
this.expanded = false;
|
|
|
2063 |
|
|
|
2064 |
this.updateIcon();
|
|
|
2065 |
}
|
|
|
2066 |
|
|
|
2067 |
// this.getSpacer().title = this.getStateText();
|
|
|
2068 |
|
|
|
2069 |
ret = this.tree.fireEvent("collapseComplete", this);
|
|
|
2070 |
|
|
|
2071 |
},
|
|
|
2072 |
|
|
|
2073 |
/**
|
|
|
2074 |
* Shows this nodes children (creating them if necessary), changes the
|
|
|
2075 |
* toggle style, and collapses its siblings if multiExpand is not set.
|
|
|
2076 |
* @method expand
|
|
|
2077 |
*/
|
|
|
2078 |
expand: function(lazySource) {
|
|
|
2079 |
// Only expand if currently collapsed.
|
|
|
2080 |
if (this.isLoading || (this.expanded && !lazySource)) {
|
|
|
2081 |
return;
|
|
|
2082 |
}
|
|
|
2083 |
|
|
|
2084 |
var ret = true;
|
|
|
2085 |
|
|
|
2086 |
// When returning from the lazy load handler, expand is called again
|
|
|
2087 |
// in order to render the new children. The "expand" event already
|
|
|
2088 |
// fired before fething the new data, so we need to skip it now.
|
|
|
2089 |
if (!lazySource) {
|
|
|
2090 |
// fire the expand event handler
|
|
|
2091 |
ret = this.tree.onExpand(this);
|
|
|
2092 |
|
|
|
2093 |
if (false === ret) {
|
|
|
2094 |
this.logger.log("Expand was stopped by the abstract onExpand");
|
|
|
2095 |
return;
|
|
|
2096 |
}
|
|
|
2097 |
|
|
|
2098 |
ret = this.tree.fireEvent("expand", this);
|
|
|
2099 |
}
|
|
|
2100 |
|
|
|
2101 |
if (false === ret) {
|
|
|
2102 |
this.logger.log("Expand was stopped by the custom event handler");
|
|
|
2103 |
return;
|
|
|
2104 |
}
|
|
|
2105 |
|
|
|
2106 |
if (!this.getEl()) {
|
|
|
2107 |
this.expanded = true;
|
|
|
2108 |
return;
|
|
|
2109 |
}
|
|
|
2110 |
|
|
|
2111 |
if (!this.childrenRendered) {
|
|
|
2112 |
this.logger.log("children not rendered yet");
|
|
|
2113 |
this.getChildrenEl().innerHTML = this.renderChildren();
|
|
|
2114 |
} else {
|
|
|
2115 |
this.logger.log("children already rendered");
|
|
|
2116 |
}
|
|
|
2117 |
|
|
|
2118 |
this.expanded = true;
|
|
|
2119 |
|
|
|
2120 |
this.updateIcon();
|
|
|
2121 |
|
|
|
2122 |
// this.getSpacer().title = this.getStateText();
|
|
|
2123 |
|
|
|
2124 |
// We do an extra check for children here because the lazy
|
|
|
2125 |
// load feature can expose nodes that have no children.
|
|
|
2126 |
|
|
|
2127 |
// if (!this.hasChildren()) {
|
|
|
2128 |
if (this.isLoading) {
|
|
|
2129 |
this.expanded = false;
|
|
|
2130 |
return;
|
|
|
2131 |
}
|
|
|
2132 |
|
|
|
2133 |
if (! this.multiExpand) {
|
|
|
2134 |
var sibs = this.getSiblings();
|
|
|
2135 |
for (var i=0; sibs && i<sibs.length; ++i) {
|
|
|
2136 |
if (sibs[i] != this && sibs[i].expanded) {
|
|
|
2137 |
sibs[i].collapse();
|
|
|
2138 |
}
|
|
|
2139 |
}
|
|
|
2140 |
}
|
|
|
2141 |
|
|
|
2142 |
this.showChildren();
|
|
|
2143 |
|
|
|
2144 |
ret = this.tree.fireEvent("expandComplete", this);
|
|
|
2145 |
},
|
|
|
2146 |
|
|
|
2147 |
updateIcon: function() {
|
|
|
2148 |
if (this.hasIcon) {
|
|
|
2149 |
var el = this.getToggleEl();
|
|
|
2150 |
if (el) {
|
|
|
2151 |
el.className = el.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi,this.getStyle());
|
|
|
2152 |
}
|
|
|
2153 |
}
|
|
|
2154 |
el = Dom.get('ygtvtableel' + this.index);
|
|
|
2155 |
if (el) {
|
|
|
2156 |
if (this.expanded) {
|
|
|
2157 |
Dom.replaceClass(el,'ygtv-collapsed','ygtv-expanded');
|
|
|
2158 |
} else {
|
|
|
2159 |
Dom.replaceClass(el,'ygtv-expanded','ygtv-collapsed');
|
|
|
2160 |
}
|
|
|
2161 |
}
|
|
|
2162 |
},
|
|
|
2163 |
|
|
|
2164 |
/**
|
|
|
2165 |
* Returns the css style name for the toggle
|
|
|
2166 |
* @method getStyle
|
|
|
2167 |
* @return {string} the css class for this node's toggle
|
|
|
2168 |
*/
|
|
|
2169 |
getStyle: function() {
|
|
|
2170 |
// this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
|
|
|
2171 |
if (this.isLoading) {
|
|
|
2172 |
this.logger.log("returning the loading icon");
|
|
|
2173 |
return "ygtvloading";
|
|
|
2174 |
} else {
|
|
|
2175 |
// location top or bottom, middle nodes also get the top style
|
|
|
2176 |
var loc = (this.nextSibling) ? "t" : "l";
|
|
|
2177 |
|
|
|
2178 |
// type p=plus(expand), m=minus(collapase), n=none(no children)
|
|
|
2179 |
var type = "n";
|
|
|
2180 |
if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
|
|
|
2181 |
// if (this.hasChildren(true)) {
|
|
|
2182 |
type = (this.expanded) ? "m" : "p";
|
|
|
2183 |
}
|
|
|
2184 |
|
|
|
2185 |
// this.logger.log("ygtv" + loc + type);
|
|
|
2186 |
return "ygtv" + loc + type;
|
|
|
2187 |
}
|
|
|
2188 |
},
|
|
|
2189 |
|
|
|
2190 |
/**
|
|
|
2191 |
* Returns the hover style for the icon
|
|
|
2192 |
* @return {string} the css class hover state
|
|
|
2193 |
* @method getHoverStyle
|
|
|
2194 |
*/
|
|
|
2195 |
getHoverStyle: function() {
|
|
|
2196 |
var s = this.getStyle();
|
|
|
2197 |
if (this.hasChildren(true) && !this.isLoading) {
|
|
|
2198 |
s += "h";
|
|
|
2199 |
}
|
|
|
2200 |
return s;
|
|
|
2201 |
},
|
|
|
2202 |
|
|
|
2203 |
/**
|
|
|
2204 |
* Recursively expands all of this node's children.
|
|
|
2205 |
* @method expandAll
|
|
|
2206 |
*/
|
|
|
2207 |
expandAll: function() {
|
|
|
2208 |
var l = this.children.length;
|
|
|
2209 |
for (var i=0;i<l;++i) {
|
|
|
2210 |
var c = this.children[i];
|
|
|
2211 |
if (c.isDynamic()) {
|
|
|
2212 |
this.logger.log("Not supported (lazy load + expand all)");
|
|
|
2213 |
break;
|
|
|
2214 |
} else if (! c.multiExpand) {
|
|
|
2215 |
this.logger.log("Not supported (no multi-expand + expand all)");
|
|
|
2216 |
break;
|
|
|
2217 |
} else {
|
|
|
2218 |
c.expand();
|
|
|
2219 |
c.expandAll();
|
|
|
2220 |
}
|
|
|
2221 |
}
|
|
|
2222 |
},
|
|
|
2223 |
|
|
|
2224 |
/**
|
|
|
2225 |
* Recursively collapses all of this node's children.
|
|
|
2226 |
* @method collapseAll
|
|
|
2227 |
*/
|
|
|
2228 |
collapseAll: function() {
|
|
|
2229 |
for (var i=0;i<this.children.length;++i) {
|
|
|
2230 |
this.children[i].collapse();
|
|
|
2231 |
this.children[i].collapseAll();
|
|
|
2232 |
}
|
|
|
2233 |
},
|
|
|
2234 |
|
|
|
2235 |
/**
|
|
|
2236 |
* Configures this node for dynamically obtaining the child data
|
|
|
2237 |
* when the node is first expanded. Calling it without the callback
|
|
|
2238 |
* will turn off dynamic load for the node.
|
|
|
2239 |
* @method setDynamicLoad
|
|
|
2240 |
* @param fmDataLoader {function} the function that will be used to get the data.
|
|
|
2241 |
* @param iconMode {int} configures the icon that is displayed when a dynamic
|
|
|
2242 |
* load node is expanded the first time without children. By default, the
|
|
|
2243 |
* "collapse" icon will be used. If set to 1, the leaf node icon will be
|
|
|
2244 |
* displayed.
|
|
|
2245 |
*/
|
|
|
2246 |
setDynamicLoad: function(fnDataLoader, iconMode) {
|
|
|
2247 |
if (fnDataLoader) {
|
|
|
2248 |
this.dataLoader = fnDataLoader;
|
|
|
2249 |
this._dynLoad = true;
|
|
|
2250 |
} else {
|
|
|
2251 |
this.dataLoader = null;
|
|
|
2252 |
this._dynLoad = false;
|
|
|
2253 |
}
|
|
|
2254 |
|
|
|
2255 |
if (iconMode) {
|
|
|
2256 |
this.iconMode = iconMode;
|
|
|
2257 |
}
|
|
|
2258 |
},
|
|
|
2259 |
|
|
|
2260 |
/**
|
|
|
2261 |
* Evaluates if this node is the root node of the tree
|
|
|
2262 |
* @method isRoot
|
|
|
2263 |
* @return {boolean} true if this is the root node
|
|
|
2264 |
*/
|
|
|
2265 |
isRoot: function() {
|
|
|
2266 |
return (this == this.tree.root);
|
|
|
2267 |
},
|
|
|
2268 |
|
|
|
2269 |
/**
|
|
|
2270 |
* Evaluates if this node's children should be loaded dynamically. Looks for
|
|
|
2271 |
* the property both in this instance and the root node. If the tree is
|
|
|
2272 |
* defined to load all children dynamically, the data callback function is
|
|
|
2273 |
* defined in the root node
|
|
|
2274 |
* @method isDynamic
|
|
|
2275 |
* @return {boolean} true if this node's children are to be loaded dynamically
|
|
|
2276 |
*/
|
|
|
2277 |
isDynamic: function() {
|
|
|
2278 |
if (this.isLeaf) {
|
|
|
2279 |
return false;
|
|
|
2280 |
} else {
|
|
|
2281 |
return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
|
|
|
2282 |
// this.logger.log("isDynamic: " + lazy);
|
|
|
2283 |
// return lazy;
|
|
|
2284 |
}
|
|
|
2285 |
},
|
|
|
2286 |
|
|
|
2287 |
/**
|
|
|
2288 |
* Returns the current icon mode. This refers to the way childless dynamic
|
|
|
2289 |
* load nodes appear (this comes into play only after the initial dynamic
|
|
|
2290 |
* load request produced no children).
|
|
|
2291 |
* @method getIconMode
|
|
|
2292 |
* @return {int} 0 for collapse style, 1 for leaf node style
|
|
|
2293 |
*/
|
|
|
2294 |
getIconMode: function() {
|
|
|
2295 |
return (this.iconMode || this.tree.root.iconMode);
|
|
|
2296 |
},
|
|
|
2297 |
|
|
|
2298 |
/**
|
|
|
2299 |
* Checks if this node has children. If this node is lazy-loading and the
|
|
|
2300 |
* children have not been rendered, we do not know whether or not there
|
|
|
2301 |
* are actual children. In most cases, we need to assume that there are
|
|
|
2302 |
* children (for instance, the toggle needs to show the expandable
|
|
|
2303 |
* presentation state). In other times we want to know if there are rendered
|
|
|
2304 |
* children. For the latter, "checkForLazyLoad" should be false.
|
|
|
2305 |
* @method hasChildren
|
|
|
2306 |
* @param checkForLazyLoad {boolean} should we check for unloaded children?
|
|
|
2307 |
* @return {boolean} true if this has children or if it might and we are
|
|
|
2308 |
* checking for this condition.
|
|
|
2309 |
*/
|
|
|
2310 |
hasChildren: function(checkForLazyLoad) {
|
|
|
2311 |
if (this.isLeaf) {
|
|
|
2312 |
return false;
|
|
|
2313 |
} else {
|
|
|
2314 |
return ( this.children.length > 0 ||
|
|
|
2315 |
(checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete)
|
|
|
2316 |
);
|
|
|
2317 |
}
|
|
|
2318 |
},
|
|
|
2319 |
|
|
|
2320 |
/**
|
|
|
2321 |
* Expands if node is collapsed, collapses otherwise.
|
|
|
2322 |
* @method toggle
|
|
|
2323 |
*/
|
|
|
2324 |
toggle: function() {
|
|
|
2325 |
if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
|
|
|
2326 |
if (this.expanded) { this.collapse(); } else { this.expand(); }
|
|
|
2327 |
}
|
|
|
2328 |
},
|
|
|
2329 |
|
|
|
2330 |
/**
|
|
|
2331 |
* Returns the markup for this node and its children.
|
|
|
2332 |
* @method getHtml
|
|
|
2333 |
* @return {string} the markup for this node and its expanded children.
|
|
|
2334 |
*/
|
|
|
2335 |
getHtml: function() {
|
|
|
2336 |
|
|
|
2337 |
this.childrenRendered = false;
|
|
|
2338 |
|
|
|
2339 |
return ['<div class="ygtvitem" id="' , this.getElId() , '">' ,this.getNodeHtml() , this.getChildrenHtml() ,'</div>'].join("");
|
|
|
2340 |
},
|
|
|
2341 |
|
|
|
2342 |
/**
|
|
|
2343 |
* Called when first rendering the tree. We always build the div that will
|
|
|
2344 |
* contain this nodes children, but we don't render the children themselves
|
|
|
2345 |
* unless this node is expanded.
|
|
|
2346 |
* @method getChildrenHtml
|
|
|
2347 |
* @return {string} the children container div html and any expanded children
|
|
|
2348 |
* @private
|
|
|
2349 |
*/
|
|
|
2350 |
getChildrenHtml: function() {
|
|
|
2351 |
|
|
|
2352 |
|
|
|
2353 |
var sb = [];
|
|
|
2354 |
sb[sb.length] = '<div class="ygtvchildren" id="' + this.getChildrenElId() + '"';
|
|
|
2355 |
|
|
|
2356 |
// This is a workaround for an IE rendering issue, the child div has layout
|
|
|
2357 |
// in IE, creating extra space if a leaf node is created with the expanded
|
|
|
2358 |
// property set to true.
|
|
|
2359 |
if (!this.expanded || !this.hasChildren()) {
|
|
|
2360 |
sb[sb.length] = ' style="display:none;"';
|
|
|
2361 |
}
|
|
|
2362 |
sb[sb.length] = '>';
|
|
|
2363 |
|
|
|
2364 |
// this.logger.log(["index", this.index,
|
|
|
2365 |
// "hasChildren", this.hasChildren(true),
|
|
|
2366 |
// "expanded", this.expanded,
|
|
|
2367 |
// "renderHidden", this.renderHidden,
|
|
|
2368 |
// "isDynamic", this.isDynamic()]);
|
|
|
2369 |
|
|
|
2370 |
// Don't render the actual child node HTML unless this node is expanded.
|
|
|
2371 |
if ( (this.hasChildren(true) && this.expanded) ||
|
|
|
2372 |
(this.renderHidden && !this.isDynamic()) ) {
|
|
|
2373 |
sb[sb.length] = this.renderChildren();
|
|
|
2374 |
}
|
|
|
2375 |
|
|
|
2376 |
sb[sb.length] = '</div>';
|
|
|
2377 |
|
|
|
2378 |
return sb.join("");
|
|
|
2379 |
},
|
|
|
2380 |
|
|
|
2381 |
/**
|
|
|
2382 |
* Generates the markup for the child nodes. This is not done until the node
|
|
|
2383 |
* is expanded.
|
|
|
2384 |
* @method renderChildren
|
|
|
2385 |
* @return {string} the html for this node's children
|
|
|
2386 |
* @private
|
|
|
2387 |
*/
|
|
|
2388 |
renderChildren: function() {
|
|
|
2389 |
|
|
|
2390 |
this.logger.log("rendering children for " + this.index);
|
|
|
2391 |
|
|
|
2392 |
var node = this;
|
|
|
2393 |
|
|
|
2394 |
if (this.isDynamic() && !this.dynamicLoadComplete) {
|
|
|
2395 |
this.isLoading = true;
|
|
|
2396 |
this.tree.locked = true;
|
|
|
2397 |
|
|
|
2398 |
if (this.dataLoader) {
|
|
|
2399 |
this.logger.log("Using dynamic loader defined for this node");
|
|
|
2400 |
|
|
|
2401 |
setTimeout(
|
|
|
2402 |
function() {
|
|
|
2403 |
node.dataLoader(node,
|
|
|
2404 |
function() {
|
|
|
2405 |
node.loadComplete();
|
|
|
2406 |
});
|
|
|
2407 |
}, 10);
|
|
|
2408 |
|
|
|
2409 |
} else if (this.tree.root.dataLoader) {
|
|
|
2410 |
this.logger.log("Using the tree-level dynamic loader");
|
|
|
2411 |
|
|
|
2412 |
setTimeout(
|
|
|
2413 |
function() {
|
|
|
2414 |
node.tree.root.dataLoader(node,
|
|
|
2415 |
function() {
|
|
|
2416 |
node.loadComplete();
|
|
|
2417 |
});
|
|
|
2418 |
}, 10);
|
|
|
2419 |
|
|
|
2420 |
} else {
|
|
|
2421 |
this.logger.log("no loader found");
|
|
|
2422 |
return "Error: data loader not found or not specified.";
|
|
|
2423 |
}
|
|
|
2424 |
|
|
|
2425 |
return "";
|
|
|
2426 |
|
|
|
2427 |
} else {
|
|
|
2428 |
return this.completeRender();
|
|
|
2429 |
}
|
|
|
2430 |
},
|
|
|
2431 |
|
|
|
2432 |
/**
|
|
|
2433 |
* Called when we know we have all the child data.
|
|
|
2434 |
* @method completeRender
|
|
|
2435 |
* @return {string} children html
|
|
|
2436 |
*/
|
|
|
2437 |
completeRender: function() {
|
|
|
2438 |
this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
|
|
|
2439 |
var sb = [];
|
|
|
2440 |
|
|
|
2441 |
for (var i=0; i < this.children.length; ++i) {
|
|
|
2442 |
// this.children[i].childrenRendered = false;
|
|
|
2443 |
sb[sb.length] = this.children[i].getHtml();
|
|
|
2444 |
}
|
|
|
2445 |
|
|
|
2446 |
this.childrenRendered = true;
|
|
|
2447 |
|
|
|
2448 |
return sb.join("");
|
|
|
2449 |
},
|
|
|
2450 |
|
|
|
2451 |
/**
|
|
|
2452 |
* Load complete is the callback function we pass to the data provider
|
|
|
2453 |
* in dynamic load situations.
|
|
|
2454 |
* @method loadComplete
|
|
|
2455 |
*/
|
|
|
2456 |
loadComplete: function() {
|
|
|
2457 |
this.logger.log(this.index + " loadComplete, children: " + this.children.length);
|
|
|
2458 |
this.getChildrenEl().innerHTML = this.completeRender();
|
|
|
2459 |
if (this.propagateHighlightDown) {
|
|
|
2460 |
if (this.highlightState === 1 && !this.tree.singleNodeHighlight) {
|
|
|
2461 |
for (var i = 0; i < this.children.length; i++) {
|
|
|
2462 |
this.children[i].highlight(true);
|
|
|
2463 |
}
|
|
|
2464 |
} else if (this.highlightState === 0 || this.tree.singleNodeHighlight) {
|
|
|
2465 |
for (i = 0; i < this.children.length; i++) {
|
|
|
2466 |
this.children[i].unhighlight(true);
|
|
|
2467 |
}
|
|
|
2468 |
} // if (highlighState == 2) leave child nodes with whichever highlight state they are set
|
|
|
2469 |
}
|
|
|
2470 |
|
|
|
2471 |
this.dynamicLoadComplete = true;
|
|
|
2472 |
this.isLoading = false;
|
|
|
2473 |
this.expand(true);
|
|
|
2474 |
this.tree.locked = false;
|
|
|
2475 |
},
|
|
|
2476 |
|
|
|
2477 |
/**
|
|
|
2478 |
* Returns this node's ancestor at the specified depth.
|
|
|
2479 |
* @method getAncestor
|
|
|
2480 |
* @param {int} depth the depth of the ancestor.
|
|
|
2481 |
* @return {Node} the ancestor
|
|
|
2482 |
*/
|
|
|
2483 |
getAncestor: function(depth) {
|
|
|
2484 |
if (depth >= this.depth || depth < 0) {
|
|
|
2485 |
this.logger.log("illegal getAncestor depth: " + depth);
|
|
|
2486 |
return null;
|
|
|
2487 |
}
|
|
|
2488 |
|
|
|
2489 |
var p = this.parent;
|
|
|
2490 |
|
|
|
2491 |
while (p.depth > depth) {
|
|
|
2492 |
p = p.parent;
|
|
|
2493 |
}
|
|
|
2494 |
|
|
|
2495 |
return p;
|
|
|
2496 |
},
|
|
|
2497 |
|
|
|
2498 |
/**
|
|
|
2499 |
* Returns the css class for the spacer at the specified depth for
|
|
|
2500 |
* this node. If this node's ancestor at the specified depth
|
|
|
2501 |
* has a next sibling the presentation is different than if it
|
|
|
2502 |
* does not have a next sibling
|
|
|
2503 |
* @method getDepthStyle
|
|
|
2504 |
* @param {int} depth the depth of the ancestor.
|
|
|
2505 |
* @return {string} the css class for the spacer
|
|
|
2506 |
*/
|
|
|
2507 |
getDepthStyle: function(depth) {
|
|
|
2508 |
return (this.getAncestor(depth).nextSibling) ?
|
|
|
2509 |
"ygtvdepthcell" : "ygtvblankdepthcell";
|
|
|
2510 |
},
|
|
|
2511 |
|
|
|
2512 |
/**
|
|
|
2513 |
* Get the markup for the node. This may be overrided so that we can
|
|
|
2514 |
* support different types of nodes.
|
|
|
2515 |
* @method getNodeHtml
|
|
|
2516 |
* @return {string} The HTML that will render this node.
|
|
|
2517 |
*/
|
|
|
2518 |
getNodeHtml: function() {
|
|
|
2519 |
this.logger.log("Generating html");
|
|
|
2520 |
var sb = [];
|
|
|
2521 |
|
|
|
2522 |
sb[sb.length] = '<table id="ygtvtableel' + this.index + '" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth' + this.depth;
|
|
|
2523 |
sb[sb.length] = ' ygtv-' + (this.expanded?'expanded':'collapsed');
|
|
|
2524 |
if (this.enableHighlight) {
|
|
|
2525 |
sb[sb.length] = ' ygtv-highlight' + this.highlightState;
|
|
|
2526 |
}
|
|
|
2527 |
if (this.className) {
|
|
|
2528 |
sb[sb.length] = ' ' + this.className;
|
|
|
2529 |
}
|
|
|
2530 |
sb[sb.length] = '"><tr class="ygtvrow">';
|
|
|
2531 |
|
|
|
2532 |
for (var i=0;i<this.depth;++i) {
|
|
|
2533 |
sb[sb.length] = '<td class="ygtvcell ' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
|
|
|
2534 |
}
|
|
|
2535 |
|
|
|
2536 |
if (this.hasIcon && this.hasChildren()) {
|
|
|
2537 |
sb[sb.length] = '<td id="' + this.getToggleElId();
|
|
|
2538 |
sb[sb.length] = '" class="ygtvcell ';
|
|
|
2539 |
sb[sb.length] = this.getStyle() ;
|
|
|
2540 |
sb[sb.length] = '"><a href="#" class="ygtvspacer"> </a></td>';
|
|
|
2541 |
} else {
|
|
|
2542 |
sb[sb.length] = '<td class="ygtvcell ' + this.getStyle() + '"><div class="ygtvspacer"></div></td>';
|
|
|
2543 |
}
|
|
|
2544 |
|
|
|
2545 |
sb[sb.length] = '<td id="' + this.contentElId;
|
|
|
2546 |
sb[sb.length] = '" class="ygtvcell ';
|
|
|
2547 |
sb[sb.length] = this.contentStyle + ' ygtvcontent" ';
|
|
|
2548 |
sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
|
|
|
2549 |
sb[sb.length] = ' >';
|
|
|
2550 |
sb[sb.length] = this.getContentHtml();
|
|
|
2551 |
sb[sb.length] = '</td></tr></table>';
|
|
|
2552 |
|
|
|
2553 |
return sb.join("");
|
|
|
2554 |
|
|
|
2555 |
},
|
|
|
2556 |
/**
|
|
|
2557 |
* Get the markup for the contents of the node. This is designed to be overrided so that we can
|
|
|
2558 |
* support different types of nodes.
|
|
|
2559 |
* @method getContentHtml
|
|
|
2560 |
* @return {string} The HTML that will render the content of this node.
|
|
|
2561 |
*/
|
|
|
2562 |
getContentHtml: function () {
|
|
|
2563 |
return "";
|
|
|
2564 |
},
|
|
|
2565 |
|
|
|
2566 |
/**
|
|
|
2567 |
* Regenerates the html for this node and its children. To be used when the
|
|
|
2568 |
* node is expanded and new children have been added.
|
|
|
2569 |
* @method refresh
|
|
|
2570 |
*/
|
|
|
2571 |
refresh: function() {
|
|
|
2572 |
// this.loadComplete();
|
|
|
2573 |
this.getChildrenEl().innerHTML = this.completeRender();
|
|
|
2574 |
|
|
|
2575 |
if (this.hasIcon) {
|
|
|
2576 |
var el = this.getToggleEl();
|
|
|
2577 |
if (el) {
|
|
|
2578 |
el.className = el.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());
|
|
|
2579 |
}
|
|
|
2580 |
}
|
|
|
2581 |
},
|
|
|
2582 |
|
|
|
2583 |
/**
|
|
|
2584 |
* Node toString
|
|
|
2585 |
* @method toString
|
|
|
2586 |
* @return {string} string representation of the node
|
|
|
2587 |
*/
|
|
|
2588 |
toString: function() {
|
|
|
2589 |
return this._type + " (" + this.index + ")";
|
|
|
2590 |
},
|
|
|
2591 |
/**
|
|
|
2592 |
* array of items that had the focus set on them
|
|
|
2593 |
* so that they can be cleaned when focus is lost
|
|
|
2594 |
* @property _focusHighlightedItems
|
|
|
2595 |
* @type Array of DOM elements
|
|
|
2596 |
* @private
|
|
|
2597 |
*/
|
|
|
2598 |
_focusHighlightedItems: [],
|
|
|
2599 |
/**
|
|
|
2600 |
* DOM element that actually got the browser focus
|
|
|
2601 |
* @property _focusedItem
|
|
|
2602 |
* @type DOM element
|
|
|
2603 |
* @private
|
|
|
2604 |
*/
|
|
|
2605 |
_focusedItem: null,
|
|
|
2606 |
|
|
|
2607 |
/**
|
|
|
2608 |
* Returns true if there are any elements in the node that can
|
|
|
2609 |
* accept the real actual browser focus
|
|
|
2610 |
* @method _canHaveFocus
|
|
|
2611 |
* @return {boolean} success
|
|
|
2612 |
* @private
|
|
|
2613 |
*/
|
|
|
2614 |
_canHaveFocus: function() {
|
|
|
2615 |
return this.getEl().getElementsByTagName('a').length > 0;
|
|
|
2616 |
},
|
|
|
2617 |
/**
|
|
|
2618 |
* Removes the focus of previously selected Node
|
|
|
2619 |
* @method _removeFocus
|
|
|
2620 |
* @private
|
|
|
2621 |
*/
|
|
|
2622 |
_removeFocus:function () {
|
|
|
2623 |
if (this._focusedItem) {
|
|
|
2624 |
Event.removeListener(this._focusedItem,'blur');
|
|
|
2625 |
this._focusedItem = null;
|
|
|
2626 |
}
|
|
|
2627 |
var el;
|
|
|
2628 |
while ((el = this._focusHighlightedItems.shift())) { // yes, it is meant as an assignment, really
|
|
|
2629 |
Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
|
|
|
2630 |
}
|
|
|
2631 |
},
|
|
|
2632 |
/**
|
|
|
2633 |
* Sets the focus on the node element.
|
|
|
2634 |
* It will only be able to set the focus on nodes that have anchor elements in it.
|
|
|
2635 |
* Toggle or branch icons have anchors and can be focused on.
|
|
|
2636 |
* If will fail in nodes that have no anchor
|
|
|
2637 |
* @method focus
|
|
|
2638 |
* @return {boolean} success
|
|
|
2639 |
*/
|
|
|
2640 |
focus: function () {
|
|
|
2641 |
var focused = false, self = this;
|
|
|
2642 |
|
|
|
2643 |
if (this.tree.currentFocus) {
|
|
|
2644 |
this.tree.currentFocus._removeFocus();
|
|
|
2645 |
}
|
|
|
2646 |
|
|
|
2647 |
var expandParent = function (node) {
|
|
|
2648 |
if (node.parent) {
|
|
|
2649 |
expandParent(node.parent);
|
|
|
2650 |
node.parent.expand();
|
|
|
2651 |
}
|
|
|
2652 |
};
|
|
|
2653 |
expandParent(this);
|
|
|
2654 |
|
|
|
2655 |
Dom.getElementsBy (
|
|
|
2656 |
function (el) {
|
|
|
2657 |
return (/ygtv(([tl][pmn]h?)|(content))/).test(el.className);
|
|
|
2658 |
} ,
|
|
|
2659 |
'td' ,
|
|
|
2660 |
self.getEl().firstChild ,
|
|
|
2661 |
function (el) {
|
|
|
2662 |
Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
|
|
|
2663 |
if (!focused) {
|
|
|
2664 |
var aEl = el.getElementsByTagName('a');
|
|
|
2665 |
if (aEl.length) {
|
|
|
2666 |
aEl = aEl[0];
|
|
|
2667 |
aEl.focus();
|
|
|
2668 |
self._focusedItem = aEl;
|
|
|
2669 |
Event.on(aEl,'blur',function () {
|
|
|
2670 |
self.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
|
|
|
2671 |
self.tree.currentFocus = null;
|
|
|
2672 |
self._removeFocus();
|
|
|
2673 |
});
|
|
|
2674 |
focused = true;
|
|
|
2675 |
}
|
|
|
2676 |
}
|
|
|
2677 |
self._focusHighlightedItems.push(el);
|
|
|
2678 |
}
|
|
|
2679 |
);
|
|
|
2680 |
if (focused) {
|
|
|
2681 |
this.tree.fireEvent('focusChanged',{oldNode:this.tree.currentFocus,newNode:this});
|
|
|
2682 |
this.tree.currentFocus = this;
|
|
|
2683 |
} else {
|
|
|
2684 |
this.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
|
|
|
2685 |
this.tree.currentFocus = null;
|
|
|
2686 |
this._removeFocus();
|
|
|
2687 |
}
|
|
|
2688 |
return focused;
|
|
|
2689 |
},
|
|
|
2690 |
|
|
|
2691 |
/**
|
|
|
2692 |
* Count of nodes in a branch
|
|
|
2693 |
* @method getNodeCount
|
|
|
2694 |
* @return {int} number of nodes in the branch
|
|
|
2695 |
*/
|
|
|
2696 |
getNodeCount: function() {
|
|
|
2697 |
for (var i = 0, count = 0;i< this.children.length;i++) {
|
|
|
2698 |
count += this.children[i].getNodeCount();
|
|
|
2699 |
}
|
|
|
2700 |
return count + 1;
|
|
|
2701 |
},
|
|
|
2702 |
|
|
|
2703 |
/**
|
|
|
2704 |
* Returns an object which could be used to build a tree out of this node and its children.
|
|
|
2705 |
* It can be passed to the tree constructor to reproduce this node as a tree.
|
|
|
2706 |
* It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
|
|
|
2707 |
* @method getNodeDefinition
|
|
|
2708 |
* @return {Object | false} definition of the tree or false if the node or any children is defined as dynamic
|
|
|
2709 |
*/
|
|
|
2710 |
getNodeDefinition: function() {
|
|
|
2711 |
|
|
|
2712 |
if (this.isDynamic()) { return false; }
|
|
|
2713 |
|
|
|
2714 |
var def, defs = Lang.merge(this.data), children = [];
|
|
|
2715 |
|
|
|
2716 |
|
|
|
2717 |
|
|
|
2718 |
if (this.expanded) {defs.expanded = this.expanded; }
|
|
|
2719 |
if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
|
|
|
2720 |
if (this.renderHidden) { defs.renderHidden = this.renderHidden; }
|
|
|
2721 |
if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
|
|
|
2722 |
if (this.nowrap) { defs.nowrap = this.nowrap; }
|
|
|
2723 |
if (this.className) { defs.className = this.className; }
|
|
|
2724 |
if (this.editable) { defs.editable = this.editable; }
|
|
|
2725 |
if (!this.enableHighlight) { defs.enableHighlight = this.enableHighlight; }
|
|
|
2726 |
if (this.highlightState) { defs.highlightState = this.highlightState; }
|
|
|
2727 |
if (this.propagateHighlightUp) { defs.propagateHighlightUp = this.propagateHighlightUp; }
|
|
|
2728 |
if (this.propagateHighlightDown) { defs.propagateHighlightDown = this.propagateHighlightDown; }
|
|
|
2729 |
defs.type = this._type;
|
|
|
2730 |
|
|
|
2731 |
|
|
|
2732 |
|
|
|
2733 |
for (var i = 0; i < this.children.length;i++) {
|
|
|
2734 |
def = this.children[i].getNodeDefinition();
|
|
|
2735 |
if (def === false) { return false;}
|
|
|
2736 |
children.push(def);
|
|
|
2737 |
}
|
|
|
2738 |
if (children.length) { defs.children = children; }
|
|
|
2739 |
return defs;
|
|
|
2740 |
},
|
|
|
2741 |
|
|
|
2742 |
|
|
|
2743 |
/**
|
|
|
2744 |
* Generates the link that will invoke this node's toggle method
|
|
|
2745 |
* @method getToggleLink
|
|
|
2746 |
* @return {string} the javascript url for toggling this node
|
|
|
2747 |
*/
|
|
|
2748 |
getToggleLink: function() {
|
|
|
2749 |
return 'return false;';
|
|
|
2750 |
},
|
|
|
2751 |
|
|
|
2752 |
/**
|
|
|
2753 |
* Sets the value of property for this node and all loaded descendants.
|
|
|
2754 |
* Only public and defined properties can be set, not methods.
|
|
|
2755 |
* Values for unknown properties will be assigned to the refNode.data object
|
|
|
2756 |
* @method setNodesProperty
|
|
|
2757 |
* @param name {string} Name of the property to be set
|
|
|
2758 |
* @param value {any} value to be set
|
|
|
2759 |
* @param refresh {boolean} if present and true, it does a refresh
|
|
|
2760 |
*/
|
|
|
2761 |
setNodesProperty: function(name, value, refresh) {
|
|
|
2762 |
if (name.charAt(0) != '_' && !Lang.isUndefined(this[name]) && !Lang.isFunction(this[name]) ) {
|
|
|
2763 |
this[name] = value;
|
|
|
2764 |
} else {
|
|
|
2765 |
this.data[name] = value;
|
|
|
2766 |
}
|
|
|
2767 |
for (var i = 0; i < this.children.length;i++) {
|
|
|
2768 |
this.children[i].setNodesProperty(name,value);
|
|
|
2769 |
}
|
|
|
2770 |
if (refresh) {
|
|
|
2771 |
this.refresh();
|
|
|
2772 |
}
|
|
|
2773 |
},
|
|
|
2774 |
/**
|
|
|
2775 |
* Toggles the highlighted state of a Node
|
|
|
2776 |
* @method toggleHighlight
|
|
|
2777 |
*/
|
|
|
2778 |
toggleHighlight: function() {
|
|
|
2779 |
if (this.enableHighlight) {
|
|
|
2780 |
// unhighlights only if fully highligthed. For not or partially highlighted it will highlight
|
|
|
2781 |
if (this.highlightState == 1) {
|
|
|
2782 |
this.unhighlight();
|
|
|
2783 |
} else {
|
|
|
2784 |
this.highlight();
|
|
|
2785 |
}
|
|
|
2786 |
}
|
|
|
2787 |
},
|
|
|
2788 |
|
|
|
2789 |
/**
|
|
|
2790 |
* Turns highlighting on node.
|
|
|
2791 |
* @method highlight
|
|
|
2792 |
* @param _silent {boolean} optional, don't fire the highlightEvent
|
|
|
2793 |
*/
|
|
|
2794 |
highlight: function(_silent) {
|
|
|
2795 |
if (this.enableHighlight) {
|
|
|
2796 |
if (this.tree.singleNodeHighlight) {
|
|
|
2797 |
if (this.tree._currentlyHighlighted) {
|
|
|
2798 |
this.tree._currentlyHighlighted.unhighlight(_silent);
|
|
|
2799 |
}
|
|
|
2800 |
this.tree._currentlyHighlighted = this;
|
|
|
2801 |
}
|
|
|
2802 |
this.highlightState = 1;
|
|
|
2803 |
this._setHighlightClassName();
|
|
|
2804 |
if (!this.tree.singleNodeHighlight) {
|
|
|
2805 |
if (this.propagateHighlightDown) {
|
|
|
2806 |
for (var i = 0;i < this.children.length;i++) {
|
|
|
2807 |
this.children[i].highlight(true);
|
|
|
2808 |
}
|
|
|
2809 |
}
|
|
|
2810 |
if (this.propagateHighlightUp) {
|
|
|
2811 |
if (this.parent) {
|
|
|
2812 |
this.parent._childrenHighlighted();
|
|
|
2813 |
}
|
|
|
2814 |
}
|
|
|
2815 |
}
|
|
|
2816 |
if (!_silent) {
|
|
|
2817 |
this.tree.fireEvent('highlightEvent',this);
|
|
|
2818 |
}
|
|
|
2819 |
}
|
|
|
2820 |
},
|
|
|
2821 |
/**
|
|
|
2822 |
* Turns highlighting off a node.
|
|
|
2823 |
* @method unhighlight
|
|
|
2824 |
* @param _silent {boolean} optional, don't fire the highlightEvent
|
|
|
2825 |
*/
|
|
|
2826 |
unhighlight: function(_silent) {
|
|
|
2827 |
if (this.enableHighlight) {
|
|
|
2828 |
// might have checked singleNodeHighlight but it wouldn't really matter either way
|
|
|
2829 |
this.tree._currentlyHighlighted = null;
|
|
|
2830 |
this.highlightState = 0;
|
|
|
2831 |
this._setHighlightClassName();
|
|
|
2832 |
if (!this.tree.singleNodeHighlight) {
|
|
|
2833 |
if (this.propagateHighlightDown) {
|
|
|
2834 |
for (var i = 0;i < this.children.length;i++) {
|
|
|
2835 |
this.children[i].unhighlight(true);
|
|
|
2836 |
}
|
|
|
2837 |
}
|
|
|
2838 |
if (this.propagateHighlightUp) {
|
|
|
2839 |
if (this.parent) {
|
|
|
2840 |
this.parent._childrenHighlighted();
|
|
|
2841 |
}
|
|
|
2842 |
}
|
|
|
2843 |
}
|
|
|
2844 |
if (!_silent) {
|
|
|
2845 |
this.tree.fireEvent('highlightEvent',this);
|
|
|
2846 |
}
|
|
|
2847 |
}
|
|
|
2848 |
},
|
|
|
2849 |
/**
|
|
|
2850 |
* Checks whether all or part of the children of a node are highlighted and
|
|
|
2851 |
* sets the node highlight to full, none or partial highlight.
|
|
|
2852 |
* If set to propagate it will further call the parent
|
|
|
2853 |
* @method _childrenHighlighted
|
|
|
2854 |
* @private
|
|
|
2855 |
*/
|
|
|
2856 |
_childrenHighlighted: function() {
|
|
|
2857 |
var yes = false, no = false;
|
|
|
2858 |
if (this.enableHighlight) {
|
|
|
2859 |
for (var i = 0;i < this.children.length;i++) {
|
|
|
2860 |
switch(this.children[i].highlightState) {
|
|
|
2861 |
case 0:
|
|
|
2862 |
no = true;
|
|
|
2863 |
break;
|
|
|
2864 |
case 1:
|
|
|
2865 |
yes = true;
|
|
|
2866 |
break;
|
|
|
2867 |
case 2:
|
|
|
2868 |
yes = no = true;
|
|
|
2869 |
break;
|
|
|
2870 |
}
|
|
|
2871 |
}
|
|
|
2872 |
if (yes && no) {
|
|
|
2873 |
this.highlightState = 2;
|
|
|
2874 |
} else if (yes) {
|
|
|
2875 |
this.highlightState = 1;
|
|
|
2876 |
} else {
|
|
|
2877 |
this.highlightState = 0;
|
|
|
2878 |
}
|
|
|
2879 |
this._setHighlightClassName();
|
|
|
2880 |
if (this.propagateHighlightUp) {
|
|
|
2881 |
if (this.parent) {
|
|
|
2882 |
this.parent._childrenHighlighted();
|
|
|
2883 |
}
|
|
|
2884 |
}
|
|
|
2885 |
}
|
|
|
2886 |
},
|
|
|
2887 |
|
|
|
2888 |
/**
|
|
|
2889 |
* Changes the classNames on the toggle and content containers to reflect the current highlighting
|
|
|
2890 |
* @method _setHighlightClassName
|
|
|
2891 |
* @private
|
|
|
2892 |
*/
|
|
|
2893 |
_setHighlightClassName: function() {
|
|
|
2894 |
var el = Dom.get('ygtvtableel' + this.index);
|
|
|
2895 |
if (el) {
|
|
|
2896 |
el.className = el.className.replace(/\bygtv-highlight\d\b/gi,'ygtv-highlight' + this.highlightState);
|
|
|
2897 |
}
|
|
|
2898 |
}
|
|
|
2899 |
|
|
|
2900 |
};
|
|
|
2901 |
|
|
|
2902 |
YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
|
|
|
2903 |
})();
|
|
|
2904 |
/**
|
|
|
2905 |
* A custom YAHOO.widget.Node that handles the unique nature of
|
|
|
2906 |
* the virtual, presentationless root node.
|
|
|
2907 |
* @namespace YAHOO.widget
|
|
|
2908 |
* @class RootNode
|
|
|
2909 |
* @extends YAHOO.widget.Node
|
|
|
2910 |
* @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
|
|
|
2911 |
* @constructor
|
|
|
2912 |
*/
|
|
|
2913 |
YAHOO.widget.RootNode = function(oTree) {
|
|
|
2914 |
// Initialize the node with null params. The root node is a
|
|
|
2915 |
// special case where the node has no presentation. So we have
|
|
|
2916 |
// to alter the standard properties a bit.
|
|
|
2917 |
this.init(null, null, true);
|
|
|
2918 |
|
|
|
2919 |
/*
|
|
|
2920 |
* For the root node, we get the tree reference from as a param
|
|
|
2921 |
* to the constructor instead of from the parent element.
|
|
|
2922 |
*/
|
|
|
2923 |
this.tree = oTree;
|
|
|
2924 |
};
|
|
|
2925 |
|
|
|
2926 |
YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
|
|
|
2927 |
|
|
|
2928 |
/**
|
|
|
2929 |
* The node type
|
|
|
2930 |
* @property _type
|
|
|
2931 |
* @type string
|
|
|
2932 |
* @private
|
|
|
2933 |
* @default "RootNode"
|
|
|
2934 |
*/
|
|
|
2935 |
_type: "RootNode",
|
|
|
2936 |
|
|
|
2937 |
// overrides YAHOO.widget.Node
|
|
|
2938 |
getNodeHtml: function() {
|
|
|
2939 |
return "";
|
|
|
2940 |
},
|
|
|
2941 |
|
|
|
2942 |
toString: function() {
|
|
|
2943 |
return this._type;
|
|
|
2944 |
},
|
|
|
2945 |
|
|
|
2946 |
loadComplete: function() {
|
|
|
2947 |
this.tree.draw();
|
|
|
2948 |
},
|
|
|
2949 |
|
|
|
2950 |
/**
|
|
|
2951 |
* Count of nodes in tree.
|
|
|
2952 |
* It overrides Nodes.getNodeCount because the root node should not be counted.
|
|
|
2953 |
* @method getNodeCount
|
|
|
2954 |
* @return {int} number of nodes in the tree
|
|
|
2955 |
*/
|
|
|
2956 |
getNodeCount: function() {
|
|
|
2957 |
for (var i = 0, count = 0;i< this.children.length;i++) {
|
|
|
2958 |
count += this.children[i].getNodeCount();
|
|
|
2959 |
}
|
|
|
2960 |
return count;
|
|
|
2961 |
},
|
|
|
2962 |
|
|
|
2963 |
/**
|
|
|
2964 |
* Returns an object which could be used to build a tree out of this node and its children.
|
|
|
2965 |
* It can be passed to the tree constructor to reproduce this node as a tree.
|
|
|
2966 |
* Since the RootNode is automatically created by treeView,
|
|
|
2967 |
* its own definition is excluded from the returned node definition
|
|
|
2968 |
* which only contains its children.
|
|
|
2969 |
* @method getNodeDefinition
|
|
|
2970 |
* @return {Object | false} definition of the tree or false if any child node is defined as dynamic
|
|
|
2971 |
*/
|
|
|
2972 |
getNodeDefinition: function() {
|
|
|
2973 |
|
|
|
2974 |
for (var def, defs = [], i = 0; i < this.children.length;i++) {
|
|
|
2975 |
def = this.children[i].getNodeDefinition();
|
|
|
2976 |
if (def === false) { return false;}
|
|
|
2977 |
defs.push(def);
|
|
|
2978 |
}
|
|
|
2979 |
return defs;
|
|
|
2980 |
},
|
|
|
2981 |
|
|
|
2982 |
collapse: function() {},
|
|
|
2983 |
expand: function() {},
|
|
|
2984 |
getSiblings: function() { return null; },
|
|
|
2985 |
focus: function () {}
|
|
|
2986 |
|
|
|
2987 |
});
|
|
|
2988 |
(function () {
|
|
|
2989 |
var Dom = YAHOO.util.Dom,
|
|
|
2990 |
Lang = YAHOO.lang,
|
|
|
2991 |
Event = YAHOO.util.Event;
|
|
|
2992 |
/**
|
|
|
2993 |
* The default node presentation. The first parameter should be
|
|
|
2994 |
* either a string that will be used as the node's label, or an object
|
|
|
2995 |
* that has at least a string property called label. By default, clicking the
|
|
|
2996 |
* label will toggle the expanded/collapsed state of the node. By
|
|
|
2997 |
* setting the href property of the instance, this behavior can be
|
|
|
2998 |
* changed so that the label will go to the specified href.
|
|
|
2999 |
* @namespace YAHOO.widget
|
|
|
3000 |
* @class TextNode
|
|
|
3001 |
* @extends YAHOO.widget.Node
|
|
|
3002 |
* @constructor
|
|
|
3003 |
* @param oData {object} a string or object containing the data that will
|
|
|
3004 |
* be used to render this node.
|
|
|
3005 |
* Providing a string is the same as providing an object with a single property named label.
|
|
|
3006 |
* All values in the oData will be used to set equally named properties in the node
|
|
|
3007 |
* as long as the node does have such properties, they are not undefined, private or functions.
|
|
|
3008 |
* All attributes are made available in noderef.data, which
|
|
|
3009 |
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
|
|
|
3010 |
* can be used to retrieve a node by one of the attributes.
|
|
|
3011 |
* @param oParent {YAHOO.widget.Node} this node's parent node
|
|
|
3012 |
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
|
|
|
3013 |
*/
|
|
|
3014 |
YAHOO.widget.TextNode = function(oData, oParent, expanded) {
|
|
|
3015 |
|
|
|
3016 |
if (oData) {
|
|
|
3017 |
if (Lang.isString(oData)) {
|
|
|
3018 |
oData = { label: oData };
|
|
|
3019 |
}
|
|
|
3020 |
this.init(oData, oParent, expanded);
|
|
|
3021 |
this.setUpLabel(oData);
|
|
|
3022 |
}
|
|
|
3023 |
|
|
|
3024 |
this.logger = new YAHOO.widget.LogWriter(this.toString());
|
|
|
3025 |
};
|
|
|
3026 |
|
|
|
3027 |
YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
|
|
|
3028 |
|
|
|
3029 |
/**
|
|
|
3030 |
* The CSS class for the label href. Defaults to ygtvlabel, but can be
|
|
|
3031 |
* overridden to provide a custom presentation for a specific node.
|
|
|
3032 |
* @property labelStyle
|
|
|
3033 |
* @type string
|
|
|
3034 |
*/
|
|
|
3035 |
labelStyle: "ygtvlabel",
|
|
|
3036 |
|
|
|
3037 |
/**
|
|
|
3038 |
* The derived element id of the label for this node
|
|
|
3039 |
* @property labelElId
|
|
|
3040 |
* @type string
|
|
|
3041 |
*/
|
|
|
3042 |
labelElId: null,
|
|
|
3043 |
|
|
|
3044 |
/**
|
|
|
3045 |
* The text for the label. It is assumed that the oData parameter will
|
|
|
3046 |
* either be a string that will be used as the label, or an object that
|
|
|
3047 |
* has a property called "label" that we will use.
|
|
|
3048 |
* @property label
|
|
|
3049 |
* @type string
|
|
|
3050 |
*/
|
|
|
3051 |
label: null,
|
|
|
3052 |
|
|
|
3053 |
/**
|
|
|
3054 |
* The text for the title (tooltip) for the label element
|
|
|
3055 |
* @property title
|
|
|
3056 |
* @type string
|
|
|
3057 |
*/
|
|
|
3058 |
title: null,
|
|
|
3059 |
|
|
|
3060 |
/**
|
|
|
3061 |
* The href for the node's label. If one is not specified, the href will
|
|
|
3062 |
* be set so that it toggles the node.
|
|
|
3063 |
* @property href
|
|
|
3064 |
* @type string
|
|
|
3065 |
*/
|
|
|
3066 |
href: null,
|
|
|
3067 |
|
|
|
3068 |
/**
|
|
|
3069 |
* The label href target, defaults to current window
|
|
|
3070 |
* @property target
|
|
|
3071 |
* @type string
|
|
|
3072 |
*/
|
|
|
3073 |
target: "_self",
|
|
|
3074 |
|
|
|
3075 |
/**
|
|
|
3076 |
* The node type
|
|
|
3077 |
* @property _type
|
|
|
3078 |
* @private
|
|
|
3079 |
* @type string
|
|
|
3080 |
* @default "TextNode"
|
|
|
3081 |
*/
|
|
|
3082 |
_type: "TextNode",
|
|
|
3083 |
|
|
|
3084 |
|
|
|
3085 |
/**
|
|
|
3086 |
* Sets up the node label
|
|
|
3087 |
* @method setUpLabel
|
|
|
3088 |
* @param oData string containing the label, or an object with a label property
|
|
|
3089 |
*/
|
|
|
3090 |
setUpLabel: function(oData) {
|
|
|
3091 |
|
|
|
3092 |
if (Lang.isString(oData)) {
|
|
|
3093 |
oData = {
|
|
|
3094 |
label: oData
|
|
|
3095 |
};
|
|
|
3096 |
} else {
|
|
|
3097 |
if (oData.style) {
|
|
|
3098 |
this.labelStyle = oData.style;
|
|
|
3099 |
}
|
|
|
3100 |
}
|
|
|
3101 |
|
|
|
3102 |
this.label = oData.label;
|
|
|
3103 |
|
|
|
3104 |
this.labelElId = "ygtvlabelel" + this.index;
|
|
|
3105 |
|
|
|
3106 |
},
|
|
|
3107 |
|
|
|
3108 |
/**
|
|
|
3109 |
* Returns the label element
|
|
|
3110 |
* @for YAHOO.widget.TextNode
|
|
|
3111 |
* @method getLabelEl
|
|
|
3112 |
* @return {object} the element
|
|
|
3113 |
*/
|
|
|
3114 |
getLabelEl: function() {
|
|
|
3115 |
return Dom.get(this.labelElId);
|
|
|
3116 |
},
|
|
|
3117 |
|
|
|
3118 |
// overrides YAHOO.widget.Node
|
|
|
3119 |
getContentHtml: function() {
|
|
|
3120 |
var sb = [];
|
|
|
3121 |
sb[sb.length] = this.href ? '<a' : '<span';
|
|
|
3122 |
sb[sb.length] = ' id="' + Lang.escapeHTML(this.labelElId) + '"';
|
|
|
3123 |
sb[sb.length] = ' class="' + Lang.escapeHTML(this.labelStyle) + '"';
|
|
|
3124 |
if (this.href) {
|
|
|
3125 |
sb[sb.length] = ' href="' + Lang.escapeHTML(this.href) + '"';
|
|
|
3126 |
sb[sb.length] = ' target="' + Lang.escapeHTML(this.target) + '"';
|
|
|
3127 |
}
|
|
|
3128 |
if (this.title) {
|
|
|
3129 |
sb[sb.length] = ' title="' + Lang.escapeHTML(this.title) + '"';
|
|
|
3130 |
}
|
|
|
3131 |
sb[sb.length] = ' >';
|
|
|
3132 |
sb[sb.length] = Lang.escapeHTML(this.label);
|
|
|
3133 |
sb[sb.length] = this.href?'</a>':'</span>';
|
|
|
3134 |
return sb.join("");
|
|
|
3135 |
},
|
|
|
3136 |
|
|
|
3137 |
|
|
|
3138 |
|
|
|
3139 |
/**
|
|
|
3140 |
* Returns an object which could be used to build a tree out of this node and its children.
|
|
|
3141 |
* It can be passed to the tree constructor to reproduce this node as a tree.
|
|
|
3142 |
* It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
|
|
|
3143 |
* @method getNodeDefinition
|
|
|
3144 |
* @return {Object | false} definition of the tree or false if this node or any descendant is defined as dynamic
|
|
|
3145 |
*/
|
|
|
3146 |
getNodeDefinition: function() {
|
|
|
3147 |
var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
|
|
|
3148 |
if (def === false) { return false; }
|
|
|
3149 |
|
|
|
3150 |
// Node specific properties
|
|
|
3151 |
def.label = this.label;
|
|
|
3152 |
if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
|
|
|
3153 |
if (this.title) { def.title = this.title; }
|
|
|
3154 |
if (this.href) { def.href = this.href; }
|
|
|
3155 |
if (this.target != '_self') { def.target = this.target; }
|
|
|
3156 |
|
|
|
3157 |
return def;
|
|
|
3158 |
|
|
|
3159 |
},
|
|
|
3160 |
|
|
|
3161 |
toString: function() {
|
|
|
3162 |
return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
|
|
|
3163 |
},
|
|
|
3164 |
|
|
|
3165 |
// deprecated
|
|
|
3166 |
onLabelClick: function() {
|
|
|
3167 |
return false;
|
|
|
3168 |
},
|
|
|
3169 |
refresh: function() {
|
|
|
3170 |
YAHOO.widget.TextNode.superclass.refresh.call(this);
|
|
|
3171 |
var label = this.getLabelEl();
|
|
|
3172 |
label.innerHTML = this.label;
|
|
|
3173 |
if (label.tagName.toUpperCase() == 'A') {
|
|
|
3174 |
label.href = this.href;
|
|
|
3175 |
label.target = this.target;
|
|
|
3176 |
}
|
|
|
3177 |
}
|
|
|
3178 |
|
|
|
3179 |
|
|
|
3180 |
|
|
|
3181 |
|
|
|
3182 |
});
|
|
|
3183 |
})();
|
|
|
3184 |
/**
|
|
|
3185 |
* A menu-specific implementation that differs from TextNode in that only
|
|
|
3186 |
* one sibling can be expanded at a time.
|
|
|
3187 |
* @namespace YAHOO.widget
|
|
|
3188 |
* @class MenuNode
|
|
|
3189 |
* @extends YAHOO.widget.TextNode
|
|
|
3190 |
* @param oData {object} a string or object containing the data that will
|
|
|
3191 |
* be used to render this node.
|
|
|
3192 |
* Providing a string is the same as providing an object with a single property named label.
|
|
|
3193 |
* All values in the oData will be used to set equally named properties in the node
|
|
|
3194 |
* as long as the node does have such properties, they are not undefined, private or functions.
|
|
|
3195 |
* All attributes are made available in noderef.data, which
|
|
|
3196 |
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
|
|
|
3197 |
* can be used to retrieve a node by one of the attributes.
|
|
|
3198 |
* @param oParent {YAHOO.widget.Node} this node's parent node
|
|
|
3199 |
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
|
|
|
3200 |
* @constructor
|
|
|
3201 |
*/
|
|
|
3202 |
YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
|
|
|
3203 |
YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
|
|
|
3204 |
|
|
|
3205 |
/*
|
|
|
3206 |
* Menus usually allow only one branch to be open at a time.
|
|
|
3207 |
*/
|
|
|
3208 |
this.multiExpand = false;
|
|
|
3209 |
|
|
|
3210 |
};
|
|
|
3211 |
|
|
|
3212 |
YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
|
|
|
3213 |
|
|
|
3214 |
/**
|
|
|
3215 |
* The node type
|
|
|
3216 |
* @property _type
|
|
|
3217 |
* @private
|
|
|
3218 |
* @default "MenuNode"
|
|
|
3219 |
*/
|
|
|
3220 |
_type: "MenuNode"
|
|
|
3221 |
|
|
|
3222 |
});
|
|
|
3223 |
(function () {
|
|
|
3224 |
var Dom = YAHOO.util.Dom,
|
|
|
3225 |
Lang = YAHOO.lang,
|
|
|
3226 |
Event = YAHOO.util.Event;
|
|
|
3227 |
|
|
|
3228 |
/**
|
|
|
3229 |
* This implementation takes either a string or object for the
|
|
|
3230 |
* oData argument. If is it a string, it will use it for the display
|
|
|
3231 |
* of this node (and it can contain any html code). If the parameter
|
|
|
3232 |
* is an object,it looks for a parameter called "html" that will be
|
|
|
3233 |
* used for this node's display.
|
|
|
3234 |
* @namespace YAHOO.widget
|
|
|
3235 |
* @class HTMLNode
|
|
|
3236 |
* @extends YAHOO.widget.Node
|
|
|
3237 |
* @constructor
|
|
|
3238 |
* @param oData {object} a string or object containing the data that will
|
|
|
3239 |
* be used to render this node.
|
|
|
3240 |
* Providing a string is the same as providing an object with a single property named html.
|
|
|
3241 |
* All values in the oData will be used to set equally named properties in the node
|
|
|
3242 |
* as long as the node does have such properties, they are not undefined, private or functions.
|
|
|
3243 |
* All other attributes are made available in noderef.data, which
|
|
|
3244 |
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
|
|
|
3245 |
* can be used to retrieve a node by one of the attributes.
|
|
|
3246 |
* @param oParent {YAHOO.widget.Node} this node's parent node
|
|
|
3247 |
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
|
|
|
3248 |
* @param hasIcon {boolean} specifies whether or not leaf nodes should
|
|
|
3249 |
* be rendered with or without a horizontal line and/or toggle icon. If the icon
|
|
|
3250 |
* is not displayed, the content fills the space it would have occupied.
|
|
|
3251 |
* This option operates independently of the leaf node presentation logic
|
|
|
3252 |
* for dynamic nodes.
|
|
|
3253 |
* (deprecated; use oData.hasIcon)
|
|
|
3254 |
*/
|
|
|
3255 |
var HN = function(oData, oParent, expanded, hasIcon) {
|
|
|
3256 |
if (oData) {
|
|
|
3257 |
this.init(oData, oParent, expanded);
|
|
|
3258 |
this.initContent(oData, hasIcon);
|
|
|
3259 |
}
|
|
|
3260 |
};
|
|
|
3261 |
|
|
|
3262 |
|
|
|
3263 |
YAHOO.widget.HTMLNode = HN;
|
|
|
3264 |
YAHOO.extend(HN, YAHOO.widget.Node, {
|
|
|
3265 |
|
|
|
3266 |
/**
|
|
|
3267 |
* The CSS class for the html content container. Defaults to ygtvhtml, but
|
|
|
3268 |
* can be overridden to provide a custom presentation for a specific node.
|
|
|
3269 |
* @property contentStyle
|
|
|
3270 |
* @type string
|
|
|
3271 |
*/
|
|
|
3272 |
contentStyle: "ygtvhtml",
|
|
|
3273 |
|
|
|
3274 |
|
|
|
3275 |
/**
|
|
|
3276 |
* The HTML content to use for this node's display
|
|
|
3277 |
* @property html
|
|
|
3278 |
* @type string
|
|
|
3279 |
*/
|
|
|
3280 |
html: null,
|
|
|
3281 |
|
|
|
3282 |
/**
|
|
|
3283 |
* The node type
|
|
|
3284 |
* @property _type
|
|
|
3285 |
* @private
|
|
|
3286 |
* @type string
|
|
|
3287 |
* @default "HTMLNode"
|
|
|
3288 |
*/
|
|
|
3289 |
_type: "HTMLNode",
|
|
|
3290 |
|
|
|
3291 |
/**
|
|
|
3292 |
* Sets up the node label
|
|
|
3293 |
* @method initContent
|
|
|
3294 |
* @param oData {object} An html string or object containing an html property
|
|
|
3295 |
* @param hasIcon {boolean} determines if the node will be rendered with an
|
|
|
3296 |
* icon or not
|
|
|
3297 |
*/
|
|
|
3298 |
initContent: function(oData, hasIcon) {
|
|
|
3299 |
this.setHtml(oData);
|
|
|
3300 |
this.contentElId = "ygtvcontentel" + this.index;
|
|
|
3301 |
if (!Lang.isUndefined(hasIcon)) { this.hasIcon = hasIcon; }
|
|
|
3302 |
|
|
|
3303 |
this.logger = new YAHOO.widget.LogWriter(this.toString());
|
|
|
3304 |
},
|
|
|
3305 |
|
|
|
3306 |
/**
|
|
|
3307 |
* Synchronizes the node.html, and the node's content
|
|
|
3308 |
* @method setHtml
|
|
|
3309 |
* @param o {object |string | HTMLElement } An html string, an object containing an html property or an HTML element
|
|
|
3310 |
*/
|
|
|
3311 |
setHtml: function(o) {
|
|
|
3312 |
this.html = (Lang.isObject(o) && 'html' in o) ? o.html : o;
|
|
|
3313 |
|
|
|
3314 |
var el = this.getContentEl();
|
|
|
3315 |
if (el) {
|
|
|
3316 |
if (o.nodeType && o.nodeType == 1 && o.tagName) {
|
|
|
3317 |
el.innerHTML = "";
|
|
|
3318 |
} else {
|
|
|
3319 |
el.innerHTML = this.html;
|
|
|
3320 |
}
|
|
|
3321 |
}
|
|
|
3322 |
|
|
|
3323 |
},
|
|
|
3324 |
|
|
|
3325 |
// overrides YAHOO.widget.Node
|
|
|
3326 |
// If property html is a string, it sets the innerHTML for the node
|
|
|
3327 |
// If it is an HTMLElement, it defers appending it to the tree until the HTML basic structure is built
|
|
|
3328 |
getContentHtml: function() {
|
|
|
3329 |
if (typeof this.html === "string") {
|
|
|
3330 |
return this.html;
|
|
|
3331 |
} else {
|
|
|
3332 |
|
|
|
3333 |
HN._deferredNodes.push(this);
|
|
|
3334 |
if (!HN._timer) {
|
|
|
3335 |
HN._timer = window.setTimeout(function () {
|
|
|
3336 |
var n;
|
|
|
3337 |
while((n = HN._deferredNodes.pop())) {
|
|
|
3338 |
n.getContentEl().appendChild(n.html);
|
|
|
3339 |
}
|
|
|
3340 |
HN._timer = null;
|
|
|
3341 |
},0);
|
|
|
3342 |
}
|
|
|
3343 |
return "";
|
|
|
3344 |
}
|
|
|
3345 |
},
|
|
|
3346 |
|
|
|
3347 |
/**
|
|
|
3348 |
* Returns an object which could be used to build a tree out of this node and its children.
|
|
|
3349 |
* It can be passed to the tree constructor to reproduce this node as a tree.
|
|
|
3350 |
* It will return false if any node loads dynamically, regardless of whether it is loaded or not.
|
|
|
3351 |
* @method getNodeDefinition
|
|
|
3352 |
* @return {Object | false} definition of the tree or false if any node is defined as dynamic
|
|
|
3353 |
*/
|
|
|
3354 |
getNodeDefinition: function() {
|
|
|
3355 |
var def = HN.superclass.getNodeDefinition.call(this);
|
|
|
3356 |
if (def === false) { return false; }
|
|
|
3357 |
def.html = this.html;
|
|
|
3358 |
return def;
|
|
|
3359 |
|
|
|
3360 |
}
|
|
|
3361 |
});
|
|
|
3362 |
|
|
|
3363 |
/**
|
|
|
3364 |
* An array of HTMLNodes created with HTML Elements that had their rendering
|
|
|
3365 |
* deferred until the basic tree structure is rendered.
|
|
|
3366 |
* @property _deferredNodes
|
|
|
3367 |
* @type YAHOO.widget.HTMLNode[]
|
|
|
3368 |
* @default []
|
|
|
3369 |
* @private
|
|
|
3370 |
* @static
|
|
|
3371 |
*/
|
|
|
3372 |
HN._deferredNodes = [];
|
|
|
3373 |
/**
|
|
|
3374 |
* A system timer value used to mark whether a deferred operation is pending.
|
|
|
3375 |
* @property _timer
|
|
|
3376 |
* @type System Timer
|
|
|
3377 |
* @default null
|
|
|
3378 |
* @private
|
|
|
3379 |
* @static
|
|
|
3380 |
*/
|
|
|
3381 |
HN._timer = null;
|
|
|
3382 |
})();
|
|
|
3383 |
(function () {
|
|
|
3384 |
var Dom = YAHOO.util.Dom,
|
|
|
3385 |
Lang = YAHOO.lang,
|
|
|
3386 |
Event = YAHOO.util.Event,
|
|
|
3387 |
Calendar = YAHOO.widget.Calendar;
|
|
|
3388 |
|
|
|
3389 |
/**
|
|
|
3390 |
* A Date-specific implementation that differs from TextNode in that it uses
|
|
|
3391 |
* YAHOO.widget.Calendar as an in-line editor, if available
|
|
|
3392 |
* If Calendar is not available, it behaves as a plain TextNode.
|
|
|
3393 |
* @namespace YAHOO.widget
|
|
|
3394 |
* @class DateNode
|
|
|
3395 |
* @extends YAHOO.widget.TextNode
|
|
|
3396 |
* @param oData {object} a string or object containing the data that will
|
|
|
3397 |
* be used to render this node.
|
|
|
3398 |
* Providing a string is the same as providing an object with a single property named label.
|
|
|
3399 |
* All values in the oData will be used to set equally named properties in the node
|
|
|
3400 |
* as long as the node does have such properties, they are not undefined, private nor functions.
|
|
|
3401 |
* All attributes are made available in noderef.data, which
|
|
|
3402 |
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
|
|
|
3403 |
* can be used to retrieve a node by one of the attributes.
|
|
|
3404 |
* @param oParent {YAHOO.widget.Node} this node's parent node
|
|
|
3405 |
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
|
|
|
3406 |
* @constructor
|
|
|
3407 |
*/
|
|
|
3408 |
YAHOO.widget.DateNode = function(oData, oParent, expanded) {
|
|
|
3409 |
YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded);
|
|
|
3410 |
};
|
|
|
3411 |
|
|
|
3412 |
YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, {
|
|
|
3413 |
|
|
|
3414 |
/**
|
|
|
3415 |
* The node type
|
|
|
3416 |
* @property _type
|
|
|
3417 |
* @type string
|
|
|
3418 |
* @private
|
|
|
3419 |
* @default "DateNode"
|
|
|
3420 |
*/
|
|
|
3421 |
_type: "DateNode",
|
|
|
3422 |
|
|
|
3423 |
/**
|
|
|
3424 |
* Configuration object for the Calendar editor, if used.
|
|
|
3425 |
* See <a href="http://developer.yahoo.com/yui/calendar/#internationalization">http://developer.yahoo.com/yui/calendar/#internationalization</a>
|
|
|
3426 |
* @property calendarConfig
|
|
|
3427 |
*/
|
|
|
3428 |
calendarConfig: null,
|
|
|
3429 |
|
|
|
3430 |
|
|
|
3431 |
|
|
|
3432 |
/**
|
|
|
3433 |
* 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
|
|
|
3434 |
* @method fillEditorContainer
|
|
|
3435 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3436 |
* @return void
|
|
|
3437 |
*/
|
|
|
3438 |
fillEditorContainer: function (editorData) {
|
|
|
3439 |
|
|
|
3440 |
var cal, container = editorData.inputContainer;
|
|
|
3441 |
|
|
|
3442 |
if (Lang.isUndefined(Calendar)) {
|
|
|
3443 |
Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode');
|
|
|
3444 |
YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData);
|
|
|
3445 |
return;
|
|
|
3446 |
}
|
|
|
3447 |
|
|
|
3448 |
if (editorData.nodeType != this._type) {
|
|
|
3449 |
editorData.nodeType = this._type;
|
|
|
3450 |
editorData.saveOnEnter = false;
|
|
|
3451 |
|
|
|
3452 |
editorData.node.destroyEditorContents(editorData);
|
|
|
3453 |
|
|
|
3454 |
editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div')));
|
|
|
3455 |
if (this.calendarConfig) {
|
|
|
3456 |
cal.cfg.applyConfig(this.calendarConfig,true);
|
|
|
3457 |
cal.cfg.fireQueue();
|
|
|
3458 |
}
|
|
|
3459 |
cal.selectEvent.subscribe(function () {
|
|
|
3460 |
this.tree._closeEditor(true);
|
|
|
3461 |
},this,true);
|
|
|
3462 |
} else {
|
|
|
3463 |
cal = editorData.inputObject;
|
|
|
3464 |
}
|
|
|
3465 |
|
|
|
3466 |
editorData.oldValue = this.label;
|
|
|
3467 |
cal.cfg.setProperty("selected",this.label, false);
|
|
|
3468 |
|
|
|
3469 |
var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER');
|
|
|
3470 |
var pageDate = this.label.split(delim);
|
|
|
3471 |
cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]);
|
|
|
3472 |
cal.cfg.fireQueue();
|
|
|
3473 |
|
|
|
3474 |
cal.render();
|
|
|
3475 |
cal.oDomContainer.focus();
|
|
|
3476 |
},
|
|
|
3477 |
/**
|
|
|
3478 |
* Returns the value from the input element.
|
|
|
3479 |
* Overrides Node.getEditorValue.
|
|
|
3480 |
* @method getEditorValue
|
|
|
3481 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3482 |
* @return {string} date entered
|
|
|
3483 |
*/
|
|
|
3484 |
|
|
|
3485 |
getEditorValue: function (editorData) {
|
|
|
3486 |
if (Lang.isUndefined(Calendar)) {
|
|
|
3487 |
return editorData.inputElement.value;
|
|
|
3488 |
} else {
|
|
|
3489 |
var cal = editorData.inputObject,
|
|
|
3490 |
date = cal.getSelectedDates()[0],
|
|
|
3491 |
dd = [];
|
|
|
3492 |
|
|
|
3493 |
dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate();
|
|
|
3494 |
dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1;
|
|
|
3495 |
dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear();
|
|
|
3496 |
return dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER'));
|
|
|
3497 |
}
|
|
|
3498 |
},
|
|
|
3499 |
|
|
|
3500 |
/**
|
|
|
3501 |
* Finally displays the newly entered date in the tree.
|
|
|
3502 |
* Overrides Node.displayEditedValue.
|
|
|
3503 |
* @method displayEditedValue
|
|
|
3504 |
* @param value {HTML} date to be displayed and stored in the node.
|
|
|
3505 |
* This data is added to the node unescaped via the innerHTML property.
|
|
|
3506 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3507 |
*/
|
|
|
3508 |
displayEditedValue: function (value,editorData) {
|
|
|
3509 |
var node = editorData.node;
|
|
|
3510 |
node.label = value;
|
|
|
3511 |
node.getLabelEl().innerHTML = value;
|
|
|
3512 |
},
|
|
|
3513 |
|
|
|
3514 |
/**
|
|
|
3515 |
* Returns an object which could be used to build a tree out of this node and its children.
|
|
|
3516 |
* It can be passed to the tree constructor to reproduce this node as a tree.
|
|
|
3517 |
* It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
|
|
|
3518 |
* @method getNodeDefinition
|
|
|
3519 |
* @return {Object | false} definition of the node or false if this node or any descendant is defined as dynamic
|
|
|
3520 |
*/
|
|
|
3521 |
getNodeDefinition: function() {
|
|
|
3522 |
var def = YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);
|
|
|
3523 |
if (def === false) { return false; }
|
|
|
3524 |
if (this.calendarConfig) { def.calendarConfig = this.calendarConfig; }
|
|
|
3525 |
return def;
|
|
|
3526 |
}
|
|
|
3527 |
|
|
|
3528 |
|
|
|
3529 |
});
|
|
|
3530 |
})();
|
|
|
3531 |
(function () {
|
|
|
3532 |
var Dom = YAHOO.util.Dom,
|
|
|
3533 |
Lang = YAHOO.lang,
|
|
|
3534 |
Event = YAHOO.util.Event,
|
|
|
3535 |
TV = YAHOO.widget.TreeView,
|
|
|
3536 |
TVproto = TV.prototype;
|
|
|
3537 |
|
|
|
3538 |
/**
|
|
|
3539 |
* An object to store information used for in-line editing
|
|
|
3540 |
* for all Nodes of all TreeViews. It contains:
|
|
|
3541 |
* <ul>
|
|
|
3542 |
* <li>active {boolean}, whether there is an active cell editor </li>
|
|
|
3543 |
* <li>whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li>
|
|
|
3544 |
* <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li>
|
|
|
3545 |
* <li>editorPanel {HTMLelement (<div>)} element holding the in-line editor</li>
|
|
|
3546 |
* <li>inputContainer {HTMLelement (<div>)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
|
|
|
3547 |
* <li>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</li>
|
|
|
3548 |
* <li>node {YAHOO.widget.Node} reference to the Node being edited</li>
|
|
|
3549 |
* <li>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 </li>
|
|
|
3550 |
* <li>oldValue {any} value before editing</li>
|
|
|
3551 |
* </ul>
|
|
|
3552 |
* Editors are free to use this object to store additional data.
|
|
|
3553 |
* @property editorData
|
|
|
3554 |
* @static
|
|
|
3555 |
* @for YAHOO.widget.TreeView
|
|
|
3556 |
*/
|
|
|
3557 |
TV.editorData = {
|
|
|
3558 |
active:false,
|
|
|
3559 |
whoHasIt:null, // which TreeView has it
|
|
|
3560 |
nodeType:null,
|
|
|
3561 |
editorPanel:null,
|
|
|
3562 |
inputContainer:null,
|
|
|
3563 |
buttonsContainer:null,
|
|
|
3564 |
node:null, // which Node is being edited
|
|
|
3565 |
saveOnEnter:true,
|
|
|
3566 |
oldValue:undefined
|
|
|
3567 |
// Each node type is free to add its own properties to this as it sees fit.
|
|
|
3568 |
};
|
|
|
3569 |
|
|
|
3570 |
/**
|
|
|
3571 |
* Validator function for edited data, called from the TreeView instance scope,
|
|
|
3572 |
* receives the arguments (newValue, oldValue, nodeInstance)
|
|
|
3573 |
* and returns either the validated (or type-converted) value or undefined.
|
|
|
3574 |
* An undefined return will prevent the editor from closing
|
|
|
3575 |
* @property validator
|
|
|
3576 |
* @type function
|
|
|
3577 |
* @default null
|
|
|
3578 |
* @for YAHOO.widget.TreeView
|
|
|
3579 |
*/
|
|
|
3580 |
TVproto.validator = null;
|
|
|
3581 |
|
|
|
3582 |
/**
|
|
|
3583 |
* Entry point for initializing the editing plug-in.
|
|
|
3584 |
* TreeView will call this method on initializing if it exists
|
|
|
3585 |
* @method _initEditor
|
|
|
3586 |
* @for YAHOO.widget.TreeView
|
|
|
3587 |
* @private
|
|
|
3588 |
*/
|
|
|
3589 |
|
|
|
3590 |
TVproto._initEditor = function () {
|
|
|
3591 |
/**
|
|
|
3592 |
* Fires when the user clicks on the ok button of a node editor
|
|
|
3593 |
* @event editorSaveEvent
|
|
|
3594 |
* @type CustomEvent
|
|
|
3595 |
* @param oArgs.newValue {mixed} the new value just entered
|
|
|
3596 |
* @param oArgs.oldValue {mixed} the value originally in the tree
|
|
|
3597 |
* @param oArgs.node {YAHOO.widget.Node} the node that has the focus
|
|
|
3598 |
* @for YAHOO.widget.TreeView
|
|
|
3599 |
*/
|
|
|
3600 |
this.createEvent("editorSaveEvent", this);
|
|
|
3601 |
|
|
|
3602 |
/**
|
|
|
3603 |
* Fires when the user clicks on the cancel button of a node editor
|
|
|
3604 |
* @event editorCancelEvent
|
|
|
3605 |
* @type CustomEvent
|
|
|
3606 |
* @param {YAHOO.widget.Node} node the node that has the focus
|
|
|
3607 |
* @for YAHOO.widget.TreeView
|
|
|
3608 |
*/
|
|
|
3609 |
this.createEvent("editorCancelEvent", this);
|
|
|
3610 |
|
|
|
3611 |
};
|
|
|
3612 |
|
|
|
3613 |
/**
|
|
|
3614 |
* Entry point of the editing plug-in.
|
|
|
3615 |
* TreeView will call this method if it exists when a node label is clicked
|
|
|
3616 |
* @method _nodeEditing
|
|
|
3617 |
* @param node {YAHOO.widget.Node} the node to be edited
|
|
|
3618 |
* @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
|
|
|
3619 |
* @for YAHOO.widget.TreeView
|
|
|
3620 |
* @private
|
|
|
3621 |
*/
|
|
|
3622 |
|
|
|
3623 |
|
|
|
3624 |
|
|
|
3625 |
TVproto._nodeEditing = function (node) {
|
|
|
3626 |
if (node.fillEditorContainer && node.editable) {
|
|
|
3627 |
var ed, topLeft, buttons, button, editorData = TV.editorData;
|
|
|
3628 |
editorData.active = true;
|
|
|
3629 |
editorData.whoHasIt = this;
|
|
|
3630 |
if (!editorData.nodeType) {
|
|
|
3631 |
// Fixes: http://yuilibrary.com/projects/yui2/ticket/2528945
|
|
|
3632 |
editorData.editorPanel = ed = this.getEl().appendChild(document.createElement('div'));
|
|
|
3633 |
Dom.addClass(ed,'ygtv-label-editor');
|
|
|
3634 |
ed.tabIndex = 0;
|
|
|
3635 |
|
|
|
3636 |
buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
|
|
|
3637 |
Dom.addClass(buttons,'ygtv-button-container');
|
|
|
3638 |
button = buttons.appendChild(document.createElement('button'));
|
|
|
3639 |
Dom.addClass(button,'ygtvok');
|
|
|
3640 |
button.innerHTML = ' ';
|
|
|
3641 |
button = buttons.appendChild(document.createElement('button'));
|
|
|
3642 |
Dom.addClass(button,'ygtvcancel');
|
|
|
3643 |
button.innerHTML = ' ';
|
|
|
3644 |
Event.on(buttons, 'click', function (ev) {
|
|
|
3645 |
var target = Event.getTarget(ev),
|
|
|
3646 |
editorData = TV.editorData,
|
|
|
3647 |
node = editorData.node,
|
|
|
3648 |
self = editorData.whoHasIt;
|
|
|
3649 |
self.logger.log('click on editor');
|
|
|
3650 |
if (Dom.hasClass(target,'ygtvok')) {
|
|
|
3651 |
node.logger.log('ygtvok');
|
|
|
3652 |
Event.stopEvent(ev);
|
|
|
3653 |
self._closeEditor(true);
|
|
|
3654 |
}
|
|
|
3655 |
if (Dom.hasClass(target,'ygtvcancel')) {
|
|
|
3656 |
node.logger.log('ygtvcancel');
|
|
|
3657 |
Event.stopEvent(ev);
|
|
|
3658 |
self._closeEditor(false);
|
|
|
3659 |
}
|
|
|
3660 |
});
|
|
|
3661 |
|
|
|
3662 |
editorData.inputContainer = ed.appendChild(document.createElement('div'));
|
|
|
3663 |
Dom.addClass(editorData.inputContainer,'ygtv-input');
|
|
|
3664 |
|
|
|
3665 |
Event.on(ed,'keydown',function (ev) {
|
|
|
3666 |
var editorData = TV.editorData,
|
|
|
3667 |
KEY = YAHOO.util.KeyListener.KEY,
|
|
|
3668 |
self = editorData.whoHasIt;
|
|
|
3669 |
switch (ev.keyCode) {
|
|
|
3670 |
case KEY.ENTER:
|
|
|
3671 |
self.logger.log('ENTER');
|
|
|
3672 |
Event.stopEvent(ev);
|
|
|
3673 |
if (editorData.saveOnEnter) {
|
|
|
3674 |
self._closeEditor(true);
|
|
|
3675 |
}
|
|
|
3676 |
break;
|
|
|
3677 |
case KEY.ESCAPE:
|
|
|
3678 |
self.logger.log('ESC');
|
|
|
3679 |
Event.stopEvent(ev);
|
|
|
3680 |
self._closeEditor(false);
|
|
|
3681 |
break;
|
|
|
3682 |
}
|
|
|
3683 |
});
|
|
|
3684 |
|
|
|
3685 |
|
|
|
3686 |
|
|
|
3687 |
} else {
|
|
|
3688 |
ed = editorData.editorPanel;
|
|
|
3689 |
}
|
|
|
3690 |
editorData.node = node;
|
|
|
3691 |
if (editorData.nodeType) {
|
|
|
3692 |
Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
|
|
|
3693 |
}
|
|
|
3694 |
Dom.addClass(ed,' ygtv-edit-' + node._type);
|
|
|
3695 |
// Fixes: http://yuilibrary.com/projects/yui2/ticket/2528945
|
|
|
3696 |
Dom.setStyle(ed,'display','block');
|
|
|
3697 |
Dom.setXY(ed,Dom.getXY(node.getContentEl()));
|
|
|
3698 |
// up to here
|
|
|
3699 |
ed.focus();
|
|
|
3700 |
node.fillEditorContainer(editorData);
|
|
|
3701 |
|
|
|
3702 |
return true; // If inline editor available, don't do anything else.
|
|
|
3703 |
}
|
|
|
3704 |
};
|
|
|
3705 |
|
|
|
3706 |
/**
|
|
|
3707 |
* Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
|
|
|
3708 |
* It calls the corresponding node editNode method.
|
|
|
3709 |
* @method onEventEditNode
|
|
|
3710 |
* @param oArgs {object} Object passed as arguments to TreeView event listeners
|
|
|
3711 |
* @for YAHOO.widget.TreeView
|
|
|
3712 |
*/
|
|
|
3713 |
|
|
|
3714 |
TVproto.onEventEditNode = function (oArgs) {
|
|
|
3715 |
if (oArgs instanceof YAHOO.widget.Node) {
|
|
|
3716 |
oArgs.editNode();
|
|
|
3717 |
} else if (oArgs.node instanceof YAHOO.widget.Node) {
|
|
|
3718 |
oArgs.node.editNode();
|
|
|
3719 |
}
|
|
|
3720 |
return false;
|
|
|
3721 |
};
|
|
|
3722 |
|
|
|
3723 |
/**
|
|
|
3724 |
* Method to be called when the inline editing is finished and the editor is to be closed
|
|
|
3725 |
* @method _closeEditor
|
|
|
3726 |
* @param save {Boolean} true if the edited value is to be saved, false if discarded
|
|
|
3727 |
* @private
|
|
|
3728 |
* @for YAHOO.widget.TreeView
|
|
|
3729 |
*/
|
|
|
3730 |
|
|
|
3731 |
TVproto._closeEditor = function (save) {
|
|
|
3732 |
var ed = TV.editorData,
|
|
|
3733 |
node = ed.node,
|
|
|
3734 |
close = true;
|
|
|
3735 |
// http://yuilibrary.com/projects/yui2/ticket/2528946
|
|
|
3736 |
// _closeEditor might now be called at any time, even when there is no label editor open
|
|
|
3737 |
// so we need to ensure there is one.
|
|
|
3738 |
if (!node || !ed.active) { return; }
|
|
|
3739 |
if (save) {
|
|
|
3740 |
close = ed.node.saveEditorValue(ed) !== false;
|
|
|
3741 |
} else {
|
|
|
3742 |
this.fireEvent( 'editorCancelEvent', node);
|
|
|
3743 |
}
|
|
|
3744 |
|
|
|
3745 |
if (close) {
|
|
|
3746 |
Dom.setStyle(ed.editorPanel,'display','none');
|
|
|
3747 |
ed.active = false;
|
|
|
3748 |
node.focus();
|
|
|
3749 |
}
|
|
|
3750 |
};
|
|
|
3751 |
|
|
|
3752 |
/**
|
|
|
3753 |
* Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
|
|
|
3754 |
* @method _destroyEditor
|
|
|
3755 |
* @private
|
|
|
3756 |
* @for YAHOO.widget.TreeView
|
|
|
3757 |
*/
|
|
|
3758 |
TVproto._destroyEditor = function() {
|
|
|
3759 |
var ed = TV.editorData;
|
|
|
3760 |
if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
|
|
|
3761 |
Event.removeListener(ed.editorPanel,'keydown');
|
|
|
3762 |
Event.removeListener(ed.buttonContainer,'click');
|
|
|
3763 |
ed.node.destroyEditorContents(ed);
|
|
|
3764 |
document.body.removeChild(ed.editorPanel);
|
|
|
3765 |
ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
|
|
|
3766 |
ed.active = false;
|
|
|
3767 |
}
|
|
|
3768 |
};
|
|
|
3769 |
|
|
|
3770 |
var Nproto = YAHOO.widget.Node.prototype;
|
|
|
3771 |
|
|
|
3772 |
/**
|
|
|
3773 |
* Signals if the label is editable. (Ignored on TextNodes with href set.)
|
|
|
3774 |
* @property editable
|
|
|
3775 |
* @type boolean
|
|
|
3776 |
* @for YAHOO.widget.Node
|
|
|
3777 |
*/
|
|
|
3778 |
Nproto.editable = false;
|
|
|
3779 |
|
|
|
3780 |
/**
|
|
|
3781 |
* pops up the contents editor, if there is one and the node is declared editable
|
|
|
3782 |
* @method editNode
|
|
|
3783 |
* @for YAHOO.widget.Node
|
|
|
3784 |
*/
|
|
|
3785 |
|
|
|
3786 |
Nproto.editNode = function () {
|
|
|
3787 |
this.tree._nodeEditing(this);
|
|
|
3788 |
};
|
|
|
3789 |
|
|
|
3790 |
|
|
|
3791 |
/** Placeholder for a function that should provide the inline node label editor.
|
|
|
3792 |
* Leaving it set to null will indicate that this node type is not editable.
|
|
|
3793 |
* It should be overridden by nodes that provide inline editing.
|
|
|
3794 |
* The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
|
|
|
3795 |
* @method fillEditorContainer
|
|
|
3796 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3797 |
* @return void
|
|
|
3798 |
* @for YAHOO.widget.Node
|
|
|
3799 |
*/
|
|
|
3800 |
Nproto.fillEditorContainer = null;
|
|
|
3801 |
|
|
|
3802 |
|
|
|
3803 |
/**
|
|
|
3804 |
* Node-specific destroy function to empty the contents of the inline editor panel.
|
|
|
3805 |
* This function is the worst case alternative that will purge all possible events and remove the editor contents.
|
|
|
3806 |
* Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
|
|
|
3807 |
* @method destroyEditorContents
|
|
|
3808 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3809 |
* @for YAHOO.widget.Node
|
|
|
3810 |
*/
|
|
|
3811 |
Nproto.destroyEditorContents = function (editorData) {
|
|
|
3812 |
// In the worst case, if the input editor (such as the Calendar) has no destroy method
|
|
|
3813 |
// we can only try to remove all possible events on it.
|
|
|
3814 |
Event.purgeElement(editorData.inputContainer,true);
|
|
|
3815 |
editorData.inputContainer.innerHTML = '';
|
|
|
3816 |
};
|
|
|
3817 |
|
|
|
3818 |
/**
|
|
|
3819 |
* Saves the value entered into the editor.
|
|
|
3820 |
* @method saveEditorValue
|
|
|
3821 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3822 |
* @return {false or none} a return of exactly false will prevent the editor from closing
|
|
|
3823 |
* @for YAHOO.widget.Node
|
|
|
3824 |
*/
|
|
|
3825 |
Nproto.saveEditorValue = function (editorData) {
|
|
|
3826 |
var node = editorData.node,
|
|
|
3827 |
value,
|
|
|
3828 |
validator = node.tree.validator;
|
|
|
3829 |
|
|
|
3830 |
value = this.getEditorValue(editorData);
|
|
|
3831 |
|
|
|
3832 |
if (Lang.isFunction(validator)) {
|
|
|
3833 |
value = validator(value,editorData.oldValue,node);
|
|
|
3834 |
if (Lang.isUndefined(value)) {
|
|
|
3835 |
return false;
|
|
|
3836 |
}
|
|
|
3837 |
}
|
|
|
3838 |
|
|
|
3839 |
if (this.tree.fireEvent( 'editorSaveEvent', {
|
|
|
3840 |
newValue:value,
|
|
|
3841 |
oldValue:editorData.oldValue,
|
|
|
3842 |
node:node
|
|
|
3843 |
}) !== false) {
|
|
|
3844 |
this.displayEditedValue(value,editorData);
|
|
|
3845 |
}
|
|
|
3846 |
};
|
|
|
3847 |
|
|
|
3848 |
|
|
|
3849 |
/**
|
|
|
3850 |
* Returns the value(s) from the input element(s) .
|
|
|
3851 |
* Should be overridden by each node type.
|
|
|
3852 |
* @method getEditorValue
|
|
|
3853 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3854 |
* @return {any} value entered
|
|
|
3855 |
* @for YAHOO.widget.Node
|
|
|
3856 |
*/
|
|
|
3857 |
|
|
|
3858 |
Nproto.getEditorValue = function (editorData) {
|
|
|
3859 |
};
|
|
|
3860 |
|
|
|
3861 |
/**
|
|
|
3862 |
* Finally displays the newly edited value(s) in the tree.
|
|
|
3863 |
* Should be overridden by each node type.
|
|
|
3864 |
* @method displayEditedValue
|
|
|
3865 |
* @param value {HTML} value to be displayed and stored in the node
|
|
|
3866 |
* This data is added to the node unescaped via the innerHTML property.
|
|
|
3867 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3868 |
* @for YAHOO.widget.Node
|
|
|
3869 |
*/
|
|
|
3870 |
Nproto.displayEditedValue = function (value,editorData) {
|
|
|
3871 |
};
|
|
|
3872 |
|
|
|
3873 |
var TNproto = YAHOO.widget.TextNode.prototype;
|
|
|
3874 |
|
|
|
3875 |
|
|
|
3876 |
|
|
|
3877 |
/**
|
|
|
3878 |
* Places an <input> textbox in the input container and loads the label text into it.
|
|
|
3879 |
* @method fillEditorContainer
|
|
|
3880 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3881 |
* @return void
|
|
|
3882 |
* @for YAHOO.widget.TextNode
|
|
|
3883 |
*/
|
|
|
3884 |
TNproto.fillEditorContainer = function (editorData) {
|
|
|
3885 |
|
|
|
3886 |
var input;
|
|
|
3887 |
// If last node edited is not of the same type as this one, delete it and fill it with our editor
|
|
|
3888 |
if (editorData.nodeType != this._type) {
|
|
|
3889 |
editorData.nodeType = this._type;
|
|
|
3890 |
editorData.saveOnEnter = true;
|
|
|
3891 |
editorData.node.destroyEditorContents(editorData);
|
|
|
3892 |
|
|
|
3893 |
editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
|
|
|
3894 |
|
|
|
3895 |
} else {
|
|
|
3896 |
// if the last node edited was of the same time, reuse the input element.
|
|
|
3897 |
input = editorData.inputElement;
|
|
|
3898 |
}
|
|
|
3899 |
editorData.oldValue = this.label;
|
|
|
3900 |
input.value = this.label;
|
|
|
3901 |
input.focus();
|
|
|
3902 |
input.select();
|
|
|
3903 |
};
|
|
|
3904 |
|
|
|
3905 |
/**
|
|
|
3906 |
* Returns the value from the input element.
|
|
|
3907 |
* Overrides Node.getEditorValue.
|
|
|
3908 |
* @method getEditorValue
|
|
|
3909 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3910 |
* @return {string} value entered
|
|
|
3911 |
* @for YAHOO.widget.TextNode
|
|
|
3912 |
*/
|
|
|
3913 |
|
|
|
3914 |
TNproto.getEditorValue = function (editorData) {
|
|
|
3915 |
return editorData.inputElement.value;
|
|
|
3916 |
};
|
|
|
3917 |
|
|
|
3918 |
/**
|
|
|
3919 |
* Finally displays the newly edited value in the tree.
|
|
|
3920 |
* Overrides Node.displayEditedValue.
|
|
|
3921 |
* @method displayEditedValue
|
|
|
3922 |
* @param value {string} value to be displayed and stored in the node
|
|
|
3923 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3924 |
* @for YAHOO.widget.TextNode
|
|
|
3925 |
*/
|
|
|
3926 |
TNproto.displayEditedValue = function (value,editorData) {
|
|
|
3927 |
var node = editorData.node;
|
|
|
3928 |
node.label = value;
|
|
|
3929 |
node.getLabelEl().innerHTML = value;
|
|
|
3930 |
};
|
|
|
3931 |
|
|
|
3932 |
/**
|
|
|
3933 |
* Destroys the contents of the inline editor panel.
|
|
|
3934 |
* Overrides Node.destroyEditorContent.
|
|
|
3935 |
* Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node.
|
|
|
3936 |
* @method destroyEditorContents
|
|
|
3937 |
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
|
|
|
3938 |
* @for YAHOO.widget.TextNode
|
|
|
3939 |
*/
|
|
|
3940 |
TNproto.destroyEditorContents = function (editorData) {
|
|
|
3941 |
editorData.inputContainer.innerHTML = '';
|
|
|
3942 |
};
|
|
|
3943 |
})();
|
|
|
3944 |
/**
|
|
|
3945 |
* A static factory class for tree view expand/collapse animations
|
|
|
3946 |
* @class TVAnim
|
|
|
3947 |
* @static
|
|
|
3948 |
*/
|
|
|
3949 |
YAHOO.widget.TVAnim = function() {
|
|
|
3950 |
return {
|
|
|
3951 |
/**
|
|
|
3952 |
* Constant for the fade in animation
|
|
|
3953 |
* @property FADE_IN
|
|
|
3954 |
* @type string
|
|
|
3955 |
* @static
|
|
|
3956 |
*/
|
|
|
3957 |
FADE_IN: "TVFadeIn",
|
|
|
3958 |
|
|
|
3959 |
/**
|
|
|
3960 |
* Constant for the fade out animation
|
|
|
3961 |
* @property FADE_OUT
|
|
|
3962 |
* @type string
|
|
|
3963 |
* @static
|
|
|
3964 |
*/
|
|
|
3965 |
FADE_OUT: "TVFadeOut",
|
|
|
3966 |
|
|
|
3967 |
/**
|
|
|
3968 |
* Returns a ygAnim instance of the given type
|
|
|
3969 |
* @method getAnim
|
|
|
3970 |
* @param type {string} the type of animation
|
|
|
3971 |
* @param el {HTMLElement} the element to element (probably the children div)
|
|
|
3972 |
* @param callback {function} function to invoke when the animation is done.
|
|
|
3973 |
* @return {YAHOO.util.Animation} the animation instance
|
|
|
3974 |
* @static
|
|
|
3975 |
*/
|
|
|
3976 |
getAnim: function(type, el, callback) {
|
|
|
3977 |
if (YAHOO.widget[type]) {
|
|
|
3978 |
return new YAHOO.widget[type](el, callback);
|
|
|
3979 |
} else {
|
|
|
3980 |
return null;
|
|
|
3981 |
}
|
|
|
3982 |
},
|
|
|
3983 |
|
|
|
3984 |
/**
|
|
|
3985 |
* Returns true if the specified animation class is available
|
|
|
3986 |
* @method isValid
|
|
|
3987 |
* @param type {string} the type of animation
|
|
|
3988 |
* @return {boolean} true if valid, false if not
|
|
|
3989 |
* @static
|
|
|
3990 |
*/
|
|
|
3991 |
isValid: function(type) {
|
|
|
3992 |
return (YAHOO.widget[type]);
|
|
|
3993 |
}
|
|
|
3994 |
};
|
|
|
3995 |
} ();
|
|
|
3996 |
/**
|
|
|
3997 |
* A 1/2 second fade-in animation.
|
|
|
3998 |
* @class TVFadeIn
|
|
|
3999 |
* @constructor
|
|
|
4000 |
* @param el {HTMLElement} the element to animate
|
|
|
4001 |
* @param callback {function} function to invoke when the animation is finished
|
|
|
4002 |
*/
|
|
|
4003 |
YAHOO.widget.TVFadeIn = function(el, callback) {
|
|
|
4004 |
/**
|
|
|
4005 |
* The element to animate
|
|
|
4006 |
* @property el
|
|
|
4007 |
* @type HTMLElement
|
|
|
4008 |
*/
|
|
|
4009 |
this.el = el;
|
|
|
4010 |
|
|
|
4011 |
/**
|
|
|
4012 |
* the callback to invoke when the animation is complete
|
|
|
4013 |
* @property callback
|
|
|
4014 |
* @type function
|
|
|
4015 |
*/
|
|
|
4016 |
this.callback = callback;
|
|
|
4017 |
|
|
|
4018 |
this.logger = new YAHOO.widget.LogWriter(this.toString());
|
|
|
4019 |
};
|
|
|
4020 |
|
|
|
4021 |
YAHOO.widget.TVFadeIn.prototype = {
|
|
|
4022 |
/**
|
|
|
4023 |
* Performs the animation
|
|
|
4024 |
* @method animate
|
|
|
4025 |
*/
|
|
|
4026 |
animate: function() {
|
|
|
4027 |
var tvanim = this;
|
|
|
4028 |
|
|
|
4029 |
var s = this.el.style;
|
|
|
4030 |
s.opacity = 0.1;
|
|
|
4031 |
s.filter = "alpha(opacity=10)";
|
|
|
4032 |
s.display = "";
|
|
|
4033 |
|
|
|
4034 |
var dur = 0.4;
|
|
|
4035 |
var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
|
|
|
4036 |
a.onComplete.subscribe( function() { tvanim.onComplete(); } );
|
|
|
4037 |
a.animate();
|
|
|
4038 |
},
|
|
|
4039 |
|
|
|
4040 |
/**
|
|
|
4041 |
* Clean up and invoke callback
|
|
|
4042 |
* @method onComplete
|
|
|
4043 |
*/
|
|
|
4044 |
onComplete: function() {
|
|
|
4045 |
this.callback();
|
|
|
4046 |
},
|
|
|
4047 |
|
|
|
4048 |
/**
|
|
|
4049 |
* toString
|
|
|
4050 |
* @method toString
|
|
|
4051 |
* @return {string} the string representation of the instance
|
|
|
4052 |
*/
|
|
|
4053 |
toString: function() {
|
|
|
4054 |
return "TVFadeIn";
|
|
|
4055 |
}
|
|
|
4056 |
};
|
|
|
4057 |
/**
|
|
|
4058 |
* A 1/2 second fade out animation.
|
|
|
4059 |
* @class TVFadeOut
|
|
|
4060 |
* @constructor
|
|
|
4061 |
* @param el {HTMLElement} the element to animate
|
|
|
4062 |
* @param callback {Function} function to invoke when the animation is finished
|
|
|
4063 |
*/
|
|
|
4064 |
YAHOO.widget.TVFadeOut = function(el, callback) {
|
|
|
4065 |
/**
|
|
|
4066 |
* The element to animate
|
|
|
4067 |
* @property el
|
|
|
4068 |
* @type HTMLElement
|
|
|
4069 |
*/
|
|
|
4070 |
this.el = el;
|
|
|
4071 |
|
|
|
4072 |
/**
|
|
|
4073 |
* the callback to invoke when the animation is complete
|
|
|
4074 |
* @property callback
|
|
|
4075 |
* @type function
|
|
|
4076 |
*/
|
|
|
4077 |
this.callback = callback;
|
|
|
4078 |
|
|
|
4079 |
this.logger = new YAHOO.widget.LogWriter(this.toString());
|
|
|
4080 |
};
|
|
|
4081 |
|
|
|
4082 |
YAHOO.widget.TVFadeOut.prototype = {
|
|
|
4083 |
/**
|
|
|
4084 |
* Performs the animation
|
|
|
4085 |
* @method animate
|
|
|
4086 |
*/
|
|
|
4087 |
animate: function() {
|
|
|
4088 |
var tvanim = this;
|
|
|
4089 |
var dur = 0.4;
|
|
|
4090 |
var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
|
|
|
4091 |
a.onComplete.subscribe( function() { tvanim.onComplete(); } );
|
|
|
4092 |
a.animate();
|
|
|
4093 |
},
|
|
|
4094 |
|
|
|
4095 |
/**
|
|
|
4096 |
* Clean up and invoke callback
|
|
|
4097 |
* @method onComplete
|
|
|
4098 |
*/
|
|
|
4099 |
onComplete: function() {
|
|
|
4100 |
var s = this.el.style;
|
|
|
4101 |
s.display = "none";
|
|
|
4102 |
s.opacity = 1;
|
|
|
4103 |
s.filter = "alpha(opacity=100)";
|
|
|
4104 |
this.callback();
|
|
|
4105 |
},
|
|
|
4106 |
|
|
|
4107 |
/**
|
|
|
4108 |
* toString
|
|
|
4109 |
* @method toString
|
|
|
4110 |
* @return {string} the string representation of the instance
|
|
|
4111 |
*/
|
|
|
4112 |
toString: function() {
|
|
|
4113 |
return "TVFadeOut";
|
|
|
4114 |
}
|
|
|
4115 |
};
|
|
|
4116 |
YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.9.0", build: "2800"});
|
|
|
4117 |
|
|
|
4118 |
}, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom", "yui2-event", "yui2-skin-sam-treeview"], "optional": ["yui2-skin-sam-calendar", "yui2-calendar", "yui2-animation"]});
|