* Fixed a multiselect bug in the mailbox view. Ctrl-click was selecting a message...
[citadel.git] / webcit / static / util.js
blob20c08831b98a3d4c82c8755da34bb511b836ac98
1 // small but works-for-me stuff for testing javascripts
2 // not ready for "production" use
4 Object.inspect = function(obj) {
5 var info = [];
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]));
18 return ("'" + obj + "' #" + typeof obj +
19 ": {" + info.join(", ") + "}");
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 License, or (at your option) any later version.
29 // See http://www.gnu.org/copyleft/lesser.html
31 Array.flatten = function(array, excludeUndefined) {
32 if (excludeUndefined === undefined) {
33 excludeUndefined = false;
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);
46 return result;
49 if (!Array.prototype.flatten) {
50 Array.prototype.flatten = function(excludeUndefined) {
51 return Array.flatten(this, excludeUndefined);
55 /*--------------------------------------------------------------------------*/
57 var Builder = {
58 node: function(elementName) {
59 var element = document.createElement('div');
60 element.innerHTML =
61 "<" + elementName + "></" + elementName + ">";
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 + ">";
75 // text, or array of children
76 if(arguments[2])
77 this._children(element.firstChild, arguments[2]);
79 return element.firstChild;
81 _text: function(text) {
82 return document.createTextNode(text);
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(" ");
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));
104 _isStringOrNumber: function(param) {
105 return(typeof param=='string' || typeof param=='number');
109 /* ------------- element ext -------------- */
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
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)
125 return ret;
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()];
138 if(value=='auto') value = null;
139 return value;
142 Element.makePositioned = function(element) {
143 element = $(element);
144 if(Element.getStyle(element, 'position')=='static')
145 element.style.position = "relative";
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';
154 Element.undoClipping = function(element) {
155 element = $(element);
156 if(element._overflow!='hidden') element.style.overflow = element._overflow;
159 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
160 var children = $(element).childNodes;
161 var text = "";
162 var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
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);
173 return text;
176 /*--------------------------------------------------------------------------*/
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;
188 } while (element);
189 return [valueL, valueT];
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
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;
203 if (element.offsetParent==document.body)
204 if (Element.getStyle(element,'position')=='absolute') break;
206 element = element.offsetParent;
207 } while (element);
208 return [valueL, valueT];
212 Position.page = function(forElement) {
213 if(element == document.body) return [0, 0];
214 var valueT = 0, valueL = 0;
216 var element = forElement;
217 do {
218 valueT += element.offsetTop || 0;
219 valueL += element.offsetLeft || 0;
221 // Safari fix
222 if (element.offsetParent==document.body)
223 if (Element.getStyle(element,'position')=='absolute') break;
225 } while (element = element.offsetParent);
227 element = forElement;
228 do {
229 valueT -= element.scrollTop || 0;
230 valueL -= element.scrollLeft || 0;
231 } while (element = element.parentNode);
233 return [valueL, valueT];
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;
242 while ((element = element.parentNode) && element != document.body)
243 if (Element.getStyle(element,'position')!='static')
244 return element;
246 return document.body;
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] || {})
259 // find page position of source
260 source = $(source);
261 var p = Position.page(source);
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);
274 // correct by body offsets (fixes Safari)
275 if (parent==document.body) {
276 delta[0] -= document.body.offsetLeft;
277 delta[1] -= document.body.offsetTop;
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";
287 Position.absolutize = function(element) {
288 element = $(element);
289 if(element.style.position=='absolute') return;
290 Position.prepare();
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;
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;
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';;
310 Position.relativize = function(element) {
311 element = $(element);
312 if(element.style.position=='relative') return;
313 Position.prepare();
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);
319 element.style.top = top + 'px';
320 element.style.left = left + 'px';
321 element.style.height = element._originalHeight;
322 element.style.width = element._originalWidth;
325 /*--------------------------------------------------------------------------*/
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]);
341 // gets space-delimited classnames of an element as an array
342 get: function(element) {
343 element = $(element);
344 return element.className.split(' ');
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, '')
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];
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;
377 } else {
378 regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
379 if(!regEx.test(element.className)) return false;
382 return true;
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;
398 } else {
399 regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
400 if(regEx.test(element.className)) return true;
403 return false;
406 childrenWith: function(element, className) {
407 var children = $(element).getElementsByTagName('*');
408 var elements = new Array();
410 for (var i = 0; i < children.length; i++) {
411 if (Element.Class.has(children[i], className)) {
412 elements.push(children[i]);
413 break;
417 return elements;
421 /*--------------------------------------------------------------------------*/
423 String.prototype.parseQuery = function() {
424 var str = this;
425 if(str.substring(0,1) == '?') {
426 str = this.substring(1);
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];
434 return result;