1 /**
  2  * @fileOverview BTM DOM Methods.
  3  * @author <a href="http://www.bobs-bits.com">Stephen Reay</a>
  4  */
  5 
  6 /**
  7  * @namespace Funtionality for selecting, traversing, reading, modifying and creating DOM elements
  8  */
  9 BTM.DOM = {
 10 	/**
 11 	 * Tag names for inline HTML Elements
 12 	 * @type {Array}
 13 	 */
 14 	inlineElements : [
 15 		'area',
 16 		'base',
 17 		'basefont',
 18 		'br',
 19 		'hr',
 20 		'input',
 21 		'img',
 22 		'link',
 23 		'meta'
 24 	],
 25 
 26 	/**
 27 	 * Valid attribute names for any element
 28 	 * @type {Array}
 29 	 */
 30 	standardAttributes : [
 31 		'class',
 32 		'id',
 33 		'style',
 34 		'title',
 35 		'dir',
 36 		'lang',
 37 		'accesskey',
 38 		'tabindex'
 39 	],
 40 
 41 	/**
 42 	 * Attributes specific to each element type
 43 	 * @type {Object}
 44 	 */
 45 	conditionalAttributes : {
 46 		a : [
 47 			'charset',
 48 			'coords',
 49 			'href',
 50 			'hreflang',
 51 			'name',
 52 			'rel',
 53 			'rev',
 54 			'shape',
 55 			'target',
 56 			'type'
 57 		],
 58 		area : [
 59 			'alt',
 60 			'coords',
 61 			'href',
 62 			'getShref',
 63 			'shape',
 64 			'target'
 65 		],
 66 		base : [
 67 			'href',
 68 			'target'
 69 		],
 70 		bdo : [
 71 			'dir'
 72 		],
 73 		blockquote : [
 74 			'cite'
 75 		],
 76 		button: [
 77 			'disabled',
 78 			'name',
 79 			'type',
 80 			'value'
 81 		],
 82 		caption : [
 83 			'align'
 84 		],
 85 		col : [
 86 			'align',
 87 			'char',
 88 			'charoff',
 89 			'span',
 90 			'valign',
 91 			'width'
 92 		],
 93 		colgroup : [
 94 			'align',
 95 			'char',
 96 			'charoff',
 97 			'span',
 98 			'valign',
 99 			'width'
100 		],
101 		del : [
102 			'cite',
103 			'datetime'
104 		],
105 		form : [
106 			'action',
107 			'accept',
108 			'accept-charset',
109 			'enctype',
110 			'method',
111 			'name',
112 			'target'
113 		],
114 		frame : [
115 			'frameborder',
116 			'longdesc',
117 			'marginheight',
118 			'marginwidth',
119 			'name',
120 			'noresize',
121 			'scrolling',
122 			'src'
123 		],
124 		frameset : [
125 			'cols',
126 			'rows'
127 		],
128 		iframe : [
129 			'frameborder',
130 			'height',
131 			'longdesc',
132 			'marginheight',
133 			'marginwidth',
134 			'name',
135 			'scrolling',
136 			'src',
137 			'width'
138 		],
139 		img : [
140 			'alt',
141 			'src'
142 		],
143 		input : [
144 			'accept',
145 			'alt',
146 			'checked',
147 			'disabled',
148 			'maxlength',
149 			'name',
150 			'readonly',
151 			'size',
152 			'src',
153 			'type',
154 			'value'
155 		],
156 		ins : [
157 			'cite',
158 			'datetime'
159 		],
160 		label : [
161 			'for'
162 		],
163 		link : [
164 			'charset',
165 			'href',
166 			'hreflang',
167 			'media',
168 			'rel',
169 			'rev',
170 			'target',
171 			'type'
172 		],
173 		map : [
174 			'name'
175 		],
176 		object : [
177 			'align',
178 			'archive',
179 			'border',
180 			'classid',
181 			'codebase',
182 			'codetype',
183 			'data',
184 			'declare',
185 			'height',
186 			'hspace',
187 			'name',
188 			'standby',
189 			'type',
190 			'usemap',
191 			'vspace',
192 			'width'
193 		],
194 		optgroup : [
195 			'label',
196 			'disabled'
197 		],
198 		option : [
199 			'disabled',
200 			'selected',
201 			'value'
202 		],
203 		param : [
204 			'name',
205 			'type',
206 			'value',
207 			'valuetype'
208 		],
209 		q : [
210 			'cite'
211 		],
212 		script : [
213 			'type',
214 			'charset',
215 			'defer',
216 			'language',
217 			'src'
218 		],
219 		select : [
220 			'disabled',
221 			'multiple',
222 			'name',
223 			'size'
224 		],
225 		style : [
226 			'type',
227 			'media'
228 		],
229 		table : [
230 			'border',
231 			'cellpadding',
232 			'cellspacing',
233 			'frame',
234 			'rules',
235 			'summary',
236 			'width'
237 		],
238 		tbody : [
239 			'align',
240 			'char',
241 			'charoff',
242 			'valign'
243 		],
244 		td : [
245 			'abbr',
246 			'align',
247 			'axis',
248 			'char',
249 			'charoff',
250 			'colspan',
251 			'headers',
252 			'rowspan',
253 			'scope',
254 			'valign'
255 		],
256 		textarea : [
257 			'cols',
258 			'rows',
259 			'disabled',
260 			'name',
261 			'readonly'
262 		],
263 		tfoot : [
264 			'align',
265 			'char',
266 			'charoff',
267 			'valign'
268 		],
269 		th : [
270 			'abbr',
271 			'align',
272 			'axis',
273 			'char',
274 			'charoff',
275 			'colspan',
276 			'headers',
277 			'rowspan',
278 			'scope',
279 			'valign'
280 		],
281 		thead : [
282 			'align',
283 			'char',
284 			'charoff',
285 			'valign'
286 		],
287 		tr : [
288 			'align',
289 			'char',
290 			'charoff',
291 			'valign'
292 		]
293 	},
294 
295 	/**
296 	 * Regular Expression used to identify a pixel length value
297 	 * @type RegExp
298 	 */
299 	pixelRegEx : /^\-?\d+(px)?$/i,
300 
301 	/**
302 	 * CSS properties that are lengths
303 	 * @type String[]
304 	 */
305 	lengthProperties : [
306 		'width',
307 		'height',
308 		'lineHeight',
309 		'top',
310 		'right',
311 		'bottom',
312 		'left',
313 		'margin',
314 		'marginLeft',
315 		'marginRight',
316 		'marginTop',
317 		'marginBottom',
318 		'padding',
319 		'paddingLeft',
320 		'paddingRight',
321 		'paddingTop',
322 		'paddingBottom',
323 		'border',
324 		'borderWidth',
325 		'borderLeft',
326 		'borderLeftWidth',
327 		'borderRight',
328 		'borderRightWidth',
329 		'borderTop',
330 		'borderTopWidth',
331 		'borderBottom',
332 		'borderBottomWidth'
333 	],
334 	
335 	/**
336 	 * The number used for generating new IDs to identify anonymous elements.
337 	 * @see BTM.DOM.identify
338 	 * @type {Number}
339 	 */
340 	anonymousElements : 0
341 };
342 
343 if (BTM.Browser.is('Trident', 7, 'lte')) {
344 	BTM.DOM.renamedAttributes = {
345 		'class' : 'className',
346 		'for' : 'htmlFor'
347 	};
348 	
349 	BTM.DOM.newElementUnsafeAttributes = [
350 		'name',
351 		'type'
352 	];
353 }
354 
355 /**
356  * Lookup an element by ID
357  * @param {HTMLElement|String} selector element ID or element reference
358  * @returns {HTMLElement|Boolean} reference to the element or false if not found
359  */
360 BTM.DOM.$ = function $(selector) {
361 	if(Object.isElement(selector) || Object.isObject(selector)) {
362 		return selector;
363 	}
364 	else if(Object.isString(selector)) {
365 		return document.getElementById(selector) || false;
366 	}
367 };
368 
369 /**
370  * Convenience shortcut to {@link BTM.DOM.$}
371  * @function
372  * @see BTM.DOM.$
373  */
374 BTM.$ = BTM.DOM.$;
375 
376 /**
377  * Get a unique reference to an element - returns existing ID if exists, if not applies and returns new, unique ID.
378  * @returns {String} the elements ID
379  */
380 BTM.DOM.identify = function identify(element) {
381 	element = BTM.$(element);
382 	
383 	if(BTM.DOM.hasAttribute(element, 'id')) {
384 		return BTM.DOM.getAttribute(element, 'id');
385 	}
386 	else {
387 		var newID;
388 		do {
389 			newID = 'anonymous_element_' + BTM.DOM.anonymousElements++;
390 		} while (BTM.$(newID));
391 	
392 		BTM.DOM.setAttribute(element, 'id', newID);
393 		return newID;
394 	}
395 };
396 
397 /**
398  * Lookup multiple elements by CSS selector. This will always try to make use of the querySelector API if the browser supports it natively.<br />
399  * If the native method exists but throws an exception (e.g.: MSIE8 using a CSS3 selector) the emulated functionality provided by an external library is used.
400  * @param {String} selector CSS selector string
401  * @returns {HTMLElement[]} elements matching the given CSS Selector
402  * @see <a href="http://www.w3.org/TR/css3-selectors/">http://www.w3.org/TR/css3-selectors/</a>
403  * @see <a href="http://dev.w3.org/2006/webapi/selectors-api/">http://dev.w3.org/2006/webapi/selectors-api/</a>
404  */	
405 BTM.DOM.$$ = function $$(selector, context) {
406 	context = BTM.$(context) || document;
407 	BTM.log("Finding elements matching: '" + selector + "'", context);
408 
409 	if (context.querySelectorAll) {
410 		try {
411 			BTM.log("Attempting lookup of '" + selector + "' using native querySelector");
412 			return Object.toArray(context.querySelectorAll(selector));
413 		}
414 		catch (e) {
415 			BTM.log('Falling back to JavaScript query selector', e);
416 		}
417 		
418 	}
419 	if (BTM.library) {
420 		switch (BTM.library.name) {
421 			case 'Prototype':
422 				return Element.select(context, selector).toArray();
423 				break;
424 			
425 			case 'jQuery':
426 				return Object.toArray(jQuery(selector, context));
427 				break;
428 			
429 			case 'cssQuery':
430 				return cssQuery(selector, context).unique();
431 				break;
432 		}
433 	}
434 	else {
435 		try {
436 			return Object.toArray(document.getElementsByTagName(selector));
437 		}
438 		catch(e) {
439 			BTM.error("Cannot process the supplied selector string natively!");
440 		}
441 	}
442 };
443 
444 /**
445  * Convenience shortcut to {@link BTM.DOM.$$}
446  * @function
447  * @see BTM.DOM.$$
448  */
449 BTM.$$ = BTM.DOM.$$;
450 
451 
452 /**
453  * Add a class to an element
454  * @param {HTMLElement|String} element element ID or element reference to add class to
455  * @param {String} className class to add to the element
456  * @returns {HTMLElement} the original element
457  */
458 BTM.DOM.addClass = function addClass(element, className) {
459 	element = BTM.$(element);
460 	if (!className) {
461 		return false;
462 	}
463 	if (Object.isString(className)  && className.indexOf(' ') >= 0) {
464 		className = className.trim().split(/\s/);
465 	}
466 	if (Object.isArray(className)) {
467 		BTM.log("Adding '" + className.length + "' classes", element);
468 		className.forEach(BTM.DOM.addClass.curry(element));
469 	}
470 	else if (!BTM.DOM.hasClass(element, className)) {
471 		BTM.log("Adding class '" + className + "'", element);
472 		element.className = element.className + ' ' + className;
473 	}
474 
475 	return element;
476 };
477 
478 /**
479  * Remove a class from an element
480  * @param {HTMLElement|String} element element ID or element reference to remove class from
481  * @param {String} className class to remove from the element
482  * @returns {HTMLElement} the original element
483  */
484 BTM.DOM.removeClass = function removeClass(element, className) {
485 	element = BTM.$(element);
486 	if(className.indexOf(' ') >= 0) {
487 		className = className.trim().split(/\s/);
488 	}
489 	if (Object.isArray(className)) {
490 		className.forEach(BTM.DOM.removeClass.curry(element));
491 	}
492 	else {
493 		BTM.log("Removing class '" + className + "'", element);
494 		element.className = element.className.replace(new RegExp('(^|\\s)' + className + '($|\\s)', 'g'),' ');
495 	}
496 	
497 	return element;
498 };
499 
500 /**
501  * Check if an element has a class
502  * @param {HTMLElement|String} element element ID or element reference to check for class
503  * @param {String} className class to check for
504  * @returns {Boolean} true if element has class, false if it does not
505  */
506 BTM.DOM.hasClass = function hasClass(element, className) {
507 	element = BTM.$(element);
508 	BTM.log("Checking for class '" + className + "'", element);
509 	var classes = element.className.split(' ');
510 
511 	return classes.inArray(className);
512 };
513 
514 /**
515  * Swap between two classes on an element
516  * @param {HTMLElement|String} element element ID or element reference to toggle classes of
517  * @param {String} className1 first class to toggle between
518  * @param {String} [className2=""] second class to toggle between.
519  * @returns {HTMLElement} the original element
520  */
521 BTM.DOM.swapClass = function swapClass(element, className1, className2) {
522 	element = BTM.$(element);
523 	className2 = className2 || '';
524 	BTM.log("Swapping classes '" + className1 + "' and '" + className2 + "'", element);
525 	var classToAdd = BTM.DOM.hasClass(element, className1) ? className2 : className1;
526 	var classToRemove = classToAdd === className1 ? className2 : className1;
527 	BTM.DOM.addClass(element, classToAdd);
528 	BTM.DOM.removeClass(element, classToRemove);
529 	
530 	return element;
531 };
532 
533 /**
534  * Wrap an element in a new element
535  * @param {HTMLElement|String} element element ID or element reference to wrap
536  * @param {String} newTag tag to wrap original element with
537  * @param {Object} [attributes] attributes to set on new element
538  * @returns {HTMLElement} the newly created element
539  */
540 BTM.DOM.wrap = function wrap(element, newTag, attributes) {
541 	element = BTM.$(element);
542 	
543 	var newElement = BTM.DOM.createElement(newTag, attributes || {});
544 	BTM.log("Wrapping with new '" + newTag + "' element", element);
545 	element.parentNode.replaceChild(newElement, element);
546 	
547 	newElement.appendChild(element);
548 	
549 	return newElement;
550 };
551 
552 /**
553  * Create a new element, optionally with attributes
554  * @param {String} tag of the new element to create
555  * @param {Object} [attributes] the attributes to add to the newly created element
556  * @returns {HTMLElement} the newly created element
557  */
558 BTM.DOM.createElement = function createElement(tag, attributes, content) {
559 	attributes = attributes || false;
560 	if (BTM.Browser.is('Trident', 7, 'lte') && (!attributes || BTM.DOM.newElementUnsafeAttributes.some(function(el){return attributes.hasOwnProperty(el);}))) {
561 		var newElStr = '<' + tag + ' ';
562 		for (var i = 0; i < BTM.DOM.newElementUnsafeAttributes.length; i++) {
563 			if (attributes.hasOwnProperty(BTM.DOM.newElementUnsafeAttributes[i])) {
564 				newElStr += BTM.DOM.newElementUnsafeAttributes[i] + '="' + attributes[BTM.DOM.newElementUnsafeAttributes[i]] + '" ';
565 			}
566 		}
567 		if (BTM.DOM.inlineElements.indexOf(tag) !== -1) {
568 			newElStr += '/>';				
569 		}
570 		else {
571 			newElStr += '></' + tag + '>';
572 		}
573 		
574 		var element = document.createElement(newElStr);
575 	}
576 	else {
577 		var element = document.createElement(tag);
578 	}
579 	
580 	BTM.log("Creating new '" + tag + "' element with " + Object.size(attributes) + " attributes", element, attributes);
581 	
582 	if(attributes) {
583 		BTM.DOM.setAttribute(element, attributes);
584 	}
585 	
586 	if (content) {
587 		BTM.DOM.update(element, content);
588 	}
589 	
590 	return element;
591 };
592 
593 /**
594  * Get the specified attribute(s) for an element
595  * @param {HTMLElement|String} element element ID or element reference to get attribute(s) of
596  * @param {String|String[]} name the name of a single attribute to get, or an Array containing names of attributes to set
597  * @returns {String|String[]} the attribute value(s) from the element.
598  */
599 BTM.DOM.getAttribute = function getAttribute(element, name) {
600 	element = BTM.$(element);
601 	if (!element) {
602 		return element;
603 	}
604 
605 	if (Object.isString(name)) {
606 		BTM.log("Getting attribute '" + name + "'", element);
607 		if (BTM.DOM.renamedAttributes && BTM.DOM.renamedAttributes.hasOwnProperty(name)) {
608 			BTM.log("Using attribute name '" + BTM.DOM.renamedAttributes[name] + "' instead of '" + name + "'", element);
609 			name = BTM.DOM.renamedAttributes[name];
610 		}
611 		
612 		if (BTM.DOM.hasAttribute(element, name)) {
613 			return element.getAttribute(name);
614 		}
615 	}
616 	else if (Object.isArray(name)) {
617 		var atts = {};
618 		BTM.log("Getting attributes", element);
619 		name.forEach(function(att) {
620 			if (BTM.DOM.renamedAttributes && BTM.DOM.renamedAttributes.hasOwnProperty(name)) {
621 				var newName = BTM.DOM.renamedAttributes[att];
622 				att = newName;
623 			}
624 			if (BTM.DOM.hasAttribute(element, att)) {
625 				atts[att] =  BTM.DOM.getAttribute(element, att);
626 			}
627 		});	
628 		return  atts;
629 	}
630 };
631 
632 /**
633  * Set attribute(s) on an element
634  * @param {HTMLElement|String} element element ID or element reference to set attribute(s) on
635  * @param {String|Object} name the name of a single attribute to set, or an object containing name:value pairs of attributes to set
636  * @param [value] the value to use when setting a single attribute
637  * @returns {HTMLElement} the original element
638  */
639 BTM.DOM.setAttribute = function setAttribute(element, name, value) {
640 	element = BTM.$(element);
641 	
642 	if (Object.isString(name)) {
643 		BTM.log("Setting '" + name + "' attribute to '" + value + "'", element);
644 		if (BTM.DOM.renamedAttributes && BTM.DOM.renamedAttributes.hasOwnProperty(name)) {
645 			BTM.log("Using attribute name '" + BTM.DOM.renamedAttributes[name] + "' instead of '" + name + "'", element);
646 			name = BTM.DOM.renamedAttributes[name];
647 		}
648 		element.setAttribute(name, value);
649 	}
650 	else {
651 		BTM.log("Setting attributes", element);
652 		for (var att in name) {
653 			if (name.hasOwnProperty(att)) {
654 				BTM.DOM.setAttribute(element, att, name[att]);
655 			}
656 		}
657 	}
658 	return element;
659 };
660 
661 /**
662  * Remove attribute(s) from an element
663  * @param {HTMLElement|String} element element ID or element reference to remove attribute(s) from
664  * @param {String|Array} name the name of a single attribute to remove, or an array containing names of attributes to remove
665  * @returns {HTMLElement} the original element
666  */
667 BTM.DOM.removeAttribute = function removeAttribute(element, name) {
668 	element = BTM.$(element);
669 	
670 	if (Object.isString(name)) {
671 		BTM.log("Removing '" + name + "' attribute", element);
672 		if (BTM.DOM.renamedAttributes && BTM.DOM.renamedAttributes.hasOwnProperty(name)) {
673 			BTM.log("Using attribute name '" + BTM.DOM.renamedAttributes[name] + "' instead of '" + name + "'", element);
674 			name = BTM.DOM.renamedAttributes[name];
675 		}
676 		element.removeAttribute(name);
677 	}
678 	else if (Object.isArray(name)) {
679 		name.forEach(BTM.DOM.removeAttribute.curry(element));
680 	}
681 	return element;
682 };
683 
684 /**
685  * Check if an element has an attribute
686  * @param {HTMLElement|String} element element ID or element reference to check for attribute
687  * @param {String} name the name of the attribute to check for
688  * @returns {Boolean} true if element has the attribute, false if it does not
689  */	
690 BTM.DOM.hasAttribute = function hasAttribute(element, name) {
691 	element = BTM.$(element);
692 	
693 	if (BTM.DOM.renamedAttributes && BTM.DOM.renamedAttributes.hasOwnProperty(name)) {
694 		name = BTM.DOM.renamedAttributes[name];
695 	}
696 	
697 	if (element.hasAttribute) {
698 		return element.hasAttribute(name);
699 	}
700 	else {
701 		return element.getAttribute(name) !== null;
702 	}
703 };
704 
705 /**
706  * Set style attribute(s) on an element
707  * @param {HTMLElement|String} element element ID or element reference to set style attribute(s) on
708  * @param {String|Object} name the name of a single style attribute to set, or an object containing name:value pairs of style attributes to set
709  * @param [value] the value to use when setting a single style attribute
710  * @returns {HTMLElement} the original element
711  */
712 BTM.DOM.setStyle = function setStyle(element, name, value) {
713 	element = BTM.$(element);
714 	
715 	if (Object.isString(name)) {
716 		if (name === 'float') {
717 			name = Object.isUndefined(element.style.styleFloat) ? 'cssFloat' : 'styleFloat';
718 		}
719 		BTM.log("Setting '" + name + "' style property to '" + value + "'", element);
720 		element.style[name.toCamelCase()] = value;
721 	}
722 	else {
723 		for (var att in name) {
724 			if (name.hasOwnProperty(att)) {
725 				BTM.log("Setting '" + att + "' style property to '" + name[att] + "'", element);
726 				element.style[att.toCamelCase()] = name[att];
727 			}
728 		}
729 	}
730 	return element;
731 };
732 
733 /**
734  * Get a Pixel value for a non-pixel length type, such as em, pt, etc
735  * @param {HTMLElement|String} element element ID or element reference to get pixel value from
736  * @param {String} value the value to extract a pixel value from
737  * @returns {Number|String} the calculated pixel value, or the original value provided
738  */
739 BTM.DOM.getPixelValue = function getPixelValue(element, value) {
740 	if (BTM.DOM.pixelRegEx.test(value)) {
741 		return parseInt(value);
742 	}
743 	try {
744 		if (element.runtimeStyle) {
745 			var style = element.style.left;
746 			var runtimeStyle = element.runtimeStyle.left;
747 			element.runtimeStyle.left = element.currentStyle.left;
748 			element.style.left = value || 0;
749 			value = element.style.pixelLeft;
750 			element.style.left = style;
751 			element.runtimeStyle.left = runtimeStyle;		
752 		}		
753 	}
754 	catch (e) {}
755 	
756 	return value;
757 };
758 
759 /**
760  * Get computed style values for an element
761  * @param {HTMLElement|String} element element ID or element reference to get style values from
762  * @param {String} styleProperty style property to get from element
763  * @returns {String} the computed value of the specified style on element	
764  */
765 BTM.DOM.getComputedStyle = function getComputedStyle(element, styleProperty) {
766 	element = BTM.$(element);
767 	if (styleProperty === 'float') {
768 		styleProperty = element.style.styleFloat ? 'styleFloat' : 'cssFloat';
769 	}
770 	
771 	styleProperty = styleProperty.toCamelCase();
772 	
773 	var hide = false;
774 	
775 	if (styleProperty !== 'display' && element.style.display === 'none') {
776 		hide = true;
777 		BTM.Effect.show(element);
778 	}
779 
780 	
781 	if (element.currentStyle) {
782 		if (styleProperty === 'border') {
783 			var style = element.currentStyle.borderStyle + " " +
784 						element.currentStyle.borderWidth + " " +
785 						element.currentStyle.borderColor;
786 		}
787 		else {
788 			var style =  element.currentStyle[styleProperty];
789 		}
790 	}
791 	else if (window.getComputedStyle && document.defaultView) {
792 		var computedStyle = document.defaultView.getComputedStyle(element, null);
793 		var style = computedStyle[styleProperty];
794 	}
795 	if (BTM.DOM.lengthProperties.inArray(styleProperty)) {
796 		style = BTM.DOM.getPixelValue(element, style);
797 	}
798 	
799 	if (hide) {
800 		BTM.Effect.hide(element);
801 	}
802 
803 	return style;
804 };
805 
806 /**
807  * Get actual style values for an element
808  * @param {HTMLElement|String} element element ID or element reference to get style values from
809  * @param {String} styleProperty style property to get from element
810  * @returns {String} the value of the specified style on element
811  */
812 BTM.DOM.getStyle = function getStyle(element, styleProperty) {
813 	element = BTM.$(element);
814 	if (styleProperty === 'float') {
815 		styleProperty = element.style.styleFloat ? 'styleFloat' : 'cssFloat';
816 	}
817 	
818 	var hide = false;
819 
820 	if (styleProperty !== 'display' && element.style.display === 'none') {
821 		hide = true;
822 		BTM.Effect.show(element);
823 	}
824 	
825 	var style = element.style[styleProperty.toCamelCase()];
826 	
827 	if (hide) {
828 		BTM.Effect.hide(element);
829 	}
830 
831 	return style;
832 };
833 
834 /**
835  * Transform an element into a different element, keeping the same attributes
836  * @param {HTMLElement|String} element element ID or element reference to transform
837  * @param {String} newTag the tag the element should be to be transformed into
838  * @param {Object} [options] currently un-used
839  * @returns {HTMLElement} a reference to the new object
840  */
841 BTM.DOM.morph = function morph(element, newTag, options) {
842 	element = BTM.$(element);
843 	
844 	var atts = BTM.DOM.getAttribute(element, BTM.DOM.standardAttributes.concat(BTM.DOM.conditionalAttributes[newTag]));
845 	atts = Object.filter(atts, function(att) {
846 		return att !== '' && att !== null;
847 	});
848 	var newElement = BTM.DOM.createElement(newTag, atts);
849 	
850 	if (!BTM.DOM.inlineElements.inArray(newTag) && atts.value && newElement.innerText === "") {
851 		newElement.appendChild(document.createTextNode(atts.value));
852 	}
853 	
854 	element.parentNode.replaceChild(newElement, element);
855 	
856 	return newElement;
857 };
858 
859 
860 /**
861  * Update the contents of an Element
862  * @param {HTMLElement|String} element element ID or element reference to update contents of
863  * @param {HTMLElement|Object|String} content the new content for the element. Can be a HTML Element, a string, or an object (requires a toHTML or toString method)
864  * @returns {HTMLElement} the original Element
865  */
866 BTM.DOM.update = function update(element, content) {
867 	element = BTM.$(element);
868 	content = content || "";
869 
870 	if (Object.isElement(content)) {
871 		element.innerHTML = "";
872 		element.appendChild(content);
873 	}
874 	else if (Object.isString(content)) {
875 		element.innerHTML = content;
876 	}
877 	else if (content.toHTML) {
878 		element.innerHTML = content.toHTML();
879 	}
880 	else if (content.toString) {
881 		element.innerHTML = content.toString();
882 	}
883 	
884 	return element;
885 };
886 
887 /**
888  * Make an element unselectable by the user
889  * @param {HTMLElement|String} element element ID or element reference to make unselectable
890  * @returns {HTMLElement} the original element
891  * @see BTM.DOM.makeSelectable
892  */
893 BTM.DOM.makeUnselectable = function makeUnselectable(element) {
894 	element = BTM.$(element);
895 	var style = {'user-select':'none'};
896 	switch(BTM.Browser.engine) {
897 		case 'Gecko':
898 			style['-moz-user-select'] = 'none';
899 			break;
900 			
901 		case 'KHTML':
902 			style['-khtml-user-select'] = 'none';
903 			break;
904 			
905 		case 'WebKit':
906 			style['-webkit-user-select'] = 'none';
907 			break;
908 			
909 		case 'Trident':
910 			var makeUnselectableFunc = BTM.observe(element, 'selectstart', BTM.self.curry(false));
911 			BTM.DOM.setAttribute('makeUnselectableFunc', makeUnselectableFunc);
912 			break;
913 	}
914 	BTM.log("Making element unselectable", element);
915 	BTM.DOM.setStyle(element, style);
916 };
917 
918 /**
919  * Make an element selectable by the user
920  * @param {HTMLElement|String} element element ID or element reference to make selectable
921  * @returns {HTMLElement} the original element
922  * @see BTM.DOM.makeUnselectable
923  */
924 BTM.DOM.makeSelectable = function makeSelectable(element) {
925 	element = BTM.$(element);
926 	var style = {'user-select':''};
927 	switch(BTM.Browser.engine) {
928 		case 'Gecko':
929 			style['-moz-user-select'] = '';
930 			break;
931 			
932 		case 'KHTML':
933 			style['-khtml-user-select'] = '';
934 			break;
935 			
936 		case 'WebKit':
937 			style['-webkit-user-select'] = '';
938 			break;
939 			
940 		case 'Trident':
941 			var makeUnselectableFunc = BTM.DOM.getAttribute('makeUnselectableFunc');
942 			BTM.stopObserving(element, 'selectstart', makeUnselectableFunc);
943 			break;
944 	}
945 	
946 	BTM.log("Making element selectable", element);
947 	BTM.DOM.setStyle(element, style);
948 };
949 
950 /**
951  * Get the dimensions of an element, optionally including it's margins
952  * @param {HTMLElement|String} element element ID or element reference to get dimensions of
953  * @param {Boolean} [includeMargins=false] flag to include margin width in dimension calculations
954  * @returns {Object} Object containing "height" and "width" properties.
955  */
956 BTM.DOM.getDimensions = function getDimensions(element, includeMargins) {
957 	element = BTM.$(element);
958 	includeMargins = includeMargins || false;
959 	
960 	var dimensions = {
961 		'height' : element.offsetHeight,
962 		'width' : element.offsetWidth
963 	};
964 	
965 	if (includeMargins) {
966 		var margins = {
967 			'width' : parseInt(BTM.DOM.getComputedStyle(element,'margin-top') || 0) + parseInt(BTM.DOM.getComputedStyle(element,'margin-bottom') || 0),
968 			'height' : parseInt(BTM.DOM.getComputedStyle(element,'margin-left') || 0) + parseInt(BTM.DOM.getComputedStyle(element,'margin-right') || 0)
969 		};
970 
971 		dimensions.height += margins.height;
972 		dimensions.width +=  margins.width;
973 	}
974 	
975 	return dimensions;
976 };
977 
978 /**
979  * Get the inner "available" space of an element
980  * @param {HTMLElement|String} element element ID or element reference to get "available" space of
981  * @param {Object} [dimensions] manual stipulation of the original dimensions to base the calculations on
982  * @returns {Object} Object containing "height" and "width" properties.
983  */
984 BTM.DOM.getAvailableSpace = function getAvailableSpace(element, dimensions) {
985 	element = BTM.$(element);
986 	dimensions = dimensions || {
987 		'width' : element.clientWidth,
988 		'height' : element.clientHeight
989 	};
990 	
991 	var spacing = {
992 		'width' : parseInt(BTM.DOM.getComputedStyle(element,'padding-left') || 0) +
993 				  parseInt(BTM.DOM.getComputedStyle(element,'padding-right') || 0) + 
994 				  parseInt(BTM.DOM.getComputedStyle(element,'margin-left') || 0) + 
995 				  parseInt(BTM.DOM.getComputedStyle(element,'margin-right') || 0) +
996 				  parseInt(BTM.DOM.getComputedStyle(element,'border-left-width') || 0) +
997 				  parseInt(BTM.DOM.getComputedStyle(element,'border-right-width') || 0),
998 		
999 		'height' : parseInt(BTM.DOM.getComputedStyle(element,'padding-top') || 0) +
1000 				   parseInt(BTM.DOM.getComputedStyle(element,'padding-bottom') || 0) + 
1001 				   parseInt(BTM.DOM.getComputedStyle(element,'margin-top') || 0) + 
1002 				   parseInt(BTM.DOM.getComputedStyle(element,'margin-bottom') || 0) +
1003 					   parseInt(BTM.DOM.getComputedStyle(element,'border-top-width') || 0) + 
1004 				   parseInt(BTM.DOM.getComputedStyle(element,'border-bottom-width') || 0)
1005 	};
1006 	
1007 	var innerDimensions = {
1008 		'width' : dimensions.width - spacing.width,
1009 		'height' : dimensions.height - spacing.height
1010 	};
1011 	
1012 	return innerDimensions;
1013 };