Skip to content

Commit

Permalink
Ensure elements returned from create() are detached from its parent e…
Browse files Browse the repository at this point in the history
…lement
  • Loading branch information
jsor committed Aug 21, 2018
1 parent 914ea7e commit 1db8004
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 39 deletions.
38 changes: 6 additions & 32 deletions src/element/create.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,16 @@
/*
* Ported from jQuery's html()
* https://github.com/jquery/jquery/blob/c9aae3565edc840961ecbeee77fb0cb367c46702/src/manipulation/buildFragment.js
*/

const wrapMap = {
thead: [1, '<table>', '</table>'],
col: [2, '<table><colgroup>', '</colgroup></table>'],
tr: [2, '<table><tbody>', '</tbody></table>'],
td: [3, '<table><tbody><tr>', '</tr></tbody></table>'],

_: [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, '').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;
Expand Down
44 changes: 44 additions & 0 deletions src/util/fragment-container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Ported from jQuery's html()
* https://github.com/jquery/jquery/blob/c9aae3565edc840961ecbeee77fb0cb367c46702/src/manipulation/buildFragment.js
*/

const wrapMap = {
thead: [1, '<table>', '</table>'],
col: [2, '<table><colgroup>', '</colgroup></table>'],
tr: [2, '<table><tbody>', '</tbody></table>'],
td: [3, '<table><tbody><tr>', '</tr></tbody></table>'],

_: [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, '').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;
}
34 changes: 27 additions & 7 deletions test/element/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@ describe('create()', () => {
});

it('creates a node from a string (single tag)', () => {
const html = '<div></div>';
const node = create('<span></span>');

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 = '<span/>';

const node = create(html);
const node = create('<span/>');

assert.equal(node.tagName.toLowerCase(), 'span');
});
Expand All @@ -32,79 +28,103 @@ describe('create()', () => {
assert.equal(node.textContent, 'comment');
});

it('returns last element for multiple root element', () => {
const node = create('<p></p><span/>');

assert.equal(node.tagName.toLowerCase(), 'span');
});

it('returns detached element', () => {
const node = create('<span></span>');

assert.isNull(node.parentNode);
});

// Special nodes which require specific parents
// See: https://github.com/jquery/jquery/blob/c9aae3565edc840961ecbeee77fb0cb367c46702/src/manipulation/wrapMap.js

it('creates a <legend/> node from a string', () => {
const node = create('<legend/>');

assert.equal(node.tagName.toLowerCase(), 'legend');
assert.isNull(node.parentNode);
});

it('creates a <option/> node from a string', () => {
const node = create('<option/>');

assert.equal(node.tagName.toLowerCase(), 'option');
assert.isNull(node.parentNode);
});

it('creates a <optgroup/> node from a string', () => {
const node = create('<optgroup/>');

assert.equal(node.tagName.toLowerCase(), 'optgroup');
assert.isNull(node.parentNode);
});

it('creates a <thead/> node from a string', () => {
const node = create('<thead/>');

assert.equal(node.tagName.toLowerCase(), 'thead');
assert.isNull(node.parentNode);
});

it('creates a <tbody/> node from a string', () => {
const node = create('<tbody/>');

assert.equal(node.tagName.toLowerCase(), 'tbody');
assert.isNull(node.parentNode);
});

it('creates a <tfoot/> node from a string', () => {
const node = create('<tfoot/>');

assert.equal(node.tagName.toLowerCase(), 'tfoot');
assert.isNull(node.parentNode);
});

it('creates a <colgroup/> node from a string', () => {
const node = create('<colgroup/>');

assert.equal(node.tagName.toLowerCase(), 'colgroup');
assert.isNull(node.parentNode);
});

it('creates a <col/> node from a string', () => {
const node = create('<col/>');

assert.equal(node.tagName.toLowerCase(), 'col');
assert.isNull(node.parentNode);
});

it('creates a <caption/> node from a string', () => {
const node = create('<caption/>');

assert.equal(node.tagName.toLowerCase(), 'caption');
assert.isNull(node.parentNode);
});

it('creates a <tr/> node from a string', () => {
const node = create('<tr/>');

assert.equal(node.tagName.toLowerCase(), 'tr');
assert.isNull(node.parentNode);
});

it('creates a <th/> node from a string', () => {
const node = create('<th/>');

assert.equal(node.tagName.toLowerCase(), 'th');
assert.isNull(node.parentNode);
});

it('creates a <td/> node from a string', () => {
const node = create('<td/>');

assert.equal(node.tagName.toLowerCase(), 'td');
assert.isNull(node.parentNode);
});

it('returns element as is if already a node', () => {
Expand Down

0 comments on commit 1db8004

Please sign in to comment.