Skip to content

Instantly share code, notes, and snippets.

@pomber
Created April 20, 2017 20:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pomber/21d9dde938a5d9ffada1420c5aed0819 to your computer and use it in GitHub Desktop.
Save pomber/21d9dde938a5d9ffada1420c5aed0819 to your computer and use it in GitHub Desktop.
Several small functions
export function createElement(type, config, ...args) {
const props = Object.assign({}, config);
if (args.length) {
props.children = [].concat(...args);
}
return { type, props };
}
export function render(element, container) {
const instance = new DomComponent(element);
const dom = instance.mount();
container.appendChild(dom);
}
class DomComponent {
constructor(element) {
this.currentElement = element;
this.dom = null;
this.childInstances = null;
}
mount() {
const { type, props } = this.currentElement;
this.dom = document.createElement(type);
this.setAttributes(this.dom, props);
this.setEvents(this.dom, props);
this.childInstances = this.setChildren(this.dom, props);
return this.dom;
}
update(nextElement) {
const prevProps = this.currentElement.props;
const nextProps = nextElement.props;
this.removeAttributes(this.dom, prevProps);
this.setAttributes(this.dom, nextProps);
this.removeEvents(this.dom, prevProps);
this.setEvents(this.dom, nextProps);
this.childInstances = this.updateChildren(
this.dom,
prevProps,
nextProps,
this.childInstances
);
this.currentElement = nextElement;
}
setAttributes(dom, props) {
Object.keys(props).filter(isAttribute).forEach(name => {
const value = props[name];
if (value != null && value !== false) {
dom[name] = value;
}
});
}
removeAttributes(dom, props) {
Object.keys(props).filter(isAttribute).forEach(name => {
dom[name] = null;
});
}
setEvents(dom, props) {
Object.keys(props).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, props[name]);
});
}
removeEvents(dom, props) {
Object.keys(props).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
dom.removeEventListener(eventType, props[name]);
});
}
setChildren(dom, props) {
const childElements = props.children || [];
const childInstances = childElements.map(
childElement => new DomComponent(childElement)
);
childInstances
.map(childInstance => childInstance.mount())
.forEach(childDom => dom.appendChild(childDom));
return childInstances;
}
updateChildren(dom, prevProps, nextProps, prevChildInstances) {
const prevChildElements = prevProps.children || [];
const nextChildElements = nextProps.children || [];
const nextChildInstances = [];
const length = Math.max(prevChildElements.length, nextChildElements.length);
for (let i = 0; i < length; i++) {
const prevChildElement = prevChildElements[i];
const nextChildElement = nextChildElements[i];
const prevChildInstance = prevChildInstances[i];
const nextChildInstance = this.updateChild(
this.dom,
prevChildElement,
nextChildElement,
prevChildInstance
);
if (nextChildInstance) {
nextChildInstances.push(nextChildInstance);
}
}
return nextChildInstances;
}
updateChild(dom, prevChildElement, nextChildElement, prevChildInstance) {
if (prevChildElement === undefined) {
return this.addChild(this.dom, nextChildElement);
} else if (nextChildElement === undefined) {
this.removeChild(this.dom, prevChildInstance);
return null;
} else if (prevChildElement.type === nextChildElement.type) {
prevChildInstance.update(nextChildElement);
return prevChildInstance;
} else {
return this.replaceChild(this.dom, prevChildInstance, nextChildElement);
}
}
addChild(dom, childElement) {
const childInstance = new DomComponent(nextChildElement);
const childDom = childInstance.mount();
dom.appendChild(childDom);
return childInstance;
}
removeChild(dom, childInstance) {
const childDom = childInstance.getDom();
dom.removeChild(childDom);
}
replaceChild(dom, prevChildInstance, nextChildElement) {
const nextChildInstance = new DomComponent(nextChildElement);
const nextChildDom = nextChildInstance.mount();
const prevChildDom = prevChildInstance.getDom();
dom.replaceChild(nextChildDom, prevChildDom);
return nextChildInstance;
}
getDom() {
return this.dom;
}
}
const isEvent = name => name.startsWith("on");
const isAttribute = name => !isEvent(name) && name != "children";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment