Merge pull request #1967 from solgenomics/topic/trait_props
[sgn.git] / js / MochiKit / DOM.js
blob8ac4c355198f22ff2996a62597a9d5683e00c164
1 /***
3 MochiKit.DOM 1.4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2005 Bob Ippolito.  All rights Reserved.
9 ***/
11 if (typeof(dojo) != 'undefined') {
12     dojo.provide("MochiKit.DOM");
13     dojo.require("MochiKit.Base");
15 if (typeof(JSAN) != 'undefined') {
16     JSAN.use("MochiKit.Base", []);
19 try {
20     if (typeof(MochiKit.Base) == 'undefined') {
21         throw "";
22     }
23 } catch (e) {
24     throw "MochiKit.DOM depends on MochiKit.Base!";
27 if (typeof(MochiKit.DOM) == 'undefined') {
28     MochiKit.DOM = {};
31 MochiKit.DOM.NAME = "MochiKit.DOM";
32 MochiKit.DOM.VERSION = "1.4";
33 MochiKit.DOM.__repr__ = function () {
34     return "[" + this.NAME + " " + this.VERSION + "]";
36 MochiKit.DOM.toString = function () {
37     return this.__repr__();
40 MochiKit.DOM.EXPORT = [
41     "removeEmptyTextNodes",
42     "formContents",
43     "currentWindow",
44     "currentDocument",
45     "withWindow",
46     "withDocument",
47     "registerDOMConverter",
48     "coerceToDOM",
49     "createDOM",
50     "createDOMFunc",
51     "isChildNode",
52     "getNodeAttribute",
53     "removeNodeAttribute",
54     "setNodeAttribute",
55     "updateNodeAttributes",
56     "appendChildNodes",
57     "insertSiblingNodesAfter",
58     "insertSiblingNodesBefore",
59     "replaceChildNodes",
60     "removeElement",
61     "swapDOM",
62     "BUTTON",
63     "TT",
64     "PRE",
65     "H1",
66     "H2",
67     "H3",
68     "BR",
69     "CANVAS",
70     "HR",
71     "LABEL",
72     "TEXTAREA",
73     "FORM",
74     "STRONG",
75     "SELECT",
76     "OPTION",
77     "OPTGROUP",
78     "LEGEND",
79     "FIELDSET",
80     "P",
81     "UL",
82     "OL",
83     "LI",
84     "TD",
85     "TR",
86     "THEAD",
87     "TBODY",
88     "TFOOT",
89     "TABLE",
90     "TH",
91     "INPUT",
92     "SPAN",
93     "A",
94     "DIV",
95     "IMG",
96     "getElement",
97     "$",
98     "getElementsByTagAndClassName",
99     "addToCallStack",
100     "addLoadEvent",
101     "focusOnLoad",
102     "setElementClass",
103     "toggleElementClass",
104     "addElementClass",
105     "removeElementClass",
106     "swapElementClass",
107     "hasElementClass",
108     "escapeHTML",
109     "toHTML",
110     "emitHTML",
111     "scrapeText",
112     "isParent",
113     "getFirstParentByTagAndClassName",
114     "makeClipping",
115     "undoClipping",
116     "makePositioned",
117     "undoPositioned",
118     "getFirstElementByTagAndClassName"
121 MochiKit.DOM.EXPORT_OK = [
122     "domConverters"
125 MochiKit.DOM.DEPRECATED = [
126     ['computedStyle', 'MochiKit.Style.getStyle', '1.4'],
127     /** @id MochiKit.DOM.elementDimensions  */
128     ['elementDimensions', 'MochiKit.Style.getElementDimensions', '1.4'],
129     /** @id MochiKit.DOM.elementPosition  */
130     ['elementPosition', 'MochiKit.Style.getElementPosition', '1.4'],
131     ['hideElement', 'MochiKit.Style.hideElement', '1.4'],
132     /** @id MochiKit.DOM.setElementDimensions */
133     ['setElementDimensions', 'MochiKit.Style.setElementDimensions', '1.4'],
134     /** @id MochiKit.DOM.setElementPosition */
135     ['setElementPosition', 'MochiKit.Style.setElementPosition', '1.4'],
136     ['setDisplayForElement', 'MochiKit.Style.setDisplayForElement', '1.4'],
137     /** @id MochiKit.DOM.setOpacity */
138     ['setOpacity', 'MochiKit.Style.setOpacity', '1.4'],
139     ['showElement', 'MochiKit.Style.showElement', '1.4'],
140     /** @id MochiKit.DOM.Coordinates */
141     ['Coordinates', 'MochiKit.Style.Coordinates', '1.4'], // FIXME: broken
142     /** @id MochiKit.DOM.Dimensions */
143     ['Dimensions', 'MochiKit.Style.Dimensions', '1.4'] // FIXME: broken
146 /** @id MochiKit.DOM.getViewportDimensions */
147 MochiKit.DOM.getViewportDimensions = new Function('' +
148     'if (!MochiKit["Style"]) {' +
149     '    throw new Error("This function has been deprecated and depends on MochiKit.Style.");' +
150     '}' +
151     'return MochiKit.Style.getViewportDimensions.apply(this, arguments);');
153 MochiKit.Base.update(MochiKit.DOM, {
155     /** @id MochiKit.DOM.currentWindow */
156     currentWindow: function () {
157         return MochiKit.DOM._window;
158     },
160     /** @id MochiKit.DOM.currentDocument */
161     currentDocument: function () {
162         return MochiKit.DOM._document;
163     },
165     /** @id MochiKit.DOM.withWindow */
166     withWindow: function (win, func) {
167         var self = MochiKit.DOM;
168         var oldDoc = self._document;
169         var oldWin = self._window;
170         var rval;
171         try {
172             self._window = win;
173             self._document = win.document;
174             rval = func();
175         } catch (e) {
176             self._window = oldWin;
177             self._document = oldDoc;
178             throw e;
179         }
180         self._window = oldWin;
181         self._document = oldDoc;
182         return rval;
183     },
185     /** @id MochiKit.DOM.formContents  */
186     formContents: function (elem/* = document.body */) {
187         var names = [];
188         var values = [];
189         var m = MochiKit.Base;
190         var self = MochiKit.DOM;
191         if (typeof(elem) == "undefined" || elem === null) {
192             elem = self._document.body;
193         } else {
194             elem = self.getElement(elem);
195         }
196         m.nodeWalk(elem, function (elem) {
197             var name = elem.name;
198             if (m.isNotEmpty(name)) {
199                 var tagName = elem.tagName.toUpperCase();
200                 if (tagName === "INPUT"
201                     && (elem.type == "radio" || elem.type == "checkbox")
202                     && !elem.checked
203                 ) {
204                     return null;
205                 }
206                 if (tagName === "SELECT") {
207                     if (elem.type == "select-one") {
208                         if (elem.selectedIndex >= 0) {
209                             var opt = elem.options[elem.selectedIndex];
210                             var v = opt.value;
211                             if (!v) {
212                                 var h = opt.outerHTML;
213                                 // internet explorer sure does suck.
214                                 if (h && !h.match(/^[^>]+\svalue\s*=/i)) {
215                                     v = opt.text;
216                                 }
217                             }
218                             names.push(name);
219                             values.push(v);
220                             return null;
221                         }
222                         // no form elements?
223                         names.push(name);
224                         values.push("");
225                         return null;
226                     } else {
227                         var opts = elem.options;
228                         if (!opts.length) {
229                             names.push(name);
230                             values.push("");
231                             return null;
232                         }
233                         for (var i = 0; i < opts.length; i++) {
234                             var opt = opts[i];
235                             if (!opt.selected) {
236                                 continue;
237                             }
238                             var v = opt.value;
239                             if (!v) {
240                                 var h = opt.outerHTML;
241                                 // internet explorer sure does suck.
242                                 if (h && !h.match(/^[^>]+\svalue\s*=/i)) {
243                                     v = opt.text;
244                                 }
245                             }
246                             names.push(name);
247                             values.push(v);
248                         }
249                         return null;
250                     }
251                 }
252                 if (tagName === "FORM" || tagName === "P" || tagName === "SPAN"
253                     || tagName === "DIV"
254                 ) {
255                     return elem.childNodes;
256                 }
257                 names.push(name);
258                 values.push(elem.value || '');
259                 return null;
260             }
261             return elem.childNodes;
262         });
263         return [names, values];
264     },
266     /** @id MochiKit.DOM.withDocument */
267     withDocument: function (doc, func) {
268         var self = MochiKit.DOM;
269         var oldDoc = self._document;
270         var rval;
271         try {
272             self._document = doc;
273             rval = func();
274         } catch (e) {
275             self._document = oldDoc;
276             throw e;
277         }
278         self._document = oldDoc;
279         return rval;
280     },
282     /** @id MochiKit.DOM.registerDOMConverter */
283     registerDOMConverter: function (name, check, wrap, /* optional */override) {
284         MochiKit.DOM.domConverters.register(name, check, wrap, override);
285     },
287     /** @id MochiKit.DOM.coerceToDOM */
288     coerceToDOM: function (node, ctx) {
289         var m = MochiKit.Base;
290         var im = MochiKit.Iter;
291         var self = MochiKit.DOM;
292         if (im) {
293             var iter = im.iter;
294             var repeat = im.repeat;
295             var map = m.map;
296         }
297         var domConverters = self.domConverters;
298         var coerceToDOM = arguments.callee;
299         var NotFound = m.NotFound;
300         while (true) {
301             if (typeof(node) == 'undefined' || node === null) {
302                 return null;
303             }
304             // this is a safari childNodes object, avoiding crashes w/ attr
305             // lookup
306             if (typeof(node) == "function" &&
307                     typeof(node.length) == "number" &&
308                     !(node instanceof Function)) {
309                 node = im.list(node);
310             }
311             if (typeof(node.nodeType) != 'undefined' && node.nodeType > 0) {
312                 return node;
313             }
314             if (typeof(node) == 'number' || typeof(node) == 'boolean') {
315                 node = node.toString();
316                 // FALL THROUGH
317             }
318             if (typeof(node) == 'string') {
319                 return self._document.createTextNode(node);
320             }
321             if (typeof(node.__dom__) == 'function') {
322                 node = node.__dom__(ctx);
323                 continue;
324             }
325             if (typeof(node.dom) == 'function') {
326                 node = node.dom(ctx);
327                 continue;
328             }
329             if (typeof(node) == 'function') {
330                 node = node.apply(ctx, [ctx]);
331                 continue;
332             }
334             if (im) {
335                 // iterable
336                 var iterNodes = null;
337                 try {
338                     iterNodes = iter(node);
339                 } catch (e) {
340                     // pass
341                 }
342                 if (iterNodes) {
343                     return map(coerceToDOM, iterNodes, repeat(ctx));
344                 }
345             }
347             // adapter
348             try {
349                 node = domConverters.match(node, ctx);
350                 continue;
351             } catch (e) {
352                 if (e != NotFound) {
353                     throw e;
354                 }
355             }
357             // fallback
358             return self._document.createTextNode(node.toString());
359         }
360         // mozilla warnings aren't too bright
361         return undefined;
362     },
364     /** @id MochiKit.DOM.isChildNode */
365     isChildNode: function (node, maybeparent) {
366         var self = MochiKit.DOM;
367         if (typeof(node) == "string") {
368             node = self.getElement(node);
369         }
370         if (typeof(maybeparent) == "string") {
371             maybeparent = self.getElement(maybeparent);
372         }
373         if (node === maybeparent) {
374             return true;
375         }
376         while (node && node.tagName.toUpperCase() != "BODY") {
377             node = node.parentNode;
378             if (node === maybeparent) {
379                 return true;
380             }
381         }
382         return false;
383     },
385     /** @id MochiKit.DOM.setNodeAttribute */
386     setNodeAttribute: function (node, attr, value) {
387         var o = {};
388         o[attr] = value;
389         try {
390             return MochiKit.DOM.updateNodeAttributes(node, o);
391         } catch (e) {
392             // pass
393         }
394         return null;
395     },
397     /** @id MochiKit.DOM.getNodeAttribute */
398     getNodeAttribute: function (node, attr) {
399         var self = MochiKit.DOM;
400         var rename = self.attributeArray.renames[attr];
401         node = self.getElement(node);
402         try {
403             if (rename) {
404                 return node[rename];
405             }
406             return node.getAttribute(attr);
407         } catch (e) {
408             // pass
409         }
410         return null;
411     },
413     /** @id MochiKit.DOM.removeNodeAttribute */
414     removeNodeAttribute: function (node, attr) {
415         var self = MochiKit.DOM;
416         var rename = self.attributeArray.renames[attr];
417         node = self.getElement(node);
418         try {
419             if (rename) {
420                 return node[rename];
421             }
422             return node.removeAttribute(attr);
423         } catch (e) {
424             // pass
425         }
426         return null;
427     },
429     /** @id MochiKit.DOM.updateNodeAttributes */
430     updateNodeAttributes: function (node, attrs) {
431         var elem = node;
432         var self = MochiKit.DOM;
433         if (typeof(node) == 'string') {
434             elem = self.getElement(node);
435         }
436         if (attrs) {
437             var updatetree = MochiKit.Base.updatetree;
438             if (self.attributeArray.compliant) {
439                 // not IE, good.
440                 for (var k in attrs) {
441                     var v = attrs[k];
442                     if (typeof(v) == 'object' && typeof(elem[k]) == 'object') {
443                         if (k == "style" && MochiKit.Style) {
444                             MochiKit.Style.setStyle(elem, v);
445                         } else {
446                             updatetree(elem[k], v);
447                         }
448                     } else if (k.substring(0, 2) == "on") {
449                         if (typeof(v) == "string") {
450                             v = new Function(v);
451                         }
452                         elem[k] = v;
453                     } else {
454                         elem.setAttribute(k, v);
455                     }
456                 }
457             } else {
458                 // IE is insane in the membrane
459                 var renames = self.attributeArray.renames;
460                 for (k in attrs) {
461                     v = attrs[k];
462                     var renamed = renames[k];
463                     if (k == "style" && typeof(v) == "string") {
464                         elem.style.cssText = v;
465                     } else if (typeof(renamed) == "string") {
466                         elem[renamed] = v;
467                     } else if (typeof(elem[k]) == 'object'
468                             && typeof(v) == 'object') {
469                         if (k == "style" && MochiKit.Style) {
470                             MochiKit.Style.setStyle(elem, v);
471                         } else {
472                             updatetree(elem[k], v);
473                         }
474                     } else if (k.substring(0, 2) == "on") {
475                         if (typeof(v) == "string") {
476                             v = new Function(v);
477                         }
478                         elem[k] = v;
479                     } else {
480                         elem.setAttribute(k, v);
481                     }
482                 }
483             }
484         }
485         return elem;
486     },
488     /** @id MochiKit.DOM.appendChildNodes */
489     appendChildNodes: function (node/*, nodes...*/) {
490         var elem = node;
491         var self = MochiKit.DOM;
492         if (typeof(node) == 'string') {
493             elem = self.getElement(node);
494         }
495         var nodeStack = [
496             self.coerceToDOM(
497                 MochiKit.Base.extend(null, arguments, 1),
498                 elem
499             )
500         ];
501         var concat = MochiKit.Base.concat;
502         while (nodeStack.length) {
503             var n = nodeStack.shift();
504             if (typeof(n) == 'undefined' || n === null) {
505                 // pass
506             } else if (typeof(n.nodeType) == 'number') {
507                 elem.appendChild(n);
508             } else {
509                 nodeStack = concat(n, nodeStack);
510             }
511         }
512         return elem;
513     },
516     /** @id MochiKit.DOM.insertSiblingNodesBefore */
517     insertSiblingNodesBefore: function (node/*, nodes...*/) {
518         var elem = node;
519         var self = MochiKit.DOM;
520         if (typeof(node) == 'string') {
521             elem = self.getElement(node);
522         }
523         var nodeStack = [
524             self.coerceToDOM(
525                 MochiKit.Base.extend(null, arguments, 1),
526                 elem
527             )
528         ];
529         var parentnode = elem.parentNode;
530         var concat = MochiKit.Base.concat;
531         while (nodeStack.length) {
532             var n = nodeStack.shift();
533             if (typeof(n) == 'undefined' || n === null) {
534                 // pass
535             } else if (typeof(n.nodeType) == 'number') {
536                 parentnode.insertBefore(n, elem);
537             } else {
538                 nodeStack = concat(n, nodeStack);
539             }
540         }
541         return parentnode;
542     },
544     /** @id MochiKit.DOM.insertSiblingNodesAfter */
545     insertSiblingNodesAfter: function (node/*, nodes...*/) {
546         var elem = node;
547         var self = MochiKit.DOM;
549         if (typeof(node) == 'string') {
550             elem = self.getElement(node);
551         }
552         var nodeStack = [
553             self.coerceToDOM(
554                 MochiKit.Base.extend(null, arguments, 1),
555                 elem
556             )
557         ];
559         if (elem.nextSibling) {
560             return self.insertSiblingNodesBefore(elem.nextSibling, nodeStack);
561         }
562         else {
563             return self.appendChildNodes(elem.parentNode, nodeStack);
564         }
565     },
567     /** @id MochiKit.DOM.replaceChildNodes */
568     replaceChildNodes: function (node/*, nodes...*/) {
569         var elem = node;
570         var self = MochiKit.DOM;
571         if (typeof(node) == 'string') {
572             elem = self.getElement(node);
573             arguments[0] = elem;
574         }
575         var child;
576         while ((child = elem.firstChild)) {
577             elem.removeChild(child);
578         }
579         if (arguments.length < 2) {
580             return elem;
581         } else {
582             return self.appendChildNodes.apply(this, arguments);
583         }
584     },
586     /** @id MochiKit.DOM.createDOM */
587     createDOM: function (name, attrs/*, nodes... */) {
588         var elem;
589         var self = MochiKit.DOM;
590         var m = MochiKit.Base;
591         if (typeof(attrs) == "string" || typeof(attrs) == "number") {
592             var args = m.extend([name, null], arguments, 1);
593             return arguments.callee.apply(this, args);
594         }
595         if (typeof(name) == 'string') {
596             // Internet Explorer is dumb
597             var xhtml = self._xhtml;
598             if (attrs && !self.attributeArray.compliant) {
599                 // http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp
600                 var contents = "";
601                 if ('name' in attrs) {
602                     contents += ' name="' + self.escapeHTML(attrs.name) + '"';
603                 }
604                 if (name == 'input' && 'type' in attrs) {
605                     contents += ' type="' + self.escapeHTML(attrs.type) + '"';
606                 }
607                 if (contents) {
608                     name = "<" + name + contents + ">";
609                     xhtml = false;
610                 }
611             }
612             var d = self._document;
613             if (xhtml && d === document) {
614                 elem = d.createElementNS("http://www.w3.org/1999/xhtml", name);
615             } else {
616                 elem = d.createElement(name);
617             }
618         } else {
619             elem = name;
620         }
621         if (attrs) {
622             self.updateNodeAttributes(elem, attrs);
623         }
624         if (arguments.length <= 2) {
625             return elem;
626         } else {
627             var args = m.extend([elem], arguments, 2);
628             return self.appendChildNodes.apply(this, args);
629         }
630     },
632     /** @id MochiKit.DOM.createDOMFunc */
633     createDOMFunc: function (/* tag, attrs, *nodes */) {
634         var m = MochiKit.Base;
635         return m.partial.apply(
636             this,
637             m.extend([MochiKit.DOM.createDOM], arguments)
638         );
639     },
641     /** @id MochiKit.DOM.removeElement */
642     removeElement: function (elem) {
643         var e = MochiKit.DOM.getElement(elem);
644         e.parentNode.removeChild(e);
645         return e;
646     },
648     /** @id MochiKit.DOM.swapDOM */
649     swapDOM: function (dest, src) {
650         var self = MochiKit.DOM;
651         dest = self.getElement(dest);
652         var parent = dest.parentNode;
653         if (src) {
654             src = self.getElement(src);
655             parent.replaceChild(src, dest);
656         } else {
657             parent.removeChild(dest);
658         }
659         return src;
660     },
662     /** @id MochiKit.DOM.getElement */
663     getElement: function (id) {
664         var self = MochiKit.DOM;
665         if (arguments.length == 1) {
666             return ((typeof(id) == "string") ?
667                 self._document.getElementById(id) : id);
668         } else {
669             return MochiKit.Base.map(self.getElement, arguments);
670         }
671     },
673     /** @id MochiKit.DOM.getElementsByTagAndClassName */
674     getElementsByTagAndClassName: function (tagName, className,
675             /* optional */parent) {
676         var self = MochiKit.DOM;
677         if (typeof(tagName) == 'undefined' || tagName === null) {
678             tagName = '*';
679         }
680         if (typeof(parent) == 'undefined' || parent === null) {
681             parent = self._document;
682         }
683         parent = self.getElement(parent);
684         var children = (parent.getElementsByTagName(tagName)
685             || self._document.all);
686         if (typeof(className) == 'undefined' || className === null) {
687             return MochiKit.Base.extend(null, children);
688         }
690         var elements = [];
691         for (var i = 0; i < children.length; i++) {
692             var child = children[i];
693             var cls = child.className;
694             if (!cls) {
695                 continue;
696             }
697             var classNames = cls.split(' ');
698             for (var j = 0; j < classNames.length; j++) {
699                 if (classNames[j] == className) {
700                     elements.push(child);
701                     break;
702                 }
703             }
704         }
706         return elements;
707     },
709     _newCallStack: function (path, once) {
710         var rval = function () {
711             var callStack = arguments.callee.callStack;
712             for (var i = 0; i < callStack.length; i++) {
713                 if (callStack[i].apply(this, arguments) === false) {
714                     break;
715                 }
716             }
717             if (once) {
718                 try {
719                     this[path] = null;
720                 } catch (e) {
721                     // pass
722                 }
723             }
724         };
725         rval.callStack = [];
726         return rval;
727     },
729     /** @id MochiKit.DOM.addToCallStack */
730     addToCallStack: function (target, path, func, once) {
731         var self = MochiKit.DOM;
732         var existing = target[path];
733         var regfunc = existing;
734         if (!(typeof(existing) == 'function'
735                 && typeof(existing.callStack) == "object"
736                 && existing.callStack !== null)) {
737             regfunc = self._newCallStack(path, once);
738             if (typeof(existing) == 'function') {
739                 regfunc.callStack.push(existing);
740             }
741             target[path] = regfunc;
742         }
743         regfunc.callStack.push(func);
744     },
746     /** @id MochiKit.DOM.addLoadEvent */
747     addLoadEvent: function (func) {
748         var self = MochiKit.DOM;
749         self.addToCallStack(self._window, "onload", func, true);
751     },
753     /** @id MochiKit.DOM.focusOnLoad */
754     focusOnLoad: function (element) {
755         var self = MochiKit.DOM;
756         self.addLoadEvent(function () {
757             element = self.getElement(element);
758             if (element) {
759                 element.focus();
760             }
761         });
762     },
764     /** @id MochiKit.DOM.setElementClass */
765     setElementClass: function (element, className) {
766         var self = MochiKit.DOM;
767         var obj = self.getElement(element);
768         if (self.attributeArray.compliant) {
769             obj.setAttribute("class", className);
770         } else {
771             obj.setAttribute("className", className);
772         }
773     },
775     /** @id MochiKit.DOM.toggleElementClass */
776     toggleElementClass: function (className/*, element... */) {
777         var self = MochiKit.DOM;
778         for (var i = 1; i < arguments.length; i++) {
779             var obj = self.getElement(arguments[i]);
780             if (!self.addElementClass(obj, className)) {
781                 self.removeElementClass(obj, className);
782             }
783         }
784     },
786     /** @id MochiKit.DOM.addElementClass */
787     addElementClass: function (element, className) {
788         var self = MochiKit.DOM;
789         var obj = self.getElement(element);
790         var cls = obj.className;
791         // trivial case, no className yet
792         if (cls == undefined || cls.length === 0) {
793             self.setElementClass(obj, className);
794             return true;
795         }
796         // the other trivial case, already set as the only class
797         if (cls == className) {
798             return false;
799         }
800         var classes = cls.split(" ");
801         for (var i = 0; i < classes.length; i++) {
802             // already present
803             if (classes[i] == className) {
804                 return false;
805             }
806         }
807         // append class
808         self.setElementClass(obj, cls + " " + className);
809         return true;
810     },
812     /** @id MochiKit.DOM.removeElementClass */
813     removeElementClass: function (element, className) {
814         var self = MochiKit.DOM;
815         var obj = self.getElement(element);
816         var cls = obj.className;
817         // trivial case, no className yet
818         if (cls == undefined || cls.length === 0) {
819             return false;
820         }
821         // other trivial case, set only to className
822         if (cls == className) {
823             self.setElementClass(obj, "");
824             return true;
825         }
826         var classes = cls.split(" ");
827         for (var i = 0; i < classes.length; i++) {
828             // already present
829             if (classes[i] == className) {
830                 // only check sane case where the class is used once
831                 classes.splice(i, 1);
832                 self.setElementClass(obj, classes.join(" "));
833                 return true;
834             }
835         }
836         // not found
837         return false;
838     },
840     /** @id MochiKit.DOM.swapElementClass */
841     swapElementClass: function (element, fromClass, toClass) {
842         var obj = MochiKit.DOM.getElement(element);
843         var res = MochiKit.DOM.removeElementClass(obj, fromClass);
844         if (res) {
845             MochiKit.DOM.addElementClass(obj, toClass);
846         }
847         return res;
848     },
850     /** @id MochiKit.DOM.hasElementClass */
851     hasElementClass: function (element, className/*...*/) {
852         var obj = MochiKit.DOM.getElement(element);
853         var cls = obj.className;
854         if (!cls) {
855             return false;
856         }
857         var classes = cls.split(" ");
858         for (var i = 1; i < arguments.length; i++) {
859             var good = false;
860             for (var j = 0; j < classes.length; j++) {
861                 if (classes[j] == arguments[i]) {
862                     good = true;
863                     break;
864                 }
865             }
866             if (!good) {
867                 return false;
868             }
869         }
870         return true;
871     },
873     /** @id MochiKit.DOM.escapeHTML */
874     escapeHTML: function (s) {
875         return s.replace(/&/g, "&amp;"
876             ).replace(/"/g, "&quot;"
877             ).replace(/</g, "&lt;"
878             ).replace(/>/g, "&gt;");
879     },
881     /** @id MochiKit.DOM.toHTML */
882     toHTML: function (dom) {
883         return MochiKit.DOM.emitHTML(dom).join("");
884     },
886     /** @id MochiKit.DOM.emitHTML */
887     emitHTML: function (dom, /* optional */lst) {
888         if (typeof(lst) == 'undefined' || lst === null) {
889             lst = [];
890         }
891         // queue is the call stack, we're doing this non-recursively
892         var queue = [dom];
893         var self = MochiKit.DOM;
894         var escapeHTML = self.escapeHTML;
895         var attributeArray = self.attributeArray;
896         while (queue.length) {
897             dom = queue.pop();
898             if (typeof(dom) == 'string') {
899                 lst.push(dom);
900             } else if (dom.nodeType == 1) {
901                 // we're not using higher order stuff here
902                 // because safari has heisenbugs.. argh.
903                 //
904                 // I think it might have something to do with
905                 // garbage collection and function calls.
906                 lst.push('<' + dom.tagName.toLowerCase());
907                 var attributes = [];
908                 var domAttr = attributeArray(dom);
909                 for (var i = 0; i < domAttr.length; i++) {
910                     var a = domAttr[i];
911                     attributes.push([
912                         " ",
913                         a.name,
914                         '="',
915                         escapeHTML(a.value),
916                         '"'
917                     ]);
918                 }
919                 attributes.sort();
920                 for (i = 0; i < attributes.length; i++) {
921                     var attrs = attributes[i];
922                     for (var j = 0; j < attrs.length; j++) {
923                         lst.push(attrs[j]);
924                     }
925                 }
926                 if (dom.hasChildNodes()) {
927                     lst.push(">");
928                     // queue is the FILO call stack, so we put the close tag
929                     // on first
930                     queue.push("</" + dom.tagName.toLowerCase() + ">");
931                     var cnodes = dom.childNodes;
932                     for (i = cnodes.length - 1; i >= 0; i--) {
933                         queue.push(cnodes[i]);
934                     }
935                 } else {
936                     lst.push('/>');
937                 }
938             } else if (dom.nodeType == 3) {
939                 lst.push(escapeHTML(dom.nodeValue));
940             }
941         }
942         return lst;
943     },
945     /** @id MochiKit.DOM.scrapeText */
946     scrapeText: function (node, /* optional */asArray) {
947         var rval = [];
948         (function (node) {
949             var cn = node.childNodes;
950             if (cn) {
951                 for (var i = 0; i < cn.length; i++) {
952                     arguments.callee.call(this, cn[i]);
953                 }
954             }
955             var nodeValue = node.nodeValue;
956             if (typeof(nodeValue) == 'string') {
957                 rval.push(nodeValue);
958             }
959         })(MochiKit.DOM.getElement(node));
960         if (asArray) {
961             return rval;
962         } else {
963             return rval.join("");
964         }
965     },
967     /** @id MochiKit.DOM.removeEmptyTextNodes */
968     removeEmptyTextNodes: function (element) {
969         element = MochiKit.DOM.getElement(element);
970         for (var i = 0; i < element.childNodes.length; i++) {
971             var node = element.childNodes[i];
972             if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) {
973                 node.parentNode.removeChild(node);
974             }
975         }
976     },
978     /** @id MochiKit.DOM.makeClipping */
979     makeClipping: function (element) {
980         element = MochiKit.DOM.getElement(element);
981         var oldOverflow = element.style.overflow;
982         if ((MochiKit.Style.getStyle(element, 'overflow') || 'visible') != 'hidden') {
983             element.style.overflow = 'hidden';
984         }
985         return oldOverflow;
986     },
988     /** @id MochiKit.DOM.undoClipping */
989     undoClipping: function (element, overflow) {
990         element = MochiKit.DOM.getElement(element);
991         if (!overflow) {
992             return;
993         }
994         element.style.overflow = overflow;
995     },
997     /** @id MochiKit.DOM.makePositioned */
998     makePositioned: function (element) {
999         element = MochiKit.DOM.getElement(element);
1000         var pos = MochiKit.Style.getStyle(element, 'position');
1001         if (pos == 'static' || !pos) {
1002             element.style.position = 'relative';
1003             // Opera returns the offset relative to the positioning context,
1004             // when an element is position relative but top and left have
1005             // not been defined
1006             if (/Opera/.test(navigator.userAgent)) {
1007                 element.style.top = 0;
1008                 element.style.left = 0;
1009             }
1010         }
1011     },
1013     /** @id MochiKit.DOM.undoPositioned */
1014     undoPositioned: function (element) {
1015         element = MochiKit.DOM.getElement(element);
1016         if (element.style.position == 'relative') {
1017             element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = '';
1018         }
1019     },
1021     /** @id MochiKit.DOM.getFirstElementByTagAndClassName */
1022     getFirstElementByTagAndClassName: function (tagName, className,
1023             /* optional */parent) {
1024         var self = MochiKit.DOM;
1025         if (typeof(tagName) == 'undefined' || tagName === null) {
1026             tagName = '*';
1027         }
1028         if (typeof(parent) == 'undefined' || parent === null) {
1029             parent = self._document;
1030         }
1031         parent = self.getElement(parent);
1032         var children = (parent.getElementsByTagName(tagName)
1033             || self._document.all);
1034         if (typeof(className) == 'undefined' || className === null) {
1035             return children[0];
1036         }
1038         for (var i = 0; i < children.length; i++) {
1039             var child = children[i];
1040             var classNames = child.className.split(' ');
1041             for (var j = 0; j < classNames.length; j++) {
1042                 if (classNames[j] == className) {
1043                     return child;
1044                 }
1045             }
1046         }
1047     },
1049     /** @id MochiKit.DOM.getFirstParentByTagAndClassName */
1050     getFirstParentByTagAndClassName: function (elem, tagName, className) {
1051         var self = MochiKit.DOM;
1052         elem = self.getElement(elem);
1053         if (typeof(tagName) == 'undefined' || tagName === null) {
1054             tagName = '*';
1055         } else {
1056             tagName = tagName.toUpperCase();
1057         }
1058         if (typeof(className) == 'undefined' || className === null) {
1059             className = null;
1060         }
1062         var classList = '';
1063         var curTagName = '';
1064         while (elem && elem.tagName) {
1065             elem = elem.parentNode;
1066             if (tagName == '*' && className === null) {
1067                 return elem;
1068             }
1069             classList = elem.className.split(' ');
1070             curTagName = elem.tagName.toUpperCase();
1071             if (className === null && tagName == curTagName) {
1072                 return elem;
1073             } else if (className !== null) {
1074                 for (var i = 0; i < classList.length; i++) {
1075                     if (tagName == '*' && classList[i] == className) {
1076                         return elem;
1077                     } else if (tagName == curTagName && classList[i] == className) {
1078                         return elem;
1079                     }
1080                 }
1081             }
1082         }
1083         return elem;
1084     },
1086     /** @id MochiKit.DOM.isParent */
1087     isParent: function (child, element) {
1088         if (!child.parentNode || child == element) {
1089             return false;
1090         }
1092         if (child.parentNode == element) {
1093             return true;
1094         }
1096         return MochiKit.DOM.isParent(child.parentNode, element);
1097     },
1099     __new__: function (win) {
1101         var m = MochiKit.Base;
1102         if (typeof(document) != "undefined") {
1103             this._document = document;
1104             var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
1105             this._xhtml = (document.documentElement &&
1106                 document.createElementNS &&
1107                 document.documentElement.namespaceURI === kXULNSURI);
1108         } else if (MochiKit.MockDOM) {
1109             this._document = MochiKit.MockDOM.document;
1110         }
1111         this._window = win;
1113         this.domConverters = new m.AdapterRegistry();
1115         var __tmpElement = this._document.createElement("span");
1116         var attributeArray;
1117         if (__tmpElement && __tmpElement.attributes &&
1118                 __tmpElement.attributes.length > 0) {
1119             // for braindead browsers (IE) that insert extra junk
1120             var filter = m.filter;
1121             attributeArray = function (node) {
1122                 return filter(attributeArray.ignoreAttrFilter, node.attributes);
1123             };
1124             attributeArray.ignoreAttr = {};
1125             var attrs = __tmpElement.attributes;
1126             var ignoreAttr = attributeArray.ignoreAttr;
1127             for (var i = 0; i < attrs.length; i++) {
1128                 var a = attrs[i];
1129                 ignoreAttr[a.name] = a.value;
1130             }
1131             attributeArray.ignoreAttrFilter = function (a) {
1132                 return (attributeArray.ignoreAttr[a.name] != a.value);
1133             };
1134             attributeArray.compliant = false;
1135             attributeArray.renames = {
1136                 "class": "className",
1137                 "checked": "defaultChecked",
1138                 "usemap": "useMap",
1139                 "for": "htmlFor",
1140                 "readonly": "readOnly",
1141                 "colspan": "colSpan",
1142                 "bgcolor": "bgColor"
1143             };
1144         } else {
1145             attributeArray = function (node) {
1146                 /***
1148                     Return an array of attributes for a given node,
1149                     filtering out attributes that don't belong for
1150                     that are inserted by "Certain Browsers".
1152                 ***/
1153                 return node.attributes;
1154             };
1155             attributeArray.compliant = true;
1156             attributeArray.renames = {};
1157         }
1158         this.attributeArray = attributeArray;
1160         // FIXME: this really belongs in Base, and could probably be cleaner
1161         var _deprecated = function(fromModule, arr) {
1162             var modules = arr[1].split('.');
1163             var str = '';
1164             var obj = {};
1166             str += 'if (!MochiKit.' + modules[1] + ') { throw new Error("';
1167             str += 'This function has been deprecated and depends on MochiKit.';
1168             str += modules[1] + '.");}';
1169             str += 'return MochiKit.' + modules[1] + '.' + arr[0];
1170             str += '.apply(this, arguments);';
1172             obj[modules[2]] = new Function(str);
1173             MochiKit.Base.update(MochiKit[fromModule], obj);
1174         }
1175         for (var i; i < MochiKit.DOM.DEPRECATED.length; i++) {
1176             _deprecated('DOM', MochiKit.DOM.DEPRECATED[i]);
1177         }
1179         // shorthand for createDOM syntax
1180         var createDOMFunc = this.createDOMFunc;
1181         /** @id MochiKit.DOM.UL */
1182         this.UL = createDOMFunc("ul");
1183         /** @id MochiKit.DOM.OL */
1184         this.OL = createDOMFunc("ol");
1185         /** @id MochiKit.DOM.LI */
1186         this.LI = createDOMFunc("li");
1187         /** @id MochiKit.DOM.TD */
1188         this.TD = createDOMFunc("td");
1189         /** @id MochiKit.DOM.TR */
1190         this.TR = createDOMFunc("tr");
1191         /** @id MochiKit.DOM.TBODY */
1192         this.TBODY = createDOMFunc("tbody");
1193         /** @id MochiKit.DOM.THEAD */
1194         this.THEAD = createDOMFunc("thead");
1195         /** @id MochiKit.DOM.TFOOT */
1196         this.TFOOT = createDOMFunc("tfoot");
1197         /** @id MochiKit.DOM.TABLE */
1198         this.TABLE = createDOMFunc("table");
1199         /** @id MochiKit.DOM.TH */
1200         this.TH = createDOMFunc("th");
1201         /** @id MochiKit.DOM.INPUT */
1202         this.INPUT = createDOMFunc("input");
1203         /** @id MochiKit.DOM.SPAN */
1204         this.SPAN = createDOMFunc("span");
1205         /** @id MochiKit.DOM.A */
1206         this.A = createDOMFunc("a");
1207         /** @id MochiKit.DOM.DIV */
1208         this.DIV = createDOMFunc("div");
1209         /** @id MochiKit.DOM.IMG */
1210         this.IMG = createDOMFunc("img");
1211         /** @id MochiKit.DOM.BUTTON */
1212         this.BUTTON = createDOMFunc("button");
1213         /** @id MochiKit.DOM.TT */
1214         this.TT = createDOMFunc("tt");
1215         /** @id MochiKit.DOM.PRE */
1216         this.PRE = createDOMFunc("pre");
1217         /** @id MochiKit.DOM.H1 */
1218         this.H1 = createDOMFunc("h1");
1219         /** @id MochiKit.DOM.H2 */
1220         this.H2 = createDOMFunc("h2");
1221         /** @id MochiKit.DOM.H3 */
1222         this.H3 = createDOMFunc("h3");
1223         /** @id MochiKit.DOM.BR */
1224         this.BR = createDOMFunc("br");
1225         /** @id MochiKit.DOM.HR */
1226         this.HR = createDOMFunc("hr");
1227         /** @id MochiKit.DOM.LABEL */
1228         this.LABEL = createDOMFunc("label");
1229         /** @id MochiKit.DOM.TEXTAREA */
1230         this.TEXTAREA = createDOMFunc("textarea");
1231         /** @id MochiKit.DOM.FORM */
1232         this.FORM = createDOMFunc("form");
1233         /** @id MochiKit.DOM.P */
1234         this.P = createDOMFunc("p");
1235         /** @id MochiKit.DOM.SELECT */
1236         this.SELECT = createDOMFunc("select");
1237         /** @id MochiKit.DOM.OPTION */
1238         this.OPTION = createDOMFunc("option");
1239         /** @id MochiKit.DOM.OPTGROUP */
1240         this.OPTGROUP = createDOMFunc("optgroup");
1241         /** @id MochiKit.DOM.LEGEND */
1242         this.LEGEND = createDOMFunc("legend");
1243         /** @id MochiKit.DOM.FIELDSET */
1244         this.FIELDSET = createDOMFunc("fieldset");
1245         /** @id MochiKit.DOM.STRONG */
1246         this.STRONG = createDOMFunc("strong");
1247         /** @id MochiKit.DOM.CANVAS */
1248         this.CANVAS = createDOMFunc("canvas");
1250         /** @id MochiKit.DOM.$ */
1251         this.$ = this.getElement;
1253         this.EXPORT_TAGS = {
1254             ":common": this.EXPORT,
1255             ":all": m.concat(this.EXPORT, this.EXPORT_OK)
1256         };
1258         m.nameFunctions(this);
1260     }
1264 MochiKit.DOM.__new__(((typeof(window) == "undefined") ? this : window));
1267 // XXX: Internet Explorer blows
1269 if (MochiKit.__export__) {
1270     withWindow = MochiKit.DOM.withWindow;
1271     withDocument = MochiKit.DOM.withDocument;
1274 MochiKit.Base._exportSymbols(this, MochiKit.DOM);