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