;(function (){ "use strict"; var el = document.createElement('DIV'); if (Array.prototype.forEach) { var coreForEach = Array.prototype.forEach; var forEach = function(collection, fn) { coreForEach.call(collection, fn); }; } else { var forEach = function(collection, fn) { for (var i = 0, len = collection.length; i < len; i++) { fn(collection[i], i); } }; } if (el.getElementsByClassName === undefined) { var getElementsByClassName = function(parent, cls) { var elements = parent.getElementsByTagName('*'); var match = []; for (var i = 0, leni = elements.length; i < leni; i++) { if (hasClass(elements[i], cls)) { match.push(elements[i]); } } return match; }; } else { var getElementsByClassName = function(parent, cls) { return parent.getElementsByClassName(cls); }; } var getElementsByTagName = function(parent, tag) { return parent.getElementsByTagName(tag); }; var _ = { forEach: forEach, cls: getElementsByClassName, tag: getElementsByTagName, id: function(id) { return document.getElementById(id); } }; var Signal = function() { var self = this; this.listeners = []; this.connect = function(callback) { if (callback === undefined) { wtf(); } self.listeners.push(callback); }; this.disconnect = function(callback) { if (callback === undefined) { self.listeners = []; return; } var pos = self.listeners.indexOf(callback); if (pos === -1) { console.log('Not connected', callback); return; } self.listeners.splice(pos, 1); }; this.send = function() { var sendArguments = arguments; _.forEach(self.listeners, function(listener) { listener.apply(this, sendArguments); }); }; }; var E = (function () { var P = function(name, value) { var name = name.split('='); this.name = name[0]; this.propname = name[1]; this.value = value; }; return function(name, propname, value) { return new P(name, propname, value); } }()); var Etree = (function () { var getText = function(node) { return node.textContent; }; var setText = function(node, value) { node.textContent = value; }; var getHTML = function(node) { return node.innerHTML; }; var setHTML = function(node, value) { node.innerHTML = value; }; var getAttribute = function(node, attribute) { return node.getAttribute(attribute); }; var setAttribute = function(node, attribute, value) { node.setAttribute(attribute, value); }; var setData = function(self, data) { for (var key in data) { if (_.has(data, key) && _.has(self.set, key)) { self.set[key](data[key]); } } }; return function(root, initialData) { var getters = {}; var setters = {}; var elements = {}; var elementNumber = 0; var valueAccessors = {}; var buildElement = function(nodeData, parent) { if (nodeData.name === '#text') { var node = document.createTextNode(nodeData.value); if (nodeData.propname !== undefined) { valueAccessors[nodeData.propname] = [elementNumber, getText, setText]; } elementNumber++; parent.appendChild(node); } else if (nodeData.name[0] === '@') { var attribute = nodeData.name.substr(1) parent.setAttribute(attribute, nodeData.value); if (nodeData.propname !== undefined) { valueAccessors[nodeData.propname] = [elementNumber - 1, function(node) { return getAttribute(node, attribute); }, function(node, value) { return setAttribute(node, attribute, value); }]; } } else { var node = document.createElement(nodeData.name); if (nodeData.propname !== undefined) { valueAccessors[nodeData.propname] = [elementNumber, getHTML, setHTML]; } elementNumber++; if (nodeData.value) { _.forEach(nodeData.value, function(subElement) { buildElement(subElement, node) }); } if (parent) { parent.appendChild(node); } return node; } }; if (root.cache) { var tree = root.cache.tree.cloneNode(true); var valueAccessors = root.cache.accessors; } else { var tree = buildElement(root); root.cache = root.cache || {}; root.cache.tree = tree.cloneNode(true); root.cache.accessors = valueAccessors; } var flatNodesList = []; var flatNodes = function(root) { flatNodesList.push(root); if (root.childNodes) { _.forEach(root.childNodes, flatNodes); } }; flatNodes(tree); for (var propname in valueAccessors) { var accessors = valueAccessors[propname]; (function (accessors) { var node = flatNodesList[accessors[0]]; getters[propname] = function() { return accessors[1](node); } setters[propname] = function(value) { return accessors[2](node, value); } elements[propname] = node; }(accessors)); } var component = { tree: tree, get: getters, set: setters, elements: elements }; component.setData = function(data) { setData(component, data); }; if (initialData !== undefined) { component.setData(initialData); } return component; } }()); var ModelInstance = function(context) { var self = this; this.context = context; this.signals = { changed: new Signal() }; this.updateContext = function(context) { self.context = context; this.signals.changed.send(self); }; }; var ListModel = function() { var self = this; this.list = []; this.signals = { inserted: new Signal(), removed: new Signal() }; this.insert = function(instance, position) { self.list.splice(position, 0, instance); self.signals.inserted.send({ position: position, instance: instance, moved: false }); }; this.move = function(positionFrom, positionTo) { var moved = {from: positionFrom, to: positionTo}; var instance = self.list[positionFrom]; self.list.splice(positionFrom, 1); self.signals.removed.send({ position: positionFrom, instance: instance, moved: moved }); self.list.splice(positionTo, 0, instance); self.signals.inserted.send({ position: positionTo, instance: instance, moved: moved }); }; this.remove = function(position) { var instance = self.list[position]; self.list.splice(position, 1); self.signals.removed.send({position: position, instance: instance, moved: false}); }; this.indexOf = function(instance) { return self.list.indexOf(instance); }; this.has = function(instance) { return self.list.indexOf(instance) !== -1; }; }; var SortedListModel = function(compareFunction) { var self = this; var model = new ListModel(); this.list = model.list; this.signals = { inserted: model.signals.inserted, removed: model.signals.removed }; this.insert = function(instance) { var position = self.findPosition(instance); model.insert(instance, position); instance.signals.changed.connect(self.moveSortedInstance); }; this.remove= function(instance) { if (!model.has(instance)) { return; } model.remove(model.indexOf(instance)); instance.signals.changed.disconnect(self.moveSortedInstance); }; this.findPosition = function(instance) { for (var i = 0, leni = self.list.length; i < leni; i++) { if (compareFunction(self.list[i], instance) > 0) { return i; } } return self.list.length; }; this.moveSortedPosition = function(position) { var instance = self.list[position]; self.list.splice(position, 1); var targetPosition = self.findPosition(instance); self.list.splice(position, 0, instance); if (position !== targetPosition) { model.move(position, targetPosition); } }; this.moveSortedInstance = function(instance) { self.moveSortedPosition(model.indexOf(instance)); }; this.indexOf = function(instance) { return model.indexOf(instance); }; this.has = function(instance) { return self.list.indexOf(instance) !== -1; }; }; var AutoListModel = function(options) { var self = this; var diffAdded = function(item, index) { self.model.insert(item, index); }; var diffDeleted = function(item, index) { self.model.remove(index); }; var diffMoved = function(pos_from, pos_to) { self.model.move(pos_from, pos_to); }; var o = { added: diffAdded, deleted: diffDeleted, moved: diffMoved }; for (var k in options) { if (!k in o) { o[k] = options[k]; } else { var old = o[k]; var custom = options[k]; (function (old, custom) { o[k] = function() { custom.apply(null, arguments); old.apply(self, arguments); } }(old, custom)); } } if (!_.has(o, 'getId')) { if (o.wrapModel) { o.getId = function(item) { return item.context.id; }; } else { o.getId = function(item) { return item.id; }; } } if (o.wrapModel) { o.applyChanges = function(oldList, newList) { for (var i = 0, leni = oldList.length; i < leni; i++) { var oldInstance = oldList[i]; var newInstance = newList[i]; if (!_.isEqual(oldInstance.context, newInstance.context)) { oldInstance.updateContext(newInstance.context); } } }; } this.model = new ListModel(); this.differ = new ListDiffer(o); this.list = this.model.list; this.setList = function(list) { var wrappedList = list; if (o.wrapModel) { wrappedList = []; _.forEach(list, function(item) { wrappedList.push(new ModelInstance(item)); }); } self.differ.setList(wrappedList); }; this.signals = { inserted: this.model.signals.inserted, removed: this.model.signals.removed }; }; var ListView = function(element, model, widget) { var self = this; var instances = []; var widgets = []; var movingWidget = undefined; this.destroy = function() { model.signals.inserted.disconnect(self.insert); model.signals.removed.disconnect(self.remove); while (instances.length) { self.remove({position: 0}); } }; this.insert = function(data) { var append = data.position === instances.length; instances.splice(data.position, 0, data.instance); if (movingWidget === undefined) { var widgetInstance = new widget(); widgetInstance.modelInstance = model; } else { var widgetInstance = movingWidget; movingWidget = undefined; } if (!append) { var old = widgets[data.position]; } if (!data.move) { widgetInstance.construct(data.instance); } widgets.splice(data.position, 0, widgetInstance); if (append) { element.appendChild(widgetInstance.component.tree); } else { element.insertBefore(widgetInstance.component.tree, old.component.tree); } }; this.remove = function(data) { instances.splice(data.position, 1); var widgetInstance = widgets[data.position]; if (widgetInstance.component.tree.parentNode) { widgetInstance.component.tree.parentNode.removeChild(widgetInstance.component.tree); } if (data.move) { movingWidget = widgetInstance; } else { widgetInstance.destroy(); } widgets.splice(data.position, 1); } _.forEach(model.list, function(data) { self.insert({ position: instances.length, instance: data, move: false }); }); model.signals.inserted.connect(this.insert); model.signals.removed.connect(this.remove); }; var dummyFunction = function() {}; var Widget = function(options) { return function () { var self = this; var constructWidget = dummyFunction; var destroyWidget = dummyFunction; var updateWidget = dummyFunction; this.options = options; this.template = options.template; this.component = undefined; this.data = undefined; this.modelInstance = undefined; if (options.construct !== undefined) { constructWidget = options.construct.bind(this); } if (options.destroy !== undefined) { destroyWidget = options.destroy.bind(this); } if (options.update !== undefined) { updateWidget = options.update.bind(this); } this.construct = function(data) { self.component = Etree(self.template); if (data === undefined) { self.data = {}; } else { self.update(data); } if (self.data.signals !== undefined) { data.signals.changed.connect(self.update); } constructWidget(data); return self.component; }; this.destroy = function() { destroyWidget(); if (self.data.signals !== undefined) { self.data.signals.changed.disconnect(self.update); } self.data = undefined; self.component = undefined self.modelInstance = undefined; }; this.update = function(data) { updateWidget(data); self.data = data; }; }; }; var ListDiffer = function(options) { var o = { initial: [], added: function() {}, deleted: function() {}, moved: function() {}, applyChanges: function(oldList, newList) { oldList = newList; }, getId: function(item) { return item.id; } }; for (var k in options) { if (options.hasOwnProperty(k)) o[k] = options[k]; } var list = o.initial; var added = o.added; var deleted = o.deleted; var moved = o.moved; var applyChanges = o.applyChanges; var getId = o.getId; this.setList = function(newList) { var currentDict = {}; var newDict = {}; _.forEach(list, function(item) { currentDict[getId(item)] = item; }); _.forEach(newList, function(item) { newDict[getId(item)] = item; }); var toCreate = []; var toDelete = []; _.forEach(newList, function(item) { var id = getId(item); if (!_.has(currentDict, id)) { toCreate.push(id); } }); _.forEach(list, function(item) { var id = getId(item); if (!_.has(newDict, id)) { toDelete.push(id); } }); toDelete.reverse(); var indexList = []; _.forEach(list, function(item) { indexList.push(getId(item)); }); _.forEach(toDelete, function(id) { var index = indexList.indexOf(id); var item = list[index]; indexList.splice(index, 1); list.splice(index, 1); deleted(item, index); }); _.forEach(toCreate, function(id) { var item = newDict[id]; var index = list.length; indexList.push(id); list.push(item); added(item, index); }); for (var i = 0, leni = newList.length; i < leni; i++) { var newId = getId(newList[i]); if (newId !== indexList[i]) { var oldIndex = indexList.indexOf(newId); var listItem = list.splice(oldIndex, 1)[0]; list.splice(i, 0, listItem); listItem = indexList.splice(oldIndex, 1)[0]; indexList.splice(i, 0, listItem); moved(oldIndex, i); } } applyChanges(list, newList); }; }; window.Reactor = { Signal: Signal, E: E, Etree: Etree, //EtreeHighlight: EtreeHighlight, ModelInstance: ModelInstance, ListModel: ListModel, SortedListModel: SortedListModel, AutoListModel: AutoListModel, ListView: ListView, Widget: Widget, ListDiffer: ListDiffer, utils: _ }; }());