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