Skip to content

Instantly share code, notes, and snippets.

@bkardell
Last active December 26, 2015 05:49
Show Gist options
  • Select an option

  • Save bkardell/7103836 to your computer and use it in GitHub Desktop.

Select an option

Save bkardell/7103836 to your computer and use it in GitHub Desktop.
DOMModificationTargets idea for sharing ...
<!DOCTYPE html>
<html>
<head>
<style>
html, body { height: 100%; width: 100%; }
pre {
padding: 1em;
border: 1px solid gray;
background-color: #cfcfcf;
}
td {
border: 1px solid blue;
}
#fixture, nav { display: block; border: 1px solid green; overflow: auto; margin: 2em; padding: 2em;}
nav { height: 10em; }
#demo-view, #readme-view { display: none; }
#demo-view.active, #readme-view.active { display: block; }
.foo {
background-color: green;
}
</style>
</head>
<body>
<a class="view-toggle" href="#readme" data-ref="readme-view">Readme</a>
|
<a class="view-toggle" href="#demo" data-ref="demo-view">Simple demo</a>
|
<a href="https://rawgithub.com/bkardell/7103836/raw/ff2a74b06b607f10576477c2fc76096b8d9926ae/DOMModificationTargets.js">Source</a>
<hr>
<section id="readme-view" class="active">
<h3>Rationale...</h3>
<p>
We've come a long way integrating ideas pioneered and made popular by jQuery back into the Web Platform: We've got querySelectorAll, querySelector, query, Elements, some sugar methods on individual Node APIs, classList and Promise based AJAX apis -- all either standards or developing standards.
And there is John Resig's <a href="http://nodelist.org/">NodeList proposal</a>.
</p>
<p><strong>But...</strong></p>
<p>
At the end of the day, I frequently need to mutate the DOM and the APIs for how we do this in the native space are still too bitter and full of
boilerplate and footguns that are conceptually irrelevant to the average web dev.
</p>
<p>
While it would be ideal to kill off old ideas or merely provide new ones, the reality is that we can't kill off apis and new has to work with old in ways that aren't frustrating to developers.
We need to realize that arrays of elements will live along side live node lists and Elements and jquery array likes for years to come...
</p>
<p>What follows is a simple and limited idea that would allow this to all work together a little better.</p>
<h3>DOMModificationTargets</h3>
<p>`DOMModificationTargets` is a constructable list that allows you to perform sugared DOM operations on all the things...</p>
<pre id="usage">
// Use...
var targets = new DOMModificationTargets(X);
// Where X maybe any of:
// * CSS selector string
// * an element
// * an array of elements
// * a node list
// * Eventually Elements collection
targets[METHOD](Y);
// Where METHOD may be any of:
// * append
// * prepend
// * replace
// * replaceWith
// * addClass
// * removeClass
// * toggleClass
// * setAttribute
// * removeAttribute
// and Y may be any of the following:
// * string of markup
// * an element
// * an array of elements
// * a node list
// * Eventually Elements collection
// See working examples under demo
</pre>
</section>
<section id="demo-view">
<!-- Holds generated demo tests -->
<nav></nav>
<div id="fixture">
<div>Tests will execute up here...</div>
<table id="myTable">
<tr>
<td>Placeholder table for testing...</td>
</tr>
</table>
</div>
</section>
<script>
// emulate require for this one thing...
var define = function (arr, fn) {
window.DOMModificationTargets = fn();
}
if (!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
if (!Object.create) {
Object.create = (function () {
function F() {}
return function (o) {
if (arguments.length != 1) {
throw new Error('Object.create implementation only accepts one parameter.');
}
F.prototype = o
return new F()
}
})()
}
var mixin = function (target, source) {
target = target || {};
for (var prop in source) {
if (typeof source[prop] === 'object') {
target[prop] = mixin(target[prop], source[prop]);
} else {
target[prop] = source[prop];
}
}
return target;
}
</script>
<script src="https://rawgithub.com/bkardell/7103836/raw/ff2a74b06b607f10576477c2fc76096b8d9926ae/DOMModificationTargets.js"></script>
<script class="test" id="appendToOneElement">
var appendToOneElement = function () {
// Create targets from a selector...
new DOMModificationTargets("#fixture").append("<h1>OK!</h1>");
}
</script>
<script class="test" id="appendToOneElementViaElement">
var appendToOneElementViaElement = function () {
// Create targets via element...
var fixture = document.getElementById("fixture");
new DOMModificationTargets(fixture).append("<h2>Added via Element Target</h2>");
}
</script>
<script class="test" id="appendToOneElementViaQsa">
var appendToOneElementViaQsa = function () {
// Create targets via qsa or array of elements...
var fixtures = document.querySelectorAll("#fixture");
new DOMModificationTargets(fixtures).append("<h2>Added via Element Array Target</h2>");
}
</script>
<script class="test" id="appendToOneElementViaNodeList">
var appendToOneElementViaNodeList = function () {
// Create targets via qsa or array of elements...
var nodelist = document.getElementsByTagName("section");
new DOMModificationTargets(nodelist).append("<h3 style='color:green'>Added via Element Array Target</h3>");
}
</script>
<script class="test" id="appendToText">
// Works with text...
var appendToText = function () {
new DOMModificationTargets("#fixture").append("This is some text...");
}
</script>
<script class="test" id="prependWithElement">
var prependWithElement = function () {
// Operations can use elements...
var element = document.createElement("div");
element.style.backgroundColor = 'orange';
element.innerHTML = "Added via element...";
new DOMModificationTargets("table tr").append(element);
}
</script>
<script class="test" id="prependWithArrayOfElement">
var prependWithArrayOfElement = function () {
// Operations can use arrays of elements...
var elements = [document.createElement("div"), document.createElement("div")];
elements[0].style.backgroundColor = 'orange';
elements[0].innerHTML = "Added via array of elements[0]...";
elements[1].style.backgroundColor = 'gray';
elements[1].innerHTML = "Added via array of elements[1]...";
new DOMModificationTargets("table tr").append(elements);
}
</script>
<script class="test" id="prependWithDocumentFragment">
var prependWithDocumentFragment = function () {
// Operations can use document fragments...
var e1, e2, fragment = document.createDocumentFragment();
e1 = document.createElement('div');
e2 = document.createElement('div');
e1.style.border = '1px solid orange';
e1.innerHTML = "Added via document fragment (e1)...";
e2.style.border = '1px solid gray';
e2.innerHTML = "Added via document fragment (e2)...";
fragment.appendChild(e1);
fragment.appendChild(e2);
new DOMModificationTargets("table tr").append(fragment);
}
</script>
<script class="test" id="appendToCell">
// Works with table markup
var appendToCell = function () {
new DOMModificationTargets("table tr").append("<td>This is some text in a table cell...</td>");
}
</script>
<script class="test" id="appendToCells">
var appendToCells = function () {
new DOMModificationTargets("table tr").append("<td>(A) in a table cell...</td><td>(B) in a table cell...</td>");
}
</script>
<script class="test" id="replaceTableCells">
var replaceTableCells = function () {
new DOMModificationTargets("td").replaceWith("<td>FOO!</td>");
}
</script>
<script class="test" id="addClass">
var addClass = function () {
new DOMModificationTargets("td").addClass("foo");
}
</script>
<script>
var testSources = document.querySelectorAll(".test");
var nav = document.querySelector("nav");
var linksSrc = ["<h2>Some tests...</h2>"];
var code;
for (var i = 0; i < testSources.length; i++) {
code = testSources[i].innerHTML.replace(/</g, "&lt;").replace(/>/g, "&gt;").trim();
code = code.replace(/.*\{/,"").replace(/\}$/,"");
linksSrc.push("<div>");
linksSrc.push("<pre>");
linksSrc.push(code.trim());
linksSrc.push("</pre>");
linksSrc.push("<button onclick=\'" + testSources[i].getAttribute("id") + "();\'>Run</button>")
linksSrc.push("</div>");
}
nav.innerHTML = linksSrc.join("");
var readmeContent = document.querySelector("#readme-view");
var demoContent = document.querySelector("#demo-view");
document.addEventListener("click", function (evt) {
if (evt.target.classList.contains("view-toggle")) {
if (evt.target.getAttribute("data-ref") === "readme-view") {
demoContent.classList.remove("active");
readmeContent.classList.add("active");
} else {
demoContent.classList.add("active");
readmeContent.classList.remove("active");
}
}
}, true);
</script>
</body>
</html>
define([], function () {
var mixin = function (target, source) {
target = target || {};
for (var prop in source) {
if (typeof source[prop] === 'object') {
target[prop] = mixin(target[prop], source[prop]);
} else {
target[prop] = source[prop];
}
}
return target;
};
var map = {
option: {
depth: 1,
prefix: '<select multiple="multiple">',
suffix: '</select>'
},
optgroup: {
depth: 1,
prefix: '<select multiple="multiple">',
suffix: '</select>'
},
legend: {
depth: 1,
prefix: '<fieldset>',
suffix: '</fieldset>'
},
tr: {
depth: 2,
prefix: '<table><tbody>',
suffix: '</tbody></table>'
},
col: {
depth: 2,
prefix: '<table><tbody></tbody><colgroup>',
suffix: '</colgroup></table>'
},
_default: {
depth: 0,
prefix: '',
suffix: ''
}
};
map.thead = map.tbody = map.tfoot = map.colgroup = map.caption = {
depth: 1,
prefix: '<table>',
suffix: '</table>'
};
map.td = map.th = {
depth: 3,
prefix: '<table><tbody><tr>',
suffix: '</tr></tbody></table>'
};
var getWrap = function (tagName) {
return (map[tagName]) ? map[tagName] : {
prefix: '',
suffix: ''
};
};
var getFromDepth = function (el, depth) {
if (depth <= 0) {
return el.children;
}
return getFromDepth(el.firstChild, --depth);
};
var Mutatable = function (arg) {
this.collection = asArray('select', arg);
return this;
};
var baseClassMutator = function (method) {
var args = Array.prototype.slice.call(arguments[1]);
this.collection.forEach(function (el) {
el.classList[method].apply(el.classList, args);
});
};
mixin(
Mutatable.prototype, {
append: function (elsOrMarkup) {
this.collection.forEach(function (el) {
el.appendChild(toFragment(elsOrMarkup));
});
return this;
},
replaceWith: function (elsOrMarkup) {
this.collection.forEach(function (el) {
el.parentNode.replaceChild(toFragment(elsOrMarkup), el);
});
return this;
},
prepend: function (elsOrMarkup) {
this.collection.forEach(function (el) {
el.insertBefore(toFragment(elsOrMarkup), el.firstChild);
});
return this;
},
replaceContent: function (elsOrMarkup) {
this.collection.forEach(function (el) {
el.innerHTML = '';
el.appendChild(toFragment(elsOrMarkup));
});
},
remove: function () {
this.collection.forEach(function (el) {
el.parentNode.removeChild(el);
});
return this;
},
addClass: function () {
baseClassMutator.call(this, 'add', arguments);
return this;
},
removeClass: function () {
baseClassMutatior.call(this, 'remove', arguments);
return this;
},
toggle: function () {
baseClassMutatior.call(this, 'toggle', arguments);
return this;
},
setAttribute: function (k, v) {
this.collection.forEach(function (el) {
el.setAttribute(k, v);
});
return this;
},
removeAttribute: function (k) {
this.collection.forEach(function (el) {
el.removeAttribute(k);
});
return this;
}
}
);
var fromString = {
parse: function (markup) {
var wrapper;
var temp = document.createElement('div');
var tagName;
var re = {
single: (/^<(\w+)\s*\/?>(?:<\/\1>|)$/),
tagName: /<([\w:]+)/
};
var parsed = re.single.exec(markup);
if (parsed) {
return [document.createElement(parsed[1])];
}
parsed = re.tagName.exec(markup);
if (!parsed) {
return [document.createTextNode(markup)];
}
tagName = parsed[1];
wrapper = getWrap(tagName);
temp.innerHTML = wrapper.prefix + markup + wrapper.suffix;
return getFromDepth(temp, wrapper.depth || 0);
},
select: function (q) {
return document.querySelectorAll(q);
}
};
var asArray = function (fromStringMethod, arg) {
var c = arg;
if (!Array.isArray(arg)) {
// Could be a nodelist, element or string..., would be better if we could use instanceof
// but IE8 is why we can't have nice things.
if ((typeof arg === 'string') || arg.tagName) {
c = (typeof arg === 'string') ? fromString[fromStringMethod](arg) : [arg];
}
// freeze it...
c = Array.prototype.slice.call(c);
}
return c;
};
var toFragment = function (elsOrMarkup) {
var c = asArray('parse', elsOrMarkup),
f = document.createDocumentFragment();
for (var i = 0; i < c.length; i++) {
f.appendChild(c[i]);
}
return f;
};
return Mutatable;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment