1 /*
  2  * Dual-licensed under the MIT License & the Academic Free License v. 2.1.
  3  * See the file LICENSE for more information.
  4  *
  5  * (c) 2007-2008 by Per Cederberg & Dynabyte AB. All rights reserved.
  6  */
  7 
  8 // Check for loaded MochiKit
  9 if (typeof(MochiKit) == "undefined") {
 10     throw new ReferenceError("MochiKit must be loaded before loading this script");
 11 }
 12 
 13 /**
 14  * @name MochiKit.DOM
 15  * @namespace Provides a painless DOM manipulation API.
 16  */
 17 // Check for loaded MochiKit.DOM
 18 if (typeof(MochiKit.DOM) == "undefined") {
 19     throw new ReferenceError("MochiKit.DOM must be loaded before loading this script");
 20 }
 21 
 22 MochiKit.DOM.NS = {
 23     XHTML: "http://www.w3.org/1999/xhtml",
 24     XLINK: "http://www.w3.org/1999/xlink",
 25     SVG: "http://www.w3.org/2000/svg",
 26     XUL: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
 27 };
 28 MochiKit.DOM.NS.HTML = [undefined, null, '', MochiKit.DOM.NS.XHTML];
 29 
 30 /**
 31  * Returns true if the specified object looks like a DOM node.
 32  * Otherwise, false will be returned. Any non-null object with a
 33  * nodeType > 0 will be considered a DOM node by this function.
 34  *
 35  * @param {Object} obj the object to check
 36  *
 37  * @return {Boolean} true if the object looks like a DOM node, or
 38  *         false otherwise
 39  */
 40 MochiKit.DOM.isDOM = function(obj) {
 41     return typeof(obj) !== "undefined" &&
 42            typeof(obj.nodeType) === "number" &&
 43            obj.nodeType > 0;
 44 }
 45 
 46 /**
 47  * Returns true if the specified object looks like an HTML or XHTML
 48  * DOM node. Otherwise, false will be returned. Any non-null object
 49  * with a nodeType > 0 will be considered a DOM node, but only those
 50  * with a matching namespaceURI will be considered HTML DOM nodes. 
 51  *
 52  * @param {Object} obj the object to check
 53  *
 54  * @return {Boolean} true if the object looks like an HTML DOM node,
 55  *         or false otherwise
 56  */
 57 MochiKit.DOM.isHTML = function(obj) {
 58     var ns = MochiKit.DOM.NS.HTML;
 59     return MochiKit.DOM.isDOM(obj) &&
 60            MochiKit.Base.findIdentical(ns, obj.namespaceURI) >= 0;
 61 }
 62 
 63 /**
 64  * Creates a programmers debug representation of a DOM node. This
 65  * method is similar to MochiKit.DOM.emitHtml, except for that it
 66  * does not recurse into child nodes.
 67  *
 68  * @param {Object} node the HTML DOM node
 69  *
 70  * @return {String} a debug representation of the DOM node
 71  */
 72 MochiKit.DOM.reprDOM = function(node) {
 73     if (node == null) {
 74         return "null";
 75     } else if (typeof(node) === 'string') {
 76         return node;
 77     } else if (node.nodeType === 1) { // Node.ELEMENT_NODE
 78         var res = "<" + node.tagName.toLowerCase();
 79         var attrs = MochiKit.Base.map(MochiKit.DOM.reprDOM, node.attributes);
 80         res += attrs.join("");
 81         if (node.hasChildNodes()) {
 82             res += " ["  + node.childNodes.length + " child nodes]";
 83         }
 84         res += "/>";
 85         return res;
 86     } else if (node.nodeType === 2) { // Node.ATTRIBUTE_NODE
 87         if (node.specified) {
 88             return " " + node.name + '="' +
 89                    MochiKit.DOM.escapeHTML(node.value) + '"';
 90         } else {
 91             return "";
 92         }
 93     } else if (node.nodeType === 3) { // Node.TEXT_NODE
 94         return MochiKit.DOM.escapeHTML(node.nodeValue);
 95     } else {
 96         return node.toString();
 97     }
 98 }
 99 
100 /**
101  * Returns an array with DOM node attribute name and value pairs.
102  * The name and value pairs are also stored in arrays with two
103  * elements.
104  *
105  * @param {Object} node the HTML DOM node
106  *
107  * @return {Array} an array containing attribute name and value
108  *             pairs (as arrays)
109  */
110 MochiKit.DOM.attributeArrayNewImpl = function(node) {
111     var res = [];
112     node = MochiKit.DOM.getElement(node);
113     for (var i = 0; node != null && i < node.attributes.length; i++) {
114         var a = node.attributes[i];
115         if (a.specified) {
116             res.push([a.name, a.value]);
117         }
118     }
119     return res;
120 }
121 
122 /**
123  * Returns an immediate child node from a parent DOM node. This
124  * function handles the index range checks and finds the immediate
125  * child node if a descendant node is specified.
126  *
127  * @param {Node} parent the parent HTML DOM node
128  * @param {Number/Node} indexOrNode the child index or a descendant
129  *            node
130  *
131  * @return {Node} the child HTML DOM node, or
132  *         null if no matching node was found
133  */
134 MochiKit.DOM.childNode = function(parent, indexOrNode) {
135     parent = MochiKit.DOM.getElement(parent);
136     if (typeof(indexOrNode) == "number") {
137         if (indexOrNode < 0 || indexOrNode >= parent.childNodes.length) {
138             return null;
139         } else {
140             return parent.childNodes[indexOrNode];
141         }
142     } else {
143         var node = MochiKit.DOM.getElement(indexOrNode);
144         while (node != null && node !== parent && node.parentNode !== parent) {
145             node = node.parentNode;
146         }
147         return (node == null || node === parent) ? null : node;
148     }
149 }
150 
151 /**
152  * Creates a DOM node with a namespace.
153  *
154  * @param {String} ns the DOM namespace
155  * @param {String} tag the DOM tag name
156  * @param {Object} [attrs] the node attributes, or null for none
157  * @param {Object} [...] the nodes or text to add as children
158  *
159  * @return {Object} the DOM node created
160  */
161 MochiKit.DOM.createDOMExt = function(ns, tag, attrs/*, ...*/) {
162     var doc = MochiKit.DOM.currentDocument();
163     var node = (ns) ? doc.createElementNS(ns, tag) : doc.createElement(tag);
164     MochiKit.DOM.updateNodeAttributes(node, attrs);
165     var children = MochiKit.Base.extend([], arguments, 3);
166     MochiKit.DOM.appendChildNodes(node, children);
167     return node;
168 }
169 
170 /**
171  * Creates a DOM text node from the specified text. This is a
172  * convenience function for currentDocument().createTextNode, in
173  * order to be compatible with the withDocument() function.
174  *
175  * @param {String} text the text content
176  *
177  * @return {Object} the DOM text node created
178  */
179 MochiKit.DOM.createTextNode = function(text) {
180     return MochiKit.DOM.currentDocument().createTextNode(text);
181 }
182 
183 /**
184  * Returns a function for creating a specific kind of DOM nodes. The
185  * returned function will optionally require a sequence of non-null
186  * arguments that will be added as attributes to the node creation.
187  * The returned function will otherwise work similar to the
188  * createDOMExt() function, taking attributes and child nodes.
189  *
190  * @param {String} ns the DOM namespace, or null for HTML
191  * @param {String} tag the DOM tag name
192  * @param {Array} [args] the array with required arguments, or null
193  *            for no required arguments
194  * @param {Object} [attrs] the default node attributes, or null for
195  *            none
196  * @param {Object} [...] the default nodes or text to add as children
197  *
198  * @return {Function} the function that creates the DOM nodes
199  */
200 MochiKit.DOM.createDOMFuncExt = function(ns, tag, args, attrs/*, ...*/) {
201     args = args || [];
202     attrs = attrs || {};
203     var children = MochiKit.Base.extend([], arguments, 4);
204     return function(/*arg1, ..., argN, attrs, ...*/) {
205         var myAttrs = MochiKit.Base.update({}, attrs);
206         for (var pos = 0; pos < args.length; pos++) {
207             if (arguments[pos] == null) {
208                 throw new Error("Argument '" + args[pos] + "' cannot be null");  
209             }
210             myAttrs[args[pos]] = arguments[pos];
211         }
212         MochiKit.Base.update(myAttrs, arguments[args.length]);
213         var myChildren = MochiKit.Base.extend([], children);
214         MochiKit.Base.extend(myChildren, arguments, args.length + 1);
215         return MochiKit.DOM.createDOMExt(ns, tag, myAttrs, myChildren);
216     }
217 }
218 
219 /**
220  * Blurs (unfocuses) a specified DOM node and all relevant child
221  * nodes. This function will recursively blur all A, BUTTON, INPUT,
222  * TEXTAREA and SELECT child nodes found.
223  *
224  * @param {Object} node the HTML DOM node
225  */
226 MochiKit.DOM.blurAll = function(node) {
227     if (arguments.length <= 1) {
228         MochiKit.DOM.blurAll(node, "A", "BUTTON", "INPUT", "TEXTAREA", "SELECT");
229     } else {
230         node.blur();
231         for (var i = 1; i < arguments.length; i++) {
232             var nodes = node.getElementsByTagName(arguments[i]);
233             for (var j = 0; j < nodes.length; j++) {
234                 nodes[j].blur();
235             }
236         }
237     }
238 }
239