1/**
2 *  $Id: helpers.js 156 2006-12-23 08:48:25Z wingedfox $
3 *  $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/tags/BrowserExtensions.003/helpers.js $
4 *
5 *  File contains differrent helper functions
6 *
7 * @author Ilya Lebedev <ilya@lebedev.net>
8 * @license LGPL
9 * @version $Rev: 156 $
10 */
11//-----------------------------------------------------------------------------
12//  Variable/property checks
13//-----------------------------------------------------------------------------
14/**
15 *  Checks if property is undefined
16 *
17 *  @param {Object} prop value to check
18 *  @return {Boolean} true if matched
19 *  @scope public
20 */
21function isUndefined (prop /* :Object */) /* :Boolean */ {
22  return (typeof prop == 'undefined');
23}
24/**
25 *  Checks if property is function
26 *
27 *  @param {Object} prop value to check
28 *  @return {Boolean} true if matched
29 *  @scope public
30 */
31function isFunction (prop /* :Object */) /* :Boolean */ {
32  return (typeof prop == 'function');
33}
34/**
35 *  Checks if property is string
36 *
37 *  @param {Object} prop value to check
38 *  @return {Boolean} true if matched
39 *  @scope public
40 */
41function isString (prop /* :Object */) /* :Boolean */ {
42  return (typeof prop == 'string');
43}
44/**
45 *  Checks if property is number
46 *
47 *  @param {Object} prop value to check
48 *  @return {Boolean} true if matched
49 *  @scope public
50 */
51function isNumber (prop /* :Object */) /* :Boolean */ {
52  return (typeof prop == 'number');
53}
54/**
55 *  Checks if property is the calculable number
56 *
57 *  @param {Object} prop value to check
58 *  @return {Boolean} true if matched
59 *  @scope public
60 */
61function isNumeric (prop /* :Object */) /* :Boolean */ {
62  return isNumber(prop)&&!isNaN(prop)&&isFinite(prop);
63}
64/**
65 *  Checks if property is array
66 *
67 *  @param {Object} prop value to check
68 *  @return {Boolean} true if matched
69 *  @scope public
70 */
71function isArray (prop /* :Object */) /* :Boolean */ {
72  return (prop instanceof Array);
73}
74/**
75 *  Checks if property is regexp
76 *
77 *  @param {Object} prop value to check
78 *  @return {Boolean} true if matched
79 *  @scope public
80 */
81function isRegExp (prop /* :Object */) /* :Boolean */ {
82  return (prop instanceof RegExp);
83}
84/**
85 *  Checks if property is a boolean value
86 *
87 *  @param {Object} prop value to check
88 *  @return {Boolean} true if matched
89 *  @scope public
90 */
91function isBoolean (prop /* :Object */) /* :Boolean */ {
92  return ('boolean' == typeof prop);
93}
94/**
95 *  Checks if property is a scalar value (value that could be used as the hash key)
96 *
97 *  @param {Object} prop value to check
98 *  @return {Boolean} true if matched
99 *  @scope public
100 */
101function isScalar (prop /* :Object */) /* :Boolean */ {
102  return isNumeric(prop)||isString(prop);
103}
104/**
105 *  Checks if property is empty
106 *
107 *  @param {Object} prop value to check
108 *  @return {Boolean} true if matched
109 *  @scope public
110 */
111function isEmpty (prop /* :Object */) /* :Boolean */ {
112  if (isBoolean(prop)) return false;
113  if (isRegExp(prop) && new RegExp("").toString() == prop.toString()) return true;
114  if (isString(prop) || isNumber(prop)) return !prop;
115  if (Boolean(prop)&&false != prop) {
116    for (var i in prop) if(prop.hasOwnProperty(i)) return false
117  }
118  return true;
119}
120
121function dump (v, l) {
122    var s = [];
123    if (!l) l=0;
124    if (l>2) return "***********8Recursion*************";
125    var sp = "";
126    for (var i=0;i<l;i++) sp+="    ";
127    for (var i in v) {
128//        if (!v.hasOwnProperty(i)) continue;
129        var q = [sp,i,': '];
130        try {
131        if (!isScalar(v[i]) && !isFunction(v[i])) {
132            q[q.length] = '{';
133            s[s.length] = q.join("");
134            s[s.length] = dump(v[i],l+1);
135            s[s.length] = '}';
136        } else {
137            q[q.length] = v[i];
138            s[s.length] = q.join("");
139        }
140        } catch (err) {}
141    }
142    return s.join("\n");
143}
144//-----------------------------------------------------------------------------
145//  File paths functions
146//-----------------------------------------------------------------------------
147/**
148 *  used to glue path's
149 *
150 *  @param {String} number of strings
151 *  @return {String} glued path
152 *  @scope public
153 */
154function gluePath () /* :String */ {
155  var aL=arguments.length, i=aL-2, s = arguments[aL-1];
156  for(;i>=0;i--)
157    s = ((!isString(arguments[i])&&!isNumber(arguments[i]))||isEmpty(arguments[i])
158        ?s
159        :arguments[i]+'\x00'+s);
160  return s?s.replace(/\/*\x00+\/*/g,"/"):"";
161}
162
163/**
164 *  return full path to the script
165 *
166 *  @param {String} sname script name
167 *  @return {String, Null} mixed string full path or null
168 *  @scope public
169 */
170function findPath (sname /* :String */) /* :String */{
171  var sc = document.getElementsByTagName('script'),
172      sr = new RegExp('^(.*/|)('+sname+')([#?]|$)');
173  for (var i=0,scL=sc.length; i<scL; i++) {
174    // matched desired script
175    var m = String(sc[i].src).match(sr);
176    if (m) {
177      /*
178      *  we've matched the full path
179      */
180      if (m[1].match(/^((https?|file)\:\/{2,}|\w:[\\])/)) return m[1];
181      /*
182      *  we've matched absolute path from the site root
183      */
184      if (m[1].indexOf("/")==0) return m[1];
185      b = document.getElementsByTagName('base');
186      if (b[0] && b[0].href) return b[0].href+m[1];
187      /*
188      *  return matching part of the document location and path to js file
189      */
190      return (document.location.href.match(/(.*[\/\\])/)[0]+m[1]).replace(/^\/+(?=\w:)/,"");
191    }
192  }
193  return null;
194}
195
196//-----------------------------------------------------------------------------
197//  DOM related stuff
198//-----------------------------------------------------------------------------
199if (isUndefined(DOM)) var DOM = {};
200/**
201 *  Performs parent lookup by
202 *   - node object: actually it's "is child of" check
203 *   - tagname: getParent(el, 'li') == getParent(el, 'tagName', 'LI')
204 *   - any node attribute
205 *
206 *  @param {HTMLElement} el source element
207 *  @param {HTMLElement, String} cp DOMNode or string tagname or string attribute name
208 *  @param {String} vl optional attribute value
209 *  @return {HTMLElement, Null}
210 *  @scope public
211 */
212DOM.getParent = function (el /* : HTMLElement */, cp /* :String, HTMLElement */, vl /* :String */) /* :HTMLElement */ {
213  if (el == null) return null;
214  else if (el.nodeType == 1 &&
215      ((!isUndefined(vl) && el[cp] == vl) ||
216       ('string' == typeof cp && DOM.hasTagName(el, cp)) ||
217       el == cp)) return el;
218  else return arguments.callee(el.parentNode, cp, vl);
219}
220/**
221 *  Calculates the offset for the DOM node from top left corner
222 *
223 *  @author Matt Kruse
224 *  @see http://javascripttoolbox.com/lib/objectposition/index.php
225 *  @param {HTMLElement} el
226 *  @return {Object} x: horizontal offset, y: vertical offset
227 *  @scope public
228 */
229DOM.getOffset = function (el /* :HTMLElement */) /* :Object */ {
230    var fixBrowserQuirks = true
231       ,o = el
232       ,left = 0
233       ,top = 0
234       ,width = 0
235       ,height = 0
236       ,parentNode = null
237       ,offsetParent = null;
238
239    if (o==null) return null;
240
241    offsetParent = o.offsetParent;
242    var originalObject = o
243       ,el = o; // "el" will be nodes as we walk up, "o" will be saved for offsetParent references
244    while (el.parentNode!=null) {
245      el = el.parentNode;
246      if (el.offsetParent==null) {
247      }
248      else {
249        var considerScroll = true;
250        /*
251        In Opera, if parentNode of the first object is scrollable, then offsetLeft/offsetTop already
252        take its scroll position into account. If elements further up the chain are scrollable, their
253        scroll offsets still need to be added in. And for some reason, TR nodes have a scrolltop value
254        which must be ignored.
255        */
256        if (fixBrowserQuirks && window.opera) {
257          if (el==originalObject.parentNode || el.nodeName=="TR") {
258            considerScroll = false;
259          }
260        }
261        if (considerScroll) {
262          if (el.scrollTop && el.scrollTop>0) {
263            top -= el.scrollTop;
264          }
265          if (el.scrollLeft && el.scrollLeft>0) {
266            left -= el.scrollLeft;
267          }
268        }
269      }
270      // If this node is also the offsetParent, add on the offsets and reset to the new offsetParent
271      if (el == offsetParent) {
272        left += o.offsetLeft;
273        if (el.clientLeft && el.nodeName!="TABLE") {
274          left += el.clientLeft;
275        }
276        top += o.offsetTop;
277        if (el.clientTop && el.nodeName!="TABLE") {
278          top += el.clientTop;
279        }
280        o = el;
281        if (o.offsetParent==null) {
282          if (o.offsetLeft) {
283            left += o.offsetLeft;
284          }
285          if (o.offsetTop) {
286            top += o.offsetTop;
287          }
288        }
289        offsetParent = o.offsetParent;
290      }
291    }
292
293
294    if (originalObject.offsetWidth) {
295      width = originalObject.offsetWidth;
296    }
297    if (originalObject.offsetHeight) {
298      height = originalObject.offsetHeight;
299    }
300
301    return {'x':left, 'y':top, 'width':width, 'height':height};
302  };
303
304//DOM.getOffset = function (el /* :HTMLElement */) /* :Object */ {
305/*
306    var xy = {'x' : el.offsetLeft , 'y' : el.offsetTop};
307    if (el.offsetParent) {
308        var xy1 = arguments.callee(el.offsetParent);
309        xy.x += xy1.x;
310        xy.y += xy1.y;
311    }
312    return xy;
313}
314*/
315/**
316 *  Returns the width of the window canvas
317 *
318 *  @return {Number}
319 *  @scope public
320 */
321DOM.getClientWidth = function () /* :Number */{
322    var w=0;
323    if (self.innerHeight) w = self.innerWidth;
324    else if (document.documentElement && document.documentElement.clientWidth) w = document.documentElement.clientWidth;
325    else if (document.body) w = document.body.clientWidth;
326    return w;
327}
328/**
329 *  Returns the height of the window canvas
330 *
331 *  @return {Number}
332 *  @scope public
333 */
334DOM.getClientHeight = function () /* :Number */{
335    var h=0;
336    if (self.innerHeight) h = self.innerHeight;
337    else if (document.documentElement && document.documentElement.clientHeight) h = document.documentElement.clientHeight;
338    else if (document.body) h = document.body.clientHeight;
339    return h;
340}
341/**
342 *  Returns the height of the scrolled area for the body
343 *
344 *  @return {Number}
345 *  @scope public
346 */
347DOM.getBodyScrollTop = function () /* :Number */{
348    return self.pageYOffset || (document.documentElement && document.documentElement.scrollTop) || (document.body && document.body.scrollTop);
349}
350/**
351 *  Returns the height of the scrolled area for the body
352 *
353 *  @return {Number}
354 *  @scope public
355 */
356DOM.getBodyScrollLeft = function () /* :Number */{
357    return self.pageXOffset || (document.documentElement && document.documentElement.scrollLeft) || (document.body && document.body.scrollLeft);
358}
359/**
360 *  Checks, if property matches a tagname(s)
361 *
362 *  @param {HTMLElement} prop
363 *  @param {String, Array} tags
364 *  @return {Boolean}
365 *  @scope public
366 */
367DOM.hasTagName = function (prop /* :HTMLElement */, tags /* :String, Array */) {
368    if (isString(tags)) tags = [tags];
369    if (!isArray(tags) || isEmpty(tags) || isUndefined(prop) || isEmpty(prop.tagName)) return false;
370    var t = prop.tagName.toLowerCase();
371    for (var i=0, tL=tags.length; i<tL; i++) {
372        if (tags[i].toLowerCase() == t) return true;
373    }
374    return false;
375}
376/**
377 *  Calculates client area width
378 *
379 *  @return {Number}
380 *  @scope public
381 */
382function getClientWidth() {
383        var w=0;
384        if (self.innerHeight) w = self.innerWidth;
385        else if (document.documentElement && document.documentElement.clientWidth) w = document.documentElement.clientWidth;
386        else if (document.body) w = document.body.clientWidth;
387        return w;
388}
389/**
390 *  Calculates client area height
391 *
392 *  @return {Number}
393 *  @scope public
394 */
395function getClientHeight() {
396        var h=0;
397        if (self.innerHeight) h = self.innerHeight;
398        else if (document.documentElement && document.documentElement.clientHeight) h = document.documentElement.clientHeight;
399        else if (document.body) h = document.body.clientHeight;
400        return h;
401}
402/**
403 *  Calculates the center of client area by the X axis
404 *
405 *  @return {Number}
406 *  @scope public
407 */
408function getClientCenterX() {
409        return parseInt(getClientWidth()/2)+getBodyScrollLeft();
410}
411/**
412 *  Calculates the center of client area by the Y axis
413 *
414 *  @return {Number}
415 *  @scope public
416 */
417function getClientCenterY() {
418        return parseInt(getClientHeight()/2)+getBodyScrollTop();
419}
420/**
421 *  Calculates scroll height
422 *
423 *  @return {Number}
424 *  @scope public
425 */
426function getBodyScrollTop() {
427        return self.pageYOffset || (document.documentElement && document.documentElement.scrollTop) || (document.body && document.body.scrollTop);
428}
429/**
430 *  Calculates scroll width
431 *
432 *  @return {Number}
433 *  @scope public
434 */
435function getBodyScrollLeft() {
436        return self.pageXOffset || (document.documentElement && document.documentElement.scrollLeft) || (document.body && document.body.scrollLeft);
437}
438
439/**
440 *  Method is used to convert table into the array
441 *
442 * @param {String, HTMLTableElement, HTMLTBodyElement, HTMLTHeadElement, HTMLTFootElement} id
443 * @param {Number} ci column indexes to put in the array
444 * @param {String} section optional section type
445 * @param {Object} subsection optional subsection index
446 * @return {NULL, Array}
447 * @scope public
448 */
449
450//-----------------------------------------------------------------------------
451//  Misc helpers
452//-----------------------------------------------------------------------------
453function table2array (id, ci, section, subsection) {
454    if (isString(id)) id = document.getElementById(id);
455    if (!id || !DOM.hasTagName(id, ['table','tbody,','thead','tfoot'])) return null;
456    if (!isEmpty(section) && (!isString(section) || !(id = id.getElementsByTagName(section)))) return null;
457    if (!isEmpty(subsection) && (!isNumber(subsection) || subsection<0 || !(id = id[subsection]))) return null;
458    if (isUndefined(id.rows)) return null;
459
460    var res = [];
461    for (var i=0, rL=id.rows.length; i<rL; i++) {
462        var tr = [];
463        if (isArray(ci)) {
464            for (var o=0, cL=ci.length; o<cL; o++)
465                if (id.rows[i].cells[ci[o]]) tr[tr.length] = id.rows[i].cells[ci[o]].innerHTML.replace(/<[^>]*?>/g,"");
466
467        } else {
468            for (var z=0, tL=id.rows[i].cells.length; z<tL; z++)
469                tr[tr.length] = id.rows[i].cells[z].innerHTML.replace(/<[^>]*?>/g,"");
470        }
471        if (!isEmpty(tr)) res[res.length] = tr;
472    }
473    return res;
474}
475
476/**
477 *  Creates element all-at-once
478 *
479 *  @param {String} tag name
480 *  @param {Object} p element properties { 'class' : 'className',
481 *                                         'style' : { 'property' : value, ... },
482 *                                         'event' : { 'eventType' : handler, ... },
483 *                                         'child' : [ child1, child2, ...],
484 *                                         'param' : { 'property' : value, ... },
485 *  @return {HTMLElement} created element or null
486 *  @scope public
487 */
488document.createElementExt = function (tag /* :String */, p /* :Object */ ) /* :HTMLElement */{
489  var L, i, k, el = document.createElement(tag);
490  if (!el) return null;
491  for (i in p) {
492    if (!p.hasOwnProperty(i)) continue;
493    switch (i) {
494      case "class" : el.setAttribute('className',p[i]); el.setAttribute('class',p[i]); break;
495      case "style" : for (k in p[i]) { if (!p[i].hasOwnProperty(k)) continue; el.style[k] = p[i][k]; } break;
496      case "event" : for (k in p[i]) { if (!p[i].hasOwnProperty(k)) continue; el.attachEvent(k,p[i][k]); } break;
497      case "child" : L = p[i].length; for (k = 0; k<L; k++) el.appendChild(p[i][k]); break;
498      case "param" : for (k in p[i]) { if (!p[i].hasOwnProperty(k)) continue; try { el[k] = p[i][k] } catch(e) {} } break;
499    }
500  }
501  return el;
502}
503
504/**
505 * simple setInterval/setTimout wrappers
506 *
507 * @param {Function} f function to be launched
508 * @param {Number} i interval
509 * @param {Array} o optional function parameters to be applied
510 * @return {Number} interval id
511 * @scope public
512 */
513function playInterval (f /* :Function */, i /* :Number */, o /* :Array */) /* :Number */ { return setInterval(function(){(o instanceof Array)?f.apply(this,o):f.call(this,o)},i) }
514function playTimeout (f /* :Function */, i /* :Number */, o /* :Array */) /* :Number */ { return setTimeout(function(){(o instanceof Array)?f.apply(this,o):f.call(this,o)},i) }
515