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