UNPKG

ingenta-lens

Version:
218 lines (182 loc) 5.18 kB
var _ = require("underscore"); var util = require("../../substance/util"); // Creates an index for the document applying a given node filter function // and grouping using a given key function // -------- // // - document: a document instance // - filter: a function that takes a node and returns true if the node should be indexed // - key: a function that provides a path for scoped indexing (default: returns empty path) // var Index = function(graph, options) { options = options || {}; this.graph = graph; this.nodes = {}; this.scopes = {}; if (options.filter) { this.filter = options.filter; } else if (options.types) { this.filter = Index.typeFilter(graph.schema, options.types); } if (options.property) { this.property = options.property; } this.createIndex(); }; Index.Prototype = function() { // Resolves a sub-hierarchy of the index via a given path // -------- // var _resolve = function(path) { var index = this; if (path !== null) { for (var i = 0; i < path.length; i++) { var id = path[i]; index.scopes[id] = index.scopes[id] || { nodes: {}, scopes: {} }; index = index.scopes[id]; } } return index; }; var _getKey = function(node) { if (!this.property) return null; var key = node[this.property] ? node[this.property] : null; if (_.isString(key)) key = [key]; return key; }; // Accumulates all indexed children of the given (sub-)index var _collect = function(index) { var result = _.extend({}, index.nodes); _.each(index.scopes, function(child, name) { if (name !== "nodes") { _.extend(result, _collect(child)); } }); return result; }; // Keeps the index up-to-date when the graph changes. // -------- // this.onGraphChange = function(op) { this.applyOp(op); }; this._add = function(node) { if (!this.filter || this.filter(node)) { var key = _getKey.call(this, node); var index = _resolve.call(this, key); index.nodes[node.id] = node.id; } }; this._remove = function(node) { if (!this.filter || this.filter(node)) { var key = _getKey.call(this, node); var index = _resolve.call(this, key); delete index.nodes[node.id]; } }; this._update = function(node, property, newValue, oldValue) { if ((this.property === property) && (!this.filter || this.filter(node))) { var key = oldValue; if (_.isString(key)) key = [key]; var index = _resolve.call(this, key); delete index.nodes[node.id]; key = newValue; index.nodes[node.id] = node.id; } }; this.applyOp = function(op) { if (op.type === "create") { this._add(op.val); } else if (op.type === "delete") { this._remove(op.val); } // type = 'update' or 'set' else { var prop = this.graph.resolve(this, op.path); var value = prop.get(); var oldValue; if (value === undefined) { return; } if (op.type === "set") { oldValue = op.original; } else { console.error("Operational updates are not supported in this implementation"); } this._update(prop.node, prop.key, value, oldValue); } }; // Initializes the index // -------- // this.createIndex = function() { this.reset(); var nodes = this.graph.nodes; _.each(nodes, function(node) { if (!this.filter || this.filter(node)) { var key = _getKey.call(this, node); var index = _resolve.call(this, key); index.nodes[node.id] = node.id; } }, this); }; // Collects all indexed nodes using a given path for scoping // -------- // this.get = function(path) { if (arguments.length === 0) { path = null; } else if (_.isString(path)) { path = [path]; } var index = _resolve.call(this, path); // EXPERIMENTAL: do we need the ability to retrieve indexed elements non-recursively // for now... // if so... we would need an paramater to prevent recursion // E.g.: // if (shallow) { // result = index.nodes; // } var collected = _collect(index); var result = new Index.Result(); _.each(collected, function(id) { result[id] = this.graph.get(id); }, this); return result; }; this.reset = function() { this.nodes = {}; this.scopes = {}; }; this.dispose = function() { this.stopListening(); }; this.rebuild = function() { this.reset(); this.createIndex(); }; }; Index.prototype = _.extend(new Index.Prototype(), util.Events.Listener); Index.typeFilter = function(schema, types) { return function(node) { var typeChain = schema.typeChain(node.type); for (var i = 0; i < types.length; i++) { if (typeChain.indexOf(types[i]) >= 0) { return true; } } return false; }; }; Index.Result = function() {}; Index.Result.prototype.asList = function() { var list = []; for (var key in this) { list.push(this[key]); } }; Index.Result.prototype.getLength = function() { return Object.keys(this).length; }; module.exports = Index;