Add access keys for menubar
[squirrelmail.git] / templates / default_advanced / js / dtree.js
1 /*--------------------------------------------------|
2 | dTree 2.05 | www.destroydrop.com/javascript/tree/ |
3 |---------------------------------------------------|
4 | Copyright (c) 2002-2003 Geir Landr? |
5 | |
6 | This script can be used freely as long as all |
7 | copyright messages are intact. |
8 | |
9 | Updated: 17.04.2003 |
10 |--------------------------------------------------*/
11
12 // Node object
13 function Node(id, pid, name, url, title, target, icon, iconOpen, open) {
14 this.id = id;
15 this.pid = pid;
16 this.name = name;
17 this.url = url;
18 this.title = title;
19 this.target = target;
20 this.icon = icon;
21 this.iconOpen = iconOpen;
22 this._io = open || false;
23 this._is = false;
24 this._ls = false;
25 this._hc = false;
26 this._ai = 0;
27 this._p;
28 };
29
30 // Tree object// imagePath parameter added by SquirrelMail Team
31 function dTree(objName, imagePath) {
32 this.config = {
33 target : null,
34 folderLinks : true,
35 useSelection : true,
36 useCookies : true,
37 useLines : true,
38 useIcons : true,
39 useStatusText : false,
40 closeSameLevel : false,
41 inOrder : false
42 }
43 this.icon = {
44 root : imagePath+'/base.png',
45 folder : imagePath+'/folder.png',
46 folderOpen : imagePath+'/folderopen.png',
47 node : imagePath+'/page.png',
48 empty : imagePath+'/empty.png',
49 line : imagePath+'/line.png',
50 join : imagePath+'/join.png',
51 joinBottom : imagePath+'/joinbottom.png',
52 plus : imagePath+'/plus_mid.png',
53 plusBottom : imagePath+'/plusbottom.png',
54 minus : imagePath+'/minus_mid.png',
55 minusBottom : imagePath+'/minusbottom.png',
56 nlPlus : imagePath+'/nolines_plus.png',
57 nlMinus : imagePath+'/nolines_minus.png'
58 };
59 this.obj = objName;
60 this.aNodes = [];
61 this.aIndent = [];
62 this.root = new Node(-1);
63 this.selectedNode = null;
64 this.selectedFound = false;
65 this.completed = false; this.imagePath = imagePath;
66 };
67
68 // Adds a new node to the node array
69 dTree.prototype.add = function(id, pid, name, url, title, target, icon, iconOpen, open) {
70 this.aNodes[this.aNodes.length] = new Node(id, pid, name, url, title, target, icon, iconOpen, open);
71 };
72
73 // Open/close all nodes
74 dTree.prototype.openAll = function() {
75 this.oAll(true);
76 };
77 dTree.prototype.closeAll = function() {
78 this.oAll(false);
79 };
80
81 // Outputs the tree to the page
82 dTree.prototype.toString = function() {
83 var str = '<div class="dtree">\n';
84 if (document.getElementById) {
85 if (this.config.useCookies) this.selectedNode = this.getSelected();
86 str += this.addNode(this.root);
87 } else str += 'Browser not supported.';
88 str += '</div>';
89 if (!this.selectedFound) this.selectedNode = null;
90 this.completed = true;
91 return str;
92 };
93
94 // Creates the tree structure
95 dTree.prototype.addNode = function(pNode) {
96 var str = '';
97 var n=0;
98 if (this.config.inOrder) n = pNode._ai;
99 for (n; n<this.aNodes.length; n++) {
100 if (this.aNodes[n].pid == pNode.id) {
101 var cn = this.aNodes[n];
102 cn._p = pNode;
103 cn._ai = n;
104 this.setCS(cn);
105 if (!cn.target && this.config.target) cn.target = this.config.target;
106 if (cn._hc && !cn._io && this.config.useCookies) cn._io = this.isOpen(cn.id);
107 if (!this.config.folderLinks && cn._hc) cn.url = null;
108 if (this.config.useSelection && cn.id == this.selectedNode && !this.selectedFound) {
109 cn._is = true;
110 this.selectedNode = n;
111 this.selectedFound = true;
112 }
113 str += this.node(cn, n);
114 if (cn._ls) break;
115 }
116 }
117 return str;
118 };
119
120 // Creates the node icon, url and text
121 dTree.prototype.node = function(node, nodeId) {
122 var str = '<div class="dTreeNode">' + this.indent(node, nodeId);
123 if (this.config.useIcons) {
124 if (!node.icon) node.icon = (this.root.id == node.pid) ? this.icon.root : ((node._hc) ? this.icon.folder : this.icon.node);
125 if (!node.iconOpen) node.iconOpen = (node._hc) ? this.icon.folderOpen : this.icon.node;
126 if (this.root.id == node.pid) {
127 node.icon = this.icon.root;
128 node.iconOpen = this.icon.root;
129 }
130 str += '<img id="i' + this.obj + nodeId + '" src="' + ((node._io) ? node.iconOpen : node.icon) + '" alt="" />';
131 }
132 if (node.url) {
133 str += '<a id="s' + this.obj + nodeId + '" class="' + ((this.config.useSelection) ? ((node._is ? 'nodeSel' : 'node')) : 'node') + '" href="' + node.url + '"';
134 if (node.title) str += ' title="' + node.title + '"';
135 if (node.target) str += ' target="' + node.target + '"';
136 if (this.config.useStatusText) str += ' onmouseover="window.status=\'' + node.name + '\';return true;" onmouseout="window.status=\'\';return true;" ';
137 if (this.config.useSelection && ((node._hc && this.config.folderLinks) || !node._hc))
138 str += ' onclick="javascript: ' + this.obj + '.s(' + nodeId + ');"';
139 str += '>';
140 }
141 else if ((!this.config.folderLinks || !node.url) && node._hc && node.pid != this.root.id)
142 str += '<a href="javascript: ' + this.obj + '.o(' + nodeId + ');" class="node">';
143 str += node.name;
144 //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)
145 if (node.url || ((!this.config.folderLinks || !node.url) && node._hc)) str += '</a>';
146 str += '</div>';
147 if (node._hc) {
148 str += '<div id="d' + this.obj + nodeId + '" class="clip" style="display:' + ((this.root.id == node.pid || node._io) ? 'block' : 'none') + ';">';
149 str += this.addNode(node);
150 str += '</div>';
151 }
152 this.aIndent.pop();
153 return str;
154 };
155
156 // Adds the empty and line icons
157 dTree.prototype.indent = function(node, nodeId) {
158 var str = '';
159 if (this.root.id != node.pid) {
160 for (var n=0; n<this.aIndent.length; n++)
161 str += '<img src="' + ( (this.aIndent[n] == 1 && this.config.useLines) ? this.icon.line : this.icon.empty ) + '" alt="" />';
162 (node._ls) ? this.aIndent.push(0) : this.aIndent.push(1);
163 if (node._hc) {
164 str += '<a href="javascript: ' + this.obj + '.o(' + nodeId + ');"><img id="j' + this.obj + nodeId + '" src="';
165 if (!this.config.useLines) str += (node._io) ? this.icon.nlMinus : this.icon.nlPlus;
166 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 ) );
167 str += '" alt="" /></a>';
168 } else str += '<img src="' + ( (this.config.useLines) ? ((node._ls) ? this.icon.joinBottom : this.icon.join ) : this.icon.empty) + '" alt="" />';
169 }
170 return str;
171 };
172
173 // Checks if a node has any children and if it is the last sibling
174 dTree.prototype.setCS = function(node) {
175 var lastId;
176 for (var n=0; n<this.aNodes.length; n++) {
177 if (this.aNodes[n].pid == node.id) node._hc = true;
178 if (this.aNodes[n].pid == node.pid) lastId = this.aNodes[n].id;
179 }
180 if (lastId==node.id) node._ls = true;
181 };
182
183 // Returns the selected node
184 dTree.prototype.getSelected = function() {
185 var sn = this.getCookie('cs' + this.obj);
186 return (sn) ? sn : null;
187 };
188
189 // Highlights the selected node
190 dTree.prototype.s = function(id) {
191 if (!this.config.useSelection) return;
192 var cn = this.aNodes[id];
193 if (cn._hc && !this.config.folderLinks) return;
194 if (this.selectedNode != id) {
195 if (this.selectedNode || this.selectedNode==0) {
196 eOld = document.getElementById("s" + this.obj + this.selectedNode);
197 eOld.className = "node";
198 }
199 eNew = document.getElementById("s" + this.obj + id);
200 eNew.className = "nodeSel";
201 this.selectedNode = id;
202 if (this.config.useCookies) this.setCookie('cs' + this.obj, cn.id);
203 }
204 };
205
206 // Toggle Open or close
207 dTree.prototype.o = function(id) {
208 var cn = this.aNodes[id];
209 this.nodeStatus(!cn._io, id, cn._ls);
210 cn._io = !cn._io;
211 if (this.config.closeSameLevel) this.closeLevel(cn);
212 if (this.config.useCookies) this.updateCookie();
213 };
214
215 // Open or close all nodes
216 dTree.prototype.oAll = function(status) {
217 for (var n=0; n<this.aNodes.length; n++) {
218 if (this.aNodes[n]._hc && this.aNodes[n].pid != this.root.id) {
219 this.nodeStatus(status, n, this.aNodes[n]._ls)
220 this.aNodes[n]._io = status;
221 }
222 }
223 if (this.config.useCookies) this.updateCookie();
224 };
225
226 // Opens the tree to a specific node
227 dTree.prototype.openTo = function(nId, bSelect, bFirst) {
228 if (!bFirst) {
229 for (var n=0; n<this.aNodes.length; n++) {
230 if (this.aNodes[n].id == nId) {
231 nId=n;
232 break;
233 }
234 }
235 }
236 var cn=this.aNodes[nId];
237 if (cn.pid==this.root.id || !cn._p) return;
238 cn._io = true;
239 cn._is = bSelect;
240 if (this.completed && cn._hc) this.nodeStatus(true, cn._ai, cn._ls);
241 if (this.completed && bSelect) this.s(cn._ai);
242 else if (bSelect) this._sn=cn._ai;
243 this.openTo(cn._p._ai, false, true);
244 };
245
246 // Closes all nodes on the same level as certain node
247 dTree.prototype.closeLevel = function(node) {
248 for (var n=0; n<this.aNodes.length; n++) {
249 if (this.aNodes[n].pid == node.pid && this.aNodes[n].id != node.id && this.aNodes[n]._hc) {
250 this.nodeStatus(false, n, this.aNodes[n]._ls);
251 this.aNodes[n]._io = false;
252 this.closeAllChildren(this.aNodes[n]);
253 }
254 }
255 }
256
257 // Closes all children of a node
258 dTree.prototype.closeAllChildren = function(node) {
259 for (var n=0; n<this.aNodes.length; n++) {
260 if (this.aNodes[n].pid == node.id && this.aNodes[n]._hc) {
261 if (this.aNodes[n]._io) this.nodeStatus(false, n, this.aNodes[n]._ls);
262 this.aNodes[n]._io = false;
263 this.closeAllChildren(this.aNodes[n]);
264 }
265 }
266 }
267
268 // Change the status of a node(open or closed)
269 dTree.prototype.nodeStatus = function(status, id, bottom) {
270 eDiv = document.getElementById('d' + this.obj + id);
271 eJoin = document.getElementById('j' + this.obj + id);
272 if (this.config.useIcons) {
273 eIcon = document.getElementById('i' + this.obj + id);
274 eIcon.src = (status) ? this.aNodes[id].iconOpen : this.aNodes[id].icon;
275 }
276 eJoin.src = (this.config.useLines)?
277 ((status)?((bottom)?this.icon.minusBottom:this.icon.minus):((bottom)?this.icon.plusBottom:this.icon.plus)):
278 ((status)?this.icon.nlMinus:this.icon.nlPlus);
279 eDiv.style.display = (status) ? 'block': 'none';
280 };
281
282
283 // [Cookie] Clears a cookie
284 dTree.prototype.clearCookie = function() {
285 var now = new Date();
286 var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);
287 this.setCookie('co'+this.obj, 'cookieValue', yesterday);
288 this.setCookie('cs'+this.obj, 'cookieValue', yesterday);
289 };
290
291 // [Cookie] Sets value in a cookie
292 // 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
293 dTree.prototype.setCookie = function(cookieName, cookieValue, expires, path, domain, secure) {
294 document.cookie =
295 escape(cookieName) + '=' + escape(cookieValue)
296 + (expires ? '; expires=' + expires.toGMTString() : '')
297 + (path ? '; path=' + path : '')
298 + (domain ? '; domain=' + domain : '')
299 + (secure ? '; secure' : '');
300 };
301
302 // [Cookie] Gets a value from a cookie
303 dTree.prototype.getCookie = function(cookieName) {
304 var cookieValue = '';
305 var posName = document.cookie.indexOf(escape(cookieName) + '=');
306 if (posName != -1) {
307 var posValue = posName + (escape(cookieName) + '=').length;
308 var endPos = document.cookie.indexOf(';', posValue);
309 if (endPos != -1) cookieValue = unescape(document.cookie.substring(posValue, endPos));
310 else cookieValue = unescape(document.cookie.substring(posValue));
311 }
312 return (cookieValue);
313 };
314
315 // [Cookie] Returns ids of open nodes as a string
316 dTree.prototype.updateCookie = function() {
317 var str = '';
318 for (var n=0; n<this.aNodes.length; n++) {
319 if (this.aNodes[n]._io && this.aNodes[n].pid != this.root.id) {
320 if (str) str += '.';
321 str += this.aNodes[n].id;
322 }
323 }
324 this.setCookie('co' + this.obj, str);
325 };
326
327 // [Cookie] Checks if a node id is in a cookie
328 dTree.prototype.isOpen = function(id) {
329 var aOpen = this.getCookie('co' + this.obj).split('.');
330 for (var n=0; n<aOpen.length; n++)
331 if (aOpen[n] == id) return true;
332 return false;
333 };
334
335 // If Push and pop is not implemented by the browser
336 if (!Array.prototype.push) {
337 Array.prototype.push = function array_push() {
338 for(var i=0;i<arguments.length;i++)
339 this[this.length]=arguments[i];
340 return this.length;
341 }
342 };
343 if (!Array.prototype.pop) {
344 Array.prototype.pop = function array_pop() {
345 lastElement = this[this.length-1];
346 this.length = Math.max(this.length-1,0);
347 return lastElement;
348 }
349 };