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