UNPKG

choo-shortcache

Version:

choo nanocomponent cache shortcut

155 lines (136 loc) 4.83 kB
var document = require('global/document') var nanotiming = require('nanotiming') var morph = require('nanomorph') var onload = require('on-load') var OL_KEY_ID = onload.KEY_ID var OL_ATTR_ID = onload.KEY_ATTR var assert = require('assert') module.exports = Nanocomponent function makeID () { return 'ncid-' + Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) } function Nanocomponent (name) { this._hasWindow = typeof window !== 'undefined' this._id = null // represents the id of the root node this._ncID = null // internal nanocomponent id this._olID = null this._proxy = null this._loaded = false // Used to debounce on-load when child-reordering this._rootNodeName = null this._name = name || 'nanocomponent' this._rerender = false this._handleLoad = this._handleLoad.bind(this) this._handleUnload = this._handleUnload.bind(this) this._arguments = [] var self = this Object.defineProperty(this, 'element', { get: function () { var el = document.getElementById(self._id) if (el) return el.dataset.nanocomponent === self._ncID ? el : undefined } }) } Nanocomponent.prototype.render = function () { var renderTiming = nanotiming(this._name + '.render') var self = this var args = new Array(arguments.length) var el for (var i = 0; i < arguments.length; i++) args[i] = arguments[i] if (!this._hasWindow) { var createTiming = nanotiming(this._name + '.create') el = this.createElement.apply(this, args) createTiming() renderTiming() return el } else if (this.element) { el = this.element // retain reference, as the ID might change on render var updateTiming = nanotiming(this._name + '.update') var shouldUpdate = this._rerender || this.update.apply(this, args) updateTiming() if (this._rerender) this._rerender = false if (shouldUpdate) { var desiredHtml = this._handleRender(args) var morphTiming = nanotiming(this._name + '.morph') morph(el, desiredHtml) morphTiming() if (this.afterupdate) this.afterupdate(el) } if (!this._proxy) { this._proxy = this._createProxy() } renderTiming() return this._proxy } else { this._reset() el = this._handleRender(args) if (this.beforerender) this.beforerender(el) if (this.load || this.unload || this.afterreorder) { onload(el, self._handleLoad, self._handleUnload, self._ncID) this._olID = el.dataset[OL_KEY_ID] } renderTiming() return el } } Nanocomponent.prototype.rerender = function () { assert(this.element, 'nanocomponent: cant rerender on an unmounted dom node') this._rerender = true this.render.apply(this, this._arguments) } Nanocomponent.prototype._handleRender = function (args) { var createElementTiming = nanotiming(this._name + '.createElement') var el = this.createElement.apply(this, args) createElementTiming() if (!this._rootNodeName) this._rootNodeName = el.nodeName assert(el instanceof window.Element, 'nanocomponent: createElement should return a DOM node') assert.equal(this._rootNodeName, el.nodeName, 'nanocomponent: root node types cannot differ between re-renders') this._arguments = args return this._brandNode(this._ensureID(el)) } Nanocomponent.prototype._createProxy = function () { var proxy = document.createElement(this._rootNodeName) var self = this this._brandNode(proxy) proxy.id = this._id proxy.setAttribute('data-proxy', '') proxy.isSameNode = function (el) { return (el && el.dataset.nanocomponent === self._ncID) } return proxy } Nanocomponent.prototype._reset = function () { this._ncID = makeID() this._olID = null this._id = null this._proxy = null this._rootNodeName = null } Nanocomponent.prototype._brandNode = function (node) { node.setAttribute('data-nanocomponent', this._ncID) if (this._olID) node.setAttribute(OL_ATTR_ID, this._olID) return node } Nanocomponent.prototype._ensureID = function (node) { if (node.id) this._id = node.id else node.id = this._id = this._ncID // Update proxy node ID if it changed if (this._proxy && this._proxy.id !== this._id) this._proxy.id = this._id return node } Nanocomponent.prototype._handleLoad = function (el) { if (this._loaded) { if (this.afterreorder) this.afterreorder(el) return // Debounce child-reorders } this._loaded = true if (this.load) this.load(el) } Nanocomponent.prototype._handleUnload = function (el) { if (this.element) return // Debounce child-reorders this._loaded = false if (this.unload) this.unload(el) } Nanocomponent.prototype.createElement = function () { throw new Error('nanocomponent: createElement should be implemented!') } Nanocomponent.prototype.update = function () { throw new Error('nanocomponent: update should be implemented!') }