From 1db8004d885d040fc2e2b683b692e733f316795a Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Tue, 21 Aug 2018 10:37:43 +0200 Subject: [PATCH] Ensure elements returned from create() are detached from its parent element --- src/element/create.js | 38 +++++------------------------ src/util/fragment-container.js | 44 ++++++++++++++++++++++++++++++++++ test/element/create.test.js | 34 ++++++++++++++++++++------ 3 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 src/util/fragment-container.js diff --git a/src/element/create.js b/src/element/create.js index a3611ff..eb6b859 100644 --- a/src/element/create.js +++ b/src/element/create.js @@ -1,42 +1,16 @@ -/* - * Ported from jQuery's html() - * https://github.com/jquery/jquery/blob/c9aae3565edc840961ecbeee77fb0cb367c46702/src/manipulation/buildFragment.js - */ - -const wrapMap = { - thead: [1, '', '
'], - col: [2, '', '
'], - tr: [2, '', '
'], - td: [3, '', '
'], - - _: [0, '', ''] -}; - -wrapMap.tbody = wrapMap.thead; -wrapMap.tfoot = wrapMap.thead; -wrapMap.colgroup = wrapMap.thead; -wrapMap.caption = wrapMap.thead; - -wrapMap.th = wrapMap.td; +import fragmentContainer from '../util/fragment-container'; export default function create(html) { if (html.nodeType) { return html; } - let element = document.createElement('div'); - - const match = /<([a-z][^/\0>\u0020\t\r\n\f]*)/i.exec(html); - - const tag = match ? match[0].replace(//g, '') : '_'; - const map = wrapMap[tag] || wrapMap._; - - element.innerHTML = map[1] + html + map[2]; - - let j = map[0] + 1; + const container = fragmentContainer(html); + const element = container.lastChild; - while (j--) { - element = element.lastChild; + if (element) { + // Detach element from container + container.removeChild(element); } return element; diff --git a/src/util/fragment-container.js b/src/util/fragment-container.js new file mode 100644 index 0000000..4e34b7b --- /dev/null +++ b/src/util/fragment-container.js @@ -0,0 +1,44 @@ +/* + * Ported from jQuery's html() + * https://github.com/jquery/jquery/blob/c9aae3565edc840961ecbeee77fb0cb367c46702/src/manipulation/buildFragment.js + */ + +const wrapMap = { + thead: [1, '', '
'], + col: [2, '', '
'], + tr: [2, '', '
'], + td: [3, '', '
'], + + _: [0, '', ''] +}; + +wrapMap.tbody = wrapMap.thead; +wrapMap.tfoot = wrapMap.thead; +wrapMap.colgroup = wrapMap.thead; +wrapMap.caption = wrapMap.thead; + +wrapMap.th = wrapMap.td; + +export default function fragmentContainer(html) { + if (html === '') { + return document.createTextNode(' '); + } + + const match = /<([a-z][^/\0>\u0020\t\r\n\f]*)/i.exec(html); + + const tag = match ? match[0].replace(//g, '') : '_'; + const map = wrapMap[tag] || wrapMap._; + + const fragment = document.createDocumentFragment(); + let container = fragment.appendChild(document.createElement('div')); + + container.innerHTML = map[1] + html + map[2]; + + let j = map[0]; + + while (j--) { + container = container.lastChild; + } + + return container; +} diff --git a/test/element/create.test.js b/test/element/create.test.js index b3d5096..433c24c 100644 --- a/test/element/create.test.js +++ b/test/element/create.test.js @@ -10,17 +10,13 @@ describe('create()', () => { }); it('creates a node from a string (single tag)', () => { - const html = '
'; + const node = create(''); - const node = create(html); - - assert.equal(node.tagName.toLowerCase(), 'div'); + assert.equal(node.tagName.toLowerCase(), 'span'); }); it('creates a node from a string (self-closing tag)', () => { - const html = ''; - - const node = create(html); + const node = create(''); assert.equal(node.tagName.toLowerCase(), 'span'); }); @@ -32,6 +28,18 @@ describe('create()', () => { assert.equal(node.textContent, 'comment'); }); + it('returns last element for multiple root element', () => { + const node = create('

'); + + assert.equal(node.tagName.toLowerCase(), 'span'); + }); + + it('returns detached element', () => { + const node = create(''); + + assert.isNull(node.parentNode); + }); + // Special nodes which require specific parents // See: https://github.com/jquery/jquery/blob/c9aae3565edc840961ecbeee77fb0cb367c46702/src/manipulation/wrapMap.js @@ -39,72 +47,84 @@ describe('create()', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'legend'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'optgroup'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'thead'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'tbody'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'tfoot'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'colgroup'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'col'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'caption'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'tr'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'th'); + assert.isNull(node.parentNode); }); it('creates a node from a string', () => { const node = create(''); assert.equal(node.tagName.toLowerCase(), 'td'); + assert.isNull(node.parentNode); }); it('returns element as is if already a node', () => {