1 /*--------------------------------------------------|
2 | dTree 2.05 | www.destroydrop.com/javascript/tree/ |
3 |---------------------------------------------------|
4 | Copyright (c) 2002-2003 Geir Landr? |
6 | This script can be used freely as long as all |
7 | copyright messages are intact. |
9 | Updated: 17.04.2003 |
10 |--------------------------------------------------*/
13 function Node(id
, pid
, name
, url
, title
, target
, icon
, iconOpen
, accesskey
, open
) {
21 this.iconOpen
= iconOpen
;
22 this.accesskey
= accesskey
;
23 this._io
= open
|| false;
31 // Tree object// imagePath parameter added by SquirrelMail Team
32 function dTree(objName
, imagePath
) {
40 useStatusText
: false,
41 closeSameLevel
: false,
45 root
: imagePath
+'/base.png',
46 folder
: imagePath
+'/folder.png',
47 folderOpen
: imagePath
+'/folderopen.png',
48 node
: imagePath
+'/page.png',
49 empty
: imagePath
+'/empty.png',
50 line
: imagePath
+'/line.png',
51 join
: imagePath
+'/join.png',
52 joinBottom
: imagePath
+'/joinbottom.png',
53 plus
: imagePath
+'/plus_mid.png',
54 plusBottom
: imagePath
+'/plusbottom.png',
55 minus
: imagePath
+'/minus_mid.png',
56 minusBottom
: imagePath
+'/minusbottom.png',
57 nlPlus
: imagePath
+'/nolines_plus.png',
58 nlMinus
: imagePath
+'/nolines_minus.png'
63 this.root
= new Node(-1);
64 this.selectedNode
= null;
65 this.selectedFound
= false;
66 this.completed
= false; this.imagePath
= imagePath
;
69 // Adds a new node to the node array
70 dTree
.prototype.add = function(id
, pid
, name
, url
, title
, target
, icon
, iconOpen
, accesskey
, open
) {
71 this.aNodes
[this.aNodes
.length
] = new Node(id
, pid
, name
, url
, title
, target
, icon
, iconOpen
, accesskey
, open
);
74 // Open/close all nodes
75 dTree
.prototype.openAll = function() {
78 dTree
.prototype.closeAll = function() {
82 // Outputs the tree to the page
83 dTree
.prototype.toString = function() {
84 var str
= '<div class="dtree">\n';
85 if (document
.getElementById
) {
86 if (this.config
.useCookies
) this.selectedNode
= this.getSelected();
87 str
+= this.addNode(this.root
);
88 } else str
+= 'Browser not supported.';
90 if (!this.selectedFound
) this.selectedNode
= null;
91 this.completed
= true;
95 // Creates the tree structure
96 dTree
.prototype.addNode = function(pNode
) {
99 if (this.config
.inOrder
) n
= pNode
._ai
;
100 for (n
; n
<this.aNodes
.length
; n
++) {
101 if (this.aNodes
[n
].pid
== pNode
.id
) {
102 var cn
= this.aNodes
[n
];
106 if (!cn
.target
&& this.config
.target
) cn
.target
= this.config
.target
;
107 if (cn
._hc
&& !cn
._io
&& this.config
.useCookies
) cn
._io
= this.isOpen(cn
.id
);
108 if (!this.config
.folderLinks
&& cn
._hc
) cn
.url
= null;
109 if (this.config
.useSelection
&& cn
.id
== this.selectedNode
&& !this.selectedFound
) {
111 this.selectedNode
= n
;
112 this.selectedFound
= true;
114 str
+= this.node(cn
, n
);
121 // Creates the node icon, url and text
122 dTree
.prototype.node = function(node
, nodeId
) {
123 var str
= '<div class="dTreeNode">' + this.indent(node
, nodeId
);
124 if (this.config
.useIcons
) {
125 if (!node
.icon
) node
.icon
= (this.root
.id
== node
.pid
) ? this.icon
.root
: ((node
._hc
) ? this.icon
.folder
: this.icon
.node
);
126 if (!node
.iconOpen
) node
.iconOpen
= (node
._hc
) ? this.icon
.folderOpen
: this.icon
.node
;
127 if (this.root
.id
== node
.pid
) {
128 node
.icon
= this.icon
.root
;
129 node
.iconOpen
= this.icon
.root
;
131 str
+= '<img id="i' + this.obj
+ nodeId
+ '" src="' + ((node
._io
) ? node
.iconOpen
: node
.icon
) + '" alt="" />';
134 str
+= '<a id="s' + this.obj
+ nodeId
+ '" class="' + ((this.config
.useSelection
) ? ((node
._is
? 'nodeSel' : 'node')) : 'node') + '" href="' + node
.url
+ '"';
135 if (node
.title
) str
+= ' title="' + node
.title
+ '"';
136 if (node
.target
) str
+= ' target="' + node
.target
+ '"';
137 if (node
.accesskey
) str
+= ' accesskey="' + node
.accesskey
+ '"';
138 if (this.config
.useStatusText
) str
+= ' onmouseover="window.status=\'' + node
.name
+ '\';return true;" onmouseout="window.status=\'\';return true;" ';
139 if (this.config
.useSelection
&& ((node
._hc
&& this.config
.folderLinks
) || !node
._hc
))
140 str
+= ' onclick="javascript: ' + this.obj
+ '.s(' + nodeId
+ ');"';
143 else if ((!this.config
.folderLinks
|| !node
.url
) && node
._hc
&& node
.pid
!= this.root
.id
)
144 str
+= '<a href="javascript: ' + this.obj
+ '.o(' + nodeId
+ ');" class="node">';
146 //FIXME: if node.name contains a hyperlink, either the plugin that put it there should be responsible for adding the </a> for the main folder link or the line below should be changed to detect inner links and close the main one before any; for now, it seems to work anyway although I think the resultant HTML is invalid (hyperlink within a hyperlink and extra, out of place closing </a> at the end)
147 if (node
.url
|| ((!this.config
.folderLinks
|| !node
.url
) && node
._hc
)) str
+= '</a>';
150 str
+= '<div id="d' + this.obj
+ nodeId
+ '" class="clip" style="display:' + ((this.root
.id
== node
.pid
|| node
._io
) ? 'block' : 'none') + ';">';
151 str
+= this.addNode(node
);
158 // Adds the empty and line icons
159 dTree
.prototype.indent = function(node
, nodeId
) {
161 if (this.root
.id
!= node
.pid
) {
162 for (var n
=0; n
<this.aIndent
.length
; n
++)
163 str
+= '<img src="' + ( (this.aIndent
[n
] == 1 && this.config
.useLines
) ? this.icon
.line
: this.icon
.empty
) + '" alt="" />';
164 (node
._ls
) ? this.aIndent
.push(0) : this.aIndent
.push(1);
166 str
+= '<a href="javascript: ' + this.obj
+ '.o(' + nodeId
+ ');"><img id="j' + this.obj
+ nodeId
+ '" src="';
167 if (!this.config
.useLines
) str
+= (node
._io
) ? this.icon
.nlMinus
: this.icon
.nlPlus
;
168 else str
+= ( (node
._io
) ? ((node
._ls
&& this.config
.useLines
) ? this.icon
.minusBottom
: this.icon
.minus
) : ((node
._ls
&& this.config
.useLines
) ? this.icon
.plusBottom
: this.icon
.plus
) );
169 str
+= '" alt="" /></a>';
170 } else str
+= '<img src="' + ( (this.config
.useLines
) ? ((node
._ls
) ? this.icon
.joinBottom
: this.icon
.join
) : this.icon
.empty
) + '" alt="" />';
175 // Checks if a node has any children and if it is the last sibling
176 dTree
.prototype.setCS = function(node
) {
178 for (var n
=0; n
<this.aNodes
.length
; n
++) {
179 if (this.aNodes
[n
].pid
== node
.id
) node
._hc
= true;
180 if (this.aNodes
[n
].pid
== node
.pid
) lastId
= this.aNodes
[n
].id
;
182 if (lastId
==node
.id
) node
._ls
= true;
185 // Returns the selected node
186 dTree
.prototype.getSelected = function() {
187 var sn
= this.getCookie('cs' + this.obj
);
188 return (sn
) ? sn
: null;
191 // Highlights the selected node
192 dTree
.prototype.s = function(id
) {
193 if (!this.config
.useSelection
) return;
194 var cn
= this.aNodes
[id
];
195 if (cn
._hc
&& !this.config
.folderLinks
) return;
196 if (this.selectedNode
!= id
) {
197 if (this.selectedNode
|| this.selectedNode
==0) {
198 eOld
= document
.getElementById("s" + this.obj
+ this.selectedNode
);
199 eOld
.className
= "node";
201 eNew
= document
.getElementById("s" + this.obj
+ id
);
202 eNew
.className
= "nodeSel";
203 this.selectedNode
= id
;
204 if (this.config
.useCookies
) this.setCookie('cs' + this.obj
, cn
.id
);
208 // Toggle Open or close
209 dTree
.prototype.o = function(id
) {
210 var cn
= this.aNodes
[id
];
211 this.nodeStatus(!cn
._io
, id
, cn
._ls
);
213 if (this.config
.closeSameLevel
) this.closeLevel(cn
);
214 if (this.config
.useCookies
) this.updateCookie();
217 // Open or close all nodes
218 dTree
.prototype.oAll = function(status
) {
219 for (var n
=0; n
<this.aNodes
.length
; n
++) {
220 if (this.aNodes
[n
]._hc
&& this.aNodes
[n
].pid
!= this.root
.id
) {
221 this.nodeStatus(status
, n
, this.aNodes
[n
]._ls
)
222 this.aNodes
[n
]._io
= status
;
225 if (this.config
.useCookies
) this.updateCookie();
228 // Opens the tree to a specific node
229 dTree
.prototype.openTo = function(nId
, bSelect
, bFirst
) {
231 for (var n
=0; n
<this.aNodes
.length
; n
++) {
232 if (this.aNodes
[n
].id
== nId
) {
238 var cn
=this.aNodes
[nId
];
239 if (cn
.pid
==this.root
.id
|| !cn
._p
) return;
242 if (this.completed
&& cn
._hc
) this.nodeStatus(true, cn
._ai
, cn
._ls
);
243 if (this.completed
&& bSelect
) this.s(cn
._ai
);
244 else if (bSelect
) this._sn
=cn
._ai
;
245 this.openTo(cn
._p
._ai
, false, true);
248 // Closes all nodes on the same level as certain node
249 dTree
.prototype.closeLevel = function(node
) {
250 for (var n
=0; n
<this.aNodes
.length
; n
++) {
251 if (this.aNodes
[n
].pid
== node
.pid
&& this.aNodes
[n
].id
!= node
.id
&& this.aNodes
[n
]._hc
) {
252 this.nodeStatus(false, n
, this.aNodes
[n
]._ls
);
253 this.aNodes
[n
]._io
= false;
254 this.closeAllChildren(this.aNodes
[n
]);
259 // Closes all children of a node
260 dTree
.prototype.closeAllChildren = function(node
) {
261 for (var n
=0; n
<this.aNodes
.length
; n
++) {
262 if (this.aNodes
[n
].pid
== node
.id
&& this.aNodes
[n
]._hc
) {
263 if (this.aNodes
[n
]._io
) this.nodeStatus(false, n
, this.aNodes
[n
]._ls
);
264 this.aNodes
[n
]._io
= false;
265 this.closeAllChildren(this.aNodes
[n
]);
270 // Change the status of a node(open or closed)
271 dTree
.prototype.nodeStatus = function(status
, id
, bottom
) {
272 eDiv
= document
.getElementById('d' + this.obj
+ id
);
273 eJoin
= document
.getElementById('j' + this.obj
+ id
);
274 if (this.config
.useIcons
) {
275 eIcon
= document
.getElementById('i' + this.obj
+ id
);
276 eIcon
.src
= (status
) ? this.aNodes
[id
].iconOpen
: this.aNodes
[id
].icon
;
278 eJoin
.src
= (this.config
.useLines
)?
279 ((status
)?((bottom
)?this.icon
.minusBottom
:this.icon
.minus
):((bottom
)?this.icon
.plusBottom
:this.icon
.plus
)):
280 ((status
)?this.icon
.nlMinus
:this.icon
.nlPlus
);
281 eDiv
.style
.display
= (status
) ? 'block': 'none';
285 // [Cookie] Clears a cookie
286 dTree
.prototype.clearCookie = function() {
287 var now
= new Date();
288 var yesterday
= new Date(now
.getTime() - 1000 * 60 * 60 * 24);
289 this.setCookie('co'+this.obj
, 'cookieValue', yesterday
);
290 this.setCookie('cs'+this.obj
, 'cookieValue', yesterday
);
293 // [Cookie] Sets value in a cookie
294 // FIXME: although setCookie() supports the secure flag, it isn't used when called anywhere in this file; ideally it should correspond to how the flag is determined in the SM core in sqsetcookie() (including the admin config that turns the secure flag off)... also, would be good to add the HTTP only flag
295 dTree
.prototype.setCookie = function(cookieName
, cookieValue
, expires
, path
, domain
, secure
) {
297 escape(cookieName
) + '=' + escape(cookieValue
)
298 + (expires
? '; expires=' + expires
.toGMTString() : '')
299 + (path
? '; path=' + path
: '')
300 + (domain
? '; domain=' + domain
: '')
301 + (secure
? '; secure' : '');
304 // [Cookie] Gets a value from a cookie
305 dTree
.prototype.getCookie = function(cookieName
) {
306 var cookieValue
= '';
307 var posName
= document
.cookie
.indexOf(escape(cookieName
) + '=');
309 var posValue
= posName
+ (escape(cookieName
) + '=').length
;
310 var endPos
= document
.cookie
.indexOf(';', posValue
);
311 if (endPos
!= -1) cookieValue
= unescape(document
.cookie
.substring(posValue
, endPos
));
312 else cookieValue
= unescape(document
.cookie
.substring(posValue
));
314 return (cookieValue
);
317 // [Cookie] Returns ids of open nodes as a string
318 dTree
.prototype.updateCookie = function() {
320 for (var n
=0; n
<this.aNodes
.length
; n
++) {
321 if (this.aNodes
[n
]._io
&& this.aNodes
[n
].pid
!= this.root
.id
) {
323 str
+= this.aNodes
[n
].id
;
326 this.setCookie('co' + this.obj
, str
);
329 // [Cookie] Checks if a node id is in a cookie
330 dTree
.prototype.isOpen = function(id
) {
331 var aOpen
= this.getCookie('co' + this.obj
).split('.');
332 for (var n
=0; n
<aOpen
.length
; n
++)
333 if (aOpen
[n
] == id
) return true;
337 // If Push and pop is not implemented by the browser
338 if (!Array
.prototype.push
) {
339 Array
.prototype.push
= function array_push() {
340 for(var i
=0;i
<arguments
.length
;i
++)
341 this[this.length
]=arguments
[i
];
345 if (!Array
.prototype.pop
) {
346 Array
.prototype.pop
= function array_pop() {
347 lastElement
= this[this.length
-1];
348 this.length
= Math
.max(this.length
-1,0);