forked from kriszyp/put-selector
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathput.js
More file actions
196 lines (196 loc) · 7.48 KB
/
put.js
File metadata and controls
196 lines (196 loc) · 7.48 KB
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
(function(define){
var forDocument, fragmentFasterHeuristic = /[-+,> ]/; // if it has any of these combinators, it is probably going to be faster with a document fragment
define([], 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;
try{
var selectorParse = /(?:\s*([-+ ,<>]))?\s*(\.|!\.?|#)?([-\w$]+)?(?:\[([^\]=]+)=?['"]?([^\]'"]*)['"]?\])?/g,
undefined,
doc = doc || document,
ieCreateElement = 1;
put('i', {name:'a'});
}catch(e){
ieCreateElement = 0;
}
function insertTextNode(element, text){
element.appendChild(doc.createTextNode(text));
}
function put(topReferenceElement){
var fragment, returnValue, lastSelectorArg, nextSibling, referenceElement, current,
args = arguments;
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
|| 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.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 + '">';
}
current = 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 == "!"){
// special signal to delete this element
// use the ol' innerHTML trick to get IE to do some cleanup
put("div", current, '<').innerHTML = "";
}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 = args[++i];
}
// [name=value]
if(attrName == "style"){
// handle the special case of setAttribute not working in old IE
current.style.cssText = attrValue;
}else{
current[attrName.charAt(0) == "!" ? (attrName = attrName.substring(1)) && 'removeAttribute' : 'setAttribute'](attrName, attrValue === '' ? 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.defaultTag = "div";
put.forDocument = forDocument;
return put;
});
})(typeof define == "undefined" ? function(deps, factory){
if(typeof window == "undefined"){
// server side JavaScript, probably (hopefully) NodeJS
require("./node-html")(module, factory);
}else{
// plain script in a browser
put = factory();
}
} : define);