-
Notifications
You must be signed in to change notification settings - Fork 39
/
put.js
231 lines (231 loc) · 9.18 KB
/
put.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
(function(localDefine){
var forDocument, fragmentFasterHeuristic = /[-+,> ]/; // if it has any of these combinators, it is probably going to be faster with a document fragment
localDefine([], forDocument = function(doc, newFragmentFasterHeuristic){
"use strict";
// module:
// put-selector/put
// summary:
// This module defines a fast lightweight function for updating and creating new elements
// terse, CSS selector-based syntax. The single function from this module creates
// new DOM elements and updates existing elements. See README.md for more information.
// examples:
// To create a simple div with a class name of "foo":
// | put("div.foo");
fragmentFasterHeuristic = newFragmentFasterHeuristic || fragmentFasterHeuristic;
var selectorParse = /(?:\s*([-+ ,<>]))?\s*(\.|!\.?|#)?([-\w\u00A0-\uFFFF%$|]+)?(?:\[([^\]=]+)=?('(?:\\.|[^'])*'|"(?:\\.|[^"])*"|[^\]]*)\])?/g,
undefined, namespaceIndex, namespaces = false,
doc = doc || document,
ieCreateElement = typeof doc.createElement == "object"; // telltale sign of the old IE behavior with createElement that does not support later addition of name
function insertTextNode(element, text){
element.appendChild(doc.createTextNode(text));
}
function put(topReferenceElement){
var fragment, lastSelectorArg, nextSibling, referenceElement, current,
args = arguments,
returnValue = args[0]; // use the first argument as the default return value in case only an element is passed in
function insertLastElement(){
// we perform insertBefore actions after the element is fully created to work properly with
// <input> tags in older versions of IE that require type attributes
// to be set before it is attached to a parent.
// We also handle top level as a document fragment actions in a complex creation
// are done on a detached DOM which is much faster
// Also if there is a parse error, we generally error out before doing any DOM operations (more atomic)
if(current && referenceElement && current != referenceElement){
(referenceElement == topReferenceElement &&
// top level, may use fragment for faster access
(fragment ||
// fragment doesn't exist yet, check to see if we really want to create it
(fragment = fragmentFasterHeuristic.test(argument) && doc.createDocumentFragment()))
// any of the above fails just use the referenceElement
? fragment : referenceElement).
insertBefore(current, nextSibling || null); // do the actual insertion
}
}
for(var i = 0; i < args.length; i++){
var argument = args[i];
if(typeof argument == "object"){
lastSelectorArg = false;
if(argument instanceof Array){
// an array
current = doc.createDocumentFragment();
for(var key = 0; key < argument.length; key++){
current.appendChild(put(argument[key]));
}
argument = current;
}
if(argument.nodeType){
current = argument;
insertLastElement();
referenceElement = argument;
nextSibling = 0;
}else{
// an object hash
for(var key in argument){
current[key] = argument[key];
}
}
}else if(lastSelectorArg){
// a text node should be created
// take a scalar value, use createTextNode so it is properly escaped
// createTextNode is generally several times faster than doing an escaped innerHTML insertion: http://jsperf.com/createtextnode-vs-innerhtml/2
lastSelectorArg = false;
insertTextNode(current, argument);
}else{
if(i < 1){
// if we are starting with a selector, there is no top element
topReferenceElement = null;
}
lastSelectorArg = true;
var leftoverCharacters = argument.replace(selectorParse, function(t, combinator, prefix, value, attrName, attrValue){
if(combinator){
// insert the last current object
insertLastElement();
if(combinator == '-' || combinator == '+'){
// + or - combinator,
// TODO: add support for >- as a means of indicating before the first child?
referenceElement = (nextSibling = (current || referenceElement)).parentNode;
current = null;
if(combinator == "+"){
nextSibling = nextSibling.nextSibling;
}// else a - operator, again not in CSS, but obvious in it's meaning (create next element before the current/referenceElement)
}else{
if(combinator == "<"){
// parent combinator (not really in CSS, but theorized, and obvious in it's meaning)
referenceElement = current = (current || referenceElement).parentNode;
}else{
if(combinator == ","){
// comma combinator, start a new selector
referenceElement = topReferenceElement;
}else if(current){
// else descendent or child selector (doesn't matter, treated the same),
referenceElement = current;
}
current = null;
}
nextSibling = 0;
}
if(current){
referenceElement = current;
}
}
var tag = !prefix && value;
if(tag || (!current && (prefix || attrName))){
if(tag == "$"){
// this is a variable to be replaced with a text node
insertTextNode(referenceElement, args[++i]);
}else{
// Need to create an element
tag = tag || put.defaultTag;
var ieInputName = ieCreateElement && args[i +1] && args[i +1].name;
if(ieInputName){
// in IE, we have to use the crazy non-standard createElement to create input's that have a name
tag = '<' + tag + ' name="' + ieInputName + '">';
}
// we swtich between creation methods based on namespace usage
current = namespaces && ~(namespaceIndex = tag.indexOf('|')) ?
doc.createElementNS(namespaces[tag.slice(0, namespaceIndex)], tag.slice(namespaceIndex + 1)) :
doc.createElement(tag);
}
}
if(prefix){
if(value == "$"){
value = args[++i];
}
if(prefix == "#"){
// #id was specified
current.id = value;
}else{
// we are in the className addition and removal branch
var currentClassName = current.className;
// remove the className (needed for addition or removal)
// see http://jsperf.com/remove-class-name-algorithm/2 for some tests on this
var removed = currentClassName && (" " + currentClassName + " ").replace(" " + value + " ", " ");
if(prefix == "."){
// addition, add the className
current.className = currentClassName ? (removed + value).substring(1) : value;
}else{
// else a '!' class removal
if(argument == "!"){
var parentNode;
// special signal to delete this element
if(ieCreateElement){
// use the ol' innerHTML trick to get IE to do some cleanup
put("div", current, '<').innerHTML = "";
}else if(parentNode = current.parentNode){ // intentional assigment
// use a faster, and more correct (for namespaced elements) removal (http://jsperf.com/removechild-innerhtml)
parentNode.removeChild(current);
}
}else{
// we already have removed the class, just need to trim
removed = removed.substring(1, removed.length - 1);
// only assign if it changed, this can save a lot of time
if(removed != currentClassName){
current.className = removed;
}
}
}
// CSS class removal
}
}
if(attrName){
if(attrValue && (attrValue.charAt(0) === '"' || attrValue.charAt(0) === "'")) {
// quoted string
attrValue = attrValue.slice(1, -1).replace(/\\/g, '')
}
if(attrValue == "$"){
attrValue = args[++i];
}
// [name=value]
if(attrName == "style"){
// handle the special case of setAttribute not working in old IE
current.style.cssText = attrValue;
}else{
var method = attrName.charAt(0) == "!" ? (attrName = attrName.substring(1)) && 'removeAttribute' : 'setAttribute';
// determine if we need to use a namespace
namespaces && ~(namespaceIndex = attrName.indexOf('|')) ?
current[method + "NS"](namespaces[attrName.slice(0, namespaceIndex)], attrName.slice(namespaceIndex + 1), attrValue) :
current[method](attrName, attrValue);
}
}
return '';
});
if(leftoverCharacters){
throw new SyntaxError("Unexpected char " + leftoverCharacters + " in " + argument);
}
insertLastElement();
referenceElement = returnValue = current || referenceElement;
}
}
if(topReferenceElement && fragment){
// we now insert the top level elements for the fragment if it exists
topReferenceElement.appendChild(fragment);
}
return returnValue;
}
put.addNamespace = function(name, uri){
if(doc.createElementNS){
(namespaces || (namespaces = {}))[name] = uri;
}else{
// for old IE
doc.namespaces.add(name, uri);
}
};
put.defaultTag = "div";
put.forDocument = forDocument;
return put;
});
})(function(id, deps, factory){
factory = factory || deps;
if(typeof define === "function"){
// AMD loader
define([], function(){
return factory();
});
}else if(typeof window == "undefined"){
// server side JavaScript, probably (hopefully) NodeJS
require("./node-html")(module, factory);
}else{
// plain script in a browser
put = factory();
}
});