1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-18 15:08:22 +00:00
seahub/media/aloha-0.22.7/lib/util/dom2.js

302 lines
9.8 KiB
JavaScript
Raw Normal View History

/* ephemera.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-2012 Gentics Software GmbH, Vienna, Austria.
* Contributors http://aloha-editor.org/contribution.php
*
* Aloha Editor is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* As an additional permission to the GNU GPL version 2, you may distribute
* non-source (e.g., minimized or compacted) forms of the Aloha-Editor
* source code without the copy of the GNU GPL normally required,
* provided you include this license notice and a URL through which
* recipients can access the Corresponding Source.
*/
define([
'jquery',
'util/maps',
'util/strings',
'util/browser'
], function (
$,
Maps,
Strings,
Browser
) {
'use strict';
var spacesRx = /\s+/;
var attrRegex = /\s([^\/<>\s=]+)(?:=(?:"[^"]*"|'[^']*'|[^>\/\s]+))?/g;
/**
* Like insertBefore, inserts firstChild into parent before
* refChild, except also inserts all the following siblings of
* firstChild.
*/
function moveNextAll(parent, firstChild, refChild) {
while (firstChild) {
var nextChild = firstChild.nextSibling;
parent.insertBefore(firstChild, refChild);
firstChild = nextChild;
}
}
/**
* 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|null}
*/
var Serializer = window.XMLSerializer && new window.XMLSerializer();
/**
* Gets the serialized HTML that describes the given DOM element and its
* innerHTML.
*
* Polyfill for older versions of Gecko, Safari, and Opera browsers.
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=92264 for background.
*
* @param {HTMLElement} node DOM Element.
* @return {String}
*/
function outerHtml(node) {
var html = node.outerHTML;
if (typeof html !== 'undefined') {
return html;
}
try {
return Serializer ? Serializer.serializeToString(node) : node.xml;
} catch (e) {
return node.xml;
}
}
/**
* Retrieves the names of all attributes from the given elmenet.
*
* Correctly handles the case that IE7 and IE8 have approx 70-90
* default attributes on each and every element.
*
* This implementation does not iterate over the elem.attributes
* property since that is much slower on IE7 (even when
* checking the attrNode.specified property). Instead it parses the
* HTML of the element. For elements with few attributes the
* performance on IE7 is improved by an order of magnitued.
*
* On IE7, when you clone a <button disabled="disabled"/> or an
* <input checked="checked"/> element the boolean properties will
* not be set on the cloned node. We choose the speed optimization
* over correctness in this case. The dom-to-xhtml plugin has a
* workaround for this case.
*/
function attrNames(elem) {
var names = [];
var html = outerHtml(elem.cloneNode(false));
var match;
while (null != (match = attrRegex.exec(html))) {
names.push(match[1]);
}
return names;
}
/**
* Gets the attributes of the given element.
*
* See attrNames() for an edge case on IE7.
*
* @param elem
* An element to get the attributes for.
* @return
* An array containing [name, value] tuples for each attribute.
* Attribute values will always be strings, but possibly empty strings.
*/
function attrs(elem) {
var as = [];
var names = attrNames(elem);
var i;
var len;
for (i = 0, len = names.length; i < len; i++) {
var name = names[i];
var value = $.attr(elem, name);
if (null == value) {
value = "";
} else {
value = value.toString();
}
as.push([name, value]);
}
return as;
}
/**
* Like indexByClass() but operates on a list of elements instead.
* The given list may be a NodeList, HTMLCollection, or an array.
*/
function indexByClassHaveList(elems, classMap) {
var index = {},
indexed,
classes,
elem,
cls,
len,
i,
j;
for (i = 0, len = elems.length; i < len; i++) {
elem = elems[i];
if (elem.className) {
classes = Strings.words(elem.className);
for (j = 0; j < classes.length; j++) {
cls = classes[j];
if (classMap[cls]) {
indexed = index[cls];
if (indexed) {
indexed.push(elem);
} else {
index[cls] = [elem];
}
}
}
}
}
return index;
}
/**
* Indexes descendant elements based on the individual classes in
* the class attribute.
*
* Based on these observations;
*
* * $('.class1, .class2') takes twice as long as $('.class1') on IE7.
*
* * $('.class1, .class2') is fast on IE8 (approx the same as
* $('.class'), no matter how many classes), but if the individual
* elements in the result set should be handled differently, the
* subsequent hasClass('.class1') and hasClass('.class2') calls
* slow things down again.
*
* * DOM traversal with elem.firstChild elem.nextSibling is very
* slow on IE7 compared to just iterating over
* root.getElementsByTagName('*').
*
* * $('name.class') is much faster than just $('.class'), but as
* soon as you need a single class in classMap that may be present
* on any element, that optimization doesn't gain anything since
* then you have to examine every element.
*
* This function will always take approx. the same amount of time
* (on IE7 approx. equivalent to a single call to $('.class')) no
* matter how many entries there are in classMap to index.
*
* This function only makes sense for multiple entries in
* classMap. For a single class lookup, $('.class') or
* $('name.class') is fine (even better in the latter case).
*
* @param root
* The root element to search for elements to index
* (will not be included in search).
* @param classMap
* A map from class name to boolean true.
* @return
* A map from class name to an array of elements with that class.
* Every entry in classMap for which elements have been found
* will have a corresponding entry in the returned
* map. Entries for which no elements have been found, may or
* may not have an entry in the returned map.
*/
function indexByClass(root, classMap) {
var elems;
if (Browser.ie7) {
elems = root.getElementsByTagName('*');
} else {
// Optimize for browsers that support querySelectorAll/getElementsByClassName.
// On IE8 for example, if there is a relatively high
// elems/resultSet ratio, performance can improve by a factor of 2.
elems = $(root).find('.' + Maps.keys(classMap).join(',.'));
}
return indexByClassHaveList(elems, classMap);
}
/**
* Indexes descendant elements based on elem.nodeName.
*
* Based on these observations:
*
* * On IE8, for moderate values of names.length, individual calls to
* getElementsByTagName is just as fast as $root.find('name, name,
* name, name').
*
* * On IE7, $root.find('name, name, name, name') is extemely slow
* (can be an order of magnitude slower than individual calls to
* getElementsByTagName, why is that?).
*
* * Although getElementsByTagName is very fast even on IE7, when
* names.length > 7 an alternative implementation that iterates
* over all tags and checks names from a hashmap (similar to how
* indexByClass does it) may become interesting, but
* names.length > 7 is unlikely.
*
* This function only makes sense if the given names array has many
* entries. For only one or two different names, calling $('name')
* or context.getElementsByTagName(name) directly is fine (but
* beware of $('name, name, ...') as explained above).
*
* The signature of this function differs from indexByClass by not
* taking a map but instead an array of names.
*
* @param root
* The root element to search for elements to index
* (will not be included in search).
* @param names
* An array of element names to look for.
* Names must be in all-uppercase (the same as elem.nodeName).
* @return
* A map from element name to an array of elements with that name.
* Names will be all-uppercase.
* Arrays will be proper arrays, not NodeLists.
* Every entry in classMap for which elements have been found
* will have a corresponding entry in the returned
* map. Entries for which no elements have been found, may or
* may not have an entry in the returned map.
*/
function indexByName(root, names) {
var i,
index = {},
len;
for (i = 0, len = names.length; i < len; i++) {
var name = names[i];
index[name] = $.makeArray(root.getElementsByTagName(name));
}
return index;
}
return {
moveNextAll: moveNextAll,
attrNames: attrNames,
attrs: attrs,
indexByClass: indexByClass,
indexByName: indexByName,
indexByClassHaveList: indexByClassHaveList,
outerHtml: outerHtml
};
});