deku
Version:
Render interfaces using pure functions and virtual DOM
137 lines (119 loc) • 4.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.insertAtIndex = undefined;
exports.default = patch;
var _setAttribute2 = require('./setAttribute');
var _element = require('../element');
var _create = require('./create');
var _create2 = _interopRequireDefault(_create);
var _diff = require('../diff');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Modify a DOM element given an array of actions. A context can be set
* that will be used to render any custom elements.
*/
function patch(dispatch, context) {
return function (DOMElement, action) {
_diff.Actions.case({
setAttribute: function setAttribute(name, value, previousValue) {
(0, _setAttribute2.setAttribute)(DOMElement, name, value, previousValue);
},
removeAttribute: function removeAttribute(name, previousValue) {
(0, _setAttribute2.removeAttribute)(DOMElement, name, previousValue);
},
insertBefore: function insertBefore(index) {
insertAtIndex(DOMElement.parentNode, index, DOMElement);
},
sameNode: function sameNode() {},
updateChildren: function updateChildren(changes) {
// Create a clone of the children so we can reference them later
// using their original position even if they move around
var childNodes = Array.prototype.slice.apply(DOMElement.childNodes);
changes.forEach(function (change) {
_diff.Actions.case({
insertChild: function insertChild(vnode, index, path) {
insertAtIndex(DOMElement, index, (0, _create2.default)(vnode, path, dispatch, context));
},
removeChild: function removeChild(index) {
DOMElement.removeChild(childNodes[index]);
},
updateChild: function updateChild(index, actions) {
var update = patch(dispatch, context);
actions.forEach(function (action) {
return update(childNodes[index], action);
});
}
}, change);
});
},
updateThunk: function updateThunk(prev, next, path) {
var props = next.props;
var children = next.children;
var component = next.component;
var onUpdate = component.onUpdate;
var render = typeof component === 'function' ? component : component.render;
var prevNode = prev.state.vnode;
var model = {
children: children,
props: props,
path: path,
dispatch: dispatch,
context: context
};
var nextNode = render(model);
var changes = (0, _diff.diffNode)(prevNode, nextNode, (0, _element.createPath)(path, '0'));
DOMElement = changes.reduce(patch(dispatch, context), DOMElement);
if (onUpdate) onUpdate(model);
next.state = {
vnode: nextNode,
model: model
};
},
replaceNode: function replaceNode(prev, next, path) {
var newEl = (0, _create2.default)(next, path, dispatch, context);
var parentEl = DOMElement.parentNode;
if (parentEl) parentEl.replaceChild(newEl, DOMElement);
DOMElement = newEl;
removeThunks(prev);
},
removeNode: function removeNode(prev) {
removeThunks(prev);
DOMElement.parentNode.removeChild(DOMElement);
DOMElement = null;
}
}, action);
return DOMElement;
};
}
/**
* Recursively remove all thunks
*/
function removeThunks(vnode) {
while ((0, _element.isThunk)(vnode)) {
var _vnode = vnode;
var component = _vnode.component;
var state = _vnode.state;
var onRemove = component.onRemove;
var model = state.model;
if (onRemove) onRemove(model);
vnode = state.vnode;
}
if (vnode.children) {
for (var i = 0; i < vnode.children.length; i++) {
removeThunks(vnode.children[i]);
}
}
}
/**
* Slightly nicer insertBefore
*/
var insertAtIndex = exports.insertAtIndex = function insertAtIndex(parent, index, el) {
var target = parent.childNodes[index];
if (target) {
parent.insertBefore(el, target);
} else {
parent.appendChild(el);
}
};