Skip to content

Instantly share code, notes, and snippets.

@gordonbrander
Last active November 6, 2022 07:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gordonbrander/6288ee006d218ff7574ac325e7ca7f50 to your computer and use it in GitHub Desktop.
Save gordonbrander/6288ee006d218ff7574ac325e7ca7f50 to your computer and use it in GitHub Desktop.
select — sugarfree D3-style enter/update/exit DOM manipulation, using a simple function instead of method chaining
// D3-style select-baed DOM updating.
//
// Rather than relying on jQuery-style method-chaining to update the DOM,
// we use a "view" object, an ordinary object that contains the following
// functions:
//
// - `enter(datum) -> Element` returns a DOM node for new elements.
// Element will be appended to parent.
// - `update(el, datum, old)` handles mutating an element in response to
// changes in data. `old` is the last-known data for this element.
// - `exit(el)` handles removing an element.
const enterWith = (enter, datum) => {
const el = enter(datum)
el.__data__ = datum
return el
}
export const updateWith = (update, el, datum) => {
update(el, datum, el.__data__)
el.__data__ = datum
}
export const select = ({update}, el, datum) => {
updateWith(update, el, datum)
}
export const selectAll = ({enter, update, exit}, query, parent, data) => {
const els = parent.querySelectorAll(':scope ' + query)
if (els.length === data.length) {
for (let i = 0; i < els.length; i++) {
updateWith(update, els[i], data[i])
}
} else if (els.length < data.length) {
for (let i = 0; i < els.length; i++) {
updateWith(update, els[i], data[i])
}
for (let i = els.length; i < data.length; i++) {
parent.appendChild(enterWith(enter, data[i]))
}
} else if (els.length > data.length) {
for (let i = 0; i < data.length; i++) {
updateWith(update, els[i], data[i])
}
for (let i = data.length; i < els.length; i++) {
exit(els[i])
}
}
}
export const Renderer = (view, query) => (parent, data) =>
selectAll(view, query, parent, data)
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script type="module">
import {Renderer} from './select.js'
const Li = {
enter: datum => {
const el = document.createElement('li')
el.innerText = datum.text
return el
},
update: (el, datum, old) => {
if (datum !== old) {
el.innerText = datum.text
}
},
exit: el => {
el.remove()
}
}
const renderUl = Renderer(Li, 'li')
const data = [
{text: 'foo'},
{text: 'bar'}
]
const ul = document.querySelector('ul')
// Reflect data to dom
renderUl(ul, data)
</script>
</head>
<body>
<ul id="parent">
<li>Some static content that will be updated by script above</li>
</ul>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment