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
, open
) {
21 this.iconOpen
= iconOpen
;
22 this._io
= open
|| false;
30 // Tree object// imagePath parameter added by SquirrelMail Team
31 function dTree(objName
, imagePath
) {
39 useStatusText
: false,
40 closeSameLevel
: false,
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'
62 this.root
= new Node(-1);
63 this.selectedNode
= null;
64 this.selectedFound
= false;
65 this.completed
= false; this.imagePath
= imagePath
;
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
);
73 // Open/close all nodes
74 dTree
.prototype.openAll = function() {
77 dTree
.prototype.closeAll = function() {
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.';
89 if (!this.selectedFound
) this.selectedNode
= null;
90 this.completed
= true;
94 // Creates the tree structure
95 dTree
.prototype.addNode = function(pNode
) {
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
];
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
) {
110 this.selectedNode
= n
;
111 this.selectedFound
= true;
113 str
+= this.node(cn
, n
);
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
;
130 str
+= '<img id="i' + this.obj
+ nodeId
+ '" src="' + ((node
._io
) ? node
.iconOpen
: node
.icon
) + '" alt="" />';
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
+ ');"';
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">';
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>';
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
);
156 // Adds the empty and line icons
157 dTree
.prototype.indent = function(node
, nodeId
) {
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);
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="" />';
173 // Checks if a node has any children and if it is the last sibling
174 dTree
.prototype.setCS = function(node
) {
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
;
180 if (lastId
==node
.id
) node
._ls
= true;
183 // Returns the selected node
184 dTree
.prototype.getSelected = function() {
185 var sn
= this.getCookie('cs' + this.obj
);
186 return (sn
) ? sn
: null;
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";
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
);
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
);
211 if (this.config
.closeSameLevel
) this.closeLevel(cn
);
212 if (this.config
.useCookies
) this.updateCookie();
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
;
223 if (this.config
.useCookies
) this.updateCookie();
226 // Opens the tree to a specific node
227 dTree
.prototype.openTo = function(nId
, bSelect
, bFirst
) {
229 for (var n
=0; n
<this.aNodes
.length
; n
++) {
230 if (this.aNodes
[n
].id
== nId
) {
236 var cn
=this.aNodes
[nId
];
237 if (cn
.pid
==this.root
.id
|| !cn
._p
) return;
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);
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
]);
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
]);
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
;
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';
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
);
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
) {
295 escape(cookieName
) + '=' + escape(cookieValue
)
296 + (expires
? '; expires=' + expires
.toGMTString() : '')
297 + (path
? '; path=' + path
: '')
298 + (domain
? '; domain=' + domain
: '')
299 + (secure
? '; secure' : '');
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
) + '=');
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
));
312 return (cookieValue
);
315 // [Cookie] Returns ids of open nodes as a string
316 dTree
.prototype.updateCookie = function() {
318 for (var n
=0; n
<this.aNodes
.length
; n
++) {
319 if (this.aNodes
[n
]._io
&& this.aNodes
[n
].pid
!= this.root
.id
) {
321 str
+= this.aNodes
[n
].id
;
324 this.setCookie('co' + this.obj
, str
);
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;
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
];
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);