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