webcit_before_automake is now the trunk
[citadel.git] / webcit / static / util.js
1 // small but works-for-me stuff for testing javascripts
2 // not ready for "production" use
3
4 Object.inspect = function(obj) {
5   var info = [];
6   
7   if(typeof obj in ["string","number"]) {
8     return obj;
9   } else {
10     for(property in obj)
11       if(typeof obj[property]!="function")
12         info.push(property + ' => ' + 
13           (typeof obj[property] == "string" ?
14             '"' + obj[property] + '"' :
15             obj[property]));
16   }
17   
18   return ("'" + obj + "' #" + typeof obj + 
19     ": {" + info.join(", ") + "}");
20 }
21
22 // borrowed from http://www.schuerig.de/michael/javascript/stdext.js
23 // Copyright (c) 2005, Michael Schuerig, michael@schuerig.de
24
25 Array.flatten = function(array, excludeUndefined) {
26   if (excludeUndefined === undefined) {
27     excludeUndefined = false;
28   }
29   var result = [];
30   var len = array.length;
31   for (var i = 0; i < len; i++) {
32     var el = array[i];
33     if (el instanceof Array) {
34       var flat = el.flatten(excludeUndefined);
35       result = result.concat(flat);
36     } else if (!excludeUndefined || el != undefined) {
37       result.push(el);
38     }
39   }
40   return result;
41 };
42
43 if (!Array.prototype.flatten) {
44   Array.prototype.flatten = function(excludeUndefined) {
45     return Array.flatten(this, excludeUndefined);
46   }
47 }
48
49 /*--------------------------------------------------------------------------*/
50
51 var Builder = {
52   node: function(elementName) {
53     var element = document.createElement('div');
54     element.innerHTML = 
55       "<" + elementName + "></" + elementName + ">";
56
57     // attributes (or text)
58     if(arguments[1])
59       if(this._isStringOrNumber(arguments[1]) ||
60         (arguments[1] instanceof Array)) {
61           this._children(element.firstChild, arguments[1]);
62         } else {
63           var attrs = this._attributes(arguments[1]);
64           if(attrs.length) 
65             element.innerHTML = "<" +elementName + " " +
66               attrs + "></" + elementName + ">";
67         } 
68
69     // text, or array of children
70     if(arguments[2])
71       this._children(element.firstChild, arguments[2]);
72
73      return element.firstChild;
74   },
75   _text: function(text) {
76      return document.createTextNode(text);
77   },
78   _attributes: function(attributes) {
79     var attrs = [];
80     for(attribute in attributes)
81       attrs.push((attribute=='className' ? 'class' : attribute) +
82           '="' + attributes[attribute].toString().escapeHTML() + '"');
83     return attrs.join(" ");
84   },
85   _children: function(element, children) {
86     if(typeof children=='object') { // array can hold nodes and text
87       children = children.flatten();
88       for(var i = 0; i<children.length; i++)
89         if(typeof children[i]=='object')
90           element.appendChild(children[i]);
91         else
92           if(this._isStringOrNumber(children[i]))
93             element.appendChild(this._text(children[i]));
94     } else
95       if(this._isStringOrNumber(children)) 
96          element.appendChild(this._text(children));
97   },
98   _isStringOrNumber: function(param) {
99     return(typeof param=='string' || typeof param=='number');
100   }
101 }
102
103 /* ------------- element ext -------------- */
104
105 // adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp
106 // note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125
107 // instead of "auto" values returns null so it's easier to use with || constructs
108
109 String.prototype.camelize = function() {
110   var oStringList = this.split('-');
111   if(oStringList.length == 1)    
112     return oStringList[0];
113   var ret = this.indexOf("-") == 0 ? 
114     oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
115   for(var i = 1, len = oStringList.length; i < len; i++){
116     var s = oStringList[i];
117     ret += s.charAt(0).toUpperCase() + s.substring(1)
118   }
119   return ret;
120 }
121
122 Element.getStyle = function(element, style) {
123   element = $(element);
124   var value = element.style[style.camelize()];
125   if(!value)
126     if(document.defaultView && document.defaultView.getComputedStyle) {
127       var css = document.defaultView.getComputedStyle(element, null);
128       value = (css!=null) ? css.getPropertyValue(style) : null;
129     } else if(element.currentStyle) {
130       value = element.currentStyle[style.camelize()];  
131     }
132   if(value=='auto') value = null;
133   return value;
134 }
135
136 Element.makePositioned = function(element) {
137   element = $(element);
138   if(Element.getStyle(element, 'position')=='static')
139     element.style.position = "relative";
140 }
141
142 Element.makeClipping = function(element) {
143   element = $(element);
144   element._overflow = Element.getStyle(element, 'overflow') || 'visible';
145   if(element._overflow!='hidden') element.style.overflow = 'hidden';
146 }
147
148 Element.undoClipping = function(element) {
149   element = $(element);
150   if(element._overflow!='hidden') element.style.overflow = element._overflow;
151 }
152
153 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
154   var children = $(element).childNodes;
155   var text     = "";
156   var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
157
158   for (var i = 0; i < children.length; i++) {
159     if(children[i].nodeType==3) {
160       text+=children[i].nodeValue;
161     } else {
162       if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
163         text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
164     }
165   }
166
167   return text;
168 }
169
170 /*--------------------------------------------------------------------------*/
171
172 Position.positionedOffset = function(element) {
173   var valueT = 0, valueL = 0;
174   do {
175     valueT += element.offsetTop  || 0;
176     valueL += element.offsetLeft || 0;
177     element = element.offsetParent;
178     if (element) {
179       p = Element.getStyle(element,'position');
180       if(p == 'relative' || p == 'absolute') break;
181     }
182   } while (element);
183   return [valueL, valueT];
184 }
185
186 // Safari returns margins on body which is incorrect if the child is absolutely positioned.
187 // for performance reasons, we create a specialized version of Position.positionedOffset for
188 // KHTML/WebKit only
189
190 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
191   Position.cumulativeOffset = function(element) {
192     var valueT = 0, valueL = 0;
193     do {
194       valueT += element.offsetTop  || 0;
195       valueL += element.offsetLeft || 0;
196       
197       if (element.offsetParent==document.body) 
198         if (Element.getStyle(element,'position')=='absolute') break;
199         
200       element = element.offsetParent;
201     } while (element);
202     return [valueL, valueT];
203   }
204 }
205
206 Position.page = function(forElement) {
207   if(element == document.body) return [0, 0];
208   var valueT = 0, valueL = 0;
209
210   var element = forElement;
211   do {
212     valueT += element.offsetTop  || 0;
213     valueL += element.offsetLeft || 0;
214
215     // Safari fix
216     if (element.offsetParent==document.body)
217       if (Element.getStyle(element,'position')=='absolute') break;
218       
219   } while (element = element.offsetParent);
220
221   element = forElement;
222   do {
223     valueT -= element.scrollTop  || 0;
224     valueL -= element.scrollLeft || 0;    
225   } while (element = element.parentNode);
226
227   return [valueL, valueT];
228 }
229
230 // elements with display:none don't return an offsetParent, 
231 // fall back to  manual calculation
232 Position.offsetParent = function(element) {
233   if(element.offsetParent) return element.offsetParent;
234   if(element == document.body) return element;
235   
236   while ((element = element.parentNode) && element != document.body)
237     if (Element.getStyle(element,'position')!='static')
238       return element;
239   
240   return document.body;
241 }
242
243 Position.clone = function(source, target) {
244   var options = Object.extend({
245     setLeft:    true,
246     setTop:     true,
247     setWidth:   true,
248     setHeight:  true,
249     offsetTop:  0,
250     offsetLeft: 0
251   }, arguments[2] || {})
252   
253   // find page position of source
254   source = $(source);
255   var p = Position.page(source);
256
257   // find coordinate system to use
258   target = $(target);
259   var delta = [0, 0];
260   var parent = null;
261   // delta [0,0] will do fine with position: fixed elements, 
262   // position:absolute needs offsetParent deltas
263   if (Element.getStyle(target,'position') == 'absolute') {
264     parent = Position.offsetParent(target);
265     delta = Position.page(parent);
266   }
267   
268   // correct by body offsets (fixes Safari)
269   if (parent==document.body) {
270     delta[0] -= document.body.offsetLeft;
271     delta[1] -= document.body.offsetTop; 
272   }
273
274   // set position
275   if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + "px";
276   if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + "px";
277   if(options.setWidth)  target.style.width = source.offsetWidth + "px";
278   if(options.setHeight) target.style.height = source.offsetHeight + "px";
279 }
280
281 Position.absolutize = function(element) {
282   element = $(element);
283   if(element.style.position=='absolute') return;
284   Position.prepare();
285
286   var offsets = Position.positionedOffset(element);
287   var top     = offsets[1];
288   var left    = offsets[0];
289   var width   = element.clientWidth;
290   var height  = element.clientHeight;
291
292   element._originalLeft   = left - parseFloat(element.style.left  || 0);
293   element._originalTop    = top  - parseFloat(element.style.top || 0);
294   element._originalWidth  = element.style.width;
295   element._originalHeight = element.style.height;
296
297   element.style.position = 'absolute';
298   element.style.top    = top + 'px';;
299   element.style.left   = left + 'px';;
300   element.style.width  = width + 'px';;
301   element.style.height = height + 'px';;
302 }
303
304 Position.relativize = function(element) {
305   element = $(element);
306   if(element.style.position=='relative') return;
307   Position.prepare();
308
309   element.style.position = 'relative';
310   var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
311   var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
312
313   element.style.top    = top + 'px';
314   element.style.left   = left + 'px';
315   element.style.height = element._originalHeight;
316   element.style.width  = element._originalWidth;
317 }
318
319 /*--------------------------------------------------------------------------*/
320
321 Element.Class = {
322     // Element.toggleClass(element, className) toggles the class being on/off
323     // Element.toggleClass(element, className1, className2) toggles between both classes,
324     //   defaulting to className1 if neither exist
325     toggle: function(element, className) {
326       if(Element.Class.has(element, className)) {
327         Element.Class.remove(element, className);
328         if(arguments.length == 3) Element.Class.add(element, arguments[2]);
329       } else {
330         Element.Class.add(element, className);
331         if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
332       }
333     },
334
335     // gets space-delimited classnames of an element as an array
336     get: function(element) {
337       element = $(element);
338       return element.className.split(' ');
339     },
340
341     // functions adapted from original functions by Gavin Kistner
342     remove: function(element) {
343       element = $(element);
344       var regEx;
345       for(var i = 1; i < arguments.length; i++) {
346         regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)", 'g');
347         element.className = element.className.replace(regEx, '')
348       }
349     },
350
351     add: function(element) {
352       element = $(element);
353       for(var i = 1; i < arguments.length; i++) {
354         Element.Class.remove(element, arguments[i]);
355         element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
356       }
357     },
358
359     // returns true if all given classes exist in said element
360     has: function(element) {
361       element = $(element);
362       if(!element || !element.className) return false;
363       var regEx;
364       for(var i = 1; i < arguments.length; i++) {
365         if((typeof arguments[i] == 'object') && 
366           (arguments[i].constructor == Array)) {
367           for(var j = 0; j < arguments[i].length; j++) {
368             regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
369             if(!regEx.test(element.className)) return false;
370           }
371         } else {
372           regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
373           if(!regEx.test(element.className)) return false;
374         }
375       }
376       return true;
377     },
378
379     // expects arrays of strings and/or strings as optional paramters
380     // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
381     has_any: function(element) {
382       element = $(element);
383       if(!element || !element.className) return false;
384       var regEx;
385       for(var i = 1; i < arguments.length; i++) {
386         if((typeof arguments[i] == 'object') && 
387           (arguments[i].constructor == Array)) {
388           for(var j = 0; j < arguments[i].length; j++) {
389             regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
390             if(regEx.test(element.className)) return true;
391           }
392         } else {
393           regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
394           if(regEx.test(element.className)) return true;
395         }
396       }
397       return false;
398     },
399
400     childrenWith: function(element, className) {
401       var children = $(element).getElementsByTagName('*');
402       var elements = new Array();
403
404       for (var i = 0; i < children.length; i++) {
405         if (Element.Class.has(children[i], className)) {
406           elements.push(children[i]);
407           break;
408         }
409       }
410
411       return elements;
412     }
413 }
414
415 /*--------------------------------------------------------------------------*/
416
417 String.prototype.parseQuery = function() {
418   var str = this;
419   if(str.substring(0,1) == '?') {
420     str = this.substring(1);
421   }
422   var result = {};
423   var pairs = str.split('&');
424   for(var i = 0; i < pairs.length; i++) {
425     var pair = pairs[i].split('=');
426     result[pair[0]] = pair[1];
427   }
428   return result;
429 }