1 /**
  2  * Utility class with static methods for manipulating the DOM
  3  *
  4  * @class DOMUtil
  5  */
  6 DOMUtil = {
  7 	/** Boots old browsers to the upgrade page  */
  8 	checkDOM: function() {
  9 		if(!Browser.DOM || !Browser.Ajax) {
 10 			window.location = "/requirements/error/browser/";
 11 		}
 12 	},
 13 	
 14 	/**
 15 	 * Removes all children of the passed node
 16 	 * 
 17 	 * @param {Node} The node whose children to remove
 18 	 * @return {Node}
 19 	 */
 20 	emptyNode: function(node) {
 21 		if(node) {
 22 			if(node.appendTo) {
 23 				node.empty();
 24 			} else {
 25 				while(node.hasChildNodes()) {
 26 					var childNode = node.childNodes[node.childNodes.length-1];
 27 					
 28 					if(childNode.owner) {
 29 						var foo = childNode.owner();
 30 						
 31 						if(foo.notifyListeners) {
 32 							foo.notifyListeners("onBeforeRemoveFromDOM");
 33 						}
 34 					}
 35 					
 36 					node.removeChild(node.childNodes[node.childNodes.length-1]);
 37 					
 38 					if(foo && foo.notifyListeners) {
 39 						foo.notifyListeners("onAfterRemoveFromDOM");
 40 					}
 41 				}
 42 			}
 43 			
 44 			return node;
 45 		}
 46 	},
 47 	
 48 	/**
 49 	 * Removes all children of the passed nodes
 50 	 *
 51 	 * @example
 52 	 * <pre><code class="language-javascript">
 53 	 * DOMUtil.emptyNodes(node1, node2…);
 54 	 * </code></pre>
 55 	 */
 56 	emptyNodes: function() {
 57 		$A(arguments).each(function(element, index) {
 58 			if(element instanceof Array) {
 59 				this.emptyNodes(element);
 60 			} else {
 61 				this.emptyNode(element);
 62 			}
 63 		}.bind(this));
 64 	},
 65 	
 66 	/**
 67 	 * Creates a node with a textNode as a child
 68 	 * 
 69 	 * @param	{string}		tagName
 70 	 * @param	{string}		text
 71 	 * @param	{Object}		attributes
 72 	 * @return	{Node}
 73 	 * @deprecated Use DOMUtil#createElement instead
 74 	 */
 75 	createTextElement: function(tagName, text, attributes) {
 76 		var element = this.createElement(tagName, attributes);
 77 		
 78 		this.append(text ? text : "", element);
 79 		
 80 		return element;
 81 	},
 82 	
 83 	/**
 84 	 * Shortcut for creating a DOM node
 85 	 *
 86 	 *
 87 	 * @example
 88 	 * <pre><code class="language-javascript">
 89 	 * // <p>Hello world!</p>
 90 	 * DOMUtil.createElement("p", "Hello world!");
 91 	 * 
 92 	 * // <p class="foo" style="color: red" >Hello world!</p>
 93 	 * DOMUtil.createElement("p", "Hello world!", {className: "foo", style: {color: "red"}});
 94 	 *
 95 	 * // <p>Contents as array</p>
 96 	 * DOMUtil.createElement("p", ["Contents", " ", "as", " ", "array"]);
 97 	 * </code></pre> 
 98 	 * @param	{string}		Tag name
 99 	 * @param	{Object}		The contents of the output node - a string, DOM Node or bbq.gui.GUIWidget
100 	 * @param	{Object}		Attributes that will be applied to the output of this function
101 	 * @return	{Node}
102 	 */
103 	createElement: function(arg1, arg2, arg3) {
104 		var tagName = arg1;
105 		var attributes;
106 		var content;
107 
108 		if(arg2 && (Object.isString(arg2) || arg2.appendChild || Object.isArray(arg2))) {
109 			content = arg2;
110 			attributes = arg3;
111 
112 			return DOMUtil.createTextElement(tagName, content, attributes);
113 		} else {
114 			attributes = arg2;
115 		}
116 
117 		var element = $(document.createElement(tagName));
118 		this.applyAttributesToElement(element, attributes);
119 		
120 		return element;
121 	},
122 	
123 	/**
124 	 * Creates a table row
125 	 * 
126 	 * @param {Number} numCells
127 	 * @param {boolean} header
128 	 * @return {Node}
129 	 */
130 	createTableRow: function(numCells, header) {
131 		var row = document.createElement("tr");
132 		
133 		for(var i = 0; i < numCells; i++) {
134 			row.appendChild(document.createElement((header ? "th" : "td")));
135 		}
136 		
137 		return row;
138 	},
139 	
140 	/**
141 	 * Creates a row of empty table header cells
142 	 * 
143 	 * @param {Number} numCells
144 	 * @return {Node}
145 	 */
146 	createTableHeaderRow: function(numCells) {
147 		return this.createTableRow(numCells, true);
148 	},
149 	
150 	/**
151 	 * Creates a single line HTML text input
152 	 * 
153 	 * @param {String} 	nodeValue
154 	 * @param {Object} 	options
155 	 * @return {Node}
156 	 */
157 	createTextInputNode: function(nodeValue, options) {
158 		var element = this.createElement("input", options);
159 		element.type = "text";
160 		element.value = (nodeValue == null ? "" : nodeValue);
161 				
162 		return element;
163 	},
164 	
165 	/**
166 	 * Creates a HTML password input
167 	 * 
168 	 * @param {String} nodeValue
169 	 * @param {Object} options
170 	 * @return {Node}
171 	 */
172 	createPasswordInputNode: function(nodeValue, options) {
173 		var element = this.createTextInputNode(nodeValue, options);
174 		element.type = "password";
175 		
176 		return element;
177 	},
178 	
179 	/**
180 	 * Creates a HTML submit button
181 	 * 
182 	 * @param {String} nodeValue
183 	 * @param {Object} options
184 	 * @return {Node}
185 	 */
186 	createSubmitInputNode: function(nodeValue, options) {
187 		var element = this.createTextInputNode(nodeValue, options);
188 		element.type = "submit";
189 		element.value = (nodeValue == null ? "" : nodeValue );
190 		
191 		return element;
192 	},
193 	
194 	/**
195 	 * Creates a HTML password input
196 	 * 
197 	 * @param {Node} element Node to apply attributes to
198 	 * @param {Object} attributes Associative array of attributes to apply to the passed node
199 	 * @return {void}
200 	 */
201 	applyAttributesToElement: function(element, attributes) {
202 		if(attributes) {
203 			for(var key in attributes) {
204 				if(key == "style") {
205 					for(s_key in attributes[key]) {
206 						element.style[s_key] = attributes[key][s_key];
207 					}
208 				} else {
209 					element[key] = attributes[key];
210 				}
211 			}
212 		}
213 	},
214 
215 	createNullForm: function(attributes) {
216 		var element = document.createElement("form");
217 		
218 		this.applyAttributesToElement(element, attributes);
219 		
220 		element.action = ".";
221 		element.method = "get";
222 		element.onsubmit = function() {return false};
223 		
224 		return element;
225 	},
226 	
227 	/**
228 	 * Adds a CSS class to the passed node
229 	 * 
230 	 * @param {Node} node
231 	 * @param {string} newClass
232 	 * @return {void}
233 	 */
234 	addClass: function(node, newClass) {
235 		if(node && newClass) {
236 			if(newClass instanceof Array) {
237 				for(var i = 0, iCount=newClass.length; i < iCount; i++) {
238 					this.addClass(node, newClass[i]);
239 				}
240 			} else if(node.addClass) {
241 				node.addClass(newClass);
242 			} else if(!DOMUtil.hasClass(node, newClass)) {
243 				Element.addClassName(node, newClass);
244 			}
245 		}
246 	},
247 	
248 	/**
249 	 * Removes a CSS class to the passed node, optionally recursing through it's child nodes
250 	 * 
251 	 * @param {Node} node
252 	 * @param {String} oldClass
253 	 * @param {boolean} recursive
254 	 * @return {void}
255 	 */
256 	removeClass: function(node, oldClass, recursive) {
257 		if(!node) {
258 			return;
259 		}
260 		
261 		if(oldClass instanceof Array) {
262 			for(var i = 0; i < oldClass.length; i++) {
263 				this.removeClass(node, oldClass[i], recursive);
264 			}
265 		} else if(recursive) {
266 			this.recursivelyRemoveClasses(node, oldClass);
267 		} else {
268 			Element.removeClassName(node, oldClass);
269 		}
270 	},
271 	
272 	removeClasses: function(nodes, oldClass) {
273 		for(var i = 0; i < nodes.length; i++) {
274 			this.removeClass(nodes[i], oldClass);
275 		}
276 	},
277 	
278 	recursivelyRemoveClasses: function(node, oldClass) {
279 		if(node && node.hasChildNodes()) {
280 			this.removeClass(node, oldClass);
281 			
282 			for(var i = 0; i < node.childNodes.length; i++) {
283 				if(node.childNodes[i].hasChildNodes()) {
284 					this.recursivelyRemoveClasses(node.childNodes[i], oldClass);
285 				}
286 			}
287 		}
288 	},
289 	
290 	/**
291 	 * Returns the iFrameDocument object from the passed iFrame in a browser neutral way
292 	 * 
293 	 * @param {Node} iframe
294 	 * @return {Node}
295 	 */
296 	getIFrameDocument: function(iframe) {
297 		var oDoc = false;
298 		
299 		if(iframe.contentWindow) {
300 			Log.info("found iframe.contentWindow");
301 			oDoc = iframe.contentWindow;
302 		} else if(iframe.contentDocument) {
303 			Log.info("found iframe.contentDocument");
304 			oDoc = iframe.contentDocument;
305 		}
306 		
307 		if(oDoc.document) {
308 			Log.info("found oDoc.document");
309 			oDoc = oDoc.document;
310 		}
311 		
312 		return oDoc;
313 	},
314 	
315 	/**
316 	 * Sets a CSS style on the passed node to the passed value
317 	 * @param {Node} element
318 	 * @param {Object} styleToSet
319 	 * @param {Object} value
320 	 */
321 	setStyle: function(element, styleToSet, value) {
322 		try {
323 			if(element) {
324 				if(element.appendChild) {
325 						element.style[styleToSet] = value;
326 				} else if(element.appendTo) {
327 					element.setStyle(styleToSet, value);
328 				}
329 			}
330 		} catch(e) {
331 			Log.error("Error setting style " + styleToSet + " to value " + value + " on element " + element, e);
332 		}
333 	},
334 	
335 	/**
336 	 * @param {Object} element
337 	 * @param {Object} styleToGet
338 	 */
339 	getStyle: function(element, styleToGet) {
340 		try {
341 			if(element.owner) {
342 				// get root node of GUIWidget
343 				element = element.owner().getRootNode();
344 			}
345 
346 			if(element.currentStyle) {
347 				return element.currentStyle[styleToGet];
348 			} else if (window.getComputedStyle) {
349 				return document.defaultView.getComputedStyle(element, null).getPropertyValue(styleToGet);
350 			}
351 
352 			if(element && element.style) {
353 				return element.style[styleToGet];
354 			}
355 		} catch(e) {
356 			Log.error("Error getting style " + styleToGet + " from element " + element, e);
357 		}
358 	},
359 	
360 	/**
361 	 * @param {Node}
362 	 * @param {string} className
363 	 * @return {boolean}
364 	 */
365 	hasClass: function(node, className) {
366 		var hasClass = false;
367 		
368 		if(node && node.className) {
369 			// broken
370 			//Element.hasClassName(node, className);
371 			
372 			var elementClassName = node.className;
373 			elementClassName = elementClassName.replace(/\s+/g, " ");
374 			
375 			elementClassName.split(" ").each(function(nodeClassName) {
376 				if(className == nodeClassName) {
377 					hasClass = true;
378 				}
379 			});
380 		}
381 		
382 		return hasClass;
383 	},
384 	
385 	/**
386 	 * @param {Object} headerArray
387 	 * @param {Object} attributes
388 	 * @return {Node}
389 	 */
390 	createTable: function(headerArray, attributes) {
391 		if(typeof(attributes) == "undefined") {
392 			attributes = {};
393 		}
394 		
395 		var table = DOMUtil.createElement("table", attributes);
396 		
397 		if(headerArray instanceof Array && headerArray.length > 0) {
398 			var thead  = DOMUtil.createElement("thead");
399 			thead.appendChild(DOMUtil._processRow(headerArray, "th"));
400 			table.appendChild(thead);
401 		}
402 		
403 		return table;
404 	},
405 	
406 	/**
407 	 * @param {Node} table
408 	 * @param {Object} rowData
409 	 */
410 	addRowToTable: function(table, rowData) {
411 		var tbody = table.getElementsByTagName("tbody")[0];
412 		
413 		if(!tbody) {
414 			tbody = document.createElement("tbody");
415 			table.appendChild(tbody);
416 		}
417 		
418 		tbody.appendChild(DOMUtil._processRow(rowData, "td"));
419 	},
420 	
421 	_processRow: function(rowData, cellType) {
422 		var row = document.createElement("tr");
423 		
424 		for(var i = 0; i < rowData.length; i++) {
425 			var headerCell = document.createElement(cellType);
426 			
427 			if(rowData[i]) {
428 				if(rowData[i].appendTo) {
429 					rowData[i].appendTo(headerCell);
430 				} else if(rowData[i].appendChild) {
431 					headerCell.appendChild(rowData[i]);
432 				} else {
433 					headerCell.appendChild(document.createTextNode(rowData[i]));
434 				}
435 			} else {
436 				headerCell.appendChild(document.createTextNode(" "));
437 			}
438 			
439 			row.appendChild(headerCell);
440 		}
441 		
442 		return row;
443 	},
444 	
445 	/**
446 	 * @param {Array} values
447 	 * @param {Array} attributes
448 	 * @return {Node}
449 	 */
450 	createDefinitionList: function(values, attributes) {
451 		var definitions = [];
452 		
453 		for(var key in values) {
454 			definitions.push(DOMUtil.createTextElement("dt", key, attributes[key]));
455 			definitions.push(DOMUtil.createTextElement("dd", values[key], attributes[key]))
456 		}
457 		
458 		return DOMUtil.createTextElement("dl", definitions);
459 	},
460 	
461 	_findSibling: function(node, type) {
462 		var sibling = null;
463 		
464 		while(true) {
465 			sibling = node[type + "Sibling"];
466 			
467 			if(!sibling) {
468 				return sibling;
469 			}
470 			
471 			if(sibling.nodeType != 3) {
472 				return $(sibling);
473 			}
474 			
475 			node = sibling;
476 		}
477 	},
478 	
479 	getOccupiedDimensions: function(node) {
480 		if(Object.isFunction(node.getRootNode)) {
481 			node = node.getRootNode();
482 		}
483 		
484 		node = $(node);
485 		
486 		var dims = {
487 			height: 0,
488 			width: 0
489 		};
490 		
491 		if(!node.getHeight || !node.getWidth) {
492 			return dims;
493 		}
494 		
495 		dims.height = node.getHeight();
496 		dims.width = node.getWidth();
497 		
498 		var nextSibling = DOMUtil._findSibling(node, "next");
499 		
500 		// find node siblings - if margins have been set we need to adjust the dimensions
501 		if(nextSibling) {
502 			
503 			// occupied size is affected by margins
504 			if(node.getStyle("marginBottom")) {
505 				var nodeMarginBotton = parseInt(node.getStyle("marginBottom"));
506 				var nextSiblingMarginTop = parseInt(nextSibling.getStyle("marginTop"));
507 				
508 				// only add if the next node's marginTop does not trump our marginBottom
509 				if(nodeMarginBotton > nextSiblingMarginTop) {
510 					dims.height += nodeMarginBotton;
511 				}
512 			}
513 			
514 			if(node.getStyle("marginRight")) {
515 				var nodeMarginRight = parseInt(node.getStyle("marginRight"));
516 				var nextSiblingMarginLeft = parseInt(nextSibling.getStyle("marginLeft"));
517 				
518 				// only add if the next node's marginTop does not trump our marginBottom
519 				if(nodeMarginRight > nextSiblingMarginLeft) {
520 					dims.width += nodeMarginRight;
521 				}
522 			}
523 		}
524 		
525 		var previousSibling = DOMUtil._findSibling(node, "previous");
526 		
527 		if(previousSibling) {
528 			
529 			// occupied size is affected by margins
530 			if(node.getStyle("marginTop")) {
531 				var nodeMarginTop = parseInt(node.getStyle("marginTop"));
532 				var previousSiblingMarginBottom = parseInt(previousSibling.getStyle("marginBottom"));
533 				
534 				// only add if the next node's marginTop does not trump our marginBottom
535 				if(nodeMarginTop > previousSiblingMarginBottom) {
536 					dims.height += nodeMarginTop;
537 				}
538 			}
539 			
540 			if(node.getStyle("marginLeft")) {
541 				var nodeMarginLeft = parseInt(node.getStyle("marginLeft"));
542 				var previousSiblingMarginBottom = parseInt(previousSibling.getStyle("marginRight"));
543 				
544 				// only add if the next node's marginTop does not trump our marginBottom
545 				if(nodeMarginLeft > previousSiblingMarginBottom) {
546 					dims.width += nodeMarginLeft;
547 				}
548 			}
549 		}
550 		
551 		return dims;
552 	},
553 	
554 	/*
555 	 * Returns the dimensions occupied by an element on screen, including padding
556 	
557 	getOccupiedDimensions: function(node) {
558 		var dims = {height: 0, width: 0};
559 		
560 		if(!(node.getStyle instanceof Function)) {
561 			return dims;
562 		}
563 		
564 		dims.height = parseInt(node.getStyle("height"));
565 		dims.height = DOMUtil._addStyleCalculation(dims.height, node, "borderTopWidth");
566 		dims.height = DOMUtil._addStyleCalculation(dims.height, node, "borderBottomWidth");
567 		dims.height = DOMUtil._addStyleCalculation(dims.height, node, "marginTop");
568 		dims.height = DOMUtil._minusStyleCalculation(dims.height, node, "marginBottom");
569 		dims.height = DOMUtil._addStyleCalculation(dims.height, node, "paddingTop");
570 		dims.height = DOMUtil._addStyleCalculation(dims.height, node, "paddingBottom");
571 		
572 		dims.width = parseInt(node.getStyle("width"));
573 		dims.width = DOMUtil._addStyleCalculation(dims.width, node, "borderLeftWidth");
574 		dims.width = DOMUtil._addStyleCalculation(dims.width, node, "borderRightWidth");
575 		dims.width = DOMUtil._minusStyleCalculation(dims.width, node, "marginLeft");
576 		dims.width = DOMUtil._minusStyleCalculation(dims.width, node, "marginRight");
577 		dims.width = DOMUtil._addStyleCalculation(dims.width, node, "paddingLeft");
578 		dims.width = DOMUtil._addStyleCalculation(dims.width, node, "paddingRight");
579 		
580 		return dims;
581 	},
582 	
583 	_addStyleCalculation: function(dim, node, style) {
584 		return DOMUtil._doStyleCalculation(dim, node, style, true);
585 	},
586 
587 	_minusStyleCalculation: function(dim, node, style) {
588 		return DOMUtil._doStyleCalculation(dim, node, style, false);
589 	},
590 	
591 	_doStyleCalculation: function(dim, node, style, add) {
592 		var value = parseInt(node.getStyle(style));
593 		
594 		if(!isNaN(value)) {
595 			if(add) {
596 				dim += value;
597 			} else {
598 				dim -= value;
599 			}
600 		}
601 		
602 		return dim;
603 	}, */
604 	
605 	/**
606 	 * Returns true if the passed node is in the DOM
607 	 * 
608 	 * @param {Node} a DOM Node
609 	 * @returns {boolean}
610 	 */
611 	isInDOM: function(node) {
612 		while(true) {
613 			if(!node.parentNode) {
614 				return false;
615 			} else if(node.parentNode.tagName == "BODY") {
616 				return true;
617 			}
618 			
619 			node = node.parentNode;
620 		}
621 	},
622 	
623 	append: function(what, toNode) {
624 		if(Object.isString(what) || Object.isNumber(what)) {
625 			// string or number
626 			toNode.appendChild(document.createTextNode(what));
627 		} else if(what.appendTo) {
628 			// GUIWidget
629 			what.appendTo(toNode);
630 		} else if(what.appendChild) {
631 			// Node
632 			toNode.appendChild(what);
633 		} else if(Object.isArray(what)) {
634 			// Array of items
635 			what.each(function(item) {
636 				DOMUtil.append(item, toNode);
637 			});
638 		}
639 	},
640 	
641 	elementWasClickedOn: function(event, element) {
642 		var position = Element.positionedOffset(element);
643 		var dims = Element.getDimensions(element);
644 		var mouseX = Event.pointerX(event);
645 		var mouseY = Event.pointerY(event);
646 		var inBoxHorizontal = mouseX > position.left && mouseX < position.left + dims.width;
647 		var inBoxVertical = mouseY > position.top && mouseY < position.top + dims.height;
648 		
649 		return inBoxHorizontal && inBoxVertical;
650 	}
651 }
652