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