]> err.no Git - scalable-opengroupware.org/blob - UI/WebServerResources/generic.js
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1294 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 = -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   }
245   
246   if (document.body.hasClassName("popup"))
247     parentWindow = window.opener;
248
249   var w = parentWindow.open(url, wId,
250                       "width=680,height=520,resizable=1,scrollbars=1,toolbar=0,"
251                       + "location=0,directories=0,status=0,menubar=0"
252                       + ",copyhistory=0");
253
254   w.focus();
255
256   return w;
257 }
258
259 function openMailTo(senderMailTo) {
260   var mailto = sanitizeMailTo(senderMailTo);
261
262   if (mailto.length > 0)
263     openMailComposeWindow(ApplicationBaseURL
264                           + "../Mail/compose?mailto=" + mailto);
265
266   return false; /* stop following the link */
267 }
268
269 function createHTTPClient() {
270   // http://developer.apple.com/internet/webcontent/xmlhttpreq.html
271   if (typeof XMLHttpRequest != "undefined")
272     return new XMLHttpRequest();
273
274   try { return new ActiveXObject("Msxml2.XMLHTTP"); } 
275   catch (e) { }
276   try { return new ActiveXObject("Microsoft.XMLHTTP"); } 
277   catch (e) { }
278
279   return null;
280 }
281
282 function appendDifferentiator(url) {
283   var url_nocache = url;
284   var position = url.indexOf('?', 0);
285   if (position < 0)
286     url_nocache += '?';
287   else
288     url_nocache += '&';
289   url_nocache += 'differentiator=' + Math.floor(Math.random()*50000);
290
291   return url_nocache;
292 }
293
294 function triggerAjaxRequest(url, callback, userdata, content, headers) {
295   var http = createHTTPClient();
296
297   activeAjaxRequests += 1;
298   document.animTimer = setTimeout("checkAjaxRequestsState();", 50);
299   //url = appendDifferentiator(url);
300
301   if (http) {
302     http.open("POST", url, true);
303     http.url = url;
304     http.onreadystatechange
305       = function() {
306 //       log ("state changed (" + http.readyState + "): " + url);
307       try {
308         if (http.readyState == 4
309             && activeAjaxRequests > 0) {
310           if (!http.aborted) {
311             if (userdata)
312               http.callbackData = userdata;
313             callback(http);
314           }
315           activeAjaxRequests -= 1;
316           checkAjaxRequestsState();
317         }
318       }
319       catch( e ) {
320         activeAjaxRequests -= 1;
321         checkAjaxRequestsState();
322         log("AJAX Request, Caught Exception: " + e.name);
323         log(e.message);
324         log(backtrace());
325       }
326     };
327     if (headers) {
328       for (var i in headers)
329         http.setRequestHeader(i, headers[i]);
330     }
331     http.send(content);
332   }
333   else {
334     log("triggerAjaxRequest: error creating HTTP Client!");
335   }
336
337   return http;
338 }
339
340 function startAnimation(parent, nextNode) {
341   var anim = $("progressIndicator");
342   if (anim) return anim;
343   
344   anim = document.createElement("img");
345   anim = $(anim);
346   anim.id = "progressIndicator";
347   anim.src = ResourcesURL + "/busy.gif";
348   anim.setStyle({ visibility: "hidden" });
349   if (nextNode)
350     parent.insertBefore(anim, nextNode);
351   else
352     parent.appendChild(anim);
353   anim.setStyle({ visibility: "visible" });
354
355   return anim;
356 }
357
358 function checkAjaxRequestsState() {
359   var toolbar = document.getElementById("toolbar");
360   if (toolbar) {
361     if (activeAjaxRequests > 0
362         && !document.busyAnim) {
363       document.busyAnim = startAnimation(toolbar);
364     }
365     else if (activeAjaxRequests == 0
366              && document.busyAnim
367              && document.busyAnim.parentNode) {
368       document.busyAnim.parentNode.removeChild(document.busyAnim);
369       document.busyAnim = null;
370     }
371   }
372 }
373
374 function isSafari3() {
375   return (navigator.appVersion.indexOf("Version") > -1);
376 }
377
378 function isSafari() {
379   //var agt = navigator.userAgent.toLowerCase();
380   //var is_safari = ((agt.indexOf('safari')!=-1)&&(agt.indexOf('mac')!=-1))?true:false;
381
382   return (navigator.vendor == "Apple Computer, Inc.");
383 }
384
385 function isHttpStatus204(status) {
386   return (status == 204 ||                                  // Firefox
387           (isSafari() && typeof(status) == 'undefined') ||  // Safari
388           status == 1223);                                  // IE
389 }
390
391 function getTarget(event) {
392   event = event || window.event;
393   if (event.target)
394     return event.target; // W3C DOM
395   else
396     return event.srcElement; // IE
397 }
398
399 function preventDefault(event) {
400   if (event.preventDefault)
401     event.preventDefault(); // W3C DOM
402   else
403     event.returnValue = false; // IE
404 }
405
406 function resetSelection(win) {
407   var t = "";
408   if (win && win.getSelection) {
409     t = win.getSelection().toString();
410     win.getSelection().removeAllRanges();
411   }
412   return t;
413 }
414
415 function refreshOpener() {
416   if (window.opener && !window.opener.closed) {
417     window.opener.location.reload();
418   }
419 }
420
421 /* query string */
422
423 function parseQueryString() {
424   var queryArray, queryDict
425     var key, value, s, idx;
426   queryDict.length = 0;
427   
428   queryDict  = new Array();
429   queryArray = location.search.substr(1).split('&');
430   for (var i in queryArray) {
431     if (!queryArray[i]) continue ;
432     s   = queryArray[i];
433     idx = s.indexOf("=");
434     if (idx == -1) {
435       key   = s;
436       value = "";
437     }
438     else {
439       key   = s.substr(0, idx);
440       value = unescape(s.substr(idx + 1));
441     }
442     
443     if (typeof queryDict[key] == 'undefined')
444       queryDict.length++;
445     
446     queryDict[key] = value;
447   }
448   return queryDict;
449 }
450
451 function generateQueryString(queryDict) {
452   var s = "";
453   for (var key in queryDict) {
454     if (s.length == 0)
455       s = "?";
456     else
457       s = s + "&";
458     s = s + key + "=" + escape(queryDict[key]);
459   }
460   return s;
461 }
462
463 /* selection mechanism */
464
465 function deselectAll(parent) {
466   for (var i = 0; i < parent.childNodes.length; i++) {
467     var node = parent.childNodes.item(i);
468     if (node.nodeType == 1)
469       $(node).deselect();
470   }
471 }
472
473 function isNodeSelected(node) {
474   return $(node).hasClassName('_selected');
475 }
476
477 function acceptMultiSelect(node) {
478   var response = false;
479   var attribute = node.getAttribute('multiselect');
480   if (attribute && attribute.length > 0) {
481     log("node '" + node.getAttribute("id")
482         + "' is still using old-stylemultiselect!");
483     response = (attribute.toLowerCase() == 'yes');
484   }
485   else
486     response = node.multiselect;
487
488   return response;
489 }
490
491 function onRowClick(event) {
492   var node = getTarget(event);
493   var rowIndex = null;
494
495   if (node.tagName == 'TD') {
496     node = node.parentNode; // select TR
497     rowIndex = node.rowIndex - $(node).up('table').down('thead').getElementsByTagName('tr').length;  
498   }
499   else if (node.tagName == 'LI') {
500     // Find index of clicked row
501     var list = node.parentNode;
502     var items = list.childNodesWithTag("li");
503     for (var i = 0; i < items.length; i++) {
504       if (items[i] == node) {
505         rowIndex = i;
506         break;
507       }
508     }
509   }
510
511   var initialSelection = $(node.parentNode).getSelectedNodes();
512   if (initialSelection.length > 0 
513       && initialSelection.indexOf(node) >= 0
514       && !Event.isLeftClick(event))
515     // Ignore non primary-click (ie right-click) inside current selection
516     return true;
517   
518   if ((event.shiftKey == 1 || event.ctrlKey == 1)
519       && (lastClickedRow >= 0)
520       && (acceptMultiSelect(node.parentNode)
521           || acceptMultiSelect(node.parentNode.parentNode))) {
522     if (event.shiftKey)
523       $(node.parentNode).selectRange(lastClickedRow, rowIndex);
524     else if (isNodeSelected(node) == true) {
525       $(node).deselect();
526     } else {
527       $(node).select();
528     }
529     // At this point, should empty content of 3-pane view
530   } else {
531     // Single line selection
532     $(node.parentNode).deselectAll();
533     $(node).select();
534   
535     if (initialSelection != $(node.parentNode).getSelectedNodes()) {
536       // Selection has changed; fire mousedown event
537       var parentNode = node.parentNode;
538       if (parentNode.tagName == 'TBODY')
539         parentNode = parentNode.parentNode;
540       parentNode.fire("mousedown");
541     }
542   }
543   lastClickedRow = rowIndex;
544   
545   return true;
546 }
547
548 /* popup menus */
549
550 // var acceptClick = false;
551
552 function popupMenu(event, menuId, target) {
553   document.menuTarget = target;
554
555   if (document.currentPopupMenu)
556     hideMenu(document.currentPopupMenu);
557
558   var popup = $(menuId);
559
560   var deltaX = 0;
561   var deltaY = 0;
562
563   var pageContent = $("pageContent");
564   if (popup.parentNode.tagName != "BODY") {
565     var offset = pageContent.cascadeLeftOffset();
566     deltaX = -($(popup.parentNode).cascadeLeftOffset() - offset);
567     offset = pageContent.cascadeTopOffset();
568     deltaY = -($(popup.parentNode).cascadeTopOffset() - offset);
569   }
570
571   var menuTop = Event.pointerY(event) + deltaY;
572   var menuLeft = Event.pointerX(event) + deltaX;
573   var heightDiff = (window.height()
574                     - (menuTop + popup.offsetHeight));
575   if (heightDiff < 0)
576     menuTop += heightDiff;
577   
578   var leftDiff = (window.width()
579                   - (menuLeft + popup.offsetWidth));
580   if (leftDiff < 0)
581     menuLeft -= popup.offsetWidth;
582
583   if (popup.prepareVisibility)
584     popup.prepareVisibility();
585   
586   popup.setStyle({ top: menuTop + "px",
587                    left: menuLeft + "px",
588                    visibility: "visible" });
589   
590   document.currentPopupMenu = popup;
591
592   $(document.body).observe("click", onBodyClickMenuHandler);
593
594   preventDefault(event);
595 }
596
597 function getParentMenu(node) {
598   var currentNode, menuNode;
599
600   menuNode = null;
601   currentNode = node;
602   var menure = new RegExp("(^|\s+)menu(\s+|$)", "i");
603
604   while (menuNode == null
605          && currentNode)
606     if (menure.test(currentNode.className))
607       menuNode = currentNode;
608     else
609       currentNode = currentNode.parentNode;
610
611   return menuNode;
612 }
613
614 function onBodyClickMenuHandler(event) {
615   hideMenu(document.currentPopupMenu);
616   document.body.stopObserving("click", onBodyClickMenuHandler);
617
618   if (event)
619     preventDefault(event);
620 }
621
622 function hideMenu(menuNode) {
623   var onHide;
624
625   if (menuNode.submenu) {
626     hideMenu(menuNode.submenu);
627     menuNode.submenu = null;
628   }
629
630   menuNode.setStyle({ visibility: "hidden" });
631   if (menuNode.parentMenuItem) {
632     menuNode.parentMenuItem.stopObserving("mouseover",onMouseEnteredSubmenu);
633     menuNode.stopObserving("mouseover", onMouseEnteredSubmenu);
634     menuNode.parentMenuItem.stopObserving("mouseout", onMouseLeftSubmenu);
635     menuNode.stopObserving("mouseout", onMouseLeftSubmenu);
636     menuNode.parentMenu.stopObserving("mouseover", onMouseEnteredParentMenu);
637     $(menuNode.parentMenuItem).removeClassName("submenu-selected");
638     menuNode.parentMenuItem.mouseInside = false;
639     menuNode.parentMenuItem = null;
640     menuNode.parentMenu.submenuItem = null;
641     menuNode.parentMenu.submenu = null;
642     menuNode.parentMenu = null;
643   }
644
645   $(menuNode).fire("mousedown");
646 }
647
648 function onMenuEntryClick(event) {
649   var node = event.target;
650
651   id = getParentMenu(node).menuTarget;
652
653   return false;
654 }
655
656 function parseQueryParameters(url) {
657   var parameters = new Array();
658
659   var params = url.split("?")[1];
660   if (params) {
661     var pairs = params.split("&");
662     for (var i = 0; i < pairs.length; i++) {
663       var pair = pairs[i].split("=");
664       parameters[pair[0]] = pair[1];
665     }
666   }
667
668   return parameters;
669 }
670
671 function initLogConsole() {
672   var logConsole = $("logConsole");
673   if (logConsole) {
674     logConsole.highlighted = false;
675     logConsole.observe("dblclick", onLogDblClick, false);
676     logConsole.update();
677     Event.observe(window, "keydown", onBodyKeyDown);
678   }
679 }
680
681 function onBodyKeyDown(event) {
682   if (event.keyCode == 27) {
683     toggleLogConsole();
684     preventDefault(event);
685   }
686 }
687
688 function onLogDblClick(event) {
689   var logConsole = $("logConsole");
690   logConsole.innerHTML = "";
691 }
692
693 function toggleLogConsole(event) {
694   var logConsole = $("logConsole");
695   var display = '' + logConsole.style.display;
696   if (display.length == 0) {
697     logConsole.setStyle({ display: 'block' });
698   } else {
699     logConsole.setStyle({ display: '' });
700   }
701   if (event)
702     preventDefault(event);
703 }
704
705 function log(message) {
706   if (!logWindow) {
707     logWindow = window;
708     while (logWindow.opener)
709       logWindow = logWindow.opener;
710   }
711   var logConsole = logWindow.document.getElementById("logConsole");
712   if (logConsole) {
713     logConsole.highlighted = !logConsole.highlighted;
714     if (message == '\c') {
715       logConsole.innerHTML = "";
716       return;
717     }
718     var logMessage = message.replace("<", "&lt;", "g");
719     logMessage = logMessage.replace(" ", "&nbsp;", "g");
720     logMessage = logMessage.replace("\r\n", "<br />\n", "g");
721     logMessage = logMessage.replace("\n", "<br />\n", "g");
722     logMessage += '<br />' + "\n";
723     if (logConsole.highlighted)
724       logMessage = '<div class="highlighted">' + logMessage + '</div>';
725     logConsole.innerHTML += logMessage;
726   }
727 }
728
729 function backtrace() {
730   var func = backtrace.caller;
731   var str = "backtrace:\n";
732
733   while (func)
734     {
735       if (func.name)
736         {
737           str += "  " + func.name;
738           if (this)
739             str += " (" + this + ")";
740         }
741       else
742         str += "[anonymous]\n";
743
744       str += "\n";
745       func = func.caller;
746     }
747   str += "--\n";
748
749   return str;
750 }
751
752 function popupSubmenu(event) {
753   if (this.submenu && this.submenu != "") {
754     var submenuNode = $(this.submenu);
755     var parentNode = getParentMenu(this);
756     if (parentNode.submenu)
757       hideMenu(parentNode.submenu);
758     submenuNode.parentMenuItem = this;
759     submenuNode.parentMenu = parentNode;
760     parentNode.submenuItem = this;
761     parentNode.submenu = submenuNode;
762
763     if (submenuNode.prepareVisibility)
764       submenuNode.prepareVisibility();
765
766     var menuTop = (parentNode.offsetTop - 1
767                    + this.offsetTop);
768
769     if (window.height()
770         < (menuTop + submenuNode.offsetHeight))
771       if (submenuNode.offsetHeight < window.height())
772         menuTop = window.height() - submenuNode.offsetHeight;
773       else
774         menuTop = 0;
775
776     var menuLeft = (parentNode.offsetLeft + parentNode.offsetWidth - 3);
777     if (window.width()
778         < (menuLeft + submenuNode.offsetWidth))
779       menuLeft = parentNode.offsetLeft - submenuNode.offsetWidth + 3;
780
781     this.mouseInside = true;
782     this.observe("mouseover", onMouseEnteredSubmenu);
783     submenuNode.observe("mouseover", onMouseEnteredSubmenu);
784     this.observe("mouseout", onMouseLeftSubmenu);
785     submenuNode.observe("mouseout", onMouseLeftSubmenu);
786     parentNode.observe("mouseover", onMouseEnteredParentMenu);
787     $(this).addClassName("submenu-selected");
788     submenuNode.setStyle({ top: menuTop + "px",
789                            left: menuLeft + "px",
790                            visibility: "visible" });
791     preventDefault(event);
792   }
793 }
794
795 function onMouseEnteredParentMenu(event) {
796   if (this.submenuItem && !this.submenuItem.mouseInside)
797     hideMenu(this.submenu);
798 }
799
800 function onMouseEnteredSubmenu(event) {
801   $(this).mouseInside = true;
802 }
803
804 function onMouseLeftSubmenu(event) {
805   $(this).mouseInside = false;
806 }
807
808 /* search field */
809 function popupSearchMenu(event) {
810   var menuId = this.getAttribute("menuid");
811   var offset = Position.cumulativeOffset(this);
812
813   relX = Event.pointerX(event) - offset[0];
814   relY = Event.pointerY(event) - offset[1];
815
816   if (event.button == 0
817       && relX < 24) {
818     event.cancelBubble = true;
819     event.returnValue = false;
820
821     if (document.currentPopupMenu)
822       hideMenu(document.currentPopupMenu);
823
824     var popup = $(menuId);
825     offset = Position.positionedOffset(this);
826     popup.setStyle({ top: this.offsetHeight + "px",
827           left: (offset[0] + 3) + "px",
828           visibility: "visible" });
829   
830     document.currentPopupMenu = popup;
831     $(document.body).observe("click", onBodyClickMenuHandler);
832   }
833 }
834
835 function setSearchCriteria(event) {
836   var searchValue = $("searchValue");
837   var searchCriteria = $("searchCriteria");
838
839   searchValue.ghostPhrase = this.innerHTML;
840   searchCriteria.value = this.getAttribute('id');
841   
842   if (this.parentNode.chosenNode)
843     this.parentNode.chosenNode.removeClassName("_chosen");
844   this.addClassName("_chosen");
845   this.parentNode.chosenNode = this;
846 }
847
848 function checkSearchValue(event) {
849   var searchValue = $("searchValue");
850
851   if (searchValue.value == searchValue.ghostPhrase)
852     searchValue.value = "";
853 }
854
855 function configureSearchField() {
856   var searchValue = $("searchValue");
857   var searchOptions = $("searchOptions");
858
859   if (!searchValue) return;
860
861   searchValue.observe("click", popupSearchMenu);
862   searchValue.observe("blur", onSearchBlur);
863   searchValue.observe("focus", onSearchFocus);
864   searchValue.observe("keydown", onSearchKeyDown);
865   searchValue.observe("mousedown", onSearchMouseDown);
866   
867   if (!searchOptions) return;
868    
869   // Set the checkmark to the first option
870   var firstOption = searchOptions.down('li');
871   firstOption.addClassName("_chosen");
872   searchOptions.chosenNode = firstOption;
873 }
874
875 function onSearchMouseDown(event) {
876   var superNode = this.parentNode.parentNode.parentNode;
877   relX = (Event.pointerX(event) - superNode.offsetLeft - this.offsetLeft);
878   relY = (Event.pointerY(event) - superNode.offsetTop - this.offsetTop);
879
880   if (relY < 24) {
881     event.cancelBubble = true;
882     event.returnValue = false;
883   }
884 }
885
886 function onSearchFocus() {
887   ghostPhrase = this.ghostPhrase;
888   if (this.value == ghostPhrase) {
889     this.value = "";
890     this.setAttribute("modified", "");
891   } else {
892     this.select();
893   }
894
895   this.setStyle({ color: "#000" });
896 }
897
898 function onSearchBlur(event) {
899   if (!this.value) {
900     this.setAttribute("modified", "");
901     this.setStyle({ color: "#aaa" });
902     this.value = this.ghostPhrase;
903     search["value"] = "";
904     if (searchValue.lastSearch != "") {
905       searchValue.lastSearch = "";
906       refreshCurrentFolder();
907     }
908   } else if (this.value == this.ghostPhrase) {
909     this.setAttribute("modified", "");
910     this.setStyle({ color: "#aaa" });
911   } else {
912     this.setAttribute("modified", "yes");
913     this.setStyle({ color: "#000" });
914   }
915 }
916
917 function onSearchKeyDown(event) {
918   if (this.timer)
919     clearTimeout(this.timer);
920
921   if (event.keyCode == 13) {
922     onSearchFormSubmit();
923     preventDefault(event);
924   }
925   else
926     this.timer = setTimeout("onSearchFormSubmit()", 1000);
927 }
928
929 function onSearchFormSubmit(event) {
930   var searchValue = $("searchValue");
931   var searchCriteria = $("searchCriteria");
932    
933   if (searchValue.value != searchValue.ghostPhrase
934       && searchValue.value != searchValue.lastSearch) {
935     search["criteria"] = searchCriteria.value;
936     search["value"] = searchValue.value;
937     searchValue.lastSearch = searchValue.value;
938     refreshCurrentFolder();
939   }
940 }
941
942 function initCriteria() {
943   var searchCriteria = $("searchCriteria");
944   var searchValue = $("searchValue");
945  
946   if (searchValue) {
947     var searchOptions = $("searchOptions").childNodesWithTag("li");
948     if (searchOptions.length > 0) {
949       var firstChild = searchOptions[0];
950       searchCriteria.value = $(firstChild).getAttribute('id');
951       searchValue.ghostPhrase = firstChild.innerHTML;
952       searchValue.lastSearch = "";
953       if (searchValue.value == '') {
954         searchValue.value = firstChild.innerHTML;
955         searchValue.setAttribute("modified", "");
956         searchValue.setStyle({ color: "#aaa" });
957       }
958     }
959   }
960 }
961
962 /* toolbar buttons */
963 function popupToolbarMenu(node, menuId) {
964   if (document.currentPopupMenu)
965     hideMenu(document.currentPopupMenu);
966
967   var popup = $(menuId);
968   var top = ($(node).getStyle('top') || 0) + node.offsetHeight - 2;
969   popup.setStyle({ top: top + "px",
970         left: $(node).cascadeLeftOffset() + "px",
971         visibility: "visible" });
972
973   document.currentPopupMenu = popup;
974   $(document.body).observe("click", onBodyClickMenuHandler);
975 }
976
977 /* contact selector */
978
979 function folderSubscriptionCallback(http) {
980   if (http.readyState == 4) {
981     if (isHttpStatus204(http.status)) {
982       if (http.callbackData)
983         http.callbackData["method"](http.callbackData["data"]);
984     }
985     else
986       window.alert(clabels["Unable to subscribe to that folder!"]);
987     document.subscriptionAjaxRequest = null;
988   }
989   else
990     log ("folderSubscriptionCallback Ajax error");
991 }
992
993 function subscribeToFolder(refreshCallback, refreshCallbackData) {
994   var folderData = refreshCallbackData["folder"].split(":");
995   var username = folderData[0];
996   var folderPath = folderData[1];
997   if (username != UserLogin) {
998     var url = (UserFolderURL + "../" + username
999                + folderPath + "/subscribe");
1000     if (document.subscriptionAjaxRequest) {
1001       document.subscriptionAjaxRequest.aborted = true;
1002       document.subscriptionAjaxRequest.abort();
1003     }
1004
1005     var rfCbData = { method: refreshCallback, data: refreshCallbackData };
1006     document.subscriptionAjaxRequest = triggerAjaxRequest(url,
1007                                                           folderSubscriptionCallback,
1008                                                           rfCbData);
1009   }
1010   else
1011     refreshCallbackData["window"].alert(clabels["You cannot subscribe to a folder that you own!"]);
1012 }
1013
1014 function folderUnsubscriptionCallback(http) {
1015   if (http.readyState == 4) {
1016     removeFolderRequestCount--;
1017     if (isHttpStatus204(http.status)) {
1018       if (http.callbackData)
1019         http.callbackData["method"](http.callbackData["data"]);
1020     }
1021     else
1022       window.alert(clabels["Unable to unsubscribe from that folder!"]);
1023   }
1024 }
1025
1026 function unsubscribeFromFolder(folder, refreshCallback, refreshCallbackData) {
1027   if (document.body.hasClassName("popup")) {
1028     window.opener.unsubscribeFromFolder(folder, refreshCallback,
1029                                         refreshCallbackData);
1030   }
1031   else {
1032     var folderData = folder.split("_");
1033     var username = folderData[0];
1034     var folderPath = folderData[1];
1035     if (username.startsWith('/'))
1036       username = username.substring(1);
1037     if (username != UserLogin) {
1038       var url = (ApplicationBaseURL + folder + "/unsubscribe");
1039       removeFolderRequestCount++;
1040       var rfCbData = { method: refreshCallback, data: refreshCallbackData };
1041       triggerAjaxRequest(url, folderUnsubscriptionCallback, rfCbData);
1042     }
1043     else
1044       window.alert(clabels["You cannot unsubscribe from a folder that you own!"]);
1045   }
1046 }
1047
1048 function accessToSubscribedFolder(serverFolder) {
1049   var folder;
1050
1051   var parts = serverFolder.split(":");
1052   if (parts.length > 1) {
1053     var paths = parts[1].split("/");
1054     folder = "/" + parts[0] + "_" + paths[2];
1055   }
1056   else
1057     folder = serverFolder;
1058   
1059   return folder;
1060 }
1061
1062 function getSubscribedFolderOwner(serverFolder) {
1063   var owner;
1064   
1065   var parts = serverFolder.split(":");
1066   if (parts.length > 1) {
1067     owner = parts[0];
1068   }
1069   
1070   return owner;
1071 }
1072
1073 function getListIndexForFolder(items, owner, folderName) {
1074   var i;
1075   var previousOwner = null;
1076   
1077   for (var i = 0; i < items.length; i++) {
1078     var currentFolderName = items[i].lastChild.nodeValue.strip();
1079     var currentOwner = items[i].readAttribute('owner');
1080     if (currentOwner == owner) {
1081       previousOwner = currentOwner;
1082       if (currentFolderName > folderName)
1083         break;
1084     }
1085     else if (previousOwner || 
1086              (currentOwner != UserLogin && currentOwner > owner))
1087       break;
1088     else if (currentOwner == "nobody")
1089       break;
1090   }
1091   
1092   return i;
1093 }
1094
1095 function listRowMouseDownHandler(event) {
1096   preventDefault(event);
1097   //Event.stop(event); 
1098 }
1099
1100 /* tabs */
1101 function initTabs() {
1102   var containers = document.getElementsByClassName("tabsContainer");
1103   for (var x = 0; x < containers.length; x++) {
1104     var container = containers[x];
1105     var firstTab = null;
1106     for (var i = 0; i < container.childNodes.length; i++) {
1107       if (container.childNodes[i].tagName == 'UL') {
1108         if (!firstTab)
1109           firstTab = i;
1110       }
1111     }
1112     var nodes = container.childNodes[firstTab].childNodes;
1113     
1114     firstTab = null;
1115     for (var i = 0; i < nodes.length; i++) {
1116       var currentNode = nodes[i];
1117       if (currentNode.tagName == 'LI') {
1118         if (!firstTab)
1119           firstTab = i;
1120         $(currentNode).observe("mousedown", onTabMouseDown);
1121         $(currentNode).observe("click", onTabClick);
1122         //$(currentNode.getAttribute("target")).hide();
1123       }
1124     }
1125
1126     nodes[firstTab].addClassName("first");
1127     nodes[firstTab].addClassName("active");
1128     container.activeTab = nodes[firstTab];
1129
1130     var target = $(nodes[firstTab].getAttribute("target"));
1131     target.addClassName("active");
1132     //target.show();
1133   }
1134 }
1135
1136 function initMenus() {
1137   var menus = getMenus();
1138   if (menus) {
1139     for (var menuID in menus) {
1140       var menuDIV = $(menuID);
1141       if (menuDIV)
1142         initMenu(menuDIV, menus[menuID]);
1143     }
1144   }
1145 }
1146
1147 function initMenu(menuDIV, callbacks) {
1148   var lis = $(menuDIV.down("ul")).childNodesWithTag("li");
1149   for (var j = 0; j < lis.length; j++) {
1150     var node = $(lis[j]);
1151     node.observe("mousedown", listRowMouseDownHandler, false);
1152     var callback = callbacks[j];
1153     if (callback) {
1154       if (typeof(callback) == "string") {
1155         if (callback == "-")
1156           node.addClassName("separator");
1157         else {
1158           node.submenu = callback;
1159           node.addClassName("submenu");
1160           node.observe("mouseover", popupSubmenu);
1161         }
1162       }
1163       else {
1164         node.observe("mouseup", onBodyClickMenuHandler);
1165         node.observe("click", callback);
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   $(document.body).observe("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     $(header).observe("click", onHeaderClick);
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 moduleLinks = [ "calendar", "contacts", "mail" ];
1397     for (var i = 0; i < moduleLinks.length; i++) {
1398       var link = $(moduleLinks[i] + "BannerLink");
1399       if (link) {
1400         link.observe("mousedown", listRowMouseDownHandler);
1401         link.observe("click", onLinkBannerClick);
1402       }
1403     }
1404     link = $("preferencesBannerLink");
1405     if (link) {
1406       link.observe("mousedown", listRowMouseDownHandler);
1407       link.observe("click", onPreferencesClick);
1408     }
1409     link = $("consoleBannerLink");
1410     if (link) {
1411       link.observe("mousedown", listRowMouseDownHandler);
1412       link.observe("click", toggleLogConsole);
1413     }
1414   }
1415 }
1416
1417 /* folder creation */
1418 function createFolder(name, okCB, notOkCB) {
1419   if (name) {
1420     if (document.newFolderAjaxRequest) {
1421       document.newFolderAjaxRequest.aborted = true;
1422       document.newFolderAjaxRequest.abort();
1423     }
1424     var url = ApplicationBaseURL + "/createFolder?name=" + name;
1425     document.newFolderAjaxRequest
1426       = triggerAjaxRequest(url, createFolderCallback,
1427                            {name: name,
1428                             okCB: okCB,
1429                             notOkCB: notOkCB});
1430   }
1431 }
1432
1433 function createFolderCallback(http) {
1434   if (http.readyState == 4) {
1435     var data = http.callbackData;
1436     if (http.status == 201) {
1437       if (data.okCB)
1438         data.okCB(data.name, "/" + http.responseText, UserLogin);
1439     }
1440     else {
1441       if (data.notOkCB)
1442         data.notOkCB(name);
1443       else
1444         log("ajax problem:" + http.status);
1445     }
1446   }
1447 }
1448
1449 function onFinalLoadHandler(event) {
1450   var safetyNet = $("javascriptSafetyNet");
1451   if (safetyNet)
1452     safetyNet.parentNode.removeChild(safetyNet);
1453 }
1454
1455 FastInit.addOnLoad(onLoadHandler);
1456
1457 function parent$(element) {
1458   return this.opener.document.getElementById(element);
1459 }
1460
1461 /* stubs */
1462 function refreshCurrentFolder() {
1463 }
1464
1465 function configureDragHandles() {
1466 }
1467
1468 function getMenus() {
1469 }
1470
1471 function onHeaderClick(event) {
1472   window.alert("generic headerClick");
1473 }