dom-serialize
Advanced tools
Comparing version 1.1.0 to 1.2.0
1.2.0 / 2015-02-02 | ||
================== | ||
* add support for one-time "serialize" callback functions | ||
* add support for a "context" argument | ||
* index: make `serializeDoctype()` more readable | ||
* README: fix typo in example output | ||
* README: better description | ||
1.1.0 / 2015-01-16 | ||
@@ -3,0 +12,0 @@ ================== |
147
index.js
@@ -30,3 +30,5 @@ | ||
* | ||
* @param {Node} node | ||
* @param {Node} node - DOM Node to serialize | ||
* @param {String} [context] - optional arbitrary "context" string to use (useful for event listeners) | ||
* @param {Function} [fn] - optional callback function to use in the "serialize" event for this call | ||
* return {String} | ||
@@ -36,4 +38,16 @@ * @public | ||
function serialize (node) { | ||
function serialize (node, context, fn) { | ||
if (!node) return ''; | ||
if ('function' === typeof context) { | ||
fn = context; | ||
context = null; | ||
} | ||
if (!context) context = null; | ||
if ('function' === typeof fn) { | ||
// one-time "serialize" event listener | ||
node.addEventListener('serialize', fn, false); | ||
} | ||
var rtn; | ||
var nodeType = node.nodeType; | ||
@@ -43,50 +57,65 @@ | ||
// assume it's a NodeList or Array of Nodes | ||
return exports.serializeNodeList(node); | ||
} | ||
rtn = exports.serializeNodeList(node, context); | ||
} else { | ||
// emit a custom "serialize" event on `node`, in case there | ||
// are event listeners for custom serialization of this node | ||
var e = new CustomEvent('serialize', { | ||
bubbles: true, | ||
cancelable: true, | ||
detail: { serialize: null } | ||
}); | ||
// emit a custom "serialize" event on `node`, in case there | ||
// are event listeners for custom serialization of this node | ||
var e = new CustomEvent('serialize', { | ||
bubbles: true, | ||
cancelable: true, | ||
detail: { | ||
serialize: null, | ||
context: context | ||
} | ||
}); | ||
var cancelled = !node.dispatchEvent(e); | ||
if (cancelled) return ''; | ||
if (node.dispatchEvent(e)) { | ||
// `e.detail.serialize` can be set to a: | ||
// String - returned directly | ||
// Node - goes through serializer logic instead of `node` | ||
// Anything else - get Stringified first, and then returned directly | ||
if (e.detail.serialize != null) { | ||
if ('string' === typeof e.detail.serialize) { | ||
return e.detail.serialize; | ||
} else if ('number' === typeof e.detail.serialize.nodeType) { | ||
// make it go through the serialization logic | ||
return serialize(e.detail.serialize); | ||
} else { | ||
return String(e.detail.serialize); | ||
// `e.detail.serialize` can be set to a: | ||
// String - returned directly | ||
// Node - goes through serializer logic instead of `node` | ||
// Anything else - get Stringified first, and then returned directly | ||
if (e.detail.serialize != null) { | ||
if ('string' === typeof e.detail.serialize) { | ||
rtn = e.detail.serialize; | ||
} else if ('number' === typeof e.detail.serialize.nodeType) { | ||
// make it go through the serialization logic | ||
rtn = serialize(e.detail.serialize, context); | ||
} else { | ||
rtn = String(e.detail.serialize); | ||
} | ||
} else { | ||
// default serialization logic | ||
switch (nodeType) { | ||
case 1 /* element */: | ||
rtn = exports.serializeElement(node, context); | ||
break; | ||
case 2 /* attribute */: | ||
rtn = exports.serializeAttribute(node); | ||
break; | ||
case 3 /* text */: | ||
rtn = exports.serializeText(node); | ||
break; | ||
case 8 /* comment */: | ||
rtn = exports.serializeComment(node); | ||
break; | ||
case 9 /* document */: | ||
rtn = exports.serializeDocument(node, context); | ||
break; | ||
case 10 /* doctype */: | ||
rtn = exports.serializeDoctype(node); | ||
break; | ||
case 11 /* document fragment */: | ||
rtn = exports.serializeDocumentFragment(node, context); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
// default serialization logic | ||
switch (nodeType) { | ||
case 1 /* element */: | ||
return exports.serializeElement(node); | ||
case 2 /* attribute */: | ||
return exports.serializeAttribute(node); | ||
case 3 /* text */: | ||
return exports.serializeText(node); | ||
case 8 /* comment */: | ||
return exports.serializeComment(node); | ||
case 9 /* document */: | ||
return exports.serializeDocument(node); | ||
case 10 /* doctype */: | ||
return exports.serializeDoctype(node); | ||
case 11 /* document fragment */: | ||
return exports.serializeDocumentFragment(node); | ||
if ('function' === typeof fn) { | ||
node.removeEventListener('serialize', fn, false); | ||
} | ||
return ''; | ||
return rtn || ''; | ||
} | ||
@@ -108,3 +137,3 @@ | ||
function serializeElement (node) { | ||
function serializeElement (node, context) { | ||
var c, i, l; | ||
@@ -124,3 +153,3 @@ var name = node.nodeName.toLowerCase(); | ||
// child nodes | ||
r += exports.serializeNodeList(node.childNodes); | ||
r += exports.serializeNodeList(node.childNodes, context); | ||
@@ -158,4 +187,4 @@ // closing tag, only for non-void elements | ||
function serializeDocument (node) { | ||
return exports.serializeNodeList(node.childNodes); | ||
function serializeDocument (node, context) { | ||
return exports.serializeNodeList(node.childNodes, context); | ||
} | ||
@@ -169,8 +198,14 @@ | ||
function serializeDoctype (node) { | ||
return '<!DOCTYPE ' | ||
+ node.name | ||
+ (node.publicId ? ' PUBLIC "' + node.publicId + '"' : '') | ||
+ (!node.publicId && node.systemId ? ' SYSTEM' : '') | ||
+ (node.systemId ? ' "' + node.systemId + '"' : '') | ||
+ '>'; | ||
var r = '<!DOCTYPE ' + node.name; | ||
if (node.publicId) { | ||
r += ' PUBLIC "' + node.publicId + '"'; | ||
} | ||
if (!node.publicId && node.systemId) { | ||
r += ' SYSTEM'; | ||
} | ||
if (node.systemId) { | ||
r += ' "' + node.systemId + '"'; | ||
} | ||
r += '>'; | ||
return r; | ||
} | ||
@@ -182,4 +217,4 @@ | ||
function serializeDocumentFragment (node) { | ||
return exports.serializeNodeList(node.childNodes); | ||
function serializeDocumentFragment (node, context) { | ||
return exports.serializeNodeList(node.childNodes, context); | ||
} | ||
@@ -191,8 +226,8 @@ | ||
function serializeNodeList (list) { | ||
function serializeNodeList (list, context, fn) { | ||
var r = ''; | ||
for (var i = 0, l = list.length; i < l; i++) { | ||
r += serialize(list[i]); | ||
r += serialize(list[i], context, fn); | ||
} | ||
return r; | ||
} |
{ | ||
"name": "dom-serialize", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Serializes any DOM node into a String", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -9,9 +9,18 @@ dom-serialize | ||
Works with Text nodes, DOM elements, etc. | ||
It's like `outerHTML`, but it works with: | ||
Dispatches a custom "serialize" event on every `Node` which event listeners | ||
can override the default behavior on by setting the `event.detail.serialize` | ||
property to a String or other Node. | ||
* DOM elements | ||
* Text nodes | ||
* Attributes | ||
* Comment nodes | ||
* Documents | ||
* DocumentFragments | ||
* Doctypes | ||
* NodeLists / Arrays | ||
For custom serialization logic, a "serialize" event is dispatched on | ||
every `Node` which event listeners can override the default behavior on by | ||
setting the `event.detail.serialize` property to a String or other Node. | ||
Installation | ||
@@ -50,5 +59,5 @@ ------------ | ||
``` | ||
<foo> & <bar> | ||
foo & <bar> | ||
<body><strong>hello</strong></body> | ||
<body>pwn</body> | ||
``` |
199
test/test.js
@@ -6,5 +6,20 @@ | ||
describe('node-serialize', function () { | ||
var node; | ||
afterEach(function () { | ||
if (node) { | ||
// clean up... | ||
if (node.parentNode) { | ||
node.parentNode.removeChild(node); | ||
} | ||
node = null; | ||
} | ||
}); | ||
it('should return an empty string on invalid input', function () { | ||
assert.strictEqual('', serialize(null)); | ||
}); | ||
it('should serialize a SPAN element', function () { | ||
var node = document.createElement('span'); | ||
node = document.createElement('span'); | ||
assert.equal('<span></span>', serialize(node)); | ||
@@ -14,3 +29,3 @@ }); | ||
it('should serialize a BR element', function () { | ||
var node = document.createElement('br'); | ||
node = document.createElement('br'); | ||
assert.equal('<br>', serialize(node)); | ||
@@ -20,3 +35,3 @@ }); | ||
it('should serialize a text node', function () { | ||
var node = document.createTextNode('test'); | ||
node = document.createTextNode('test'); | ||
assert.equal('test', serialize(node)); | ||
@@ -26,3 +41,3 @@ }); | ||
it('should serialize a text node with special HTML characters', function () { | ||
var node = document.createTextNode('<>\'"&'); | ||
node = document.createTextNode('<>\'"&'); | ||
assert.equal('<>\'"&', serialize(node)); | ||
@@ -32,3 +47,3 @@ }); | ||
it('should serialize a DIV element with child nodes', function () { | ||
var node = document.createElement('div'); | ||
node = document.createElement('div'); | ||
node.appendChild(document.createTextNode('hello ')); | ||
@@ -44,3 +59,3 @@ var b = document.createElement('b'); | ||
it('should serialize a DIV element with attributes', function () { | ||
var node = document.createElement('div'); | ||
node = document.createElement('div'); | ||
node.setAttribute('foo', 'bar'); | ||
@@ -54,3 +69,3 @@ node.setAttribute('escape', '<>&"\''); | ||
div.setAttribute('foo', 'bar'); | ||
var node = div.attributes[0]; | ||
node = div.attributes[0]; | ||
assert.equal('foo="bar"', serialize(node)); | ||
@@ -60,3 +75,3 @@ }); | ||
it('should serialize a Comment node', function () { | ||
var node = document.createComment('test'); | ||
node = document.createComment('test'); | ||
assert.equal('<!--test-->', serialize(node)); | ||
@@ -66,3 +81,3 @@ }); | ||
it('should serialize a Document node', function () { | ||
var node = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null); | ||
node = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null); | ||
assert.equal('<html></html>', serialize(node)); | ||
@@ -72,3 +87,3 @@ }); | ||
it('should serialize a Doctype node', function () { | ||
var node = document.implementation.createDocumentType( | ||
node = document.implementation.createDocumentType( | ||
'html', | ||
@@ -89,3 +104,3 @@ '-//W3C//DTD XHTML 1.0 Strict//EN', | ||
it('should serialize a DocumentFragment node', function () { | ||
var node = document.createDocumentFragment(); | ||
node = document.createDocumentFragment(); | ||
node.appendChild(document.createElement('b')); | ||
@@ -105,63 +120,125 @@ node.appendChild(document.createElement('i')); | ||
it('should emit a "serialize" event on a DIV node', function () { | ||
var node = document.createElement('div'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = 'MEOW'; | ||
describe('"serialize" event', function () { | ||
it('should emit a "serialize" event on a DIV node', function () { | ||
node = document.createElement('div'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = 'MEOW'; | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('MEOW', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('MEOW', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
it('should emit a "serialize" event on a Text node', function () { | ||
var node = document.createTextNode('whaaaaa!!!!!!'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = 'MEOW'; | ||
it('should emit a "serialize" event on a Text node', function () { | ||
node = document.createTextNode('whaaaaa!!!!!!'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = 'MEOW'; | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('MEOW', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('MEOW', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
it('should output an empty string when "serialize" event is cancelled', function () { | ||
var node = document.createElement('div'); | ||
node.appendChild(document.createTextNode('!')); | ||
var count = 0; | ||
node.firstChild.addEventListener('serialize', function (e) { | ||
count++; | ||
e.preventDefault(); | ||
it('should output an empty string when event is cancelled', function () { | ||
node = document.createElement('div'); | ||
node.appendChild(document.createTextNode('!')); | ||
var count = 0; | ||
node.firstChild.addEventListener('serialize', function (e) { | ||
count++; | ||
e.preventDefault(); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('<div></div>', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('<div></div>', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
it('should render a Number when set as `e.detail.serialize`', function () { | ||
var node = document.createTextNode('whaaaaa!!!!!!'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = 123; | ||
it('should render a Number when set as `e.detail.serialize`', function () { | ||
node = document.createTextNode('whaaaaa!!!!!!'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = 123; | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('123', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('123', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
it('should render a Node when set as `e.detail.serialize`', function () { | ||
var node = document.createTextNode('whaaaaa!!!!!!'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = document.createTextNode('foo'); | ||
it('should render a Node when set as `e.detail.serialize`', function () { | ||
node = document.createTextNode('whaaaaa!!!!!!'); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = document.createTextNode('foo'); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('foo', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('foo', serialize(node)); | ||
assert.equal(1, count); | ||
it('should have `context` set on the event', function () { | ||
node = document.createTextNode(''); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
e.detail.serialize = e.detail.context; | ||
}); | ||
assert.equal(0, count); | ||
assert.equal('foo', serialize(node, 'foo')); | ||
assert.equal(1, count); | ||
assert.equal('bar', serialize(node, 'bar')); | ||
assert.equal(2, count); | ||
assert.equal('baz', serialize(node, 'baz')); | ||
assert.equal(3, count); | ||
}); | ||
it('should bubble', function () { | ||
node = document.createElement('div'); | ||
node.appendChild(document.createTextNode('foo')); | ||
node.appendChild(document.createTextNode(' ')); | ||
node.appendChild(document.createTextNode('bar')); | ||
// `node` must be inside the DOM for the "serialize" event to bubble | ||
document.body.appendChild(node); | ||
var count = 0; | ||
node.addEventListener('serialize', function (e) { | ||
count++; | ||
assert.equal('foo', e.detail.context); | ||
if (e.target === node) | ||
return; | ||
else if (e.target === node.firstChild) | ||
e.detail.serialize = document.createTextNode('…'); | ||
else | ||
e.preventDefault(); | ||
}, false); | ||
assert.equal(0, count); | ||
assert.equal('<div>…</div>', serialize(node, 'foo')); | ||
assert.equal(4, count); | ||
}); | ||
it('should support one-time callback function on Elements', function () { | ||
node = document.createElement('div'); | ||
var count = 0; | ||
function callback (e) { | ||
count++; | ||
e.detail.serialize = count; | ||
} | ||
assert.equal(0, count); | ||
assert.equal('1', serialize(node, callback)); | ||
assert.equal(1, count); | ||
assert.equal('<div></div>', serialize(node)); | ||
assert.equal(1, count); | ||
}); | ||
}); | ||
}); |
18436
386
62