Comparing version 0.1.0 to 0.2.0
@@ -7,2 +7,40 @@ (function (global, factory) { | ||
function assign(obj, props) { | ||
for (var i in props) obj[i] = props[i]; | ||
} | ||
function toLower(str) { | ||
return String(str).toLowerCase(); | ||
} | ||
function createAttributeFilter(ns, name) { | ||
return function (o) { return o.ns===ns && o.name===toLower(name); }; | ||
} | ||
function splice(arr, item, add, byValueOnly) { | ||
var i = arr ? findWhere(arr, item, true, byValueOnly) : -1; | ||
if (~i) add ? arr.splice(i, 0, add) : arr.splice(i, 1); | ||
return i; | ||
} | ||
function findWhere(arr, fn, returnIndex, byValueOnly) { | ||
var i = arr.length; | ||
while (i--) if (typeof fn==='function' && !byValueOnly ? fn(arr[i]) : arr[i]===fn) break; | ||
return returnIndex ? i : arr[i]; | ||
} | ||
/* | ||
const NODE_TYPES = { | ||
ELEMENT_NODE: 1, | ||
ATTRIBUTE_NODE: 2, | ||
TEXT_NODE: 3, | ||
CDATA_SECTION_NODE: 4, | ||
ENTITY_REFERENCE_NODE: 5, | ||
COMMENT_NODE: 6, | ||
PROCESSING_INSTRUCTION_NODE: 7, | ||
DOCUMENT_NODE: 9 | ||
}; | ||
*/ | ||
/** Create a minimally viable DOM Document | ||
@@ -12,17 +50,2 @@ * @returns {Document} document | ||
function undom() { | ||
var NODE_TYPES = { | ||
ELEMENT_NODE: 1, | ||
ATTRIBUTE_NODE: 2, | ||
TEXT_NODE: 3, | ||
CDATA_SECTION_NODE: 4, | ||
ENTITY_REFERENCE_NODE: 5, | ||
COMMENT_NODE: 6, | ||
PROCESSING_INSTRUCTION_NODE: 7, | ||
DOCUMENT_NODE: 9 | ||
}; | ||
function assign(obj, props) { | ||
for (var i in props) obj[i] = props[i]; | ||
} | ||
var Node = function Node(nodeType, nodeName) { | ||
@@ -34,25 +57,29 @@ this.nodeType = nodeType; | ||
Node.prototype.appendChild = function appendChild (child) { | ||
child.remove(); | ||
child.parentNode = this; | ||
this.childNodes.push(child); | ||
if (this.children && child.nodeType===1) this.children.push(child); | ||
this.childNodes.push(child); | ||
}; | ||
Node.prototype.insertBefore = function insertBefore (child, ref) { | ||
var i = this.childNodes.indexOf(ref); | ||
if (~i) this.childNodes.splice(i, 0, child); | ||
if (this.children && child.nodeType===1) { | ||
while (i<this.childNodes.length && this.childNodes[i].nodeType!==1) i++; | ||
if (i<this.childNodes.length) { | ||
this.children.splice(this.children.indexOf(this.childNodes[i]), 0, child); | ||
} | ||
child.remove(); | ||
var i = splice(this.childNodes, ref, child); | ||
if (!ref) this.appendChild(child); | ||
else if (~i && child.nodeType===1) { | ||
while (i<this.childNodes.length && (ref = this.childNodes[i]).nodeType!==1 || ref===child) i++; | ||
if (ref) splice(this.children, ref, child); | ||
} | ||
}; | ||
Node.prototype.removeChild = function removeChild (child) { | ||
var i = this.childNodes.indexOf(child); | ||
if (~i) this.childNodes.splice(i, 1); | ||
if (this.children && child.nodeType===1) { | ||
i = this.children.indexOf(child); | ||
if (~i) this.children.splice(this.children.indexOf(child), 1); | ||
Node.prototype.replaceChild = function replaceChild (child, ref) { | ||
if (ref.parentNode===this) { | ||
this.insertBefore(child, ref); | ||
ref.remove(); | ||
} | ||
}; | ||
assign(Node, NODE_TYPES); | ||
assign(Node.prototype, NODE_TYPES); | ||
Node.prototype.removeChild = function removeChild (child) { | ||
splice(this.childNodes, child); | ||
if (child.nodeType===1) splice(this.children, child); | ||
}; | ||
Node.prototype.remove = function remove () { | ||
if (this.parentNode) this.parentNode.removeChild(this); | ||
}; | ||
@@ -62,3 +89,3 @@ | ||
function TextNode(text) { | ||
Node.call(this, 3, '#text'); // NODE_TYPES.TEXT_NODE | ||
Node.call(this, 3, '#text'); // TEXT_NODE | ||
this.textContent = this.nodeValue = text; | ||
@@ -77,5 +104,6 @@ } | ||
function Element(nodeType, nodeName) { | ||
Node.call(this, nodeType || 1, nodeName); // NODE_TYPES.ELEMENT_NODE | ||
Node.call(this, nodeType || 1, nodeName); // ELEMENT_NODE | ||
this.attributes = []; | ||
this.children = []; | ||
this.__handlers = {}; | ||
} | ||
@@ -86,2 +114,3 @@ | ||
Element.prototype.constructor = Element; | ||
Element.prototype.setAttribute = function setAttribute (key, value) { | ||
@@ -96,22 +125,33 @@ this.setAttributeNS(null, key, value); | ||
}; | ||
Element.prototype.setAttributeNS = function setAttributeNS (ns, name, value) { | ||
name = String(name).toLowerCase(); | ||
value = String(value); | ||
var attr = this.attributes.filter( function (o) { return o.ns===ns && o.name===name; } )[0]; | ||
if (!attr) this.attributes.push({ ns: ns, name: name, value: value }); | ||
else attr.value = value; | ||
var attr = findWhere(this.attributes, createAttributeFilter(ns, name)); | ||
if (!attr) this.attributes.push(attr = { ns: ns, name: name }); | ||
attr.value = String(value); | ||
}; | ||
Element.prototype.getAttributeNS = function getAttributeNS (ns, name) { | ||
var attr = this.attributes.filter( function (o) { return o.ns===ns && o.name===name; } )[0]; | ||
var attr = findWhere(this.attributes, createAttributeFilter(ns, name)); | ||
return attr && attr.value; | ||
}; | ||
Element.prototype.removeAttributeNS = function removeAttributeNS (ns, name) { | ||
var this$1 = this; | ||
splice(this.attributes, createAttributeFilter(ns, name)); | ||
}; | ||
for (var i=this.attributes.length; i--; ) { | ||
if (this$1.attributes[i].ns===ns && this$1.attributes[i].name===name) { | ||
this$1.attributes.splice(i, 1); | ||
return; | ||
Element.prototype.addEventListener = function addEventListener (type, handler) { | ||
(this.__handlers[toLower(type)] || (this.__handlers[toLower(type)] = [])).push(handler); | ||
}; | ||
Element.prototype.removeEventListener = function removeEventListener (type, handler) { | ||
splice(this.__handlers[toLower(type)], handler, 0, true); | ||
}; | ||
Element.prototype.dispatchEvent = function dispatchEvent (event) { | ||
var t = event.currentTarget = this, | ||
c = event.cancelable, | ||
l, i; | ||
do { | ||
l = t.__handlers[toLower(event.type)]; | ||
if (l) for (i=l.length; i--; ) { | ||
if ((l[i](event)===false || event._end) && c) break; | ||
} | ||
} | ||
} while (event.bubbles && !(c && event._stop) && (event.target=t=t.parentNode)); | ||
return !event.defaultPrevented; | ||
}; | ||
@@ -136,2 +176,18 @@ | ||
var Event = function Event(type, opts) { | ||
this.type = type; | ||
this.bubbles = !!opts.bubbles; | ||
this.cancelable = !!opts.cancelable; | ||
}; | ||
Event.prototype.stopPropagation = function stopPropagation () { | ||
this._stop = true; | ||
}; | ||
Event.prototype.stopImmediatePropagation = function stopImmediatePropagation () { | ||
this._end = this._stop = true; | ||
}; | ||
Event.prototype.preventDefault = function preventDefault () { | ||
this.defaultPrevented = true; | ||
}; | ||
function createElement(type) { | ||
@@ -141,2 +197,3 @@ return new Element(null, String(type).toUpperCase()); | ||
function createElementNS(ns, type) { | ||
@@ -148,2 +205,3 @@ var element = createElement(type); | ||
function createTextNode(text) { | ||
@@ -153,13 +211,12 @@ return new TextNode(text); | ||
function createDocument() { | ||
var document = new Document(); | ||
assign(document, { | ||
Document: Document, Node: Node, TextNode: TextNode, Element: Element, | ||
createElement: createElement, createElementNS: createElementNS, createTextNode: createTextNode | ||
}); | ||
document.documentElement = document; | ||
document.appendChild(document.body = document.createElement('body')); | ||
assign(document, document.defaultView = { document: document, Document: Document, Node: Node, TextNode: TextNode, Element: Element, Event: Event }); | ||
assign(document, { documentElement:document, createElement: createElement, createElementNS: createElementNS, createTextNode: createTextNode }); | ||
document.appendChild(document.body = createElement('body')); | ||
return document; | ||
} | ||
return createDocument(); | ||
@@ -166,0 +223,0 @@ } |
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.undom=e()}(this,function(){function t(){function t(t,e){for(var n in e)t[n]=e[n]}function e(t){return new c(null,String(t).toUpperCase())}function n(t,n){var i=e(n);return i.namespace=t,i}function i(t){return new u(t)}function o(){var o=new h;return t(o,{Document:h,Node:s,TextNode:u,Element:c,createElement:e,createElementNS:n,createTextNode:i}),o.documentElement=o,o.appendChild(o.body=o.createElement("body")),o}var r={ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,COMMENT_NODE:6,PROCESSING_INSTRUCTION_NODE:7,DOCUMENT_NODE:9},s=function(t,e){this.nodeType=t,this.nodeName=e,this.childNodes=[]};s.prototype.appendChild=function(t){this.children&&1===t.nodeType&&this.children.push(t),this.childNodes.push(t)},s.prototype.insertBefore=function(t,e){var n=this.childNodes.indexOf(e);if(~n&&this.childNodes.splice(n,0,t),this.children&&1===t.nodeType){for(;n<this.childNodes.length&&1!==this.childNodes[n].nodeType;)n++;n<this.childNodes.length&&this.children.splice(this.children.indexOf(this.childNodes[n]),0,t)}},s.prototype.removeChild=function(t){var e=this.childNodes.indexOf(t);~e&&this.childNodes.splice(e,1),this.children&&1===t.nodeType&&(e=this.children.indexOf(t),~e&&this.children.splice(this.children.indexOf(t),1))},t(s,r),t(s.prototype,r);var u=function(t){function e(e){t.call(this,3,"#text"),this.textContent=this.nodeValue=e}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(s),c=function(t){function e(e,n){t.call(this,e||1,n),this.attributes=[],this.children=[]}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setAttribute=function(t,e){this.setAttributeNS(null,t,e)},e.prototype.getAttribute=function(t){return this.getAttributeNS(null,t)},e.prototype.removeAttribute=function(t){this.removeAttributeNS(null,t)},e.prototype.setAttributeNS=function(t,e,n){e=String(e).toLowerCase(),n=String(n);var i=this.attributes.filter(function(n){return n.ns===t&&n.name===e})[0];i?i.value=n:this.attributes.push({ns:t,name:e,value:n})},e.prototype.getAttributeNS=function(t,e){var n=this.attributes.filter(function(n){return n.ns===t&&n.name===e})[0];return n&&n.value},e.prototype.removeAttributeNS=function(t,e){for(var n=this,i=this.attributes.length;i--;)if(n.attributes[i].ns===t&&n.attributes[i].name===e)return void n.attributes.splice(i,1)},e}(s),h=function(t){function e(){t.call(this,9,"#document")}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(c);return o()}return t}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.undom=e()}(this,function(){function t(t,e){for(var n in e)t[n]=e[n]}function e(t){return String(t).toLowerCase()}function n(t,n){return function(o){return o.ns===t&&o.name===e(n)}}function o(t,e,n,o){var i=t?r(t,e,!0,o):-1;return~i&&(n?t.splice(i,0,n):t.splice(i,1)),i}function r(t,e,n,o){for(var r=t.length;r--&&("function"!=typeof e||o?t[r]!==e:!e(t[r])););return n?r:t[r]}function i(){function i(t){return new d(null,String(t).toUpperCase())}function u(t,e){var n=i(e);return n.namespace=t,n}function p(t){return new h(t)}function s(){var e=new a;return t(e,e.defaultView={document:e,Document:a,Node:c,TextNode:h,Element:d,Event:l}),t(e,{documentElement:e,createElement:i,createElementNS:u,createTextNode:p}),e.appendChild(e.body=i("body")),e}var c=function(t,e){this.nodeType=t,this.nodeName=e,this.childNodes=[]};c.prototype.appendChild=function(t){t.remove(),t.parentNode=this,this.childNodes.push(t),this.children&&1===t.nodeType&&this.children.push(t)},c.prototype.insertBefore=function(t,e){t.remove();var n=o(this.childNodes,e,t);if(e){if(~n&&1===t.nodeType){for(;n<this.childNodes.length&&1!==(e=this.childNodes[n]).nodeType||e===t;)n++;e&&o(this.children,e,t)}}else this.appendChild(t)},c.prototype.replaceChild=function(t,e){e.parentNode===this&&(this.insertBefore(t,e),e.remove())},c.prototype.removeChild=function(t){o(this.childNodes,t),1===t.nodeType&&o(this.children,t)},c.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)};var h=function(t){function e(e){t.call(this,3,"#text"),this.textContent=this.nodeValue=e}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(c),d=function(t){function i(e,n){t.call(this,e||1,n),this.attributes=[],this.children=[],this.__handlers={}}return t&&(i.__proto__=t),i.prototype=Object.create(t&&t.prototype),i.prototype.constructor=i,i.prototype.setAttribute=function(t,e){this.setAttributeNS(null,t,e)},i.prototype.getAttribute=function(t){return this.getAttributeNS(null,t)},i.prototype.removeAttribute=function(t){this.removeAttributeNS(null,t)},i.prototype.setAttributeNS=function(t,e,o){var i=r(this.attributes,n(t,e));i||this.attributes.push(i={ns:t,name:e}),i.value=String(o)},i.prototype.getAttributeNS=function(t,e){var o=r(this.attributes,n(t,e));return o&&o.value},i.prototype.removeAttributeNS=function(t,e){o(this.attributes,n(t,e))},i.prototype.addEventListener=function(t,n){(this.__handlers[e(t)]||(this.__handlers[e(t)]=[])).push(n)},i.prototype.removeEventListener=function(t,n){o(this.__handlers[e(t)],n,0,!0)},i.prototype.dispatchEvent=function(t){var n,o,r=t.currentTarget=this,i=t.cancelable;do if(n=r.__handlers[e(t.type)])for(o=n.length;o--&&(n[o](t)!==!1&&!t._end||!i););while(t.bubbles&&(!i||!t._stop)&&(t.target=r=r.parentNode));return!t.defaultPrevented},i}(c),a=function(t){function e(){t.call(this,9,"#document")}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(d),l=function(t,e){this.type=t,this.bubbles=!!e.bubbles,this.cancelable=!!e.cancelable};return l.prototype.stopPropagation=function(){this._stop=!0},l.prototype.stopImmediatePropagation=function(){this._end=this._stop=!0},l.prototype.preventDefault=function(){this.defaultPrevented=!0},s()}return i}); | ||
//# sourceMappingURL=undom.min.js.map |
{ | ||
"name": "undom", | ||
"amdName": "undom", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Minimally viable DOM Document implementation.", | ||
@@ -62,4 +62,6 @@ "main": "dist/undom.js", | ||
"rollup-plugin-buble": "^0.12.1", | ||
"sinon": "^1.17.4", | ||
"sinon-chai": "^2.8.0", | ||
"uglify-js": "^2.6.2" | ||
} | ||
} |
@@ -16,2 +16,12 @@ # undom | ||
## Project Goals | ||
Undom aims to find a sweet spot between size/performance and utility. The goal is to provide the simplest possible implementation of a DOM Document, such that libraries relying on the DOM can run in places where there isn't one available. | ||
The intent to keep things as simple as possible means undom lacks some DOM features like HTML parsing & serialization, Web Components, etc. These features can be added through additional libraries. | ||
--- | ||
## Installation | ||
@@ -27,2 +37,17 @@ | ||
## Require Hook | ||
In CommonJS environments, simply import `undom/register` to patch the global object with a singleton Document. | ||
```js | ||
require('undom/register'); | ||
// now you have a DOM. | ||
document.createElement('div'); | ||
``` | ||
--- | ||
## Usage | ||
@@ -40,1 +65,48 @@ | ||
``` | ||
--- | ||
## Recipe: Serialize to HTML | ||
One task `undom` doesn't handle for you by default is HTML serialization. A proper implementation of this would be cumbersome to maintain and would rely heavily on getters and setters, which limits browser support. Below is a simple recipe for serializing an `undom` Element (Document, etc) to HTML. | ||
#### Small & in ES2015: | ||
```js | ||
Element.prototype.toString = el => this.nodeType===3 ? enc(this.textContent) : ( | ||
'<'+this.nodeName.toLowerCase() + this.attributes.map(attr).join('') + '>' + | ||
this.childNodes.map(serialize).join('') + '</'+this.nodeName.toLowerCase()+'>' | ||
); | ||
let attr = a => ` ${a.name}="${enc(a.value)}"`; | ||
let enc = s => s.replace(/[&'"<>]/g, a => `&#${a};`); | ||
``` | ||
#### ES3 Version | ||
This also does pretty-printing. | ||
```js | ||
function serialize(el) { | ||
if (el.nodeType===3) return el.textContent; | ||
var name = String(el.nodeName).toLowerCase(), | ||
str = '<'+name, | ||
c, i; | ||
for (i=0; i<el.attributes.length; i++) { | ||
str += ' '+el.attributes[i].name+'="'+el.attributes[i].value+'"'; | ||
} | ||
str += '>'; | ||
for (i=0; i<el.childNodes.length; i++) { | ||
c = serialize(el.childNodes[i]); | ||
if (c) str += '\n\t'+c.replace(/\n/g,'\n\t'); | ||
} | ||
return str + (c?'\n':'') + '</'+name+'>'; | ||
} | ||
function enc(s) { | ||
return s.replace(/[&'"<>]/g, function(a){ return `&#${a};` }); | ||
} | ||
``` |
144
src/undom.js
@@ -0,2 +1,23 @@ | ||
import { | ||
assign, | ||
toLower, | ||
splice, | ||
findWhere, | ||
createAttributeFilter | ||
} from './util'; | ||
/* | ||
const NODE_TYPES = { | ||
ELEMENT_NODE: 1, | ||
ATTRIBUTE_NODE: 2, | ||
TEXT_NODE: 3, | ||
CDATA_SECTION_NODE: 4, | ||
ENTITY_REFERENCE_NODE: 5, | ||
COMMENT_NODE: 6, | ||
PROCESSING_INSTRUCTION_NODE: 7, | ||
DOCUMENT_NODE: 9 | ||
}; | ||
*/ | ||
/** Create a minimally viable DOM Document | ||
@@ -6,17 +27,2 @@ * @returns {Document} document | ||
export default function undom() { | ||
const NODE_TYPES = { | ||
ELEMENT_NODE: 1, | ||
ATTRIBUTE_NODE: 2, | ||
TEXT_NODE: 3, | ||
CDATA_SECTION_NODE: 4, | ||
ENTITY_REFERENCE_NODE: 5, | ||
COMMENT_NODE: 6, | ||
PROCESSING_INSTRUCTION_NODE: 7, | ||
DOCUMENT_NODE: 9 | ||
}; | ||
function assign(obj, props) { | ||
for (let i in props) obj[i] = props[i]; | ||
} | ||
class Node { | ||
@@ -29,26 +35,30 @@ constructor(nodeType, nodeName) { | ||
appendChild(child) { | ||
child.remove(); | ||
child.parentNode = this; | ||
this.childNodes.push(child); | ||
if (this.children && child.nodeType===1) this.children.push(child); | ||
this.childNodes.push(child); | ||
} | ||
insertBefore(child, ref) { | ||
let i = this.childNodes.indexOf(ref); | ||
if (~i) this.childNodes.splice(i, 0, child); | ||
if (this.children && child.nodeType===1) { | ||
while (i<this.childNodes.length && this.childNodes[i].nodeType!==1) i++; | ||
if (i<this.childNodes.length) { | ||
this.children.splice(this.children.indexOf(this.childNodes[i]), 0, child); | ||
} | ||
child.remove(); | ||
let i = splice(this.childNodes, ref, child); | ||
if (!ref) this.appendChild(child); | ||
else if (~i && child.nodeType===1) { | ||
while (i<this.childNodes.length && (ref = this.childNodes[i]).nodeType!==1 || ref===child) i++; | ||
if (ref) splice(this.children, ref, child); | ||
} | ||
} | ||
removeChild(child) { | ||
let i = this.childNodes.indexOf(child); | ||
if (~i) this.childNodes.splice(i, 1); | ||
if (this.children && child.nodeType===1) { | ||
i = this.children.indexOf(child); | ||
if (~i) this.children.splice(this.children.indexOf(child), 1); | ||
replaceChild(child, ref) { | ||
if (ref.parentNode===this) { | ||
this.insertBefore(child, ref); | ||
ref.remove(); | ||
} | ||
} | ||
removeChild(child) { | ||
splice(this.childNodes, child); | ||
if (child.nodeType===1) splice(this.children, child); | ||
} | ||
remove() { | ||
if (this.parentNode) this.parentNode.removeChild(this); | ||
} | ||
} | ||
assign(Node, NODE_TYPES); | ||
assign(Node.prototype, NODE_TYPES); | ||
@@ -58,3 +68,3 @@ | ||
constructor(text) { | ||
super(3, '#text'); // NODE_TYPES.TEXT_NODE | ||
super(3, '#text'); // TEXT_NODE | ||
this.textContent = this.nodeValue = text; | ||
@@ -67,6 +77,8 @@ } | ||
constructor(nodeType, nodeName) { | ||
super(nodeType || 1, nodeName); // NODE_TYPES.ELEMENT_NODE | ||
super(nodeType || 1, nodeName); // ELEMENT_NODE | ||
this.attributes = []; | ||
this.children = []; | ||
this.__handlers = {}; | ||
} | ||
setAttribute(key, value) { | ||
@@ -81,20 +93,33 @@ this.setAttributeNS(null, key, value); | ||
} | ||
setAttributeNS(ns, name, value) { | ||
name = String(name).toLowerCase(); | ||
value = String(value); | ||
let attr = this.attributes.filter( o => o.ns===ns && o.name===name )[0]; | ||
if (!attr) this.attributes.push({ ns, name, value }); | ||
else attr.value = value; | ||
let attr = findWhere(this.attributes, createAttributeFilter(ns, name)); | ||
if (!attr) this.attributes.push(attr = { ns, name }); | ||
attr.value = String(value); | ||
} | ||
getAttributeNS(ns, name) { | ||
let attr = this.attributes.filter( o => o.ns===ns && o.name===name )[0]; | ||
let attr = findWhere(this.attributes, createAttributeFilter(ns, name)); | ||
return attr && attr.value; | ||
} | ||
removeAttributeNS(ns, name) { | ||
for (let i=this.attributes.length; i--; ) { | ||
if (this.attributes[i].ns===ns && this.attributes[i].name===name) { | ||
this.attributes.splice(i, 1); | ||
return; | ||
splice(this.attributes, createAttributeFilter(ns, name)); | ||
} | ||
addEventListener(type, handler) { | ||
(this.__handlers[toLower(type)] || (this.__handlers[toLower(type)] = [])).push(handler); | ||
} | ||
removeEventListener(type, handler) { | ||
splice(this.__handlers[toLower(type)], handler, 0, true); | ||
} | ||
dispatchEvent(event) { | ||
let t = event.currentTarget = this, | ||
c = event.cancelable, | ||
l, i; | ||
do { | ||
l = t.__handlers[toLower(event.type)]; | ||
if (l) for (i=l.length; i--; ) { | ||
if ((l[i](event)===false || event._end) && c) break; | ||
} | ||
} | ||
} while (event.bubbles && !(c && event._stop) && (event.target=t=t.parentNode)); | ||
return !event.defaultPrevented; | ||
} | ||
@@ -111,2 +136,20 @@ } | ||
class Event { | ||
constructor(type, opts) { | ||
this.type = type; | ||
this.bubbles = !!opts.bubbles; | ||
this.cancelable = !!opts.cancelable; | ||
} | ||
stopPropagation() { | ||
this._stop = true; | ||
} | ||
stopImmediatePropagation() { | ||
this._end = this._stop = true; | ||
} | ||
preventDefault() { | ||
this.defaultPrevented = true; | ||
} | ||
} | ||
function createElement(type) { | ||
@@ -116,2 +159,3 @@ return new Element(null, String(type).toUpperCase()); | ||
function createElementNS(ns, type) { | ||
@@ -123,2 +167,3 @@ let element = createElement(type); | ||
function createTextNode(text) { | ||
@@ -128,14 +173,13 @@ return new TextNode(text); | ||
function createDocument() { | ||
let document = new Document(); | ||
assign(document, { | ||
Document, Node, TextNode, Element, | ||
createElement, createElementNS, createTextNode | ||
}); | ||
document.documentElement = document; | ||
document.appendChild(document.body = document.createElement('body')); | ||
assign(document, document.defaultView = { document, Document, Node, TextNode, Element, Event }); | ||
assign(document, { documentElement:document, createElement, createElementNS, createTextNode }); | ||
document.appendChild(document.body = createElement('body')); | ||
return document; | ||
} | ||
return createDocument(); | ||
} |
import undom from '../src/undom'; | ||
import { expect } from 'chai'; | ||
import chai, { expect } from 'chai'; | ||
import { spy } from 'sinon'; | ||
import sinonChai from 'sinon-chai'; | ||
chai.use(sinonChai); | ||
@@ -35,2 +38,62 @@ describe('undom', () => { | ||
describe('#appendChild', () => { | ||
it('should set parentNode', () => { | ||
let child = document.createElement('span'); | ||
let parent = document.createElement('div'); | ||
parent.appendChild(child); | ||
expect(child).to.have.property('parentNode', parent); | ||
}); | ||
it('should insert into .children / .childNodes', () => { | ||
let child = document.createElement('span'); | ||
let parent = document.createElement('div'); | ||
parent.appendChild(child); | ||
expect(parent).to.have.property('childNodes').that.deep.equals([child]); | ||
expect(parent).to.have.property('children').that.deep.equals([child]); | ||
expect(child).to.have.property('parentNode', parent); | ||
parent.appendChild(child); | ||
expect(parent, 're-append').to.have.property('childNodes').that.deep.equals([child]); | ||
expect(parent, 're-append').to.have.property('children').that.deep.equals([child]); | ||
expect(child, 're-append').to.have.property('parentNode', parent); | ||
}); | ||
it('should remove child from any current parentNode', () => { | ||
let child = document.createElement('span'); | ||
let parent1 = document.createElement('div'); | ||
let parent2 = document.createElement('div'); | ||
parent1.appendChild(child); | ||
expect(parent1.childNodes).to.eql([child]); | ||
expect(child).to.have.property('parentNode', parent1); | ||
parent2.appendChild(child); | ||
expect(child, 'switch parentNode').to.have.property('parentNode', parent2); | ||
expect(parent1.childNodes, 'old parent').to.eql([]); | ||
expect(parent2.childNodes, 'new parent').to.eql([child]); | ||
}); | ||
}); | ||
describe('#replaceChild()', () => { | ||
it('should replace a child', () => { | ||
let parent = document.createElement('div'); | ||
let child1 = document.createElement('child1'); | ||
let child2 = document.createElement('child2'); | ||
let child3 = document.createElement('child3'); | ||
let child4 = document.createElement('child4'); | ||
parent.appendChild(child1); | ||
parent.appendChild(child2); | ||
parent.appendChild(child3); | ||
expect(parent).to.have.property('childNodes').eql([child1, child2, child3]); | ||
expect(parent).to.have.property('children').eql([child1, child2, child3]); | ||
parent.replaceChild(child4, child2); | ||
expect(parent).to.have.property('childNodes').eql([child1, child4, child3]); | ||
expect(parent).to.have.property('children').eql([child1, child4, child3]); | ||
}); | ||
}); | ||
describe('#setAttribute()', () => { | ||
@@ -71,3 +134,122 @@ it('should set a given named attribute', () => { | ||
}); | ||
describe('#addEventListener', () => { | ||
it('should append listener to event list', () => { | ||
let el = document.createElement('div'); | ||
expect(el.__handlers).to.eql({}); | ||
let fn = () => {}; | ||
el.addEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [fn] }); | ||
}); | ||
it('should allow duplicates', () => { | ||
let el = document.createElement('div'); | ||
let fn = () => {}; | ||
el.addEventListener('type', fn); | ||
el.addEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [fn, fn] }); | ||
}); | ||
it('should normalize type', () => { | ||
let el = document.createElement('div'); | ||
let fn = () => {}; | ||
el.addEventListener('TYPE', fn); | ||
el.addEventListener('TyPe', fn); | ||
el.addEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [fn, fn, fn] }); | ||
}); | ||
}); | ||
describe('#removeEventListener', () => { | ||
it('should remove existing listeners', () => { | ||
let el = document.createElement('div'); | ||
let fn = () => {}; | ||
el.addEventListener('type', fn); | ||
el.removeEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [] }); | ||
}); | ||
it('should normalize type', () => { | ||
let el = document.createElement('div'); | ||
let fn = () => {}; | ||
el.addEventListener('type', fn); | ||
el.addEventListener('type', fn); | ||
el.addEventListener('type', fn); | ||
el.removeEventListener('TYPE', fn); | ||
el.removeEventListener('TyPe', fn); | ||
el.removeEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [] }); | ||
}); | ||
it('should remove only one listener at a time', () => { | ||
let el = document.createElement('div'); | ||
let fn = () => {}; | ||
el.addEventListener('type', fn); | ||
el.addEventListener('type', fn); | ||
el.removeEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [fn] }); | ||
el.removeEventListener('type', fn); | ||
expect(el.__handlers).to.eql({ type: [] }); | ||
}); | ||
}); | ||
describe('#dispatchEvent()', () => { | ||
it('should invoke matched listener', () => { | ||
let event = { type:'foo', cancelable:true, bubbles:true }; | ||
let el = document.createElement('div'); | ||
let fn = spy(); | ||
let fn2 = spy(); | ||
el.addEventListener('foo', fn); | ||
el.addEventListener('bar', fn2); | ||
el.dispatchEvent(event); | ||
expect(fn).to.have.been.calledOnce; | ||
expect(fn2).not.to.have.been.called; | ||
}); | ||
it('should invoke multiple listeners', () => { | ||
let event = { type:'foo', cancelable:true, bubbles:true }; | ||
let el = document.createElement('div'); | ||
let fn = spy(); | ||
el.addEventListener('foo', fn); | ||
el.addEventListener('foo', fn); | ||
el.addEventListener('foo', fn); | ||
el.dispatchEvent(event); | ||
expect(fn).to.have.been.calledThrice; | ||
}); | ||
it('should bubble if enabled', () => { | ||
let event = { type:'foo', cancelable:true, bubbles:true }; | ||
let child = document.createElement('div'); | ||
let parent = document.createElement('div'); | ||
parent.appendChild(child); | ||
child.addEventListener('foo', child.fn = spy()); | ||
parent.addEventListener('foo', parent.fn = spy()); | ||
child.dispatchEvent(event); | ||
expect(child.fn).to.have.been.calledOnce; | ||
expect(parent.fn).to.have.been.calledOnce; | ||
child.fn.reset(); | ||
parent.fn.reset(); | ||
parent.dispatchEvent(event); | ||
expect(child.fn).not.to.have.been.called; | ||
child.fn.reset(); | ||
parent.fn.reset(); | ||
event.bubbles = false; | ||
child.addEventListener('foo', e => e._stop = true ); | ||
child.dispatchEvent(event); | ||
expect(parent.fn).not.to.have.been.called; | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47511
16
666
110
22