Source: dom.js

/**
 * dom.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2014 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 */
define([
	'dom/attrs',
	'dom/classes',
	'dom/mutation',
	'dom/nodes',
	'dom/style',
	'dom/traversing'
], /** @exports Dom */ function Dom(
	Attrs,
	Classes,
	Mutation,
	Nodes,
	Style,
	Traversing
) {
	'use strict';

	/**
	 * Checks whether the given node is content editable.  An editing host is a
	 * node that is either an Element with a contenteditable attribute set to
	 * the true state, or the Element child of a Document whose designMode is
	 * enabled.
	 *
	 * An element with the class "aloha-editable" is considered an editing
	 * host.
	 *
	 * @param {!Node} node
	 * @return {boolean} True if `node` is content editable.
	 */
	function isEditingHost(node) {
		if (!Nodes.isElementNode(node)) {
			return false;
		}
		if ('true' === node.getAttribute('contentEditable')) {
			return true;
		}
		if (Classes.has(node, 'aloha-editable')) {
			return true;
		}
		var parent = node.paretNode;
		if (!parent) {
			return false;
		}
		if (parent.nodeType === Nodes.Nodes.DOCUMENT && 'on' === parent.designMode) {
			return true;
		}
	}

	/**
	 * Check if the given node's contentEditable attribute is `true`.
	 *
	 * @param  {Element} node
	 * @return {boolean}
	 */
	function isContentEditable(node) {
		return Nodes.isElementNode(node) && 'true' === node.contentEditable;
	}

	/**
	 * Checks whether the given element is editable.
	 *
	 * An element with the class "aloha-editable" is considered editable.
	 *
	 * @reference:
	 * http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#contenteditable
	 * http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#designMode
	 *
	 * @param {!Node} node
	 * @return {boolean}
	 */
	function isEditable(node) {
		if (!Nodes.isElementNode(node)) {
			return false;
		}
		var contentEditable = node.getAttribute('contentEditable');
		if ('true' === contentEditable || '' === contentEditable) {
			return true;
		}
		if ('false' === contentEditable) {
			return false;
		}
		// Because the value of `contentEditable` can be "inherited" according
		// to specification, and null according to browser implementation.
		if (Classes.has(node, 'aloha-editable')) {
			return true;
		}
		var parent = node.parentNode;
		if (!parent) {
			return false;
		}
		if (parent.nodeType === Nodes.Nodes.DOCUMENT && 'on' === parent.designMode) {
			return true;
		}
		return isEditable(parent);
	}

	function isEditableNode(node) {
		return isEditable(Nodes.isTextNode(node) ? node.parentNode : node);
	}

	/**
	 * Checks whether the given element is an editing host.
	 *
	 * @param {!Node} node
	 * @return {boolean}
	 */
	function editingHost(node) {
		if (isEditingHost(node)) {
			return node;
		}
		if (!isEditable(node)) {
			return null;
		}
		var ancestor = node.parentNode;
		while (ancestor && !isEditingHost(ancestor)) {
			ancestor = ancestor.parentNode;
		}
		return ancestor;
	}

	function editableParent(node) {
		var ancestor = node.parentNode;
		while (ancestor && !isEditable(ancestor)) {
			ancestor = ancestor.parentNode;
		}
		return ancestor;
	}

	var parser = document.createElement('DIV');

	function parseNode(html) {
		parser.innerHTML = html;
		var node = parser.firstChild;
		parser.removeChild(node);
		return node;
	}

	/**
	 * Used to serialize outerHTML of DOM elements in older (pre-HTML5) Gecko,
	 * Safari, and Opera browsers.
	 *
	 * Beware that XMLSerializer generates an XHTML string (<div class="team" />
	 * instead of <div class="team"></div>).  It is noted here:
	 * http://stackoverflow.com/questions/1700870/how-do-i-do-outerhtml-in-firefox
	 * that some browsers (like older versions of Firefox) have problems with
	 * XMLSerializer, and an alternative, albeit more expensive option, is
	 * described.
	 *
	 * @type {XMLSerializer}
	 */
	var Serializer = window.XMLSerializer && new window.XMLSerializer();

	function stringify(node) {
		return Serializer.serializeToString(node);
	}

	function isNode(node) {
		var str = Object.prototype.toString.call(node);
		// TODO: is this really the best way to do it?
		return (/^\[object (Text|Comment|HTML\w*Element)\]$/).test(str);
	}

	function parseReviver(key, value) {
		if (value && value['type'] === 'Node') {
			var str = value['value'];
			if (null != str) {
				value = parseNode(str);
			}
		}
		return value;
	}

	function stringifyReplacer(key, value) {
		if (value && isNode(value)) {
			value = {
				'type': 'Node',
				'value': stringify(value)
			};
		}
		return value;
	}

	var expandoIdCnt = 0;
	var expandoIdProp = '!aloha-expando-node-id';

	/**
	 * @fix me jslint hates this. it generates 4 errors
	 */
	function ensureExpandoId(node) {
		return node[expandoIdProp] = node[expandoIdProp] || ++expandoIdCnt;
	}

	function enableSelection(elem) {
		elem.removeAttribute('unselectable', 'on');
		Style.set(elem, Browsers.VENDOR_PREFIX + '-user-select', 'all');
		elem.onselectstart = null;
	}

	function disableSelection(elem) {
		elem.removeAttribute('unselectable', 'on');
		Style.set(elem, Browsers.VENDOR_PREFIX + '-user-select', 'none');
		elem.onselectstart = Fn.returnFalse;
	}

	/**
	 * Gets the window to which the given document belongs.
	 *
	 * @param   {Document} doc
	 * @returns {Window}
	 */
	function documentWindow(doc) {
		return doc['defaultView'] || doc['parentWindow'];
	}

	return {
		Nodes                   : Nodes.Nodes,
		offset                  : Nodes.offset,
		cloneShallow            : Nodes.cloneShallow,
		clone                   : Nodes.clone,
		text                    : Nodes.text,
		children                : Nodes.children,
		nthChild                : Nodes.nthChild,
		numChildren             : Nodes.numChildren,
		nodeIndex               : Nodes.nodeIndex,
		nodeLength              : Nodes.nodeLength,
		hasChildren             : Nodes.hasChildren,
		nodeAtOffset            : Nodes.nodeAtOffset,
		normalizedNthChild      : Nodes.normalizedNthChild,
		normalizedNodeIndex     : Nodes.normalizedNodeIndex,
		realFromNormalizedIndex : Nodes.realFromNormalizedIndex,
		normalizedNumChildren   : Nodes.normalizedNumChildren,
		isTextNode              : Nodes.isTextNode,
		isElementNode           : Nodes.isElementNode,
		isFragmentNode          : Nodes.isFragmentNode,
		isEmptyTextNode         : Nodes.isEmptyTextNode,
		isSameNode              : Nodes.isSameNode,
		equals                  : Nodes.equals,
		contains                : Nodes.contains,
		followedBy              : Nodes.followedBy,
		hasText                 : Nodes.hasText,
		fragmentHtml            : Nodes.fragmentHtml,

		append            : Mutation.append,
		merge             : Mutation.merge,
		moveNextAll       : Mutation.moveNextAll,
		moveBefore        : Mutation.moveBefore,
		moveAfter         : Mutation.moveAfter,
		move              : Mutation.move,
		copy              : Mutation.copy,
		wrap              : Mutation.wrap,
		wrapWith          : Mutation.wrapWith,
		insert            : Mutation.insert,
		insertAfter       : Mutation.insertAfter,
		replace           : Mutation.replace,
		replaceShallow    : Mutation.replaceShallow,
		remove            : Mutation.remove,
		removeShallow     : Mutation.removeShallow,
		removeChildren    : Mutation.removeChildren,

		addClass     : Classes.add,
		removeClass  : Classes.remove,
		hasClass     : Classes.has,

		attrNames    : Attrs.attrNames,
		hasAttrs     : Attrs.has,
		attrs        : Attrs.attrs,
		setAttr      : Attrs.set,
		setAttrNS    : Attrs.setNS,
		getAttr      : Attrs.get,
		getAttrNS    : Attrs.getNS,
		removeAttr   : Attrs.remove,
		removeAttrNS : Attrs.removeNS,
		removeAttrs  : Attrs.removeAll,

		removeStyle       : Style.remove,
		setStyle          : Style.set,
		getStyle          : Style.get,
		getComputedStyle  : Style.getComputedStyle,
		getComputedStyles : Style.getComputedStyles,

		query                        : Traversing.query,
		nextNonAncestor              : Traversing.nextNonAncestor,
		nextWhile                    : Traversing.nextWhile,
		nextUntil                    : Traversing.nextUntil,
		nextSibling                  : Traversing.nextSibling,
		nextSiblings                 : Traversing.nextSiblings,
		prevWhile                    : Traversing.prevWhile,
		prevUntil                    : Traversing.prevUntil,
		prevSibling                  : Traversing.prevSibling,
		prevSiblings                 : Traversing.prevSiblings,
		walk                         : Traversing.walk,
		walkRec                      : Traversing.walkRec,
		walkUntilNode                : Traversing.walkUntilNode,
		forward                      : Traversing.forward,
		backward                     : Traversing.backward,
		findForward                  : Traversing.findForward,
		findBackward                 : Traversing.findBackward,
		upWhile                      : Traversing.upWhile,
		climbUntil                   : Traversing.climbUntil,
		childAndParentsUntil         : Traversing.childAndParentsUntil,
		childAndParentsUntilIncl     : Traversing.childAndParentsUntilIncl,
		childAndParentsUntilNode     : Traversing.childAndParentsUntilNode,
		childAndParentsUntilInclNode : Traversing.childAndParentsUntilInclNode,

		stringify         : stringify,
		stringifyReplacer : stringifyReplacer,
		parseReviver      : parseReviver,

		ensureExpandoId   : ensureExpandoId,

		enableSelection   : enableSelection,
		disableSelection  : disableSelection,

		// FIXME: move to html.js
		isEditable        : isEditable,
		isEditableNode    : isEditableNode,
		isEditingHost     : isEditingHost,
		isContentEditable : isContentEditable,

		documentWindow     : documentWindow,
		editingHost        : editingHost,
		editableParent     : editableParent
	};
});