Skip to content

Instantly share code, notes, and snippets.

@sstur
Last active October 8, 2023 04:17
Show Gist options
  • Star 93 You must be signed in to star a gist
  • Fork 35 You must be signed in to fork a gist
  • Save sstur/7379870 to your computer and use it in GitHub Desktop.
Save sstur/7379870 to your computer and use it in GitHub Desktop.
Stringify DOM nodes using JSON (and revive again)
function toJSON(node) {
let propFix = { for: 'htmlFor', class: 'className' };
let specialGetters = {
style: (node) => node.style.cssText,
};
let attrDefaultValues = { style: '' };
let obj = {
nodeType: node.nodeType,
};
if (node.tagName) {
obj.tagName = node.tagName.toLowerCase();
} else if (node.nodeName) {
obj.nodeName = node.nodeName;
}
if (node.nodeValue) {
obj.nodeValue = node.nodeValue;
}
let attrs = node.attributes;
if (attrs) {
let defaultValues = new Map();
for (let i = 0; i < attrs.length; i++) {
let name = attrs[i].nodeName;
defaultValues.set(name, attrDefaultValues[name]);
}
// Add some special cases that might not be included by enumerating
// attributes above. Note: this list is probably not exhaustive.
switch (obj.tagName) {
case 'input': {
if (node.type === 'checkbox' || node.type === 'radio') {
defaultValues.set('checked', false);
} else if (node.type !== 'file') {
// Don't store the value for a file input.
defaultValues.set('value', '');
}
break;
}
case 'option': {
defaultValues.set('selected', false);
break;
}
case 'textarea': {
defaultValues.set('value', '');
break;
}
}
let arr = [];
for (let [name, defaultValue] of defaultValues) {
let propName = propFix[name] || name;
let specialGetter = specialGetters[propName];
let value = specialGetter ? specialGetter(node) : node[propName];
if (value !== defaultValue) {
arr.push([name, value]);
}
}
if (arr.length) {
obj.attributes = arr;
}
}
let childNodes = node.childNodes;
// Don't process children for a textarea since we used `value` above.
if (obj.tagName !== 'textarea' && childNodes && childNodes.length) {
let arr = (obj.childNodes = []);
for (let i = 0; i < childNodes.length; i++) {
arr[i] = toJSON(childNodes[i]);
}
}
return obj;
}
function toDOM(input) {
let obj = typeof input === 'string' ? JSON.parse(input) : input;
let propFix = { for: 'htmlFor', class: 'className' };
let node;
let nodeType = obj.nodeType;
switch (nodeType) {
// ELEMENT_NODE
case 1: {
node = document.createElement(obj.tagName);
if (obj.attributes) {
for (let [attrName, value] of obj.attributes) {
let propName = propFix[attrName] || attrName;
// Note: this will throw if setting the value of an input[type=file]
node[propName] = value;
}
}
break;
}
// TEXT_NODE
case 3: {
return document.createTextNode(obj.nodeValue);
}
// COMMENT_NODE
case 8: {
return document.createComment(obj.nodeValue);
}
// DOCUMENT_FRAGMENT_NODE
case 11: {
node = document.createDocumentFragment();
break;
}
default: {
// Default to an empty fragment node.
return document.createDocumentFragment();
}
}
if (obj.childNodes && obj.childNodes.length) {
for (let childNode of obj.childNodes) {
node.appendChild(toDOM(childNode));
}
}
return node;
}
@sstur
Copy link
Author

sstur commented Apr 24, 2021

it doesn't capture special attributes like data-glide-el

True. The reason is that data attributes don't change the way the static HTML displays, so it doesn't make sense to capture them for the original goals of this script.

In most cases, the purpose of data attributes is for JavaScript to do something with the data, but toJSON() also doesn't capture JavaScript or JS-related attributes (like onclick).

So I'd say data attributes are out of scope for this, but I'd be open to hear if there's a good use for adding support.

don't know how to generate the same HTML from JSON

If you need HTML output, I'd suggest you first create DOM and then convert that to HTML using innerHTML or something similar.

where can I provide my root node in toDOM()

toDOM() doesn't require a root node, instead it returns a node. You can attach that node to your document using something like document.body.appendChild(node).

@prakhartiwari0
Copy link

Can we support SVG too?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment