UNPKG

10.5 kBJavaScriptView Raw
1
2
3/**
4 * @version 0.3.1
5 * @date 2015-02-02
6 * @stability 2 - Unstable
7 * @author Lauri Rooden <lauri@rooden.ee>
8 * @license MIT License
9 */
10
11
12var hasOwn = Object.prototype.hasOwnProperty
13
14function extend(obj, _super, extras) {
15 obj.prototype = Object.create(_super.prototype)
16 for (var key in extras) {
17 obj.prototype[key] = extras[key]
18 }
19 obj.prototype.constructor = obj
20}
21
22function StyleMap(style) {
23 var styleMap = this
24 if (style) style.split(/\s*;\s*/g).map(function(val) {
25 val = val.split(/\s*:\s*/)
26 if(val[1]) styleMap[val[0]] = val[1]
27 })
28}
29
30StyleMap.prototype.valueOf = function() {
31 var styleMap = this
32 return Object.keys(styleMap).map(function(key) {
33 return key + ": " + styleMap[key]
34 }).join("; ")
35}
36
37function Node(){}
38
39function getSibling(node, step) {
40 var silbings = node.parentNode && node.parentNode.childNodes
41 , index = silbings && silbings.indexOf(node)
42
43 return silbings && index > -1 && silbings[ index + step ] || null
44}
45
46Node.prototype = {
47 nodeName: null,
48 parentNode: null,
49 ownerDocument: null,
50 childNodes: null,
51 get nodeValue() {
52 return this.nodeType === 3 || this.nodeType === 8 ? this.data : null
53 },
54 set nodeValue(text) {
55 return this.nodeType === 3 || this.nodeType === 8 ? (this.data = text) : null
56 },
57 get textContent() {
58 return this.hasChildNodes() ? this.childNodes.map(function(child) {
59 return child[ child.nodeType == 3 ? "data" : "textContent" ]
60 }).join("") : this.nodeType === 3 ? this.data : ""
61 },
62 set textContent(text) {
63 if (this.nodeType === 3) return (this.data = text)
64 for (var node = this; node.firstChild;) node.removeChild(node.firstChild)
65 node.appendChild(node.ownerDocument.createTextNode(text))
66 },
67 get firstChild() {
68 return this.childNodes && this.childNodes[0] || null
69 },
70 get lastChild() {
71 return this.childNodes && this.childNodes[ this.childNodes.length - 1 ] || null
72 },
73 get previousSibling() {
74 return getSibling(this, -1)
75 },
76 get nextSibling() {
77 return getSibling(this, 1)
78 },
79 get innerHTML() {
80 return Node.prototype.toString.call(this)
81 },
82 get outerHTML() {
83 return this.toString()
84 },
85 get htmlFor() {
86 return this["for"]
87 },
88 set htmlFor(value) {
89 this["for"] = value
90 },
91 get className() {
92 return this["class"] || ""
93 },
94 set className(value) {
95 this["class"] = value
96 },
97 get style() {
98 return this.styleMap || (this.styleMap = new StyleMap())
99 },
100 set style(value) {
101 this.styleMap = new StyleMap(value)
102 },
103 hasChildNodes: function() {
104 return this.childNodes && this.childNodes.length > 0
105 },
106 appendChild: function(el) {
107 return this.insertBefore(el)
108 },
109 insertBefore: function(el, ref) {
110 var node = this
111 , childs = node.childNodes
112
113 if (el.nodeType == 11) {
114 while (el.firstChild) node.insertBefore(el.firstChild, ref)
115 } else {
116 if (el.parentNode) el.parentNode.removeChild(el)
117 el.parentNode = node
118
119 // If ref is null, insert el at the end of the list of children.
120 childs.splice(ref ? childs.indexOf(ref) : childs.length, 0, el)
121 }
122 return el
123 },
124 removeChild: function(el) {
125 var node = this
126 , index = node.childNodes.indexOf(el)
127 if (index == -1) throw new Error("NOT_FOUND_ERR")
128
129 node.childNodes.splice(index, 1)
130 el.parentNode = null
131 return el
132 },
133 replaceChild: function(el, ref) {
134 this.insertBefore(el, ref)
135 return this.removeChild(ref)
136 },
137 cloneNode: function(deep) {
138 var key
139 , node = this
140 , clone = new node.constructor(node.tagName || node.data)
141 clone.ownerDocument = node.ownerDocument
142
143 if (node.hasAttribute) {
144 for (key in node) if (node.hasAttribute(key)) clone[key] = node[key].valueOf()
145 }
146
147 if (deep && node.hasChildNodes()) {
148 node.childNodes.forEach(function(child) {
149 clone.appendChild(child.cloneNode(deep))
150 })
151 }
152 return clone
153 },
154 toString: function() {
155 return this.hasChildNodes() ? this.childNodes.reduce(function(memo, node) {
156 return memo + node
157 }, "") : ""
158 }
159}
160
161
162function DocumentFragment() {
163 this.childNodes = []
164}
165
166extend(DocumentFragment, Node, {
167 nodeType: 11,
168 nodeName: "#document-fragment"
169})
170
171function Attribute(node, name) {
172 this.name = name.toLowerCase()
173
174 Object.defineProperty(this, "value", {
175 get: function() {return node.getAttribute(name)},
176 set: function(val) {node.setAttribute(name, val)}
177 })
178}
179Attribute.prototype.toString = function() {
180 if (!this.value) return this.name
181 // jshint -W108
182 return this.name + '="' + this.value.replace(/&/g, "&amp;").replace(/"/g, "&quot;") + '"'
183}
184
185function HTMLElement(tag) {
186 var element = this
187 element.nodeName = element.tagName = tag.toUpperCase()
188 element.localName = tag.toLowerCase()
189 element.childNodes = []
190}
191
192function findEl(node, sel, first) {
193 var el
194 , i = 0
195 , out = []
196 , els = node.getElementsByTagName("*")
197 , fn = selectorFn(sel.split(/\s*,\s*/).map(selectorFnStr).join("||"))
198
199 for (; (el = els[i++]); ) if (fn(el)) {
200 if (first) return el
201 out.push(el)
202 }
203 return first ? null : out
204}
205
206/*
207* Void elements:
208* http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
209*/
210var voidElements = {
211 AREA:1, BASE:1, BR:1, COL:1, EMBED:1, HR:1, IMG:1, INPUT:1,
212 KEYGEN:1, LINK:1, MENUITEM:1, META:1, PARAM:1, SOURCE:1, TRACK:1, WBR:1
213}
214, pseudoClasses = {
215 "empty": "!_.hasChildNodes()",
216 "first-child": "_.parentNode&&_.parentNode.firstChild==_",
217 "last-child" : "_.parentNode&&_.parentNode.lastChild==_",
218 "link": "_.nodeName=='A'&&_.getAttribute('href')"
219}
220, selectorRe = /([.#:[])([-\w]+)(?:=((["'\/])(?:\\?.)*?\4|[-\w]+)]])?/g
221, lastSelectorRe = /(\s*[>+]?\s*)((["'\/])(?:\\?.)*?\2|[^\s+>])+$/
222, fnCache = {}
223
224function escapeAttributeName(name) {
225 name = name.toLowerCase()
226 if (name === "constructor" || name === "attributes") return name.toUpperCase()
227 return name
228}
229
230function selectorFnStr(sel) {
231 var rules = ["_"]
232 , tag = sel.replace(selectorRe, function(_, op, key, val, quotation) {
233 if (quotation) val = val.slice(1, -1)
234 rules.push(
235 op == "." ? "(' '+_.className+' ').indexOf(' " + key + " ')>-1" :
236 op == "#" ? "_.id=='" + key + "'" :
237 op == ":" && pseudoClasses[key] ||
238 "_.getAttribute('" + key + "')" + (val ? "=='" + val.replace(/'/g, "\\'") + "'" : "")
239 )
240 return ""
241 })
242
243 if (tag && tag != "*") rules.unshift("_.nodeName=='" + tag.toUpperCase() + "'")
244 return rules.join("&&")
245}
246
247function selectorFn(str) {
248 // jshint evil:true
249 return fnCache[str] ||
250 (fnCache[str] = Function("_", "return " + str))
251}
252
253extend(HTMLElement, Node, {
254 matches: function(sel) {
255 var relation, from
256 , parentSel = sel.replace(lastSelectorRe, function(_, _rel, a, b, start) {
257 from = start + _rel.length
258 relation = _rel.trim()
259 return ""
260 })
261 , next = relation == ">" ? this.parentNode : relation == "+" ? this.previousSibling : this
262 , fn = selectorFn(selectorFnStr(sel.slice(from)))
263
264 if (!fn(this)) return false
265
266 if (parentSel) {
267 if (!relation) return !!(next.parentNode && next.parentNode.closest && next.parentNode.closest(parentSel))
268 return next && next.matches && next.matches(parentSel) || false
269 }
270 return true
271 },
272 closest: function(sel) {
273 for (var el = this; el; el = el.parentNode) if (el.matches && el.matches(sel)) return el
274 return null
275 },
276 namespaceURI: "http://www.w3.org/1999/xhtml",
277 nodeType: 1,
278 localName: null,
279 tagName: null,
280 styleMap: null,
281 hasAttribute: function(name) {
282 name = escapeAttributeName(name)
283 return name == "style" && !!this.style.valueOf() || hasOwn.call(this, name)
284 },
285 getAttribute: function(name) {
286 name = escapeAttributeName(name)
287 return this.hasAttribute(name) ? "" + this[name] : null
288 },
289 setAttribute: function(name, value) {
290 name = escapeAttributeName(name)
291 this[name] = "" + value
292 },
293 removeAttribute: function(name) {
294 name = escapeAttributeName(name)
295 this[name] = ""
296 delete this[name]
297 },
298 getElementById: function(id) {
299 if (this.id == id) return this
300 for (var el, found, i = 0; !found && (el = this.childNodes[i++]);) {
301 if (el.nodeType == 1) found = el.getElementById(id)
302 }
303 return found || null
304 },
305 getElementsByTagName: function(tag) {
306 var el, els = [], next = this.firstChild
307 tag = tag === "*" ? 1 : tag.toUpperCase()
308 for (var i = 0, key = tag === 1 ? "nodeType" : "nodeName"; (el = next); ) {
309 if (el[key] === tag) els[i++] = el
310 next = el.firstChild || el.nextSibling
311 while (!next && ((el = el.parentNode) !== this)) next = el.nextSibling
312 }
313 return els
314 },
315 querySelector: function(sel) {
316 return findEl(this, sel, 1)
317 },
318 querySelectorAll: function(sel) {
319 return findEl(this, sel)
320 },
321 toString: function() {
322 var attrs = this.attributes.join(" ")
323 return "<" + this.localName + (attrs ? " " + attrs : "") + ">" +
324 (voidElements[this.tagName] ? "" : this.innerHTML + "</" + this.localName + ">")
325 }
326})
327
328Object.defineProperty(HTMLElement.prototype, "attributes", {
329 get: function() {
330 var key
331 , attrs = []
332 , element = this
333 for (key in element) if (key === escapeAttributeName(key) && element.hasAttribute(key))
334 attrs.push(new Attribute(element, escapeAttributeName(key)))
335 return attrs
336 }
337})
338
339function ElementNS(namespace, tag) {
340 var element = this
341 element.namespaceURI = namespace
342 element.nodeName = element.tagName = element.localName = tag
343 element.childNodes = []
344}
345
346ElementNS.prototype = HTMLElement.prototype
347
348function Text(data) {
349 this.data = data
350}
351
352extend(Text, Node, {
353 nodeType: 3,
354 nodeName: "#text",
355 toString: function() {
356 return ("" + this.data).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
357 }
358})
359
360function Comment(data) {
361 this.data = data
362}
363
364extend(Comment, Node, {
365 nodeType: 8,
366 nodeName: "#comment",
367 toString: function() {
368 return "<!--" + this.data + "-->"
369 }
370})
371
372function Document() {
373 this.childNodes = []
374 this.documentElement = this.createElement("html")
375 this.appendChild(this.documentElement)
376 this.body = this.createElement("body")
377 this.documentElement.appendChild(this.body)
378}
379
380function own(Element) {
381 return function($1, $2) {
382 var node = new Element($1, $2)
383 node.ownerDocument = this
384 return node
385 }
386}
387
388extend(Document, Node, {
389 nodeType: 9,
390 nodeName: "#document",
391 createElement: own(HTMLElement),
392 createElementNS: own(ElementNS),
393 createTextNode: own(Text),
394 createComment: own(Comment),
395 createDocumentFragment: own(DocumentFragment),
396 getElementById: HTMLElement.prototype.getElementById,
397 getElementsByTagName: HTMLElement.prototype.getElementsByTagName,
398 querySelector: HTMLElement.prototype.querySelector,
399 querySelectorAll: HTMLElement.prototype.querySelectorAll
400})
401
402module.exports = {
403 document: new Document(),
404 Document: Document,
405 HTMLElement: HTMLElement
406}
407