UNPKG

kaiju

Version:

Virtual dom, observables and stateful components

225 lines (168 loc) 7.25 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); exports.default = Component; var _h = require('snabbdom/h'); var _h2 = _interopRequireDefault(_h); var _render = require('./render'); var _util = require('./util'); var _messages = require('./messages'); var _messages2 = _interopRequireDefault(_messages); var _observable = require('../observable'); var _store = require('../store'); var _log = require('./log'); var _log2 = _interopRequireDefault(_log); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var empty = {}; function Component(options) { var name = options.name, _options$props = options.props, props = _options$props === undefined ? empty : _options$props, _options$sel = options.sel, sel = _options$sel === undefined ? 'component' : _options$sel, initState = options.initState, connect = options.connect, render = options.render, _options$log = options.log, log = _options$log === undefined ? true : _options$log; var key = props.key === undefined ? name : name + '_' + props.key; var data = { key: key, hook: { insert: insert, postpatch: postpatch, destroy: destroy }, component: { props: props, initState: initState, connect: connect, render: render, key: name, log: log }, attrs: { name: name } // An empty placeholder is returned, and that's all our parent is going to see. // Each component handles its own internal rendering. };var compVnode = (0, _h2.default)(sel, data); data.component.compVnode = compVnode; return compVnode; } function insert(vnode) { var component = vnode.data.component; var props = component.props, initState = component.initState, connect = component.connect; var connected = false; // Lookup from HTML Element to component, used in DOM-based messaging vnode.elm.__comp__ = component; // Store the component depth once it's attached to the DOM so we can render // component hierarchies in a predictive (top -> down) manner. component.depth = getDepth(vnode.elm); // Internal callbacks component.lifecycle = { rendered: rendered }; var messages = new _messages2.default(vnode.elm); component.elm = vnode.elm; component.messages = messages; var propsObservable = (0, _observable.Observable)(function (add) { add(component.props); component.lifecycle.propsChanged = add; }).named('props'); // Eagerly subscribe so that the observable get its first value and we honour // the ObservableWithInitialValue interface contract. propsObservable.subscribe(function (x) { return x; }); component.store = (0, _store.Store)(initState(props), function (_ref) { var on = _ref.on, msg = _ref.msg, state = _ref.state; messages.storeMsg = msg; var connectParams = { on: on, state: state, props: propsObservable, msg: messages }; connect(connectParams); connected = true; // First render. // Render right after our parent (which is in the middle of a patch) // so that we honour the snabbdom's insert hook, // e.g we get patched into our parent after our parent was added to the document. (0, _render.renderNewComponentNow)(component); }, { name: component.key, log: (0, _log.shouldLog)(_log2.default.message, component) }); component.store.state.sliding2().subscribe(function (_ref2) { var _ref3 = _slicedToArray(_ref2, 2), newState = _ref3[0], oldState = _ref3[1]; var shouldRender = // Skip the first notification (hot observable) oldState && // synchronous observables triggering before the first render should just be accumulated connected && // the props observable triggered, a synchronous render is made right after so skip !component.lifecycle.propsChanging && // null update !(0, _util.shallowEqual)(oldState, newState); if (shouldRender) (0, _render.renderComponentNextFrame)(component); }); } // Called on every parent re-render, this is where the props passed by the component's parent may have changed. function postpatch(oldVnode, vnode) { var oldData = oldVnode.data; var newData = vnode.data; // Server side rendering: Reconcilating with a server-rendered node will have skipped calling insert() if (!oldData.component) { insert(vnode); } // oldData wouldn't have a component reference set if it came from the server (it's first set in insert()) var component = oldData.component || newData.component; var oldProps = component.props; var newProps = newData.component.props; // Update the original component with any property that may have changed during this render pass component.props = newProps; newData.component = component; // If the props changed, render immediately as we are already // in the render context of our parent if (!(0, _util.shallowEqual)(oldProps, newProps)) { component.lifecycle.propsChanging = true; component.lifecycle.propsChanged(newProps); component.lifecycle.propsChanging = false; (0, _render.renderComponentNow)(component); } } function rendered(component, newVnode) { // Store the new vnode inside the component so we can diff it next render component.vnode = newVnode; // For now, only lift the hook of non Array render outputs if (newVnode && !Array.isArray(newVnode)) { // Lift any 'remove' hook to our placeholder vnode for it to be called // as the placeholder is all our parent vnode knows about. // TODO: Call all the hooks of an Array VNode? var hook = newVnode.data.hook && newVnode.data.hook.remove; if (hook) component.compVnode.data.hook.remove = hook; } } function destroy(vnode) { var comp = vnode.data.component; comp.vnode.elm.__comp__ = null; Array.isArray(comp.vnode) ? comp.vnode.forEach(destroyVnode) : destroyVnode(comp.vnode); comp.store.destroy(); comp.destroyed = true; } // Destroy our vnode recursively // Note: Can't invoke modules' destroy hook as they're hidden in snabbdom's closure. // The default modules don't do anything in destroy() anyway. function destroyVnode(vnode) { if (!vnode) return; var data = vnode.data; if (!data) return; if (data.hook && data.hook.destroy) data.hook.destroy(vnode); if (vnode.children) vnode.children.forEach(destroyVnode); } function getDepth(elm) { var depth = 0; var parent = elm.parentElement; while (parent) { depth++; parent = parent.parentElement; } return depth; }