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