Skip to content

Instantly share code, notes, and snippets.

@pomber
Created April 20, 2017 20:38
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/221d47de3afc1f84fc98c0b4ce9c1dbb to your computer and use it in GitHub Desktop.
Save pomber/221d47de3afc1f84fc98c0b4ce9c1dbb to your computer and use it in GitHub Desktop.
Two big 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);
// Set attributes
Object.keys(props).filter(isAttribute).forEach(name => {
const value = props[name];
if (value != null && value !== false) {
this.dom[name] = value;
}
});
// Set events
Object.keys(props).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
this.dom.addEventListener(eventType, props[name]);
});
// Set children
const childElements = props.children || [];
this.childInstances = childElements.map(
childElement => new DomComponent(childElement)
);
this.childInstances
.map(childInstance => childInstance.mount())
.forEach(childDom => this.dom.appendChild(childDom));
return this.dom;
}
update(nextElement) {
const prevProps = this.currentElement.props;
const nextProps = nextElement.props;
// Remove attributes
Object.keys(prevProps).filter(isAttribute).forEach(name => {
this.dom[name] = null;
});
// Set attributes
Object.keys(nextProps).filter(isAttribute).forEach(name => {
const value = nextProps[name];
if (value != null && value !== false) {
this.dom[name] = value;
}
});
// Remove events
Object.keys(prevProps).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
this.dom.removeEventListener(eventType, prevProps[name]);
});
// Set events
Object.keys(nextProps).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
this.dom.addEventListener(eventType, nextProps[name]);
});
// Update children
const prevChildElements = prevProps.children || [];
const nextChildElements = nextProps.children || [];
const prevChildInstances = this.childInstances;
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];
if (prevChildElement === undefined) {
// Add new child
const childInstance = new DomComponent(nextChildElement);
nextChildInstances.push(childInstance);
const childDom = childInstance.mount();
this.dom.appendChild(childDom);
} else if (nextChildElement === undefined) {
// Remove old child
const childInstance = prevChildInstances[i];
const childDom = childInstance.getDom();
this.dom.removeChild(childDom);
} else if (prevChildElement.type === nextChildElement.type) {
// Update child
const childInstance = prevChildInstances[i];
nextChildInstances.push(childInstance);
childInstance.update(nextChildElement);
} else {
// Replace old with new
const nextChildInstance = new DomComponent(nextChildElement);
nextChildInstances.push(nextChildInstance);
const nextChildDom = nextChildInstance.mount();
const prevChildInstance = prevChildInstances[i];
const prevChildDom = prevChildInstance.getDom();
this.dom.replaceChild(nextChildDom, prevChildDom);
}
}
this.currentElement = nextElement;
this.childInstances = nextChildInstances;
}
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