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