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