Projekt

Obecné

Profil

Stáhnout (35.6 KB) Statistiky
| Větev: | Tag: | Revize:
1
/*----------------------------------------------------------------------------\
2
|                            XTree 2 PRE RELEASE                              |
3
|                                                                             |
4
| This is a pre release and redistribution is discouraged.                    |
5
| Watch http://webfx.eae.net for the final version                            |
6
|                                                                             |
7
|-----------------------------------------------------------------------------|
8
|                   Created by Erik Arvidsson & Emil A Eklund                 |
9
|                  (http://webfx.eae.net/contact.html#erik)                   |
10
|                  (http://webfx.eae.net/contact.html#emil)                   |
11
|                      For WebFX (http://webfx.eae.net/)                      |
12
|-----------------------------------------------------------------------------|
13
| A tree menu system for IE 5.5+, Mozilla 1.4+, Opera 7, KHTML                |
14
|-----------------------------------------------------------------------------|
15
|         Copyright (c) 1999 - 2005 Erik Arvidsson & Emil A Eklund            |
16
|-----------------------------------------------------------------------------|
17
| This software is provided "as is", without warranty of any kind, express or |
18
| implied, including  but not limited  to the warranties of  merchantability, |
19
| fitness for a particular purpose and noninfringement. In no event shall the |
20
| authors or  copyright  holders be  liable for any claim,  damages or  other |
21
| liability, whether  in an  action of  contract, tort  or otherwise, arising |
22
| from,  out of  or in  connection with  the software or  the  use  or  other |
23
| dealings in the software.                                                   |
24
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
25
| This  software is  available under the  three different licenses  mentioned |
26
| below.  To use this software you must chose, and qualify, for one of those. |
27
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
28
| The WebFX Non-Commercial License          http://webfx.eae.net/license.html |
29
| Permits  anyone the right to use the  software in a  non-commercial context |
30
| free of charge.                                                             |
31
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
32
| The WebFX Commercial license           http://webfx.eae.net/commercial.html |
33
| Permits the  license holder the right to use  the software in a  commercial |
34
| context. Such license must be specifically obtained, however it's valid for |
35
| any number of  implementations of the licensed software.                    |
36
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
37
| GPL - The GNU General Public License    http://www.gnu.org/licenses/gpl.txt |
38
| Permits anyone the right to use and modify the software without limitations |
39
| as long as proper  credits are given  and the original  and modified source |
40
| code are included. Requires  that the final product, software derivate from |
41
| the original  source or any  software  utilizing a GPL  component, such  as |
42
| this, is also licensed under the GPL license.                               |
43
|-----------------------------------------------------------------------------|
44
| 2004-02-21 | Pre release distributed to a few selected tester               |
45
| 2005-06-06 | Added single tab index to improve keyboard navigation          |
46
|-----------------------------------------------------------------------------|
47
| Dependencies: xtree2.css Used to define the look and feel                   |
48
|-----------------------------------------------------------------------------|
49
| Created 2003-??-?? | All changes are in the log above. | Updated 2004-06-06 |
50
\----------------------------------------------------------------------------*/
51

    
52

    
53
//
54
// WebFXTreePersisitance
55
function WebFXTreePersistence() {}
56
var _p = WebFXTreePersistence.prototype;
57
_p.getExpanded = function (oNode) { return false; };
58
_p.setExpanded = function (oNode, bOpen) {};
59

    
60

    
61

    
62
// Cookie handling
63
function WebFXCookie() {}
64

    
65
_p = WebFXCookie.prototype;
66

    
67
_p.setCookie = function (sName, sValue, nDays) {
68
	var expires = "";
69
	if (typeof nDays == "number") {
70
		var d = new Date();
71
		d.setTime(d.getTime() + nDays * 24 * 60 * 60 * 1000);
72
		expires = "; expires=" + d.toGMTString();
73
	}
74

    
75
	document.cookie = sName + "=" + escape(sValue) + expires + "; path=/";
76
};
77

    
78
_p.getCookie = function (sName) {
79
	var re = new RegExp("(\;|^)[^;]*(" + sName + ")\=([^;]*)(;|$)");
80
	var res = re.exec(document.cookie);
81
	return res != null ? unescape(res[3]) : null;
82
};
83

    
84
_p.removeCookie = function (name) {
85
	this.setCookie(name, "", -1);
86
};
87

    
88

    
89
//
90
// persistence using cookies
91
//
92
// This is uses one cookie with the ids of the expanded nodes separated using '+'
93
//
94
function WebFXTreeCookiePersistence() {
95
	this._openedMap = {};
96
	this._cookies = new WebFXCookie;
97
	var s = this._cookies.getCookie(this.cookieName);
98
	if (s) {
99
		var a = s.split("+");
100
		for (var i = a.length - 1; i >= 0; i--)
101
			this._openedMap[a[i]] = true;
102
	}
103
}
104

    
105
_p = WebFXTreeCookiePersistence.prototype = new WebFXTreePersistence;
106

    
107
_p.cookieName = "webfx-tree-cookie-persistence"
108

    
109
_p.getExpanded = function (oNode) {
110
	return oNode.id in this._openedMap;
111
};
112

    
113
_p.setExpanded = function (oNode, bOpen) {
114
	var old = this.getExpanded(oNode);
115
	if (old != bOpen) {
116
		if (bOpen) {
117
			this._openedMap[oNode.id] = true;
118
		} else {
119
			delete this._openedMap[oNode.id];
120
		}
121

    
122
		var res = [];
123
		var i = 0;
124
		for (var id in this._openedMap)
125
			res[i++] = id;
126
		this._cookies.setCookie(this.cookieName, res.join("+"));
127
	}
128
};
129

    
130

    
131

    
132
// this object provides a few useful methods when working with arrays
133
var arrayHelper = {
134
	indexOf: function (a, o) {
135
		for (var i = 0; i < a.length; i++) {
136
			if (a[i] == o) {
137
				return i;
138
			}
139
		}
140
		return -1;
141
	},
142

    
143
	insertBefore: function (a, o, o2) {
144
		var i = this.indexOf(a, o2);
145
		if (i == -1) {
146
			a.push(o);
147
		} else {
148
			a.splice(i, 0, o);
149
		}
150
	},
151

    
152
	remove: function (a, o) {
153
		var i = this.indexOf(a, o);
154
		if (i != -1) {
155
			a.splice(i, 1);
156
		}
157
	}
158
};
159

    
160
///////////////////////////////////////////////////////////////////////////////
161
// WebFX Tree Config object                                                  //
162
///////////////////////////////////////////////////////////////////////////////
163
var webFXTreeConfig = {
164
	rootIcon        : "images/folder.png",
165
	openRootIcon    : "images/openfolder.png",
166
	folderIcon      : "images/folder.png",
167
	openFolderIcon  : "images/openfolder.png",
168
	fileIcon        : "images/file.png",
169
	iIcon           : "images/I.png",
170
	lIcon           : "images/L.png",
171
	lMinusIcon      : "images/Lminus.png",
172
	lPlusIcon       : "images/Lplus.png",
173
	tIcon           : "images/T.png",
174
	tMinusIcon      : "images/Tminus.png",
175
	tPlusIcon       : "images/Tplus.png",
176
	plusIcon        : "images/plus.png",
177
	minusIcon       : "images/minus.png",
178
	blankIcon       : "images/blank.png",
179
	defaultText     : "Tree Item",
180
	defaultAction   : null,
181
	defaultBehavior : "classic",
182
	usePersistence	: true
183
};
184

    
185
///////////////////////////////////////////////////////////////////////////////
186
// WebFX Tree Handler object                                                 //
187
///////////////////////////////////////////////////////////////////////////////
188

    
189
var webFXTreeHandler = {
190
	ie: /msie/i.test(navigator.userAgent),
191
	opera: /opera/i.test(navigator.userAgent),
192
	idCounter: 0,
193
	idPrefix: "wfxt-",
194
	getUniqueId: function () {
195
		return this.idPrefix + this.idCounter++;
196
	},
197
	all: {},
198
	getNodeById: function (sId) {
199
		return all[sId];
200
	},
201
	addNode: function (oNode) {
202
		this.all[oNode.id] = oNode;
203
	},
204
	removeNode:	function (oNode) {
205
		delete this.all[oNode.id];
206
	},
207

    
208
	handleEvent: function (e) {
209
		var el = e.target || e.srcElement;
210
		while (el != null && !this.all[el.id]) {
211
			el = el.parentNode;
212
		}
213

    
214
		if (el == null) {
215
			return false;
216
		}
217
		var node = this.all[el.id];
218
		if (typeof node["_on" + e.type] == "function") {
219
			return node["_on" + e.type](e);
220
		}
221
		return false;
222
	},
223

    
224
	dispose: function () {
225
		if (this.disposed) return;
226
		for (var id in this.all) {
227
			this.all[id].dispose();
228
		}
229
		this.disposed = true;
230
	},
231

    
232
	htmlToText: function (s) {
233
		return String(s).replace(/\s+|<([^>])+>|&amp;|&lt;|&gt;|&quot;|&nbsp;/gi, this._htmlToText);
234
	},
235

    
236
	_htmlToText: function (s) {
237
		switch (s) {
238
			case "&amp;":
239
				return "&";
240
			case "&lt;":
241
				return "<";
242
			case "&gt;":
243
				return ">";
244
			case "&quot;":
245
				return "\"";
246
			case "&nbsp;":
247
				return String.fromCharCode(160);
248
			default:
249
				if (/\s+/.test(s)) {
250
					return " ";
251
				}
252
				if (/^<BR/gi.test(s)) {
253
					return "\n";
254
				}
255
				return "";
256
		}
257
	},
258

    
259
	textToHtml: function (s) {
260
		return String(s).replace(/&|<|>|\n|\"\u00A0/g, this._textToHtml);
261
	},
262

    
263
	_textToHtml: function (s) {
264
		switch (s) {
265
			case "&":
266
				return "&amp;";
267
			case "<":
268
				return "&lt;";
269
			case ">":
270
				return "&gt;";
271
			case "\n":
272
				return "<BR>";
273
			case "\"":
274
				return "&quot;";	// so we can use this in attributes
275
			default:
276
				return "&nbsp;";
277
		}
278
	},
279

    
280
	persistenceManager: new WebFXTreeCookiePersistence()
281
};
282

    
283

    
284
///////////////////////////////////////////////////////////////////////////////
285
// WebFXTreeAbstractNode
286
///////////////////////////////////////////////////////////////////////////////
287

    
288
function WebFXTreeAbstractNode(sText, oAction) {
289
	this.childNodes = [];
290
	if (sText) this.text = sText;
291
	if (oAction) this.action = oAction;
292
	this.id = webFXTreeHandler.getUniqueId();
293
	if (webFXTreeConfig.usePersistence) {
294
		this.open = webFXTreeHandler.persistenceManager.getExpanded(this);
295
	}
296
	webFXTreeHandler.addNode(this);
297
}
298

    
299

    
300
_p = WebFXTreeAbstractNode.prototype;
301
_p._selected = false;
302
_p.indentWidth = 19;
303
_p.open = false;
304
_p.text = webFXTreeConfig.defaultText;
305
_p.action = null;
306
_p.target = null;
307
_p.toolTip = null;
308
_p._focused = false;
309

    
310
/* begin tree model */
311

    
312
_p.add = function (oChild, oBefore) {
313
	var oldLast;
314
	var emptyBefore = this.childNodes.length == 0;
315
	var p = oChild.parentNode;
316

    
317
	if (oBefore == null) { // append
318
		if (p != null)
319
			p.remove(oChild);
320
		oldLast = this.getLastChild();
321
		this.childNodes.push(oChild);
322
	} else { // insertBefore
323
		if (oBefore.parentNode != this) {
324
			throw new Error("Can only add nodes before siblings");
325
		}
326
		if (p != null) {
327
			p.remove(oChild);
328
		}
329

    
330
		arrayHelper.insertBefore(this.childNodes, oChild, oBefore);
331
	}
332

    
333
	if (oBefore) {
334
		if (oBefore == this.firstChild) {
335
			this.firstChild = oChild;
336
		}
337
		oChild.previousSibling = oBefore.previousSibling;
338
		oBefore.previousSibling = oChild;
339
		oChild.nextSibling = oBefore;
340
	} else {
341
		if (!this.firstChild) {
342
			this.firstChild = oChild;
343
		}
344
		if (this.lastChild) {
345
			this.lastChild.nextSibling = oChild;
346
		}
347
		oChild.previousSibling = this.lastChild;
348
		this.lastChild = oChild;
349
	}
350

    
351
	oChild.parentNode = this;
352
	var t = this.getTree();
353
	if (t) {
354
		oChild.tree = t;
355
	}
356
	var d = this.getDepth();
357
	if (d != null) {
358
		oChild.depth = d + 1;
359
	}
360

    
361
	if (this.getCreated() && !t.getSuspendRedraw()) {
362
		var el = this.getChildrenElement();
363
		var newEl = oChild.create();
364
		var refEl = oBefore ? oBefore.getElement() : null;
365
		el.insertBefore(newEl, refEl);
366

    
367
		if (oldLast) {
368
			oldLast.updateExpandIcon();
369
		}
370
		if (emptyBefore) {
371
			this.setExpanded(this.getExpanded());
372
			// if we are using classic expand will not update icon
373
			if (t && t.getBehavior() != "classic")
374
				this.updateIcon();
375
		}
376
	}
377

    
378
	return oChild;
379
};
380

    
381

    
382

    
383
_p.remove = function (oChild) {
384
	// backwards compatible. If no argument remove the node
385
	if (arguments.length == 0) {
386
		if (this.parentNode) {
387
			return this.parentNode.remove(this);
388
		}
389
		return null;
390
	}
391

    
392
	// if we remove selected or tree with the selected we should select this
393
	var t = this.getTree();
394
	var si = t ? t.getSelected() : null;
395
	if (si == oChild || oChild.contains(si)) {
396
		if (si.getFocused()) {
397
			this.select();
398
			window.setTimeout("WebFXTreeAbstractNode._onTimeoutFocus(\"" + this.id + "\")", 10);
399
		} else {
400
			this.select();
401
		}
402
	}
403

    
404
	if (oChild.parentNode != this) {
405
		throw new Error("Can only remove children");
406
	}
407
	arrayHelper.remove(this.childNodes, oChild);
408

    
409
	if (this.lastChild == oChild) {
410
		this.lastChild = oChild.previousSibling;
411
	}
412
	if (this.firstChild == oChild) {
413
		this.firstChild = oChild.nextSibling;
414
	}
415
	if (oChild.previousSibling) {
416
		oChild.previousSibling.nextSibling = oChild.nextSibling;
417
	}
418
	if (oChild.nextSibling) {
419
		oChild.nextSibling.previousSibling = oChild.previousSibling;
420
	}
421

    
422
	var wasLast = oChild.isLastSibling();
423

    
424
	oChild.parentNode = null;
425
	oChild.tree = null;
426
	oChild.depth = null;
427

    
428
	if (t && this.getCreated() && !t.getSuspendRedraw()) {
429
		var el = this.getChildrenElement();
430
		var childEl = oChild.getElement();
431
		el.removeChild(childEl);
432
		if (wasLast) {
433
			var newLast = this.getLastChild();
434
			if (newLast) {
435
				newLast.updateExpandIcon();
436
			}
437
		}
438
		if (!this.hasChildren()) {
439
			el.style.display = "none";
440
			this.updateExpandIcon();
441
			this.updateIcon();
442
		}
443
	}
444

    
445
	return oChild;
446
};
447

    
448
WebFXTreeAbstractNode._onTimeoutFocus = function (sId) {
449
	var jsNode = webFXTreeHandler.all[sId];
450
	jsNode.focus();
451
};
452

    
453
_p.getId = function () {
454
	return this.id;
455
};
456

    
457
_p.getTree = function () {
458
	throw new Error("getTree called on Abstract Node");
459
};
460

    
461
_p.getDepth = function () {
462
	throw new Error("getDepth called on Abstract Node");
463
};
464

    
465
_p.getCreated = function () {
466
	var t = this.getTree();
467
	return t && t.rendered;
468
};
469

    
470
_p.getParent = function () {
471
	return this.parentNode;
472
};
473

    
474
_p.contains = function (oDescendant) {
475
	if (oDescendant == null) return false;
476
	if (oDescendant == this) return true;
477
	var p = oDescendant.parentNode;
478
	return this.contains(p);
479
};
480

    
481
_p.getChildren = _p.getChildNodes = function () {
482
	return this.childNodes;
483
};
484

    
485
_p.getFirstChild = function () {
486
	return this.childNodes[0];
487
};
488

    
489
_p.getLastChild = function () {
490
	return this.childNodes[this.childNodes.length - 1];
491
};
492

    
493
_p.getPreviousSibling = function () {
494
	return this.previousSibling;
495
	//var p = this.parentNode;
496
	//if (p == null) return null;
497
	//var cs = p.childNodes;
498
	//return cs[arrayHelper.indexOf(cs, this) - 1]
499
};
500

    
501
_p.getNextSibling = function () {
502
	return this.nextSibling;
503
	//var p = this.parentNode;
504
	//if (p == null) return null;
505
	//var cs = p.childNodes;
506
	//return cs[arrayHelper.indexOf(cs, this) + 1]
507
};
508

    
509
_p.hasChildren = function () {
510
	return this.childNodes.length > 0;
511
};
512

    
513
_p.isLastSibling = function () {
514
	return this.nextSibling == null;
515
	//return this.parentNode && this == this.parentNode.getLastChild();
516
};
517

    
518
_p.findChildByText = function (s, n) {
519
	if (!n) {
520
		n = 0;
521
	}
522
	var isRe = s instanceof RegExp;
523
	for (var i = 0; i < this.childNodes.length; i++) {
524
		if (isRe && s.test(this.childNodes[i].getText()) ||
525
			this.childNodes[i].getText() == s) {
526
			if (n == 0) {
527
				return this.childNodes[i];
528
			}
529
			n--;
530
		}
531
	}
532
	return null;
533
};
534

    
535
_p.findNodeByText = function (s, n) {
536
	if (!n) {
537
		n = 0;
538
	}
539
	var isRe = s instanceof RegExp;
540
	if (isRe && s.test(this.getText()) || this.getText() == s) {
541
		if (n == 0) {
542
			return this.childNodes[i];
543
		}
544
		n--;
545
	}
546

    
547
	var res;
548
	for (var i = 0; i < this.childNodes.length; i++) {
549
		res = this.childNodes[i].findNodeByText(s, n);
550
		if (res) {
551
			return res;
552
		}
553
	}
554
	return null;
555
};
556

    
557
/* end tree model */
558

    
559
_p.setId = function (sId) {
560
	var el = this.getElement();
561
	webFXTreeHandler.removeNode(this);
562
	this.id = sId;
563
	if (el) {
564
		el.id = sId;
565
	}
566
	webFXTreeHandler.addNode(this);
567
};
568

    
569
_p.isSelected = function () {
570
	return this._selected;
571
};
572

    
573
_p.select = function () {
574
	this._setSelected(true);
575
};
576

    
577
_p.deselect = function () {
578
	this._setSelected(false);
579
};
580

    
581
_p._setSelected = function (b) {
582
	var t = this.getTree();
583
	if (!t) return;
584
	if (this._selected != b) {
585
		this._selected = b;
586

    
587
		var wasFocused = false;	// used to keep focus state
588
		var si = t.getSelected();
589
		if (b && si != null && si != this) {
590
			var oldFireChange = t._fireChange;
591
			wasFocused = si._focused;
592
			t._fireChange = false;
593
			si._setSelected(false);
594
			t._fireChange = oldFireChange;
595
		}
596

    
597
		var el = this.getRowElement();
598
		if (el) {
599
			el.className = this.getRowClassName();
600
		}
601
		if (b) {
602
			this._setTabIndex(t.tabIndex);
603
			t._selectedItem = this;
604
			t._fireOnChange();
605
			t.setSelected(this);
606
			if (wasFocused) {
607
				this.focus();
608
			}
609
		} else {
610
			this._setTabIndex(-1);
611
		}
612

    
613
		if (t.getBehavior() != "classic") {
614
			this.updateIcon();
615
		}
616
	}
617
};
618

    
619

    
620
_p.getExpanded = function () {
621
	return this.open;
622
};
623

    
624
_p.setExpanded = function (b) {
625
	var ce;
626
	this.open = b;
627
	var t = this.getTree();
628
	if (this.hasChildren()) {
629
		var si = t ? t.getSelected() : null;
630
		if (!b && this.contains(si)) {
631
			this.select();
632
		}
633

    
634
		var el = this.getElement();
635
		if (el) {
636
			ce = this.getChildrenElement();
637
			if (ce) {
638
				ce.style.display = b ? "block" : "none";
639
			}
640
			var eie = this.getExpandIconElement();
641
			if (eie) {
642
				eie.src = this.getExpandIconSrc();
643
			}
644
		}
645

    
646
		if (webFXTreeConfig.usePersistence) {
647
			webFXTreeHandler.persistenceManager.setExpanded(this, b);
648
		}
649
	} else {
650
		ce = this.getChildrenElement();
651
		if (ce)
652
			ce.style.display = "none";
653
	}
654
	if (t && t.getBehavior() == "classic") {
655
		this.updateIcon();
656
	}
657
};
658

    
659
_p.toggle = function () {
660
	this.setExpanded(!this.getExpanded());
661
};
662

    
663
_p.expand = function () {
664
	this.setExpanded(true);
665
};
666

    
667
_p.collapse = function () {
668
	this.setExpanded(false);
669
};
670

    
671
_p.collapseChildren = function () {
672
	var cs = this.childNodes;
673
	for (var i = 0; i < cs.length; i++) {
674
		cs[i].collapseAll();
675
	}
676
};
677

    
678
_p.collapseAll = function () {
679
	this.collapseChildren();
680
	this.collapse();
681
};
682

    
683
_p.expandChildren = function () {
684
	var cs = this.childNodes;
685
	for (var i = 0; i < cs.length; i++) {
686
		cs[i].expandAll();
687
	}
688
};
689

    
690
_p.expandAll = function () {
691
	this.expandChildren();
692
	this.expand();
693
};
694

    
695
_p.reveal = function () {
696
	var p = this.getParent();
697
	if (p) {
698
		p.setExpanded(true);
699
		p.reveal();
700
	}
701
};
702

    
703
_p.openPath = function (sPath, bSelect, bFocus) {
704
	if (sPath == "") {
705
		if (bSelect) {
706
			this.select();
707
		}
708
		if (bFocus) {
709
			window.setTimeout("WebFXTreeAbstractNode._onTimeoutFocus(\"" + this.id + "\")", 10);
710
		}
711
		return;
712
	}
713

    
714
	var parts = sPath.split("/");
715
	var remainingPath = parts.slice(1).join("/");
716
	var t = this.getTree();
717
	if (sPath.charAt(0) == "/") {
718
		if (t) {
719
			t.openPath(remainingPath, bSelect, bFocus);
720
		} else {
721
			throw "Invalid path";
722
		}
723
	} else {
724
		// open
725
		this.setExpanded(true);
726
		parts = sPath.split("/");
727
		var ti = this.findChildByText(parts[0]);
728
		if (!ti) {
729
			throw "Could not find child node with text \"" + parts[0] + "\"";
730
		}
731
		ti.openPath(remainingPath, bSelect, bFocus);
732
	}
733
};
734

    
735
_p.focus = function () {
736
	var el = this.getLabelElement();
737
	if (el) {
738
		el.focus();
739
	}
740
};
741

    
742
_p.getFocused = function () {
743
	return this._focused;
744
};
745

    
746
_p._setTabIndex = function (i) {
747
	var a = this.getLabelElement();
748
	if (a) {
749
		a.setAttribute("tabindex", i);
750
	}
751
};
752

    
753

    
754
// HTML generation
755

    
756
_p.toHtml = function () {
757
	var sb = [];
758
	var cs = this.childNodes;
759
	var l = cs.length;
760
	for (var y = 0; y < l; y++) {
761
		sb[y] = cs[y].toHtml();
762
	}
763

    
764
	var t = this.getTree();
765
	var hideLines = !t.getShowLines() || t == this.parentNode && !t.getShowRootLines();
766

    
767
	var childrenHtml = "<div class=\"webfx-tree-children" +
768
		(hideLines ? "-nolines" : "") + "\" style=\"" +
769
		this.getLineStyle() +
770
		(this.getExpanded() && this.hasChildren() ? "" : "display:none;") +
771
		"\">" +
772
		sb.join("") +
773
		"</div>";
774

    
775
	return "<div class=\"webfx-tree-item\" id=\"" +
776
		this.id + "\"" + this.getEventHandlersHtml() + ">" +
777
		this.getRowHtml() +
778
		childrenHtml +
779
		"</div>";
780
};
781

    
782
_p.getRowHtml = function () {
783
	var t = this.getTree();
784
	return "<div class=\"" + this.getRowClassName() + "\" style=\"padding-left:" +
785
		Math.max(0, (this.getDepth() - 1) * this.indentWidth) + "px\">" +
786
		this.getExpandIconHtml() +
787
		//"<span class=\"webfx-tree-icon-and-label\">" +
788
		this.getIconHtml() +
789
		this.getLabelHtml() +
790
		//"</span>" +
791
		"</div>";
792
};
793

    
794
_p.getRowClassName = function () {
795
	return "webfx-tree-row" + (this.isSelected() ? " selected" : "") +
796
		(this.action ? "" : " no-action");
797
};
798

    
799
_p.getLabelHtml = function () {
800
	var toolTip = this.getToolTip();
801
	var target = this.getTarget();
802
	return "<a href=\"" + webFXTreeHandler.textToHtml(this._getHref()) +
803
		"\" class=\"webfx-tree-item-label\" tabindex=\"-1\"" +
804
		(toolTip ? " title=\"" + webFXTreeHandler.textToHtml(toolTip) + "\"" : "") +
805
		(target ? " target=\"" + target + "\"" : "") +
806
		" onfocus=\"webFXTreeHandler.handleEvent(event)\"" +
807
		" onblur=\"webFXTreeHandler.handleEvent(event)\">" +
808
		this.getHtml() + "</a>";
809
};
810

    
811
_p._getHref = function () {
812
	if (typeof this.action == "string")
813
		return this.action;
814
	else
815
		return "#";
816
};
817

    
818
_p.getEventHandlersHtml = function () {
819
	return "";
820
};
821

    
822
_p.getIconHtml = function () {
823
	// here we are not using textToHtml since the file names rarerly contains
824
	// HTML...
825
	return "<img class=\"webfx-tree-icon\" src=\"" + this.getIconSrc() + "\">";
826
};
827

    
828
_p.getIconSrc = function () {
829
	throw new Error("getIconSrc called on Abstract Node");
830
};
831

    
832
_p.getExpandIconHtml = function () {
833
	// here we are not using textToHtml since the file names rarerly contains
834
	// HTML...
835
	return "<img class=\"webfx-tree-expand-icon\" src=\"" +
836
		this.getExpandIconSrc() + "\">";
837
};
838

    
839

    
840
_p.getExpandIconSrc = function () {
841
	var src;
842
	var t = this.getTree();
843
	var hideLines = !t.getShowLines() || t == this.parentNode && !t.getShowRootLines();
844

    
845
	if (this.hasChildren()) {
846
		var bits = 0;
847
		/*
848
			Bitmap used to determine which icon to use
849
			1  Plus
850
			2  Minus
851
			4  T Line
852
			8  L Line
853
		*/
854

    
855
		if (t && t.getShowExpandIcons()) {
856
			if (this.getExpanded()) {
857
				bits = 2;
858
			} else {
859
				bits = 1;
860
			}
861
		}
862

    
863
		if (t && !hideLines) {
864
			if (this.isLastSibling()) {
865
				bits += 4;
866
			} else {
867
				bits += 8;
868
			}
869
		}
870

    
871
		switch (bits) {
872
			case 1:
873
				return webFXTreeConfig.plusIcon;
874
			case 2:
875
				return webFXTreeConfig.minusIcon;
876
			case 4:
877
				return webFXTreeConfig.lIcon;
878
			case 5:
879
				return webFXTreeConfig.lPlusIcon;
880
			case 6:
881
				return webFXTreeConfig.lMinusIcon;
882
			case 8:
883
				return webFXTreeConfig.tIcon;
884
			case 9:
885
				return webFXTreeConfig.tPlusIcon;
886
			case 10:
887
				return webFXTreeConfig.tMinusIcon;
888
			default:	// 0
889
				return webFXTreeConfig.blankIcon;
890
		}
891
	} else {
892
		if (t && hideLines) {
893
			return webFXTreeConfig.blankIcon;
894
		} else if (this.isLastSibling()) {
895
			return webFXTreeConfig.lIcon;
896
		} else {
897
			return webFXTreeConfig.tIcon;
898
		}
899
	}
900
};
901

    
902
_p.getLineStyle = function () {
903
	return "background-position:" + this.getLineStyle2() + ";";
904
};
905

    
906
_p.getLineStyle2 = function () {
907
	return (this.isLastSibling() ? "-100" : (this.getDepth() - 1) * this.indentWidth) + "px 0";
908
};
909

    
910
// End HTML generation
911

    
912
// DOM
913
// this returns the div for the tree node
914
_p.getElement = function () {
915
	return document.getElementById(this.id);
916
};
917

    
918
// the row is the div that is used to draw the node without the children
919
_p.getRowElement = function () {
920
	var el = this.getElement();
921
	if (!el) return null;
922
	return el.firstChild;
923
};
924

    
925
// plus/minus image
926
_p.getExpandIconElement = function () {
927
	var el = this.getRowElement();
928
	if (!el) return null;
929
	return el.firstChild;
930
};
931

    
932
_p.getIconElement = function () {
933
	var el = this.getRowElement();
934
	if (!el) return null;
935
	return el.childNodes[1];
936
};
937

    
938
// anchor element
939
_p.getLabelElement = function () {
940
	var el = this.getRowElement();
941
	if (!el) return null;
942
	return el.lastChild;
943
};
944

    
945
// the div containing the children
946
_p.getChildrenElement = function () {
947
	var el = this.getElement();
948
	if (!el) return null;
949
	return el.lastChild;
950
};
951

    
952

    
953
// IE uses about:blank if not attached to document and this can cause Win2k3
954
// to fail
955
if (webFXTreeHandler.ie) {
956
	_p.create = function () {
957
		var dummy = document.createElement("div");
958
		dummy.style.display = "none";
959
		document.body.appendChild(dummy);
960
		dummy.innerHTML = this.toHtml();
961
		var res = dummy.removeChild(dummy.firstChild);
962
		document.body.removeChild(dummy);
963
		return res;
964
	};
965
} else {
966
	_p.create = function () {
967
		var dummy = document.createElement("div");
968
		dummy.innerHTML = this.toHtml();
969
		return dummy.removeChild(dummy.firstChild);
970
	};
971
}
972

    
973
// Getters and setters for some common fields
974

    
975
_p.setIcon = function (s) {
976
	this.icon = s;
977
	if (this.getCreated()) {
978
		this.updateIcon();
979
	}
980
};
981

    
982
_p.getIcon = function () {
983
	return this.icon;
984
};
985

    
986
_p.setOpenIcon = function (s) {
987
	this.openIcon = s;
988
	if (this.getCreated()) {
989
		this.updateIcon();
990
	}
991
};
992

    
993
_p.getOpenIcon = function () {
994
	return this.openIcon;
995
};
996

    
997
_p.setText = function (s) {
998
	this.setHtml(webFXTreeHandler.textToHtml(s));
999
};
1000

    
1001
_p.getText = function () {
1002
	return webFXTreeHandler.htmlToText(this.getHtml());
1003
};
1004

    
1005
_p.setHtml = function (s) {
1006
	this.text = s;
1007
	var el = this.getLabelElement();
1008
	if (el) {
1009
		el.innerHTML = s;
1010
	}
1011
};
1012

    
1013
_p.getHtml = function () {
1014
	return this.text;
1015
};
1016

    
1017
_p.setTarget = function (s) {
1018
	this.target = s;
1019
};
1020

    
1021
_p.getTarget = function () {
1022
	return this.target;
1023
};
1024

    
1025
_p.setToolTip = function (s) {
1026
	this.toolTip = s;
1027
	var el = this.getLabelElement();
1028
	if (el) {
1029
		el.title = s;
1030
	}
1031
};
1032

    
1033
_p.getToolTip = function () {
1034
	return this.toolTip;
1035
};
1036

    
1037
_p.setAction = function (oAction) {
1038
	this.action = oAction;
1039
	var el = this.getLabelElement();
1040
	if (el) {
1041
		el.href = this._getHref();
1042
	}
1043
	el = this.getRowElement();
1044
	if (el) {
1045
		el.className = this.getRowClassName();
1046
	}
1047
};
1048

    
1049
_p.getAction = function () {
1050
	return this.action;
1051
};
1052

    
1053
// update methods
1054

    
1055
_p.update = function () {
1056
	var t = this.getTree();
1057
	if (t.suspendRedraw) return;
1058
	var el = this.getElement();
1059
	if (!el || !el.parentNode) return;
1060
	var newEl = this.create();
1061
	el.parentNode.replaceChild(newEl, el);
1062
	this._setTabIndex(this.tabIndex); // in case root had the tab index
1063
	var si = t.getSelected();
1064
	if (si && si.getFocused()) {
1065
		si.focus();
1066
	}
1067
};
1068

    
1069
_p.updateExpandIcon = function () {
1070
	var t = this.getTree();
1071
	if (t.suspendRedraw) return;
1072
	var img = this.getExpandIconElement();
1073
	img.src = this.getExpandIconSrc();
1074
	var cel = this.getChildrenElement();
1075
	cel.style.backgroundPosition = this.getLineStyle2();
1076
};
1077

    
1078
_p.updateIcon = function () {
1079
	var t = this.getTree();
1080
	if (t.suspendRedraw) return;
1081
	var img = this.getIconElement();
1082
	img.src = this.getIconSrc();
1083
};
1084

    
1085
// End DOM
1086

    
1087
_p._callSuspended = function (f) {
1088
	var t = this.getTree();
1089
	var sr = t.getSuspendRedraw();
1090
	t.setSuspendRedraw(true);
1091
	f.call(this);
1092
	t.setSuspendRedraw(sr);
1093
};
1094

    
1095
// Event handlers
1096

    
1097
_p._onmousedown = function (e) {
1098
	var el = e.target || e.srcElement;
1099
	// expand icon
1100
	if (/webfx-tree-expand-icon/.test(el.className) && this.hasChildren()) {
1101
		this.toggle();
1102
		if (webFXTreeHandler.ie) {
1103
			window.setTimeout("WebFXTreeAbstractNode._onTimeoutFocus(\"" + this.id + "\")", 10);
1104
		}
1105
		return false;
1106
	}
1107

    
1108
	this.select();
1109
	if (/*!/webfx-tree-item-label/.test(el.className) && */!webFXTreeHandler.opera)	{ // opera cancels the click if focus is called
1110
		
1111
		// in case we are not clicking on the label
1112
		if (webFXTreeHandler.ie) {
1113
			window.setTimeout("WebFXTreeAbstractNode._onTimeoutFocus(\"" + this.id + "\")", 10);
1114
		} else {
1115
			this.focus();
1116
		}
1117
	}
1118
	var rowEl = this.getRowElement();
1119
	if (rowEl) {
1120
		rowEl.className = this.getRowClassName();
1121
	}
1122

    
1123
	return false;
1124
};
1125

    
1126
_p._onclick = function (e) {
1127
	var el = e.target || e.srcElement;
1128
	// expand icon
1129
	if (/webfx-tree-expand-icon/.test(el.className) && this.hasChildren()) {
1130
		return false;
1131
	}
1132

    
1133
	if (typeof this.action == "function") {
1134
		this.action();
1135
	} else if (this.action != null) {
1136
		window.open(this.action, this.target || "_self");
1137
	}
1138
	return false;
1139
};
1140

    
1141

    
1142
_p._ondblclick = function (e) {
1143
	var el = e.target || e.srcElement;
1144
	// expand icon
1145
	if (/webfx-tree-expand-icon/.test(el.className) && this.hasChildren()) {
1146
		return;
1147
	}
1148

    
1149
	this.toggle();
1150
};
1151

    
1152
_p._onfocus = function (e) {
1153
	this.select();
1154
	this._focused = true;
1155
};
1156

    
1157
_p._onblur = function (e) {
1158
	this._focused = false;
1159
};
1160

    
1161
_p._onkeydown = function (e) {
1162
	var n;
1163
	var rv = true;
1164
	switch (e.keyCode) {
1165
		case 39:	// RIGHT
1166
			if (e.altKey) {
1167
				rv = true;
1168
				break;
1169
			}
1170
			if (this.hasChildren()) {
1171
				if (!this.getExpanded()) {
1172
					this.setExpanded(true);
1173
				} else {
1174
					this.getFirstChild().focus();
1175
				}
1176
			}
1177
			rv = false;
1178
			break;
1179
		case 37:	// LEFT
1180
			if (e.altKey) {
1181
				rv = true;
1182
				break;
1183
			}
1184
			if (this.hasChildren() && this.getExpanded()) {
1185
				this.setExpanded(false);
1186
			} else {
1187
				var p = this.getParent();
1188
				var t = this.getTree();
1189
				// don't go to root if hidden
1190
				if (p && (t.showRootNode || p != t)) {
1191
					p.focus();
1192
				}
1193
			}
1194
			rv = false;
1195
			break;
1196

    
1197
		case 40:	// DOWN
1198
			n = this.getNextShownNode();
1199
			if (n) {
1200
				n.focus();
1201
			}
1202
			rv = false;
1203
			break;
1204
		case 38:	// UP
1205
			n = this.getPreviousShownNode()
1206
			if (n) {
1207
				n.focus();
1208
			}
1209
			rv = false;
1210
			break;
1211
	}
1212

    
1213
	if (!rv && e.preventDefault) {
1214
		e.preventDefault();
1215
	}
1216
	e.returnValue = rv;
1217
	return rv;
1218
};
1219

    
1220
_p._onkeypress = function (e) {
1221
	if (!e.altKey && e.keyCode >= 37 && e.keyCode <= 40) {
1222
		if (e.preventDefault) {
1223
			e.preventDefault();
1224
		}
1225
		e.returnValue = false;
1226
		return false;
1227
	}
1228
};
1229

    
1230
// End event handlers
1231

    
1232
_p.dispose = function () {
1233
	if (this.disposed) return;
1234
	for (var i = this.childNodes.length - 1; i >= 0; i--) {
1235
		this.childNodes[i].dispose();
1236
	}
1237
	this.tree = null;
1238
	this.parentNode = null;
1239
	this.childNodes = null;
1240
	this.disposed = true;
1241
};
1242

    
1243
// Some methods that are usable when navigating the tree using the arrows
1244
_p.getLastShownDescendant = function () {
1245
	if (!this.getExpanded() || !this.hasChildren()) {
1246
		return this;
1247
	}
1248
	// we know there is at least 1 child
1249
	return this.getLastChild().getLastShownDescendant();
1250
};
1251

    
1252
_p.getNextShownNode = function () {
1253
	if (this.hasChildren() && this.getExpanded()) {
1254
		return this.getFirstChild();
1255
	} else {
1256
		var p = this;
1257
		var next;
1258
		while (p != null) {
1259
			next = p.getNextSibling();
1260
			if (next != null) {
1261
				return next;
1262
			}
1263
			p = p.getParent();
1264
		}
1265
		return null;
1266
	}
1267
};
1268

    
1269
_p.getPreviousShownNode = function () {
1270
	var ps = this.getPreviousSibling();
1271
	if (ps != null) {
1272
		return ps.getLastShownDescendant();
1273
	}
1274
	var p = this.getParent();
1275
	var t = this.getTree();
1276
	if (!t.showRootNode && p == t) {
1277
		return null;
1278
	}
1279
	return p;
1280
};
1281

    
1282

    
1283

    
1284

    
1285

    
1286

    
1287

    
1288
///////////////////////////////////////////////////////////////////////////////
1289
// WebFXTree
1290
///////////////////////////////////////////////////////////////////////////////
1291

    
1292
function WebFXTree(sText, oAction, sBehavior, sIcon, sOpenIcon) {
1293
	WebFXTreeAbstractNode.call(this, sText, oAction);
1294
	if (sIcon) this.icon = sIcon;
1295
	if (sOpenIcon) this.openIcon = sOpenIcon;
1296
	if (sBehavior) this.behavior = sBehavior;
1297
}
1298

    
1299
_p = WebFXTree.prototype = new WebFXTreeAbstractNode;
1300
_p.indentWidth = 19;
1301
_p.open = true;
1302
_p._selectedItem = null;
1303
_p._fireChange = true;
1304
_p.rendered = false;
1305
_p.suspendRedraw = false;
1306
_p.showLines = true;
1307
_p.showExpandIcons = true;
1308
_p.showRootNode = true;
1309
_p.showRootLines = true;
1310

    
1311
_p.getTree = function () {
1312
	return this;
1313
};
1314

    
1315
_p.getDepth = function () {
1316
	return 0;
1317
};
1318

    
1319
_p.getCreated = function () {
1320
	return this.rendered;
1321
};
1322

    
1323

    
1324
/* end tree model */
1325

    
1326
_p.getExpanded = function () {
1327
	return !this.showRootNode || WebFXTreeAbstractNode.prototype.getExpanded.call(this);
1328
};
1329

    
1330
_p.setExpanded = function (b) {
1331
	if (!this.showRootNode) {
1332
		this.open = b;
1333
	} else {
1334
		WebFXTreeAbstractNode.prototype.setExpanded.call(this, b);
1335
	}
1336
};
1337

    
1338
_p.getExpandIconHtml = function () {
1339
	return "";
1340
};
1341

    
1342
// we don't have an expand icon here
1343
_p.getIconElement = function () {
1344
	var el = this.getRowElement();
1345
	if (!el) return null;
1346
	return el.firstChild;
1347
};
1348

    
1349
// no expand icon for root element
1350
_p.getExpandIconElement = function (oDoc) {
1351
	return null;
1352
};
1353

    
1354
_p.updateExpandIcon = function () {
1355
	// no expand icon
1356
};
1357

    
1358
_p.getRowClassName = function () {
1359
	return WebFXTreeAbstractNode.prototype.getRowClassName.call(this) +
1360
		(this.showRootNode ? "" : " webfx-tree-hide-root");
1361
};
1362

    
1363

    
1364
// if classic then the openIcon is used for expanded, otherwise openIcon is used
1365
// for selected
1366

    
1367
_p.getIconSrc = function () {
1368
	var behavior = this.getTree() ? this.getTree().getBehavior() : webFXTreeConfig.defaultBehavior;
1369
	var open = behavior == "classic" && this.getExpanded() ||
1370
			   behavior != "classic" && this.isSelected();
1371
	if (open && this.openIcon) {
1372
		return this.openIcon;
1373
	}
1374
	if (!open && this.icon) {
1375
		return this.icon;
1376
	}
1377
	// fall back on default icons
1378
	return open ? webFXTreeConfig.openRootIcon : webFXTreeConfig.rootIcon;
1379
};
1380

    
1381
_p.getEventHandlersHtml = function () {
1382
	return " onclick=\"return webFXTreeHandler.handleEvent(event)\" " +
1383
		"onmousedown=\"return webFXTreeHandler.handleEvent(event)\" " +
1384
		"ondblclick=\"return webFXTreeHandler.handleEvent(event)\" " +
1385
		"onkeydown=\"return webFXTreeHandler.handleEvent(event)\" " +
1386
		"onkeypress=\"return webFXTreeHandler.handleEvent(event)\"";
1387
};
1388

    
1389
_p.setSelected = function (o) {
1390
	if (this._selectedItem != o && o) {
1391
		o._setSelected(true);
1392
	}
1393
};
1394

    
1395
_p._fireOnChange = function () {
1396
	if (this._fireChange && typeof this.onchange == "function") {
1397
		this.onchange();
1398
	}
1399
};
1400

    
1401
_p.getSelected = function () {
1402
	return this._selectedItem;
1403
};
1404

    
1405
_p.tabIndex = "";
1406

    
1407
_p.setTabIndex = function (i) {
1408
	var n = this._selectedItem || (this.showRootNode ? this : this.firstChild);
1409
	this.tabIndex = i;
1410
	if (n) {
1411
		n._setTabIndex(i);
1412
	}	
1413
};
1414

    
1415
_p.getTabIndex = function () {
1416
	return this.tabIndex;
1417
};
1418

    
1419
_p.setBehavior = function (s) {
1420
	this.behavior = s;
1421
};
1422

    
1423
_p.getBehavior = function () {
1424
	return this.behavior || webFXTreeConfig.defaultBehavior;
1425
};
1426

    
1427
_p.setShowLines = function (b) {
1428
	if (this.showLines != b) {
1429
		this.showLines = b;
1430
		if (this.rendered) {
1431
			this.update();
1432
		}
1433
	}
1434
};
1435

    
1436
_p.getShowLines = function () {
1437
	return this.showLines;
1438
};
1439

    
1440
_p.setShowRootLines = function (b) {
1441
	if (this.showRootLines != b) {
1442
		this.showRootLines = b;
1443
		if (this.rendered) {
1444
			this.update();
1445
		}
1446
	}
1447
};
1448

    
1449
_p.getShowRootLines = function () {
1450
	return this.showRootLines;
1451
};
1452

    
1453
_p.setShowExpandIcons = function (b) {
1454
	if (this.showExpandIcons != b) {
1455
		this.showExpandIcons = b;
1456
		if (this.rendered) {
1457
			this.getTree().update();
1458
		}
1459
	}
1460
};
1461

    
1462
_p.getShowExpandIcons = function () {
1463
	return this.showExpandIcons;
1464
};
1465

    
1466
_p.setShowRootNode = function (b) {
1467
	if (this.showRootNode != b) {
1468
		this.showRootNode = b;
1469
		if (this.rendered) {
1470
			this.getTree().update();
1471
		}
1472
	}
1473
};
1474

    
1475
_p.getShowRoootNode = function () {
1476
	return this.showRootNode;
1477
};
1478

    
1479
_p.onchange = function () {};
1480

    
1481
_p.create = function () {
1482
	var el = WebFXTreeAbstractNode.prototype.create.call(this);
1483
	this.setTabIndex(this.tabIndex);
1484
	this.rendered = true;
1485
	return el;
1486
};
1487

    
1488
_p.write = function () {
1489
	document.write(this.toHtml());
1490
	this.setTabIndex(this.tabIndex);
1491
	this.rendered = true;
1492
};
1493

    
1494
_p.setSuspendRedraw = function (b) {
1495
	this.suspendRedraw = b;
1496
};
1497

    
1498
_p.getSuspendRedraw = function () {
1499
	return this.suspendRedraw;
1500
};
1501

    
1502

    
1503

    
1504
///////////////////////////////////////////////////////////////////////////////
1505
// WebFXTreeItem
1506
///////////////////////////////////////////////////////////////////////////////
1507

    
1508
function WebFXTreeItem(sText, oAction, eParent, sIcon, sOpenIcon) {
1509
	WebFXTreeAbstractNode.call(this, sText, oAction);
1510
	if (sIcon) this.icon = sIcon;
1511
	if (sOpenIcon) this.openIcon = sOpenIcon;
1512
	if (eParent) eParent.add(this);
1513
}
1514

    
1515
_p = WebFXTreeItem.prototype = new WebFXTreeAbstractNode;
1516
_p.tree = null;
1517

    
1518
/* tree model */
1519

    
1520
_p.getDepth = function () {
1521
	if (this.depth != null) {
1522
		return this.depth;
1523
	}
1524
	if (this.parentNode) {
1525
		var pd = this.parentNode.getDepth();
1526
		return this.depth = (pd != null ? pd + 1 : null);
1527
	}
1528
	return null;
1529
};
1530

    
1531
_p.getTree = function () {
1532
	if (this.tree) {
1533
		return this.tree;
1534
	}
1535
	if (this.parentNode) {
1536
		return this.tree = this.parentNode.getTree();
1537
	}
1538
	return null;
1539
};
1540

    
1541
_p.getCreated = function () {
1542
	var t = this.getTree();
1543
	return t && t.getCreated();
1544
};
1545

    
1546
// if classic then the openIcon is used for expanded, otherwise openIcon is used
1547
// for selected
1548
_p.getIconSrc = function () {
1549
	var behavior = this.getTree() ? this.getTree().getBehavior() : webFXTreeConfig.defaultBehavior;
1550
	var open = behavior == "classic" && this.getExpanded() ||
1551
	           behavior != "classic" && this.isSelected();
1552
	if (open && this.openIcon) {
1553
		return this.openIcon;
1554
	}
1555
	if (!open && this.icon) {
1556
		return this.icon;
1557
	}
1558

    
1559
	// fall back on default icons
1560
	if (this.hasChildren()) {
1561
		return open ? webFXTreeConfig.openFolderIcon : webFXTreeConfig.folderIcon;
1562
	}
1563
	return webFXTreeConfig.fileIcon;
1564
};
1565

    
1566
/* end tree model */
1567

    
1568

    
1569

    
1570

    
1571
if (window.attachEvent) {
1572
	window.attachEvent("onunload", function () {
1573
		for (var id in webFXTreeHandler.all)
1574
			webFXTreeHandler.all[id].dispose();
1575
	});
1576
}
(2-2/2)