]> err.no Git - scalable-opengroupware.org/blob - UI/WebServerResources/generic.js
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1078 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / WebServerResources / generic.js
1 /*
2   Copyright (C) 2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21 /* some generic JavaScript code for SOGo */
22
23 /* generic stuff */
24
25 var logConsole;
26 var logWindow = null;
27
28 var queryParameters;
29
30 var activeAjaxRequests = 0;
31 var menus = new Array();
32
33 // logArea = null;
34 var allDocumentElements = null;
35
36 /* a W3C compliant document.all */
37 function getAllScopeElements(scope) {
38   var elements = new Array();
39
40   for (var i = 0; i < scope.childNodes.length; i++)
41     if (typeof(scope.childNodes[i]) == "object"
42         && scope.childNodes[i].tagName
43         && scope.childNodes[i].tagName != '')
44       {
45         elements.push(scope.childNodes[i]);
46         var childElements = getAllElements(scope.childNodes[i]);
47         if (childElements.length > 0)
48           elements.push(childElements);
49       }
50
51   return elements;
52 }
53
54 function getAllElements(scope) {
55   var elements;
56
57   if (scope == null)
58     scope = document;
59
60   if (scope == document
61       && allDocumentElements != null)
62     elements = allDocumentElements;
63   else
64     {
65       elements = getAllScopeElements(scope);
66       if (scope == document)
67         allDocumentElements = elements;
68     }
69
70   return elements;
71 }
72
73 /* from
74    http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/ */
75 function getElementsByClassName2(_tag, _class, _scope) {
76   var regexp, classes, elements, element, returnElements;
77
78   _scope = _scope || document;
79
80   elements = (!_tag || _tag == "*"
81               ? getAllElements(null)
82               : _scope.getElementsByTagName(_tag));
83   returnElements = [];
84
85   classes = _class.split(/\s+/);
86   regexp = new RegExp("(^|\s+)("+ classes.join("|") +")(\s+|$)","i");
87
88   if (_class) {
89     for(var i = 0; element = elements[i]; i++) {
90       if (regexp.test(element.className)) {
91         returnElements.push(element);
92       }
93     }
94     return returnElements;
95   } else {
96     return elements;
97   }
98 }
99
100 function ml_stripActionInURL(url) {
101   if (url[url.length - 1] != '/') {
102     var i;
103     
104     i = url.lastIndexOf("/");
105     if (i != -1) url = url.substring(0, i);
106   }
107   if (url[url.length - 1] != '/') // ensure trailing slash
108     url = url + "/";
109   return url;
110 }
111
112 function URLForFolderID(folderID) {
113    var folderInfos = folderID.split(":");
114    var url;
115    if (folderInfos.length > 1) {
116       url = UserFolderURL + "../" + folderInfos[0];
117       if (folderInfos[1][0] != '/')
118         url += '/';
119       url += folderInfos[1];
120    }
121    else
122       url = ApplicationBaseURL + folderInfos[0];
123    
124    if (url[url.length-1] == '/')
125       url = url.substr(0, url.length-1);
126
127    return url;
128 }
129
130 function extractEmailAddress(mailTo) {
131   var email = "";
132
133   var emailre
134     = /([a-zA-Z0-9]+[a-zA-Z0-9\._-]+[a-zA-Z0-9]+@[a-zA-Z0-9]+[a-zA-Z0-9\._-]+[a-zA-Z0-9]+)/g;
135   if (emailre.test(mailTo)) {
136     emailre.exec(mailTo);
137     email = RegExp.$1;
138   }
139
140   return email;
141 }
142
143 function extractEmailName(mailTo) {
144   var emailName = "";
145
146    var tmpMailTo = mailTo.replace("&lt;", "<");
147    tmpMailTo = tmpMailTo.replace("&gt;", ">");
148
149    var emailNamere = /([        ]+)?(.+)\ </;
150    if (emailNamere.test(tmpMailTo)) {
151       emailNamere.exec(tmpMailTo);
152       emailName = RegExp.$2;
153    }
154    
155    return emailName;
156 }
157
158 function sanitizeMailTo(dirtyMailTo) {
159    var emailName = extractEmailName(dirtyMailTo);
160    var email = "" + extractEmailAddress(dirtyMailTo);
161    
162    var mailto = "";
163    if (emailName && emailName.length > 0)
164       mailto = emailName + ' <' + email + '>';
165    else
166       mailto = email;
167
168    return mailto;
169 }
170
171 function openUserFolderSelector(callback, type) {
172    var urlstr = ApplicationBaseURL;
173    if (urlstr[urlstr.length-1] != '/')
174       urlstr += '/';
175    urlstr += ("../../" + UserLogin + "/Contacts/userFolders");
176    var w = window.open(urlstr, "User Selector",
177                        "width=322,height=250,resizable=1,scrollbars=0");
178    w.opener = window;
179    w.userFolderCallback = callback;
180    w.userFolderType = type;
181    w.focus();
182 }
183
184 function openMailComposeWindow(url) {
185   var w = window.open(url, null,
186                       "width=680,height=520,resizable=1,scrollbars=1,toolbar=0,"
187                       + "location=0,directories=0,status=0,menubar=0"
188                       + ",copyhistory=0");
189   w.focus();
190
191   return w;
192 }
193
194 function openMailTo(senderMailTo) {
195    var mailto = sanitizeMailTo(senderMailTo);
196    if (mailto.length > 0)
197       openMailComposeWindow(ApplicationBaseURL
198                             + "/../Mail/compose?mailto=" + mailto);
199
200   return false; /* stop following the link */
201 }
202
203 function createHTTPClient() {
204   // http://developer.apple.com/internet/webcontent/xmlhttpreq.html
205   if (typeof XMLHttpRequest != "undefined")
206     return new XMLHttpRequest();
207   
208   try { return new ActiveXObject("Msxml2.XMLHTTP"); } 
209   catch (e) { }
210   try { return new ActiveXObject("Microsoft.XMLHTTP"); } 
211   catch (e) { }
212
213   return null;
214 }
215
216 function triggerAjaxRequest(url, callback, userdata) {
217   var http = createHTTPClient();
218
219   activeAjaxRequests += 1;
220   document.animTimer = setTimeout("checkAjaxRequestsState();", 200);
221
222   if (http) {
223     http.onreadystatechange
224       = function() {
225 //         log ("state changed (" + http.readyState + "): " + url);
226         try {
227           if (http.readyState == 4
228               && activeAjaxRequests > 0) {
229                 if (!http.aborted) {
230                   http.callbackData = userdata;
231                   callback(http);
232                 }
233                 activeAjaxRequests -= 1;
234                 checkAjaxRequestsState();
235               }
236         }
237         catch( e ) {
238           activeAjaxRequests -= 1;
239           checkAjaxRequestsState();
240           log("AJAX Request, Caught Exception: " + e.name);
241           log(e.message);
242           log(backtrace());
243         }
244       };
245     http.url = url;
246     http.open("GET", url, true);
247     http.send("");
248   }
249
250   return http;
251 }
252
253 function checkAjaxRequestsState() {
254   var toolbar = document.getElementById("toolbar");
255   if (toolbar) {
256     if (activeAjaxRequests > 0
257         && !document.busyAnim) {
258       var anim = document.createElement("img");
259       anim = $(anim);
260       document.busyAnim = anim;
261       anim.id = "progressIndicator";
262       anim.src = ResourcesURL + "/busy.gif";
263       anim.setStyle({ visibility: "hidden" });
264       toolbar.appendChild(anim);
265       anim.setStyle({ visibility: "visible" });
266     }
267     else if (activeAjaxRequests == 0
268              && document.busyAnim
269              && document.busyAnim.parentNode) {
270       document.busyAnim.parentNode.removeChild(document.busyAnim);
271       document.busyAnim = null;
272     }
273   }
274 }
275
276 function getTarget(event) {
277   event = event || window.event;
278   if (event.target)
279     return event.target; // W3C DOM
280   else
281     return event.srcElement; // IE
282 }
283
284 function preventDefault(event) {
285   if (event.preventDefault)
286     event.preventDefault(); // W3C DOM
287   else
288     event.returnValue = false; // IE
289 }
290
291 function resetSelection(win) {
292   var t = "";
293   if (win && win.getSelection) {
294     t = win.getSelection().toString();
295     win.getSelection().removeAllRanges();
296   }
297   return t;
298 }
299
300 function refreshOpener() {
301   if (window.opener && !window.opener.closed) {
302     window.opener.location.reload();
303   }
304 }
305
306 /* query string */
307
308 function parseQueryString() {
309   var queryArray, queryDict
310   var key, value, s, idx;
311   queryDict.length = 0;
312   
313   queryDict  = new Array();
314   queryArray = location.search.substr(1).split('&');
315   for (var i in queryArray) {
316     if (!queryArray[i]) continue ;
317     s   = queryArray[i];
318     idx = s.indexOf("=");
319     if (idx == -1) {
320       key   = s;
321       value = "";
322     }
323     else {
324       key   = s.substr(0, idx);
325       value = unescape(s.substr(idx + 1));
326     }
327     
328     if (typeof queryDict[key] == 'undefined')
329       queryDict.length++;
330     
331     queryDict[key] = value;
332   }
333   return queryDict;
334 }
335
336 function generateQueryString(queryDict) {
337   var s = "";
338   for (var key in queryDict) {
339     if (s.length == 0)
340       s = "?";
341     else
342       s = s + "&";
343     s = s + key + "=" + escape(queryDict[key]);
344   }
345   return s;
346 }
347
348 function getQueryParaArray(s) {
349   if (s.charAt(0) == "?") s = s.substr(1, s.length - 1);
350   return s.split("&");
351 }
352
353 function getQueryParaValue(s, name) {
354   var t;
355   
356   t = getQueryParaArray(s);
357   for (var i = 0; i < t.length; i++) {
358     var s = t[i];
359     
360     if (s.indexOf(name) != 0)
361       continue;
362     
363     s = s.substr(name.length, s.length - name.length);
364     return decodeURIComponent(s);
365   }
366   return null;
367 }
368
369 /* opener callback */
370
371 function triggerOpenerCallback() {
372   /* this code has some issue if the folder has no proper trailing slash! */
373   if (window.opener && !window.opener.closed) {
374     var t, cburl;
375     
376     t = getQueryParaValue(window.location.search, "openerurl=");
377     cburl = window.opener.location.href;
378     if (cburl[cburl.length - 1] != "/") {
379       cburl = cburl.substr(0, cburl.lastIndexOf("/") + 1);
380     }
381     cburl = cburl + t;
382     window.opener.location.href = cburl;
383   }
384 }
385
386 /* selection mechanism */
387
388 function deselectAll(parent) {
389   for (var i = 0; i < parent.childNodes.length; i++) {
390     var node = parent.childNodes.item(i);
391     if (node.nodeType == 1)
392       node.deselect();
393   }
394 }
395
396 function isNodeSelected(node) {
397   var classStr = '' + node.getAttribute('class');
398   var position = classStr.indexOf('_selected', 0);
399
400   return (position > -1);
401 }
402
403 function acceptMultiSelect(node) {
404   var accept = ('' + node.getAttribute('multiselect')).toLowerCase();
405
406   return (accept == 'yes');
407 }
408
409 function onRowClick(event) {
410   var node = getTarget(event);
411
412   if (node.tagName == 'TD')
413     node = node.parentNode;
414
415   var startSelection = node.parentNode.getSelectedNodes();
416   if (event.shiftKey == 1
417       && (acceptMultiSelect(node.parentNode)
418           || acceptMultiSelect(node.parentNode.parentNode))) {
419     if (isNodeSelected(node) == true) {
420       node.deselect();
421     } else {
422       node.select();
423     }
424   } else {
425     deselectAll(node.parentNode);
426     node.select();
427   }
428
429   if (startSelection != node.parentNode.getSelectedNodes()) {
430     var parentNode = node.parentNode;
431     if (parentNode.tagName == 'TBODY')
432       parentNode = parentNode.parentNode;
433     log("onRowClick: parentNode = " + parentNode.tagName);
434     if (document.createEvent) {
435       var onSelectionChangeEvent = document.createEvent("UIEvents");
436       onSelectionChangeEvent.initEvent("selectionchange", true, true);
437       parentNode.dispatchEvent(onSelectionChangeEvent);
438     }
439     else if (document.createEventObject) {
440       // TODO: add support for IE
441       //parentNode.fireEvent("change");
442       //parentNode is UL or TABLE
443     }
444   }
445 }
446
447 /* popup menus */
448
449 // var acceptClick = false;
450
451 function popupMenu(event, menuId, target) {
452    document.menuTarget = target;
453
454    if (document.currentPopupMenu)
455        hideMenu(event, document.currentPopupMenu);
456
457    var popup = $(menuId);
458    var menuTop = event.pageY;
459    var menuLeft = event.pageX;
460    var heightDiff = (window.innerHeight
461                      - (menuTop + popup.offsetHeight));
462    if (heightDiff < 0)
463       menuTop += heightDiff;
464   
465    var leftDiff = (window.innerWidth
466                    - (menuLeft + popup.offsetWidth));
467    if (leftDiff < 0)
468       menuLeft -= popup.offsetWidth;
469
470    popup.setStyle({ top: menuTop + "px",
471                     left: menuLeft + "px",
472                     visibility: "visible" });
473
474    document.currentPopupMenu = popup;
475    Event.observe(document.body, "click", onBodyClickMenuHandler);
476
477    preventDefault(event);
478 }
479
480 function getParentMenu(node) {
481   var currentNode, menuNode;
482
483   menuNode = null;
484   currentNode = node;
485   var menure = new RegExp("(^|\s+)menu(\s+|$)", "i");
486
487   while (menuNode == null
488          && currentNode)
489     if (menure.test(currentNode.className))
490       menuNode = currentNode;
491     else
492       currentNode = currentNode.parentNode;
493
494   return menuNode;
495 }
496
497 function onBodyClickMenuHandler(event) {
498    document.body.menuTarget = null;
499    hideMenu(event, document.currentPopupMenu);
500    Event.stopObserving(document.body, "click", onBodyClickMenuHandler);
501
502    preventDefault(event);
503 }
504
505 function hideMenu(event, menuNode) {
506   var onHide;
507
508 //   log('hiding menu "' + menuNode.getAttribute('id') + '"');
509   if (menuNode.submenu) {
510     hideMenu(event, menuNode.submenu);
511     menuNode.submenu = null;
512   }
513
514   menuNode.setStyle({ visibility: "hidden" });
515   //   menuNode.hide();
516   if (menuNode.parentMenuItem) {
517     menuNode.parentMenuItem.setAttribute('class', 'submenu');
518     menuNode.parentMenuItem = null;
519     menuNode.parentMenu.setAttribute('onmousemove', null);
520     menuNode.parentMenu.submenuItem = null;
521     menuNode.parentMenu.submenu = null;
522     menuNode.parentMenu = null;
523   }
524
525   if (document.initEvent) {
526     var onhideEvent = document.createEvent("UIEvents");
527     onhideEvent.initEvent("hideMenu", false, true);
528     menuNode.dispatchEvent(onhideEvent);
529   }
530   else if (document.createEventObject) {
531     // TODO: add support for IE
532   }
533 }
534
535 function onMenuEntryClick(event) {
536   var node = event.target;
537
538   id = getParentMenu(node).menuTarget;
539 //   log("clicked " + id + "/" + id.tagName);
540
541   return false;
542 }
543
544 function parseQueryParameters(url) {
545   var parameters = new Array();
546
547   var params = url.split("?")[1];
548   if (params) {
549     var pairs = params.split("&");
550     for (var i = 0; i < pairs.length; i++) {
551       var pair = pairs[i].split("=");
552       parameters[pair[0]] = pair[1];
553     }
554   }
555
556   return parameters;
557 }
558
559 function initLogConsole() {
560     var logConsole = $("logConsole");
561     if (logConsole) {
562         logConsole.highlighted = false;
563         Event.observe(logConsole, "dblclick", onLogDblClick, false);
564         logConsole.innerHTML = "";
565         Event.observe(window, "keydown", onBodyKeyDown);
566     }
567 }
568
569 function onBodyKeyDown(event) {
570     if (event.keyCode == 27) {
571         toggleLogConsole();
572         preventDefault(event);
573     }
574 }
575
576 function onLogDblClick(event) {
577   var logConsole = $("logConsole");
578   logConsole.innerHTML = "";
579 }
580
581 function toggleLogConsole(event) {
582   var logConsole = $("logConsole");
583   var display = '' + logConsole.style.display;
584   if (display.length == 0) {
585     logConsole.setStyle({ display: 'block' });
586   } else {
587     logConsole.setStyle({ display: '' });
588   }
589   if (event)
590       preventDefault(event);
591 }
592
593 function log(message) {
594   if (!logWindow) {
595     logWindow = window;
596     while (logWindow.opener)
597       logWindow = logWindow.opener;
598   }
599   var logConsole = logWindow.document.getElementById("logConsole");
600   if (logConsole) {
601       logConsole.highlighted = !logConsole.highlighted;
602       var logMessage = message.replace("<", "&lt;", "g");
603       logMessage = logMessage.replace(" ", "&nbsp;", "g");
604       logMessage = logMessage.replace("\r\n", "<br />\n", "g");
605       logMessage = logMessage.replace("\n", "<br />\n", "g");
606       logMessage += '<br />' + "\n";
607       if (logConsole.highlighted)
608           logMessage = '<div class="highlighted">' + logMessage + '</div>';
609       logConsole.innerHTML += logMessage;
610   }
611 }
612
613 function backtrace() {
614    var func = backtrace.caller;
615    var str = "backtrace:\n";
616
617    while (func)
618    {
619       if (func.name)
620       {
621          str += "  " + func.name;
622          if (this)
623             str += " (" + this + ")";
624       }
625       else
626          str += "[anonymous]\n";
627
628       str += "\n";
629       func = func.caller;
630    }
631    str += "--\n";
632
633    return str;
634 }
635
636 function dropDownSubmenu(event) {
637    var node = this;
638    if (this.submenu && this.submenu != "") {
639       log ("submenu: " + this.submenu);
640       var submenuNode = $(this.submenu);
641       var parentNode = getParentMenu(node);
642       if (parentNode.submenu)
643          hideMenu(event, parentNode.submenu);
644       submenuNode.parentMenuItem = node;
645       submenuNode.parentMenu = parentNode;
646       parentNode.submenuItem = node;
647       parentNode.submenu = submenuNode;
648       
649       var menuTop = (node.offsetTop - 2);
650       
651       var heightDiff = (window.innerHeight
652                         - (menuTop + submenuNode.offsetHeight));
653       if (heightDiff < 0)
654          menuTop += heightDiff;
655       
656       var menuLeft = parentNode.offsetWidth - 3;
657       if (window.innerWidth
658           < (menuLeft + submenuNode.offsetWidth
659              + parentNode.cascadeLeftOffset()))
660          menuLeft = - submenuNode.offsetWidth + 3;
661       
662       parentNode.setAttribute('onmousemove', 'checkDropDown(event);');
663       node.setAttribute('class', 'submenu-selected');
664       submenuNode.setStyle({ top: menuTop + "px",
665                                      left: menuLeft + "px",
666                                      visibility: "visible" });
667    }
668 }
669
670 function checkDropDown(event) {
671   var parentMenu = getParentMenu(event.target);
672   var submenuItem = parentMenu.submenuItem;
673   if (submenuItem) {
674     var menuX = event.clientX - parentMenu.cascadeLeftOffset();
675     var menuY = event.clientY - parentMenu.cascadeTopOffset();
676     var itemX = submenuItem.offsetLeft;
677     var itemY = submenuItem.offsetTop - 75;
678
679     if (menuX >= itemX
680         && menuX < itemX + submenuItem.offsetWidth
681         && (menuY < itemY
682             || menuY > (itemY + submenuItem.offsetHeight))) {
683       hideMenu(event, parentMenu.submenu);
684       parentMenu.submenu = null;
685       parentMenu.submenuItem = null;
686       parentMenu.setAttribute('onmousemove', null);
687     }
688   }
689 }
690
691 /* search field */
692 function popupSearchMenu(event) {
693   var node = getTarget(event);
694
695   var menuId = this.getAttribute("menuid");
696   relX = event.pageX - node.cascadeLeftOffset();
697   relY = event.pageY - node.cascadeTopOffset();
698
699   if (event.button == 0
700       && relX < 24) {
701     event.cancelBubble = true;
702     event.returnValue = false;
703
704     if (document.currentPopupMenu)
705       hideMenu(event, document.currentPopupMenu);
706
707     var popup = document.getElementById(menuId);
708     popup.setStyle({ top: node.offsetHeight + "px",
709                      left: (node.offsetLeft + 3) + "px",
710                             visibility: "visible" });
711   
712     document.currentPopupMenu = popup;
713     Event.observe(document.body, "click", onBodyClickMenuHandler);
714   }
715 }
716
717 function setSearchCriteria(event) {
718   searchValue = $("searchValue");
719   searchCriteria = $("searchCriteria");
720
721   searchValue.setAttribute("ghost-phrase", this.innerHTML);
722 //   searchCriteria = this.getAttribute('id');
723 }
724
725 function checkSearchValue(event) {
726   var form = event.target;
727   var searchValue = $("searchValue");
728   var ghostPhrase = searchValue.getAttribute('ghost-phrase');
729
730   if (searchValue.value == ghostPhrase)
731     searchValue.value = "";
732 }
733
734 function onSearchChange() {
735   log ("onSearchChange()...");
736 }
737
738 function onSearchMouseDown(event) {
739    var superNode = this.parentNode.parentNode.parentNode;
740    relX = (event.pageX - superNode.offsetLeft - this.offsetLeft);
741    relY = (event.pageY - superNode.offsetTop - this.offsetTop);
742
743    if (relY < 24) {
744       event.cancelBubble = true;
745       event.returnValue = false;
746    }
747 }
748
749 function onSearchFocus() {
750   ghostPhrase = this.getAttribute("ghost-phrase");
751   if (this.value == ghostPhrase) {
752     this.value = "";
753     this.setAttribute("modified", "");
754   } else {
755     this.select();
756   }
757
758   this.setStyle({ color: "#000" });
759 }
760
761 function onSearchBlur(event) {
762   var ghostPhrase = this.getAttribute("ghost-phrase");
763 //   log ("search blur: '" + this.value + "'");
764   if (!this.value) {
765     this.setAttribute("modified", "");
766     this.setStyle({ color: "#aaa" });
767     this.value = ghostPhrase;
768   } else if (this.value == ghostPhrase) {
769     this.setAttribute("modified", "");
770     this.setStyle({ color: "#aaa" });
771   } else {
772     this.setAttribute("modified", "yes");
773     this.setStyle({ color: "#000" });
774   }
775 }
776
777 function onSearchKeyDown(event) {
778   if (this.timer)
779     clearTimeout(this.timer);
780
781   this.timer = setTimeout("onSearchFormSubmit()", 1000);
782 }
783
784 function initCriteria() {
785   var searchCriteria = $("searchCriteria");
786   var searchValue = $("searchValue");
787   var firstOption;
788  
789   var searchOptions = $("searchOptions");
790   if (searchOptions) {
791     var firstOption;
792     $A(searchOptions.childNodes).each(function (item) {
793       if (item.tagName == 'LI') {
794         firstOption = item;
795       }
796     });
797     searchCriteria.value = firstOption.getAttribute('id');
798     searchValue.setAttribute('ghost-phrase', firstOption.innerHTML);
799     if (searchValue.value == '') {
800       searchValue.value = firstOption.innerHTML;
801       searchValue.setAttribute("modified", "");
802       searchValue.setStyle({ color: "#aaa" });
803     }
804   }
805 }
806
807 /* toolbar buttons */
808 function popupToolbarMenu(event, menuId) {
809    var toolbar = $("toolbar");
810    var node = event.target;
811    if (node.tagName != 'A')
812       node = node.getParentWithTagName("a");
813    node = node.childNodesWithTag("span")[0];
814
815    if (event.button == 0) {
816       event.cancelBubble = true;
817       event.returnValue = false;
818       
819       if (document.currentPopupMenu)
820          hideMenu(event, document.currentPopupMenu);
821       
822       var popup = document.getElementById(menuId);
823       var top = node.offsetTop + node.offsetHeight - 2;
824       popup.setStyle({ top: top + "px",
825                        left: node.cascadeLeftOffset() + "px",
826                        visibility: "visible" });
827
828       document.currentPopupMenu = popup;
829       Event.observe(document.body, "click", onBodyClickMenuHandler);
830    }
831 }
832
833 /* contact selector */
834
835 function folderSubscriptionCallback(http) {
836    if (http.readyState == 4) {
837       if (http.status == 204) {
838          if (http.callbackData)
839             http.callbackData["method"](http.callbackData["data"]);
840       }
841       else
842          window.alert(labels["Unable to subscribe to that folder!"].decodeEntities());
843       document.subscriptionAjaxRequest = null;
844    }
845    else
846       log ("ajax fuckage");
847 }
848
849 function subscribeToFolder(refreshCallback, refreshCallbackData) {
850    var folderData = refreshCallbackData["folder"].split(":");
851    var username = folderData[0];
852    var folderPath = folderData[1];
853    if (username != UserLogin) {
854       var url = (UserFolderURL + "../" + username
855                  + folderPath + "/subscribe");
856       if (document.subscriptionAjaxRequest) {
857          document.subscriptionAjaxRequest.aborted = true;
858          document.subscriptionAjaxRequest.abort();
859       }
860       var rfCbData = { method: refreshCallback, data: refreshCallbackData };
861       document.subscriptionAjaxRequest = triggerAjaxRequest(url,
862                                                             folderSubscriptionCallback,
863                                                             rfCbData);
864    }
865    else
866       window.alert(labels["You cannot subscribe to a folder that you own!"]
867                    .decodeEntities());
868 }
869
870 function folderUnsubscriptionCallback(http) {
871    if (http.readyState == 4) {
872       if (http.status == 204) {
873          if (http.callbackData)
874             http.callbackData["method"](http.callbackData["data"]);
875       }
876       else
877          window.alert(labels["Unable to unsubscribe from that folder!"].decodeEntities());
878       document.unsubscriptionAjaxRequest = null;
879    }
880 }
881
882 function unsubscribeFromFolder(folder, refreshCallback, refreshCallbackData) {
883    if (document.body.hasClassName("popup")) {
884       window.opener.unsubscribeFromFolder(folder, refreshCallback,
885                                           refreshCallbackData);
886    }
887    else {
888       var folderData = folder.split(":");
889       var username = folderData[0];
890       var folderPath = folderData[1];
891       if (username != UserLogin) {
892          var url = (UserFolderURL + "../" + username
893                     + "/" + folderPath + "/unsubscribe");
894          if (document.unsubscriptionAjaxRequest) {
895             document.unsubscriptionAjaxRequest.aborted = true;
896             document.unsubscriptionAjaxRequest.abort();
897          }
898          var rfCbData = { method: refreshCallback, data: refreshCallbackData };
899          document.unsubscriptionAjaxRequest
900             = triggerAjaxRequest(url, folderUnsubscriptionCallback,
901                                  rfCbData);
902       }
903       else
904          window.alert(labels["You cannot unsubscribe from a folder that you own!"].decodeEntities());
905    }
906 }
907
908 function listRowMouseDownHandler(event) {
909   preventDefault(event);
910   return false;
911 }
912
913 /* tabs */
914 function initTabs() {
915   var containers = document.getElementsByClassName("tabsContainer");
916   for (var x = 0; x < containers.length; x++) {
917     var container = containers[x];
918     var firstTab = null;
919     for (var i = 0; i < container.childNodes.length; i++) {
920       if (container.childNodes[i].tagName == 'UL') {
921         if (!firstTab)
922           firstTab = i;
923       }
924     }
925     var nodes = container.childNodes[firstTab].childNodes;
926     
927     firstTab = null;
928     for (var i = 0; i < nodes.length; i++) {
929         var currentNode = nodes[i];
930         if (currentNode.tagName == 'LI') {
931             if (!firstTab)
932                 firstTab = i;
933             Event.observe(currentNode, "mousedown",
934                           onTabMouseDown.bindAsEventListener(currentNode));
935             Event.observe(currentNode, "click",
936                           onTabClick.bindAsEventListener(currentNode));
937         }
938     }
939
940     nodes[firstTab].addClassName("first");
941     nodes[firstTab].addClassName("active");
942     container.activeTab = nodes[firstTab];
943
944     var target = $(nodes[firstTab].getAttribute("target"));
945     target.addClassName("active");
946   }
947 }
948
949 function initMenus() {
950    var menus = getMenus();
951    if (menus) {
952       for (var menuID in menus) {
953          var menuDIV = $(menuID);
954          if (menuDIV)
955             initMenu(menuDIV, menus[menuID]);
956       }
957    }
958 }
959
960 function initMenu(menuDIV, callbacks) {
961    var lis = $(menuDIV.childNodesWithTag("ul")[0]).childNodesWithTag("li");
962    for (var j = 0; j < lis.length; j++) {
963       var node = lis[j];
964       Event.observe(node, "mousedown", listRowMouseDownHandler, false);
965       var callback = callbacks[j];
966       if (callback) {
967          if (typeof(callback) == "string") {
968             if (callback == "-")
969                node.addClassName("separator");
970             else {
971                node.submenu = callback;
972                node.addClassName("submenu");
973                Event.observe(node, "mouseover", dropDownSubmenu);
974             }
975          }
976          else
977             Event.observe(node, "mouseup",
978                           $(callback).bindAsEventListener(node));
979       }
980       else
981          node.addClassName("disabled");
982    }
983 }
984
985 function onTabMouseDown(event) {
986   event.cancelBubble = true;
987   preventDefault(event);
988 }
989
990 function openExternalLink(anchor) {
991   return false;
992 }
993
994 function openAclWindow(url) {
995   var w = window.open(url, "aclWindow",
996                       "width=420,height=300,resizable=1,scrollbars=1,toolbar=0,"
997                       + "location=0,directories=0,status=0,menubar=0"
998                       + ",copyhistory=0");
999   w.opener = window;
1000   w.focus();
1001
1002   return w;
1003 }
1004
1005 function getUsersRightsWindowHeight() {
1006    return usersRightsWindowHeight;
1007 }
1008
1009 function getUsersRightsWindowWidth() {
1010    return usersRightsWindowWidth;
1011 }
1012
1013 function getTopWindow() {
1014    var topWindow = null;
1015    var currentWindow = window;
1016    while (!topWindow) {
1017       if (currentWindow.document.body.hasClassName("popup")
1018           && currentWindow.opener)
1019          currentWindow = currentWindow.opener;
1020       else
1021          topWindow = currentWindow;
1022    }
1023
1024    return topWindow;
1025 }
1026
1027 function onTabClick(event) {
1028   var node = getTarget(event);
1029
1030   var target = node.getAttribute("target");
1031
1032   var container = node.parentNode.parentNode;
1033   var oldTarget = container.activeTab.getAttribute("target");
1034   var content = $(target);
1035   var oldContent = $(oldTarget);
1036
1037   oldContent.removeClassName("active");
1038   container.activeTab.removeClassName("active");
1039   container.activeTab = node;
1040   container.activeTab.addClassName("active");
1041   content.addClassName("active");
1042
1043   return false;
1044 }
1045
1046 function enableAnchor(anchor) {
1047   var classStr = '' + anchor.getAttribute("class");
1048   var position = classStr.indexOf("_disabled", 0);
1049   if (position > -1) {
1050     var disabledHref = anchor.getAttribute("disabled-href");
1051     if (disabledHref)
1052       anchor.setAttribute("href", disabledHref);
1053     var disabledOnclick = anchor.getAttribute("disabled-onclick");
1054     if (disabledOnclick)
1055       anchor.setAttribute("onclick", disabledOnclick);
1056     anchor.removeClassName("_disabled");
1057     anchor.setAttribute("disabled-href", null);
1058     anchor.setAttribute("disabled-onclick", null);
1059     anchor.disabled = 0;
1060     anchor.enabled = 1;
1061   }
1062 }
1063
1064 function disableAnchor(anchor) {
1065   var classStr = '' + anchor.getAttribute("class");
1066   var position = classStr.indexOf("_disabled", 0);
1067   if (position < 0) {
1068     var href = anchor.getAttribute("href");
1069     if (href)
1070       anchor.setAttribute("disabled-href", href);
1071     var onclick = anchor.getAttribute("onclick");
1072     if (onclick)
1073       anchor.setAttribute("disabled-onclick", onclick);
1074     anchor.addClassName("_disabled");
1075     anchor.setAttribute("href", "#");
1076     anchor.setAttribute("onclick", "return false;");
1077     anchor.disabled = 1;
1078     anchor.enabled = 0;
1079   }
1080 }
1081
1082 function d2h(d) {
1083   var hD = "0123456789abcdef";
1084   var h = hD.substr(d & 15, 1);
1085
1086   while (d > 15) {
1087     d >>= 4;
1088     h = hD.substr(d & 15, 1) + h;
1089   }
1090
1091   return h;
1092 }
1093
1094 function indexColor(number) {
1095   var color;
1096
1097   if (number == 0)
1098     color = "#ccf";
1099   else {
1100     var colorTable = new Array(1, 1, 1);
1101     
1102     var currentValue = number;
1103     var index = 0;
1104     while (currentValue) {
1105        if (currentValue & 1)
1106           colorTable[index]++;
1107        if (index == 3)
1108           index = 0;
1109        currentValue >>= 1;
1110        index++;
1111     }
1112     
1113     color = ("#"
1114              + d2h((256 / colorTable[2]) - 1)
1115              + d2h((256 / colorTable[1]) - 1)
1116              + d2h((256 / colorTable[0]) - 1));
1117   }
1118
1119   return color;
1120 }
1121
1122 function onLoadHandler(event) {
1123     queryParameters = parseQueryParameters('' + window.location);
1124     if (!$(document.body).hasClassName("popup")) {
1125         initLogConsole();
1126         initCriteria();
1127     }
1128     initMenus();
1129     initTabs();
1130     configureDragHandles();
1131     configureSortableTableHeaders();
1132     configureLinkBanner();
1133     var progressImage = $("progressIndicator");
1134     if (progressImage)
1135         progressImage.parentNode.removeChild(progressImage);
1136     Event.observe(document.body, "contextmenu", onBodyClickContextMenu);
1137 }
1138
1139 function onBodyClickContextMenu(event) {
1140   preventDefault(event);
1141 }
1142
1143 function configureSortableTableHeaders() {
1144   var headers = document.getElementsByClassName("sortableTableHeader");
1145   for (var i = 0; i < headers.length; i++) {
1146     var anchor = $(headers[i]).childNodesWithTag("a")[0];
1147     if (!anchor.link) {
1148       anchor.link = anchor.getAttribute("href");
1149       anchor.href = "#";
1150       Event.observe(anchor, "click", onHeaderClick.bindAsEventListener(anchor), true);
1151     }
1152   }
1153 }
1154
1155 function onLinkBannerClick() {
1156   activeAjaxRequests++;
1157   checkAjaxRequestsState();
1158 }
1159
1160 function configureLinkBanner() {
1161   var linkBanner = $("linkBanner");
1162   if (linkBanner) {
1163     var anchors = linkBanner.childNodesWithTag("a");
1164     for (var i = 0; i < 2; i++) {
1165        Event.observe(anchors[i], "mousedown", listRowMouseDownHandler);
1166        Event.observe(anchors[i], "click", onLinkBannerClick);
1167     }
1168     if (anchors.length > 3)
1169        Event.observe(anchors[3], "click", toggleLogConsole);
1170   }
1171 }
1172
1173 addEvent(window, 'load', onLoadHandler);
1174
1175 /* stubs */
1176 function configureDragHandles() {
1177 }
1178
1179 function getMenus() {
1180 }
1181
1182 function onHeaderClick(event) {
1183    window.alert("generic headerClick");
1184 }
1185
1186 function parent$(element) {
1187    return this.opener.document.getElementById(element);
1188 }