Diffstat (limited to 'frontend/beta/js/YUI-extensions/data/Tree.js') (more/less context) (ignore whitespace changes)
-rw-r--r-- | frontend/beta/js/YUI-extensions/data/Tree.js | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/frontend/beta/js/YUI-extensions/data/Tree.js b/frontend/beta/js/YUI-extensions/data/Tree.js new file mode 100644 index 0000000..afa5b20 --- a/dev/null +++ b/frontend/beta/js/YUI-extensions/data/Tree.js | |||
@@ -0,0 +1,412 @@ | |||
1 | YAHOO.namespace('ext.data'); | ||
2 | |||
3 | /** | ||
4 | * @class YAHOO.ext.data.Tree | ||
5 | * @extends YAHOO.ext.util.Observable | ||
6 | * The class represents a tree data structure and bubbles all the events for it's nodes. The nodes | ||
7 | * in the tree have most standard DOM functionality. | ||
8 | * @constructor | ||
9 | * @param {Node} root (optional) The root node | ||
10 | */ | ||
11 | YAHOO.ext.data.Tree = function(root){ | ||
12 | this.nodeHash = {}; | ||
13 | this.root = null; | ||
14 | if(root){ | ||
15 | this.setRootNode(root); | ||
16 | } | ||
17 | this.events = { | ||
18 | 'append' : true, | ||
19 | 'remove' : true, | ||
20 | 'move' : true, | ||
21 | 'insert' : true, | ||
22 | 'beforeappend' : true, | ||
23 | 'beforeremove' : true, | ||
24 | 'beforemove' : true, | ||
25 | 'beforeinsert' : true | ||
26 | }; | ||
27 | }; | ||
28 | |||
29 | YAHOO.extendX(YAHOO.ext.data.Tree, YAHOO.ext.util.Observable, { | ||
30 | pathSeparator: '/', | ||
31 | |||
32 | getRootNode : function(){ | ||
33 | return this.root; | ||
34 | }, | ||
35 | |||
36 | setRootNode : function(node){ | ||
37 | this.root = node; | ||
38 | node.ownerTree = this; | ||
39 | node.isRoot = true; | ||
40 | return node; | ||
41 | }, | ||
42 | |||
43 | getNodeById : function(id){ | ||
44 | return this.nodeHash[id]; | ||
45 | }, | ||
46 | |||
47 | registerNode : function(node){ | ||
48 | this.nodeHash[node.id] = node; | ||
49 | }, | ||
50 | |||
51 | unregisterNode : function(node){ | ||
52 | delete this.nodeHash[node.id]; | ||
53 | }, | ||
54 | |||
55 | toString : function(){ | ||
56 | return '[Tree'+(this.id?' '+this.id:'')+']'; | ||
57 | } | ||
58 | }); | ||
59 | |||
60 | /** | ||
61 | * @class YAHOO.ext.tree.Node | ||
62 | * @extends YAHOO.ext.util.Observable | ||
63 | * @cfg {String} text The text for this node | ||
64 | * @cfg {String} id The id for this node | ||
65 | * @constructor | ||
66 | * @param {Object} attributes The attributes/config for the node | ||
67 | */ | ||
68 | YAHOO.ext.data.Node = function(attributes){ | ||
69 | this.attributes = attributes || {}; | ||
70 | this.leaf = this.attributes.leaf; | ||
71 | this.id = this.attributes.id; | ||
72 | if(!this.id){ | ||
73 | this.id = YAHOO.util.Dom.generateId(null, 'ynode-'); | ||
74 | this.attributes.id = this.id; | ||
75 | } | ||
76 | |||
77 | this.childNodes = []; | ||
78 | if(!this.childNodes.indexOf){ // indexOf is a must | ||
79 | this.childNodes.indexOf = function(o){ | ||
80 | for(var i = 0, len = this.length; i < len; i++){ | ||
81 | if(this[i] == o) return i; | ||
82 | } | ||
83 | return -1; | ||
84 | }; | ||
85 | } | ||
86 | this.parentNode = null; | ||
87 | this.firstChild = null; | ||
88 | this.lastChild = null; | ||
89 | this.previousSibling = null; | ||
90 | this.nextSibling = null; | ||
91 | |||
92 | this.events = { | ||
93 | 'append' : true, | ||
94 | 'remove' : true, | ||
95 | 'move' : true, | ||
96 | 'insert' : true, | ||
97 | 'beforeappend' : true, | ||
98 | 'beforeremove' : true, | ||
99 | 'beforemove' : true, | ||
100 | 'beforeinsert' : true | ||
101 | }; | ||
102 | }; | ||
103 | |||
104 | YAHOO.extendX(YAHOO.ext.data.Node, YAHOO.ext.util.Observable, { | ||
105 | fireEvent : function(evtName){ | ||
106 | // first do standard event for this node | ||
107 | if(YAHOO.ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){ | ||
108 | return false; | ||
109 | } | ||
110 | // then bubble it up to the tree if the event wasn't cancelled | ||
111 | if(this.ownerTree){ | ||
112 | if(this.ownerTree.fireEvent.apply(this.ownerTree, arguments) === false){ | ||
113 | return false; | ||
114 | } | ||
115 | } | ||
116 | return true; | ||
117 | }, | ||
118 | |||
119 | isLeaf : function(){ | ||
120 | return this.leaf === true; | ||
121 | }, | ||
122 | |||
123 | setFirstChild : function(node){ | ||
124 | this.firstChild = node; | ||
125 | }, | ||
126 | |||
127 | setLastChild : function(node){ | ||
128 | this.lastChild = node; | ||
129 | }, | ||
130 | |||
131 | isLast : function(){ | ||
132 | return (!this.parentNode ? true : this.parentNode.lastChild == this); | ||
133 | }, | ||
134 | |||
135 | isFirst : function(){ | ||
136 | return (!this.parentNode ? true : this.parentNode.firstChild == this); | ||
137 | }, | ||
138 | |||
139 | hasChildNodes : function(){ | ||
140 | return !this.isLeaf() && this.childNodes.length > 0; | ||
141 | }, | ||
142 | |||
143 | appendChild : function(node){ | ||
144 | var multi = false; | ||
145 | if(node instanceof Array){ | ||
146 | multi = node; | ||
147 | }else if(arguments.length > 1){ | ||
148 | multi = arguments; | ||
149 | } | ||
150 | // if passed an array or multiple args do them one by one | ||
151 | if(multi){ | ||
152 | for(var i = 0, len = multi.length; i < len; i++) { | ||
153 | this.appendChild(multi[i]); | ||
154 | } | ||
155 | }else{ | ||
156 | if(this.fireEvent('beforeappend', this.ownerTree, this, node) === false){ | ||
157 | return false; | ||
158 | } | ||
159 | var index = this.childNodes.length; | ||
160 | var oldParent = node.parentNode; | ||
161 | // it's a move, make sure we move it cleanly | ||
162 | if(oldParent){ | ||
163 | if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index) === false){ | ||
164 | return false; | ||
165 | } | ||
166 | oldParent.removeChild(node); | ||
167 | } | ||
168 | var index = this.childNodes.length; | ||
169 | if(index == 0){ | ||
170 | this.setFirstChild(node); | ||
171 | } | ||
172 | this.childNodes.push(node); | ||
173 | node.parentNode = this; | ||
174 | var ps = this.childNodes[index-1]; | ||
175 | if(ps){ | ||
176 | node.previousSibling = ps; | ||
177 | ps.nextSibling = node; | ||
178 | } | ||
179 | this.setLastChild(node); | ||
180 | node.setOwnerTree(this.getOwnerTree()); | ||
181 | this.fireEvent('append', this.ownerTree, this, node, index); | ||
182 | if(oldParent){ | ||
183 | node.fireEvent('move', this.ownerTree, node, oldParent, this, index); | ||
184 | } | ||
185 | return node; | ||
186 | } | ||
187 | }, | ||
188 | |||
189 | removeChild : function(node){ | ||
190 | var index = this.childNodes.indexOf(node); | ||
191 | if(index == -1){ | ||
192 | return false; | ||
193 | } | ||
194 | if(this.fireEvent('beforeremove', this.ownerTree, this, node) === false){ | ||
195 | return false; | ||
196 | } | ||
197 | |||
198 | // remove it from childNodes collection | ||
199 | this.childNodes.splice(index, 1); | ||
200 | |||
201 | // update siblings | ||
202 | if(node.previousSibling){ | ||
203 | node.previousSibling.nextSibling = node.nextSibling; | ||
204 | } | ||
205 | if(node.nextSibling){ | ||
206 | node.nextSibling.previousSibling = node.previousSibling; | ||
207 | } | ||
208 | |||
209 | // update child refs | ||
210 | if(this.firstChild == node){ | ||
211 | this.setFirstChild(node.nextSibling); | ||
212 | } | ||
213 | if(this.lastChild == node){ | ||
214 | this.setLastChild(node.previousSibling); | ||
215 | } | ||
216 | |||
217 | node.setOwnerTree(null); | ||
218 | // clear any references from the node | ||
219 | node.parentNode = null; | ||
220 | node.previousSibling = null; | ||
221 | node.nextSibling = null; | ||
222 | this.fireEvent('remove', this.ownerTree, this, node); | ||
223 | return node; | ||
224 | }, | ||
225 | |||
226 | insertBefore : function(node, refNode){ | ||
227 | if(!refNode){ // like standard Dom, refNode can be null for append | ||
228 | return this.appendChild(node); | ||
229 | } | ||
230 | // nothing to do | ||
231 | if(node == refNode){ | ||
232 | return false; | ||
233 | } | ||
234 | |||
235 | if(this.fireEvent('beforeinsert', this.ownerTree, this, node, refNode) === false){ | ||
236 | return false; | ||
237 | } | ||
238 | var index = this.childNodes.indexOf(refNode); | ||
239 | var oldParent = node.parentNode; | ||
240 | var refIndex = index; | ||
241 | |||
242 | // when moving internally, indexes will change after remove | ||
243 | if(oldParent == this && this.childNodes.indexOf(node) < index){ | ||
244 | refIndex--; | ||
245 | } | ||
246 | |||
247 | // it's a move, make sure we move it cleanly | ||
248 | if(oldParent){ | ||
249 | if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index, refNode) === false){ | ||
250 | return false; | ||
251 | } | ||
252 | oldParent.removeChild(node); | ||
253 | } | ||
254 | if(refIndex == 0){ | ||
255 | this.setFirstChild(node); | ||
256 | } | ||
257 | this.childNodes.splice(refIndex, 0, node); | ||
258 | node.parentNode = this; | ||
259 | var ps = this.childNodes[refIndex-1]; | ||
260 | if(ps){ | ||
261 | node.previousSibling = ps; | ||
262 | ps.nextSibling = node; | ||
263 | } | ||
264 | node.nextSibling = refNode; | ||
265 | node.setOwnerTree(this.getOwnerTree()); | ||
266 | this.fireEvent('insert', this.ownerTree, this, node, refNode); | ||
267 | if(oldParent){ | ||
268 | node.fireEvent('move', this.ownerTree, node, oldParent, this, refIndex, refNode); | ||
269 | } | ||
270 | return node; | ||
271 | }, | ||
272 | |||
273 | item : function(index){ | ||
274 | return this.childNodes[index]; | ||
275 | }, | ||
276 | |||
277 | replaceChild : function(newChild, oldChild){ | ||
278 | this.insertBefore(newChild, oldChild); | ||
279 | this.removeChild(oldChild); | ||
280 | return oldChild; | ||
281 | }, | ||
282 | |||
283 | indexOf : function(child){ | ||
284 | return this.childNodes.indexOf(child); | ||
285 | }, | ||
286 | |||
287 | getOwnerTree : function(){ | ||
288 | // if it doesn't have one, look for one | ||
289 | if(!this.ownerTree){ | ||
290 | var p = this; | ||
291 | while(p){ | ||
292 | if(p.ownerTree){ | ||
293 | this.ownerTree = p.ownerTree; | ||
294 | break; | ||
295 | } | ||
296 | p = p.parentNode; | ||
297 | } | ||
298 | } | ||
299 | return this.ownerTree; | ||
300 | }, | ||
301 | |||
302 | setOwnerTree : function(tree){ | ||
303 | // if it's move, we need to update everyone | ||
304 | if(tree != this.ownerTree){ | ||
305 | if(this.ownerTree){ | ||
306 | this.ownerTree.unregisterNode(this); | ||
307 | } | ||
308 | this.ownerTree = tree; | ||
309 | var cs = this.childNodes; | ||
310 | for(var i = 0, len = cs.length; i < len; i++) { | ||
311 | cs[i].setOwnerTree(tree); | ||
312 | } | ||
313 | if(tree){ | ||
314 | tree.registerNode(this); | ||
315 | } | ||
316 | } | ||
317 | }, | ||
318 | |||
319 | getPath : function(attr){ | ||
320 | attr = attr || 'id'; | ||
321 | var p = this.parentNode; | ||
322 | var b = [this.attributes[attr]]; | ||
323 | while(p){ | ||
324 | b.unshift(p.attributes[attr]); | ||
325 | p = p.parentNode; | ||
326 | } | ||
327 | var sep = this.getOwnerTree().pathSeparator; | ||
328 | return sep + b.join(sep); | ||
329 | }, | ||
330 | |||
331 | bubble : function(fn, scope, args){ | ||
332 | var p = this; | ||
333 | while(p){ | ||
334 | if(fn.call(scope || p, args || p) === false){ | ||
335 | break; | ||
336 | } | ||
337 | p = p.parentNode; | ||
338 | } | ||
339 | }, | ||
340 | |||
341 | cascade : function(fn, scope, args){ | ||
342 | if(fn.call(scope || this, args || this) !== false){ | ||
343 | var cs = this.childNodes; | ||
344 | for(var i = 0, len = cs.length; i < len; i++) { | ||
345 | cs[i].cascade(fn, scope, args); | ||
346 | } | ||
347 | } | ||
348 | }, | ||
349 | |||
350 | eachChild : function(fn, scope, args){ | ||
351 | var cs = this.childNodes; | ||
352 | for(var i = 0, len = cs.length; i < len; i++) { | ||
353 | if(fn.call(scope || this, args || cs[i]) === false){ | ||
354 | break; | ||
355 | } | ||
356 | } | ||
357 | }, | ||
358 | |||
359 | findChild : function(attribute, value){ | ||
360 | var cs = this.childNodes; | ||
361 | for(var i = 0, len = cs.length; i < len; i++) { | ||
362 | if(cs[i].attributes[attribute] == value){ | ||
363 | return cs[i]; | ||
364 | } | ||
365 | } | ||
366 | return null; | ||
367 | }, | ||
368 | |||
369 | /** | ||
370 | * Sorts this nodes children using the supplied sort function | ||
371 | * @param {Function} fn | ||
372 | * @param {Object} scope | ||
373 | */ | ||
374 | sort : function(fn, scope){ | ||
375 | var cs = this.childNodes; | ||
376 | var len = cs.length; | ||
377 | if(len > 0){ | ||
378 | var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn; | ||
379 | cs.sort(sortFn); | ||
380 | for(var i = 0; i < len; i++){ | ||
381 | var n = cs[i]; | ||
382 | n.previousSibling = cs[i-1]; | ||
383 | n.nextSibling = cs[i+1]; | ||
384 | if(i == 0){ | ||
385 | this.setFirstChild(n); | ||
386 | } | ||
387 | if(i == len-1){ | ||
388 | this.setLastChild(n); | ||
389 | } | ||
390 | } | ||
391 | } | ||
392 | }, | ||
393 | |||
394 | contains : function(node){ | ||
395 | return node.isAncestor(this); | ||
396 | }, | ||
397 | |||
398 | isAncestor : function(node){ | ||
399 | var p = this.parentNode; | ||
400 | while(p){ | ||
401 | if(p == node){ | ||
402 | return true; | ||
403 | } | ||
404 | p = p.parentNode; | ||
405 | } | ||
406 | return false; | ||
407 | }, | ||
408 | |||
409 | toString : function(){ | ||
410 | return '[Node'+(this.id?' '+this.id:'')+']'; | ||
411 | } | ||
412 | }); | ||