Use requirejs for clientside scripting
diff --git a/dev/js/lib/almond.js b/dev/js/lib/almond.js
new file mode 100644
index 0000000..2a06588
--- /dev/null
+++ b/dev/js/lib/almond.js
@@ -0,0 +1,430 @@
+/**
+ * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/almond for details
+ */
+//Going sloppy to avoid 'use strict' string cost, but strict practices should
+//be followed.
+/*jslint sloppy: true */
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+ var main, req, makeMap, handlers,
+ defined = {},
+ waiting = {},
+ config = {},
+ defining = {},
+ hasOwn = Object.prototype.hasOwnProperty,
+ aps = [].slice,
+ jsSuffixRegExp = /\.js$/;
+
+ function hasProp(obj, prop) {
+ return hasOwn.call(obj, prop);
+ }
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName) {
+ var nameParts, nameSegment, mapValue, foundMap, lastIndex,
+ foundI, foundStarMap, starI, i, j, part,
+ baseParts = baseName && baseName.split("/"),
+ map = config.map,
+ starMap = (map && map['*']) || {};
+
+ //Adjust any relative paths.
+ if (name && name.charAt(0) === ".") {
+ //If have a base name, try to normalize against it,
+ //otherwise, assume it is a top-level require that will
+ //be relative to baseUrl in the end.
+ if (baseName) {
+ name = name.split('/');
+ lastIndex = name.length - 1;
+
+ // Node .js allowance:
+ if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+ name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+ }
+
+ //Lop off the last part of baseParts, so that . matches the
+ //"directory" and not name of the baseName's module. For instance,
+ //baseName of "one/two/three", maps to "one/two/three.js", but we
+ //want the directory, "one/two" for this normalization.
+ name = baseParts.slice(0, baseParts.length - 1).concat(name);
+
+ //start trimDots
+ for (i = 0; i < name.length; i += 1) {
+ part = name[i];
+ if (part === ".") {
+ name.splice(i, 1);
+ i -= 1;
+ } else if (part === "..") {
+ if (i === 1 && (name[2] === '..' || name[0] === '..')) {
+ //End of the line. Keep at least one non-dot
+ //path segment at the front so it can be mapped
+ //correctly to disk. Otherwise, there is likely
+ //no path mapping for a path starting with '..'.
+ //This can still fail, but catches the most reasonable
+ //uses of ..
+ break;
+ } else if (i > 0) {
+ name.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ //end trimDots
+
+ name = name.join("/");
+ } else if (name.indexOf('./') === 0) {
+ // No baseName, so this is ID is resolved relative
+ // to baseUrl, pull off the leading dot.
+ name = name.substring(2);
+ }
+ }
+
+ //Apply map config if available.
+ if ((baseParts || starMap) && map) {
+ nameParts = name.split('/');
+
+ for (i = nameParts.length; i > 0; i -= 1) {
+ nameSegment = nameParts.slice(0, i).join("/");
+
+ if (baseParts) {
+ //Find the longest baseName segment match in the config.
+ //So, do joins on the biggest to smallest lengths of baseParts.
+ for (j = baseParts.length; j > 0; j -= 1) {
+ mapValue = map[baseParts.slice(0, j).join('/')];
+
+ //baseName segment has config, find if it has one for
+ //this name.
+ if (mapValue) {
+ mapValue = mapValue[nameSegment];
+ if (mapValue) {
+ //Match, update name to the new value.
+ foundMap = mapValue;
+ foundI = i;
+ break;
+ }
+ }
+ }
+ }
+
+ if (foundMap) {
+ break;
+ }
+
+ //Check for a star map match, but just hold on to it,
+ //if there is a shorter segment match later in a matching
+ //config, then favor over this star map.
+ if (!foundStarMap && starMap && starMap[nameSegment]) {
+ foundStarMap = starMap[nameSegment];
+ starI = i;
+ }
+ }
+
+ if (!foundMap && foundStarMap) {
+ foundMap = foundStarMap;
+ foundI = starI;
+ }
+
+ if (foundMap) {
+ nameParts.splice(0, foundI, foundMap);
+ name = nameParts.join('/');
+ }
+ }
+
+ return name;
+ }
+
+ function makeRequire(relName, forceSync) {
+ return function () {
+ //A version of a require function that passes a moduleName
+ //value for items that may need to
+ //look up paths relative to the moduleName
+ var args = aps.call(arguments, 0);
+
+ //If first arg is not require('string'), and there is only
+ //one arg, it is the array form without a callback. Insert
+ //a null so that the following concat is correct.
+ if (typeof args[0] !== 'string' && args.length === 1) {
+ args.push(null);
+ }
+ return req.apply(undef, args.concat([relName, forceSync]));
+ };
+ }
+
+ function makeNormalize(relName) {
+ return function (name) {
+ return normalize(name, relName);
+ };
+ }
+
+ function makeLoad(depName) {
+ return function (value) {
+ defined[depName] = value;
+ };
+ }
+
+ function callDep(name) {
+ if (hasProp(waiting, name)) {
+ var args = waiting[name];
+ delete waiting[name];
+ defining[name] = true;
+ main.apply(undef, args);
+ }
+
+ if (!hasProp(defined, name) && !hasProp(defining, name)) {
+ throw new Error('No ' + name);
+ }
+ return defined[name];
+ }
+
+ //Turns a plugin!resource to [plugin, resource]
+ //with the plugin being undefined if the name
+ //did not have a plugin prefix.
+ function splitPrefix(name) {
+ var prefix,
+ index = name ? name.indexOf('!') : -1;
+ if (index > -1) {
+ prefix = name.substring(0, index);
+ name = name.substring(index + 1, name.length);
+ }
+ return [prefix, name];
+ }
+
+ /**
+ * Makes a name map, normalizing the name, and using a plugin
+ * for normalization if necessary. Grabs a ref to plugin
+ * too, as an optimization.
+ */
+ makeMap = function (name, relName) {
+ var plugin,
+ parts = splitPrefix(name),
+ prefix = parts[0];
+
+ name = parts[1];
+
+ if (prefix) {
+ prefix = normalize(prefix, relName);
+ plugin = callDep(prefix);
+ }
+
+ //Normalize according
+ if (prefix) {
+ if (plugin && plugin.normalize) {
+ name = plugin.normalize(name, makeNormalize(relName));
+ } else {
+ name = normalize(name, relName);
+ }
+ } else {
+ name = normalize(name, relName);
+ parts = splitPrefix(name);
+ prefix = parts[0];
+ name = parts[1];
+ if (prefix) {
+ plugin = callDep(prefix);
+ }
+ }
+
+ //Using ridiculous property names for space reasons
+ return {
+ f: prefix ? prefix + '!' + name : name, //fullName
+ n: name,
+ pr: prefix,
+ p: plugin
+ };
+ };
+
+ function makeConfig(name) {
+ return function () {
+ return (config && config.config && config.config[name]) || {};
+ };
+ }
+
+ handlers = {
+ require: function (name) {
+ return makeRequire(name);
+ },
+ exports: function (name) {
+ var e = defined[name];
+ if (typeof e !== 'undefined') {
+ return e;
+ } else {
+ return (defined[name] = {});
+ }
+ },
+ module: function (name) {
+ return {
+ id: name,
+ uri: '',
+ exports: defined[name],
+ config: makeConfig(name)
+ };
+ }
+ };
+
+ main = function (name, deps, callback, relName) {
+ var cjsModule, depName, ret, map, i,
+ args = [],
+ callbackType = typeof callback,
+ usingExports;
+
+ //Use name if no relName
+ relName = relName || name;
+
+ //Call the callback to define the module, if necessary.
+ if (callbackType === 'undefined' || callbackType === 'function') {
+ //Pull out the defined dependencies and pass the ordered
+ //values to the callback.
+ //Default to [require, exports, module] if no deps
+ deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
+ for (i = 0; i < deps.length; i += 1) {
+ map = makeMap(deps[i], relName);
+ depName = map.f;
+
+ //Fast path CommonJS standard dependencies.
+ if (depName === "require") {
+ args[i] = handlers.require(name);
+ } else if (depName === "exports") {
+ //CommonJS module spec 1.1
+ args[i] = handlers.exports(name);
+ usingExports = true;
+ } else if (depName === "module") {
+ //CommonJS module spec 1.1
+ cjsModule = args[i] = handlers.module(name);
+ } else if (hasProp(defined, depName) ||
+ hasProp(waiting, depName) ||
+ hasProp(defining, depName)) {
+ args[i] = callDep(depName);
+ } else if (map.p) {
+ map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+ args[i] = defined[depName];
+ } else {
+ throw new Error(name + ' missing ' + depName);
+ }
+ }
+
+ ret = callback ? callback.apply(defined[name], args) : undefined;
+
+ if (name) {
+ //If setting exports via "module" is in play,
+ //favor that over return value and exports. After that,
+ //favor a non-undefined return value over exports use.
+ if (cjsModule && cjsModule.exports !== undef &&
+ cjsModule.exports !== defined[name]) {
+ defined[name] = cjsModule.exports;
+ } else if (ret !== undef || !usingExports) {
+ //Use the return value from the function.
+ defined[name] = ret;
+ }
+ }
+ } else if (name) {
+ //May just be an object definition for the module. Only
+ //worry about defining if have a module name.
+ defined[name] = callback;
+ }
+ };
+
+ requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
+ if (typeof deps === "string") {
+ if (handlers[deps]) {
+ //callback in this case is really relName
+ return handlers[deps](callback);
+ }
+ //Just return the module wanted. In this scenario, the
+ //deps arg is the module name, and second arg (if passed)
+ //is just the relName.
+ //Normalize module name, if it contains . or ..
+ return callDep(makeMap(deps, callback).f);
+ } else if (!deps.splice) {
+ //deps is a config object, not an array.
+ config = deps;
+ if (config.deps) {
+ req(config.deps, config.callback);
+ }
+ if (!callback) {
+ return;
+ }
+
+ if (callback.splice) {
+ //callback is an array, which means it is a dependency list.
+ //Adjust args if there are dependencies
+ deps = callback;
+ callback = relName;
+ relName = null;
+ } else {
+ deps = undef;
+ }
+ }
+
+ //Support require(['a'])
+ callback = callback || function () {};
+
+ //If relName is a function, it is an errback handler,
+ //so remove it.
+ if (typeof relName === 'function') {
+ relName = forceSync;
+ forceSync = alt;
+ }
+
+ //Simulate async callback;
+ if (forceSync) {
+ main(undef, deps, callback, relName);
+ } else {
+ //Using a non-zero value because of concern for what old browsers
+ //do, and latest browsers "upgrade" to 4 if lower value is used:
+ //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
+ //If want a value immediately, use require('id') instead -- something
+ //that works in almond on the global level, but not guaranteed and
+ //unlikely to work in other AMD implementations.
+ setTimeout(function () {
+ main(undef, deps, callback, relName);
+ }, 4);
+ }
+
+ return req;
+ };
+
+ /**
+ * Just drops the config on the floor, but returns req in case
+ * the config return value is used.
+ */
+ req.config = function (cfg) {
+ return req(cfg);
+ };
+
+ /**
+ * Expose module registry for debugging and tooling
+ */
+ requirejs._defined = defined;
+
+ define = function (name, deps, callback) {
+ if (typeof name !== 'string') {
+ throw new Error('See almond README: incorrect module build, no module name');
+ }
+
+ //This module may not have dependencies
+ if (!deps.splice) {
+ //deps is not an array, so probably means
+ //an object literal or factory function for
+ //the value. Adjust args.
+ callback = deps;
+ deps = [];
+ }
+
+ if (!hasProp(defined, name) && !hasProp(waiting, name)) {
+ waiting[name] = [name, deps, callback];
+ }
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+}());
diff --git a/dev/js/lib/dagre.js b/dev/js/lib/dagre.js
new file mode 100644
index 0000000..b62dde6
--- /dev/null
+++ b/dev/js/lib/dagre.js
@@ -0,0 +1,10825 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.dagre=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+Copyright (c) 2012-2014 Chris Pettitt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+module.exports = {
+ graphlib: require("./lib/graphlib"),
+
+ layout: require("./lib/layout"),
+ debug: require("./lib/debug"),
+ util: {
+ time: require("./lib/util").time,
+ notime: require("./lib/util").notime
+ },
+ version: require("./lib/version")
+};
+
+},{"./lib/debug":6,"./lib/graphlib":7,"./lib/layout":9,"./lib/util":29,"./lib/version":30}],2:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ greedyFAS = require("./greedy-fas");
+
+module.exports = {
+ run: run,
+ undo: undo
+};
+
+function run(g) {
+ var fas = (g.graph().acyclicer === "greedy"
+ ? greedyFAS(g, weightFn(g))
+ : dfsFAS(g));
+ _.each(fas, function(e) {
+ var label = g.edge(e);
+ g.removeEdge(e);
+ label.forwardName = e.name;
+ label.reversed = true;
+ g.setEdge(e.w, e.v, label, _.uniqueId("rev"));
+ });
+
+ function weightFn(g) {
+ return function(e) {
+ return g.edge(e).weight;
+ };
+ }
+}
+
+function dfsFAS(g) {
+ var fas = [],
+ stack = {},
+ visited = {};
+
+ function dfs(v) {
+ if (_.has(visited, v)) {
+ return;
+ }
+ visited[v] = true;
+ stack[v] = true;
+ _.each(g.outEdges(v), function(e) {
+ if (_.has(stack, e.w)) {
+ fas.push(e);
+ } else {
+ dfs(e.w);
+ }
+ });
+ delete stack[v];
+ }
+
+ _.each(g.nodes(), dfs);
+ return fas;
+}
+
+function undo(g) {
+ _.each(g.edges(), function(e) {
+ var label = g.edge(e);
+ if (label.reversed) {
+ g.removeEdge(e);
+
+ var forwardName = label.forwardName;
+ delete label.reversed;
+ delete label.forwardName;
+ g.setEdge(e.w, e.v, label, forwardName);
+ }
+ });
+}
+
+},{"./greedy-fas":8,"./lodash":10}],3:[function(require,module,exports){
+var _ = require("./lodash"),
+ util = require("./util");
+
+module.exports = addBorderSegments;
+
+function addBorderSegments(g) {
+ function dfs(v) {
+ var children = g.children(v),
+ node = g.node(v);
+ if (children.length) {
+ _.each(children, dfs);
+ }
+
+ if (_.has(node, "minRank")) {
+ node.borderLeft = [];
+ node.borderRight = [];
+ for (var rank = node.minRank, maxRank = node.maxRank + 1;
+ rank < maxRank;
+ ++rank) {
+ addBorderNode(g, "borderLeft", "_bl", v, node, rank);
+ addBorderNode(g, "borderRight", "_br", v, node, rank);
+ }
+ }
+ }
+
+ _.each(g.children(), dfs);
+}
+
+function addBorderNode(g, prop, prefix, sg, sgNode, rank) {
+ var label = { width: 0, height: 0, rank: rank },
+ prev = sgNode[prop][rank - 1],
+ curr = util.addDummyNode(g, "border", label, prefix);
+ sgNode[prop][rank] = curr;
+ g.setParent(curr, sg);
+ if (prev) {
+ g.setEdge(prev, curr, { weight: 1 });
+ }
+}
+
+},{"./lodash":10,"./util":29}],4:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash");
+
+module.exports = {
+ adjust: adjust,
+ undo: undo
+};
+
+function adjust(g) {
+ var rankDir = g.graph().rankdir.toLowerCase();
+ if (rankDir === "lr" || rankDir === "rl") {
+ swapWidthHeight(g);
+ }
+}
+
+function undo(g) {
+ var rankDir = g.graph().rankdir.toLowerCase();
+ if (rankDir === "bt" || rankDir === "rl") {
+ reverseY(g);
+ }
+
+ if (rankDir === "lr" || rankDir === "rl") {
+ swapXY(g);
+ swapWidthHeight(g);
+ }
+}
+
+function swapWidthHeight(g) {
+ _.each(g.nodes(), function(v) { swapWidthHeightOne(g.node(v)); });
+ _.each(g.edges(), function(e) { swapWidthHeightOne(g.edge(e)); });
+}
+
+function swapWidthHeightOne(attrs) {
+ var w = attrs.width;
+ attrs.width = attrs.height;
+ attrs.height = w;
+}
+
+function reverseY(g) {
+ _.each(g.nodes(), function(v) { reverseYOne(g.node(v)); });
+
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ _.each(edge.points, reverseYOne);
+ if (_.has(edge, "y")) {
+ reverseYOne(edge);
+ }
+ });
+}
+
+function reverseYOne(attrs) {
+ attrs.y = -attrs.y;
+}
+
+function swapXY(g) {
+ _.each(g.nodes(), function(v) { swapXYOne(g.node(v)); });
+
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ _.each(edge.points, swapXYOne);
+ if (_.has(edge, "x")) {
+ swapXYOne(edge);
+ }
+ });
+}
+
+function swapXYOne(attrs) {
+ var x = attrs.x;
+ attrs.x = attrs.y;
+ attrs.y = x;
+}
+
+},{"./lodash":10}],5:[function(require,module,exports){
+/*
+ * Simple doubly linked list implementation derived from Cormen, et al.,
+ * "Introduction to Algorithms".
+ */
+
+module.exports = List;
+
+function List() {
+ var sentinel = {};
+ sentinel._next = sentinel._prev = sentinel;
+ this._sentinel = sentinel;
+}
+
+List.prototype.dequeue = function() {
+ var sentinel = this._sentinel,
+ entry = sentinel._prev;
+ if (entry !== sentinel) {
+ unlink(entry);
+ return entry;
+ }
+};
+
+List.prototype.enqueue = function(entry) {
+ var sentinel = this._sentinel;
+ if (entry._prev && entry._next) {
+ unlink(entry);
+ }
+ entry._next = sentinel._next;
+ sentinel._next._prev = entry;
+ sentinel._next = entry;
+ entry._prev = sentinel;
+};
+
+List.prototype.toString = function() {
+ var strs = [],
+ sentinel = this._sentinel,
+ curr = sentinel._prev;
+ while (curr !== sentinel) {
+ strs.push(JSON.stringify(curr, filterOutLinks));
+ curr = curr._prev;
+ }
+ return "[" + strs.join(", ") + "]";
+};
+
+function unlink(entry) {
+ entry._prev._next = entry._next;
+ entry._next._prev = entry._prev;
+ delete entry._next;
+ delete entry._prev;
+}
+
+function filterOutLinks(k, v) {
+ if (k !== "_next" && k !== "_prev") {
+ return v;
+ }
+}
+
+},{}],6:[function(require,module,exports){
+var _ = require("./lodash"),
+ util = require("./util"),
+ Graph = require("./graphlib").Graph;
+
+module.exports = {
+ debugOrdering: debugOrdering
+};
+
+/* istanbul ignore next */
+function debugOrdering(g) {
+ var layerMatrix = util.buildLayerMatrix(g);
+
+ var h = new Graph({ compound: true, multigraph: true }).setGraph({});
+
+ _.each(g.nodes(), function(v) {
+ h.setNode(v, { label: v });
+ h.setParent(v, "layer" + g.node(v).rank);
+ });
+
+ _.each(g.edges(), function(e) {
+ h.setEdge(e.v, e.w, {}, e.name);
+ });
+
+ _.each(layerMatrix, function(layer, i) {
+ var layerV = "layer" + i;
+ h.setNode(layerV, { rank: "same" });
+ _.reduce(layer, function(u, v) {
+ h.setEdge(u, v, { style: "invis" });
+ return v;
+ });
+ });
+
+ return h;
+}
+
+},{"./graphlib":7,"./lodash":10,"./util":29}],7:[function(require,module,exports){
+/* global window */
+
+var graphlib;
+
+if (require) {
+ try {
+ graphlib = require("graphlib");
+ } catch (e) {}
+}
+
+if (!graphlib) {
+ graphlib = window.graphlib;
+}
+
+module.exports = graphlib;
+
+},{"graphlib":31}],8:[function(require,module,exports){
+var _ = require("./lodash"),
+ Graph = require("./graphlib").Graph,
+ List = require("./data/list");
+
+/*
+ * A greedy heuristic for finding a feedback arc set for a graph. A feedback
+ * arc set is a set of edges that can be removed to make a graph acyclic.
+ * The algorithm comes from: P. Eades, X. Lin, and W. F. Smyth, "A fast and
+ * effective heuristic for the feedback arc set problem." This implementation
+ * adjusts that from the paper to allow for weighted edges.
+ */
+module.exports = greedyFAS;
+
+var DEFAULT_WEIGHT_FN = _.constant(1);
+
+function greedyFAS(g, weightFn) {
+ if (g.nodeCount() <= 1) {
+ return [];
+ }
+ var state = buildState(g, weightFn || DEFAULT_WEIGHT_FN);
+ var results = doGreedyFAS(state.graph, state.buckets, state.zeroIdx);
+
+ // Expand multi-edges
+ return _.flatten(_.map(results, function(e) {
+ return g.outEdges(e.v, e.w);
+ }), true);
+}
+
+function doGreedyFAS(g, buckets, zeroIdx) {
+ var results = [],
+ sources = buckets[buckets.length - 1],
+ sinks = buckets[0];
+
+ var entry;
+ while (g.nodeCount()) {
+ while ((entry = sinks.dequeue())) { removeNode(g, buckets, zeroIdx, entry); }
+ while ((entry = sources.dequeue())) { removeNode(g, buckets, zeroIdx, entry); }
+ if (g.nodeCount()) {
+ for (var i = buckets.length - 2; i > 0; --i) {
+ entry = buckets[i].dequeue();
+ if (entry) {
+ results = results.concat(removeNode(g, buckets, zeroIdx, entry, true));
+ break;
+ }
+ }
+ }
+ }
+
+ return results;
+}
+
+function removeNode(g, buckets, zeroIdx, entry, collectPredecessors) {
+ var results = collectPredecessors ? [] : undefined;
+
+ _.each(g.inEdges(entry.v), function(edge) {
+ var weight = g.edge(edge),
+ uEntry = g.node(edge.v);
+
+ if (collectPredecessors) {
+ results.push({ v: edge.v, w: edge.w });
+ }
+
+ uEntry.out -= weight;
+ assignBucket(buckets, zeroIdx, uEntry);
+ });
+
+ _.each(g.outEdges(entry.v), function(edge) {
+ var weight = g.edge(edge),
+ w = edge.w,
+ wEntry = g.node(w);
+ wEntry["in"] -= weight;
+ assignBucket(buckets, zeroIdx, wEntry);
+ });
+
+ g.removeNode(entry.v);
+
+ return results;
+}
+
+function buildState(g, weightFn) {
+ var fasGraph = new Graph(),
+ maxIn = 0,
+ maxOut = 0;
+
+ _.each(g.nodes(), function(v) {
+ fasGraph.setNode(v, { v: v, "in": 0, out: 0 });
+ });
+
+ // Aggregate weights on nodes, but also sum the weights across multi-edges
+ // into a single edge for the fasGraph.
+ _.each(g.edges(), function(e) {
+ var prevWeight = fasGraph.edge(e.v, e.w) || 0,
+ weight = weightFn(e),
+ edgeWeight = prevWeight + weight;
+ fasGraph.setEdge(e.v, e.w, edgeWeight);
+ maxOut = Math.max(maxOut, fasGraph.node(e.v).out += weight);
+ maxIn = Math.max(maxIn, fasGraph.node(e.w)["in"] += weight);
+ });
+
+ var buckets = _.range(maxOut + maxIn + 3).map(function() { return new List(); });
+ var zeroIdx = maxIn + 1;
+
+ _.each(fasGraph.nodes(), function(v) {
+ assignBucket(buckets, zeroIdx, fasGraph.node(v));
+ });
+
+ return { graph: fasGraph, buckets: buckets, zeroIdx: zeroIdx };
+}
+
+function assignBucket(buckets, zeroIdx, entry) {
+ if (!entry.out) {
+ buckets[0].enqueue(entry);
+ } else if (!entry["in"]) {
+ buckets[buckets.length - 1].enqueue(entry);
+ } else {
+ buckets[entry.out - entry["in"] + zeroIdx].enqueue(entry);
+ }
+}
+
+},{"./data/list":5,"./graphlib":7,"./lodash":10}],9:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ acyclic = require("./acyclic"),
+ normalize = require("./normalize"),
+ rank = require("./rank"),
+ normalizeRanks = require("./util").normalizeRanks,
+ parentDummyChains = require("./parent-dummy-chains"),
+ removeEmptyRanks = require("./util").removeEmptyRanks,
+ nestingGraph = require("./nesting-graph"),
+ addBorderSegments = require("./add-border-segments"),
+ coordinateSystem = require("./coordinate-system"),
+ order = require("./order"),
+ position = require("./position"),
+ util = require("./util"),
+ Graph = require("./graphlib").Graph;
+
+module.exports = layout;
+
+function layout(g, opts) {
+ var time = opts && opts.debugTiming ? util.time : util.notime;
+ time("layout", function() {
+ var layoutGraph = time(" buildLayoutGraph",
+ function() { return buildLayoutGraph(g); });
+ time(" runLayout", function() { runLayout(layoutGraph, time); });
+ time(" updateInputGraph", function() { updateInputGraph(g, layoutGraph); });
+ });
+}
+
+function runLayout(g, time) {
+ time(" makeSpaceForEdgeLabels", function() { makeSpaceForEdgeLabels(g); });
+ time(" removeSelfEdges", function() { removeSelfEdges(g); });
+ time(" acyclic", function() { acyclic.run(g); });
+ time(" nestingGraph.run", function() { nestingGraph.run(g); });
+ time(" rank", function() { rank(util.asNonCompoundGraph(g)); });
+ time(" injectEdgeLabelProxies", function() { injectEdgeLabelProxies(g); });
+ time(" removeEmptyRanks", function() { removeEmptyRanks(g); });
+ time(" nestingGraph.cleanup", function() { nestingGraph.cleanup(g); });
+ time(" normalizeRanks", function() { normalizeRanks(g); });
+ time(" assignRankMinMax", function() { assignRankMinMax(g); });
+ time(" removeEdgeLabelProxies", function() { removeEdgeLabelProxies(g); });
+ time(" normalize.run", function() { normalize.run(g); });
+ time(" parentDummyChains", function() { parentDummyChains(g); });
+ time(" addBorderSegments", function() { addBorderSegments(g); });
+ time(" order", function() { order(g); });
+ time(" insertSelfEdges", function() { insertSelfEdges(g); });
+ time(" adjustCoordinateSystem", function() { coordinateSystem.adjust(g); });
+ time(" position", function() { position(g); });
+ time(" positionSelfEdges", function() { positionSelfEdges(g); });
+ time(" removeBorderNodes", function() { removeBorderNodes(g); });
+ time(" normalize.undo", function() { normalize.undo(g); });
+ time(" fixupEdgeLabelCoords", function() { fixupEdgeLabelCoords(g); });
+ time(" undoCoordinateSystem", function() { coordinateSystem.undo(g); });
+ time(" translateGraph", function() { translateGraph(g); });
+ time(" assignNodeIntersects", function() { assignNodeIntersects(g); });
+ time(" reversePoints", function() { reversePointsForReversedEdges(g); });
+ time(" acyclic.undo", function() { acyclic.undo(g); });
+}
+
+/*
+ * Copies final layout information from the layout graph back to the input
+ * graph. This process only copies whitelisted attributes from the layout graph
+ * to the input graph, so it serves as a good place to determine what
+ * attributes can influence layout.
+ */
+function updateInputGraph(inputGraph, layoutGraph) {
+ _.each(inputGraph.nodes(), function(v) {
+ var inputLabel = inputGraph.node(v),
+ layoutLabel = layoutGraph.node(v);
+
+ if (inputLabel) {
+ inputLabel.x = layoutLabel.x;
+ inputLabel.y = layoutLabel.y;
+
+ if (layoutGraph.children(v).length) {
+ inputLabel.width = layoutLabel.width;
+ inputLabel.height = layoutLabel.height;
+ }
+ }
+ });
+
+ _.each(inputGraph.edges(), function(e) {
+ var inputLabel = inputGraph.edge(e),
+ layoutLabel = layoutGraph.edge(e);
+
+ inputLabel.points = layoutLabel.points;
+ if (_.has(layoutLabel, "x")) {
+ inputLabel.x = layoutLabel.x;
+ inputLabel.y = layoutLabel.y;
+ }
+ });
+
+ inputGraph.graph().width = layoutGraph.graph().width;
+ inputGraph.graph().height = layoutGraph.graph().height;
+}
+
+var graphNumAttrs = ["nodesep", "edgesep", "ranksep", "marginx", "marginy"],
+ graphDefaults = { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: "tb" },
+ graphAttrs = ["acyclicer", "ranker", "rankdir", "align"],
+ nodeNumAttrs = ["width", "height"],
+ nodeDefaults = { width: 0, height: 0 },
+ edgeNumAttrs = ["minlen", "weight", "width", "height", "labeloffset"],
+ edgeDefaults = {
+ minlen: 1, weight: 1, width: 0, height: 0,
+ labeloffset: 10, labelpos: "r"
+ },
+ edgeAttrs = ["labelpos"];
+
+/*
+ * Constructs a new graph from the input graph, which can be used for layout.
+ * This process copies only whitelisted attributes from the input graph to the
+ * layout graph. Thus this function serves as a good place to determine what
+ * attributes can influence layout.
+ */
+function buildLayoutGraph(inputGraph) {
+ var g = new Graph({ multigraph: true, compound: true }),
+ graph = canonicalize(inputGraph.graph());
+
+ g.setGraph(_.merge({},
+ graphDefaults,
+ selectNumberAttrs(graph, graphNumAttrs),
+ _.pick(graph, graphAttrs)));
+
+ _.each(inputGraph.nodes(), function(v) {
+ var node = canonicalize(inputGraph.node(v));
+ g.setNode(v, _.defaults(selectNumberAttrs(node, nodeNumAttrs), nodeDefaults));
+ g.setParent(v, inputGraph.parent(v));
+ });
+
+ _.each(inputGraph.edges(), function(e) {
+ var edge = canonicalize(inputGraph.edge(e));
+ g.setEdge(e, _.merge({},
+ edgeDefaults,
+ selectNumberAttrs(edge, edgeNumAttrs),
+ _.pick(edge, edgeAttrs)));
+ });
+
+ return g;
+}
+
+/*
+ * This idea comes from the Gansner paper: to account for edge labels in our
+ * layout we split each rank in half by doubling minlen and halving ranksep.
+ * Then we can place labels at these mid-points between nodes.
+ *
+ * We also add some minimal padding to the width to push the label for the edge
+ * away from the edge itself a bit.
+ */
+function makeSpaceForEdgeLabels(g) {
+ var graph = g.graph();
+ graph.ranksep /= 2;
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ edge.minlen *= 2;
+ if (edge.labelpos.toLowerCase() !== "c") {
+ if (graph.rankdir === "TB" || graph.rankdir === "BT") {
+ edge.width += edge.labeloffset;
+ } else {
+ edge.height += edge.labeloffset;
+ }
+ }
+ });
+}
+
+/*
+ * Creates temporary dummy nodes that capture the rank in which each edge's
+ * label is going to, if it has one of non-zero width and height. We do this
+ * so that we can safely remove empty ranks while preserving balance for the
+ * label's position.
+ */
+function injectEdgeLabelProxies(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (edge.width && edge.height) {
+ var v = g.node(e.v),
+ w = g.node(e.w),
+ label = { rank: (w.rank - v.rank) / 2 + v.rank, e: e };
+ util.addDummyNode(g, "edge-proxy", label, "_ep");
+ }
+ });
+}
+
+function assignRankMinMax(g) {
+ var maxRank = 0;
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (node.borderTop) {
+ node.minRank = g.node(node.borderTop).rank;
+ node.maxRank = g.node(node.borderBottom).rank;
+ maxRank = _.max(maxRank, node.maxRank);
+ }
+ });
+ g.graph().maxRank = maxRank;
+}
+
+function removeEdgeLabelProxies(g) {
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (node.dummy === "edge-proxy") {
+ g.edge(node.e).labelRank = node.rank;
+ g.removeNode(v);
+ }
+ });
+}
+
+function translateGraph(g) {
+ var minX = Number.POSITIVE_INFINITY,
+ maxX = 0,
+ minY = Number.POSITIVE_INFINITY,
+ maxY = 0,
+ graphLabel = g.graph(),
+ marginX = graphLabel.marginx || 0,
+ marginY = graphLabel.marginy || 0;
+
+ function getExtremes(attrs) {
+ var x = attrs.x,
+ y = attrs.y,
+ w = attrs.width,
+ h = attrs.height;
+ minX = Math.min(minX, x - w / 2);
+ maxX = Math.max(maxX, x + w / 2);
+ minY = Math.min(minY, y - h / 2);
+ maxY = Math.max(maxY, y + h / 2);
+ }
+
+ _.each(g.nodes(), function(v) { getExtremes(g.node(v)); });
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (_.has(edge, "x")) {
+ getExtremes(edge);
+ }
+ });
+
+ minX -= marginX;
+ minY -= marginY;
+
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ node.x -= minX;
+ node.y -= minY;
+ });
+
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ _.each(edge.points, function(p) {
+ p.x -= minX;
+ p.y -= minY;
+ });
+ if (_.has(edge, "x")) { edge.x -= minX; }
+ if (_.has(edge, "y")) { edge.y -= minY; }
+ });
+
+ graphLabel.width = maxX - minX + marginX;
+ graphLabel.height = maxY - minY + marginY;
+}
+
+function assignNodeIntersects(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e),
+ nodeV = g.node(e.v),
+ nodeW = g.node(e.w),
+ p1, p2;
+ if (!edge.points) {
+ edge.points = [];
+ p1 = nodeW;
+ p2 = nodeV;
+ } else {
+ p1 = edge.points[0];
+ p2 = edge.points[edge.points.length - 1];
+ }
+ edge.points.unshift(util.intersectRect(nodeV, p1));
+ edge.points.push(util.intersectRect(nodeW, p2));
+ });
+}
+
+function fixupEdgeLabelCoords(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (_.has(edge, "x")) {
+ if (edge.labelpos === "l" || edge.labelpos === "r") {
+ edge.width -= edge.labeloffset;
+ }
+ switch (edge.labelpos) {
+ case "l": edge.x -= edge.width / 2 + edge.labeloffset; break;
+ case "r": edge.x += edge.width / 2 + edge.labeloffset; break;
+ }
+ }
+ });
+}
+
+function reversePointsForReversedEdges(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (edge.reversed) {
+ edge.points.reverse();
+ }
+ });
+}
+
+function removeBorderNodes(g) {
+ _.each(g.nodes(), function(v) {
+ if (g.children(v).length) {
+ var node = g.node(v),
+ t = g.node(node.borderTop),
+ b = g.node(node.borderBottom),
+ l = g.node(_.last(node.borderLeft)),
+ r = g.node(_.last(node.borderRight));
+
+ node.width = Math.abs(r.x - l.x);
+ node.height = Math.abs(b.y - t.y);
+ node.x = l.x + node.width / 2;
+ node.y = t.y + node.height / 2;
+ }
+ });
+
+ _.each(g.nodes(), function(v) {
+ if (g.node(v).dummy === "border") {
+ g.removeNode(v);
+ }
+ });
+}
+
+function removeSelfEdges(g) {
+ _.each(g.edges(), function(e) {
+ if (e.v === e.w) {
+ var node = g.node(e.v);
+ if (!node.selfEdges) {
+ node.selfEdges = [];
+ }
+ node.selfEdges.push({ e: e, label: g.edge(e) });
+ g.removeEdge(e);
+ }
+ });
+}
+
+function insertSelfEdges(g) {
+ var layers = util.buildLayerMatrix(g);
+ _.each(layers, function(layer) {
+ var orderShift = 0;
+ _.each(layer, function(v, i) {
+ var node = g.node(v);
+ node.order = i + orderShift;
+ _.each(node.selfEdges, function(selfEdge) {
+ util.addDummyNode(g, "selfedge", {
+ width: selfEdge.label.width,
+ height: selfEdge.label.height,
+ rank: node.rank,
+ order: i + (++orderShift),
+ e: selfEdge.e,
+ label: selfEdge.label
+ }, "_se");
+ });
+ delete node.selfEdges;
+ });
+ });
+}
+
+function positionSelfEdges(g) {
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (node.dummy === "selfedge") {
+ var selfNode = g.node(node.e.v),
+ x = selfNode.x + selfNode.width / 2,
+ y = selfNode.y,
+ dx = node.x - x,
+ dy = selfNode.height / 2;
+ g.setEdge(node.e, node.label);
+ g.removeNode(v);
+ node.label.points = [
+ { x: x + 2 * dx / 3, y: y - dy },
+ { x: x + 5 * dx / 6, y: y - dy },
+ { x: x + dx , y: y },
+ { x: x + 5 * dx / 6, y: y + dy },
+ { x: x + 2 * dx / 3, y: y + dy },
+ ];
+ node.label.x = node.x;
+ node.label.y = node.y;
+ }
+ });
+}
+
+function selectNumberAttrs(obj, attrs) {
+ return _.mapValues(_.pick(obj, attrs), Number);
+}
+
+function canonicalize(attrs) {
+ var newAttrs = {};
+ _.each(attrs, function(v, k) {
+ newAttrs[k.toLowerCase()] = v;
+ });
+ return newAttrs;
+}
+
+},{"./acyclic":2,"./add-border-segments":3,"./coordinate-system":4,"./graphlib":7,"./lodash":10,"./nesting-graph":11,"./normalize":12,"./order":17,"./parent-dummy-chains":22,"./position":24,"./rank":26,"./util":29}],10:[function(require,module,exports){
+/* global window */
+
+var lodash;
+
+if (require) {
+ try {
+ lodash = require("lodash");
+ } catch (e) {}
+}
+
+if (!lodash) {
+ lodash = window._;
+}
+
+module.exports = lodash;
+
+},{"lodash":51}],11:[function(require,module,exports){
+var _ = require("./lodash"),
+ util = require("./util");
+
+module.exports = {
+ run: run,
+ cleanup: cleanup
+};
+
+/*
+ * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
+ * adds appropriate edges to ensure that all cluster nodes are placed between
+ * these boundries, and ensures that the graph is connected.
+ *
+ * In addition we ensure, through the use of the minlen property, that nodes
+ * and subgraph border nodes to not end up on the same rank.
+ *
+ * Preconditions:
+ *
+ * 1. Input graph is a DAG
+ * 2. Nodes in the input graph has a minlen attribute
+ *
+ * Postconditions:
+ *
+ * 1. Input graph is connected.
+ * 2. Dummy nodes are added for the tops and bottoms of subgraphs.
+ * 3. The minlen attribute for nodes is adjusted to ensure nodes do not
+ * get placed on the same rank as subgraph border nodes.
+ *
+ * The nesting graph idea comes from Sander, "Layout of Compound Directed
+ * Graphs."
+ */
+function run(g) {
+ var root = util.addDummyNode(g, "root", {}, "_root"),
+ depths = treeDepths(g),
+ height = _.max(depths) - 1,
+ nodeSep = 2 * height + 1;
+
+ g.graph().nestingRoot = root;
+
+ // Multiply minlen by nodeSep to align nodes on non-border ranks.
+ _.each(g.edges(), function(e) { g.edge(e).minlen *= nodeSep; });
+
+ // Calculate a weight that is sufficient to keep subgraphs vertically compact
+ var weight = sumWeights(g) + 1;
+
+ // Create border nodes and link them up
+ _.each(g.children(), function(child) {
+ dfs(g, root, nodeSep, weight, height, depths, child);
+ });
+
+ // Save the multiplier for node layers for later removal of empty border
+ // layers.
+ g.graph().nodeRankFactor = nodeSep;
+}
+
+function dfs(g, root, nodeSep, weight, height, depths, v) {
+ var children = g.children(v);
+ if (!children.length) {
+ if (v !== root) {
+ g.setEdge(root, v, { weight: 0, minlen: nodeSep });
+ }
+ return;
+ }
+
+ var top = util.addBorderNode(g, "_bt"),
+ bottom = util.addBorderNode(g, "_bb"),
+ label = g.node(v);
+
+ g.setParent(top, v);
+ label.borderTop = top;
+ g.setParent(bottom, v);
+ label.borderBottom = bottom;
+
+ _.each(children, function(child) {
+ dfs(g, root, nodeSep, weight, height, depths, child);
+
+ var childNode = g.node(child),
+ childTop = childNode.borderTop ? childNode.borderTop : child,
+ childBottom = childNode.borderBottom ? childNode.borderBottom : child,
+ thisWeight = childNode.borderTop ? weight : 2 * weight,
+ minlen = childTop !== childBottom ? 1 : height - depths[v] + 1;
+
+ g.setEdge(top, childTop, {
+ weight: thisWeight,
+ minlen: minlen,
+ nestingEdge: true
+ });
+
+ g.setEdge(childBottom, bottom, {
+ weight: thisWeight,
+ minlen: minlen,
+ nestingEdge: true
+ });
+ });
+
+ if (!g.parent(v)) {
+ g.setEdge(root, top, { weight: 0, minlen: height + depths[v] });
+ }
+}
+
+function treeDepths(g) {
+ var depths = {};
+ function dfs(v, depth) {
+ var children = g.children(v);
+ if (children && children.length) {
+ _.each(children, function(child) {
+ dfs(child, depth + 1);
+ });
+ }
+ depths[v] = depth;
+ }
+ _.each(g.children(), function(v) { dfs(v, 1); });
+ return depths;
+}
+
+function sumWeights(g) {
+ return _.reduce(g.edges(), function(acc, e) {
+ return acc + g.edge(e).weight;
+ }, 0);
+}
+
+function cleanup(g) {
+ var graphLabel = g.graph();
+ g.removeNode(graphLabel.nestingRoot);
+ delete graphLabel.nestingRoot;
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (edge.nestingEdge) {
+ g.removeEdge(e);
+ }
+ });
+}
+
+},{"./lodash":10,"./util":29}],12:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ util = require("./util");
+
+module.exports = {
+ run: run,
+ undo: undo
+};
+
+/*
+ * Breaks any long edges in the graph into short segments that span 1 layer
+ * each. This operation is undoable with the denormalize function.
+ *
+ * Pre-conditions:
+ *
+ * 1. The input graph is a DAG.
+ * 2. Each node in the graph has a "rank" property.
+ *
+ * Post-condition:
+ *
+ * 1. All edges in the graph have a length of 1.
+ * 2. Dummy nodes are added where edges have been split into segments.
+ * 3. The graph is augmented with a "dummyChains" attribute which contains
+ * the first dummy in each chain of dummy nodes produced.
+ */
+function run(g) {
+ g.graph().dummyChains = [];
+ _.each(g.edges(), function(edge) { normalizeEdge(g, edge); });
+}
+
+function normalizeEdge(g, e) {
+ var v = e.v,
+ vRank = g.node(v).rank,
+ w = e.w,
+ wRank = g.node(w).rank,
+ name = e.name,
+ edgeLabel = g.edge(e),
+ labelRank = edgeLabel.labelRank;
+
+ if (wRank === vRank + 1) return;
+
+ g.removeEdge(e);
+
+ var dummy, attrs, i;
+ for (i = 0, ++vRank; vRank < wRank; ++i, ++vRank) {
+ edgeLabel.points = [];
+ attrs = {
+ width: 0, height: 0,
+ edgeLabel: edgeLabel, edgeObj: e,
+ rank: vRank
+ };
+ dummy = util.addDummyNode(g, "edge", attrs, "_d");
+ if (vRank === labelRank) {
+ attrs.width = edgeLabel.width;
+ attrs.height = edgeLabel.height;
+ attrs.dummy = "edge-label";
+ attrs.labelpos = edgeLabel.labelpos;
+ }
+ g.setEdge(v, dummy, { weight: edgeLabel.weight }, name);
+ if (i === 0) {
+ g.graph().dummyChains.push(dummy);
+ }
+ v = dummy;
+ }
+
+ g.setEdge(v, w, { weight: edgeLabel.weight }, name);
+}
+
+function undo(g) {
+ _.each(g.graph().dummyChains, function(v) {
+ var node = g.node(v),
+ origLabel = node.edgeLabel,
+ w;
+ g.setEdge(node.edgeObj, origLabel);
+ while (node.dummy) {
+ w = g.successors(v)[0];
+ g.removeNode(v);
+ origLabel.points.push({ x: node.x, y: node.y });
+ if (node.dummy === "edge-label") {
+ origLabel.x = node.x;
+ origLabel.y = node.y;
+ origLabel.width = node.width;
+ origLabel.height = node.height;
+ }
+ v = w;
+ node = g.node(v);
+ }
+ });
+}
+
+},{"./lodash":10,"./util":29}],13:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = addSubgraphConstraints;
+
+function addSubgraphConstraints(g, cg, vs) {
+ var prev = {},
+ rootPrev;
+
+ _.each(vs, function(v) {
+ var child = g.parent(v),
+ parent,
+ prevChild;
+ while (child) {
+ parent = g.parent(child);
+ if (parent) {
+ prevChild = prev[parent];
+ prev[parent] = child;
+ } else {
+ prevChild = rootPrev;
+ rootPrev = child;
+ }
+ if (prevChild && prevChild !== child) {
+ cg.setEdge(prevChild, child);
+ return;
+ }
+ child = parent;
+ }
+ });
+
+ /*
+ function dfs(v) {
+ var children = v ? g.children(v) : g.children();
+ if (children.length) {
+ var min = Number.POSITIVE_INFINITY,
+ subgraphs = [];
+ _.each(children, function(child) {
+ var childMin = dfs(child);
+ if (g.children(child).length) {
+ subgraphs.push({ v: child, order: childMin });
+ }
+ min = Math.min(min, childMin);
+ });
+ _.reduce(_.sortBy(subgraphs, "order"), function(prev, curr) {
+ cg.setEdge(prev.v, curr.v);
+ return curr;
+ });
+ return min;
+ }
+ return g.node(v).order;
+ }
+ dfs(undefined);
+ */
+}
+
+},{"../lodash":10}],14:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = barycenter;
+
+function barycenter(g, movable) {
+ return _.map(movable, function(v) {
+ var inV = g.inEdges(v);
+ if (!inV.length) {
+ return { v: v };
+ } else {
+ var result = _.reduce(inV, function(acc, e) {
+ var edge = g.edge(e),
+ nodeU = g.node(e.v);
+ return {
+ sum: acc.sum + (edge.weight * nodeU.order),
+ weight: acc.weight + edge.weight
+ };
+ }, { sum: 0, weight: 0 });
+
+ return {
+ v: v,
+ barycenter: result.sum / result.weight,
+ weight: result.weight
+ };
+ }
+ });
+}
+
+
+},{"../lodash":10}],15:[function(require,module,exports){
+var _ = require("../lodash"),
+ Graph = require("../graphlib").Graph;
+
+module.exports = buildLayerGraph;
+
+/*
+ * Constructs a graph that can be used to sort a layer of nodes. The graph will
+ * contain all base and subgraph nodes from the request layer in their original
+ * hierarchy and any edges that are incident on these nodes and are of the type
+ * requested by the "relationship" parameter.
+ *
+ * Nodes from the requested rank that do not have parents are assigned a root
+ * node in the output graph, which is set in the root graph attribute. This
+ * makes it easy to walk the hierarchy of movable nodes during ordering.
+ *
+ * Pre-conditions:
+ *
+ * 1. Input graph is a DAG
+ * 2. Base nodes in the input graph have a rank attribute
+ * 3. Subgraph nodes in the input graph has minRank and maxRank attributes
+ * 4. Edges have an assigned weight
+ *
+ * Post-conditions:
+ *
+ * 1. Output graph has all nodes in the movable rank with preserved
+ * hierarchy.
+ * 2. Root nodes in the movable layer are made children of the node
+ * indicated by the root attribute of the graph.
+ * 3. Non-movable nodes incident on movable nodes, selected by the
+ * relationship parameter, are included in the graph (without hierarchy).
+ * 4. Edges incident on movable nodes, selected by the relationship
+ * parameter, are added to the output graph.
+ * 5. The weights for copied edges are aggregated as need, since the output
+ * graph is not a multi-graph.
+ */
+function buildLayerGraph(g, rank, relationship) {
+ var root = createRootNode(g),
+ result = new Graph({ compound: true }).setGraph({ root: root })
+ .setDefaultNodeLabel(function(v) { return g.node(v); });
+
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v),
+ parent = g.parent(v);
+
+ if (node.rank === rank || node.minRank <= rank && rank <= node.maxRank) {
+ result.setNode(v);
+ result.setParent(v, parent || root);
+
+ // This assumes we have only short edges!
+ _.each(g[relationship](v), function(e) {
+ var u = e.v === v ? e.w : e.v,
+ edge = result.edge(u, v),
+ weight = !_.isUndefined(edge) ? edge.weight : 0;
+ result.setEdge(u, v, { weight: g.edge(e).weight + weight });
+ });
+
+ if (_.has(node, "minRank")) {
+ result.setNode(v, {
+ borderLeft: node.borderLeft[rank],
+ borderRight: node.borderRight[rank]
+ });
+ }
+ }
+ });
+
+ return result;
+}
+
+function createRootNode(g) {
+ var v;
+ while (g.hasNode((v = _.uniqueId("_root"))));
+ return v;
+}
+
+},{"../graphlib":7,"../lodash":10}],16:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = crossCount;
+
+/*
+ * A function that takes a layering (an array of layers, each with an array of
+ * ordererd nodes) and a graph and returns a weighted crossing count.
+ *
+ * Pre-conditions:
+ *
+ * 1. Input graph must be simple (not a multigraph), directed, and include
+ * only simple edges.
+ * 2. Edges in the input graph must have assigned weights.
+ *
+ * Post-conditions:
+ *
+ * 1. The graph and layering matrix are left unchanged.
+ *
+ * This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
+ */
+function crossCount(g, layering) {
+ var cc = 0;
+ for (var i = 1; i < layering.length; ++i) {
+ cc += twoLayerCrossCount(g, layering[i-1], layering[i]);
+ }
+ return cc;
+}
+
+function twoLayerCrossCount(g, northLayer, southLayer) {
+ // Sort all of the edges between the north and south layers by their position
+ // in the north layer and then the south. Map these edges to the position of
+ // their head in the south layer.
+ var southPos = _.zipObject(southLayer,
+ _.map(southLayer, function (v, i) { return i; }));
+ var southEntries = _.flatten(_.map(northLayer, function(v) {
+ return _.chain(g.outEdges(v))
+ .map(function(e) {
+ return { pos: southPos[e.w], weight: g.edge(e).weight };
+ })
+ .sortBy("pos")
+ .value();
+ }), true);
+
+ // Build the accumulator tree
+ var firstIndex = 1;
+ while (firstIndex < southLayer.length) firstIndex <<= 1;
+ var treeSize = 2 * firstIndex - 1;
+ firstIndex -= 1;
+ var tree = _.map(new Array(treeSize), function() { return 0; });
+
+ // Calculate the weighted crossings
+ var cc = 0;
+ _.each(southEntries.forEach(function(entry) {
+ var index = entry.pos + firstIndex;
+ tree[index] += entry.weight;
+ var weightSum = 0;
+ while (index > 0) {
+ if (index % 2) {
+ weightSum += tree[index + 1];
+ }
+ index = (index - 1) >> 1;
+ tree[index] += entry.weight;
+ }
+ cc += entry.weight * weightSum;
+ }));
+
+ return cc;
+}
+
+},{"../lodash":10}],17:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ initOrder = require("./init-order"),
+ crossCount = require("./cross-count"),
+ sortSubgraph = require("./sort-subgraph"),
+ buildLayerGraph = require("./build-layer-graph"),
+ addSubgraphConstraints = require("./add-subgraph-constraints"),
+ Graph = require("../graphlib").Graph,
+ util = require("../util");
+
+module.exports = order;
+
+/*
+ * Applies heuristics to minimize edge crossings in the graph and sets the best
+ * order solution as an order attribute on each node.
+ *
+ * Pre-conditions:
+ *
+ * 1. Graph must be DAG
+ * 2. Graph nodes must be objects with a "rank" attribute
+ * 3. Graph edges must have the "weight" attribute
+ *
+ * Post-conditions:
+ *
+ * 1. Graph nodes will have an "order" attribute based on the results of the
+ * algorithm.
+ */
+function order(g) {
+ var maxRank = util.maxRank(g),
+ downLayerGraphs = buildLayerGraphs(g, _.range(1, maxRank + 1), "inEdges"),
+ upLayerGraphs = buildLayerGraphs(g, _.range(maxRank - 1, -1, -1), "outEdges");
+
+ var layering = initOrder(g);
+ assignOrder(g, layering);
+
+ var bestCC = Number.POSITIVE_INFINITY,
+ best;
+
+ for (var i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
+ sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);
+
+ layering = util.buildLayerMatrix(g);
+ var cc = crossCount(g, layering);
+ if (cc < bestCC) {
+ lastBest = 0;
+ best = _.cloneDeep(layering);
+ bestCC = cc;
+ }
+ }
+
+ assignOrder(g, best);
+}
+
+function buildLayerGraphs(g, ranks, relationship) {
+ return _.map(ranks, function(rank) {
+ return buildLayerGraph(g, rank, relationship);
+ });
+}
+
+function sweepLayerGraphs(layerGraphs, biasRight) {
+ var cg = new Graph();
+ _.each(layerGraphs, function(lg) {
+ var root = lg.graph().root;
+ var sorted = sortSubgraph(lg, root, cg, biasRight);
+ _.each(sorted.vs, function(v, i) {
+ lg.node(v).order = i;
+ });
+ addSubgraphConstraints(lg, cg, sorted.vs);
+ });
+}
+
+function assignOrder(g, layering) {
+ _.each(layering, function(layer) {
+ _.each(layer, function(v, i) {
+ g.node(v).order = i;
+ });
+ });
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29,"./add-subgraph-constraints":13,"./build-layer-graph":15,"./cross-count":16,"./init-order":18,"./sort-subgraph":20}],18:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = initOrder;
+
+/*
+ * Assigns an initial order value for each node by performing a DFS search
+ * starting from nodes in the first rank. Nodes are assigned an order in their
+ * rank as they are first visited.
+ *
+ * This approach comes from Gansner, et al., "A Technique for Drawing Directed
+ * Graphs."
+ *
+ * Returns a layering matrix with an array per layer and each layer sorted by
+ * the order of its nodes.
+ */
+function initOrder(g) {
+ var visited = {},
+ simpleNodes = _.filter(g.nodes(), function(v) {
+ return !g.children(v).length;
+ }),
+ maxRank = _.max(_.map(simpleNodes, function(v) { return g.node(v).rank; })),
+ layers = _.map(_.range(maxRank + 1), function() { return []; });
+
+ function dfs(v) {
+ if (_.has(visited, v)) return;
+ visited[v] = true;
+ var node = g.node(v);
+ layers[node.rank].push(v);
+ _.each(g.successors(v), dfs);
+ }
+
+ var orderedVs = _.sortBy(simpleNodes, function(v) { return g.node(v).rank; });
+ _.each(orderedVs, dfs);
+
+ return layers;
+}
+
+},{"../lodash":10}],19:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = resolveConflicts;
+
+/*
+ * Given a list of entries of the form {v, barycenter, weight} and a
+ * constraint graph this function will resolve any conflicts between the
+ * constraint graph and the barycenters for the entries. If the barycenters for
+ * an entry would violate a constraint in the constraint graph then we coalesce
+ * the nodes in the conflict into a new node that respects the contraint and
+ * aggregates barycenter and weight information.
+ *
+ * This implementation is based on the description in Forster, "A Fast and
+ * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
+ * differs in some specific details.
+ *
+ * Pre-conditions:
+ *
+ * 1. Each entry has the form {v, barycenter, weight}, or if the node has
+ * no barycenter, then {v}.
+ *
+ * Returns:
+ *
+ * A new list of entries of the form {vs, i, barycenter, weight}. The list
+ * `vs` may either be a singleton or it may be an aggregation of nodes
+ * ordered such that they do not violate constraints from the constraint
+ * graph. The property `i` is the lowest original index of any of the
+ * elements in `vs`.
+ */
+function resolveConflicts(entries, cg) {
+ var mappedEntries = {};
+ _.each(entries, function(entry, i) {
+ var tmp = mappedEntries[entry.v] = {
+ indegree: 0,
+ "in": [],
+ out: [],
+ vs: [entry.v],
+ i: i
+ };
+ if (!_.isUndefined(entry.barycenter)) {
+ tmp.barycenter = entry.barycenter;
+ tmp.weight = entry.weight;
+ }
+ });
+
+ _.each(cg.edges(), function(e) {
+ var entryV = mappedEntries[e.v],
+ entryW = mappedEntries[e.w];
+ if (!_.isUndefined(entryV) && !_.isUndefined(entryW)) {
+ entryW.indegree++;
+ entryV.out.push(mappedEntries[e.w]);
+ }
+ });
+
+ var sourceSet = _.filter(mappedEntries, function(entry) {
+ return !entry.indegree;
+ });
+
+ return doResolveConflicts(sourceSet);
+}
+
+function doResolveConflicts(sourceSet) {
+ var entries = [];
+
+ function handleIn(vEntry) {
+ return function(uEntry) {
+ if (uEntry.merged) {
+ return;
+ }
+ if (_.isUndefined(uEntry.barycenter) ||
+ _.isUndefined(vEntry.barycenter) ||
+ uEntry.barycenter >= vEntry.barycenter) {
+ mergeEntries(vEntry, uEntry);
+ }
+ };
+ }
+
+ function handleOut(vEntry) {
+ return function(wEntry) {
+ wEntry["in"].push(vEntry);
+ if (--wEntry.indegree === 0) {
+ sourceSet.push(wEntry);
+ }
+ };
+ }
+
+ while (sourceSet.length) {
+ var entry = sourceSet.pop();
+ entries.push(entry);
+ _.each(entry["in"].reverse(), handleIn(entry));
+ _.each(entry.out, handleOut(entry));
+ }
+
+ return _.chain(entries)
+ .filter(function(entry) { return !entry.merged; })
+ .map(function(entry) {
+ return _.pick(entry, ["vs", "i", "barycenter", "weight"]);
+ })
+ .value();
+}
+
+function mergeEntries(target, source) {
+ var sum = 0,
+ weight = 0;
+
+ if (target.weight) {
+ sum += target.barycenter * target.weight;
+ weight += target.weight;
+ }
+
+ if (source.weight) {
+ sum += source.barycenter * source.weight;
+ weight += source.weight;
+ }
+
+ target.vs = source.vs.concat(target.vs);
+ target.barycenter = sum / weight;
+ target.weight = weight;
+ target.i = Math.min(source.i, target.i);
+ source.merged = true;
+}
+
+},{"../lodash":10}],20:[function(require,module,exports){
+var _ = require("../lodash"),
+ barycenter = require("./barycenter"),
+ resolveConflicts = require("./resolve-conflicts"),
+ sort = require("./sort");
+
+module.exports = sortSubgraph;
+
+function sortSubgraph(g, v, cg, biasRight) {
+ var movable = g.children(v),
+ node = g.node(v),
+ bl = node ? node.borderLeft : undefined,
+ br = node ? node.borderRight: undefined,
+ subgraphs = {};
+
+ if (bl) {
+ movable = _.filter(movable, function(w) {
+ return w !== bl && w !== br;
+ });
+ }
+
+ var barycenters = barycenter(g, movable);
+ _.each(barycenters, function(entry) {
+ if (g.children(entry.v).length) {
+ var subgraphResult = sortSubgraph(g, entry.v, cg, biasRight);
+ subgraphs[entry.v] = subgraphResult;
+ if (_.has(subgraphResult, "barycenter")) {
+ mergeBarycenters(entry, subgraphResult);
+ }
+ }
+ });
+
+ var entries = resolveConflicts(barycenters, cg);
+ expandSubgraphs(entries, subgraphs);
+
+ var result = sort(entries, biasRight);
+
+ if (bl) {
+ result.vs = _.flatten([bl, result.vs, br], true);
+ if (g.predecessors(bl).length) {
+ var blPred = g.node(g.predecessors(bl)[0]),
+ brPred = g.node(g.predecessors(br)[0]);
+ if (!_.has(result, "barycenter")) {
+ result.barycenter = 0;
+ result.weight = 0;
+ }
+ result.barycenter = (result.barycenter * result.weight +
+ blPred.order + brPred.order) / (result.weight + 2);
+ result.weight += 2;
+ }
+ }
+
+ return result;
+}
+
+function expandSubgraphs(entries, subgraphs) {
+ _.each(entries, function(entry) {
+ entry.vs = _.flatten(entry.vs.map(function(v) {
+ if (subgraphs[v]) {
+ return subgraphs[v].vs;
+ }
+ return v;
+ }), true);
+ });
+}
+
+function mergeBarycenters(target, other) {
+ if (!_.isUndefined(target.barycenter)) {
+ target.barycenter = (target.barycenter * target.weight +
+ other.barycenter * other.weight) /
+ (target.weight + other.weight);
+ target.weight += other.weight;
+ } else {
+ target.barycenter = other.barycenter;
+ target.weight = other.weight;
+ }
+}
+
+},{"../lodash":10,"./barycenter":14,"./resolve-conflicts":19,"./sort":21}],21:[function(require,module,exports){
+var _ = require("../lodash"),
+ util = require("../util");
+
+module.exports = sort;
+
+function sort(entries, biasRight) {
+ var parts = util.partition(entries, function(entry) {
+ return _.has(entry, "barycenter");
+ });
+ var sortable = parts.lhs,
+ unsortable = _.sortBy(parts.rhs, function(entry) { return -entry.i; }),
+ vs = [],
+ sum = 0,
+ weight = 0,
+ vsIndex = 0;
+
+ sortable.sort(compareWithBias(!!biasRight));
+
+ vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
+
+ _.each(sortable, function (entry) {
+ vsIndex += entry.vs.length;
+ vs.push(entry.vs);
+ sum += entry.barycenter * entry.weight;
+ weight += entry.weight;
+ vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
+ });
+
+ var result = { vs: _.flatten(vs, true) };
+ if (weight) {
+ result.barycenter = sum / weight;
+ result.weight = weight;
+ }
+ return result;
+}
+
+function consumeUnsortable(vs, unsortable, index) {
+ var last;
+ while (unsortable.length && (last = _.last(unsortable)).i <= index) {
+ unsortable.pop();
+ vs.push(last.vs);
+ index++;
+ }
+ return index;
+}
+
+function compareWithBias(bias) {
+ return function(entryV, entryW) {
+ if (entryV.barycenter < entryW.barycenter) {
+ return -1;
+ } else if (entryV.barycenter > entryW.barycenter) {
+ return 1;
+ }
+
+ return !bias ? entryV.i - entryW.i : entryW.i - entryV.i;
+ };
+}
+
+},{"../lodash":10,"../util":29}],22:[function(require,module,exports){
+var _ = require("./lodash");
+
+module.exports = parentDummyChains;
+
+function parentDummyChains(g) {
+ var postorderNums = postorder(g);
+
+ _.each(g.graph().dummyChains, function(v) {
+ var node = g.node(v),
+ edgeObj = node.edgeObj,
+ pathData = findPath(g, postorderNums, edgeObj.v, edgeObj.w),
+ path = pathData.path,
+ lca = pathData.lca,
+ pathIdx = 0,
+ pathV = path[pathIdx],
+ ascending = true;
+
+ while (v !== edgeObj.w) {
+ node = g.node(v);
+
+ if (ascending) {
+ while ((pathV = path[pathIdx]) !== lca &&
+ g.node(pathV).maxRank < node.rank) {
+ pathIdx++;
+ }
+
+ if (pathV === lca) {
+ ascending = false;
+ }
+ }
+
+ if (!ascending) {
+ while (pathIdx < path.length - 1 &&
+ g.node(pathV = path[pathIdx + 1]).minRank <= node.rank) {
+ pathIdx++;
+ }
+ pathV = path[pathIdx];
+ }
+
+ g.setParent(v, pathV);
+ v = g.successors(v)[0];
+ }
+ });
+}
+
+// Find a path from v to w through the lowest common ancestor (LCA). Return the
+// full path and the LCA.
+function findPath(g, postorderNums, v, w) {
+ var vPath = [],
+ wPath = [],
+ low = Math.min(postorderNums[v].low, postorderNums[w].low),
+ lim = Math.max(postorderNums[v].lim, postorderNums[w].lim),
+ parent,
+ lca;
+
+ // Traverse up from v to find the LCA
+ parent = v;
+ do {
+ parent = g.parent(parent);
+ vPath.push(parent);
+ } while (parent &&
+ (postorderNums[parent].low > low || lim > postorderNums[parent].lim));
+ lca = parent;
+
+ // Traverse from w to LCA
+ parent = w;
+ while ((parent = g.parent(parent)) !== lca) {
+ wPath.push(parent);
+ }
+
+ return { path: vPath.concat(wPath.reverse()), lca: lca };
+}
+
+function postorder(g) {
+ var result = {},
+ lim = 0;
+
+ function dfs(v) {
+ var low = lim;
+ _.each(g.children(v), dfs);
+ result[v] = { low: low, lim: lim++ };
+ }
+ _.each(g.children(), dfs);
+
+ return result;
+}
+
+},{"./lodash":10}],23:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ Graph = require("../graphlib").Graph,
+ util = require("../util");
+
+/*
+ * This module provides coordinate assignment based on Brandes and Köpf, "Fast
+ * and Simple Horizontal Coordinate Assignment."
+ */
+
+module.exports = {
+ positionX: positionX,
+ findType1Conflicts: findType1Conflicts,
+ findType2Conflicts: findType2Conflicts,
+ addConflict: addConflict,
+ hasConflict: hasConflict,
+ verticalAlignment: verticalAlignment,
+ horizontalCompaction: horizontalCompaction,
+ alignCoordinates: alignCoordinates,
+ findSmallestWidthAlignment: findSmallestWidthAlignment,
+ balance: balance
+};
+
+/*
+ * Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
+ * property. A type-1 conflict is one where a non-inner segment crosses an
+ * inner segment. An inner segment is an edge with both incident nodes marked
+ * with the "dummy" property.
+ *
+ * This algorithm scans layer by layer, starting with the second, for type-1
+ * conflicts between the current layer and the previous layer. For each layer
+ * it scans the nodes from left to right until it reaches one that is incident
+ * on an inner segment. It then scans predecessors to determine if they have
+ * edges that cross that inner segment. At the end a final scan is done for all
+ * nodes on the current rank to see if they cross the last visited inner
+ * segment.
+ *
+ * This algorithm (safely) assumes that a dummy node will only be incident on a
+ * single node in the layers being scanned.
+ */
+function findType1Conflicts(g, layering) {
+ var conflicts = {};
+
+ function visitLayer(prevLayer, layer) {
+ var
+ // last visited node in the previous layer that is incident on an inner
+ // segment.
+ k0 = 0,
+ // Tracks the last node in this layer scanned for crossings with a type-1
+ // segment.
+ scanPos = 0,
+ prevLayerLength = prevLayer.length,
+ lastNode = _.last(layer);
+
+ _.each(layer, function(v, i) {
+ var w = findOtherInnerSegmentNode(g, v),
+ k1 = w ? g.node(w).order : prevLayerLength;
+
+ if (w || v === lastNode) {
+ _.each(layer.slice(scanPos, i +1), function(scanNode) {
+ _.each(g.predecessors(scanNode), function(u) {
+ var uLabel = g.node(u),
+ uPos = uLabel.order;
+ if ((uPos < k0 || k1 < uPos) &&
+ !(uLabel.dummy && g.node(scanNode).dummy)) {
+ addConflict(conflicts, u, scanNode);
+ }
+ });
+ });
+ scanPos = i + 1;
+ k0 = k1;
+ }
+ });
+
+ return layer;
+ }
+
+ _.reduce(layering, visitLayer);
+ return conflicts;
+}
+
+function findType2Conflicts(g, layering) {
+ var conflicts = {};
+
+ function scan(south, southPos, southEnd, prevNorthBorder, nextNorthBorder) {
+ var v;
+ _.each(_.range(southPos, southEnd), function(i) {
+ v = south[i];
+ if (g.node(v).dummy) {
+ _.each(g.predecessors(v), function(u) {
+ var uNode = g.node(u);
+ if (uNode.dummy &&
+ (uNode.order < prevNorthBorder || uNode.order > nextNorthBorder)) {
+ addConflict(conflicts, u, v);
+ }
+ });
+ }
+ });
+ }
+
+
+ function visitLayer(north, south) {
+ var prevNorthPos = -1,
+ nextNorthPos,
+ southPos = 0;
+
+ _.each(south, function(v, southLookahead) {
+ if (g.node(v).dummy === "border") {
+ var predecessors = g.predecessors(v);
+ if (predecessors.length) {
+ nextNorthPos = g.node(predecessors[0]).order;
+ scan(south, southPos, southLookahead, prevNorthPos, nextNorthPos);
+ southPos = southLookahead;
+ prevNorthPos = nextNorthPos;
+ }
+ }
+ scan(south, southPos, south.length, nextNorthPos, north.length);
+ });
+
+ return south;
+ }
+
+ _.reduce(layering, visitLayer);
+ return conflicts;
+}
+
+function findOtherInnerSegmentNode(g, v) {
+ if (g.node(v).dummy) {
+ return _.find(g.predecessors(v), function(u) {
+ return g.node(u).dummy;
+ });
+ }
+}
+
+function addConflict(conflicts, v, w) {
+ if (v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+
+ var conflictsV = conflicts[v];
+ if (!conflictsV) {
+ conflicts[v] = conflictsV = {};
+ }
+ conflictsV[w] = true;
+}
+
+function hasConflict(conflicts, v, w) {
+ if (v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+ return _.has(conflicts[v], w);
+}
+
+/*
+ * Try to align nodes into vertical "blocks" where possible. This algorithm
+ * attempts to align a node with one of its median neighbors. If the edge
+ * connecting a neighbor is a type-1 conflict then we ignore that possibility.
+ * If a previous node has already formed a block with a node after the node
+ * we're trying to form a block with, we also ignore that possibility - our
+ * blocks would be split in that scenario.
+ */
+function verticalAlignment(g, layering, conflicts, neighborFn) {
+ var root = {},
+ align = {},
+ pos = {};
+
+ // We cache the position here based on the layering because the graph and
+ // layering may be out of sync. The layering matrix is manipulated to
+ // generate different extreme alignments.
+ _.each(layering, function(layer) {
+ _.each(layer, function(v, order) {
+ root[v] = v;
+ align[v] = v;
+ pos[v] = order;
+ });
+ });
+
+ _.each(layering, function(layer) {
+ var prevIdx = -1;
+ _.each(layer, function(v) {
+ var ws = neighborFn(v);
+ if (ws.length) {
+ ws = _.sortBy(ws, function(w) { return pos[w]; });
+ var mp = (ws.length - 1) / 2;
+ for (var i = Math.floor(mp), il = Math.ceil(mp); i <= il; ++i) {
+ var w = ws[i];
+ if (align[v] === v &&
+ prevIdx < pos[w] &&
+ !hasConflict(conflicts, v, w)) {
+ align[w] = v;
+ align[v] = root[v] = root[w];
+ prevIdx = pos[w];
+ }
+ }
+ }
+ });
+ });
+
+ return { root: root, align: align };
+}
+
+function horizontalCompaction(g, layering, root, align, reverseSep) {
+ // This portion of the algorithm differs from BK due to a number of problems.
+ // Instead of their algorithm we construct a new block graph and do two
+ // sweeps. The first sweep places blocks with the smallest possible
+ // coordinates. The second sweep removes unused space by moving blocks to the
+ // greatest coordinates without violating separation.
+ var xs = {},
+ blockG = buildBlockGraph(g, layering, root, reverseSep);
+
+ // First pass, assign smallest coordinates via DFS
+ var visited = {};
+ function pass1(v) {
+ if (!_.has(visited, v)) {
+ visited[v] = true;
+ xs[v] = _.reduce(blockG.inEdges(v), function(max, e) {
+ pass1(e.v);
+ return Math.max(max, xs[e.v] + blockG.edge(e));
+ }, 0);
+ }
+ }
+ _.each(blockG.nodes(), pass1);
+
+ function pass2(v) {
+ if (visited[v] !== 2) {
+ visited[v]++;
+ var min = _.reduce(blockG.outEdges(v), function(min, e) {
+ pass2(e.w);
+ return Math.min(min, xs[e.w] - blockG.edge(e));
+ }, Number.POSITIVE_INFINITY);
+ if (min !== Number.POSITIVE_INFINITY) {
+ xs[v] = Math.max(xs[v], min);
+ }
+ }
+ }
+ _.each(blockG.nodes(), pass2);
+
+
+ // Assign x coordinates to all nodes
+ _.each(align, function(v) {
+ xs[v] = xs[root[v]];
+ });
+
+ return xs;
+}
+
+
+function buildBlockGraph(g, layering, root, reverseSep) {
+ var blockGraph = new Graph(),
+ graphLabel = g.graph(),
+ sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
+
+ _.each(layering, function(layer) {
+ var u;
+ _.each(layer, function(v) {
+ var vRoot = root[v];
+ blockGraph.setNode(vRoot);
+ if (u) {
+ var uRoot = root[u],
+ prevMax = blockGraph.edge(uRoot, vRoot);
+ blockGraph.setEdge(uRoot, vRoot, Math.max(sepFn(g, v, u), prevMax || 0));
+ }
+ u = v;
+ });
+ });
+
+ return blockGraph;
+}
+
+/*
+ * Returns the alignment that has the smallest width of the given alignments.
+ */
+function findSmallestWidthAlignment(g, xss) {
+ return _.min(xss, function(xs) {
+ var min = _.min(xs, function(x, v) { return x - width(g, v) / 2; }),
+ max = _.max(xs, function(x, v) { return x + width(g, v) / 2; });
+ return max - min;
+ });
+}
+
+/*
+ * Align the coordinates of each of the layout alignments such that
+ * left-biased alignments have their minimum coordinate at the same point as
+ * the minimum coordinate of the smallest width alignment and right-biased
+ * alignments have their maximum coordinate at the same point as the maximum
+ * coordinate of the smallest width alignment.
+ */
+function alignCoordinates(xss, alignTo) {
+ var alignToMin = _.min(alignTo),
+ alignToMax = _.max(alignTo);
+
+ _.each(["u", "d"], function(vert) {
+ _.each(["l", "r"], function(horiz) {
+ var alignment = vert + horiz,
+ xs = xss[alignment],
+ delta;
+ if (xs === alignTo) return;
+
+ delta = horiz === "l" ? alignToMin - _.min(xs) : alignToMax - _.max(xs);
+
+ if (delta) {
+ xss[alignment] = _.mapValues(xs, function(x) { return x + delta; });
+ }
+ });
+ });
+}
+
+function balance(xss, align) {
+ return _.mapValues(xss.ul, function(ignore, v) {
+ if (align) {
+ return xss[align.toLowerCase()][v];
+ } else {
+ var xs = _.sortBy(_.pluck(xss, v));
+ return (xs[1] + xs[2]) / 2;
+ }
+ });
+}
+
+function positionX(g) {
+ var layering = util.buildLayerMatrix(g),
+ conflicts = _.merge(findType1Conflicts(g, layering),
+ findType2Conflicts(g, layering));
+
+ var xss = {},
+ adjustedLayering;
+ _.each(["u", "d"], function(vert) {
+ adjustedLayering = vert === "u" ? layering : _.values(layering).reverse();
+ _.each(["l", "r"], function(horiz) {
+ if (horiz === "r") {
+ adjustedLayering = _.map(adjustedLayering, function(inner) {
+ return _.values(inner).reverse();
+ });
+ }
+
+ var neighborFn = _.bind(vert === "u" ? g.predecessors : g.successors, g);
+ var align = verticalAlignment(g, adjustedLayering, conflicts, neighborFn);
+ var xs = horizontalCompaction(g, adjustedLayering,
+ align.root, align.align,
+ horiz === "r");
+ if (horiz === "r") {
+ xs = _.mapValues(xs, function(x) { return -x; });
+ }
+ xss[vert + horiz] = xs;
+ });
+ });
+
+ var smallestWidth = findSmallestWidthAlignment(g, xss);
+ alignCoordinates(xss, smallestWidth);
+ return balance(xss, g.graph().align);
+}
+
+function sep(nodeSep, edgeSep, reverseSep) {
+ return function(g, v, w) {
+ var vLabel = g.node(v),
+ wLabel = g.node(w),
+ sum = 0,
+ delta;
+
+ sum += vLabel.width / 2;
+ if (_.has(vLabel, "labelpos")) {
+ switch (vLabel.labelpos.toLowerCase()) {
+ case "l": delta = -vLabel.width / 2; break;
+ case "r": delta = vLabel.width / 2; break;
+ }
+ }
+ if (delta) {
+ sum += reverseSep ? delta : -delta;
+ }
+ delta = 0;
+
+ sum += (vLabel.dummy ? edgeSep : nodeSep) / 2;
+ sum += (wLabel.dummy ? edgeSep : nodeSep) / 2;
+
+ sum += wLabel.width / 2;
+ if (_.has(wLabel, "labelpos")) {
+ switch (wLabel.labelpos.toLowerCase()) {
+ case "l": delta = wLabel.width / 2; break;
+ case "r": delta = -wLabel.width / 2; break;
+ }
+ }
+ if (delta) {
+ sum += reverseSep ? delta : -delta;
+ }
+ delta = 0;
+
+ return sum;
+ };
+}
+
+function width(g, v) {
+ return g.node(v).width;
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29}],24:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ util = require("../util"),
+ positionX = require("./bk").positionX;
+
+module.exports = position;
+
+function position(g) {
+ g = util.asNonCompoundGraph(g);
+
+ positionY(g);
+ _.each(positionX(g), function(x, v) {
+ g.node(v).x = x;
+ });
+}
+
+function positionY(g) {
+ var layering = util.buildLayerMatrix(g),
+ rankSep = g.graph().ranksep,
+ prevY = 0;
+ _.each(layering, function(layer) {
+ var maxHeight = _.max(_.map(layer, function(v) { return g.node(v).height; }));
+ _.each(layer, function(v) {
+ g.node(v).y = prevY + maxHeight / 2;
+ });
+ prevY += maxHeight + rankSep;
+ });
+}
+
+
+},{"../lodash":10,"../util":29,"./bk":23}],25:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ Graph = require("../graphlib").Graph,
+ slack = require("./util").slack;
+
+module.exports = feasibleTree;
+
+/*
+ * Constructs a spanning tree with tight edges and adjusted the input node's
+ * ranks to achieve this. A tight edge is one that is has a length that matches
+ * its "minlen" attribute.
+ *
+ * The basic structure for this function is derived from Gansner, et al., "A
+ * Technique for Drawing Directed Graphs."
+ *
+ * Pre-conditions:
+ *
+ * 1. Graph must be a DAG.
+ * 2. Graph must be connected.
+ * 3. Graph must have at least one node.
+ * 5. Graph nodes must have been previously assigned a "rank" property that
+ * respects the "minlen" property of incident edges.
+ * 6. Graph edges must have a "minlen" property.
+ *
+ * Post-conditions:
+ *
+ * - Graph nodes will have their rank adjusted to ensure that all edges are
+ * tight.
+ *
+ * Returns a tree (undirected graph) that is constructed using only "tight"
+ * edges.
+ */
+function feasibleTree(g) {
+ var t = new Graph({ directed: false });
+
+ // Choose arbitrary node from which to start our tree
+ var start = g.nodes()[0],
+ size = g.nodeCount();
+ t.setNode(start, {});
+
+ var edge, delta;
+ while (tightTree(t, g) < size) {
+ edge = findMinSlackEdge(t, g);
+ delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge);
+ shiftRanks(t, g, delta);
+ }
+
+ return t;
+}
+
+/*
+ * Finds a maximal tree of tight edges and returns the number of nodes in the
+ * tree.
+ */
+function tightTree(t, g) {
+ function dfs(v) {
+ _.each(g.nodeEdges(v), function(e) {
+ var edgeV = e.v,
+ w = (v === edgeV) ? e.w : edgeV;
+ if (!t.hasNode(w) && !slack(g, e)) {
+ t.setNode(w, {});
+ t.setEdge(v, w, {});
+ dfs(w);
+ }
+ });
+ }
+
+ _.each(t.nodes(), dfs);
+ return t.nodeCount();
+}
+
+/*
+ * Finds the edge with the smallest slack that is incident on tree and returns
+ * it.
+ */
+function findMinSlackEdge(t, g) {
+ return _.min(g.edges(), function(e) {
+ if (t.hasNode(e.v) !== t.hasNode(e.w)) {
+ return slack(g, e);
+ }
+ });
+}
+
+function shiftRanks(t, g, delta) {
+ _.each(t.nodes(), function(v) {
+ g.node(v).rank += delta;
+ });
+}
+
+},{"../graphlib":7,"../lodash":10,"./util":28}],26:[function(require,module,exports){
+"use strict";
+
+var rankUtil = require("./util"),
+ longestPath = rankUtil.longestPath,
+ feasibleTree = require("./feasible-tree"),
+ networkSimplex = require("./network-simplex");
+
+module.exports = rank;
+
+/*
+ * Assigns a rank to each node in the input graph that respects the "minlen"
+ * constraint specified on edges between nodes.
+ *
+ * This basic structure is derived from Gansner, et al., "A Technique for
+ * Drawing Directed Graphs."
+ *
+ * Pre-conditions:
+ *
+ * 1. Graph must be a connected DAG
+ * 2. Graph nodes must be objects
+ * 3. Graph edges must have "weight" and "minlen" attributes
+ *
+ * Post-conditions:
+ *
+ * 1. Graph nodes will have a "rank" attribute based on the results of the
+ * algorithm. Ranks can start at any index (including negative), we'll
+ * fix them up later.
+ */
+function rank(g) {
+ switch(g.graph().ranker) {
+ case "network-simplex": networkSimplexRanker(g); break;
+ case "tight-tree": tightTreeRanker(g); break;
+ case "longest-path": longestPathRanker(g); break;
+ default: networkSimplexRanker(g);
+ }
+}
+
+// A fast and simple ranker, but results are far from optimal.
+var longestPathRanker = longestPath;
+
+function tightTreeRanker(g) {
+ longestPath(g);
+ feasibleTree(g);
+}
+
+function networkSimplexRanker(g) {
+ networkSimplex(g);
+}
+
+},{"./feasible-tree":25,"./network-simplex":27,"./util":28}],27:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ feasibleTree = require("./feasible-tree"),
+ slack = require("./util").slack,
+ initRank = require("./util").longestPath,
+ preorder = require("../graphlib").alg.preorder,
+ postorder = require("../graphlib").alg.postorder,
+ simplify = require("../util").simplify;
+
+module.exports = networkSimplex;
+
+// Expose some internals for testing purposes
+networkSimplex.initLowLimValues = initLowLimValues;
+networkSimplex.initCutValues = initCutValues;
+networkSimplex.calcCutValue = calcCutValue;
+networkSimplex.leaveEdge = leaveEdge;
+networkSimplex.enterEdge = enterEdge;
+networkSimplex.exchangeEdges = exchangeEdges;
+
+/*
+ * The network simplex algorithm assigns ranks to each node in the input graph
+ * and iteratively improves the ranking to reduce the length of edges.
+ *
+ * Preconditions:
+ *
+ * 1. The input graph must be a DAG.
+ * 2. All nodes in the graph must have an object value.
+ * 3. All edges in the graph must have "minlen" and "weight" attributes.
+ *
+ * Postconditions:
+ *
+ * 1. All nodes in the graph will have an assigned "rank" attribute that has
+ * been optimized by the network simplex algorithm. Ranks start at 0.
+ *
+ *
+ * A rough sketch of the algorithm is as follows:
+ *
+ * 1. Assign initial ranks to each node. We use the longest path algorithm,
+ * which assigns ranks to the lowest position possible. In general this
+ * leads to very wide bottom ranks and unnecessarily long edges.
+ * 2. Construct a feasible tight tree. A tight tree is one such that all
+ * edges in the tree have no slack (difference between length of edge
+ * and minlen for the edge). This by itself greatly improves the assigned
+ * rankings by shorting edges.
+ * 3. Iteratively find edges that have negative cut values. Generally a
+ * negative cut value indicates that the edge could be removed and a new
+ * tree edge could be added to produce a more compact graph.
+ *
+ * Much of the algorithms here are derived from Gansner, et al., "A Technique
+ * for Drawing Directed Graphs." The structure of the file roughly follows the
+ * structure of the overall algorithm.
+ */
+function networkSimplex(g) {
+ g = simplify(g);
+ initRank(g);
+ var t = feasibleTree(g);
+ initLowLimValues(t);
+ initCutValues(t, g);
+
+ var e, f;
+ while ((e = leaveEdge(t))) {
+ f = enterEdge(t, g, e);
+ exchangeEdges(t, g, e, f);
+ }
+}
+
+/*
+ * Initializes cut values for all edges in the tree.
+ */
+function initCutValues(t, g) {
+ var vs = postorder(t, t.nodes());
+ vs = vs.slice(0, vs.length - 1);
+ _.each(vs, function(v) {
+ assignCutValue(t, g, v);
+ });
+}
+
+function assignCutValue(t, g, child) {
+ var childLab = t.node(child),
+ parent = childLab.parent;
+ t.edge(child, parent).cutvalue = calcCutValue(t, g, child);
+}
+
+/*
+ * Given the tight tree, its graph, and a child in the graph calculate and
+ * return the cut value for the edge between the child and its parent.
+ */
+function calcCutValue(t, g, child) {
+ var childLab = t.node(child),
+ parent = childLab.parent,
+ // True if the child is on the tail end of the edge in the directed graph
+ childIsTail = true,
+ // The graph's view of the tree edge we're inspecting
+ graphEdge = g.edge(child, parent),
+ // The accumulated cut value for the edge between this node and its parent
+ cutValue = 0;
+
+ if (!graphEdge) {
+ childIsTail = false;
+ graphEdge = g.edge(parent, child);
+ }
+
+ cutValue = graphEdge.weight;
+
+ _.each(g.nodeEdges(child), function(e) {
+ var isOutEdge = e.v === child,
+ other = isOutEdge ? e.w : e.v;
+
+ if (other !== parent) {
+ var pointsToHead = isOutEdge === childIsTail,
+ otherWeight = g.edge(e).weight;
+
+ cutValue += pointsToHead ? otherWeight : -otherWeight;
+ if (isTreeEdge(t, child, other)) {
+ var otherCutValue = t.edge(child, other).cutvalue;
+ cutValue += pointsToHead ? -otherCutValue : otherCutValue;
+ }
+ }
+ });
+
+ return cutValue;
+}
+
+function initLowLimValues(tree, root) {
+ if (arguments.length < 2) {
+ root = tree.nodes()[0];
+ }
+ dfsAssignLowLim(tree, {}, 1, root);
+}
+
+function dfsAssignLowLim(tree, visited, nextLim, v, parent) {
+ var low = nextLim,
+ label = tree.node(v);
+
+ visited[v] = true;
+ _.each(tree.neighbors(v), function(w) {
+ if (!_.has(visited, w)) {
+ nextLim = dfsAssignLowLim(tree, visited, nextLim, w, v);
+ }
+ });
+
+ label.low = low;
+ label.lim = nextLim++;
+ if (parent) {
+ label.parent = parent;
+ } else {
+ // TODO should be able to remove this when we incrementally update low lim
+ delete label.parent;
+ }
+
+ return nextLim;
+}
+
+function leaveEdge(tree) {
+ return _.find(tree.edges(), function(e) {
+ return tree.edge(e).cutvalue < 0;
+ });
+}
+
+function enterEdge(t, g, edge) {
+ var v = edge.v,
+ w = edge.w;
+
+ // For the rest of this function we assume that v is the tail and w is the
+ // head, so if we don't have this edge in the graph we should flip it to
+ // match the correct orientation.
+ if (!g.hasEdge(v, w)) {
+ v = edge.w;
+ w = edge.v;
+ }
+
+ var vLabel = t.node(v),
+ wLabel = t.node(w),
+ tailLabel = vLabel,
+ flip = false;
+
+ // If the root is in the tail of the edge then we need to flip the logic that
+ // checks for the head and tail nodes in the candidates function below.
+ if (vLabel.lim > wLabel.lim) {
+ tailLabel = wLabel;
+ flip = true;
+ }
+
+ var candidates = _.filter(g.edges(), function(edge) {
+ return flip === isDescendant(t, t.node(edge.v), tailLabel) &&
+ flip !== isDescendant(t, t.node(edge.w), tailLabel);
+ });
+
+ return _.min(candidates, function(edge) { return slack(g, edge); });
+}
+
+function exchangeEdges(t, g, e, f) {
+ var v = e.v,
+ w = e.w;
+ t.removeEdge(v, w);
+ t.setEdge(f.v, f.w, {});
+ initLowLimValues(t);
+ initCutValues(t, g);
+ updateRanks(t, g);
+}
+
+function updateRanks(t, g) {
+ var root = _.find(t.nodes(), function(v) { return !g.node(v).parent; }),
+ vs = preorder(t, root);
+ vs = vs.slice(1);
+ _.each(vs, function(v) {
+ var parent = t.node(v).parent,
+ edge = g.edge(v, parent),
+ flipped = false;
+
+ if (!edge) {
+ edge = g.edge(parent, v);
+ flipped = true;
+ }
+
+ g.node(v).rank = g.node(parent).rank + (flipped ? edge.minlen : -edge.minlen);
+ });
+}
+
+/*
+ * Returns true if the edge is in the tree.
+ */
+function isTreeEdge(tree, u, v) {
+ return tree.hasEdge(u, v);
+}
+
+/*
+ * Returns true if the specified node is descendant of the root node per the
+ * assigned low and lim attributes in the tree.
+ */
+function isDescendant(tree, vLabel, rootLabel) {
+ return rootLabel.low <= vLabel.lim && vLabel.lim <= rootLabel.lim;
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29,"./feasible-tree":25,"./util":28}],28:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = {
+ longestPath: longestPath,
+ slack: slack
+};
+
+/*
+ * Initializes ranks for the input graph using the longest path algorithm. This
+ * algorithm scales well and is fast in practice, it yields rather poor
+ * solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
+ * ranks wide and leaving edges longer than necessary. However, due to its
+ * speed, this algorithm is good for getting an initial ranking that can be fed
+ * into other algorithms.
+ *
+ * This algorithm does not normalize layers because it will be used by other
+ * algorithms in most cases. If using this algorithm directly, be sure to
+ * run normalize at the end.
+ *
+ * Pre-conditions:
+ *
+ * 1. Input graph is a DAG.
+ * 2. Input graph node labels can be assigned properties.
+ *
+ * Post-conditions:
+ *
+ * 1. Each node will be assign an (unnormalized) "rank" property.
+ */
+function longestPath(g) {
+ var visited = {};
+
+ function dfs(v) {
+ var label = g.node(v);
+ if (_.has(visited, v)) {
+ return label.rank;
+ }
+ visited[v] = true;
+
+ var rank = _.min(_.map(g.outEdges(v), function(e) {
+ return dfs(e.w) - g.edge(e).minlen;
+ }));
+
+ if (rank === Number.POSITIVE_INFINITY) {
+ rank = 0;
+ }
+
+ return (label.rank = rank);
+ }
+
+ _.each(g.sources(), dfs);
+}
+
+/*
+ * Returns the amount of slack for the given edge. The slack is defined as the
+ * difference between the length of the edge and its minimum length.
+ */
+function slack(g, e) {
+ return g.node(e.w).rank - g.node(e.v).rank - g.edge(e).minlen;
+}
+
+},{"../lodash":10}],29:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ Graph = require("./graphlib").Graph;
+
+module.exports = {
+ addDummyNode: addDummyNode,
+ simplify: simplify,
+ asNonCompoundGraph: asNonCompoundGraph,
+ successorWeights: successorWeights,
+ predecessorWeights: predecessorWeights,
+ intersectRect: intersectRect,
+ buildLayerMatrix: buildLayerMatrix,
+ normalizeRanks: normalizeRanks,
+ removeEmptyRanks: removeEmptyRanks,
+ addBorderNode: addBorderNode,
+ maxRank: maxRank,
+ partition: partition,
+ time: time,
+ notime: notime
+};
+
+/*
+ * Adds a dummy node to the graph and return v.
+ */
+function addDummyNode(g, type, attrs, name) {
+ var v;
+ do {
+ v = _.uniqueId(name);
+ } while (g.hasNode(v));
+
+ attrs.dummy = type;
+ g.setNode(v, attrs);
+ return v;
+}
+
+/*
+ * Returns a new graph with only simple edges. Handles aggregation of data
+ * associated with multi-edges.
+ */
+function simplify(g) {
+ var simplified = new Graph().setGraph(g.graph());
+ _.each(g.nodes(), function(v) { simplified.setNode(v, g.node(v)); });
+ _.each(g.edges(), function(e) {
+ var simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 },
+ label = g.edge(e);
+ simplified.setEdge(e.v, e.w, {
+ weight: simpleLabel.weight + label.weight,
+ minlen: Math.max(simpleLabel.minlen, label.minlen)
+ });
+ });
+ return simplified;
+}
+
+function asNonCompoundGraph(g) {
+ var simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph());
+ _.each(g.nodes(), function(v) {
+ if (!g.children(v).length) {
+ simplified.setNode(v, g.node(v));
+ }
+ });
+ _.each(g.edges(), function(e) {
+ simplified.setEdge(e, g.edge(e));
+ });
+ return simplified;
+}
+
+function successorWeights(g) {
+ var weightMap = _.map(g.nodes(), function(v) {
+ var sucs = {};
+ _.each(g.outEdges(v), function(e) {
+ sucs[e.w] = (sucs[e.w] || 0) + g.edge(e).weight;
+ });
+ return sucs;
+ });
+ return _.zipObject(g.nodes(), weightMap);
+}
+
+function predecessorWeights(g) {
+ var weightMap = _.map(g.nodes(), function(v) {
+ var preds = {};
+ _.each(g.inEdges(v), function(e) {
+ preds[e.v] = (preds[e.v] || 0) + g.edge(e).weight;
+ });
+ return preds;
+ });
+ return _.zipObject(g.nodes(), weightMap);
+}
+
+/*
+ * Finds where a line starting at point ({x, y}) would intersect a rectangle
+ * ({x, y, width, height}) if it were pointing at the rectangle's center.
+ */
+function intersectRect(rect, point) {
+ var x = rect.x;
+ var y = rect.y;
+
+ // Rectangle intersection algorithm from:
+ // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
+ var dx = point.x - x;
+ var dy = point.y - y;
+ var w = rect.width / 2;
+ var h = rect.height / 2;
+
+ if (!dx && !dy) {
+ throw new Error("Not possible to find intersection inside of the rectangle");
+ }
+
+ var sx, sy;
+ if (Math.abs(dy) * w > Math.abs(dx) * h) {
+ // Intersection is top or bottom of rect.
+ if (dy < 0) {
+ h = -h;
+ }
+ sx = h * dx / dy;
+ sy = h;
+ } else {
+ // Intersection is left or right of rect.
+ if (dx < 0) {
+ w = -w;
+ }
+ sx = w;
+ sy = w * dy / dx;
+ }
+
+ return { x: x + sx, y: y + sy };
+}
+
+/*
+ * Given a DAG with each node assigned "rank" and "order" properties, this
+ * function will produce a matrix with the ids of each node.
+ */
+function buildLayerMatrix(g) {
+ var layering = _.map(_.range(maxRank(g) + 1), function() { return []; });
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v),
+ rank = node.rank;
+ if (!_.isUndefined(rank)) {
+ layering[rank][node.order] = v;
+ }
+ });
+ return layering;
+}
+
+/*
+ * Adjusts the ranks for all nodes in the graph such that all nodes v have
+ * rank(v) >= 0 and at least one node w has rank(w) = 0.
+ */
+function normalizeRanks(g) {
+ var min = _.min(_.map(g.nodes(), function(v) { return g.node(v).rank; }));
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (_.has(node, "rank")) {
+ node.rank -= min;
+ }
+ });
+}
+
+function removeEmptyRanks(g) {
+ // Ranks may not start at 0, so we need to offset them
+ var offset = _.min(_.map(g.nodes(), function(v) { return g.node(v).rank; }));
+
+ var layers = [];
+ _.each(g.nodes(), function(v) {
+ var rank = g.node(v).rank - offset;
+ if (!_.has(layers, rank)) {
+ layers[rank] = [];
+ }
+ layers[rank].push(v);
+ });
+
+ var delta = 0,
+ nodeRankFactor = g.graph().nodeRankFactor;
+ _.each(layers, function(vs, i) {
+ if (_.isUndefined(vs) && i % nodeRankFactor !== 0) {
+ --delta;
+ } else if (delta) {
+ _.each(vs, function(v) { g.node(v).rank += delta; });
+ }
+ });
+}
+
+function addBorderNode(g, prefix, rank, order) {
+ var node = {
+ width: 0,
+ height: 0
+ };
+ if (arguments.length >= 4) {
+ node.rank = rank;
+ node.order = order;
+ }
+ return addDummyNode(g, "border", node, prefix);
+}
+
+function maxRank(g) {
+ return _.max(_.map(g.nodes(), function(v) {
+ var rank = g.node(v).rank;
+ if (!_.isUndefined(rank)) {
+ return rank;
+ }
+ }));
+}
+
+/*
+ * Partition a collection into two groups: `lhs` and `rhs`. If the supplied
+ * function returns true for an entry it goes into `lhs`. Otherwise it goes
+ * into `rhs.
+ */
+function partition(collection, fn) {
+ var result = { lhs: [], rhs: [] };
+ _.each(collection, function(value) {
+ if (fn(value)) {
+ result.lhs.push(value);
+ } else {
+ result.rhs.push(value);
+ }
+ });
+ return result;
+}
+
+/*
+ * Returns a new function that wraps `fn` with a timer. The wrapper logs the
+ * time it takes to execute the function.
+ */
+function time(name, fn) {
+ var start = _.now();
+ try {
+ return fn();
+ } finally {
+ console.log(name + " time: " + (_.now() - start) + "ms");
+ }
+}
+
+function notime(name, fn) {
+ return fn();
+}
+
+},{"./graphlib":7,"./lodash":10}],30:[function(require,module,exports){
+module.exports = "0.7.1";
+
+},{}],31:[function(require,module,exports){
+/**
+ * Copyright (c) 2014, Chris Pettitt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var lib = require("./lib");
+
+module.exports = {
+ Graph: lib.Graph,
+ json: require("./lib/json"),
+ alg: require("./lib/alg"),
+ version: lib.version
+};
+
+},{"./lib":47,"./lib/alg":38,"./lib/json":48}],32:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = components;
+
+function components(g) {
+ var visited = {},
+ cmpts = [],
+ cmpt;
+
+ function dfs(v) {
+ if (_.has(visited, v)) return;
+ visited[v] = true;
+ cmpt.push(v);
+ _.each(g.successors(v), dfs);
+ _.each(g.predecessors(v), dfs);
+ }
+
+ _.each(g.nodes(), function(v) {
+ cmpt = [];
+ dfs(v);
+ if (cmpt.length) {
+ cmpts.push(cmpt);
+ }
+ });
+
+ return cmpts;
+}
+
+},{"../lodash":49}],33:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = dfs;
+
+/*
+ * A helper that preforms a pre- or post-order traversal on the input graph
+ * and returns the nodes in the order they were visited. This algorithm treats
+ * the input as undirected.
+ *
+ * Order must be one of "pre" or "post".
+ */
+function dfs(g, vs, order) {
+ if (!_.isArray(vs)) {
+ vs = [vs];
+ }
+
+ var acc = [],
+ visited = {};
+ _.each(vs, function(v) {
+ if (!g.hasNode(v)) {
+ throw new Error("Graph does not have node: " + v);
+ }
+
+ doDfs(g, v, order === "post", visited, acc);
+ });
+ return acc;
+}
+
+function doDfs(g, v, postorder, visited, acc) {
+ if (!_.has(visited, v)) {
+ visited[v] = true;
+
+ if (!postorder) { acc.push(v); }
+ _.each(g.neighbors(v), function(w) {
+ doDfs(g, w, postorder, visited, acc);
+ });
+ if (postorder) { acc.push(v); }
+ }
+}
+
+},{"../lodash":49}],34:[function(require,module,exports){
+var dijkstra = require("./dijkstra"),
+ _ = require("../lodash");
+
+module.exports = dijkstraAll;
+
+function dijkstraAll(g, weightFunc, edgeFunc) {
+ return _.transform(g.nodes(), function(acc, v) {
+ acc[v] = dijkstra(g, v, weightFunc, edgeFunc);
+ }, {});
+}
+
+},{"../lodash":49,"./dijkstra":35}],35:[function(require,module,exports){
+var _ = require("../lodash"),
+ PriorityQueue = require("../data/priority-queue");
+
+module.exports = dijkstra;
+
+var DEFAULT_WEIGHT_FUNC = _.constant(1);
+
+function dijkstra(g, source, weightFn, edgeFn) {
+ return runDijkstra(g, String(source),
+ weightFn || DEFAULT_WEIGHT_FUNC,
+ edgeFn || function(v) { return g.outEdges(v); });
+}
+
+function runDijkstra(g, source, weightFn, edgeFn) {
+ var results = {},
+ pq = new PriorityQueue(),
+ v, vEntry;
+
+ var updateNeighbors = function(edge) {
+ var w = edge.v !== v ? edge.v : edge.w,
+ wEntry = results[w],
+ weight = weightFn(edge),
+ distance = vEntry.distance + weight;
+
+ if (weight < 0) {
+ throw new Error("dijkstra does not allow negative edge weights. " +
+ "Bad edge: " + edge + " Weight: " + weight);
+ }
+
+ if (distance < wEntry.distance) {
+ wEntry.distance = distance;
+ wEntry.predecessor = v;
+ pq.decrease(w, distance);
+ }
+ };
+
+ g.nodes().forEach(function(v) {
+ var distance = v === source ? 0 : Number.POSITIVE_INFINITY;
+ results[v] = { distance: distance };
+ pq.add(v, distance);
+ });
+
+ while (pq.size() > 0) {
+ v = pq.removeMin();
+ vEntry = results[v];
+ if (vEntry.distance === Number.POSITIVE_INFINITY) {
+ break;
+ }
+
+ edgeFn(v).forEach(updateNeighbors);
+ }
+
+ return results;
+}
+
+},{"../data/priority-queue":45,"../lodash":49}],36:[function(require,module,exports){
+var _ = require("../lodash"),
+ tarjan = require("./tarjan");
+
+module.exports = findCycles;
+
+function findCycles(g) {
+ return _.filter(tarjan(g), function(cmpt) { return cmpt.length > 1; });
+}
+
+},{"../lodash":49,"./tarjan":43}],37:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = floydWarshall;
+
+var DEFAULT_WEIGHT_FUNC = _.constant(1);
+
+function floydWarshall(g, weightFn, edgeFn) {
+ return runFloydWarshall(g,
+ weightFn || DEFAULT_WEIGHT_FUNC,
+ edgeFn || function(v) { return g.outEdges(v); });
+}
+
+function runFloydWarshall(g, weightFn, edgeFn) {
+ var results = {},
+ nodes = g.nodes();
+
+ nodes.forEach(function(v) {
+ results[v] = {};
+ results[v][v] = { distance: 0 };
+ nodes.forEach(function(w) {
+ if (v !== w) {
+ results[v][w] = { distance: Number.POSITIVE_INFINITY };
+ }
+ });
+ edgeFn(v).forEach(function(edge) {
+ var w = edge.v === v ? edge.w : edge.v,
+ d = weightFn(edge);
+ results[v][w] = { distance: d, predecessor: v };
+ });
+ });
+
+ nodes.forEach(function(k) {
+ var rowK = results[k];
+ nodes.forEach(function(i) {
+ var rowI = results[i];
+ nodes.forEach(function(j) {
+ var ik = rowI[k];
+ var kj = rowK[j];
+ var ij = rowI[j];
+ var altDistance = ik.distance + kj.distance;
+ if (altDistance < ij.distance) {
+ ij.distance = altDistance;
+ ij.predecessor = kj.predecessor;
+ }
+ });
+ });
+ });
+
+ return results;
+}
+
+},{"../lodash":49}],38:[function(require,module,exports){
+module.exports = {
+ components: require("./components"),
+ dijkstra: require("./dijkstra"),
+ dijkstraAll: require("./dijkstra-all"),
+ findCycles: require("./find-cycles"),
+ floydWarshall: require("./floyd-warshall"),
+ isAcyclic: require("./is-acyclic"),
+ postorder: require("./postorder"),
+ preorder: require("./preorder"),
+ prim: require("./prim"),
+ tarjan: require("./tarjan"),
+ topsort: require("./topsort")
+};
+
+},{"./components":32,"./dijkstra":35,"./dijkstra-all":34,"./find-cycles":36,"./floyd-warshall":37,"./is-acyclic":39,"./postorder":40,"./preorder":41,"./prim":42,"./tarjan":43,"./topsort":44}],39:[function(require,module,exports){
+var topsort = require("./topsort");
+
+module.exports = isAcyclic;
+
+function isAcyclic(g) {
+ try {
+ topsort(g);
+ } catch (e) {
+ if (e instanceof topsort.CycleException) {
+ return false;
+ }
+ throw e;
+ }
+ return true;
+}
+
+},{"./topsort":44}],40:[function(require,module,exports){
+var dfs = require("./dfs");
+
+module.exports = postorder;
+
+function postorder(g, vs) {
+ return dfs(g, vs, "post");
+}
+
+},{"./dfs":33}],41:[function(require,module,exports){
+var dfs = require("./dfs");
+
+module.exports = preorder;
+
+function preorder(g, vs) {
+ return dfs(g, vs, "pre");
+}
+
+},{"./dfs":33}],42:[function(require,module,exports){
+var _ = require("../lodash"),
+ Graph = require("../graph"),
+ PriorityQueue = require("../data/priority-queue");
+
+module.exports = prim;
+
+function prim(g, weightFunc) {
+ var result = new Graph(),
+ parents = {},
+ pq = new PriorityQueue(),
+ v;
+
+ function updateNeighbors(edge) {
+ var w = edge.v === v ? edge.w : edge.v,
+ pri = pq.priority(w);
+ if (pri !== undefined) {
+ var edgeWeight = weightFunc(edge);
+ if (edgeWeight < pri) {
+ parents[w] = v;
+ pq.decrease(w, edgeWeight);
+ }
+ }
+ }
+
+ if (g.nodeCount() === 0) {
+ return result;
+ }
+
+ _.each(g.nodes(), function(v) {
+ pq.add(v, Number.POSITIVE_INFINITY);
+ result.setNode(v);
+ });
+
+ // Start from an arbitrary node
+ pq.decrease(g.nodes()[0], 0);
+
+ var init = false;
+ while (pq.size() > 0) {
+ v = pq.removeMin();
+ if (_.has(parents, v)) {
+ result.setEdge(v, parents[v]);
+ } else if (init) {
+ throw new Error("Input graph is not connected: " + g);
+ } else {
+ init = true;
+ }
+
+ g.nodeEdges(v).forEach(updateNeighbors);
+ }
+
+ return result;
+}
+
+},{"../data/priority-queue":45,"../graph":46,"../lodash":49}],43:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = tarjan;
+
+function tarjan(g) {
+ var index = 0,
+ stack = [],
+ visited = {}, // node id -> { onStack, lowlink, index }
+ results = [];
+
+ function dfs(v) {
+ var entry = visited[v] = {
+ onStack: true,
+ lowlink: index,
+ index: index++
+ };
+ stack.push(v);
+
+ g.successors(v).forEach(function(w) {
+ if (!_.has(visited, w)) {
+ dfs(w);
+ entry.lowlink = Math.min(entry.lowlink, visited[w].lowlink);
+ } else if (visited[w].onStack) {
+ entry.lowlink = Math.min(entry.lowlink, visited[w].index);
+ }
+ });
+
+ if (entry.lowlink === entry.index) {
+ var cmpt = [],
+ w;
+ do {
+ w = stack.pop();
+ visited[w].onStack = false;
+ cmpt.push(w);
+ } while (v !== w);
+ results.push(cmpt);
+ }
+ }
+
+ g.nodes().forEach(function(v) {
+ if (!_.has(visited, v)) {
+ dfs(v);
+ }
+ });
+
+ return results;
+}
+
+},{"../lodash":49}],44:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = topsort;
+topsort.CycleException = CycleException;
+
+function topsort(g) {
+ var visited = {},
+ stack = {},
+ results = [];
+
+ function visit(node) {
+ if (_.has(stack, node)) {
+ throw new CycleException();
+ }
+
+ if (!_.has(visited, node)) {
+ stack[node] = true;
+ visited[node] = true;
+ _.each(g.predecessors(node), visit);
+ delete stack[node];
+ results.push(node);
+ }
+ }
+
+ _.each(g.sinks(), visit);
+
+ if (_.size(visited) !== g.nodeCount()) {
+ throw new CycleException();
+ }
+
+ return results;
+}
+
+function CycleException() {}
+
+},{"../lodash":49}],45:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = PriorityQueue;
+
+/**
+ * A min-priority queue data structure. This algorithm is derived from Cormen,
+ * et al., "Introduction to Algorithms". The basic idea of a min-priority
+ * queue is that you can efficiently (in O(1) time) get the smallest key in
+ * the queue. Adding and removing elements takes O(log n) time. A key can
+ * have its priority decreased in O(log n) time.
+ */
+function PriorityQueue() {
+ this._arr = [];
+ this._keyIndices = {};
+}
+
+/**
+ * Returns the number of elements in the queue. Takes `O(1)` time.
+ */
+PriorityQueue.prototype.size = function() {
+ return this._arr.length;
+};
+
+/**
+ * Returns the keys that are in the queue. Takes `O(n)` time.
+ */
+PriorityQueue.prototype.keys = function() {
+ return this._arr.map(function(x) { return x.key; });
+};
+
+/**
+ * Returns `true` if **key** is in the queue and `false` if not.
+ */
+PriorityQueue.prototype.has = function(key) {
+ return _.has(this._keyIndices, key);
+};
+
+/**
+ * Returns the priority for **key**. If **key** is not present in the queue
+ * then this function returns `undefined`. Takes `O(1)` time.
+ *
+ * @param {Object} key
+ */
+PriorityQueue.prototype.priority = function(key) {
+ var index = this._keyIndices[key];
+ if (index !== undefined) {
+ return this._arr[index].priority;
+ }
+};
+
+/**
+ * Returns the key for the minimum element in this queue. If the queue is
+ * empty this function throws an Error. Takes `O(1)` time.
+ */
+PriorityQueue.prototype.min = function() {
+ if (this.size() === 0) {
+ throw new Error("Queue underflow");
+ }
+ return this._arr[0].key;
+};
+
+/**
+ * Inserts a new key into the priority queue. If the key already exists in
+ * the queue this function returns `false`; otherwise it will return `true`.
+ * Takes `O(n)` time.
+ *
+ * @param {Object} key the key to add
+ * @param {Number} priority the initial priority for the key
+ */
+PriorityQueue.prototype.add = function(key, priority) {
+ var keyIndices = this._keyIndices;
+ key = String(key);
+ if (!_.has(keyIndices, key)) {
+ var arr = this._arr;
+ var index = arr.length;
+ keyIndices[key] = index;
+ arr.push({key: key, priority: priority});
+ this._decrease(index);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Removes and returns the smallest key in the queue. Takes `O(log n)` time.
+ */
+PriorityQueue.prototype.removeMin = function() {
+ this._swap(0, this._arr.length - 1);
+ var min = this._arr.pop();
+ delete this._keyIndices[min.key];
+ this._heapify(0);
+ return min.key;
+};
+
+/**
+ * Decreases the priority for **key** to **priority**. If the new priority is
+ * greater than the previous priority, this function will throw an Error.
+ *
+ * @param {Object} key the key for which to raise priority
+ * @param {Number} priority the new priority for the key
+ */
+PriorityQueue.prototype.decrease = function(key, priority) {
+ var index = this._keyIndices[key];
+ if (priority > this._arr[index].priority) {
+ throw new Error("New priority is greater than current priority. " +
+ "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority);
+ }
+ this._arr[index].priority = priority;
+ this._decrease(index);
+};
+
+PriorityQueue.prototype._heapify = function(i) {
+ var arr = this._arr;
+ var l = 2 * i,
+ r = l + 1,
+ largest = i;
+ if (l < arr.length) {
+ largest = arr[l].priority < arr[largest].priority ? l : largest;
+ if (r < arr.length) {
+ largest = arr[r].priority < arr[largest].priority ? r : largest;
+ }
+ if (largest !== i) {
+ this._swap(i, largest);
+ this._heapify(largest);
+ }
+ }
+};
+
+PriorityQueue.prototype._decrease = function(index) {
+ var arr = this._arr;
+ var priority = arr[index].priority;
+ var parent;
+ while (index !== 0) {
+ parent = index >> 1;
+ if (arr[parent].priority < priority) {
+ break;
+ }
+ this._swap(index, parent);
+ index = parent;
+ }
+};
+
+PriorityQueue.prototype._swap = function(i, j) {
+ var arr = this._arr;
+ var keyIndices = this._keyIndices;
+ var origArrI = arr[i];
+ var origArrJ = arr[j];
+ arr[i] = origArrJ;
+ arr[j] = origArrI;
+ keyIndices[origArrJ.key] = i;
+ keyIndices[origArrI.key] = j;
+};
+
+},{"../lodash":49}],46:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash");
+
+module.exports = Graph;
+
+var DEFAULT_EDGE_NAME = "\x00",
+ GRAPH_NODE = "\x00",
+ EDGE_KEY_DELIM = "\x01";
+
+// Implementation notes:
+//
+// * Node id query functions should return string ids for the nodes
+// * Edge id query functions should return an "edgeObj", edge object, that is
+// composed of enough information to uniquely identify an edge: {v, w, name}.
+// * Internally we use an "edgeId", a stringified form of the edgeObj, to
+// reference edges. This is because we need a performant way to look these
+// edges up and, object properties, which have string keys, are the closest
+// we're going to get to a performant hashtable in JavaScript.
+
+function Graph(opts) {
+ this._isDirected = _.has(opts, "directed") ? opts.directed : true;
+ this._isMultigraph = _.has(opts, "multigraph") ? opts.multigraph : false;
+ this._isCompound = _.has(opts, "compound") ? opts.compound : false;
+
+ // Label for the graph itself
+ this._label = undefined;
+
+ // Defaults to be set when creating a new node
+ this._defaultNodeLabelFn = _.constant(undefined);
+
+ // Defaults to be set when creating a new edge
+ this._defaultEdgeLabelFn = _.constant(undefined);
+
+ // v -> label
+ this._nodes = {};
+
+ if (this._isCompound) {
+ // v -> parent
+ this._parent = {};
+
+ // v -> children
+ this._children = {};
+ this._children[GRAPH_NODE] = {};
+ }
+
+ // v -> edgeObj
+ this._in = {};
+
+ // u -> v -> Number
+ this._preds = {};
+
+ // v -> edgeObj
+ this._out = {};
+
+ // v -> w -> Number
+ this._sucs = {};
+
+ // e -> edgeObj
+ this._edgeObjs = {};
+
+ // e -> label
+ this._edgeLabels = {};
+}
+
+/* Number of nodes in the graph. Should only be changed by the implementation. */
+Graph.prototype._nodeCount = 0;
+
+/* Number of edges in the graph. Should only be changed by the implementation. */
+Graph.prototype._edgeCount = 0;
+
+
+/* === Graph functions ========= */
+
+Graph.prototype.isDirected = function() {
+ return this._isDirected;
+};
+
+Graph.prototype.isMultigraph = function() {
+ return this._isMultigraph;
+};
+
+Graph.prototype.isCompound = function() {
+ return this._isCompound;
+};
+
+Graph.prototype.setGraph = function(label) {
+ this._label = label;
+ return this;
+};
+
+Graph.prototype.graph = function() {
+ return this._label;
+};
+
+
+/* === Node functions ========== */
+
+Graph.prototype.setDefaultNodeLabel = function(newDefault) {
+ if (!_.isFunction(newDefault)) {
+ newDefault = _.constant(newDefault);
+ }
+ this._defaultNodeLabelFn = newDefault;
+ return this;
+};
+
+Graph.prototype.nodeCount = function() {
+ return this._nodeCount;
+};
+
+Graph.prototype.nodes = function() {
+ return _.keys(this._nodes);
+};
+
+Graph.prototype.sources = function() {
+ return _.filter(this.nodes(), function(v) {
+ return _.isEmpty(this._in[v]);
+ }, this);
+};
+
+Graph.prototype.sinks = function() {
+ return _.filter(this.nodes(), function(v) {
+ return _.isEmpty(this._out[v]);
+ }, this);
+};
+
+Graph.prototype.setNodes = function(vs, value) {
+ var args = arguments;
+ _.each(vs, function(v) {
+ if (args.length > 1) {
+ this.setNode(v, value);
+ } else {
+ this.setNode(v);
+ }
+ }, this);
+ return this;
+};
+
+Graph.prototype.setNode = function(v, value) {
+ if (_.has(this._nodes, v)) {
+ if (arguments.length > 1) {
+ this._nodes[v] = value;
+ }
+ return this;
+ }
+
+ this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v);
+ if (this._isCompound) {
+ this._parent[v] = GRAPH_NODE;
+ this._children[v] = {};
+ this._children[GRAPH_NODE][v] = true;
+ }
+ this._in[v] = {};
+ this._preds[v] = {};
+ this._out[v] = {};
+ this._sucs[v] = {};
+ ++this._nodeCount;
+ return this;
+};
+
+Graph.prototype.node = function(v) {
+ return this._nodes[v];
+};
+
+Graph.prototype.hasNode = function(v) {
+ return _.has(this._nodes, v);
+};
+
+Graph.prototype.removeNode = function(v) {
+ var self = this;
+ if (_.has(this._nodes, v)) {
+ var removeEdge = function(e) { self.removeEdge(self._edgeObjs[e]); };
+ delete this._nodes[v];
+ if (this._isCompound) {
+ this._removeFromParentsChildList(v);
+ delete this._parent[v];
+ _.each(this.children(v), function(child) {
+ this.setParent(child);
+ }, this);
+ delete this._children[v];
+ }
+ _.each(_.keys(this._in[v]), removeEdge);
+ delete this._in[v];
+ delete this._preds[v];
+ _.each(_.keys(this._out[v]), removeEdge);
+ delete this._out[v];
+ delete this._sucs[v];
+ --this._nodeCount;
+ }
+ return this;
+};
+
+Graph.prototype.setParent = function(v, parent) {
+ if (!this._isCompound) {
+ throw new Error("Cannot set parent in a non-compound graph");
+ }
+
+ if (_.isUndefined(parent)) {
+ parent = GRAPH_NODE;
+ } else {
+ for (var ancestor = parent;
+ !_.isUndefined(ancestor);
+ ancestor = this.parent(ancestor)) {
+ if (ancestor === v) {
+ throw new Error("Setting " + parent+ " as parent of " + v +
+ " would create create a cycle");
+ }
+ }
+
+ this.setNode(parent);
+ }
+
+ this.setNode(v);
+ this._removeFromParentsChildList(v);
+ this._parent[v] = parent;
+ this._children[parent][v] = true;
+ return this;
+};
+
+Graph.prototype._removeFromParentsChildList = function(v) {
+ delete this._children[this._parent[v]][v];
+};
+
+Graph.prototype.parent = function(v) {
+ if (this._isCompound) {
+ var parent = this._parent[v];
+ if (parent !== GRAPH_NODE) {
+ return parent;
+ }
+ }
+};
+
+Graph.prototype.children = function(v) {
+ if (_.isUndefined(v)) {
+ v = GRAPH_NODE;
+ }
+
+ if (this._isCompound) {
+ var children = this._children[v];
+ if (children) {
+ return _.keys(children);
+ }
+ } else if (v === GRAPH_NODE) {
+ return this.nodes();
+ } else if (this.hasNode(v)) {
+ return [];
+ }
+};
+
+Graph.prototype.predecessors = function(v) {
+ var predsV = this._preds[v];
+ if (predsV) {
+ return _.keys(predsV);
+ }
+};
+
+Graph.prototype.successors = function(v) {
+ var sucsV = this._sucs[v];
+ if (sucsV) {
+ return _.keys(sucsV);
+ }
+};
+
+Graph.prototype.neighbors = function(v) {
+ var preds = this.predecessors(v);
+ if (preds) {
+ return _.union(preds, this.successors(v));
+ }
+};
+
+/* === Edge functions ========== */
+
+Graph.prototype.setDefaultEdgeLabel = function(newDefault) {
+ if (!_.isFunction(newDefault)) {
+ newDefault = _.constant(newDefault);
+ }
+ this._defaultEdgeLabelFn = newDefault;
+ return this;
+};
+
+Graph.prototype.edgeCount = function() {
+ return this._edgeCount;
+};
+
+Graph.prototype.edges = function() {
+ return _.values(this._edgeObjs);
+};
+
+Graph.prototype.setPath = function(vs, value) {
+ var self = this,
+ args = arguments;
+ _.reduce(vs, function(v, w) {
+ if (args.length > 1) {
+ self.setEdge(v, w, value);
+ } else {
+ self.setEdge(v, w);
+ }
+ return w;
+ });
+ return this;
+};
+
+/*
+ * setEdge(v, w, [value, [name]])
+ * setEdge({ v, w, [name] }, [value])
+ */
+Graph.prototype.setEdge = function() {
+ var v, w, name, value,
+ valueSpecified = false;
+
+ if (_.isPlainObject(arguments[0])) {
+ v = arguments[0].v;
+ w = arguments[0].w;
+ name = arguments[0].name;
+ if (arguments.length === 2) {
+ value = arguments[1];
+ valueSpecified = true;
+ }
+ } else {
+ v = arguments[0];
+ w = arguments[1];
+ name = arguments[3];
+ if (arguments.length > 2) {
+ value = arguments[2];
+ valueSpecified = true;
+ }
+ }
+
+ v = "" + v;
+ w = "" + w;
+ if (!_.isUndefined(name)) {
+ name = "" + name;
+ }
+
+ var e = edgeArgsToId(this._isDirected, v, w, name);
+ if (_.has(this._edgeLabels, e)) {
+ if (valueSpecified) {
+ this._edgeLabels[e] = value;
+ }
+ return this;
+ }
+
+ if (!_.isUndefined(name) && !this._isMultigraph) {
+ throw new Error("Cannot set a named edge when isMultigraph = false");
+ }
+
+ // It didn't exist, so we need to create it.
+ // First ensure the nodes exist.
+ this.setNode(v);
+ this.setNode(w);
+
+ this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name);
+
+ var edgeObj = edgeArgsToObj(this._isDirected, v, w, name);
+ // Ensure we add undirected edges in a consistent way.
+ v = edgeObj.v;
+ w = edgeObj.w;
+
+ Object.freeze(edgeObj);
+ this._edgeObjs[e] = edgeObj;
+ incrementOrInitEntry(this._preds[w], v);
+ incrementOrInitEntry(this._sucs[v], w);
+ this._in[w][e] = edgeObj;
+ this._out[v][e] = edgeObj;
+ this._edgeCount++;
+ return this;
+};
+
+Graph.prototype.edge = function(v, w, name) {
+ var e = (arguments.length === 1
+ ? edgeObjToId(this._isDirected, arguments[0])
+ : edgeArgsToId(this._isDirected, v, w, name));
+ return this._edgeLabels[e];
+};
+
+Graph.prototype.hasEdge = function(v, w, name) {
+ var e = (arguments.length === 1
+ ? edgeObjToId(this._isDirected, arguments[0])
+ : edgeArgsToId(this._isDirected, v, w, name));
+ return _.has(this._edgeLabels, e);
+};
+
+Graph.prototype.removeEdge = function(v, w, name) {
+ var e = (arguments.length === 1
+ ? edgeObjToId(this._isDirected, arguments[0])
+ : edgeArgsToId(this._isDirected, v, w, name)),
+ edge = this._edgeObjs[e];
+ if (edge) {
+ v = edge.v;
+ w = edge.w;
+ delete this._edgeLabels[e];
+ delete this._edgeObjs[e];
+ decrementOrRemoveEntry(this._preds[w], v);
+ decrementOrRemoveEntry(this._sucs[v], w);
+ delete this._in[w][e];
+ delete this._out[v][e];
+ this._edgeCount--;
+ }
+ return this;
+};
+
+Graph.prototype.inEdges = function(v, u) {
+ var inV = this._in[v];
+ if (inV) {
+ var edges = _.values(inV);
+ if (!u) {
+ return edges;
+ }
+ return _.filter(edges, function(edge) { return edge.v === u; });
+ }
+};
+
+Graph.prototype.outEdges = function(v, w) {
+ var outV = this._out[v];
+ if (outV) {
+ var edges = _.values(outV);
+ if (!w) {
+ return edges;
+ }
+ return _.filter(edges, function(edge) { return edge.w === w; });
+ }
+};
+
+Graph.prototype.nodeEdges = function(v, w) {
+ var inEdges = this.inEdges(v, w);
+ if (inEdges) {
+ return inEdges.concat(this.outEdges(v, w));
+ }
+};
+
+function incrementOrInitEntry(map, k) {
+ if (_.has(map, k)) {
+ map[k]++;
+ } else {
+ map[k] = 1;
+ }
+}
+
+function decrementOrRemoveEntry(map, k) {
+ if (!--map[k]) { delete map[k]; }
+}
+
+function edgeArgsToId(isDirected, v, w, name) {
+ if (!isDirected && v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+ return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM +
+ (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name);
+}
+
+function edgeArgsToObj(isDirected, v, w, name) {
+ if (!isDirected && v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+ var edgeObj = { v: v, w: w };
+ if (name) {
+ edgeObj.name = name;
+ }
+ return edgeObj;
+}
+
+function edgeObjToId(isDirected, edgeObj) {
+ return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name);
+}
+
+},{"./lodash":49}],47:[function(require,module,exports){
+// Includes only the "core" of graphlib
+module.exports = {
+ Graph: require("./graph"),
+ version: require("./version")
+};
+
+},{"./graph":46,"./version":50}],48:[function(require,module,exports){
+var _ = require("./lodash"),
+ Graph = require("./graph");
+
+module.exports = {
+ write: write,
+ read: read
+};
+
+function write(g) {
+ var json = {
+ options: {
+ directed: g.isDirected(),
+ multigraph: g.isMultigraph(),
+ compound: g.isCompound()
+ },
+ nodes: writeNodes(g),
+ edges: writeEdges(g)
+ };
+ if (!_.isUndefined(g.graph())) {
+ json.value = _.clone(g.graph());
+ }
+ return json;
+}
+
+function writeNodes(g) {
+ return _.map(g.nodes(), function(v) {
+ var nodeValue = g.node(v),
+ parent = g.parent(v),
+ node = { v: v };
+ if (!_.isUndefined(nodeValue)) {
+ node.value = nodeValue;
+ }
+ if (!_.isUndefined(parent)) {
+ node.parent = parent;
+ }
+ return node;
+ });
+}
+
+function writeEdges(g) {
+ return _.map(g.edges(), function(e) {
+ var edgeValue = g.edge(e),
+ edge = { v: e.v, w: e.w };
+ if (!_.isUndefined(e.name)) {
+ edge.name = e.name;
+ }
+ if (!_.isUndefined(edgeValue)) {
+ edge.value = edgeValue;
+ }
+ return edge;
+ });
+}
+
+function read(json) {
+ var g = new Graph(json.options).setGraph(json.value);
+ _.each(json.nodes, function(entry) {
+ g.setNode(entry.v, entry.value);
+ if (entry.parent) {
+ g.setParent(entry.v, entry.parent);
+ }
+ });
+ _.each(json.edges, function(entry) {
+ g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value);
+ });
+ return g;
+}
+
+},{"./graph":46,"./lodash":49}],49:[function(require,module,exports){
+module.exports=require(10)
+},{"/Users/cpettitt/projects/dagre/lib/lodash.js":10,"lodash":51}],50:[function(require,module,exports){
+module.exports = '1.0.1';
+
+},{}],51:[function(require,module,exports){
+(function (global){
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Build: `lodash modern -o ./dist/lodash.js`
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+ /** Used as a safe reference for `undefined` in pre ES5 environments */
+ var undefined;
+
+ /** Used to pool arrays and objects used internally */
+ var arrayPool = [],
+ objectPool = [];
+
+ /** Used to generate unique IDs */
+ var idCounter = 0;
+
+ /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+ var keyPrefix = +new Date + '';
+
+ /** Used as the size when optimizations are enabled for large arrays */
+ var largeArraySize = 75;
+
+ /** Used as the max size of the `arrayPool` and `objectPool` */
+ var maxPoolSize = 40;
+
+ /** Used to detect and test whitespace */
+ var whitespace = (
+ // whitespace
+ ' \t\x0B\f\xA0\ufeff' +
+
+ // line terminators
+ '\n\r\u2028\u2029' +
+
+ // unicode category "Zs" space separators
+ '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+ );
+
+ /** Used to match empty string literals in compiled template source */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /**
+ * Used to match ES6 template delimiters
+ * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
+ */
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+ /** Used to match regexp flags from their coerced string values */
+ var reFlags = /\w*$/;
+
+ /** Used to detected named functions */
+ var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+ /** Used to match "interpolate" template delimiters */
+ var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+ /** Used to match leading whitespace and zeros to be removed */
+ var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
+
+ /** Used to ensure capturing order of template delimiters */
+ var reNoMatch = /($^)/;
+
+ /** Used to detect functions containing a `this` reference */
+ var reThis = /\bthis\b/;
+
+ /** Used to match unescaped characters in compiled string literals */
+ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+ /** Used to assign default `context` object properties */
+ var contextProps = [
+ 'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object',
+ 'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
+ 'parseInt', 'setTimeout'
+ ];
+
+ /** Used to make template sourceURLs easier to identify */
+ var templateCounter = 0;
+
+ /** `Object#toString` result shortcuts */
+ var argsClass = '[object Arguments]',
+ arrayClass = '[object Array]',
+ boolClass = '[object Boolean]',
+ dateClass = '[object Date]',
+ funcClass = '[object Function]',
+ numberClass = '[object Number]',
+ objectClass = '[object Object]',
+ regexpClass = '[object RegExp]',
+ stringClass = '[object String]';
+
+ /** Used to identify object classifications that `_.clone` supports */
+ var cloneableClasses = {};
+ cloneableClasses[funcClass] = false;
+ cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+ cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+ cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+ cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+ /** Used as an internal `_.debounce` options object */
+ var debounceOptions = {
+ 'leading': false,
+ 'maxWait': 0,
+ 'trailing': false
+ };
+
+ /** Used as the property descriptor for `__bindData__` */
+ var descriptor = {
+ 'configurable': false,
+ 'enumerable': false,
+ 'value': null,
+ 'writable': false
+ };
+
+ /** Used to determine if values are of the language type Object */
+ var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /** Used as a reference to the global object */
+ var root = (objectTypes[typeof window] && window) || this;
+
+ /** Detect free variable `exports` */
+ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module` */
+ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports` */
+ var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+ /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+ var freeGlobal = objectTypes[typeof global] && global;
+ if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The base implementation of `_.indexOf` without support for binary searches
+ * or `fromIndex` constraints.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ */
+ function baseIndexOf(array, value, fromIndex) {
+ var index = (fromIndex || 0) - 1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * An implementation of `_.contains` for cache objects that mimics the return
+ * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
+ *
+ * @private
+ * @param {Object} cache The cache object to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns `0` if `value` is found, else `-1`.
+ */
+ function cacheIndexOf(cache, value) {
+ var type = typeof value;
+ cache = cache.cache;
+
+ if (type == 'boolean' || value == null) {
+ return cache[value] ? 0 : -1;
+ }
+ if (type != 'number' && type != 'string') {
+ type = 'object';
+ }
+ var key = type == 'number' ? value : keyPrefix + value;
+ cache = (cache = cache[type]) && cache[key];
+
+ return type == 'object'
+ ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
+ : (cache ? 0 : -1);
+ }
+
+ /**
+ * Adds a given value to the corresponding cache object.
+ *
+ * @private
+ * @param {*} value The value to add to the cache.
+ */
+ function cachePush(value) {
+ var cache = this.cache,
+ type = typeof value;
+
+ if (type == 'boolean' || value == null) {
+ cache[value] = true;
+ } else {
+ if (type != 'number' && type != 'string') {
+ type = 'object';
+ }
+ var key = type == 'number' ? value : keyPrefix + value,
+ typeCache = cache[type] || (cache[type] = {});
+
+ if (type == 'object') {
+ (typeCache[key] || (typeCache[key] = [])).push(value);
+ } else {
+ typeCache[key] = true;
+ }
+ }
+ }
+
+ /**
+ * Used by `_.max` and `_.min` as the default callback when a given
+ * collection is a string value.
+ *
+ * @private
+ * @param {string} value The character to inspect.
+ * @returns {number} Returns the code unit of given character.
+ */
+ function charAtCallback(value) {
+ return value.charCodeAt(0);
+ }
+
+ /**
+ * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+ * them in ascending order.
+ *
+ * @private
+ * @param {Object} a The object to compare to `b`.
+ * @param {Object} b The object to compare to `a`.
+ * @returns {number} Returns the sort order indicator of `1` or `-1`.
+ */
+ function compareAscending(a, b) {
+ var ac = a.criteria,
+ bc = b.criteria,
+ index = -1,
+ length = ac.length;
+
+ while (++index < length) {
+ var value = ac[index],
+ other = bc[index];
+
+ if (value !== other) {
+ if (value > other || typeof value == 'undefined') {
+ return 1;
+ }
+ if (value < other || typeof other == 'undefined') {
+ return -1;
+ }
+ }
+ }
+ // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+ // that causes it, under certain circumstances, to return the same value for
+ // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+ //
+ // This also ensures a stable sort in V8 and other engines.
+ // See http://code.google.com/p/v8/issues/detail?id=90
+ return a.index - b.index;
+ }
+
+ /**
+ * Creates a cache object to optimize linear searches of large arrays.
+ *
+ * @private
+ * @param {Array} [array=[]] The array to search.
+ * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
+ */
+ function createCache(array) {
+ var index = -1,
+ length = array.length,
+ first = array[0],
+ mid = array[(length / 2) | 0],
+ last = array[length - 1];
+
+ if (first && typeof first == 'object' &&
+ mid && typeof mid == 'object' && last && typeof last == 'object') {
+ return false;
+ }
+ var cache = getObject();
+ cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
+
+ var result = getObject();
+ result.array = array;
+ result.cache = cache;
+ result.push = cachePush;
+
+ while (++index < length) {
+ result.push(array[index]);
+ }
+ return result;
+ }
+
+ /**
+ * Used by `template` to escape characters for inclusion in compiled
+ * string literals.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeStringChar(match) {
+ return '\\' + stringEscapes[match];
+ }
+
+ /**
+ * Gets an array from the array pool or creates a new one if the pool is empty.
+ *
+ * @private
+ * @returns {Array} The array from the pool.
+ */
+ function getArray() {
+ return arrayPool.pop() || [];
+ }
+
+ /**
+ * Gets an object from the object pool or creates a new one if the pool is empty.
+ *
+ * @private
+ * @returns {Object} The object from the pool.
+ */
+ function getObject() {
+ return objectPool.pop() || {
+ 'array': null,
+ 'cache': null,
+ 'criteria': null,
+ 'false': false,
+ 'index': 0,
+ 'null': false,
+ 'number': null,
+ 'object': null,
+ 'push': null,
+ 'string': null,
+ 'true': false,
+ 'undefined': false,
+ 'value': null
+ };
+ }
+
+ /**
+ * Releases the given array back to the array pool.
+ *
+ * @private
+ * @param {Array} [array] The array to release.
+ */
+ function releaseArray(array) {
+ array.length = 0;
+ if (arrayPool.length < maxPoolSize) {
+ arrayPool.push(array);
+ }
+ }
+
+ /**
+ * Releases the given object back to the object pool.
+ *
+ * @private
+ * @param {Object} [object] The object to release.
+ */
+ function releaseObject(object) {
+ var cache = object.cache;
+ if (cache) {
+ releaseObject(cache);
+ }
+ object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
+ if (objectPool.length < maxPoolSize) {
+ objectPool.push(object);
+ }
+ }
+
+ /**
+ * Slices the `collection` from the `start` index up to, but not including,
+ * the `end` index.
+ *
+ * Note: This function is used instead of `Array#slice` to support node lists
+ * in IE < 9 and to ensure dense arrays are returned.
+ *
+ * @private
+ * @param {Array|Object|string} collection The collection to slice.
+ * @param {number} start The start index.
+ * @param {number} end The end index.
+ * @returns {Array} Returns the new array.
+ */
+ function slice(array, start, end) {
+ start || (start = 0);
+ if (typeof end == 'undefined') {
+ end = array ? array.length : 0;
+ }
+ var index = -1,
+ length = end - start || 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = array[start + index];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a new `lodash` function using the given context object.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns the `lodash` function.
+ */
+ function runInContext(context) {
+ // Avoid issues with some ES3 environments that attempt to use values, named
+ // after built-in constructors like `Object`, for the creation of literals.
+ // ES5 clears this up by stating that literals must use built-in constructors.
+ // See http://es5.github.io/#x11.1.5.
+ context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+ /** Native constructor references */
+ var Array = context.Array,
+ Boolean = context.Boolean,
+ Date = context.Date,
+ Function = context.Function,
+ Math = context.Math,
+ Number = context.Number,
+ Object = context.Object,
+ RegExp = context.RegExp,
+ String = context.String,
+ TypeError = context.TypeError;
+
+ /**
+ * Used for `Array` method references.
+ *
+ * Normally `Array.prototype` would suffice, however, using an array literal
+ * avoids issues in Narwhal.
+ */
+ var arrayRef = [];
+
+ /** Used for native method references */
+ var objectProto = Object.prototype;
+
+ /** Used to restore the original `_` reference in `noConflict` */
+ var oldDash = context._;
+
+ /** Used to resolve the internal [[Class]] of values */
+ var toString = objectProto.toString;
+
+ /** Used to detect if a method is native */
+ var reNative = RegExp('^' +
+ String(toString)
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ .replace(/toString| for [^\]]+/g, '.*?') + '$'
+ );
+
+ /** Native method shortcuts */
+ var ceil = Math.ceil,
+ clearTimeout = context.clearTimeout,
+ floor = Math.floor,
+ fnToString = Function.prototype.toString,
+ getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+ hasOwnProperty = objectProto.hasOwnProperty,
+ push = arrayRef.push,
+ setTimeout = context.setTimeout,
+ splice = arrayRef.splice,
+ unshift = arrayRef.unshift;
+
+ /** Used to set meta data on functions */
+ var defineProperty = (function() {
+ // IE 8 only accepts DOM elements
+ try {
+ var o = {},
+ func = isNative(func = Object.defineProperty) && func,
+ result = func(o, o, o) && func;
+ } catch(e) { }
+ return result;
+ }());
+
+ /* Native method shortcuts for methods with the same name as other `lodash` methods */
+ var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+ nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+ nativeIsFinite = context.isFinite,
+ nativeIsNaN = context.isNaN,
+ nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeParseInt = context.parseInt,
+ nativeRandom = Math.random;
+
+ /** Used to lookup a built-in constructor by [[Class]] */
+ var ctorByClass = {};
+ ctorByClass[arrayClass] = Array;
+ ctorByClass[boolClass] = Boolean;
+ ctorByClass[dateClass] = Date;
+ ctorByClass[funcClass] = Function;
+ ctorByClass[objectClass] = Object;
+ ctorByClass[numberClass] = Number;
+ ctorByClass[regexpClass] = RegExp;
+ ctorByClass[stringClass] = String;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object which wraps the given value to enable intuitive
+ * method chaining.
+ *
+ * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+ * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+ * and `unshift`
+ *
+ * Chaining is supported in custom builds as long as the `value` method is
+ * implicitly or explicitly included in the build.
+ *
+ * The chainable wrapper functions are:
+ * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+ * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+ * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+ * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+ * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+ * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+ * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+ * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+ * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+ * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+ * and `zip`
+ *
+ * The non-chainable wrapper functions are:
+ * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+ * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+ * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+ * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+ * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+ * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+ * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+ * `template`, `unescape`, `uniqueId`, and `value`
+ *
+ * The wrapper functions `first` and `last` return wrapped values when `n` is
+ * provided, otherwise they return unwrapped values.
+ *
+ * Explicit chaining can be enabled by using the `_.chain` method.
+ *
+ * @name _
+ * @constructor
+ * @category Chaining
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns a `lodash` instance.
+ * @example
+ *
+ * var wrapped = _([1, 2, 3]);
+ *
+ * // returns an unwrapped value
+ * wrapped.reduce(function(sum, num) {
+ * return sum + num;
+ * });
+ * // => 6
+ *
+ * // returns a wrapped value
+ * var squares = wrapped.map(function(num) {
+ * return num * num;
+ * });
+ *
+ * _.isArray(squares);
+ * // => false
+ *
+ * _.isArray(squares.value());
+ * // => true
+ */
+ function lodash(value) {
+ // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
+ return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
+ ? value
+ : new lodashWrapper(value);
+ }
+
+ /**
+ * A fast path for creating `lodash` wrapper objects.
+ *
+ * @private
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @param {boolean} chainAll A flag to enable chaining for all methods
+ * @returns {Object} Returns a `lodash` instance.
+ */
+ function lodashWrapper(value, chainAll) {
+ this.__chain__ = !!chainAll;
+ this.__wrapped__ = value;
+ }
+ // ensure `new lodashWrapper` is an instance of `lodash`
+ lodashWrapper.prototype = lodash.prototype;
+
+ /**
+ * An object used to flag environments features.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ var support = lodash.support = {};
+
+ /**
+ * Detect if functions can be decompiled by `Function#toString`
+ * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+
+ /**
+ * Detect if `Function#name` is supported (all but IE).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.funcNames = typeof Function.name == 'string';
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in
+ * embedded Ruby (ERB). Change the following template settings to use alternative
+ * delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'escape': /<%-([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'evaluate': /<%([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'interpolate': reInterpolate,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type string
+ */
+ 'variable': '',
+
+ /**
+ * Used to import variables into the compiled template.
+ *
+ * @memberOf _.templateSettings
+ * @type Object
+ */
+ 'imports': {
+
+ /**
+ * A reference to the `lodash` function.
+ *
+ * @memberOf _.templateSettings.imports
+ * @type Function
+ */
+ '_': lodash
+ }
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The base implementation of `_.bind` that creates the bound function and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new bound function.
+ */
+ function baseBind(bindData) {
+ var func = bindData[0],
+ partialArgs = bindData[2],
+ thisArg = bindData[4];
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.io/#x15.3.4.5
+ if (partialArgs) {
+ // avoid `arguments` object deoptimizations by using `slice` instead
+ // of `Array.prototype.slice.call` and not assigning `arguments` to a
+ // variable as a ternary expression
+ var args = slice(partialArgs);
+ push.apply(args, arguments);
+ }
+ // mimic the constructor's `return` behavior
+ // http://es5.github.io/#x13.2.2
+ if (this instanceof bound) {
+ // ensure `new bound` is an instance of `func`
+ var thisBinding = baseCreate(func.prototype),
+ result = func.apply(thisBinding, args || arguments);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisArg, args || arguments);
+ }
+ setBindData(bound, bindData);
+ return bound;
+ }
+
+ /**
+ * The base implementation of `_.clone` without argument juggling or support
+ * for `thisArg` binding.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep=false] Specify a deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates clones with source counterparts.
+ * @returns {*} Returns the cloned value.
+ */
+ function baseClone(value, isDeep, callback, stackA, stackB) {
+ if (callback) {
+ var result = callback(value);
+ if (typeof result != 'undefined') {
+ return result;
+ }
+ }
+ // inspect [[Class]]
+ var isObj = isObject(value);
+ if (isObj) {
+ var className = toString.call(value);
+ if (!cloneableClasses[className]) {
+ return value;
+ }
+ var ctor = ctorByClass[className];
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ return new ctor(+value);
+
+ case numberClass:
+ case stringClass:
+ return new ctor(value);
+
+ case regexpClass:
+ result = ctor(value.source, reFlags.exec(value));
+ result.lastIndex = value.lastIndex;
+ return result;
+ }
+ } else {
+ return value;
+ }
+ var isArr = isArray(value);
+ if (isDeep) {
+ // check for circular references and return corresponding clone
+ var initedStack = !stackA;
+ stackA || (stackA = getArray());
+ stackB || (stackB = getArray());
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == value) {
+ return stackB[length];
+ }
+ }
+ result = isArr ? ctor(value.length) : {};
+ }
+ else {
+ result = isArr ? slice(value) : assign({}, value);
+ }
+ // add array properties assigned by `RegExp#exec`
+ if (isArr) {
+ if (hasOwnProperty.call(value, 'index')) {
+ result.index = value.index;
+ }
+ if (hasOwnProperty.call(value, 'input')) {
+ result.input = value.input;
+ }
+ }
+ // exit for shallow clone
+ if (!isDeep) {
+ return result;
+ }
+ // add the source value to the stack of traversed objects
+ // and associate it with its clone
+ stackA.push(value);
+ stackB.push(result);
+
+ // recursively populate clone (susceptible to call stack limits)
+ (isArr ? forEach : forOwn)(value, function(objValue, key) {
+ result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
+ });
+
+ if (initedStack) {
+ releaseArray(stackA);
+ releaseArray(stackB);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} prototype The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+ function baseCreate(prototype, properties) {
+ return isObject(prototype) ? nativeCreate(prototype) : {};
+ }
+ // fallback for browsers without `Object.create`
+ if (!nativeCreate) {
+ baseCreate = (function() {
+ function Object() {}
+ return function(prototype) {
+ if (isObject(prototype)) {
+ Object.prototype = prototype;
+ var result = new Object;
+ Object.prototype = null;
+ }
+ return result || context.Object();
+ };
+ }());
+ }
+
+ /**
+ * The base implementation of `_.createCallback` without support for creating
+ * "_.pluck" or "_.where" style callbacks.
+ *
+ * @private
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ */
+ function baseCreateCallback(func, thisArg, argCount) {
+ if (typeof func != 'function') {
+ return identity;
+ }
+ // exit early for no `thisArg` or already bound by `Function#bind`
+ if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+ return func;
+ }
+ var bindData = func.__bindData__;
+ if (typeof bindData == 'undefined') {
+ if (support.funcNames) {
+ bindData = !func.name;
+ }
+ bindData = bindData || !support.funcDecomp;
+ if (!bindData) {
+ var source = fnToString.call(func);
+ if (!support.funcNames) {
+ bindData = !reFuncName.test(source);
+ }
+ if (!bindData) {
+ // checks if `func` references the `this` keyword and stores the result
+ bindData = reThis.test(source);
+ setBindData(func, bindData);
+ }
+ }
+ }
+ // exit early if there are no `this` references or `func` is bound
+ if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+ return func;
+ }
+ switch (argCount) {
+ case 1: return function(value) {
+ return func.call(thisArg, value);
+ };
+ case 2: return function(a, b) {
+ return func.call(thisArg, a, b);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(thisArg, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(thisArg, accumulator, value, index, collection);
+ };
+ }
+ return bind(func, thisArg);
+ }
+
+ /**
+ * The base implementation of `createWrapper` that creates the wrapper and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new function.
+ */
+ function baseCreateWrapper(bindData) {
+ var func = bindData[0],
+ bitmask = bindData[1],
+ partialArgs = bindData[2],
+ partialRightArgs = bindData[3],
+ thisArg = bindData[4],
+ arity = bindData[5];
+
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ key = func;
+
+ function bound() {
+ var thisBinding = isBind ? thisArg : this;
+ if (partialArgs) {
+ var args = slice(partialArgs);
+ push.apply(args, arguments);
+ }
+ if (partialRightArgs || isCurry) {
+ args || (args = slice(arguments));
+ if (partialRightArgs) {
+ push.apply(args, partialRightArgs);
+ }
+ if (isCurry && args.length < arity) {
+ bitmask |= 16 & ~32;
+ return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+ }
+ }
+ args || (args = arguments);
+ if (isBindKey) {
+ func = thisBinding[key];
+ }
+ if (this instanceof bound) {
+ thisBinding = baseCreate(func.prototype);
+ var result = func.apply(thisBinding, args);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisBinding, args);
+ }
+ setBindData(bound, bindData);
+ return bound;
+ }
+
+ /**
+ * The base implementation of `_.difference` that accepts a single array
+ * of values to exclude.
+ *
+ * @private
+ * @param {Array} array The array to process.
+ * @param {Array} [values] The array of values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ */
+ function baseDifference(array, values) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = array ? array.length : 0,
+ isLarge = length >= largeArraySize && indexOf === baseIndexOf,
+ result = [];
+
+ if (isLarge) {
+ var cache = createCache(values);
+ if (cache) {
+ indexOf = cacheIndexOf;
+ values = cache;
+ } else {
+ isLarge = false;
+ }
+ }
+ while (++index < length) {
+ var value = array[index];
+ if (indexOf(values, value) < 0) {
+ result.push(value);
+ }
+ }
+ if (isLarge) {
+ releaseObject(values);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.flatten` without support for callback
+ * shorthands or `thisArg` binding.
+ *
+ * @private
+ * @param {Array} array The array to flatten.
+ * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+ * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+ * @param {number} [fromIndex=0] The index to start from.
+ * @returns {Array} Returns a new flattened array.
+ */
+ function baseFlatten(array, isShallow, isStrict, fromIndex) {
+ var index = (fromIndex || 0) - 1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ if (value && typeof value == 'object' && typeof value.length == 'number'
+ && (isArray(value) || isArguments(value))) {
+ // recursively flatten arrays (susceptible to call stack limits)
+ if (!isShallow) {
+ value = baseFlatten(value, isShallow, isStrict);
+ }
+ var valIndex = -1,
+ valLength = value.length,
+ resIndex = result.length;
+
+ result.length += valLength;
+ while (++valIndex < valLength) {
+ result[resIndex++] = value[valIndex];
+ }
+ } else if (!isStrict) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+ * that allows partial "_.where" style comparisons.
+ *
+ * @private
+ * @param {*} a The value to compare.
+ * @param {*} b The other value to compare.
+ * @param {Function} [callback] The function to customize comparing values.
+ * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+ * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+ * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
+ function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
+ // used to indicate that when comparing objects, `a` has at least the properties of `b`
+ if (callback) {
+ var result = callback(a, b);
+ if (typeof result != 'undefined') {
+ return !!result;
+ }
+ }
+ // exit early for identical values
+ if (a === b) {
+ // treat `+0` vs. `-0` as not equal
+ return a !== 0 || (1 / a == 1 / b);
+ }
+ var type = typeof a,
+ otherType = typeof b;
+
+ // exit early for unlike primitive values
+ if (a === a &&
+ !(a && objectTypes[type]) &&
+ !(b && objectTypes[otherType])) {
+ return false;
+ }
+ // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
+ // http://es5.github.io/#x15.3.4.4
+ if (a == null || b == null) {
+ return a === b;
+ }
+ // compare [[Class]] names
+ var className = toString.call(a),
+ otherClass = toString.call(b);
+
+ if (className == argsClass) {
+ className = objectClass;
+ }
+ if (otherClass == argsClass) {
+ otherClass = objectClass;
+ }
+ if (className != otherClass) {
+ return false;
+ }
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ // coerce dates and booleans to numbers, dates to milliseconds and booleans
+ // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
+ return +a == +b;
+
+ case numberClass:
+ // treat `NaN` vs. `NaN` as equal
+ return (a != +a)
+ ? b != +b
+ // but treat `+0` vs. `-0` as not equal
+ : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+ case regexpClass:
+ case stringClass:
+ // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
+ // treat string primitives and their corresponding object instances as equal
+ return a == String(b);
+ }
+ var isArr = className == arrayClass;
+ if (!isArr) {
+ // unwrap any `lodash` wrapped values
+ var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
+ bWrapped = hasOwnProperty.call(b, '__wrapped__');
+
+ if (aWrapped || bWrapped) {
+ return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
+ }
+ // exit for functions and DOM nodes
+ if (className != objectClass) {
+ return false;
+ }
+ // in older versions of Opera, `arguments` objects have `Array` constructors
+ var ctorA = a.constructor,
+ ctorB = b.constructor;
+
+ // non `Object` object instances with different constructors are not equal
+ if (ctorA != ctorB &&
+ !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+ ('constructor' in a && 'constructor' in b)
+ ) {
+ return false;
+ }
+ }
+ // assume cyclic structures are equal
+ // the algorithm for detecting cyclic structures is adapted from ES 5.1
+ // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
+ var initedStack = !stackA;
+ stackA || (stackA = getArray());
+ stackB || (stackB = getArray());
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == a) {
+ return stackB[length] == b;
+ }
+ }
+ var size = 0;
+ result = true;
+
+ // add `a` and `b` to the stack of traversed objects
+ stackA.push(a);
+ stackB.push(b);
+
+ // recursively compare objects and arrays (susceptible to call stack limits)
+ if (isArr) {
+ // compare lengths to determine if a deep comparison is necessary
+ length = a.length;
+ size = b.length;
+ result = size == length;
+
+ if (result || isWhere) {
+ // deep compare the contents, ignoring non-numeric properties
+ while (size--) {
+ var index = length,
+ value = b[size];
+
+ if (isWhere) {
+ while (index--) {
+ if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
+ break;
+ }
+ }
+ } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
+ break;
+ }
+ }
+ }
+ }
+ else {
+ // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+ // which, in this case, is more costly
+ forIn(b, function(value, key, b) {
+ if (hasOwnProperty.call(b, key)) {
+ // count the number of properties.
+ size++;
+ // deep compare each property value.
+ return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
+ }
+ });
+
+ if (result && !isWhere) {
+ // ensure both objects have the same number of properties
+ forIn(a, function(value, key, a) {
+ if (hasOwnProperty.call(a, key)) {
+ // `size` will be `-1` if `a` has more properties than `b`
+ return (result = --size > -1);
+ }
+ });
+ }
+ }
+ stackA.pop();
+ stackB.pop();
+
+ if (initedStack) {
+ releaseArray(stackA);
+ releaseArray(stackB);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.merge` without argument juggling or support
+ * for `thisArg` binding.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {Function} [callback] The function to customize merging properties.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
+ */
+ function baseMerge(object, source, callback, stackA, stackB) {
+ (isArray(source) ? forEach : forOwn)(source, function(source, key) {
+ var found,
+ isArr,
+ result = source,
+ value = object[key];
+
+ if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+ // avoid merging previously merged cyclic sources
+ var stackLength = stackA.length;
+ while (stackLength--) {
+ if ((found = stackA[stackLength] == source)) {
+ value = stackB[stackLength];
+ break;
+ }
+ }
+ if (!found) {
+ var isShallow;
+ if (callback) {
+ result = callback(value, source);
+ if ((isShallow = typeof result != 'undefined')) {
+ value = result;
+ }
+ }
+ if (!isShallow) {
+ value = isArr
+ ? (isArray(value) ? value : [])
+ : (isPlainObject(value) ? value : {});
+ }
+ // add `source` and associated `value` to the stack of traversed objects
+ stackA.push(source);
+ stackB.push(value);
+
+ // recursively merge objects and arrays (susceptible to call stack limits)
+ if (!isShallow) {
+ baseMerge(value, source, callback, stackA, stackB);
+ }
+ }
+ }
+ else {
+ if (callback) {
+ result = callback(value, source);
+ if (typeof result == 'undefined') {
+ result = source;
+ }
+ }
+ if (typeof result != 'undefined') {
+ value = result;
+ }
+ }
+ object[key] = value;
+ });
+ }
+
+ /**
+ * The base implementation of `_.random` without argument juggling or support
+ * for returning floating-point numbers.
+ *
+ * @private
+ * @param {number} min The minimum possible value.
+ * @param {number} max The maximum possible value.
+ * @returns {number} Returns a random number.
+ */
+ function baseRandom(min, max) {
+ return min + floor(nativeRandom() * (max - min + 1));
+ }
+
+ /**
+ * The base implementation of `_.uniq` without support for callback shorthands
+ * or `thisArg` binding.
+ *
+ * @private
+ * @param {Array} array The array to process.
+ * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+ * @param {Function} [callback] The function called per iteration.
+ * @returns {Array} Returns a duplicate-value-free array.
+ */
+ function baseUniq(array, isSorted, callback) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = array ? array.length : 0,
+ result = [];
+
+ var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
+ seen = (callback || isLarge) ? getArray() : result;
+
+ if (isLarge) {
+ var cache = createCache(seen);
+ indexOf = cacheIndexOf;
+ seen = cache;
+ }
+ while (++index < length) {
+ var value = array[index],
+ computed = callback ? callback(value, index, array) : value;
+
+ if (isSorted
+ ? !index || seen[seen.length - 1] !== computed
+ : indexOf(seen, computed) < 0
+ ) {
+ if (callback || isLarge) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ if (isLarge) {
+ releaseArray(seen.array);
+ releaseObject(seen);
+ } else if (callback) {
+ releaseArray(seen);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a function that aggregates a collection, creating an object composed
+ * of keys generated from the results of running each element of the collection
+ * through a callback. The given `setter` function sets the keys and values
+ * of the composed object.
+ *
+ * @private
+ * @param {Function} setter The setter function.
+ * @returns {Function} Returns the new aggregator function.
+ */
+ function createAggregator(setter) {
+ return function(collection, callback, thisArg) {
+ var result = {};
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ var value = collection[index];
+ setter(result, value, callback(value, index, collection), collection);
+ }
+ } else {
+ forOwn(collection, function(value, key, collection) {
+ setter(result, value, callback(value, key, collection), collection);
+ });
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, either curries or invokes `func`
+ * with an optional `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to reference.
+ * @param {number} bitmask The bitmask of method flags to compose.
+ * The bitmask may be composed of the following flags:
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry`
+ * 8 - `_.curry` (bound)
+ * 16 - `_.partial`
+ * 32 - `_.partialRight`
+ * @param {Array} [partialArgs] An array of arguments to prepend to those
+ * provided to the new function.
+ * @param {Array} [partialRightArgs] An array of arguments to append to those
+ * provided to the new function.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new function.
+ */
+ function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ isPartial = bitmask & 16,
+ isPartialRight = bitmask & 32;
+
+ if (!isBindKey && !isFunction(func)) {
+ throw new TypeError;
+ }
+ if (isPartial && !partialArgs.length) {
+ bitmask &= ~16;
+ isPartial = partialArgs = false;
+ }
+ if (isPartialRight && !partialRightArgs.length) {
+ bitmask &= ~32;
+ isPartialRight = partialRightArgs = false;
+ }
+ var bindData = func && func.__bindData__;
+ if (bindData && bindData !== true) {
+ // clone `bindData`
+ bindData = slice(bindData);
+ if (bindData[2]) {
+ bindData[2] = slice(bindData[2]);
+ }
+ if (bindData[3]) {
+ bindData[3] = slice(bindData[3]);
+ }
+ // set `thisBinding` is not previously bound
+ if (isBind && !(bindData[1] & 1)) {
+ bindData[4] = thisArg;
+ }
+ // set if previously bound but not currently (subsequent curried functions)
+ if (!isBind && bindData[1] & 1) {
+ bitmask |= 8;
+ }
+ // set curried arity if not yet set
+ if (isCurry && !(bindData[1] & 4)) {
+ bindData[5] = arity;
+ }
+ // append partial left arguments
+ if (isPartial) {
+ push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+ }
+ // append partial right arguments
+ if (isPartialRight) {
+ unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+ }
+ // merge flags
+ bindData[1] |= bitmask;
+ return createWrapper.apply(null, bindData);
+ }
+ // fast path for `_.bind`
+ var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+ return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+ }
+
+ /**
+ * Used by `escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeHtmlChar(match) {
+ return htmlEscapes[match];
+ }
+
+ /**
+ * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+ * customized, this method returns the custom method, otherwise it returns
+ * the `baseIndexOf` function.
+ *
+ * @private
+ * @returns {Function} Returns the "indexOf" function.
+ */
+ function getIndexOf() {
+ var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a native function.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+ */
+ function isNative(value) {
+ return typeof value == 'function' && reNative.test(value);
+ }
+
+ /**
+ * Sets `this` binding data on a given function.
+ *
+ * @private
+ * @param {Function} func The function to set data on.
+ * @param {Array} value The data array to set.
+ */
+ var setBindData = !defineProperty ? noop : function(func, value) {
+ descriptor.value = value;
+ defineProperty(func, '__bindData__', descriptor);
+ };
+
+ /**
+ * A fallback implementation of `isPlainObject` which checks if a given value
+ * is an object created by the `Object` constructor, assuming objects created
+ * by the `Object` constructor have no inherited enumerable properties and that
+ * there are no `Object.prototype` extensions.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ */
+ function shimIsPlainObject(value) {
+ var ctor,
+ result;
+
+ // avoid non Object objects, `arguments` objects, and DOM elements
+ if (!(value && toString.call(value) == objectClass) ||
+ (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) {
+ return false;
+ }
+ // In most environments an object's own properties are iterated before
+ // its inherited properties. If the last iterated property is an object's
+ // own property then there are no inherited enumerable properties.
+ forIn(value, function(value, key) {
+ result = key;
+ });
+ return typeof result == 'undefined' || hasOwnProperty.call(value, result);
+ }
+
+ /**
+ * Used by `unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} match The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
+ function unescapeHtmlChar(match) {
+ return htmlUnescapes[match];
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if `value` is an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+ * @example
+ *
+ * (function() { return _.isArguments(arguments); })(1, 2, 3);
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ function isArguments(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ toString.call(value) == argsClass || false;
+ }
+
+ /**
+ * Checks if `value` is an array.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+ * @example
+ *
+ * (function() { return _.isArray(arguments); })();
+ * // => false
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ */
+ var isArray = nativeIsArray || function(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ toString.call(value) == arrayClass || false;
+ };
+
+ /**
+ * A fallback implementation of `Object.keys` which produces an array of the
+ * given object's own enumerable property names.
+ *
+ * @private
+ * @type Function
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ */
+ var shimKeys = function(object) {
+ var index, iterable = object, result = [];
+ if (!iterable) return result;
+ if (!(objectTypes[typeof object])) return result;
+ for (index in iterable) {
+ if (hasOwnProperty.call(iterable, index)) {
+ result.push(index);
+ }
+ }
+ return result
+ };
+
+ /**
+ * Creates an array composed of the own enumerable property names of an object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+ */
+ var keys = !nativeKeys ? shimKeys : function(object) {
+ if (!isObject(object)) {
+ return [];
+ }
+ return nativeKeys(object);
+ };
+
+ /**
+ * Used to convert characters to HTML entities:
+ *
+ * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+ * don't require escaping in HTML and have no special meaning unless they're part
+ * of a tag or an unquoted attribute value.
+ * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+ */
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+
+ /** Used to convert HTML entities to characters */
+ var htmlUnescapes = invert(htmlEscapes);
+
+ /** Used to match HTML entities and HTML characters */
+ var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+ reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object. Subsequent sources will overwrite property assignments of previous
+ * sources. If a callback is provided it will be executed to produce the
+ * assigned values. The callback is bound to `thisArg` and invoked with two
+ * arguments; (objectValue, sourceValue).
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @alias extend
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param {Function} [callback] The function to customize assigning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+ * // => { 'name': 'fred', 'employer': 'slate' }
+ *
+ * var defaults = _.partialRight(_.assign, function(a, b) {
+ * return typeof a == 'undefined' ? b : a;
+ * });
+ *
+ * var object = { 'name': 'barney' };
+ * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+ * // => { 'name': 'barney', 'employer': 'slate' }
+ */
+ var assign = function(object, source, guard) {
+ var index, iterable = object, result = iterable;
+ if (!iterable) return result;
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = typeof guard == 'number' ? 2 : args.length;
+ if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
+ var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
+ } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
+ callback = args[--argsLength];
+ }
+ while (++argsIndex < argsLength) {
+ iterable = args[argsIndex];
+ if (iterable && objectTypes[typeof iterable]) {
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
+ }
+ }
+ }
+ return result
+ };
+
+ /**
+ * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+ * be cloned, otherwise they will be assigned by reference. If a callback
+ * is provided it will be executed to produce the cloned values. If the
+ * callback returns `undefined` cloning will be handled by the method instead.
+ * The callback is bound to `thisArg` and invoked with one argument; (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep=false] Specify a deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the cloned value.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * var shallow = _.clone(characters);
+ * shallow[0] === characters[0];
+ * // => true
+ *
+ * var deep = _.clone(characters, true);
+ * deep[0] === characters[0];
+ * // => false
+ *
+ * _.mixin({
+ * 'clone': _.partialRight(_.clone, function(value) {
+ * return _.isElement(value) ? value.cloneNode(false) : undefined;
+ * })
+ * });
+ *
+ * var clone = _.clone(document.body);
+ * clone.childNodes.length;
+ * // => 0
+ */
+ function clone(value, isDeep, callback, thisArg) {
+ // allows working with "Collections" methods without using their `index`
+ // and `collection` arguments for `isDeep` and `callback`
+ if (typeof isDeep != 'boolean' && isDeep != null) {
+ thisArg = callback;
+ callback = isDeep;
+ isDeep = false;
+ }
+ return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+ }
+
+ /**
+ * Creates a deep clone of `value`. If a callback is provided it will be
+ * executed to produce the cloned values. If the callback returns `undefined`
+ * cloning will be handled by the method instead. The callback is bound to
+ * `thisArg` and invoked with one argument; (value).
+ *
+ * Note: This method is loosely based on the structured clone algorithm. Functions
+ * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
+ * objects created by constructors other than `Object` are cloned to plain `Object` objects.
+ * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the deep cloned value.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * var deep = _.cloneDeep(characters);
+ * deep[0] === characters[0];
+ * // => false
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'node': element
+ * };
+ *
+ * var clone = _.cloneDeep(view, function(value) {
+ * return _.isElement(value) ? value.cloneNode(true) : undefined;
+ * });
+ *
+ * clone.node == view.node;
+ * // => false
+ */
+ function cloneDeep(value, callback, thisArg) {
+ return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+ }
+
+ /**
+ * Creates an object that inherits from the given `prototype` object. If a
+ * `properties` object is provided its own enumerable properties are assigned
+ * to the created object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} prototype The object to inherit from.
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * function Circle() {
+ * Shape.call(this);
+ * }
+ *
+ * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
+ *
+ * var circle = new Circle;
+ * circle instanceof Circle;
+ * // => true
+ *
+ * circle instanceof Shape;
+ * // => true
+ */
+ function create(prototype, properties) {
+ var result = baseCreate(prototype);
+ return properties ? assign(result, properties) : result;
+ }
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object for all destination properties that resolve to `undefined`. Once a
+ * property is set, additional defaults of the same property will be ignored.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param- {Object} [guard] Allows working with `_.reduce` without using its
+ * `key` and `object` arguments as sources.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var object = { 'name': 'barney' };
+ * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+ * // => { 'name': 'barney', 'employer': 'slate' }
+ */
+ var defaults = function(object, source, guard) {
+ var index, iterable = object, result = iterable;
+ if (!iterable) return result;
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = typeof guard == 'number' ? 2 : args.length;
+ while (++argsIndex < argsLength) {
+ iterable = args[argsIndex];
+ if (iterable && objectTypes[typeof iterable]) {
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ if (typeof result[index] == 'undefined') result[index] = iterable[index];
+ }
+ }
+ }
+ return result
+ };
+
+ /**
+ * This method is like `_.findIndex` except that it returns the key of the
+ * first element that passes the callback check, instead of the element itself.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [callback=identity] The function called per
+ * iteration. If a property name or object is provided it will be used to
+ * create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+ * @example
+ *
+ * var characters = {
+ * 'barney': { 'age': 36, 'blocked': false },
+ * 'fred': { 'age': 40, 'blocked': true },
+ * 'pebbles': { 'age': 1, 'blocked': false }
+ * };
+ *
+ * _.findKey(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => 'barney' (property order is not guaranteed across environments)
+ *
+ * // using "_.where" callback shorthand
+ * _.findKey(characters, { 'age': 1 });
+ * // => 'pebbles'
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findKey(characters, 'blocked');
+ * // => 'fred'
+ */
+ function findKey(object, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forOwn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * This method is like `_.findKey` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [callback=identity] The function called per
+ * iteration. If a property name or object is provided it will be used to
+ * create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+ * @example
+ *
+ * var characters = {
+ * 'barney': { 'age': 36, 'blocked': true },
+ * 'fred': { 'age': 40, 'blocked': false },
+ * 'pebbles': { 'age': 1, 'blocked': true }
+ * };
+ *
+ * _.findLastKey(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => returns `pebbles`, assuming `_.findKey` returns `barney`
+ *
+ * // using "_.where" callback shorthand
+ * _.findLastKey(characters, { 'age': 40 });
+ * // => 'fred'
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findLastKey(characters, 'blocked');
+ * // => 'pebbles'
+ */
+ function findLastKey(object, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forOwnRight(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over own and inherited enumerable properties of an object,
+ * executing the callback for each property. The callback is bound to `thisArg`
+ * and invoked with three arguments; (value, key, object). Callbacks may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * Shape.prototype.move = function(x, y) {
+ * this.x += x;
+ * this.y += y;
+ * };
+ *
+ * _.forIn(new Shape, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+ */
+ var forIn = function(collection, callback, thisArg) {
+ var index, iterable = collection, result = iterable;
+ if (!iterable) return result;
+ if (!objectTypes[typeof iterable]) return result;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ for (index in iterable) {
+ if (callback(iterable[index], index, collection) === false) return result;
+ }
+ return result
+ };
+
+ /**
+ * This method is like `_.forIn` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * Shape.prototype.move = function(x, y) {
+ * this.x += x;
+ * this.y += y;
+ * };
+ *
+ * _.forInRight(new Shape, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
+ */
+ function forInRight(object, callback, thisArg) {
+ var pairs = [];
+
+ forIn(object, function(value, key) {
+ pairs.push(key, value);
+ });
+
+ var length = pairs.length;
+ callback = baseCreateCallback(callback, thisArg, 3);
+ while (length--) {
+ if (callback(pairs[length--], pairs[length], object) === false) {
+ break;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Iterates over own enumerable properties of an object, executing the callback
+ * for each property. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, key, object). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+ */
+ var forOwn = function(collection, callback, thisArg) {
+ var index, iterable = collection, result = iterable;
+ if (!iterable) return result;
+ if (!objectTypes[typeof iterable]) return result;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ if (callback(iterable[index], index, collection) === false) return result;
+ }
+ return result
+ };
+
+ /**
+ * This method is like `_.forOwn` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
+ */
+ function forOwnRight(object, callback, thisArg) {
+ var props = keys(object),
+ length = props.length;
+
+ callback = baseCreateCallback(callback, thisArg, 3);
+ while (length--) {
+ var key = props[length];
+ if (callback(object[key], key, object) === false) {
+ break;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Creates a sorted array of property names of all enumerable properties,
+ * own and inherited, of `object` that have function values.
+ *
+ * @static
+ * @memberOf _
+ * @alias methods
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names that have function values.
+ * @example
+ *
+ * _.functions(_);
+ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+ */
+ function functions(object) {
+ var result = [];
+ forIn(object, function(value, key) {
+ if (isFunction(value)) {
+ result.push(key);
+ }
+ });
+ return result.sort();
+ }
+
+ /**
+ * Checks if the specified property name exists as a direct property of `object`,
+ * instead of an inherited property.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to check.
+ * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+ * @example
+ *
+ * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+ * // => true
+ */
+ function has(object, key) {
+ return object ? hasOwnProperty.call(object, key) : false;
+ }
+
+ /**
+ * Creates an object composed of the inverted keys and values of the given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the created inverted object.
+ * @example
+ *
+ * _.invert({ 'first': 'fred', 'second': 'barney' });
+ * // => { 'fred': 'first', 'barney': 'second' }
+ */
+ function invert(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = {};
+
+ while (++index < length) {
+ var key = props[index];
+ result[object[key]] = key;
+ }
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a boolean value.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+ * @example
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false ||
+ value && typeof value == 'object' && toString.call(value) == boolClass || false;
+ }
+
+ /**
+ * Checks if `value` is a date.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ */
+ function isDate(value) {
+ return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+ }
+
+ /**
+ * Checks if `value` is a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ */
+ function isElement(value) {
+ return value && value.nodeType === 1 || false;
+ }
+
+ /**
+ * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+ * length of `0` and objects with no own enumerable properties are considered
+ * "empty".
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|string} value The value to inspect.
+ * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({});
+ * // => true
+ *
+ * _.isEmpty('');
+ * // => true
+ */
+ function isEmpty(value) {
+ var result = true;
+ if (!value) {
+ return result;
+ }
+ var className = toString.call(value),
+ length = value.length;
+
+ if ((className == arrayClass || className == stringClass || className == argsClass ) ||
+ (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+ return !length;
+ }
+ forOwn(value, function() {
+ return (result = false);
+ });
+ return result;
+ }
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent to each other. If a callback is provided it will be executed
+ * to compare values. If the callback returns `undefined` comparisons will
+ * be handled by the method instead. The callback is bound to `thisArg` and
+ * invoked with two arguments; (a, b).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} a The value to compare.
+ * @param {*} b The other value to compare.
+ * @param {Function} [callback] The function to customize comparing values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * var copy = { 'name': 'fred' };
+ *
+ * object == copy;
+ * // => false
+ *
+ * _.isEqual(object, copy);
+ * // => true
+ *
+ * var words = ['hello', 'goodbye'];
+ * var otherWords = ['hi', 'goodbye'];
+ *
+ * _.isEqual(words, otherWords, function(a, b) {
+ * var reGreet = /^(?:hello|hi)$/i,
+ * aGreet = _.isString(a) && reGreet.test(a),
+ * bGreet = _.isString(b) && reGreet.test(b);
+ *
+ * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+ * });
+ * // => true
+ */
+ function isEqual(a, b, callback, thisArg) {
+ return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
+ }
+
+ /**
+ * Checks if `value` is, or can be coerced to, a finite number.
+ *
+ * Note: This is not the same as native `isFinite` which will return true for
+ * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+ * @example
+ *
+ * _.isFinite(-101);
+ * // => true
+ *
+ * _.isFinite('10');
+ * // => true
+ *
+ * _.isFinite(true);
+ * // => false
+ *
+ * _.isFinite('');
+ * // => false
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+ }
+
+ /**
+ * Checks if `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ */
+ function isFunction(value) {
+ return typeof value == 'function';
+ }
+
+ /**
+ * Checks if `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+ function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.io/#x8
+ // and avoid a V8 bug
+ // http://code.google.com/p/v8/issues/detail?id=2291
+ return !!(value && objectTypes[typeof value]);
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * Note: This is not the same as native `isNaN` which will return `true` for
+ * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // `NaN` as a primitive is the only value that is not equal to itself
+ // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+ return isNumber(value) && value != +value;
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(undefined);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is a number.
+ *
+ * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(8.4 * 5);
+ * // => true
+ */
+ function isNumber(value) {
+ return typeof value == 'number' ||
+ value && typeof value == 'object' && toString.call(value) == numberClass || false;
+ }
+
+ /**
+ * Checks if `value` is an object created by the `Object` constructor.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * _.isPlainObject(new Shape);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ */
+ var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+ if (!(value && toString.call(value) == objectClass)) {
+ return false;
+ }
+ var valueOf = value.valueOf,
+ objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+ return objProto
+ ? (value == objProto || getPrototypeOf(value) == objProto)
+ : shimIsPlainObject(value);
+ };
+
+ /**
+ * Checks if `value` is a regular expression.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+ * @example
+ *
+ * _.isRegExp(/fred/);
+ * // => true
+ */
+ function isRegExp(value) {
+ return value && typeof value == 'object' && toString.call(value) == regexpClass || false;
+ }
+
+ /**
+ * Checks if `value` is a string.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('fred');
+ * // => true
+ */
+ function isString(value) {
+ return typeof value == 'string' ||
+ value && typeof value == 'object' && toString.call(value) == stringClass || false;
+ }
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ */
+ function isUndefined(value) {
+ return typeof value == 'undefined';
+ }
+
+ /**
+ * Creates an object with the same keys as `object` and values generated by
+ * running each own enumerable property of `object` through the callback.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new object with values of the results of each `callback` execution.
+ * @example
+ *
+ * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ *
+ * var characters = {
+ * 'fred': { 'name': 'fred', 'age': 40 },
+ * 'pebbles': { 'name': 'pebbles', 'age': 1 }
+ * };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.mapValues(characters, 'age');
+ * // => { 'fred': 40, 'pebbles': 1 }
+ */
+ function mapValues(object, callback, thisArg) {
+ var result = {};
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ forOwn(object, function(value, key, object) {
+ result[key] = callback(value, key, object);
+ });
+ return result;
+ }
+
+ /**
+ * Recursively merges own enumerable properties of the source object(s), that
+ * don't resolve to `undefined` into the destination object. Subsequent sources
+ * will overwrite property assignments of previous sources. If a callback is
+ * provided it will be executed to produce the merged values of the destination
+ * and source properties. If the callback returns `undefined` merging will
+ * be handled by the method instead. The callback is bound to `thisArg` and
+ * invoked with two arguments; (objectValue, sourceValue).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param {Function} [callback] The function to customize merging properties.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var names = {
+ * 'characters': [
+ * { 'name': 'barney' },
+ * { 'name': 'fred' }
+ * ]
+ * };
+ *
+ * var ages = {
+ * 'characters': [
+ * { 'age': 36 },
+ * { 'age': 40 }
+ * ]
+ * };
+ *
+ * _.merge(names, ages);
+ * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
+ *
+ * var food = {
+ * 'fruits': ['apple'],
+ * 'vegetables': ['beet']
+ * };
+ *
+ * var otherFood = {
+ * 'fruits': ['banana'],
+ * 'vegetables': ['carrot']
+ * };
+ *
+ * _.merge(food, otherFood, function(a, b) {
+ * return _.isArray(a) ? a.concat(b) : undefined;
+ * });
+ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
+ */
+ function merge(object) {
+ var args = arguments,
+ length = 2;
+
+ if (!isObject(object)) {
+ return object;
+ }
+ // allows working with `_.reduce` and `_.reduceRight` without using
+ // their `index` and `collection` arguments
+ if (typeof args[2] != 'number') {
+ length = args.length;
+ }
+ if (length > 3 && typeof args[length - 2] == 'function') {
+ var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
+ } else if (length > 2 && typeof args[length - 1] == 'function') {
+ callback = args[--length];
+ }
+ var sources = slice(arguments, 1, length),
+ index = -1,
+ stackA = getArray(),
+ stackB = getArray();
+
+ while (++index < length) {
+ baseMerge(object, sources[index], callback, stackA, stackB);
+ }
+ releaseArray(stackA);
+ releaseArray(stackB);
+ return object;
+ }
+
+ /**
+ * Creates a shallow clone of `object` excluding the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If a callback is provided it will be executed for each
+ * property of `object` omitting the properties the callback returns truey
+ * for. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|...string|string[]} [callback] The properties to omit or the
+ * function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object without the omitted properties.
+ * @example
+ *
+ * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+ * // => { 'name': 'fred' }
+ *
+ * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+ * return typeof value == 'number';
+ * });
+ * // => { 'name': 'fred' }
+ */
+ function omit(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var props = [];
+ forIn(object, function(value, key) {
+ props.push(key);
+ });
+ props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+ result[key] = object[key];
+ }
+ } else {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forIn(object, function(value, key, object) {
+ if (!callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates a two dimensional array of an object's key-value pairs,
+ * i.e. `[[key1, value1], [key2, value2]]`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns new array of key-value pairs.
+ * @example
+ *
+ * _.pairs({ 'barney': 36, 'fred': 40 });
+ * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+ */
+ function pairs(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = Array(length);
+
+ while (++index < length) {
+ var key = props[index];
+ result[index] = [key, object[key]];
+ }
+ return result;
+ }
+
+ /**
+ * Creates a shallow clone of `object` composed of the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If a callback is provided it will be executed for each
+ * property of `object` picking the properties the callback returns truey
+ * for. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|...string|string[]} [callback] The function called per
+ * iteration or property names to pick, specified as individual property
+ * names or arrays of property names.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object composed of the picked properties.
+ * @example
+ *
+ * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+ * // => { 'name': 'fred' }
+ *
+ * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+ * return key.charAt(0) != '_';
+ * });
+ * // => { 'name': 'fred' }
+ */
+ function pick(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var index = -1,
+ props = baseFlatten(arguments, true, false, 1),
+ length = isObject(object) ? props.length : 0;
+
+ while (++index < length) {
+ var key = props[index];
+ if (key in object) {
+ result[key] = object[key];
+ }
+ }
+ } else {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forIn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * An alternative to `_.reduce` this method transforms `object` to a new
+ * `accumulator` object which is the result of running each of its own
+ * enumerable properties through a callback, with each callback execution
+ * potentially mutating the `accumulator` object. The callback is bound to
+ * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
+ * Callbacks may exit iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] The custom accumulator value.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
+ * num *= num;
+ * if (num % 2) {
+ * return result.push(num) < 3;
+ * }
+ * });
+ * // => [1, 9, 25]
+ *
+ * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+ * result[key] = num * 3;
+ * });
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ */
+ function transform(object, callback, accumulator, thisArg) {
+ var isArr = isArray(object);
+ if (accumulator == null) {
+ if (isArr) {
+ accumulator = [];
+ } else {
+ var ctor = object && object.constructor,
+ proto = ctor && ctor.prototype;
+
+ accumulator = baseCreate(proto);
+ }
+ }
+ if (callback) {
+ callback = lodash.createCallback(callback, thisArg, 4);
+ (isArr ? forEach : forOwn)(object, function(value, index, object) {
+ return callback(accumulator, value, index, object);
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property values of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property values.
+ * @example
+ *
+ * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => [1, 2, 3] (property order is not guaranteed across environments)
+ */
+ function values(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = object[props[index]];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of elements from the specified indexes, or keys, of the
+ * `collection`. Indexes may be specified as individual arguments or as arrays
+ * of indexes.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
+ * to retrieve, specified as individual indexes or arrays of indexes.
+ * @returns {Array} Returns a new array of elements corresponding to the
+ * provided indexes.
+ * @example
+ *
+ * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
+ * // => ['a', 'c', 'e']
+ *
+ * _.at(['fred', 'barney', 'pebbles'], 0, 2);
+ * // => ['fred', 'pebbles']
+ */
+ function at(collection) {
+ var args = arguments,
+ index = -1,
+ props = baseFlatten(args, true, false, 1),
+ length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
+ result = Array(length);
+
+ while(++index < length) {
+ result[index] = collection[props[index]];
+ }
+ return result;
+ }
+
+ /**
+ * Checks if a given value is present in a collection using strict equality
+ * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+ * offset from the end of the collection.
+ *
+ * @static
+ * @memberOf _
+ * @alias include
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {*} target The value to check for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+ * @example
+ *
+ * _.contains([1, 2, 3], 1);
+ * // => true
+ *
+ * _.contains([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+ * // => true
+ *
+ * _.contains('pebbles', 'eb');
+ * // => true
+ */
+ function contains(collection, target, fromIndex) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = collection ? collection.length : 0,
+ result = false;
+
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+ if (isArray(collection)) {
+ result = indexOf(collection, target, fromIndex) > -1;
+ } else if (typeof length == 'number') {
+ result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
+ } else {
+ forOwn(collection, function(value) {
+ if (++index >= fromIndex) {
+ return !(result = value === target);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` through the callback. The corresponding value
+ * of each key is the number of times the key was returned by the callback.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ var countBy = createAggregator(function(result, value, key) {
+ (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+ });
+
+ /**
+ * Checks if the given callback returns truey value for **all** elements of
+ * a collection. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias all
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if all elements passed the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes']);
+ * // => false
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.every(characters, 'age');
+ * // => true
+ *
+ * // using "_.where" callback shorthand
+ * _.every(characters, { 'age': 36 });
+ * // => false
+ */
+ function every(collection, callback, thisArg) {
+ var result = true;
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if (!(result = !!callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ return (result = !!callback(value, index, collection));
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, returning an array of all elements
+ * the callback returns truey for. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias select
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that passed the callback check.
+ * @example
+ *
+ * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [2, 4, 6]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.filter(characters, 'blocked');
+ * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+ *
+ * // using "_.where" callback shorthand
+ * _.filter(characters, { 'age': 36 });
+ * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+ */
+ function filter(collection, callback, thisArg) {
+ var result = [];
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, returning the first element that
+ * the callback returns truey for. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias detect, findWhere
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the found element, else `undefined`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ * ];
+ *
+ * _.find(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+ *
+ * // using "_.where" callback shorthand
+ * _.find(characters, { 'age': 1 });
+ * // => { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ *
+ * // using "_.pluck" callback shorthand
+ * _.find(characters, 'blocked');
+ * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+ */
+ function find(collection, callback, thisArg) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ return value;
+ }
+ }
+ } else {
+ var result;
+ forOwn(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+ }
+
+ /**
+ * This method is like `_.find` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the found element, else `undefined`.
+ * @example
+ *
+ * _.findLast([1, 2, 3, 4], function(num) {
+ * return num % 2 == 1;
+ * });
+ * // => 3
+ */
+ function findLast(collection, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forEachRight(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, executing the callback for each
+ * element. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * Note: As with other "Collections" methods, objects with a `length` property
+ * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+ * may be used for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+ * // => logs each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+ * // => logs each number and returns the object (property order is not guaranteed across environments)
+ */
+ function forEach(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if (callback(collection[index], index, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, callback);
+ }
+ return collection;
+ }
+
+ /**
+ * This method is like `_.forEach` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias eachRight
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+ * // => logs each number from right to left and returns '3,2,1'
+ */
+ function forEachRight(collection, callback, thisArg) {
+ var length = collection ? collection.length : 0;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ while (length--) {
+ if (callback(collection[length], length, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ var props = keys(collection);
+ length = props.length;
+ forOwn(collection, function(value, key, collection) {
+ key = props ? props[--length] : --length;
+ return callback(collection[key], key, collection);
+ });
+ }
+ return collection;
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of a collection through the callback. The corresponding value
+ * of each key is an array of the elements responsible for generating the key.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * // using "_.pluck" callback shorthand
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ var groupBy = createAggregator(function(result, value, key) {
+ (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+ });
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of the collection through the given callback. The corresponding
+ * value of each key is the last element responsible for generating the key.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * var keys = [
+ * { 'dir': 'left', 'code': 97 },
+ * { 'dir': 'right', 'code': 100 }
+ * ];
+ *
+ * _.indexBy(keys, 'dir');
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ */
+ var indexBy = createAggregator(function(result, value, key) {
+ result[key] = value;
+ });
+
+ /**
+ * Invokes the method named by `methodName` on each element in the `collection`
+ * returning an array of the results of each invoked method. Additional arguments
+ * will be provided to each invoked method. If `methodName` is a function it
+ * will be invoked for, and `this` bound to, each element in the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|string} methodName The name of the method to invoke or
+ * the function invoked per iteration.
+ * @param {...*} [arg] Arguments to invoke the method with.
+ * @returns {Array} Returns a new array of the results of each invoked method.
+ * @example
+ *
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invoke([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ function invoke(collection, methodName) {
+ var args = slice(arguments, 2),
+ index = -1,
+ isFunc = typeof methodName == 'function',
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ forEach(collection, function(value) {
+ result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+ });
+ return result;
+ }
+
+ /**
+ * Creates an array of values by running each element in the collection
+ * through the callback. The callback is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias collect
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of the results of each `callback` execution.
+ * @example
+ *
+ * _.map([1, 2, 3], function(num) { return num * 3; });
+ * // => [3, 6, 9]
+ *
+ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+ * // => [3, 6, 9] (property order is not guaranteed across environments)
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.map(characters, 'name');
+ * // => ['barney', 'fred']
+ */
+ function map(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ var result = Array(length);
+ while (++index < length) {
+ result[index] = callback(collection[index], index, collection);
+ }
+ } else {
+ result = [];
+ forOwn(collection, function(value, key, collection) {
+ result[++index] = callback(value, key, collection);
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the maximum value of a collection. If the collection is empty or
+ * falsey `-Infinity` is returned. If a callback is provided it will be executed
+ * for each value in the collection to generate the criterion by which the value
+ * is ranked. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * _.max([4, 2, 8, 6]);
+ * // => 8
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.max(characters, function(chr) { return chr.age; });
+ * // => { 'name': 'fred', 'age': 40 };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.max(characters, 'age');
+ * // => { 'name': 'fred', 'age': 40 };
+ */
+ function max(collection, callback, thisArg) {
+ var computed = -Infinity,
+ result = computed;
+
+ // allows working with functions like `_.map` without using
+ // their `index` argument as a callback
+ if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+ callback = null;
+ }
+ if (callback == null && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (value > result) {
+ result = value;
+ }
+ }
+ } else {
+ callback = (callback == null && isString(collection))
+ ? charAtCallback
+ : lodash.createCallback(callback, thisArg, 3);
+
+ forEach(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current > computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the minimum value of a collection. If the collection is empty or
+ * falsey `Infinity` is returned. If a callback is provided it will be executed
+ * for each value in the collection to generate the criterion by which the value
+ * is ranked. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * _.min([4, 2, 8, 6]);
+ * // => 2
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.min(characters, function(chr) { return chr.age; });
+ * // => { 'name': 'barney', 'age': 36 };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.min(characters, 'age');
+ * // => { 'name': 'barney', 'age': 36 };
+ */
+ function min(collection, callback, thisArg) {
+ var computed = Infinity,
+ result = computed;
+
+ // allows working with functions like `_.map` without using
+ // their `index` argument as a callback
+ if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+ callback = null;
+ }
+ if (callback == null && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (value < result) {
+ result = value;
+ }
+ }
+ } else {
+ callback = (callback == null && isString(collection))
+ ? charAtCallback
+ : lodash.createCallback(callback, thisArg, 3);
+
+ forEach(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current < computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the value of a specified property from all elements in the collection.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {string} property The name of the property to pluck.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.pluck(characters, 'name');
+ * // => ['barney', 'fred']
+ */
+ var pluck = map;
+
+ /**
+ * Reduces a collection to a value which is the accumulated result of running
+ * each element in the collection through the callback, where each successive
+ * callback execution consumes the return value of the previous execution. If
+ * `accumulator` is not provided the first element of the collection will be
+ * used as the initial `accumulator` value. The callback is bound to `thisArg`
+ * and invoked with four arguments; (accumulator, value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias foldl, inject
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] Initial value of the accumulator.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var sum = _.reduce([1, 2, 3], function(sum, num) {
+ * return sum + num;
+ * });
+ * // => 6
+ *
+ * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+ * result[key] = num * 3;
+ * return result;
+ * }, {});
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ */
+ function reduce(collection, callback, accumulator, thisArg) {
+ if (!collection) return accumulator;
+ var noaccum = arguments.length < 3;
+ callback = lodash.createCallback(callback, thisArg, 4);
+
+ var index = -1,
+ length = collection.length;
+
+ if (typeof length == 'number') {
+ if (noaccum) {
+ accumulator = collection[++index];
+ }
+ while (++index < length) {
+ accumulator = callback(accumulator, collection[index], index, collection);
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection)
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * This method is like `_.reduce` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias foldr
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] Initial value of the accumulator.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var list = [[0, 1], [2, 3], [4, 5]];
+ * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, callback, accumulator, thisArg) {
+ var noaccum = arguments.length < 3;
+ callback = lodash.createCallback(callback, thisArg, 4);
+ forEachRight(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The opposite of `_.filter` this method returns the elements of a
+ * collection that the callback does **not** return truey for.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that failed the callback check.
+ * @example
+ *
+ * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [1, 3, 5]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.reject(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+ *
+ * // using "_.where" callback shorthand
+ * _.reject(characters, { 'age': 36 });
+ * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+ */
+ function reject(collection, callback, thisArg) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ return filter(collection, function(value, index, collection) {
+ return !callback(value, index, collection);
+ });
+ }
+
+ /**
+ * Retrieves a random element or `n` random elements from a collection.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to sample.
+ * @param {number} [n] The number of elements to sample.
+ * @param- {Object} [guard] Allows working with functions like `_.map`
+ * without using their `index` arguments as `n`.
+ * @returns {Array} Returns the random sample(s) of `collection`.
+ * @example
+ *
+ * _.sample([1, 2, 3, 4]);
+ * // => 2
+ *
+ * _.sample([1, 2, 3, 4], 2);
+ * // => [3, 1]
+ */
+ function sample(collection, n, guard) {
+ if (collection && typeof collection.length != 'number') {
+ collection = values(collection);
+ }
+ if (n == null || guard) {
+ return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+ }
+ var result = shuffle(collection);
+ result.length = nativeMin(nativeMax(0, n), result.length);
+ return result;
+ }
+
+ /**
+ * Creates an array of shuffled values, using a version of the Fisher-Yates
+ * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to shuffle.
+ * @returns {Array} Returns a new shuffled collection.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4, 5, 6]);
+ * // => [4, 1, 6, 3, 5, 2]
+ */
+ function shuffle(collection) {
+ var index = -1,
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ forEach(collection, function(value) {
+ var rand = baseRandom(0, ++index);
+ result[index] = result[rand];
+ result[rand] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Gets the size of the `collection` by returning `collection.length` for arrays
+ * and array-like objects or the number of own enumerable properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @returns {number} Returns `collection.length` or number of own enumerable properties.
+ * @example
+ *
+ * _.size([1, 2]);
+ * // => 2
+ *
+ * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => 3
+ *
+ * _.size('pebbles');
+ * // => 7
+ */
+ function size(collection) {
+ var length = collection ? collection.length : 0;
+ return typeof length == 'number' ? length : keys(collection).length;
+ }
+
+ /**
+ * Checks if the callback returns a truey value for **any** element of a
+ * collection. The function returns as soon as it finds a passing value and
+ * does not iterate over the entire collection. The callback is bound to
+ * `thisArg` and invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias any
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if any element passed the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.some(characters, 'blocked');
+ * // => true
+ *
+ * // using "_.where" callback shorthand
+ * _.some(characters, { 'age': 1 });
+ * // => false
+ */
+ function some(collection, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if ((result = callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ return !(result = callback(value, index, collection));
+ });
+ }
+ return !!result;
+ }
+
+ /**
+ * Creates an array of elements, sorted in ascending order by the results of
+ * running each element in a collection through the callback. This method
+ * performs a stable sort, that is, it will preserve the original sort order
+ * of equal elements. The callback is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an array of property names is provided for `callback` the collection
+ * will be sorted by each property value.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Array|Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of sorted elements.
+ * @example
+ *
+ * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+ * // => [3, 1, 2]
+ *
+ * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+ * // => [3, 1, 2]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'barney', 'age': 26 },
+ * { 'name': 'fred', 'age': 30 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.map(_.sortBy(characters, 'age'), _.values);
+ * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+ *
+ * // sorting by multiple properties
+ * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+ * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+ */
+ function sortBy(collection, callback, thisArg) {
+ var index = -1,
+ isArr = isArray(callback),
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ if (!isArr) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ }
+ forEach(collection, function(value, key, collection) {
+ var object = result[++index] = getObject();
+ if (isArr) {
+ object.criteria = map(callback, function(key) { return value[key]; });
+ } else {
+ (object.criteria = getArray())[0] = callback(value, key, collection);
+ }
+ object.index = index;
+ object.value = value;
+ });
+
+ length = result.length;
+ result.sort(compareAscending);
+ while (length--) {
+ var object = result[length];
+ result[length] = object.value;
+ if (!isArr) {
+ releaseArray(object.criteria);
+ }
+ releaseObject(object);
+ }
+ return result;
+ }
+
+ /**
+ * Converts the `collection` to an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to convert.
+ * @returns {Array} Returns the new converted array.
+ * @example
+ *
+ * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+ * // => [2, 3, 4]
+ */
+ function toArray(collection) {
+ if (collection && typeof collection.length == 'number') {
+ return slice(collection);
+ }
+ return values(collection);
+ }
+
+ /**
+ * Performs a deep comparison of each element in a `collection` to the given
+ * `properties` object, returning an array of all elements that have equivalent
+ * property values.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Object} props The object of property values to filter by.
+ * @returns {Array} Returns a new array of elements that have the given properties.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+ * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
+ * ];
+ *
+ * _.where(characters, { 'age': 36 });
+ * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+ *
+ * _.where(characters, { 'pets': ['dino'] });
+ * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+ */
+ var where = filter;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array with all falsey values removed. The values `false`, `null`,
+ * `0`, `""`, `undefined`, and `NaN` are all falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array excluding all values of the provided arrays using strict
+ * equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {...Array} [values] The arrays of values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+ * // => [1, 3, 4]
+ */
+ function difference(array) {
+ return baseDifference(array, baseFlatten(arguments, true, true, 1));
+ }
+
+ /**
+ * This method is like `_.find` except that it returns the index of the first
+ * element that passes the callback check, instead of the element itself.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ * ];
+ *
+ * _.findIndex(characters, function(chr) {
+ * return chr.age < 20;
+ * });
+ * // => 2
+ *
+ * // using "_.where" callback shorthand
+ * _.findIndex(characters, { 'age': 36 });
+ * // => 0
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findIndex(characters, 'blocked');
+ * // => 1
+ */
+ function findIndex(array, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length) {
+ if (callback(array[index], index, array)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.findIndex` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': true },
+ * { 'name': 'fred', 'age': 40, 'blocked': false },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': true }
+ * ];
+ *
+ * _.findLastIndex(characters, function(chr) {
+ * return chr.age > 30;
+ * });
+ * // => 1
+ *
+ * // using "_.where" callback shorthand
+ * _.findLastIndex(characters, { 'age': 36 });
+ * // => 0
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findLastIndex(characters, 'blocked');
+ * // => 2
+ */
+ function findLastIndex(array, callback, thisArg) {
+ var length = array ? array.length : 0;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (length--) {
+ if (callback(array[length], length, array)) {
+ return length;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets the first element or first `n` elements of an array. If a callback
+ * is provided elements at the beginning of the array are returned as long
+ * as the callback returns truey. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias head, take
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback] The function called
+ * per element or the number of elements to return. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the first element(s) of `array`.
+ * @example
+ *
+ * _.first([1, 2, 3]);
+ * // => 1
+ *
+ * _.first([1, 2, 3], 2);
+ * // => [1, 2]
+ *
+ * _.first([1, 2, 3], function(num) {
+ * return num < 3;
+ * });
+ * // => [1, 2]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.first(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+ *
+ * // using "_.where" callback shorthand
+ * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+ * // => ['barney', 'fred']
+ */
+ function first(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = -1;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = callback;
+ if (n == null || thisArg) {
+ return array ? array[0] : undefined;
+ }
+ }
+ return slice(array, 0, nativeMin(nativeMax(0, n), length));
+ }
+
+ /**
+ * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+ * is truey, the array will only be flattened a single level. If a callback
+ * is provided each element of the array is passed through the callback before
+ * flattening. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to flatten.
+ * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2], [3, [[4]]]]);
+ * // => [1, 2, 3, 4];
+ *
+ * _.flatten([1, [2], [3, [[4]]]], true);
+ * // => [1, 2, 3, [[4]]];
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+ * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.flatten(characters, 'pets');
+ * // => ['hoppy', 'baby puss', 'dino']
+ */
+ function flatten(array, isShallow, callback, thisArg) {
+ // juggle arguments
+ if (typeof isShallow != 'boolean' && isShallow != null) {
+ thisArg = callback;
+ callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
+ isShallow = false;
+ }
+ if (callback != null) {
+ array = map(array, callback, thisArg);
+ }
+ return baseFlatten(array, isShallow);
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`. If the array is already sorted
+ * providing `true` for `fromIndex` will run a faster binary search.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+ * to perform a binary search on a sorted array.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 1
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 4
+ *
+ * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+ * // => 2
+ */
+ function indexOf(array, value, fromIndex) {
+ if (typeof fromIndex == 'number') {
+ var length = array ? array.length : 0;
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+ } else if (fromIndex) {
+ var index = sortedIndex(array, value);
+ return array[index] === value ? index : -1;
+ }
+ return baseIndexOf(array, value, fromIndex);
+ }
+
+ /**
+ * Gets all but the last element or last `n` elements of an array. If a
+ * callback is provided elements at the end of the array are excluded from
+ * the result as long as the callback returns truey. The callback is bound
+ * to `thisArg` and invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback=1] The function called
+ * per element or the number of elements to exclude. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a slice of `array`.
+ * @example
+ *
+ * _.initial([1, 2, 3]);
+ * // => [1, 2]
+ *
+ * _.initial([1, 2, 3], 2);
+ * // => [1]
+ *
+ * _.initial([1, 2, 3], function(num) {
+ * return num > 1;
+ * });
+ * // => [1]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.initial(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }]
+ *
+ * // using "_.where" callback shorthand
+ * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+ * // => ['barney', 'fred']
+ */
+ function initial(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = length;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (index-- && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = (callback == null || thisArg) ? 1 : callback || n;
+ }
+ return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+ }
+
+ /**
+ * Creates an array of unique values present in all provided arrays using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of shared values.
+ * @example
+ *
+ * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+ * // => [1, 2]
+ */
+ function intersection() {
+ var args = [],
+ argsIndex = -1,
+ argsLength = arguments.length,
+ caches = getArray(),
+ indexOf = getIndexOf(),
+ trustIndexOf = indexOf === baseIndexOf,
+ seen = getArray();
+
+ while (++argsIndex < argsLength) {
+ var value = arguments[argsIndex];
+ if (isArray(value) || isArguments(value)) {
+ args.push(value);
+ caches.push(trustIndexOf && value.length >= largeArraySize &&
+ createCache(argsIndex ? args[argsIndex] : seen));
+ }
+ }
+ var array = args[0],
+ index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ outer:
+ while (++index < length) {
+ var cache = caches[0];
+ value = array[index];
+
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
+ argsIndex = argsLength;
+ (cache || seen).push(value);
+ while (--argsIndex) {
+ cache = caches[argsIndex];
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ }
+ }
+ while (argsLength--) {
+ cache = caches[argsLength];
+ if (cache) {
+ releaseObject(cache);
+ }
+ }
+ releaseArray(caches);
+ releaseArray(seen);
+ return result;
+ }
+
+ /**
+ * Gets the last element or last `n` elements of an array. If a callback is
+ * provided elements at the end of the array are returned as long as the
+ * callback returns truey. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback] The function called
+ * per element or the number of elements to return. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the last element(s) of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ *
+ * _.last([1, 2, 3], 2);
+ * // => [2, 3]
+ *
+ * _.last([1, 2, 3], function(num) {
+ * return num > 1;
+ * });
+ * // => [2, 3]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.pluck(_.last(characters, 'blocked'), 'name');
+ * // => ['fred', 'pebbles']
+ *
+ * // using "_.where" callback shorthand
+ * _.last(characters, { 'employer': 'na' });
+ * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+ */
+ function last(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = length;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (index-- && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = callback;
+ if (n == null || thisArg) {
+ return array ? array[length - 1] : undefined;
+ }
+ }
+ return slice(array, nativeMax(0, length - n));
+ }
+
+ /**
+ * Gets the index at which the last occurrence of `value` is found using strict
+ * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+ * as the offset from the end of the collection.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 4
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ var index = array ? array.length : 0;
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all provided values from the given array using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to modify.
+ * @param {...*} [value] The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3, 1, 2, 3];
+ * _.pull(array, 2, 3);
+ * console.log(array);
+ * // => [1, 1]
+ */
+ function pull(array) {
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = args.length,
+ length = array ? array.length : 0;
+
+ while (++argsIndex < argsLength) {
+ var index = -1,
+ value = args[argsIndex];
+ while (++index < length) {
+ if (array[index] === value) {
+ splice.call(array, index--, 1);
+ length--;
+ }
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to but not including `end`. If `start` is less than `stop` a
+ * zero-length range is created unless a negative `step` is specified.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns a new range array.
+ * @example
+ *
+ * _.range(4);
+ * // => [0, 1, 2, 3]
+ *
+ * _.range(1, 5);
+ * // => [1, 2, 3, 4]
+ *
+ * _.range(0, 20, 5);
+ * // => [0, 5, 10, 15]
+ *
+ * _.range(0, -4, -1);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.range(0);
+ * // => []
+ */
+ function range(start, end, step) {
+ start = +start || 0;
+ step = typeof step == 'number' ? step : (+step || 1);
+
+ if (end == null) {
+ end = start;
+ start = 0;
+ }
+ // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+ // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+ var index = -1,
+ length = nativeMax(0, ceil((end - start) / (step || 1))),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * Removes all elements from an array that the callback returns truey for
+ * and returns an array of removed elements. The callback is bound to `thisArg`
+ * and invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to modify.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of removed elements.
+ * @example
+ *
+ * var array = [1, 2, 3, 4, 5, 6];
+ * var evens = _.remove(array, function(num) { return num % 2 == 0; });
+ *
+ * console.log(array);
+ * // => [1, 3, 5]
+ *
+ * console.log(evens);
+ * // => [2, 4, 6]
+ */
+ function remove(array, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length) {
+ var value = array[index];
+ if (callback(value, index, array)) {
+ result.push(value);
+ splice.call(array, index--, 1);
+ length--;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.initial` this method gets all but the first element or
+ * first `n` elements of an array. If a callback function is provided elements
+ * at the beginning of the array are excluded from the result as long as the
+ * callback returns truey. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias drop, tail
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback=1] The function called
+ * per element or the number of elements to exclude. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a slice of `array`.
+ * @example
+ *
+ * _.rest([1, 2, 3]);
+ * // => [2, 3]
+ *
+ * _.rest([1, 2, 3], 2);
+ * // => [3]
+ *
+ * _.rest([1, 2, 3], function(num) {
+ * return num < 3;
+ * });
+ * // => [3]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.pluck(_.rest(characters, 'blocked'), 'name');
+ * // => ['fred', 'pebbles']
+ *
+ * // using "_.where" callback shorthand
+ * _.rest(characters, { 'employer': 'slate' });
+ * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+ */
+ function rest(array, callback, thisArg) {
+ if (typeof callback != 'number' && callback != null) {
+ var n = 0,
+ index = -1,
+ length = array ? array.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+ }
+ return slice(array, n);
+ }
+
+ /**
+ * Uses a binary search to determine the smallest index at which a value
+ * should be inserted into a given sorted array in order to maintain the sort
+ * order of the array. If a callback is provided it will be executed for
+ * `value` and each element of `array` to compute their sort ranking. The
+ * callback is bound to `thisArg` and invoked with one argument; (value).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([20, 30, 50], 40);
+ * // => 2
+ *
+ * // using "_.pluck" callback shorthand
+ * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+ * // => 2
+ *
+ * var dict = {
+ * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+ * };
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return dict.wordToNumber[word];
+ * });
+ * // => 2
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return this.wordToNumber[word];
+ * }, dict);
+ * // => 2
+ */
+ function sortedIndex(array, value, callback, thisArg) {
+ var low = 0,
+ high = array ? array.length : low;
+
+ // explicitly reference `identity` for better inlining in Firefox
+ callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
+ value = callback(value);
+
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ (callback(array[mid]) < value)
+ ? low = mid + 1
+ : high = mid;
+ }
+ return low;
+ }
+
+ /**
+ * Creates an array of unique values, in order, of the provided arrays using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of combined values.
+ * @example
+ *
+ * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+ * // => [1, 2, 3, 5, 4]
+ */
+ function union() {
+ return baseUniq(baseFlatten(arguments, true, true));
+ }
+
+ /**
+ * Creates a duplicate-value-free version of an array using strict equality
+ * for comparisons, i.e. `===`. If the array is sorted, providing
+ * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+ * each element of `array` is passed through the callback before uniqueness
+ * is computed. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias unique
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a duplicate-value-free array.
+ * @example
+ *
+ * _.uniq([1, 2, 1, 3, 1]);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 1, 2, 2, 3], true);
+ * // => [1, 2, 3]
+ *
+ * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+ * // => ['A', 'b', 'C']
+ *
+ * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+ * // => [1, 2.5, 3]
+ *
+ * // using "_.pluck" callback shorthand
+ * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ function uniq(array, isSorted, callback, thisArg) {
+ // juggle arguments
+ if (typeof isSorted != 'boolean' && isSorted != null) {
+ thisArg = callback;
+ callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+ isSorted = false;
+ }
+ if (callback != null) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ }
+ return baseUniq(array, isSorted, callback);
+ }
+
+ /**
+ * Creates an array excluding all provided values using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to filter.
+ * @param {...*} [value] The values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+ * // => [2, 3, 4]
+ */
+ function without(array) {
+ return baseDifference(array, slice(arguments, 1));
+ }
+
+ /**
+ * Creates an array that is the symmetric difference of the provided arrays.
+ * See http://en.wikipedia.org/wiki/Symmetric_difference.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of values.
+ * @example
+ *
+ * _.xor([1, 2, 3], [5, 2, 1, 4]);
+ * // => [3, 5, 4]
+ *
+ * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+ * // => [1, 4, 5]
+ */
+ function xor() {
+ var index = -1,
+ length = arguments.length;
+
+ while (++index < length) {
+ var array = arguments[index];
+ if (isArray(array) || isArguments(array)) {
+ var result = result
+ ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
+ : array;
+ }
+ }
+ return result || [];
+ }
+
+ /**
+ * Creates an array of grouped elements, the first of which contains the first
+ * elements of the given arrays, the second of which contains the second
+ * elements of the given arrays, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @alias unzip
+ * @category Arrays
+ * @param {...Array} [array] Arrays to process.
+ * @returns {Array} Returns a new array of grouped elements.
+ * @example
+ *
+ * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+ * // => [['fred', 30, true], ['barney', 40, false]]
+ */
+ function zip() {
+ var array = arguments.length > 1 ? arguments : arguments[0],
+ index = -1,
+ length = array ? max(pluck(array, 'length')) : 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = pluck(array, index);
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed from arrays of `keys` and `values`. Provide
+ * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+ * or two arrays, one of `keys` and one of corresponding `values`.
+ *
+ * @static
+ * @memberOf _
+ * @alias object
+ * @category Arrays
+ * @param {Array} keys The array of keys.
+ * @param {Array} [values=[]] The array of values.
+ * @returns {Object} Returns an object composed of the given keys and
+ * corresponding values.
+ * @example
+ *
+ * _.zipObject(['fred', 'barney'], [30, 40]);
+ * // => { 'fred': 30, 'barney': 40 }
+ */
+ function zipObject(keys, values) {
+ var index = -1,
+ length = keys ? keys.length : 0,
+ result = {};
+
+ if (!values && length && !isArray(keys[0])) {
+ values = [];
+ }
+ while (++index < length) {
+ var key = keys[index];
+ if (values) {
+ result[key] = values[index];
+ } else if (key) {
+ result[key[0]] = key[1];
+ }
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that executes `func`, with the `this` binding and
+ * arguments of the created function, only after being called `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {number} n The number of times the function must be called before
+ * `func` is executed.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var saves = ['profile', 'settings'];
+ *
+ * var done = _.after(saves.length, function() {
+ * console.log('Done saving!');
+ * });
+ *
+ * _.forEach(saves, function(type) {
+ * asyncSave({ 'type': type, 'complete': done });
+ * });
+ * // => logs 'Done saving!', after all saves have completed
+ */
+ function after(n, func) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * provided to the bound function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to bind.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var func = function(greeting) {
+ * return greeting + ' ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'fred' }, 'hi');
+ * func();
+ * // => 'hi fred'
+ */
+ function bind(func, thisArg) {
+ return arguments.length > 2
+ ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+ : createWrapper(func, 1, null, null, thisArg);
+ }
+
+ /**
+ * Binds methods of an object to the object itself, overwriting the existing
+ * method. Method names may be specified as individual arguments or as arrays
+ * of method names. If no method names are provided all the function properties
+ * of `object` will be bound.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {...string} [methodName] The object method names to
+ * bind, specified as individual method names or arrays of method names.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'onClick': function() { console.log('clicked ' + this.label); }
+ * };
+ *
+ * _.bindAll(view);
+ * jQuery('#docs').on('click', view.onClick);
+ * // => logs 'clicked docs', when the button is clicked
+ */
+ function bindAll(object) {
+ var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+ index = -1,
+ length = funcs.length;
+
+ while (++index < length) {
+ var key = funcs[index];
+ object[key] = createWrapper(object[key], 1, null, null, object);
+ }
+ return object;
+ }
+
+ /**
+ * Creates a function that, when called, invokes the method at `object[key]`
+ * and prepends any additional `bindKey` arguments to those provided to the bound
+ * function. This method differs from `_.bind` by allowing bound functions to
+ * reference methods that will be redefined or don't yet exist.
+ * See http://michaux.ca/articles/lazy-function-definition-pattern.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object the method belongs to.
+ * @param {string} key The key of the method.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'name': 'fred',
+ * 'greet': function(greeting) {
+ * return greeting + ' ' + this.name;
+ * }
+ * };
+ *
+ * var func = _.bindKey(object, 'greet', 'hi');
+ * func();
+ * // => 'hi fred'
+ *
+ * object.greet = function(greeting) {
+ * return greeting + 'ya ' + this.name + '!';
+ * };
+ *
+ * func();
+ * // => 'hiya fred!'
+ */
+ function bindKey(object, key) {
+ return arguments.length > 2
+ ? createWrapper(key, 19, slice(arguments, 2), null, object)
+ : createWrapper(key, 3, null, null, object);
+ }
+
+ /**
+ * Creates a function that is the composition of the provided functions,
+ * where each function consumes the return value of the function that follows.
+ * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * Each function is executed with the `this` binding of the composed function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {...Function} [func] Functions to compose.
+ * @returns {Function} Returns the new composed function.
+ * @example
+ *
+ * var realNameMap = {
+ * 'pebbles': 'penelope'
+ * };
+ *
+ * var format = function(name) {
+ * name = realNameMap[name.toLowerCase()] || name;
+ * return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+ * };
+ *
+ * var greet = function(formatted) {
+ * return 'Hiya ' + formatted + '!';
+ * };
+ *
+ * var welcome = _.compose(greet, format);
+ * welcome('pebbles');
+ * // => 'Hiya Penelope!'
+ */
+ function compose() {
+ var funcs = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ if (!isFunction(funcs[length])) {
+ throw new TypeError;
+ }
+ }
+ return function() {
+ var args = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ args = [funcs[length].apply(this, args)];
+ }
+ return args[0];
+ };
+ }
+
+ /**
+ * Creates a function which accepts one or more arguments of `func` that when
+ * invoked either executes `func` returning its result, if all `func` arguments
+ * have been provided, or returns a function that accepts one or more of the
+ * remaining `func` arguments, and so on. The arity of `func` can be specified
+ * if `func.length` is not sufficient.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var curried = _.curry(function(a, b, c) {
+ * console.log(a + b + c);
+ * });
+ *
+ * curried(1)(2)(3);
+ * // => 6
+ *
+ * curried(1, 2)(3);
+ * // => 6
+ *
+ * curried(1, 2, 3);
+ * // => 6
+ */
+ function curry(func, arity) {
+ arity = typeof arity == 'number' ? arity : (+arity || func.length);
+ return createWrapper(func, 4, null, null, null, arity);
+ }
+
+ /**
+ * Creates a function that will delay the execution of `func` until after
+ * `wait` milliseconds have elapsed since the last time it was invoked.
+ * Provide an options object to indicate that `func` should be invoked on
+ * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+ * to the debounced function will return the result of the last `func` call.
+ *
+ * Note: If `leading` and `trailing` options are `true` `func` will be called
+ * on the trailing edge of the timeout only if the the debounced function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to debounce.
+ * @param {number} wait The number of milliseconds to delay.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+ * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+ * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // avoid costly calculations while the window size is in flux
+ * var lazyLayout = _.debounce(calculateLayout, 150);
+ * jQuery(window).on('resize', lazyLayout);
+ *
+ * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+ * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * });
+ *
+ * // ensure `batchLog` is executed once after 1 second of debounced calls
+ * var source = new EventSource('/stream');
+ * source.addEventListener('message', _.debounce(batchLog, 250, {
+ * 'maxWait': 1000
+ * }, false);
+ */
+ function debounce(func, wait, options) {
+ var args,
+ maxTimeoutId,
+ result,
+ stamp,
+ thisArg,
+ timeoutId,
+ trailingCall,
+ lastCalled = 0,
+ maxWait = false,
+ trailing = true;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ wait = nativeMax(0, wait) || 0;
+ if (options === true) {
+ var leading = true;
+ trailing = false;
+ } else if (isObject(options)) {
+ leading = options.leading;
+ maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+ trailing = 'trailing' in options ? options.trailing : trailing;
+ }
+ var delayed = function() {
+ var remaining = wait - (now() - stamp);
+ if (remaining <= 0) {
+ if (maxTimeoutId) {
+ clearTimeout(maxTimeoutId);
+ }
+ var isCalled = trailingCall;
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (isCalled) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ }
+ } else {
+ timeoutId = setTimeout(delayed, remaining);
+ }
+ };
+
+ var maxDelayed = function() {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (trailing || (maxWait !== wait)) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ }
+ };
+
+ return function() {
+ args = arguments;
+ stamp = now();
+ thisArg = this;
+ trailingCall = trailing && (timeoutId || !leading);
+
+ if (maxWait === false) {
+ var leadingCall = leading && !timeoutId;
+ } else {
+ if (!maxTimeoutId && !leading) {
+ lastCalled = stamp;
+ }
+ var remaining = maxWait - (stamp - lastCalled),
+ isCalled = remaining <= 0;
+
+ if (isCalled) {
+ if (maxTimeoutId) {
+ maxTimeoutId = clearTimeout(maxTimeoutId);
+ }
+ lastCalled = stamp;
+ result = func.apply(thisArg, args);
+ }
+ else if (!maxTimeoutId) {
+ maxTimeoutId = setTimeout(maxDelayed, remaining);
+ }
+ }
+ if (isCalled && timeoutId) {
+ timeoutId = clearTimeout(timeoutId);
+ }
+ else if (!timeoutId && wait !== maxWait) {
+ timeoutId = setTimeout(delayed, wait);
+ }
+ if (leadingCall) {
+ isCalled = true;
+ result = func.apply(thisArg, args);
+ }
+ if (isCalled && !timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Defers executing the `func` function until the current call stack has cleared.
+ * Additional arguments will be provided to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to defer.
+ * @param {...*} [arg] Arguments to invoke the function with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.defer(function(text) { console.log(text); }, 'deferred');
+ * // logs 'deferred' after one or more milliseconds
+ */
+ function defer(func) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var args = slice(arguments, 1);
+ return setTimeout(function() { func.apply(undefined, args); }, 1);
+ }
+
+ /**
+ * Executes the `func` function after `wait` milliseconds. Additional arguments
+ * will be provided to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay execution.
+ * @param {...*} [arg] Arguments to invoke the function with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.delay(function(text) { console.log(text); }, 1000, 'later');
+ * // => logs 'later' after one second
+ */
+ function delay(func, wait) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var args = slice(arguments, 2);
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided it will be used to determine the cache key for storing the result
+ * based on the arguments provided to the memoized function. By default, the
+ * first argument provided to the memoized function is used as the cache key.
+ * The `func` is executed with the `this` binding of the memoized function.
+ * The result cache is exposed as the `cache` property on the memoized function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] A function used to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var fibonacci = _.memoize(function(n) {
+ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+ * });
+ *
+ * fibonacci(9)
+ * // => 34
+ *
+ * var data = {
+ * 'fred': { 'name': 'fred', 'age': 40 },
+ * 'pebbles': { 'name': 'pebbles', 'age': 1 }
+ * };
+ *
+ * // modifying the result cache
+ * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+ * get('pebbles');
+ * // => { 'name': 'pebbles', 'age': 1 }
+ *
+ * get.cache.pebbles.name = 'penelope';
+ * get('pebbles');
+ * // => { 'name': 'penelope', 'age': 1 }
+ */
+ function memoize(func, resolver) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var memoized = function() {
+ var cache = memoized.cache,
+ key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+
+ return hasOwnProperty.call(cache, key)
+ ? cache[key]
+ : (cache[key] = func.apply(this, arguments));
+ }
+ memoized.cache = {};
+ return memoized;
+ }
+
+ /**
+ * Creates a function that is restricted to execute `func` once. Repeat calls to
+ * the function will return the value of the first call. The `func` is executed
+ * with the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // `initialize` executes `createApplication` once
+ */
+ function once(func) {
+ var ran,
+ result;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ return function() {
+ if (ran) {
+ return result;
+ }
+ ran = true;
+ result = func.apply(this, arguments);
+
+ // clear the `func` variable so the function may be garbage collected
+ func = null;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with any additional
+ * `partial` arguments prepended to those provided to the new function. This
+ * method is similar to `_.bind` except it does **not** alter the `this` binding.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) { return greeting + ' ' + name; };
+ * var hi = _.partial(greet, 'hi');
+ * hi('fred');
+ * // => 'hi fred'
+ */
+ function partial(func) {
+ return createWrapper(func, 16, slice(arguments, 1));
+ }
+
+ /**
+ * This method is like `_.partial` except that `partial` arguments are
+ * appended to those provided to the new function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var defaultsDeep = _.partialRight(_.merge, _.defaults);
+ *
+ * var options = {
+ * 'variable': 'data',
+ * 'imports': { 'jq': $ }
+ * };
+ *
+ * defaultsDeep(options, _.templateSettings);
+ *
+ * options.variable
+ * // => 'data'
+ *
+ * options.imports
+ * // => { '_': _, 'jq': $ }
+ */
+ function partialRight(func) {
+ return createWrapper(func, 32, null, slice(arguments, 1));
+ }
+
+ /**
+ * Creates a function that, when executed, will only call the `func` function
+ * at most once per every `wait` milliseconds. Provide an options object to
+ * indicate that `func` should be invoked on the leading and/or trailing edge
+ * of the `wait` timeout. Subsequent calls to the throttled function will
+ * return the result of the last `func` call.
+ *
+ * Note: If `leading` and `trailing` options are `true` `func` will be called
+ * on the trailing edge of the timeout only if the the throttled function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to throttle.
+ * @param {number} wait The number of milliseconds to throttle executions to.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+ * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // avoid excessively updating the position while scrolling
+ * var throttled = _.throttle(updatePosition, 100);
+ * jQuery(window).on('scroll', throttled);
+ *
+ * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+ * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+ * 'trailing': false
+ * }));
+ */
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ if (options === false) {
+ leading = false;
+ } else if (isObject(options)) {
+ leading = 'leading' in options ? options.leading : leading;
+ trailing = 'trailing' in options ? options.trailing : trailing;
+ }
+ debounceOptions.leading = leading;
+ debounceOptions.maxWait = wait;
+ debounceOptions.trailing = trailing;
+
+ return debounce(func, wait, debounceOptions);
+ }
+
+ /**
+ * Creates a function that provides `value` to the wrapper function as its
+ * first argument. Additional arguments provided to the function are appended
+ * to those provided to the wrapper function. The wrapper is executed with
+ * the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {*} value The value to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var p = _.wrap(_.escape, function(func, text) {
+ * return '<p>' + func(text) + '</p>';
+ * });
+ *
+ * p('Fred, Wilma, & Pebbles');
+ * // => '<p>Fred, Wilma, & Pebbles</p>'
+ */
+ function wrap(value, wrapper) {
+ return createWrapper(wrapper, 16, [value]);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * var getter = _.constant(object);
+ * getter() === object;
+ * // => true
+ */
+ function constant(value) {
+ return function() {
+ return value;
+ };
+ }
+
+ /**
+ * Produces a callback bound to an optional `thisArg`. If `func` is a property
+ * name the created callback will return the property value for a given element.
+ * If `func` is an object the created callback will return `true` for elements
+ * that contain the equivalent object properties, otherwise it will return `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // wrap to create custom callback shorthands
+ * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+ * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+ * return !match ? func(callback, thisArg) : function(object) {
+ * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+ * };
+ * });
+ *
+ * _.filter(characters, 'age__gt38');
+ * // => [{ 'name': 'fred', 'age': 40 }]
+ */
+ function createCallback(func, thisArg, argCount) {
+ var type = typeof func;
+ if (func == null || type == 'function') {
+ return baseCreateCallback(func, thisArg, argCount);
+ }
+ // handle "_.pluck" style callback shorthands
+ if (type != 'object') {
+ return property(func);
+ }
+ var props = keys(func),
+ key = props[0],
+ a = func[key];
+
+ // handle "_.where" style callback shorthands
+ if (props.length == 1 && a === a && !isObject(a)) {
+ // fast path the common case of providing an object with a single
+ // property containing a primitive value
+ return function(object) {
+ var b = object[key];
+ return a === b && (a !== 0 || (1 / a == 1 / b));
+ };
+ }
+ return function(object) {
+ var length = props.length,
+ result = false;
+
+ while (length--) {
+ if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
+ break;
+ }
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding HTML entities.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} string The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Fred, Wilma, & Pebbles');
+ * // => 'Fred, Wilma, & Pebbles'
+ */
+ function escape(string) {
+ return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+ }
+
+ /**
+ * This method returns the first argument provided to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.identity(object) === object;
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Adds function properties of a source object to the destination object.
+ * If `object` is a function methods will be added to its prototype as well.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Function|Object} [object=lodash] object The destination object.
+ * @param {Object} source The object of functions to add.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+ * @example
+ *
+ * function capitalize(string) {
+ * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ * }
+ *
+ * _.mixin({ 'capitalize': capitalize });
+ * _.capitalize('fred');
+ * // => 'Fred'
+ *
+ * _('fred').capitalize().value();
+ * // => 'Fred'
+ *
+ * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+ * _('fred').capitalize();
+ * // => 'Fred'
+ */
+ function mixin(object, source, options) {
+ var chain = true,
+ methodNames = source && functions(source);
+
+ if (!source || (!options && !methodNames.length)) {
+ if (options == null) {
+ options = source;
+ }
+ ctor = lodashWrapper;
+ source = object;
+ object = lodash;
+ methodNames = functions(source);
+ }
+ if (options === false) {
+ chain = false;
+ } else if (isObject(options) && 'chain' in options) {
+ chain = options.chain;
+ }
+ var ctor = object,
+ isFunc = isFunction(ctor);
+
+ forEach(methodNames, function(methodName) {
+ var func = object[methodName] = source[methodName];
+ if (isFunc) {
+ ctor.prototype[methodName] = function() {
+ var chainAll = this.__chain__,
+ value = this.__wrapped__,
+ args = [value];
+
+ push.apply(args, arguments);
+ var result = func.apply(object, args);
+ if (chain || chainAll) {
+ if (value === result && isObject(result)) {
+ return this;
+ }
+ result = new ctor(result);
+ result.__chain__ = chainAll;
+ }
+ return result;
+ };
+ }
+ });
+ }
+
+ /**
+ * Reverts the '_' variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ context._ = oldDash;
+ return this;
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.noop(object) === undefined;
+ * // => true
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * Gets the number of milliseconds that have elapsed since the Unix epoch
+ * (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var stamp = _.now();
+ * _.defer(function() { console.log(_.now() - stamp); });
+ * // => logs the number of milliseconds it took for the deferred function to be called
+ */
+ var now = isNative(now = Date.now) && now || function() {
+ return new Date().getTime();
+ };
+
+ /**
+ * Converts the given value into an integer of the specified radix.
+ * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
+ * `value` is a hexadecimal, in which case a `radix` of `16` is used.
+ *
+ * Note: This method avoids differences in native ES3 and ES5 `parseInt`
+ * implementations. See http://es5.github.io/#E.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} value The value to parse.
+ * @param {number} [radix] The radix used to interpret the value to parse.
+ * @returns {number} Returns the new integer value.
+ * @example
+ *
+ * _.parseInt('08');
+ * // => 8
+ */
+ var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
+ // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
+ return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
+ };
+
+ /**
+ * Creates a "_.pluck" style function, which returns the `key` value of a
+ * given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} key The name of the property to retrieve.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'barney', 'age': 36 }
+ * ];
+ *
+ * var getName = _.property('name');
+ *
+ * _.map(characters, getName);
+ * // => ['barney', 'fred']
+ *
+ * _.sortBy(characters, getName);
+ * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }]
+ */
+ function property(key) {
+ return function(object) {
+ return object[key];
+ };
+ }
+
+ /**
+ * Produces a random number between `min` and `max` (inclusive). If only one
+ * argument is provided a number between `0` and the given number will be
+ * returned. If `floating` is truey or either `min` or `max` are floats a
+ * floating-point number will be returned instead of an integer.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {number} [min=0] The minimum possible value.
+ * @param {number} [max=1] The maximum possible value.
+ * @param {boolean} [floating=false] Specify returning a floating-point number.
+ * @returns {number} Returns a random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => an integer between 0 and 5
+ *
+ * _.random(5);
+ * // => also an integer between 0 and 5
+ *
+ * _.random(5, true);
+ * // => a floating-point number between 0 and 5
+ *
+ * _.random(1.2, 5.2);
+ * // => a floating-point number between 1.2 and 5.2
+ */
+ function random(min, max, floating) {
+ var noMin = min == null,
+ noMax = max == null;
+
+ if (floating == null) {
+ if (typeof min == 'boolean' && noMax) {
+ floating = min;
+ min = 1;
+ }
+ else if (!noMax && typeof max == 'boolean') {
+ floating = max;
+ noMax = true;
+ }
+ }
+ if (noMin && noMax) {
+ max = 1;
+ }
+ min = +min || 0;
+ if (noMax) {
+ max = min;
+ min = 0;
+ } else {
+ max = +max || 0;
+ }
+ if (floating || min % 1 || max % 1) {
+ var rand = nativeRandom();
+ return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
+ }
+ return baseRandom(min, max);
+ }
+
+ /**
+ * Resolves the value of property `key` on `object`. If `key` is a function
+ * it will be invoked with the `this` binding of `object` and its result returned,
+ * else the property value is returned. If `object` is falsey then `undefined`
+ * is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to resolve.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = {
+ * 'cheese': 'crumpets',
+ * 'stuff': function() {
+ * return 'nonsense';
+ * }
+ * };
+ *
+ * _.result(object, 'cheese');
+ * // => 'crumpets'
+ *
+ * _.result(object, 'stuff');
+ * // => 'nonsense'
+ */
+ function result(object, key) {
+ if (object) {
+ var value = object[key];
+ return isFunction(value) ? object[key]() : value;
+ }
+ }
+
+ /**
+ * A micro-templating method that handles arbitrary delimiters, preserves
+ * whitespace, and correctly escapes quotes within interpolated code.
+ *
+ * Note: In the development build, `_.template` utilizes sourceURLs for easier
+ * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ *
+ * For more information on precompiling templates see:
+ * http://lodash.com/custom-builds
+ *
+ * For more information on Chrome extension sandboxes see:
+ * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} text The template text.
+ * @param {Object} data The data object used to populate the text.
+ * @param {Object} [options] The options object.
+ * @param {RegExp} [options.escape] The "escape" delimiter.
+ * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+ * @param {Object} [options.imports] An object to import into the template as local variables.
+ * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+ * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+ * @param {string} [variable] The data object variable name.
+ * @returns {Function|string} Returns a compiled function when no `data` object
+ * is given, else it returns the interpolated text.
+ * @example
+ *
+ * // using the "interpolate" delimiter to create a compiled template
+ * var compiled = _.template('hello <%= name %>');
+ * compiled({ 'name': 'fred' });
+ * // => 'hello fred'
+ *
+ * // using the "escape" delimiter to escape HTML in data property values
+ * _.template('<b><%- value %></b>', { 'value': '<script>' });
+ * // => '<b><script></b>'
+ *
+ * // using the "evaluate" delimiter to generate HTML
+ * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
+ * _.template(list, { 'people': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
+ * _.template('hello ${ name }', { 'name': 'pebbles' });
+ * // => 'hello pebbles'
+ *
+ * // using the internal `print` function in "evaluate" delimiters
+ * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
+ * // => 'hello barney!'
+ *
+ * // using a custom template delimiters
+ * _.templateSettings = {
+ * 'interpolate': /{{([\s\S]+?)}}/g
+ * };
+ *
+ * _.template('hello {{ name }}!', { 'name': 'mustache' });
+ * // => 'hello mustache!'
+ *
+ * // using the `imports` option to import jQuery
+ * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
+ * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // using the `sourceURL` option to specify a custom sourceURL for the template
+ * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
+ * compiled(data);
+ * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+ *
+ * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+ * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
+ * compiled.source;
+ * // => function(data) {
+ * var __t, __p = '', __e = _.escape;
+ * __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
+ * return __p;
+ * }
+ *
+ * // using the `source` property to inline compiled templates for meaningful
+ * // line numbers in error messages and a stack trace
+ * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+ * var JST = {\
+ * "main": ' + _.template(mainText).source + '\
+ * };\
+ * ');
+ */
+ function template(text, data, options) {
+ // based on John Resig's `tmpl` implementation
+ // http://ejohn.org/blog/javascript-micro-templating/
+ // and Laura Doktorova's doT.js
+ // https://github.com/olado/doT
+ var settings = lodash.templateSettings;
+ text = String(text || '');
+
+ // avoid missing dependencies when `iteratorTemplate` is not defined
+ options = defaults({}, options, settings);
+
+ var imports = defaults({}, options.imports, settings.imports),
+ importsKeys = keys(imports),
+ importsValues = values(imports);
+
+ var isEvaluating,
+ index = 0,
+ interpolate = options.interpolate || reNoMatch,
+ source = "__p += '";
+
+ // compile the regexp to match each delimiter
+ var reDelimiters = RegExp(
+ (options.escape || reNoMatch).source + '|' +
+ interpolate.source + '|' +
+ (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+ (options.evaluate || reNoMatch).source + '|$'
+ , 'g');
+
+ text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+ interpolateValue || (interpolateValue = esTemplateValue);
+
+ // escape characters that cannot be included in string literals
+ source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+ // replace delimiters with snippets
+ if (escapeValue) {
+ source += "' +\n__e(" + escapeValue + ") +\n'";
+ }
+ if (evaluateValue) {
+ isEvaluating = true;
+ source += "';\n" + evaluateValue + ";\n__p += '";
+ }
+ if (interpolateValue) {
+ source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+ }
+ index = offset + match.length;
+
+ // the JS engine embedded in Adobe products requires returning the `match`
+ // string in order to produce the correct `offset` value
+ return match;
+ });
+
+ source += "';\n";
+
+ // if `variable` is not specified, wrap a with-statement around the generated
+ // code to add the data object to the top of the scope chain
+ var variable = options.variable,
+ hasVariable = variable;
+
+ if (!hasVariable) {
+ variable = 'obj';
+ source = 'with (' + variable + ') {\n' + source + '\n}\n';
+ }
+ // cleanup code by stripping empty strings
+ source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+ .replace(reEmptyStringMiddle, '$1')
+ .replace(reEmptyStringTrailing, '$1;');
+
+ // frame code as the function body
+ source = 'function(' + variable + ') {\n' +
+ (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
+ "var __t, __p = '', __e = _.escape" +
+ (isEvaluating
+ ? ', __j = Array.prototype.join;\n' +
+ "function print() { __p += __j.call(arguments, '') }\n"
+ : ';\n'
+ ) +
+ source +
+ 'return __p\n}';
+
+ // Use a sourceURL for easier debugging.
+ // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';
+
+ try {
+ var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
+ } catch(e) {
+ e.source = source;
+ throw e;
+ }
+ if (data) {
+ return result(data);
+ }
+ // provide the compiled function's source by its `toString` method, in
+ // supported environments, or the `source` property as a convenience for
+ // inlining compiled templates during the build process
+ result.source = source;
+ return result;
+ }
+
+ /**
+ * Executes the callback `n` times, returning an array of the results
+ * of each callback execution. The callback is bound to `thisArg` and invoked
+ * with one argument; (index).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {number} n The number of times to execute the callback.
+ * @param {Function} callback The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns an array of the results of each `callback` execution.
+ * @example
+ *
+ * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
+ * // => [3, 6, 4]
+ *
+ * _.times(3, function(n) { mage.castSpell(n); });
+ * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
+ *
+ * _.times(3, function(n) { this.cast(n); }, mage);
+ * // => also calls `mage.castSpell(n)` three times
+ */
+ function times(n, callback, thisArg) {
+ n = (n = +n) > -1 ? n : 0;
+ var index = -1,
+ result = Array(n);
+
+ callback = baseCreateCallback(callback, thisArg, 1);
+ while (++index < n) {
+ result[index] = callback(index);
+ }
+ return result;
+ }
+
+ /**
+ * The inverse of `_.escape` this method converts the HTML entities
+ * `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding characters.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} string The string to unescape.
+ * @returns {string} Returns the unescaped string.
+ * @example
+ *
+ * _.unescape('Fred, Barney & Pebbles');
+ * // => 'Fred, Barney & Pebbles'
+ */
+ function unescape(string) {
+ return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+ }
+
+ /**
+ * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} [prefix] The value to prefix the ID with.
+ * @returns {string} Returns the unique ID.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ *
+ * _.uniqueId();
+ * // => '105'
+ */
+ function uniqueId(prefix) {
+ var id = ++idCounter;
+ return String(prefix == null ? '' : prefix) + id;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object that wraps the given value with explicit
+ * method chaining enabled.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {*} value The value to wrap.
+ * @returns {Object} Returns the wrapper object.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'pebbles', 'age': 1 }
+ * ];
+ *
+ * var youngest = _.chain(characters)
+ * .sortBy('age')
+ * .map(function(chr) { return chr.name + ' is ' + chr.age; })
+ * .first()
+ * .value();
+ * // => 'pebbles is 1'
+ */
+ function chain(value) {
+ value = new lodashWrapper(value);
+ value.__chain__ = true;
+ return value;
+ }
+
+ /**
+ * Invokes `interceptor` with the `value` as the first argument and then
+ * returns `value`. The purpose of this method is to "tap into" a method
+ * chain in order to perform operations on intermediate results within
+ * the chain.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * _([1, 2, 3, 4])
+ * .tap(function(array) { array.pop(); })
+ * .reverse()
+ * .value();
+ * // => [3, 2, 1]
+ */
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
+ }
+
+ /**
+ * Enables explicit method chaining on the wrapper object.
+ *
+ * @name chain
+ * @memberOf _
+ * @category Chaining
+ * @returns {*} Returns the wrapper object.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // without explicit chaining
+ * _(characters).first();
+ * // => { 'name': 'barney', 'age': 36 }
+ *
+ * // with explicit chaining
+ * _(characters).chain()
+ * .first()
+ * .pick('age')
+ * .value();
+ * // => { 'age': 36 }
+ */
+ function wrapperChain() {
+ this.__chain__ = true;
+ return this;
+ }
+
+ /**
+ * Produces the `toString` result of the wrapped value.
+ *
+ * @name toString
+ * @memberOf _
+ * @category Chaining
+ * @returns {string} Returns the string result.
+ * @example
+ *
+ * _([1, 2, 3]).toString();
+ * // => '1,2,3'
+ */
+ function wrapperToString() {
+ return String(this.__wrapped__);
+ }
+
+ /**
+ * Extracts the wrapped value.
+ *
+ * @name valueOf
+ * @memberOf _
+ * @alias value
+ * @category Chaining
+ * @returns {*} Returns the wrapped value.
+ * @example
+ *
+ * _([1, 2, 3]).valueOf();
+ * // => [1, 2, 3]
+ */
+ function wrapperValueOf() {
+ return this.__wrapped__;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // add functions that return wrapped values when chaining
+ lodash.after = after;
+ lodash.assign = assign;
+ lodash.at = at;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.bindKey = bindKey;
+ lodash.chain = chain;
+ lodash.compact = compact;
+ lodash.compose = compose;
+ lodash.constant = constant;
+ lodash.countBy = countBy;
+ lodash.create = create;
+ lodash.createCallback = createCallback;
+ lodash.curry = curry;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.filter = filter;
+ lodash.flatten = flatten;
+ lodash.forEach = forEach;
+ lodash.forEachRight = forEachRight;
+ lodash.forIn = forIn;
+ lodash.forInRight = forInRight;
+ lodash.forOwn = forOwn;
+ lodash.forOwnRight = forOwnRight;
+ lodash.functions = functions;
+ lodash.groupBy = groupBy;
+ lodash.indexBy = indexBy;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.invert = invert;
+ lodash.invoke = invoke;
+ lodash.keys = keys;
+ lodash.map = map;
+ lodash.mapValues = mapValues;
+ lodash.max = max;
+ lodash.memoize = memoize;
+ lodash.merge = merge;
+ lodash.min = min;
+ lodash.omit = omit;
+ lodash.once = once;
+ lodash.pairs = pairs;
+ lodash.partial = partial;
+ lodash.partialRight = partialRight;
+ lodash.pick = pick;
+ lodash.pluck = pluck;
+ lodash.property = property;
+ lodash.pull = pull;
+ lodash.range = range;
+ lodash.reject = reject;
+ lodash.remove = remove;
+ lodash.rest = rest;
+ lodash.shuffle = shuffle;
+ lodash.sortBy = sortBy;
+ lodash.tap = tap;
+ lodash.throttle = throttle;
+ lodash.times = times;
+ lodash.toArray = toArray;
+ lodash.transform = transform;
+ lodash.union = union;
+ lodash.uniq = uniq;
+ lodash.values = values;
+ lodash.where = where;
+ lodash.without = without;
+ lodash.wrap = wrap;
+ lodash.xor = xor;
+ lodash.zip = zip;
+ lodash.zipObject = zipObject;
+
+ // add aliases
+ lodash.collect = map;
+ lodash.drop = rest;
+ lodash.each = forEach;
+ lodash.eachRight = forEachRight;
+ lodash.extend = assign;
+ lodash.methods = functions;
+ lodash.object = zipObject;
+ lodash.select = filter;
+ lodash.tail = rest;
+ lodash.unique = uniq;
+ lodash.unzip = zip;
+
+ // add functions to `lodash.prototype`
+ mixin(lodash);
+
+ /*--------------------------------------------------------------------------*/
+
+ // add functions that return unwrapped values when chaining
+ lodash.clone = clone;
+ lodash.cloneDeep = cloneDeep;
+ lodash.contains = contains;
+ lodash.escape = escape;
+ lodash.every = every;
+ lodash.find = find;
+ lodash.findIndex = findIndex;
+ lodash.findKey = findKey;
+ lodash.findLast = findLast;
+ lodash.findLastIndex = findLastIndex;
+ lodash.findLastKey = findLastKey;
+ lodash.has = has;
+ lodash.identity = identity;
+ lodash.indexOf = indexOf;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isBoolean = isBoolean;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isNaN = isNaN;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isPlainObject = isPlainObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isString = isString;
+ lodash.isUndefined = isUndefined;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.mixin = mixin;
+ lodash.noConflict = noConflict;
+ lodash.noop = noop;
+ lodash.now = now;
+ lodash.parseInt = parseInt;
+ lodash.random = random;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.result = result;
+ lodash.runInContext = runInContext;
+ lodash.size = size;
+ lodash.some = some;
+ lodash.sortedIndex = sortedIndex;
+ lodash.template = template;
+ lodash.unescape = unescape;
+ lodash.uniqueId = uniqueId;
+
+ // add aliases
+ lodash.all = every;
+ lodash.any = some;
+ lodash.detect = find;
+ lodash.findWhere = find;
+ lodash.foldl = reduce;
+ lodash.foldr = reduceRight;
+ lodash.include = contains;
+ lodash.inject = reduce;
+
+ mixin(function() {
+ var source = {}
+ forOwn(lodash, function(func, methodName) {
+ if (!lodash.prototype[methodName]) {
+ source[methodName] = func;
+ }
+ });
+ return source;
+ }(), false);
+
+ /*--------------------------------------------------------------------------*/
+
+ // add functions capable of returning wrapped and unwrapped values when chaining
+ lodash.first = first;
+ lodash.last = last;
+ lodash.sample = sample;
+
+ // add aliases
+ lodash.take = first;
+ lodash.head = first;
+
+ forOwn(lodash, function(func, methodName) {
+ var callbackable = methodName !== 'sample';
+ if (!lodash.prototype[methodName]) {
+ lodash.prototype[methodName]= function(n, guard) {
+ var chainAll = this.__chain__,
+ result = func(this.__wrapped__, n, guard);
+
+ return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
+ ? result
+ : new lodashWrapper(result, chainAll);
+ };
+ }
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type string
+ */
+ lodash.VERSION = '2.4.1';
+
+ // add "Chaining" functions to the wrapper
+ lodash.prototype.chain = wrapperChain;
+ lodash.prototype.toString = wrapperToString;
+ lodash.prototype.value = wrapperValueOf;
+ lodash.prototype.valueOf = wrapperValueOf;
+
+ // add `Array` functions that return unwrapped values
+ forEach(['join', 'pop', 'shift'], function(methodName) {
+ var func = arrayRef[methodName];
+ lodash.prototype[methodName] = function() {
+ var chainAll = this.__chain__,
+ result = func.apply(this.__wrapped__, arguments);
+
+ return chainAll
+ ? new lodashWrapper(result, chainAll)
+ : result;
+ };
+ });
+
+ // add `Array` functions that return the existing wrapped value
+ forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
+ var func = arrayRef[methodName];
+ lodash.prototype[methodName] = function() {
+ func.apply(this.__wrapped__, arguments);
+ return this;
+ };
+ });
+
+ // add `Array` functions that return new wrapped values
+ forEach(['concat', 'slice', 'splice'], function(methodName) {
+ var func = arrayRef[methodName];
+ lodash.prototype[methodName] = function() {
+ return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
+ };
+ });
+
+ return lodash;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose Lo-Dash
+ var _ = runInContext();
+
+ // some AMD build optimizers like r.js check for condition patterns like the following:
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+ // Expose Lo-Dash to the global object even when an AMD loader is present in
+ // case Lo-Dash is loaded with a RequireJS shim config.
+ // See http://requirejs.org/docs/api.html#config-shim
+ root._ = _;
+
+ // define as an anonymous module so, through path mapping, it can be
+ // referenced as the "underscore" module
+ define(function() {
+ return _;
+ });
+ }
+ // check for `exports` after `define` in case a build optimizer adds an `exports` object
+ else if (freeExports && freeModule) {
+ // in Node.js or RingoJS
+ if (moduleExports) {
+ (freeModule.exports = _)._ = _;
+ }
+ // in Narwhal or Rhino -require
+ else {
+ freeExports._ = _;
+ }
+ }
+ else {
+ // in a browser or Rhino
+ root._ = _;
+ }
+}.call(this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/dev/js/lib/domReady.js b/dev/js/lib/domReady.js
new file mode 100644
index 0000000..2b54122
--- /dev/null
+++ b/dev/js/lib/domReady.js
@@ -0,0 +1,129 @@
+/**
+ * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/requirejs/domReady for details
+ */
+/*jslint */
+/*global require: false, define: false, requirejs: false,
+ window: false, clearInterval: false, document: false,
+ self: false, setInterval: false */
+
+
+define(function () {
+ 'use strict';
+
+ var isTop, testDiv, scrollIntervalId,
+ isBrowser = typeof window !== "undefined" && window.document,
+ isPageLoaded = !isBrowser,
+ doc = isBrowser ? document : null,
+ readyCalls = [];
+
+ function runCallbacks(callbacks) {
+ var i;
+ for (i = 0; i < callbacks.length; i += 1) {
+ callbacks[i](doc);
+ }
+ }
+
+ function callReady() {
+ var callbacks = readyCalls;
+
+ if (isPageLoaded) {
+ //Call the DOM ready callbacks
+ if (callbacks.length) {
+ readyCalls = [];
+ runCallbacks(callbacks);
+ }
+ }
+ }
+
+ /**
+ * Sets the page as loaded.
+ */
+ function pageLoaded() {
+ if (!isPageLoaded) {
+ isPageLoaded = true;
+ if (scrollIntervalId) {
+ clearInterval(scrollIntervalId);
+ }
+
+ callReady();
+ }
+ }
+
+ if (isBrowser) {
+ if (document.addEventListener) {
+ //Standards. Hooray! Assumption here that if standards based,
+ //it knows about DOMContentLoaded.
+ document.addEventListener("DOMContentLoaded", pageLoaded, false);
+ window.addEventListener("load", pageLoaded, false);
+ } else if (window.attachEvent) {
+ window.attachEvent("onload", pageLoaded);
+
+ testDiv = document.createElement('div');
+ try {
+ isTop = window.frameElement === null;
+ } catch (e) {}
+
+ //DOMContentLoaded approximation that uses a doScroll, as found by
+ //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/,
+ //but modified by other contributors, including jdalton
+ if (testDiv.doScroll && isTop && window.external) {
+ scrollIntervalId = setInterval(function () {
+ try {
+ testDiv.doScroll();
+ pageLoaded();
+ } catch (e) {}
+ }, 30);
+ }
+ }
+
+ //Check if document already complete, and if so, just trigger page load
+ //listeners. Latest webkit browsers also use "interactive", and
+ //will fire the onDOMContentLoaded before "interactive" but not after
+ //entering "interactive" or "complete". More details:
+ //http://dev.w3.org/html5/spec/the-end.html#the-end
+ //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded
+ //Hmm, this is more complicated on further use, see "firing too early"
+ //bug: https://github.com/requirejs/domReady/issues/1
+ //so removing the || document.readyState === "interactive" test.
+ //There is still a window.onload binding that should get fired if
+ //DOMContentLoaded is missed.
+ if (document.readyState === "complete") {
+ pageLoaded();
+ }
+ }
+
+ /** START OF PUBLIC API **/
+
+ /**
+ * Registers a callback for DOM ready. If DOM is already ready, the
+ * callback is called immediately.
+ * @param {Function} callback
+ */
+ function domReady(callback) {
+ if (isPageLoaded) {
+ callback(doc);
+ } else {
+ readyCalls.push(callback);
+ }
+ return domReady;
+ }
+
+ domReady.version = '2.0.1';
+
+ /**
+ * Loader Plugin API method
+ */
+ domReady.load = function (name, req, onLoad, config) {
+ if (config.isBuild) {
+ onLoad(null);
+ } else {
+ domReady(onLoad);
+ }
+ };
+
+ /** END OF PUBLIC API **/
+
+ return domReady;
+});
diff --git a/dev/js/lib/require.js b/dev/js/lib/require.js
new file mode 100644
index 0000000..babfa9a
--- /dev/null
+++ b/dev/js/lib/require.js
@@ -0,0 +1,2083 @@
+/** vim: et:ts=4:sw=4:sts=4
+ * @license RequireJS 2.1.17 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/requirejs for details
+ */
+//Not using strict: uneven strict support in browsers, #392, and causes
+//problems with requirejs.exec()/transpiler plugins that may not be strict.
+/*jslint regexp: true, nomen: true, sloppy: true */
+/*global window, navigator, document, importScripts, setTimeout, opera */
+
+var requirejs, require, define;
+(function (global) {
+ var req, s, head, baseElement, dataMain, src,
+ interactiveScript, currentlyAddingScript, mainScript, subPath,
+ version = '2.1.17',
+ commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
+ cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
+ jsSuffixRegExp = /\.js$/,
+ currDirRegExp = /^\.\//,
+ op = Object.prototype,
+ ostring = op.toString,
+ hasOwn = op.hasOwnProperty,
+ ap = Array.prototype,
+ apsp = ap.splice,
+ isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document),
+ isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
+ //PS3 indicates loaded and complete, but need to wait for complete
+ //specifically. Sequence is 'loading', 'loaded', execution,
+ // then 'complete'. The UA check is unfortunate, but not sure how
+ //to feature test w/o causing perf issues.
+ readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
+ /^complete$/ : /^(complete|loaded)$/,
+ defContextName = '_',
+ //Oh the tragedy, detecting opera. See the usage of isOpera for reason.
+ isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
+ contexts = {},
+ cfg = {},
+ globalDefQueue = [],
+ useInteractive = false;
+
+ function isFunction(it) {
+ return ostring.call(it) === '[object Function]';
+ }
+
+ function isArray(it) {
+ return ostring.call(it) === '[object Array]';
+ }
+
+ /**
+ * Helper function for iterating over an array. If the func returns
+ * a true value, it will break out of the loop.
+ */
+ function each(ary, func) {
+ if (ary) {
+ var i;
+ for (i = 0; i < ary.length; i += 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function for iterating over an array backwards. If the func
+ * returns a true value, it will break out of the loop.
+ */
+ function eachReverse(ary, func) {
+ if (ary) {
+ var i;
+ for (i = ary.length - 1; i > -1; i -= 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ function hasProp(obj, prop) {
+ return hasOwn.call(obj, prop);
+ }
+
+ function getOwn(obj, prop) {
+ return hasProp(obj, prop) && obj[prop];
+ }
+
+ /**
+ * Cycles over properties in an object and calls a function for each
+ * property value. If the function returns a truthy value, then the
+ * iteration is stopped.
+ */
+ function eachProp(obj, func) {
+ var prop;
+ for (prop in obj) {
+ if (hasProp(obj, prop)) {
+ if (func(obj[prop], prop)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple function to mix in properties from source into target,
+ * but only if target does not already have a property of the same name.
+ */
+ function mixin(target, source, force, deepStringMixin) {
+ if (source) {
+ eachProp(source, function (value, prop) {
+ if (force || !hasProp(target, prop)) {
+ if (deepStringMixin && typeof value === 'object' && value &&
+ !isArray(value) && !isFunction(value) &&
+ !(value instanceof RegExp)) {
+
+ if (!target[prop]) {
+ target[prop] = {};
+ }
+ mixin(target[prop], value, force, deepStringMixin);
+ } else {
+ target[prop] = value;
+ }
+ }
+ });
+ }
+ return target;
+ }
+
+ //Similar to Function.prototype.bind, but the 'this' object is specified
+ //first, since it is easier to read/figure out what 'this' will be.
+ function bind(obj, fn) {
+ return function () {
+ return fn.apply(obj, arguments);
+ };
+ }
+
+ function scripts() {
+ return document.getElementsByTagName('script');
+ }
+
+ function defaultOnError(err) {
+ throw err;
+ }
+
+ //Allow getting a global that is expressed in
+ //dot notation, like 'a.b.c'.
+ function getGlobal(value) {
+ if (!value) {
+ return value;
+ }
+ var g = global;
+ each(value.split('.'), function (part) {
+ g = g[part];
+ });
+ return g;
+ }
+
+ /**
+ * Constructs an error with a pointer to an URL with more information.
+ * @param {String} id the error ID that maps to an ID on a web page.
+ * @param {String} message human readable error.
+ * @param {Error} [err] the original error, if there is one.
+ *
+ * @returns {Error}
+ */
+ function makeError(id, msg, err, requireModules) {
+ var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
+ e.requireType = id;
+ e.requireModules = requireModules;
+ if (err) {
+ e.originalError = err;
+ }
+ return e;
+ }
+
+ if (typeof define !== 'undefined') {
+ //If a define is already in play via another AMD loader,
+ //do not overwrite.
+ return;
+ }
+
+ if (typeof requirejs !== 'undefined') {
+ if (isFunction(requirejs)) {
+ //Do not overwrite an existing requirejs instance.
+ return;
+ }
+ cfg = requirejs;
+ requirejs = undefined;
+ }
+
+ //Allow for a require config object
+ if (typeof require !== 'undefined' && !isFunction(require)) {
+ //assume it is a config object.
+ cfg = require;
+ require = undefined;
+ }
+
+ function newContext(contextName) {
+ var inCheckLoaded, Module, context, handlers,
+ checkLoadedTimeoutId,
+ config = {
+ //Defaults. Do not set a default for map
+ //config to speed up normalize(), which
+ //will run faster if there is no default.
+ waitSeconds: 7,
+ baseUrl: './',
+ paths: {},
+ bundles: {},
+ pkgs: {},
+ shim: {},
+ config: {}
+ },
+ registry = {},
+ //registry of just enabled modules, to speed
+ //cycle breaking code when lots of modules
+ //are registered, but not activated.
+ enabledRegistry = {},
+ undefEvents = {},
+ defQueue = [],
+ defined = {},
+ urlFetched = {},
+ bundlesMap = {},
+ requireCounter = 1,
+ unnormalizedCounter = 1;
+
+ /**
+ * Trims the . and .. from an array of path segments.
+ * It will keep a leading path segment if a .. will become
+ * the first path segment, to help with module name lookups,
+ * which act like paths, but can be remapped. But the end result,
+ * all paths that use this function should look normalized.
+ * NOTE: this method MODIFIES the input array.
+ * @param {Array} ary the array of path segments.
+ */
+ function trimDots(ary) {
+ var i, part;
+ for (i = 0; i < ary.length; i++) {
+ part = ary[i];
+ if (part === '.') {
+ ary.splice(i, 1);
+ i -= 1;
+ } else if (part === '..') {
+ // If at the start, or previous value is still ..,
+ // keep them so that when converted to a path it may
+ // still work when converted to a path, even though
+ // as an ID it is less than ideal. In larger point
+ // releases, may be better to just kick out an error.
+ if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
+ continue;
+ } else if (i > 0) {
+ ary.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ }
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @param {Boolean} applyMap apply the map config to the value. Should
+ * only be done if this normalization is for a dependency ID.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName, applyMap) {
+ var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
+ foundMap, foundI, foundStarMap, starI, normalizedBaseParts,
+ baseParts = (baseName && baseName.split('/')),
+ map = config.map,
+ starMap = map && map['*'];
+
+ //Adjust any relative paths.
+ if (name) {
+ name = name.split('/');
+ lastIndex = name.length - 1;
+
+ // If wanting node ID compatibility, strip .js from end
+ // of IDs. Have to do this here, and not in nameToUrl
+ // because node allows either .js or non .js to map
+ // to same file.
+ if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+ name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+ }
+
+ // Starts with a '.' so need the baseName
+ if (name[0].charAt(0) === '.' && baseParts) {
+ //Convert baseName to array, and lop off the last part,
+ //so that . matches that 'directory' and not name of the baseName's
+ //module. For instance, baseName of 'one/two/three', maps to
+ //'one/two/three.js', but we want the directory, 'one/two' for
+ //this normalization.
+ normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+ name = normalizedBaseParts.concat(name);
+ }
+
+ trimDots(name);
+ name = name.join('/');
+ }
+
+ //Apply map config if available.
+ if (applyMap && map && (baseParts || starMap)) {
+ nameParts = name.split('/');
+
+ outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
+ nameSegment = nameParts.slice(0, i).join('/');
+
+ if (baseParts) {
+ //Find the longest baseName segment match in the config.
+ //So, do joins on the biggest to smallest lengths of baseParts.
+ for (j = baseParts.length; j > 0; j -= 1) {
+ mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
+
+ //baseName segment has config, find if it has one for
+ //this name.
+ if (mapValue) {
+ mapValue = getOwn(mapValue, nameSegment);
+ if (mapValue) {
+ //Match, update name to the new value.
+ foundMap = mapValue;
+ foundI = i;
+ break outerLoop;
+ }
+ }
+ }
+ }
+
+ //Check for a star map match, but just hold on to it,
+ //if there is a shorter segment match later in a matching
+ //config, then favor over this star map.
+ if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
+ foundStarMap = getOwn(starMap, nameSegment);
+ starI = i;
+ }
+ }
+
+ if (!foundMap && foundStarMap) {
+ foundMap = foundStarMap;
+ foundI = starI;
+ }
+
+ if (foundMap) {
+ nameParts.splice(0, foundI, foundMap);
+ name = nameParts.join('/');
+ }
+ }
+
+ // If the name points to a package's name, use
+ // the package main instead.
+ pkgMain = getOwn(config.pkgs, name);
+
+ return pkgMain ? pkgMain : name;
+ }
+
+ function removeScript(name) {
+ if (isBrowser) {
+ each(scripts(), function (scriptNode) {
+ if (scriptNode.getAttribute('data-requiremodule') === name &&
+ scriptNode.getAttribute('data-requirecontext') === context.contextName) {
+ scriptNode.parentNode.removeChild(scriptNode);
+ return true;
+ }
+ });
+ }
+ }
+
+ function hasPathFallback(id) {
+ var pathConfig = getOwn(config.paths, id);
+ if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
+ //Pop off the first array value, since it failed, and
+ //retry
+ pathConfig.shift();
+ context.require.undef(id);
+
+ //Custom require that does not do map translation, since
+ //ID is "absolute", already mapped/resolved.
+ context.makeRequire(null, {
+ skipMap: true
+ })([id]);
+
+ return true;
+ }
+ }
+
+ //Turns a plugin!resource to [plugin, resource]
+ //with the plugin being undefined if the name
+ //did not have a plugin prefix.
+ function splitPrefix(name) {
+ var prefix,
+ index = name ? name.indexOf('!') : -1;
+ if (index > -1) {
+ prefix = name.substring(0, index);
+ name = name.substring(index + 1, name.length);
+ }
+ return [prefix, name];
+ }
+
+ /**
+ * Creates a module mapping that includes plugin prefix, module
+ * name, and path. If parentModuleMap is provided it will
+ * also normalize the name via require.normalize()
+ *
+ * @param {String} name the module name
+ * @param {String} [parentModuleMap] parent module map
+ * for the module name, used to resolve relative names.
+ * @param {Boolean} isNormalized: is the ID already normalized.
+ * This is true if this call is done for a define() module ID.
+ * @param {Boolean} applyMap: apply the map config to the ID.
+ * Should only be true if this map is for a dependency.
+ *
+ * @returns {Object}
+ */
+ function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
+ var url, pluginModule, suffix, nameParts,
+ prefix = null,
+ parentName = parentModuleMap ? parentModuleMap.name : null,
+ originalName = name,
+ isDefine = true,
+ normalizedName = '';
+
+ //If no name, then it means it is a require call, generate an
+ //internal name.
+ if (!name) {
+ isDefine = false;
+ name = '_@r' + (requireCounter += 1);
+ }
+
+ nameParts = splitPrefix(name);
+ prefix = nameParts[0];
+ name = nameParts[1];
+
+ if (prefix) {
+ prefix = normalize(prefix, parentName, applyMap);
+ pluginModule = getOwn(defined, prefix);
+ }
+
+ //Account for relative paths if there is a base name.
+ if (name) {
+ if (prefix) {
+ if (pluginModule && pluginModule.normalize) {
+ //Plugin is loaded, use its normalize method.
+ normalizedName = pluginModule.normalize(name, function (name) {
+ return normalize(name, parentName, applyMap);
+ });
+ } else {
+ // If nested plugin references, then do not try to
+ // normalize, as it will not normalize correctly. This
+ // places a restriction on resourceIds, and the longer
+ // term solution is not to normalize until plugins are
+ // loaded and all normalizations to allow for async
+ // loading of a loader plugin. But for now, fixes the
+ // common uses. Details in #1131
+ normalizedName = name.indexOf('!') === -1 ?
+ normalize(name, parentName, applyMap) :
+ name;
+ }
+ } else {
+ //A regular module.
+ normalizedName = normalize(name, parentName, applyMap);
+
+ //Normalized name may be a plugin ID due to map config
+ //application in normalize. The map config values must
+ //already be normalized, so do not need to redo that part.
+ nameParts = splitPrefix(normalizedName);
+ prefix = nameParts[0];
+ normalizedName = nameParts[1];
+ isNormalized = true;
+
+ url = context.nameToUrl(normalizedName);
+ }
+ }
+
+ //If the id is a plugin id that cannot be determined if it needs
+ //normalization, stamp it with a unique ID so two matching relative
+ //ids that may conflict can be separate.
+ suffix = prefix && !pluginModule && !isNormalized ?
+ '_unnormalized' + (unnormalizedCounter += 1) :
+ '';
+
+ return {
+ prefix: prefix,
+ name: normalizedName,
+ parentMap: parentModuleMap,
+ unnormalized: !!suffix,
+ url: url,
+ originalName: originalName,
+ isDefine: isDefine,
+ id: (prefix ?
+ prefix + '!' + normalizedName :
+ normalizedName) + suffix
+ };
+ }
+
+ function getModule(depMap) {
+ var id = depMap.id,
+ mod = getOwn(registry, id);
+
+ if (!mod) {
+ mod = registry[id] = new context.Module(depMap);
+ }
+
+ return mod;
+ }
+
+ function on(depMap, name, fn) {
+ var id = depMap.id,
+ mod = getOwn(registry, id);
+
+ if (hasProp(defined, id) &&
+ (!mod || mod.defineEmitComplete)) {
+ if (name === 'defined') {
+ fn(defined[id]);
+ }
+ } else {
+ mod = getModule(depMap);
+ if (mod.error && name === 'error') {
+ fn(mod.error);
+ } else {
+ mod.on(name, fn);
+ }
+ }
+ }
+
+ function onError(err, errback) {
+ var ids = err.requireModules,
+ notified = false;
+
+ if (errback) {
+ errback(err);
+ } else {
+ each(ids, function (id) {
+ var mod = getOwn(registry, id);
+ if (mod) {
+ //Set error on module, so it skips timeout checks.
+ mod.error = err;
+ if (mod.events.error) {
+ notified = true;
+ mod.emit('error', err);
+ }
+ }
+ });
+
+ if (!notified) {
+ req.onError(err);
+ }
+ }
+ }
+
+ /**
+ * Internal method to transfer globalQueue items to this context's
+ * defQueue.
+ */
+ function takeGlobalQueue() {
+ //Push all the globalDefQueue items into the context's defQueue
+ if (globalDefQueue.length) {
+ //Array splice in the values since the context code has a
+ //local var ref to defQueue, so cannot just reassign the one
+ //on context.
+ apsp.apply(defQueue,
+ [defQueue.length, 0].concat(globalDefQueue));
+ globalDefQueue = [];
+ }
+ }
+
+ handlers = {
+ 'require': function (mod) {
+ if (mod.require) {
+ return mod.require;
+ } else {
+ return (mod.require = context.makeRequire(mod.map));
+ }
+ },
+ 'exports': function (mod) {
+ mod.usingExports = true;
+ if (mod.map.isDefine) {
+ if (mod.exports) {
+ return (defined[mod.map.id] = mod.exports);
+ } else {
+ return (mod.exports = defined[mod.map.id] = {});
+ }
+ }
+ },
+ 'module': function (mod) {
+ if (mod.module) {
+ return mod.module;
+ } else {
+ return (mod.module = {
+ id: mod.map.id,
+ uri: mod.map.url,
+ config: function () {
+ return getOwn(config.config, mod.map.id) || {};
+ },
+ exports: mod.exports || (mod.exports = {})
+ });
+ }
+ }
+ };
+
+ function cleanRegistry(id) {
+ //Clean up machinery used for waiting modules.
+ delete registry[id];
+ delete enabledRegistry[id];
+ }
+
+ function breakCycle(mod, traced, processed) {
+ var id = mod.map.id;
+
+ if (mod.error) {
+ mod.emit('error', mod.error);
+ } else {
+ traced[id] = true;
+ each(mod.depMaps, function (depMap, i) {
+ var depId = depMap.id,
+ dep = getOwn(registry, depId);
+
+ //Only force things that have not completed
+ //being defined, so still in the registry,
+ //and only if it has not been matched up
+ //in the module already.
+ if (dep && !mod.depMatched[i] && !processed[depId]) {
+ if (getOwn(traced, depId)) {
+ mod.defineDep(i, defined[depId]);
+ mod.check(); //pass false?
+ } else {
+ breakCycle(dep, traced, processed);
+ }
+ }
+ });
+ processed[id] = true;
+ }
+ }
+
+ function checkLoaded() {
+ var err, usingPathFallback,
+ waitInterval = config.waitSeconds * 1000,
+ //It is possible to disable the wait interval by using waitSeconds of 0.
+ expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
+ noLoads = [],
+ reqCalls = [],
+ stillLoading = false,
+ needCycleCheck = true;
+
+ //Do not bother if this call was a result of a cycle break.
+ if (inCheckLoaded) {
+ return;
+ }
+
+ inCheckLoaded = true;
+
+ //Figure out the state of all the modules.
+ eachProp(enabledRegistry, function (mod) {
+ var map = mod.map,
+ modId = map.id;
+
+ //Skip things that are not enabled or in error state.
+ if (!mod.enabled) {
+ return;
+ }
+
+ if (!map.isDefine) {
+ reqCalls.push(mod);
+ }
+
+ if (!mod.error) {
+ //If the module should be executed, and it has not
+ //been inited and time is up, remember it.
+ if (!mod.inited && expired) {
+ if (hasPathFallback(modId)) {
+ usingPathFallback = true;
+ stillLoading = true;
+ } else {
+ noLoads.push(modId);
+ removeScript(modId);
+ }
+ } else if (!mod.inited && mod.fetched && map.isDefine) {
+ stillLoading = true;
+ if (!map.prefix) {
+ //No reason to keep looking for unfinished
+ //loading. If the only stillLoading is a
+ //plugin resource though, keep going,
+ //because it may be that a plugin resource
+ //is waiting on a non-plugin cycle.
+ return (needCycleCheck = false);
+ }
+ }
+ }
+ });
+
+ if (expired && noLoads.length) {
+ //If wait time expired, throw error of unloaded modules.
+ err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
+ err.contextName = context.contextName;
+ return onError(err);
+ }
+
+ //Not expired, check for a cycle.
+ if (needCycleCheck) {
+ each(reqCalls, function (mod) {
+ breakCycle(mod, {}, {});
+ });
+ }
+
+ //If still waiting on loads, and the waiting load is something
+ //other than a plugin resource, or there are still outstanding
+ //scripts, then just try back later.
+ if ((!expired || usingPathFallback) && stillLoading) {
+ //Something is still waiting to load. Wait for it, but only
+ //if a timeout is not already in effect.
+ if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
+ checkLoadedTimeoutId = setTimeout(function () {
+ checkLoadedTimeoutId = 0;
+ checkLoaded();
+ }, 50);
+ }
+ }
+
+ inCheckLoaded = false;
+ }
+
+ Module = function (map) {
+ this.events = getOwn(undefEvents, map.id) || {};
+ this.map = map;
+ this.shim = getOwn(config.shim, map.id);
+ this.depExports = [];
+ this.depMaps = [];
+ this.depMatched = [];
+ this.pluginMaps = {};
+ this.depCount = 0;
+
+ /* this.exports this.factory
+ this.depMaps = [],
+ this.enabled, this.fetched
+ */
+ };
+
+ Module.prototype = {
+ init: function (depMaps, factory, errback, options) {
+ options = options || {};
+
+ //Do not do more inits if already done. Can happen if there
+ //are multiple define calls for the same module. That is not
+ //a normal, common case, but it is also not unexpected.
+ if (this.inited) {
+ return;
+ }
+
+ this.factory = factory;
+
+ if (errback) {
+ //Register for errors on this module.
+ this.on('error', errback);
+ } else if (this.events.error) {
+ //If no errback already, but there are error listeners
+ //on this module, set up an errback to pass to the deps.
+ errback = bind(this, function (err) {
+ this.emit('error', err);
+ });
+ }
+
+ //Do a copy of the dependency array, so that
+ //source inputs are not modified. For example
+ //"shim" deps are passed in here directly, and
+ //doing a direct modification of the depMaps array
+ //would affect that config.
+ this.depMaps = depMaps && depMaps.slice(0);
+
+ this.errback = errback;
+
+ //Indicate this module has be initialized
+ this.inited = true;
+
+ this.ignore = options.ignore;
+
+ //Could have option to init this module in enabled mode,
+ //or could have been previously marked as enabled. However,
+ //the dependencies are not known until init is called. So
+ //if enabled previously, now trigger dependencies as enabled.
+ if (options.enabled || this.enabled) {
+ //Enable this module and dependencies.
+ //Will call this.check()
+ this.enable();
+ } else {
+ this.check();
+ }
+ },
+
+ defineDep: function (i, depExports) {
+ //Because of cycles, defined callback for a given
+ //export can be called more than once.
+ if (!this.depMatched[i]) {
+ this.depMatched[i] = true;
+ this.depCount -= 1;
+ this.depExports[i] = depExports;
+ }
+ },
+
+ fetch: function () {
+ if (this.fetched) {
+ return;
+ }
+ this.fetched = true;
+
+ context.startTime = (new Date()).getTime();
+
+ var map = this.map;
+
+ //If the manager is for a plugin managed resource,
+ //ask the plugin to load it now.
+ if (this.shim) {
+ context.makeRequire(this.map, {
+ enableBuildCallback: true
+ })(this.shim.deps || [], bind(this, function () {
+ return map.prefix ? this.callPlugin() : this.load();
+ }));
+ } else {
+ //Regular dependency.
+ return map.prefix ? this.callPlugin() : this.load();
+ }
+ },
+
+ load: function () {
+ var url = this.map.url;
+
+ //Regular dependency.
+ if (!urlFetched[url]) {
+ urlFetched[url] = true;
+ context.load(this.map.id, url);
+ }
+ },
+
+ /**
+ * Checks if the module is ready to define itself, and if so,
+ * define it.
+ */
+ check: function () {
+ if (!this.enabled || this.enabling) {
+ return;
+ }
+
+ var err, cjsModule,
+ id = this.map.id,
+ depExports = this.depExports,
+ exports = this.exports,
+ factory = this.factory;
+
+ if (!this.inited) {
+ this.fetch();
+ } else if (this.error) {
+ this.emit('error', this.error);
+ } else if (!this.defining) {
+ //The factory could trigger another require call
+ //that would result in checking this module to
+ //define itself again. If already in the process
+ //of doing that, skip this work.
+ this.defining = true;
+
+ if (this.depCount < 1 && !this.defined) {
+ if (isFunction(factory)) {
+ //If there is an error listener, favor passing
+ //to that instead of throwing an error. However,
+ //only do it for define()'d modules. require
+ //errbacks should not be called for failures in
+ //their callbacks (#699). However if a global
+ //onError is set, use that.
+ if ((this.events.error && this.map.isDefine) ||
+ req.onError !== defaultOnError) {
+ try {
+ exports = context.execCb(id, factory, depExports, exports);
+ } catch (e) {
+ err = e;
+ }
+ } else {
+ exports = context.execCb(id, factory, depExports, exports);
+ }
+
+ // Favor return value over exports. If node/cjs in play,
+ // then will not have a return value anyway. Favor
+ // module.exports assignment over exports object.
+ if (this.map.isDefine && exports === undefined) {
+ cjsModule = this.module;
+ if (cjsModule) {
+ exports = cjsModule.exports;
+ } else if (this.usingExports) {
+ //exports already set the defined value.
+ exports = this.exports;
+ }
+ }
+
+ if (err) {
+ err.requireMap = this.map;
+ err.requireModules = this.map.isDefine ? [this.map.id] : null;
+ err.requireType = this.map.isDefine ? 'define' : 'require';
+ return onError((this.error = err));
+ }
+
+ } else {
+ //Just a literal value
+ exports = factory;
+ }
+
+ this.exports = exports;
+
+ if (this.map.isDefine && !this.ignore) {
+ defined[id] = exports;
+
+ if (req.onResourceLoad) {
+ req.onResourceLoad(context, this.map, this.depMaps);
+ }
+ }
+
+ //Clean up
+ cleanRegistry(id);
+
+ this.defined = true;
+ }
+
+ //Finished the define stage. Allow calling check again
+ //to allow define notifications below in the case of a
+ //cycle.
+ this.defining = false;
+
+ if (this.defined && !this.defineEmitted) {
+ this.defineEmitted = true;
+ this.emit('defined', this.exports);
+ this.defineEmitComplete = true;
+ }
+
+ }
+ },
+
+ callPlugin: function () {
+ var map = this.map,
+ id = map.id,
+ //Map already normalized the prefix.
+ pluginMap = makeModuleMap(map.prefix);
+
+ //Mark this as a dependency for this plugin, so it
+ //can be traced for cycles.
+ this.depMaps.push(pluginMap);
+
+ on(pluginMap, 'defined', bind(this, function (plugin) {
+ var load, normalizedMap, normalizedMod,
+ bundleId = getOwn(bundlesMap, this.map.id),
+ name = this.map.name,
+ parentName = this.map.parentMap ? this.map.parentMap.name : null,
+ localRequire = context.makeRequire(map.parentMap, {
+ enableBuildCallback: true
+ });
+
+ //If current map is not normalized, wait for that
+ //normalized name to load instead of continuing.
+ if (this.map.unnormalized) {
+ //Normalize the ID if the plugin allows it.
+ if (plugin.normalize) {
+ name = plugin.normalize(name, function (name) {
+ return normalize(name, parentName, true);
+ }) || '';
+ }
+
+ //prefix and name should already be normalized, no need
+ //for applying map config again either.
+ normalizedMap = makeModuleMap(map.prefix + '!' + name,
+ this.map.parentMap);
+ on(normalizedMap,
+ 'defined', bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true,
+ ignore: true
+ });
+ }));
+
+ normalizedMod = getOwn(registry, normalizedMap.id);
+ if (normalizedMod) {
+ //Mark this as a dependency for this plugin, so it
+ //can be traced for cycles.
+ this.depMaps.push(normalizedMap);
+
+ if (this.events.error) {
+ normalizedMod.on('error', bind(this, function (err) {
+ this.emit('error', err);
+ }));
+ }
+ normalizedMod.enable();
+ }
+
+ return;
+ }
+
+ //If a paths config, then just load that file instead to
+ //resolve the plugin, as it is built into that paths layer.
+ if (bundleId) {
+ this.map.url = context.nameToUrl(bundleId);
+ this.load();
+ return;
+ }
+
+ load = bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true
+ });
+ });
+
+ load.error = bind(this, function (err) {
+ this.inited = true;
+ this.error = err;
+ err.requireModules = [id];
+
+ //Remove temp unnormalized modules for this module,
+ //since they will never be resolved otherwise now.
+ eachProp(registry, function (mod) {
+ if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
+ cleanRegistry(mod.map.id);
+ }
+ });
+
+ onError(err);
+ });
+
+ //Allow plugins to load other code without having to know the
+ //context or how to 'complete' the load.
+ load.fromText = bind(this, function (text, textAlt) {
+ /*jslint evil: true */
+ var moduleName = map.name,
+ moduleMap = makeModuleMap(moduleName),
+ hasInteractive = useInteractive;
+
+ //As of 2.1.0, support just passing the text, to reinforce
+ //fromText only being called once per resource. Still
+ //support old style of passing moduleName but discard
+ //that moduleName in favor of the internal ref.
+ if (textAlt) {
+ text = textAlt;
+ }
+
+ //Turn off interactive script matching for IE for any define
+ //calls in the text, then turn it back on at the end.
+ if (hasInteractive) {
+ useInteractive = false;
+ }
+
+ //Prime the system by creating a module instance for
+ //it.
+ getModule(moduleMap);
+
+ //Transfer any config to this other module.
+ if (hasProp(config.config, id)) {
+ config.config[moduleName] = config.config[id];
+ }
+
+ try {
+ req.exec(text);
+ } catch (e) {
+ return onError(makeError('fromtexteval',
+ 'fromText eval for ' + id +
+ ' failed: ' + e,
+ e,
+ [id]));
+ }
+
+ if (hasInteractive) {
+ useInteractive = true;
+ }
+
+ //Mark this as a dependency for the plugin
+ //resource
+ this.depMaps.push(moduleMap);
+
+ //Support anonymous modules.
+ context.completeLoad(moduleName);
+
+ //Bind the value of that module to the value for this
+ //resource ID.
+ localRequire([moduleName], load);
+ });
+
+ //Use parentName here since the plugin's name is not reliable,
+ //could be some weird string with no path that actually wants to
+ //reference the parentName's path.
+ plugin.load(map.name, localRequire, load, config);
+ }));
+
+ context.enable(pluginMap, this);
+ this.pluginMaps[pluginMap.id] = pluginMap;
+ },
+
+ enable: function () {
+ enabledRegistry[this.map.id] = this;
+ this.enabled = true;
+
+ //Set flag mentioning that the module is enabling,
+ //so that immediate calls to the defined callbacks
+ //for dependencies do not trigger inadvertent load
+ //with the depCount still being zero.
+ this.enabling = true;
+
+ //Enable each dependency
+ each(this.depMaps, bind(this, function (depMap, i) {
+ var id, mod, handler;
+
+ if (typeof depMap === 'string') {
+ //Dependency needs to be converted to a depMap
+ //and wired up to this module.
+ depMap = makeModuleMap(depMap,
+ (this.map.isDefine ? this.map : this.map.parentMap),
+ false,
+ !this.skipMap);
+ this.depMaps[i] = depMap;
+
+ handler = getOwn(handlers, depMap.id);
+
+ if (handler) {
+ this.depExports[i] = handler(this);
+ return;
+ }
+
+ this.depCount += 1;
+
+ on(depMap, 'defined', bind(this, function (depExports) {
+ this.defineDep(i, depExports);
+ this.check();
+ }));
+
+ if (this.errback) {
+ on(depMap, 'error', bind(this, this.errback));
+ } else if (this.events.error) {
+ // No direct errback on this module, but something
+ // else is listening for errors, so be sure to
+ // propagate the error correctly.
+ on(depMap, 'error', bind(this, function(err) {
+ this.emit('error', err);
+ }));
+ }
+ }
+
+ id = depMap.id;
+ mod = registry[id];
+
+ //Skip special modules like 'require', 'exports', 'module'
+ //Also, don't call enable if it is already enabled,
+ //important in circular dependency cases.
+ if (!hasProp(handlers, id) && mod && !mod.enabled) {
+ context.enable(depMap, this);
+ }
+ }));
+
+ //Enable each plugin that is used in
+ //a dependency
+ eachProp(this.pluginMaps, bind(this, function (pluginMap) {
+ var mod = getOwn(registry, pluginMap.id);
+ if (mod && !mod.enabled) {
+ context.enable(pluginMap, this);
+ }
+ }));
+
+ this.enabling = false;
+
+ this.check();
+ },
+
+ on: function (name, cb) {
+ var cbs = this.events[name];
+ if (!cbs) {
+ cbs = this.events[name] = [];
+ }
+ cbs.push(cb);
+ },
+
+ emit: function (name, evt) {
+ each(this.events[name], function (cb) {
+ cb(evt);
+ });
+ if (name === 'error') {
+ //Now that the error handler was triggered, remove
+ //the listeners, since this broken Module instance
+ //can stay around for a while in the registry.
+ delete this.events[name];
+ }
+ }
+ };
+
+ function callGetModule(args) {
+ //Skip modules already defined.
+ if (!hasProp(defined, args[0])) {
+ getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
+ }
+ }
+
+ function removeListener(node, func, name, ieName) {
+ //Favor detachEvent because of IE9
+ //issue, see attachEvent/addEventListener comment elsewhere
+ //in this file.
+ if (node.detachEvent && !isOpera) {
+ //Probably IE. If not it will throw an error, which will be
+ //useful to know.
+ if (ieName) {
+ node.detachEvent(ieName, func);
+ }
+ } else {
+ node.removeEventListener(name, func, false);
+ }
+ }
+
+ /**
+ * Given an event from a script node, get the requirejs info from it,
+ * and then removes the event listeners on the node.
+ * @param {Event} evt
+ * @returns {Object}
+ */
+ function getScriptData(evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ var node = evt.currentTarget || evt.srcElement;
+
+ //Remove the listeners once here.
+ removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
+ removeListener(node, context.onScriptError, 'error');
+
+ return {
+ node: node,
+ id: node && node.getAttribute('data-requiremodule')
+ };
+ }
+
+ function intakeDefines() {
+ var args;
+
+ //Any defined modules in the global queue, intake them now.
+ takeGlobalQueue();
+
+ //Make sure any remaining defQueue items get properly processed.
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
+ } else {
+ //args are id, deps, factory. Should be normalized by the
+ //define() function.
+ callGetModule(args);
+ }
+ }
+ }
+
+ context = {
+ config: config,
+ contextName: contextName,
+ registry: registry,
+ defined: defined,
+ urlFetched: urlFetched,
+ defQueue: defQueue,
+ Module: Module,
+ makeModuleMap: makeModuleMap,
+ nextTick: req.nextTick,
+ onError: onError,
+
+ /**
+ * Set a configuration for the context.
+ * @param {Object} cfg config object to integrate.
+ */
+ configure: function (cfg) {
+ //Make sure the baseUrl ends in a slash.
+ if (cfg.baseUrl) {
+ if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
+ cfg.baseUrl += '/';
+ }
+ }
+
+ //Save off the paths since they require special processing,
+ //they are additive.
+ var shim = config.shim,
+ objs = {
+ paths: true,
+ bundles: true,
+ config: true,
+ map: true
+ };
+
+ eachProp(cfg, function (value, prop) {
+ if (objs[prop]) {
+ if (!config[prop]) {
+ config[prop] = {};
+ }
+ mixin(config[prop], value, true, true);
+ } else {
+ config[prop] = value;
+ }
+ });
+
+ //Reverse map the bundles
+ if (cfg.bundles) {
+ eachProp(cfg.bundles, function (value, prop) {
+ each(value, function (v) {
+ if (v !== prop) {
+ bundlesMap[v] = prop;
+ }
+ });
+ });
+ }
+
+ //Merge shim
+ if (cfg.shim) {
+ eachProp(cfg.shim, function (value, id) {
+ //Normalize the structure
+ if (isArray(value)) {
+ value = {
+ deps: value
+ };
+ }
+ if ((value.exports || value.init) && !value.exportsFn) {
+ value.exportsFn = context.makeShimExports(value);
+ }
+ shim[id] = value;
+ });
+ config.shim = shim;
+ }
+
+ //Adjust packages if necessary.
+ if (cfg.packages) {
+ each(cfg.packages, function (pkgObj) {
+ var location, name;
+
+ pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
+
+ name = pkgObj.name;
+ location = pkgObj.location;
+ if (location) {
+ config.paths[name] = pkgObj.location;
+ }
+
+ //Save pointer to main module ID for pkg name.
+ //Remove leading dot in main, so main paths are normalized,
+ //and remove any trailing .js, since different package
+ //envs have different conventions: some use a module name,
+ //some use a file name.
+ config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
+ .replace(currDirRegExp, '')
+ .replace(jsSuffixRegExp, '');
+ });
+ }
+
+ //If there are any "waiting to execute" modules in the registry,
+ //update the maps for them, since their info, like URLs to load,
+ //may have changed.
+ eachProp(registry, function (mod, id) {
+ //If module already has init called, since it is too
+ //late to modify them, and ignore unnormalized ones
+ //since they are transient.
+ if (!mod.inited && !mod.map.unnormalized) {
+ mod.map = makeModuleMap(id);
+ }
+ });
+
+ //If a deps array or a config callback is specified, then call
+ //require with those args. This is useful when require is defined as a
+ //config object before require.js is loaded.
+ if (cfg.deps || cfg.callback) {
+ context.require(cfg.deps || [], cfg.callback);
+ }
+ },
+
+ makeShimExports: function (value) {
+ function fn() {
+ var ret;
+ if (value.init) {
+ ret = value.init.apply(global, arguments);
+ }
+ return ret || (value.exports && getGlobal(value.exports));
+ }
+ return fn;
+ },
+
+ makeRequire: function (relMap, options) {
+ options = options || {};
+
+ function localRequire(deps, callback, errback) {
+ var id, map, requireMod;
+
+ if (options.enableBuildCallback && callback && isFunction(callback)) {
+ callback.__requireJsBuild = true;
+ }
+
+ if (typeof deps === 'string') {
+ if (isFunction(callback)) {
+ //Invalid call
+ return onError(makeError('requireargs', 'Invalid require call'), errback);
+ }
+
+ //If require|exports|module are requested, get the
+ //value for them from the special handlers. Caveat:
+ //this only works while module is being defined.
+ if (relMap && hasProp(handlers, deps)) {
+ return handlers[deps](registry[relMap.id]);
+ }
+
+ //Synchronous access to one module. If require.get is
+ //available (as in the Node adapter), prefer that.
+ if (req.get) {
+ return req.get(context, deps, relMap, localRequire);
+ }
+
+ //Normalize module name, if it contains . or ..
+ map = makeModuleMap(deps, relMap, false, true);
+ id = map.id;
+
+ if (!hasProp(defined, id)) {
+ return onError(makeError('notloaded', 'Module name "' +
+ id +
+ '" has not been loaded yet for context: ' +
+ contextName +
+ (relMap ? '' : '. Use require([])')));
+ }
+ return defined[id];
+ }
+
+ //Grab defines waiting in the global queue.
+ intakeDefines();
+
+ //Mark all the dependencies as needing to be loaded.
+ context.nextTick(function () {
+ //Some defines could have been added since the
+ //require call, collect them.
+ intakeDefines();
+
+ requireMod = getModule(makeModuleMap(null, relMap));
+
+ //Store if map config should be applied to this require
+ //call for dependencies.
+ requireMod.skipMap = options.skipMap;
+
+ requireMod.init(deps, callback, errback, {
+ enabled: true
+ });
+
+ checkLoaded();
+ });
+
+ return localRequire;
+ }
+
+ mixin(localRequire, {
+ isBrowser: isBrowser,
+
+ /**
+ * Converts a module name + .extension into an URL path.
+ * *Requires* the use of a module name. It does not support using
+ * plain URLs like nameToUrl.
+ */
+ toUrl: function (moduleNamePlusExt) {
+ var ext,
+ index = moduleNamePlusExt.lastIndexOf('.'),
+ segment = moduleNamePlusExt.split('/')[0],
+ isRelative = segment === '.' || segment === '..';
+
+ //Have a file extension alias, and it is not the
+ //dots from a relative path.
+ if (index !== -1 && (!isRelative || index > 1)) {
+ ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
+ moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
+ }
+
+ return context.nameToUrl(normalize(moduleNamePlusExt,
+ relMap && relMap.id, true), ext, true);
+ },
+
+ defined: function (id) {
+ return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
+ },
+
+ specified: function (id) {
+ id = makeModuleMap(id, relMap, false, true).id;
+ return hasProp(defined, id) || hasProp(registry, id);
+ }
+ });
+
+ //Only allow undef on top level require calls
+ if (!relMap) {
+ localRequire.undef = function (id) {
+ //Bind any waiting define() calls to this context,
+ //fix for #408
+ takeGlobalQueue();
+
+ var map = makeModuleMap(id, relMap, true),
+ mod = getOwn(registry, id);
+
+ removeScript(id);
+
+ delete defined[id];
+ delete urlFetched[map.url];
+ delete undefEvents[id];
+
+ //Clean queued defines too. Go backwards
+ //in array so that the splices do not
+ //mess up the iteration.
+ eachReverse(defQueue, function(args, i) {
+ if(args[0] === id) {
+ defQueue.splice(i, 1);
+ }
+ });
+
+ if (mod) {
+ //Hold on to listeners in case the
+ //module will be attempted to be reloaded
+ //using a different config.
+ if (mod.events.defined) {
+ undefEvents[id] = mod.events;
+ }
+
+ cleanRegistry(id);
+ }
+ };
+ }
+
+ return localRequire;
+ },
+
+ /**
+ * Called to enable a module if it is still in the registry
+ * awaiting enablement. A second arg, parent, the parent module,
+ * is passed in for context, when this method is overridden by
+ * the optimizer. Not shown here to keep code compact.
+ */
+ enable: function (depMap) {
+ var mod = getOwn(registry, depMap.id);
+ if (mod) {
+ getModule(depMap).enable();
+ }
+ },
+
+ /**
+ * Internal method used by environment adapters to complete a load event.
+ * A load event could be a script load or just a load pass from a synchronous
+ * load call.
+ * @param {String} moduleName the name of the module to potentially complete.
+ */
+ completeLoad: function (moduleName) {
+ var found, args, mod,
+ shim = getOwn(config.shim, moduleName) || {},
+ shExports = shim.exports;
+
+ takeGlobalQueue();
+
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ args[0] = moduleName;
+ //If already found an anonymous module and bound it
+ //to this name, then this is some other anon module
+ //waiting for its completeLoad to fire.
+ if (found) {
+ break;
+ }
+ found = true;
+ } else if (args[0] === moduleName) {
+ //Found matching define call for this script!
+ found = true;
+ }
+
+ callGetModule(args);
+ }
+
+ //Do this after the cycle of callGetModule in case the result
+ //of those calls/init calls changes the registry.
+ mod = getOwn(registry, moduleName);
+
+ if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
+ if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
+ if (hasPathFallback(moduleName)) {
+ return;
+ } else {
+ return onError(makeError('nodefine',
+ 'No define call for ' + moduleName,
+ null,
+ [moduleName]));
+ }
+ } else {
+ //A script that does not call define(), so just simulate
+ //the call for it.
+ callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
+ }
+ }
+
+ checkLoaded();
+ },
+
+ /**
+ * Converts a module name to a file path. Supports cases where
+ * moduleName may actually be just an URL.
+ * Note that it **does not** call normalize on the moduleName,
+ * it is assumed to have already been normalized. This is an
+ * internal API, not a public one. Use toUrl for the public API.
+ */
+ nameToUrl: function (moduleName, ext, skipExt) {
+ var paths, syms, i, parentModule, url,
+ parentPath, bundleId,
+ pkgMain = getOwn(config.pkgs, moduleName);
+
+ if (pkgMain) {
+ moduleName = pkgMain;
+ }
+
+ bundleId = getOwn(bundlesMap, moduleName);
+
+ if (bundleId) {
+ return context.nameToUrl(bundleId, ext, skipExt);
+ }
+
+ //If a colon is in the URL, it indicates a protocol is used and it is just
+ //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
+ //or ends with .js, then assume the user meant to use an url and not a module id.
+ //The slash is important for protocol-less URLs as well as full paths.
+ if (req.jsExtRegExp.test(moduleName)) {
+ //Just a plain path, not module name lookup, so just return it.
+ //Add extension if it is included. This is a bit wonky, only non-.js things pass
+ //an extension, this method probably needs to be reworked.
+ url = moduleName + (ext || '');
+ } else {
+ //A module that needs to be converted to a path.
+ paths = config.paths;
+
+ syms = moduleName.split('/');
+ //For each module name segment, see if there is a path
+ //registered for it. Start with most specific name
+ //and work up from it.
+ for (i = syms.length; i > 0; i -= 1) {
+ parentModule = syms.slice(0, i).join('/');
+
+ parentPath = getOwn(paths, parentModule);
+ if (parentPath) {
+ //If an array, it means there are a few choices,
+ //Choose the one that is desired
+ if (isArray(parentPath)) {
+ parentPath = parentPath[0];
+ }
+ syms.splice(0, i, parentPath);
+ break;
+ }
+ }
+
+ //Join the path parts together, then figure out if baseUrl is needed.
+ url = syms.join('/');
+ url += (ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js'));
+ url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
+ }
+
+ return config.urlArgs ? url +
+ ((url.indexOf('?') === -1 ? '?' : '&') +
+ config.urlArgs) : url;
+ },
+
+ //Delegates to req.load. Broken out as a separate function to
+ //allow overriding in the optimizer.
+ load: function (id, url) {
+ req.load(context, id, url);
+ },
+
+ /**
+ * Executes a module callback function. Broken out as a separate function
+ * solely to allow the build system to sequence the files in the built
+ * layer in the right sequence.
+ *
+ * @private
+ */
+ execCb: function (name, callback, args, exports) {
+ return callback.apply(exports, args);
+ },
+
+ /**
+ * callback for script loads, used to check status of loading.
+ *
+ * @param {Event} evt the event from the browser for the script
+ * that was loaded.
+ */
+ onScriptLoad: function (evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ if (evt.type === 'load' ||
+ (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
+ //Reset interactive script so a script node is not held onto for
+ //to long.
+ interactiveScript = null;
+
+ //Pull out the name of the module and the context.
+ var data = getScriptData(evt);
+ context.completeLoad(data.id);
+ }
+ },
+
+ /**
+ * Callback for script errors.
+ */
+ onScriptError: function (evt) {
+ var data = getScriptData(evt);
+ if (!hasPathFallback(data.id)) {
+ return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id]));
+ }
+ }
+ };
+
+ context.require = context.makeRequire();
+ return context;
+ }
+
+ /**
+ * Main entry point.
+ *
+ * If the only argument to require is a string, then the module that
+ * is represented by that string is fetched for the appropriate context.
+ *
+ * If the first argument is an array, then it will be treated as an array
+ * of dependency string names to fetch. An optional function callback can
+ * be specified to execute when all of those dependencies are available.
+ *
+ * Make a local req variable to help Caja compliance (it assumes things
+ * on a require that are not standardized), and to give a short
+ * name for minification/local scope use.
+ */
+ req = requirejs = function (deps, callback, errback, optional) {
+
+ //Find the right context, use default
+ var context, config,
+ contextName = defContextName;
+
+ // Determine if have config object in the call.
+ if (!isArray(deps) && typeof deps !== 'string') {
+ // deps is a config object
+ config = deps;
+ if (isArray(callback)) {
+ // Adjust args if there are dependencies
+ deps = callback;
+ callback = errback;
+ errback = optional;
+ } else {
+ deps = [];
+ }
+ }
+
+ if (config && config.context) {
+ contextName = config.context;
+ }
+
+ context = getOwn(contexts, contextName);
+ if (!context) {
+ context = contexts[contextName] = req.s.newContext(contextName);
+ }
+
+ if (config) {
+ context.configure(config);
+ }
+
+ return context.require(deps, callback, errback);
+ };
+
+ /**
+ * Support require.config() to make it easier to cooperate with other
+ * AMD loaders on globally agreed names.
+ */
+ req.config = function (config) {
+ return req(config);
+ };
+
+ /**
+ * Execute something after the current tick
+ * of the event loop. Override for other envs
+ * that have a better solution than setTimeout.
+ * @param {Function} fn function to execute later.
+ */
+ req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
+ setTimeout(fn, 4);
+ } : function (fn) { fn(); };
+
+ /**
+ * Export require as a global, but only if it does not already exist.
+ */
+ if (!require) {
+ require = req;
+ }
+
+ req.version = version;
+
+ //Used to filter out dependencies that are already paths.
+ req.jsExtRegExp = /^\/|:|\?|\.js$/;
+ req.isBrowser = isBrowser;
+ s = req.s = {
+ contexts: contexts,
+ newContext: newContext
+ };
+
+ //Create default context.
+ req({});
+
+ //Exports some context-sensitive methods on global require.
+ each([
+ 'toUrl',
+ 'undef',
+ 'defined',
+ 'specified'
+ ], function (prop) {
+ //Reference from contexts instead of early binding to default context,
+ //so that during builds, the latest instance of the default context
+ //with its config gets used.
+ req[prop] = function () {
+ var ctx = contexts[defContextName];
+ return ctx.require[prop].apply(ctx, arguments);
+ };
+ });
+
+ if (isBrowser) {
+ head = s.head = document.getElementsByTagName('head')[0];
+ //If BASE tag is in play, using appendChild is a problem for IE6.
+ //When that browser dies, this can be removed. Details in this jQuery bug:
+ //http://dev.jquery.com/ticket/2709
+ baseElement = document.getElementsByTagName('base')[0];
+ if (baseElement) {
+ head = s.head = baseElement.parentNode;
+ }
+ }
+
+ /**
+ * Any errors that require explicitly generates will be passed to this
+ * function. Intercept/override it if you want custom error handling.
+ * @param {Error} err the error object.
+ */
+ req.onError = defaultOnError;
+
+ /**
+ * Creates the node for the load command. Only used in browser envs.
+ */
+ req.createNode = function (config, moduleName, url) {
+ var node = config.xhtml ?
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
+ document.createElement('script');
+ node.type = config.scriptType || 'text/javascript';
+ node.charset = 'utf-8';
+ node.async = true;
+ return node;
+ };
+
+ /**
+ * Does the request to load a module for the browser case.
+ * Make this a separate function to allow other environments
+ * to override it.
+ *
+ * @param {Object} context the require context to find state.
+ * @param {String} moduleName the name of the module.
+ * @param {Object} url the URL to the module.
+ */
+ req.load = function (context, moduleName, url) {
+ var config = (context && context.config) || {},
+ node;
+ if (isBrowser) {
+ //In the browser so use a script tag
+ node = req.createNode(config, moduleName, url);
+
+ node.setAttribute('data-requirecontext', context.contextName);
+ node.setAttribute('data-requiremodule', moduleName);
+
+ //Set up load listener. Test attachEvent first because IE9 has
+ //a subtle issue in its addEventListener and script onload firings
+ //that do not match the behavior of all other browsers with
+ //addEventListener support, which fire the onload event for a
+ //script right after the script execution. See:
+ //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
+ //UNFORTUNATELY Opera implements attachEvent but does not follow the script
+ //script execution mode.
+ if (node.attachEvent &&
+ //Check if node.attachEvent is artificially added by custom script or
+ //natively supported by browser
+ //read https://github.com/jrburke/requirejs/issues/187
+ //if we can NOT find [native code] then it must NOT natively supported.
+ //in IE8, node.attachEvent does not have toString()
+ //Note the test for "[native code" with no closing brace, see:
+ //https://github.com/jrburke/requirejs/issues/273
+ !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
+ !isOpera) {
+ //Probably IE. IE (at least 6-8) do not fire
+ //script onload right after executing the script, so
+ //we cannot tie the anonymous define call to a name.
+ //However, IE reports the script as being in 'interactive'
+ //readyState at the time of the define call.
+ useInteractive = true;
+
+ node.attachEvent('onreadystatechange', context.onScriptLoad);
+ //It would be great to add an error handler here to catch
+ //404s in IE9+. However, onreadystatechange will fire before
+ //the error handler, so that does not help. If addEventListener
+ //is used, then IE will fire error before load, but we cannot
+ //use that pathway given the connect.microsoft.com issue
+ //mentioned above about not doing the 'script execute,
+ //then fire the script load event listener before execute
+ //next script' that other browsers do.
+ //Best hope: IE10 fixes the issues,
+ //and then destroys all installs of IE 6-9.
+ //node.attachEvent('onerror', context.onScriptError);
+ } else {
+ node.addEventListener('load', context.onScriptLoad, false);
+ node.addEventListener('error', context.onScriptError, false);
+ }
+ node.src = url;
+
+ //For some cache cases in IE 6-8, the script executes before the end
+ //of the appendChild execution, so to tie an anonymous define
+ //call to the module name (which is stored on the node), hold on
+ //to a reference to this node, but clear after the DOM insertion.
+ currentlyAddingScript = node;
+ if (baseElement) {
+ head.insertBefore(node, baseElement);
+ } else {
+ head.appendChild(node);
+ }
+ currentlyAddingScript = null;
+
+ return node;
+ } else if (isWebWorker) {
+ try {
+ //In a web worker, use importScripts. This is not a very
+ //efficient use of importScripts, importScripts will block until
+ //its script is downloaded and evaluated. However, if web workers
+ //are in play, the expectation that a build has been done so that
+ //only one script needs to be loaded anyway. This may need to be
+ //reevaluated if other use cases become common.
+ importScripts(url);
+
+ //Account for anonymous modules
+ context.completeLoad(moduleName);
+ } catch (e) {
+ context.onError(makeError('importscripts',
+ 'importScripts failed for ' +
+ moduleName + ' at ' + url,
+ e,
+ [moduleName]));
+ }
+ }
+ };
+
+ function getInteractiveScript() {
+ if (interactiveScript && interactiveScript.readyState === 'interactive') {
+ return interactiveScript;
+ }
+
+ eachReverse(scripts(), function (script) {
+ if (script.readyState === 'interactive') {
+ return (interactiveScript = script);
+ }
+ });
+ return interactiveScript;
+ }
+
+ //Look for a data-main script attribute, which could also adjust the baseUrl.
+ if (isBrowser && !cfg.skipDataMain) {
+ //Figure out baseUrl. Get it from the script tag with require.js in it.
+ eachReverse(scripts(), function (script) {
+ //Set the 'head' where we can append children by
+ //using the script's parent.
+ if (!head) {
+ head = script.parentNode;
+ }
+
+ //Look for a data-main attribute to set main script for the page
+ //to load. If it is there, the path to data main becomes the
+ //baseUrl, if it is not already set.
+ dataMain = script.getAttribute('data-main');
+ if (dataMain) {
+ //Preserve dataMain in case it is a path (i.e. contains '?')
+ mainScript = dataMain;
+
+ //Set final baseUrl if there is not already an explicit one.
+ if (!cfg.baseUrl) {
+ //Pull off the directory of data-main for use as the
+ //baseUrl.
+ src = mainScript.split('/');
+ mainScript = src.pop();
+ subPath = src.length ? src.join('/') + '/' : './';
+
+ cfg.baseUrl = subPath;
+ }
+
+ //Strip off any trailing .js since mainScript is now
+ //like a module name.
+ mainScript = mainScript.replace(jsSuffixRegExp, '');
+
+ //If mainScript is still a path, fall back to dataMain
+ if (req.jsExtRegExp.test(mainScript)) {
+ mainScript = dataMain;
+ }
+
+ //Put the data-main script in the files to load.
+ cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * The function that handles definitions of modules. Differs from
+ * require() in that a string for the module should be the first argument,
+ * and the function to execute after dependencies are loaded should
+ * return a value to define the module corresponding to the first argument's
+ * name.
+ */
+ define = function (name, deps, callback) {
+ var node, context;
+
+ //Allow for anonymous modules
+ if (typeof name !== 'string') {
+ //Adjust args appropriately
+ callback = deps;
+ deps = name;
+ name = null;
+ }
+
+ //This module may not have dependencies
+ if (!isArray(deps)) {
+ callback = deps;
+ deps = null;
+ }
+
+ //If no name, and callback is a function, then figure out if it a
+ //CommonJS thing with dependencies.
+ if (!deps && isFunction(callback)) {
+ deps = [];
+ //Remove comments from the callback string,
+ //look for require calls, and pull them into the dependencies,
+ //but only if there are function args.
+ if (callback.length) {
+ callback
+ .toString()
+ .replace(commentRegExp, '')
+ .replace(cjsRequireRegExp, function (match, dep) {
+ deps.push(dep);
+ });
+
+ //May be a CommonJS thing even without require calls, but still
+ //could use exports, and module. Avoid doing exports and module
+ //work though if it just needs require.
+ //REQUIRES the function to expect the CommonJS variables in the
+ //order listed below.
+ deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
+ }
+ }
+
+ //If in IE 6-8 and hit an anonymous define() call, do the interactive
+ //work.
+ if (useInteractive) {
+ node = currentlyAddingScript || getInteractiveScript();
+ if (node) {
+ if (!name) {
+ name = node.getAttribute('data-requiremodule');
+ }
+ context = contexts[node.getAttribute('data-requirecontext')];
+ }
+ }
+
+ //Always save off evaluating the def call until the script onload handler.
+ //This allows multiple modules to be in a file without prematurely
+ //tracing dependencies, and allows for anonymous module support,
+ //where the module name is not known until the script onload event
+ //occurs. If no context, use the global queue, and get it processed
+ //in the onscript load callback.
+ (context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+
+
+ /**
+ * Executes the text. Normally just uses eval, but can be modified
+ * to use a better, environment-specific call. Only used for transpiling
+ * loader plugins, not for plain JS modules.
+ * @param {String} text the text to execute/evaluate.
+ */
+ req.exec = function (text) {
+ /*jslint evil: true */
+ return eval(text);
+ };
+
+ //Set up with config info.
+ req(cfg);
+}(this));
diff --git a/dev/js/src/api.js b/dev/js/src/api.js
index ed33cc2..f9058bc 100644
--- a/dev/js/src/api.js
+++ b/dev/js/src/api.js
@@ -1,15 +1,4 @@
-var KorAP = KorAP || {};
-
-(function (KorAP) {
- "use strict";
-
- // Default log message
- KorAP.log = KorAP.log || function (type, msg) {
- console.log(type + ": " + msg);
- };
-
- KorAP.URL = KorAP.URL || 'http://korap.ids-mannheim.de/kalamar';
-
+define(['util'], function () {
// TODO: https://github.com/honza/140medley/blob/master/140medley.js
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
@@ -17,6 +6,8 @@
// http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml
// http://stackoverflow.com/questions/6112744/load-javascript-on-demand
+ KorAP.URL = KorAP.URL || 'http://korap.ids-mannheim.de/kalamar';
+
KorAP.API = {
getMatchInfo : function (match, param, cb) {
// match is a KorAP.Match object
@@ -44,14 +35,11 @@
this.getJSON(url, cb);
},
+
getJSON : function (url, onload) {
var req = new XMLHttpRequest();
- console.log('Request url: ' + url);
-
req.open("GET", url, true);
-
-
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
req.onreadystatechange = function () {
@@ -62,7 +50,7 @@
2 - headers received
3 - loading (responseText has partial data)
4 - done
- */
+ */
if (this.readyState == 4) {
if (this.status === 200)
onload(JSON.parse(this.responseText));
@@ -76,5 +64,4 @@
req.send();
}
};
-
-}(this.KorAP));
+});
diff --git a/dev/js/src/hint.js b/dev/js/src/hint.js
index 11c3e1e..6aed544 100644
--- a/dev/js/src/hint.js
+++ b/dev/js/src/hint.js
@@ -3,23 +3,20 @@
*
* @author Nils Diewald
*/
-
-// requires menu.js
-
-var KorAP = KorAP || {};
-
-(function (KorAP) {
+define([
+ 'hint/input',
+ 'hint/menu',
+ 'hint/contextanalyzer',
+ 'util'
+], function (inputClass,
+ menuClass,
+ analyzerClass) {
"use strict";
- // Default log message
- KorAP.log = KorAP.log || function (type, msg) {
- console.log(type + ": " + msg);
- };
-
/**
* @define {regex} Regular expression for context
*/
- KorAP.context =
+ KorAP.context = KorAP.context ||
"(?:^|[^-_a-zA-Z0-9])" + // Anchor
"((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
"(?:" +
@@ -27,10 +24,18 @@
"(?:(?:[^:=\/ ]+?):)?" + // Key
")?" +
")$";
-
- // Initialize hint array
KorAP.hintArray = KorAP.hintArray || {};
+ /**
+ * Return keycode based on event
+ */
+ function _codeFromEvent (e) {
+ if ((e.charCode) && (e.keyCode==0))
+ return e.charCode
+ return e.keyCode;
+ };
+
+ // Initialize hint array
/**
* KorAP.Hint.create({
@@ -38,16 +43,20 @@
* context : context regex
* });
*/
- KorAP.Hint = {
+ return {
// Some variables
// _firstTry : true,
active : false,
+ /**
+ * Create new hint helper.
+ */
create : function (param) {
- return Object.create(KorAP.Hint)._init(param);
+ return Object.create(this)._init(param);
},
+ // Initialize hint helper
_init : function (param) {
param = param || {};
@@ -55,9 +64,11 @@
this._menu = {};
// Get input field
- this._inputField = KorAP.InputField.create(
- param["inputField"] || document.getElementById("q-field")
- );
+ var qfield = param["inputField"] || document.getElementById("q-field");
+ if (!qfield)
+ return null;
+
+ this._inputField = inputClass.create(qfield);
var inputFieldElement = this._inputField.element();
@@ -83,7 +94,7 @@
);
// Set Analyzer for context
- this._analyzer = KorAP.ContextAnalyzer.create(
+ this._analyzer = analyzerClass.create(
param["context"] || KorAP.context
);
return this;
@@ -124,10 +135,9 @@
return;
// Create matching hint menu
- this._menu[action] = KorAP.HintMenu.create(
+ this._menu[action] = menuClass.create(
this, action, KorAP.hintArray[action]
);
-
};
// Return matching hint menu
@@ -189,278 +199,4 @@
};
}
};
-
-
- // Input field for queries
- KorAP.InputField = {
- create : function (element) {
- return Object.create(KorAP.InputField)._init(element);
- },
-
- _init : function (element) {
- this._element = element;
-
- // Create mirror for searchField
- if ((this._mirror = document.getElementById("searchMirror")) === null) {
- this._mirror = document.createElement("div");
- this._mirror.setAttribute("id", "searchMirror");
- this._mirror.appendChild(document.createElement("span"));
- this._container = this._mirror.appendChild(document.createElement("div"));
- this._mirror.style.height = "0px";
- document.getElementsByTagName("body")[0].appendChild(this._mirror);
- };
-
- // Update position of the mirror
- var that = this;
- var repos = function () {
- that.reposition();
- };
- window.addEventListener('resize', repos);
- this._element.addEventListener('onfocus', repos);
- that.reposition();
-
- return this;
- },
-
- rightPos : function () {
- var box = this._mirror.firstChild.getBoundingClientRect();
- return box.right - box.left;
- },
-
- mirror : function () {
- return this._mirror;
- },
-
- container : function () {
- return this._container;
- },
-
- element : function () {
- return this._element;
- },
-
- value : function () {
- return this._element.value;
- },
-
- update : function () {
- this._mirror.firstChild.textContent = this.split()[0];
- this._container.style.left = this.rightPos() + 'px';
- },
-
- insert : function (text) {
- var splittedText = this.split();
- var s = this._element;
- s.value = splittedText[0] + text + splittedText[1];
- s.selectionStart = (splittedText[0] + text).length;
- s.selectionEnd = s.selectionStart;
- this._mirror.firstChild.textContent = splittedText[0] + text;
- },
-
- // Return two substrings, splitted at current cursor position
- split : function () {
- var s = this._element;
- var value = s.value;
- var start = s.selectionStart;
- return new Array(
- value.substring(0, start),
- value.substring(start, value.length)
- );
- },
-
- // Position the input mirror directly below the input box
- reposition : function () {
- var inputClientRect = this._element.getBoundingClientRect();
- var inputStyle = window.getComputedStyle(this._element, null);
-
- var bodyClientRect =
- document.getElementsByTagName('body')[0].getBoundingClientRect();
-
- // Reset position
- var mirrorStyle = this._mirror.style;
- mirrorStyle.left = inputClientRect.left + "px";
- mirrorStyle.top = (inputClientRect.bottom - bodyClientRect.top) + "px";
- mirrorStyle.width = inputStyle.getPropertyValue("width");
-
- // These may be relevant in case of media depending css
- mirrorStyle.paddingLeft = inputStyle.getPropertyValue("padding-left");
- mirrorStyle.marginLeft = inputStyle.getPropertyValue("margin-left");
- mirrorStyle.borderLeftWidth = inputStyle.getPropertyValue("border-left-width");
- mirrorStyle.borderLeftStyle = inputStyle.getPropertyValue("border-left-style");
- mirrorStyle.fontSize = inputStyle.getPropertyValue("font-size");
- mirrorStyle.fontFamily = inputStyle.getPropertyValue("font-family");
- },
- context : function () {
- return this.split()[0];
- }
- };
-
-
- /**
- * Regex object for checking the context of the hint
- */
- KorAP.ContextAnalyzer = {
- create : function (regex) {
- return Object.create(KorAP.ContextAnalyzer)._init(regex);
- },
- _init : function (regex) {
- try {
- this._regex = new RegExp(regex);
- }
- catch (e) {
- KorAP.log(0, e);
- return;
- };
- return this;
- },
- test : function (text) {
- if (!this._regex.exec(text))
- return;
- return RegExp.$1;
- }
- };
-
-
- /**
- * Hint menu
- */
- KorAP.HintMenu = {
- create : function (hint, context, params) {
- var obj = Object.create(KorAP.Menu)
- .upgradeTo(KorAP.HintMenu)
- ._init(KorAP.HintMenuItem, KorAP.HintMenuPrefix, params);
- obj._context = context;
- obj._element.classList.add('hint');
- obj._hint = hint;
-
- // This is only domspecific
- obj.element().addEventListener('blur', function (e) {
- this.menu.hide();
- });
-
- // Focus on input field on hide
- obj.onHide = function () {
- var input = this._hint.inputField();
- input.container().classList.remove('active');
- input.element().focus();
- };
-
- return obj;
- },
- // Todo: Is this necessary?
- context : function () {
- return this._context;
- },
- hint : function () {
- return this._hint;
- }
- };
-
-
- /**
- * Hint menu item based on MenuItem
- */
- KorAP.HintMenuItem = {
- create : function (params) {
- return Object.create(KorAP.MenuItem)
- .upgradeTo(KorAP.HintMenuItem)
- ._init(params);
- },
- _init : function (params) {
- if (params[0] === undefined ||
- params[1] === undefined)
- throw new Error("Missing parameters");
-
- this._name = params[0];
- this._action = params[1];
- this._lcField = ' ' + this._name.toLowerCase();
-
- if (params.length > 2) {
- this._desc = params[2];
- this._lcField += " " + this._desc.toLowerCase();
- };
-
- return this;
- },
- content : function (content) {
- if (arguments.length === 1) {
- this._content = content;
- };
- return this._content;
- },
- onclick : function () {
- var m = this.menu();
- var h = m.hint();
- m.hide();
-
- // Update input field
- var input = h.inputField();
- input.insert(this._action);
- input.update();
-
- h.active = false;
- h.show(true);
- },
- name : function () {
- return this._name;
- },
- action : function () {
- return this._action;
- },
- desc : function () {
- return this._desc;
- },
- element : function () {
- // already defined
- if (this._element !== undefined)
- return this._element;
-
- // Create list item
- var li = document.createElement("li");
-
- if (this.onclick !== undefined) {
- li["onclick"] = this.onclick.bind(this);
- };
-
- // Create title
- var name = document.createElement("span");
- name.appendChild(document.createTextNode(this._name));
-
- li.appendChild(name);
-
- // Create description
- if (this._desc !== undefined) {
- var desc = document.createElement("span");
- desc.classList.add('desc');
- desc.appendChild(document.createTextNode(this._desc));
- li.appendChild(desc);
- };
- return this._element = li;
- }
- };
-
- KorAP.HintMenuPrefix = {
- create : function (params) {
- return Object.create(KorAP.MenuPrefix).upgradeTo(KorAP.HintMenuPrefix)._init(params);
- },
- onclick : function () {
- var m = this.menu();
- var h = m.hint();
- m.hide();
-
- h.inputField().insert(this.value());
- h.active = false;
- }
- };
-
-
- /**
- * Return keycode based on event
- */
- function _codeFromEvent (e) {
- if ((e.charCode) && (e.keyCode==0))
- return e.charCode
- return e.keyCode;
- };
-
-}(this.KorAP));
+});
diff --git a/dev/js/src/hint/contextanalyzer.js b/dev/js/src/hint/contextanalyzer.js
new file mode 100644
index 0000000..bdbf885
--- /dev/null
+++ b/dev/js/src/hint/contextanalyzer.js
@@ -0,0 +1,23 @@
+/**
+ * Regex object for checking the context of the hint
+ */
+define({
+ create : function (regex) {
+ return Object.create(this)._init(regex);
+ },
+ _init : function (regex) {
+ try {
+ this._regex = new RegExp(regex);
+ }
+ catch (e) {
+ KorAP.log(0, e);
+ return;
+ };
+ return this;
+ },
+ test : function (text) {
+ if (!this._regex.exec(text))
+ return;
+ return RegExp.$1;
+ }
+});
diff --git a/dev/js/src/hint/input.js b/dev/js/src/hint/input.js
new file mode 100644
index 0000000..b1c6632
--- /dev/null
+++ b/dev/js/src/hint/input.js
@@ -0,0 +1,103 @@
+// Input field for queries
+define({
+ create : function (element) {
+ return Object.create(this)._init(element);
+ },
+
+ _init : function (element) {
+ this._element = element;
+
+ // Create mirror for searchField
+ if ((this._mirror = document.getElementById("searchMirror")) === null) {
+ this._mirror = document.createElement("div");
+ this._mirror.setAttribute("id", "searchMirror");
+ this._mirror.appendChild(document.createElement("span"));
+ this._container = this._mirror.appendChild(document.createElement("div"));
+ this._mirror.style.height = "0px";
+ document.getElementsByTagName("body")[0].appendChild(this._mirror);
+ };
+
+ // Update position of the mirror
+ var that = this;
+ var repos = function () {
+ that.reposition();
+ };
+ window.addEventListener('resize', repos);
+ this._element.addEventListener('onfocus', repos);
+ that.reposition();
+
+ return this;
+ },
+
+ rightPos : function () {
+ var box = this._mirror.firstChild.getBoundingClientRect();
+ return box.right - box.left;
+ },
+
+ mirror : function () {
+ return this._mirror;
+ },
+
+ container : function () {
+ return this._container;
+ },
+
+ element : function () {
+ return this._element;
+ },
+
+ value : function () {
+ return this._element.value;
+ },
+
+ update : function () {
+ this._mirror.firstChild.textContent = this.split()[0];
+ this._container.style.left = this.rightPos() + 'px';
+ },
+
+ insert : function (text) {
+ var splittedText = this.split();
+ var s = this._element;
+ s.value = splittedText[0] + text + splittedText[1];
+ s.selectionStart = (splittedText[0] + text).length;
+ s.selectionEnd = s.selectionStart;
+ this._mirror.firstChild.textContent = splittedText[0] + text;
+ },
+
+ // Return two substrings, splitted at current cursor position
+ split : function () {
+ var s = this._element;
+ var value = s.value;
+ var start = s.selectionStart;
+ return new Array(
+ value.substring(0, start),
+ value.substring(start, value.length)
+ );
+ },
+
+ // Position the input mirror directly below the input box
+ reposition : function () {
+ var inputClientRect = this._element.getBoundingClientRect();
+ var inputStyle = window.getComputedStyle(this._element, null);
+
+ var bodyClientRect =
+ document.getElementsByTagName('body')[0].getBoundingClientRect();
+
+ // Reset position
+ var mirrorStyle = this._mirror.style;
+ mirrorStyle.left = inputClientRect.left + "px";
+ mirrorStyle.top = (inputClientRect.bottom - bodyClientRect.top) + "px";
+ mirrorStyle.width = inputStyle.getPropertyValue("width");
+
+ // These may be relevant in case of media depending css
+ mirrorStyle.paddingLeft = inputStyle.getPropertyValue("padding-left");
+ mirrorStyle.marginLeft = inputStyle.getPropertyValue("margin-left");
+ mirrorStyle.borderLeftWidth = inputStyle.getPropertyValue("border-left-width");
+ mirrorStyle.borderLeftStyle = inputStyle.getPropertyValue("border-left-style");
+ mirrorStyle.fontSize = inputStyle.getPropertyValue("font-size");
+ mirrorStyle.fontFamily = inputStyle.getPropertyValue("font-family");
+ },
+ context : function () {
+ return this.split()[0];
+ }
+});
diff --git a/dev/js/src/hint/item.js b/dev/js/src/hint/item.js
new file mode 100644
index 0000000..f23cabc
--- /dev/null
+++ b/dev/js/src/hint/item.js
@@ -0,0 +1,85 @@
+/**
+ * Hint menu item based on MenuItem
+ */
+define(['menu/item'], function (itemClass) {
+ return {
+ create : function (params) {
+ return Object.create(itemClass)
+ .upgradeTo(this)
+ ._init(params);
+ },
+ _init : function (params) {
+ if (params[0] === undefined ||
+ params[1] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._action = params[1];
+ this._lcField = ' ' + this._name.toLowerCase();
+
+ if (params.length > 2) {
+ this._desc = params[2];
+ this._lcField += " " + this._desc.toLowerCase();
+ };
+
+ return this;
+ },
+
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+
+ onclick : function () {
+ var m = this.menu();
+ var h = m.hint();
+ m.hide();
+
+ // Update input field
+ var input = h.inputField();
+ input.insert(this._action);
+ input.update();
+
+ h.active = false;
+ h.show(true);
+ },
+ name : function () {
+ return this._name;
+ },
+ action : function () {
+ return this._action;
+ },
+ desc : function () {
+ return this._desc;
+ },
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+
+ if (this.onclick !== undefined) {
+ li["onclick"] = this.onclick.bind(this);
+ };
+
+ // Create title
+ var name = document.createElement("span");
+ name.appendChild(document.createTextNode(this._name));
+
+ li.appendChild(name);
+
+ // Create description
+ if (this._desc !== undefined) {
+ var desc = document.createElement("span");
+ desc.classList.add('desc');
+ desc.appendChild(document.createTextNode(this._desc));
+ li.appendChild(desc);
+ };
+ return this._element = li;
+ }
+ };
+});
diff --git a/dev/js/src/hint/menu.js b/dev/js/src/hint/menu.js
new file mode 100644
index 0000000..c579945
--- /dev/null
+++ b/dev/js/src/hint/menu.js
@@ -0,0 +1,36 @@
+/**
+ * Hint menu
+ */
+define(['menu', 'hint/item', 'hint/prefix'], function (menuClass, itemClass, prefixClass) {
+ return {
+ create : function (hint, context, params) {
+ var obj = Object.create(menuClass)
+ .upgradeTo(this)
+ ._init(itemClass, prefixClass, params);
+ obj._context = context;
+ obj._element.classList.add('hint');
+ obj._hint = hint;
+
+ // This is only domspecific
+ obj.element().addEventListener('blur', function (e) {
+ this.menu.hide();
+ });
+
+ // Focus on input field on hide
+ obj.onHide = function () {
+ var input = this._hint.inputField();
+ input.container().classList.remove('active');
+ input.element().focus();
+ };
+
+ return obj;
+ },
+ // Todo: Is this necessary?
+ context : function () {
+ return this._context;
+ },
+ hint : function () {
+ return this._hint;
+ }
+ };
+});
diff --git a/dev/js/src/hint/prefix.js b/dev/js/src/hint/prefix.js
new file mode 100644
index 0000000..84f785a
--- /dev/null
+++ b/dev/js/src/hint/prefix.js
@@ -0,0 +1,15 @@
+define(['menu/prefix'], function (prefixClass) {
+ return {
+ create : function (params) {
+ return Object.create(prefixClass).upgradeTo(this)._init(params);
+ },
+ onclick : function () {
+ var m = this.menu();
+ var h = m.hint();
+ m.hide();
+
+ h.inputField().insert(this.value());
+ h.active = false;
+ }
+ };
+});
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index ecaad6f..66bc081 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -1,42 +1,17 @@
-/**
- * These are utility functions for the frontend
- */
-
-// Add toggleClass method similar to jquery
-HTMLElement.prototype.toggleClass = function (c1, c2) {
- var cl = this.classList;
- if (cl.contains(c1)) {
- cl.add(c2);
- cl.remove(c1);
- }
- else {
- cl.remove(c2);
- cl.add(c1);
- };
-};
-
-
-// Don't let events bubble up
-if (Event.halt === undefined) {
- // Don't let events bubble up
- Event.prototype.halt = function () {
- this.stopPropagation();
- this.preventDefault();
- };
-};
-
-var KorAP = KorAP || {};
-
-
-(function (KorAP) {
- "use strict";
-
-
- /**
- * Initialize user interface elements
- */
- KorAP.init = function () {
- var obj = Object.create(KorAP.init);
+define([
+ 'match',
+ 'hint',
+ 'vc',
+ 'tutorial',
+ 'lib/domReady',
+ 'util'
+], function (matchClass,
+ hintClass,
+ vcClass,
+ tutClass,
+ domReady) {
+ domReady(function (event) {
+ var obj = {};
/**
* Add actions to match entries
@@ -49,13 +24,13 @@
inactiveLi[i].addEventListener('click', function (e) {
if (this._match !== undefined)
this._match.open();
- else
- KorAP.Match.create(this).open();
+ else {
+ matchClass.create(this).open();
+ };
e.halt();
});
};
-
/**
* Toggle the alignment (left <=> right)
*/
@@ -97,28 +72,29 @@
input.parentNode.insertBefore(vcname, input);
vcname.onclick = function () {
- var vc = KorAP.VirtualCollection.render(vcExample);
+ var vc = vcClass.render(vcExample);
var view = document.getElementById('vc-view');
view.appendChild(vc.element());
};
};
+
/**
* Init Tutorial view
*/
- obj.tutorial = KorAP.Tutorial.create(
+ obj.tutorial = tutClass.create(
document.getElementById('view-tutorial')
);
+
/**
* Init hint helper
* has to be final because of
* reposition
*/
-// Todo: Pass an element, so this works with
-// tutorial pages as well!
- obj.hint = KorAP.Hint.create();
+ // Todo: Pass an element, so this works with
+ // tutorial pages as well!
+ obj.hint = hintClass.create();
return obj;
- };
-
-}(this.KorAP));
+ });
+});
diff --git a/dev/js/src/match.js b/dev/js/src/match.js
index 4e3786b..beb4bca 100644
--- a/dev/js/src/match.js
+++ b/dev/js/src/match.js
@@ -4,46 +4,31 @@
*
* @author Nils Diewald
*/
-// require menu.js, dagre
/*
* - Highlight (at least mark as bold) the match
* - Scroll to match vertically per default
*/
-var KorAP = KorAP || {};
-
-(function (KorAP) {
- "use strict";
-
- var svgXmlns = "http://www.w3.org/2000/svg";
-
- // Default log message
- KorAP.log = KorAP.log || function (type, msg) {
- console.log(type + ": " + msg);
- };
+define([
+ 'match/infolayer',
+ 'match/info',
+ 'util'
+], function (infoLayerClass,
+ infoClass) {
// Localization values
- var loc = (KorAP.Locale = KorAP.Locale || {} );
+ var loc = KorAP.Locale;
loc.ADDTREE = loc.ADDTREE || 'Add tree view';
loc.SHOWINFO = loc.SHOWINFO || 'Show information';
loc.CLOSE = loc.CLOSE || 'Close';
-
- KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
- KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
- KorAP._matchTerms = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
-
- // API requests
- KorAP.API = KorAP.API || {};
-
- // TODO: Make this async
- KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
- KorAP.log(0, 'KorAP.API.getMatchInfo() not implemented')
- return {};
- };
+
+ // KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
+ // KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+ var _matchTerms = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
/**
* Match object
*/
- KorAP.Match = {
+ return {
/**
* Create a new annotation object.
@@ -51,7 +36,7 @@
* Supported types are 'spans', 'tokens' and 'rels'.
*/
create : function (match) {
- return Object.create(KorAP.Match)._init(match);
+ return Object.create(this)._init(match);
},
/**
@@ -87,8 +72,8 @@
else {
// Iterate over allowed match terms
- for (var i in KorAP._matchTerms) {
- var term = KorAP._matchTerms[i];
+ for (var i in _matchTerms) {
+ var term = _matchTerms[i];
if (match[term] !== undefined) {
this[term] = match[term];
}
@@ -97,7 +82,7 @@
}
};
};
-
+
this._available = {
tokens : [],
spans : [],
@@ -110,7 +95,7 @@
// Create info layer objects
try {
- var layer = KorAP.InfoLayer.create(term);
+ var layer = infoLayerClass.create(term);
this._available[layer.type].push(layer);
}
catch (e) {
@@ -178,7 +163,7 @@
.appendChild(document.createTextNode(loc.CLOSE));
close.classList.add('close');
close.setAttribute('title', loc.CLOSE);
-
+
// Add info button
var info = document.createElement('li');
info.appendChild(document.createElement('span'))
@@ -227,7 +212,7 @@
// Create match info
if (this._info === undefined)
- this._info = KorAP.MatchInfo.create(this);
+ this._info = infoClass.create(this);
// There is an element to append
if (this._element === undefined ||
@@ -251,812 +236,4 @@
return this._element;
}
};
-
-
-
- /**
- * Information about a match.
- */
- KorAP.MatchInfo = {
-
- /**
- * Create new object
- */
- create : function (match) {
- return Object.create(KorAP.MatchInfo)._init(match);
- },
-
- /**
- * Initialize object
- */
- _init : function (match) {
- this._match = match;
- this.opened = false;
- return this;
- },
-
- /**
- * Get match object
- */
- match : function () {
- return this._match;
- },
-
- toggle : function () {
- if (this.opened == true) {
- this._match.element().children[0].removeChild(
- this.element()
- );
- this.opened = false;
- }
- else {
- // Append element to match
- this._match.element().children[0].appendChild(
- this.element()
- );
- this.opened = true;
- };
-
- return this.opened;
- },
-
-
- /**
- * Retrieve and parse snippet for table representation
- */
- getTable : function (tokens, cb) {
- var focus = [];
-
- // Get all tokens
- if (tokens === undefined) {
- focus = this._match.getTokens();
- }
-
- // Get only some tokens
- else {
-
- // Push newly to focus array
- for (var i = 0; i < tokens.length; i++) {
- var term = tokens[i];
- try {
- // Create info layer objects
- var layer = KorAP.InfoLayer.create(term);
- layer.type = "tokens";
- focus.push(layer);
- }
- catch (e) {
- continue;
- };
- };
- };
-
- // No tokens chosen
- if (focus.length == 0)
- cb(null);
-
- // Get info (may be cached)
- // TODO: Async
- KorAP.API.getMatchInfo(
- this._match,
- { 'spans' : false, 'layer' : focus },
-
- // Callback for retrieval
- function (matchResponse) {
- // Get snippet from match info
- if (matchResponse["snippet"] !== undefined) {
- this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
- cb(this._table);
- };
- }.bind(this)
- );
-
-/*
- // Todo: Store the table as a hash of the focus
- return null;
-*/
- },
-
-
- /**
- * Retrieve and parse snippet for tree representation
- */
- getTree : function (foundry, layer, cb) {
- var focus = [];
-
- // TODO: Support and cache multiple trees
-
- KorAP.API.getMatchInfo(
- this._match, {
- 'spans' : true,
- 'foundry' : foundry,
- 'layer' : layer
- },
- function (matchResponse) {
- // Get snippet from match info
- if (matchResponse["snippet"] !== undefined) {
- // Todo: This should be cached somehow
- cb(KorAP.MatchTree.create(matchResponse["snippet"]));
- }
- else {
- cb(null);
- };
- }.bind(this)
- );
- },
-
- /**
- * Destroy this match information view.
- */
- destroy : function () {
-
- // Remove circular reference
- if (this._treeMenu !== undefined)
- delete this._treeMenu["info"];
-
- this._treeMenu.destroy();
- this._treeMenu = undefined;
- this._match = undefined;
-
- // Element destroy
- },
-
-
- /**
- * Add a new tree view to the list
- */
- addTree : function (foundry, layer, cb) {
- var matchtree = document.createElement('div');
- matchtree.classList.add('matchtree');
-
- var h6 = matchtree.appendChild(document.createElement('h6'));
- h6.appendChild(document.createElement('span'))
- .appendChild(document.createTextNode(foundry));
- h6.appendChild(document.createElement('span'))
- .appendChild(document.createTextNode(layer));
-
- var tree = matchtree.appendChild(
- document.createElement('div')
- );
-
- this._element.insertBefore(matchtree, this._element.lastChild);
-
- var close = tree.appendChild(document.createElement('em'));
- close.addEventListener(
- 'click', function (e) {
- matchtree.parentNode.removeChild(matchtree);
- e.halt();
- }
- );
-
- // Get tree data async
- this.getTree(foundry, layer, function (treeObj) {
- // Something went wrong - probably log!!!
- if (treeObj === null) {
- tree.appendChild(document.createTextNode('No data available.'));
- }
- else {
- tree.appendChild(treeObj.element());
- // Reposition the view to the center
- // (This may in a future release be a reposition
- // to move the root into the center or the actual
- // match)
- treeObj.center();
- }
-
- if (cb !== undefined)
- cb(treeObj);
- });
- },
-
- /**
- * Create match information view.
- */
- element : function () {
-
- if (this._element !== undefined)
- return this._element;
-
- // Create info table
- var info = document.createElement('div');
- info.classList.add('matchinfo');
-
- // Append default table
- var matchtable = document.createElement('div');
- matchtable.classList.add('matchtable');
- info.appendChild(matchtable);
-
- // Create the table asynchronous
- this.getTable(undefined, function (table) {
- if (table !== null) {
- matchtable.appendChild(table.element());
- };
- });
-
- // Get spans
- var spanLayers = this._match.getSpans().sort(
- function (a, b) {
- if (a.foundry < b.foundry) {
- return -1;
- }
- else if (a.foundry > b.foundry) {
- return 1;
- }
- else if (a.layer < b.layer) {
- return -1;
- }
- else if (a.layer > b.layer) {
- return 1;
- };
- return 0;
- });
-
- var menuList = [];
-
- // Show tree views
- for (var i = 0; i < spanLayers.length; i++) {
- var span = spanLayers[i];
-
- // Add foundry/layer to menu list
- menuList.push([
- span.foundry + '/' + span.layer,
- span.foundry,
- span.layer
- ]);
- };
-
- // Create tree menu
- var treemenu = this.treeMenu(menuList);
- var span = info.appendChild(document.createElement('p'));
- span.classList.add('addtree');
- span.appendChild(document.createTextNode(loc.ADDTREE));
-
- var treeElement = treemenu.element();
- span.appendChild(treeElement);
-
- span.addEventListener('click', function (e) {
- treemenu.show('');
- treemenu.focus();
- });
-
- this._element = info;
-
- return info;
-
- },
-
-
- /**
- * Get tree menu.
- * There is only one menu rendered
- * - no matter how many trees exist
- */
- treeMenu : function (list) {
- if (this._treeMenu !== undefined)
- return this._treeMenu;
-
- return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
- }
- };
-
-
-
- /**
- *
- * Alternatively pass a string as <tt>base/s=span</tt>
- *
- * @param foundry
- */
- KorAP.InfoLayer = {
- create : function (foundry, layer, type) {
- return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
- },
- _init : function (foundry, layer, type) {
- if (foundry === undefined)
- throw new Error("Missing parameters");
-
- if (layer === undefined) {
- if (KorAP._AvailableRE.exec(foundry)) {
- this.foundry = RegExp.$1;
- this.layer = RegExp.$2;
- this.type = RegExp.$3;
- }
- else {
- throw new Error("Missing parameters");
- };
- }
- else {
- this.foundry = foundry;
- this.layer = layer;
- this.type = type;
- };
-
- if (this.type === undefined)
- this.type = 'tokens';
-
- return this;
- }
- };
-
-
- KorAP.MatchTable = {
- create : function (snippet) {
- return Object.create(KorAP.MatchTable)._init(snippet);
- },
- _init : function (snippet) {
- // Create html for traversal
- var html = document.createElement("div");
- html.innerHTML = snippet;
-
- this._pos = 0;
- this._token = [];
- this._info = [];
- this._foundry = {};
- this._layer = {};
-
- // Parse the snippet
- this._parse(html.childNodes);
-
- html.innerHTML = '';
- return this;
- },
-
- length : function () {
- return this._pos;
- },
-
- getToken : function (pos) {
- if (pos === undefined)
- return this._token;
- return this._token[pos];
- },
-
- getValue : function (pos, foundry, layer) {
- return this._info[pos][foundry + '/' + layer]
- },
-
- getLayerPerFoundry : function (foundry) {
- return this._foundry[foundry]
- },
-
- getFoundryPerLayer : function (layer) {
- return this._layer[layer];
- },
-
- // Parse the snippet
- _parse : function (children) {
-
- // Get all children
- for (var i in children) {
- var c = children[i];
-
- // Create object on position unless it exists
- if (this._info[this._pos] === undefined)
- this._info[this._pos] = {};
-
- // Store at position in foundry/layer as array
- var found = this._info[this._pos];
-
- // Element with title
- if (c.nodeType === 1) {
- if (c.getAttribute("title") &&
- KorAP._TermRE.exec(c.getAttribute("title"))) {
-
- // Fill position with info
- var foundry, layer, value;
- if (RegExp.$2) {
- foundry = RegExp.$1;
- layer = RegExp.$2;
- }
- else {
- foundry = "base";
- layer = RegExp.$1
- };
-
- value = RegExp.$3;
-
- if (found[foundry + "/" + layer] === undefined)
- found[foundry + "/" + layer] = [];
-
- // Push value to foundry/layer at correct position
- found[foundry + "/" + layer].push(RegExp.$3);
-
- // Set foundry
- if (this._foundry[foundry] === undefined)
- this._foundry[foundry] = {};
- this._foundry[foundry][layer] = 1;
-
- // Set layer
- if (this._layer[layer] === undefined)
- this._layer[layer] = {};
- this._layer[layer][foundry] = 1;
- };
-
- // depth search
- if (c.hasChildNodes())
- this._parse(c.childNodes);
- }
-
- // Leaf node
- // store string on position and go to next string
- else if (c.nodeType === 3) {
- if (c.nodeValue.match(/[a-z0-9]/i))
- this._token[this._pos++] = c.nodeValue;
- };
- };
-
- delete this._info[this._pos];
- },
-
-
- /**
- * Get HTML table view of annotations.
- */
- element : function () {
- if (this._element !== undefined)
- return this._element;
-
- // First the legend table
- var d = document;
- var table = d.createElement('table');
-
- // Single row in head
- var tr = table.appendChild(d.createElement('thead'))
- .appendChild(d.createElement('tr'));
-
- // Add cell to row
- var addCell = function (type, name) {
- var c = this.appendChild(d.createElement(type))
- if (name === undefined)
- return c;
-
- if (name instanceof Array) {
- for (var n = 0; n < name.length; n++) {
- c.appendChild(d.createTextNode(name[n]));
- if (n !== name.length - 1) {
- c.appendChild(d.createElement('br'));
- };
- };
- }
- else {
- c.appendChild(d.createTextNode(name));
- };
- };
-
- tr.addCell = addCell;
-
- // Add header information
- tr.addCell('th', 'Foundry');
- tr.addCell('th', 'Layer');
-
- // Add tokens
- for (var i in this._token) {
- tr.addCell('th', this.getToken(i));
- };
-
- var tbody = table.appendChild(
- d.createElement('tbody')
- );
-
- var foundryList = Object.keys(this._foundry).sort();
-
- for (var f = 0; f < foundryList.length; f++) {
- var foundry = foundryList[f];
- var layerList =
- Object.keys(this._foundry[foundry]).sort();
-
- for (var l = 0; l < layerList.length; l++) {
- var layer = layerList[l];
- tr = tbody.appendChild(
- d.createElement('tr')
- );
- tr.setAttribute('tabindex', 0);
- tr.addCell = addCell;
-
- tr.addCell('th', foundry);
- tr.addCell('th', layer);
-
- for (var v = 0; v < this.length(); v++) {
- tr.addCell(
- 'td',
- this.getValue(v, foundry, layer)
- );
- };
- };
- };
-
- return this._element = table;
- }
- };
-
-
- /**
- * Visualize span annotations as a tree using Dagre.
- */
- KorAP.MatchTree = {
-
- create : function (snippet) {
- return Object.create(KorAP.MatchTree)._init(snippet);
- },
-
- nodes : function () {
- return this._next;
- },
-
- _addNode : function (id, obj) {
- obj["width"] = 55;
- obj["height"] = 20;
- this._graph.setNode(id, obj)
- },
-
- _addEdge : function (src, target) {
- this._graph.setEdge(src, target);
- },
-
- _init : function (snippet) {
- this._next = new Number(0);
-
- // Create html for traversal
- var html = document.createElement("div");
- html.innerHTML = snippet;
- var g = new dagre.graphlib.Graph({
- "directed" : true
- });
- g.setGraph({
- "nodesep" : 35,
- "ranksep" : 15,
- "marginx" : 40,
- "marginy" : 10
- });
- g.setDefaultEdgeLabel({});
-
- this._graph = g;
-
- // This is a new root
- this._addNode(
- this._next++,
- { "class" : "root" }
- );
-
- // Parse nodes from root
- this._parse(0, html.childNodes);
-
- // Root node has only one child - remove
- if (g.outEdges(0).length === 1)
- g.removeNode(0);
-
- html = undefined;
- return this;
- },
-
- // Remove foundry and layer for labels
- _clean : function (title) {
- return title.replace(KorAP._TermRE, "$3");
- },
-
- // Parse the snippet
- _parse : function (parent, children) {
- for (var i in children) {
- var c = children[i];
-
- // Element node
- if (c.nodeType == 1) {
-
- // Get title from html
- if (c.getAttribute("title")) {
- var title = this._clean(c.getAttribute("title"));
-
- // Add child node
- var id = this._next++;
-
- this._addNode(id, {
- "class" : "middle",
- "label" : title
- });
- this._addEdge(parent, id);
-
- // Check for next level
- if (c.hasChildNodes())
- this._parse(id, c.childNodes);
- }
-
- // Step further
- else if (c.hasChildNodes())
- this._parse(parent, c.childNodes);
- }
-
- // Text node
- else if (c.nodeType == 3)
-
- if (c.nodeValue.match(/[-a-z0-9]/i)) {
-
- // Add child node
- var id = this._next++;
- this._addNode(id, {
- "class" : "leaf",
- "label" : c.nodeValue
- });
-
- this._addEdge(parent, id);
- };
- };
- return this;
- },
-
- /**
- * Center the viewport of the canvas
- */
- center : function () {
- if (this._element === undefined)
- return;
-
- var treeDiv = this._element.parentNode;
-
- var cWidth = parseFloat(window.getComputedStyle(this._element).width);
- var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
- // Reposition:
- if (cWidth > treeWidth) {
- var scrollValue = (cWidth - treeWidth) / 2;
- treeDiv.scrollLeft = scrollValue;
- };
- },
-
- // Get element
- element : function () {
- if (this._element !== undefined)
- return this._element;
-
- var g = this._graph;
-
- dagre.layout(g);
-
- var canvas = document.createElementNS(svgXmlns, 'svg');
- this._element = canvas;
-
- canvas.setAttribute('height', g.graph().height);
- canvas.setAttribute('width', g.graph().width);
-
- // Create edges
- g.edges().forEach(
- function (e) {
- var src = g.node(e.v);
- var target = g.node(e.w);
- var p = document.createElementNS(svgXmlns, 'path');
- p.setAttributeNS(null, "d", _line(src, target));
- p.classList.add('edge');
- canvas.appendChild(p);
- });
-
- // Create nodes
- g.nodes().forEach(
- function (v) {
- v = g.node(v);
- var group = document.createElementNS(svgXmlns, 'g');
- group.classList.add(v.class);
-
- // Add node box
- var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
- rect.setAttributeNS(null, 'x', v.x - v.width / 2);
- rect.setAttributeNS(null, 'y', v.y - v.height / 2);
- rect.setAttributeNS(null, 'rx', 5);
- rect.setAttributeNS(null, 'ry', 5);
- rect.setAttributeNS(null, 'width', v.width);
- rect.setAttributeNS(null, 'height', v.height);
-
- if (v.class === 'root' && v.label === undefined) {
- rect.setAttributeNS(null, 'width', v.height);
- rect.setAttributeNS(null, 'x', v.x - v.height / 2);
- rect.setAttributeNS(null, 'class', 'empty');
- };
-
- // Add label
- if (v.label !== undefined) {
- var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
- text.setAttributeNS(null, 'x', v.x - v.width / 2);
- text.setAttributeNS(null, 'y', v.y - v.height / 2);
- text.setAttributeNS(
- null,
- 'transform',
- 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
- );
-
- var tspan = document.createElementNS(svgXmlns, 'tspan');
- tspan.appendChild(document.createTextNode(v.label));
- text.appendChild(tspan);
- };
-
- canvas.appendChild(group);
- }
- );
-
- return this._element;
- }
- };
-
- /**
- * Menu item for tree view choice.
- */
- KorAP.MatchTreeItem = {
- create : function (params) {
- return Object.create(KorAP.MenuItem)
- .upgradeTo(KorAP.MatchTreeItem)._init(params);
- },
- content : function (content) {
- if (arguments.length === 1) {
- this._content = content;
- };
- return this._content;
- },
-
- // The foundry attribute
- foundry : function () {
- return this._foundry;
- },
-
- // The layer attribute
- layer : function () {
- return this._layer;
- },
-
- // enter or click
- onclick : function (e) {
- var menu = this.menu();
- menu.hide();
- e.halt();
- if (menu.info() !== undefined)
- menu.info().addTree(this._foundry, this._layer);
- },
-
- _init : function (params) {
- if (params[0] === undefined)
- throw new Error("Missing parameters");
-
- this._name = params[0];
- this._foundry = params[1];
- this._layer = params[2];
- this._content = document.createTextNode(this._name);
- this._lcField = ' ' + this.content().textContent.toLowerCase();
- return this;
- }
- };
-
-
- /**
- * Menu to choose from for tree views.
- */
- KorAP.MatchTreeMenu = {
- create : function (info, params) {
- var obj = Object.create(KorAP.Menu)
- .upgradeTo(KorAP.MatchTreeMenu)
- ._init(KorAP.MatchTreeItem, undefined, params);
- obj.limit(6);
-
- obj._info = info;
-
- // This is only domspecific
- obj.element().addEventListener('blur', function (e) {
- this.menu.hide();
- });
-
- return obj;
- },
- info :function () {
- return this._info;
- }
- };
-
-
- // Create path for node connections
- function _line (src, target) {
- var x1 = src.x,
- y1 = src.y,
- x2 = target.x,
- y2 = target.y - target.height / 2;
-
- // c 0,0 -10,0
- return 'M ' + x1 + ',' + y1 + ' ' +
- 'C ' + x1 + ',' + y1 + ' ' +
- x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
- x2 + ',' + y2;
- };
-
-}(this.KorAP));
+});
diff --git a/dev/js/src/match/info.js b/dev/js/src/match/info.js
new file mode 100644
index 0000000..b24bed5
--- /dev/null
+++ b/dev/js/src/match/info.js
@@ -0,0 +1,290 @@
+ /**
+ * Information about a match.
+ */
+define(['match/infolayer','match/table','match/tree', 'match/treemenu', 'util'], function (infoLayerClass, matchTableClass, matchTreeClass, matchTreeMenuClass) {
+
+ // TODO: Make this async
+ KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
+ KorAP.log(0, 'KorAP.API.getMatchInfo() not implemented')
+ return {};
+ };
+
+ var loc = KorAP.Locale;
+
+ /**
+ * Create new object
+ */
+ return {
+ create : function (match) {
+ return Object.create(this)._init(match);
+ },
+
+ /**
+ * Initialize object
+ */
+ _init : function (match) {
+ this._match = match;
+ this.opened = false;
+ return this;
+ },
+
+ /**
+ * Get match object
+ */
+ match : function () {
+ return this._match;
+ },
+
+ toggle : function () {
+ if (this.opened == true) {
+ this._match.element().children[0].removeChild(
+ this.element()
+ );
+ this.opened = false;
+ }
+ else {
+ // Append element to match
+ this._match.element().children[0].appendChild(
+ this.element()
+ );
+ this.opened = true;
+ };
+
+ return this.opened;
+ },
+
+
+ /**
+ * Retrieve and parse snippet for table representation
+ */
+ getTable : function (tokens, cb) {
+ var focus = [];
+
+ // Get all tokens
+ if (tokens === undefined) {
+ focus = this._match.getTokens();
+ }
+
+ // Get only some tokens
+ else {
+
+ // Push newly to focus array
+ for (var i = 0; i < tokens.length; i++) {
+ var term = tokens[i];
+ try {
+ // Create info layer objects
+ var layer = infoLayerClass.create(term);
+ layer.type = "tokens";
+ focus.push(layer);
+ }
+ catch (e) {
+ continue;
+ };
+ };
+ };
+
+ // No tokens chosen
+ if (focus.length == 0)
+ cb(null);
+
+ // Get info (may be cached)
+ // TODO: Async
+ KorAP.API.getMatchInfo(
+ this._match,
+ { 'spans' : false, 'layer' : focus },
+
+ // Callback for retrieval
+ function (matchResponse) {
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ this._table = matchTableClass.create(matchResponse["snippet"]);
+ cb(this._table);
+ };
+ }.bind(this)
+ );
+
+ /*
+ // Todo: Store the table as a hash of the focus
+ return null;
+ */
+ },
+
+
+ /**
+ * Retrieve and parse snippet for tree representation
+ */
+ getTree : function (foundry, layer, cb) {
+ var focus = [];
+
+ // TODO: Support and cache multiple trees
+ KorAP.API.getMatchInfo(
+ this._match, {
+ 'spans' : true,
+ 'foundry' : foundry,
+ 'layer' : layer
+ },
+ function (matchResponse) {
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ // Todo: This should be cached somehow
+ cb(matchTreeClass.create(matchResponse["snippet"]));
+ }
+ else {
+ cb(null);
+ };
+ }.bind(this)
+ );
+ },
+
+ /**
+ * Destroy this match information view.
+ */
+ destroy : function () {
+
+ // Remove circular reference
+ if (this._treeMenu !== undefined)
+ delete this._treeMenu["info"];
+
+ this._treeMenu.destroy();
+ this._treeMenu = undefined;
+ this._match = undefined;
+
+ // Element destroy
+ },
+
+ /**
+ * Add a new tree view to the list
+ */
+ addTree : function (foundry, layer, cb) {
+ var matchtree = document.createElement('div');
+ matchtree.classList.add('matchtree');
+
+ var h6 = matchtree.appendChild(document.createElement('h6'));
+ h6.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(foundry));
+ h6.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(layer));
+
+ var tree = matchtree.appendChild(
+ document.createElement('div')
+ );
+
+ this._element.insertBefore(matchtree, this._element.lastChild);
+
+ var close = tree.appendChild(document.createElement('em'));
+ close.addEventListener(
+ 'click', function (e) {
+ matchtree.parentNode.removeChild(matchtree);
+ e.halt();
+ }
+ );
+
+ // Get tree data async
+ this.getTree(foundry, layer, function (treeObj) {
+ // Something went wrong - probably log!!!
+ if (treeObj === null) {
+ tree.appendChild(document.createTextNode('No data available.'));
+ }
+ else {
+ tree.appendChild(treeObj.element());
+ // Reposition the view to the center
+ // (This may in a future release be a reposition
+ // to move the root into the center or the actual
+ // match)
+ treeObj.center();
+ }
+
+ if (cb !== undefined)
+ cb(treeObj);
+ });
+ },
+
+ /**
+ * Create match information view.
+ */
+ element : function () {
+
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create info table
+ var info = document.createElement('div');
+ info.classList.add('matchinfo');
+
+ // Append default table
+ var matchtable = document.createElement('div');
+ matchtable.classList.add('matchtable');
+ info.appendChild(matchtable);
+
+ // Create the table asynchronous
+ this.getTable(undefined, function (table) {
+ if (table !== null) {
+ matchtable.appendChild(table.element());
+ };
+ });
+
+ // Get spans
+ var spanLayers = this._match.getSpans().sort(
+ function (a, b) {
+ if (a.foundry < b.foundry) {
+ return -1;
+ }
+ else if (a.foundry > b.foundry) {
+ return 1;
+ }
+ else if (a.layer < b.layer) {
+ return -1;
+ }
+ else if (a.layer > b.layer) {
+ return 1;
+ };
+ return 0;
+ });
+
+ var menuList = [];
+
+ // Show tree views
+ for (var i = 0; i < spanLayers.length; i++) {
+ var span = spanLayers[i];
+
+ // Add foundry/layer to menu list
+ menuList.push([
+ span.foundry + '/' + span.layer,
+ span.foundry,
+ span.layer
+ ]);
+ };
+
+ // Create tree menu
+ var treemenu = this.treeMenu(menuList);
+ var span = info.appendChild(document.createElement('p'));
+ span.classList.add('addtree');
+ span.appendChild(document.createTextNode(loc.ADDTREE));
+
+ var treeElement = treemenu.element();
+ span.appendChild(treeElement);
+
+ span.addEventListener('click', function (e) {
+ treemenu.show('');
+ treemenu.focus();
+ });
+
+ this._element = info;
+
+ return info;
+ },
+
+
+ /**
+ * Get tree menu.
+ * There is only one menu rendered
+ * - no matter how many trees exist
+ */
+ treeMenu : function (list) {
+ if (this._treeMenu !== undefined)
+ return this._treeMenu;
+
+ return this._treeMenu = matchTreeMenuClass.create(this, list);
+ }
+ };
+});
diff --git a/dev/js/src/match/infolayer.js b/dev/js/src/match/infolayer.js
new file mode 100644
index 0000000..dbd93f9
--- /dev/null
+++ b/dev/js/src/match/infolayer.js
@@ -0,0 +1,41 @@
+/**
+ *
+ * Alternatively pass a string as <tt>base/s=span</tt>
+ *
+ * @param foundry
+ */
+define(function () {
+ var _AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
+
+ return {
+ create : function (foundry, layer, type) {
+ return Object.create(this)._init(foundry, layer, type);
+ },
+ _init : function (foundry, layer, type) {
+ if (foundry === undefined)
+ throw new Error("Missing parameters");
+
+ if (layer === undefined) {
+ if (_AvailableRE.exec(foundry)) {
+ this.foundry = RegExp.$1;
+ this.layer = RegExp.$2;
+ this.type = RegExp.$3;
+ }
+ else {
+ throw new Error("Missing parameters");
+ };
+ }
+ else {
+ this.foundry = foundry;
+ this.layer = layer;
+ this.type = type;
+ };
+
+ if (this.type === undefined)
+ this.type = 'tokens';
+
+ return this;
+ }
+ };
+});
+
diff --git a/dev/js/src/match/table.js b/dev/js/src/match/table.js
new file mode 100644
index 0000000..7eba8a0
--- /dev/null
+++ b/dev/js/src/match/table.js
@@ -0,0 +1,193 @@
+define(function () {
+ var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+
+ return {
+ create : function (snippet) {
+ return Object.create(this)._init(snippet);
+ },
+ _init : function (snippet) {
+ // Create html for traversal
+ var html = document.createElement("div");
+ html.innerHTML = snippet;
+
+ this._pos = 0;
+ this._token = [];
+ this._info = [];
+ this._foundry = {};
+ this._layer = {};
+
+ // Parse the snippet
+ this._parse(html.childNodes);
+
+ html.innerHTML = '';
+ return this;
+ },
+
+ length : function () {
+ return this._pos;
+ },
+
+ getToken : function (pos) {
+ if (pos === undefined)
+ return this._token;
+ return this._token[pos];
+ },
+
+ getValue : function (pos, foundry, layer) {
+ return this._info[pos][foundry + '/' + layer]
+ },
+
+ getLayerPerFoundry : function (foundry) {
+ return this._foundry[foundry]
+ },
+
+ getFoundryPerLayer : function (layer) {
+ return this._layer[layer];
+ },
+
+ // Parse the snippet
+ _parse : function (children) {
+
+ // Get all children
+ for (var i in children) {
+ var c = children[i];
+
+ // Create object on position unless it exists
+ if (this._info[this._pos] === undefined)
+ this._info[this._pos] = {};
+
+ // Store at position in foundry/layer as array
+ var found = this._info[this._pos];
+
+ // Element with title
+ if (c.nodeType === 1) {
+ if (c.getAttribute("title") &&
+ _TermRE.exec(c.getAttribute("title"))) {
+
+ // Fill position with info
+ var foundry, layer, value;
+ if (RegExp.$2) {
+ foundry = RegExp.$1;
+ layer = RegExp.$2;
+ }
+ else {
+ foundry = "base";
+ layer = RegExp.$1
+ };
+
+ value = RegExp.$3;
+
+ if (found[foundry + "/" + layer] === undefined)
+ found[foundry + "/" + layer] = [];
+
+ // Push value to foundry/layer at correct position
+ found[foundry + "/" + layer].push(RegExp.$3);
+
+ // Set foundry
+ if (this._foundry[foundry] === undefined)
+ this._foundry[foundry] = {};
+ this._foundry[foundry][layer] = 1;
+
+ // Set layer
+ if (this._layer[layer] === undefined)
+ this._layer[layer] = {};
+ this._layer[layer][foundry] = 1;
+ };
+
+ // depth search
+ if (c.hasChildNodes())
+ this._parse(c.childNodes);
+ }
+
+ // Leaf node
+ // store string on position and go to next string
+ else if (c.nodeType === 3) {
+ if (c.nodeValue.match(/[a-z0-9]/i))
+ this._token[this._pos++] = c.nodeValue;
+ };
+ };
+
+ delete this._info[this._pos];
+ },
+
+
+ /**
+ * Get HTML table view of annotations.
+ */
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ // First the legend table
+ var d = document;
+ var table = d.createElement('table');
+
+ // Single row in head
+ var tr = table.appendChild(d.createElement('thead'))
+ .appendChild(d.createElement('tr'));
+
+ // Add cell to row
+ var addCell = function (type, name) {
+ var c = this.appendChild(d.createElement(type))
+ if (name === undefined)
+ return c;
+
+ if (name instanceof Array) {
+ for (var n = 0; n < name.length; n++) {
+ c.appendChild(d.createTextNode(name[n]));
+ if (n !== name.length - 1) {
+ c.appendChild(d.createElement('br'));
+ };
+ };
+ }
+ else {
+ c.appendChild(d.createTextNode(name));
+ };
+ };
+
+ tr.addCell = addCell;
+
+ // Add header information
+ tr.addCell('th', 'Foundry');
+ tr.addCell('th', 'Layer');
+
+ // Add tokens
+ for (var i in this._token) {
+ tr.addCell('th', this.getToken(i));
+ };
+
+ var tbody = table.appendChild(
+ d.createElement('tbody')
+ );
+
+ var foundryList = Object.keys(this._foundry).sort();
+
+ for (var f = 0; f < foundryList.length; f++) {
+ var foundry = foundryList[f];
+ var layerList =
+ Object.keys(this._foundry[foundry]).sort();
+
+ for (var l = 0; l < layerList.length; l++) {
+ var layer = layerList[l];
+ tr = tbody.appendChild(
+ d.createElement('tr')
+ );
+ tr.setAttribute('tabindex', 0);
+ tr.addCell = addCell;
+
+ tr.addCell('th', foundry);
+ tr.addCell('th', layer);
+
+ for (var v = 0; v < this.length(); v++) {
+ tr.addCell(
+ 'td',
+ this.getValue(v, foundry, layer)
+ );
+ };
+ };
+ };
+
+ return this._element = table;
+ }
+ };
+});
diff --git a/dev/js/src/match/tree.js b/dev/js/src/match/tree.js
new file mode 100644
index 0000000..b379b37
--- /dev/null
+++ b/dev/js/src/match/tree.js
@@ -0,0 +1,222 @@
+/**
+ * Visualize span annotations as a tree using Dagre.
+ */
+define(['lib/dagre'], function (dagre) {
+ "use strict";
+
+ var svgXmlns = "http://www.w3.org/2000/svg";
+ var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+
+ // Create path for node connections
+ function _line (src, target) {
+ var x1 = src.x,
+ y1 = src.y,
+ x2 = target.x,
+ y2 = target.y - target.height / 2;
+
+ // c 0,0 -10,0
+ return 'M ' + x1 + ',' + y1 + ' ' +
+ 'C ' + x1 + ',' + y1 + ' ' +
+ x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
+ x2 + ',' + y2;
+ };
+
+ return {
+ create : function (snippet) {
+ return Object.create(this)._init(snippet);
+ },
+
+ nodes : function () {
+ return this._next;
+ },
+
+ _addNode : function (id, obj) {
+ obj["width"] = 55;
+ obj["height"] = 20;
+ this._graph.setNode(id, obj)
+ },
+
+ _addEdge : function (src, target) {
+ this._graph.setEdge(src, target);
+ },
+
+ _init : function (snippet) {
+ this._next = new Number(0);
+
+ // Create html for traversal
+ var html = document.createElement("div");
+ html.innerHTML = snippet;
+ var g = new dagre.graphlib.Graph({
+ "directed" : true
+ });
+ g.setGraph({
+ "nodesep" : 35,
+ "ranksep" : 15,
+ "marginx" : 40,
+ "marginy" : 10
+ });
+ g.setDefaultEdgeLabel({});
+
+ this._graph = g;
+
+ // This is a new root
+ this._addNode(
+ this._next++,
+ { "class" : "root" }
+ );
+
+ // Parse nodes from root
+ this._parse(0, html.childNodes);
+
+ // Root node has only one child - remove
+ if (g.outEdges(0).length === 1)
+ g.removeNode(0);
+
+ html = undefined;
+ return this;
+ },
+
+ // Remove foundry and layer for labels
+ _clean : function (title) {
+ return title.replace(_TermRE, "$3");
+ },
+
+ // Parse the snippet
+ _parse : function (parent, children) {
+ for (var i in children) {
+ var c = children[i];
+
+ // Element node
+ if (c.nodeType == 1) {
+
+ // Get title from html
+ if (c.getAttribute("title")) {
+ var title = this._clean(c.getAttribute("title"));
+
+ // Add child node
+ var id = this._next++;
+
+ this._addNode(id, {
+ "class" : "middle",
+ "label" : title
+ });
+ this._addEdge(parent, id);
+
+ // Check for next level
+ if (c.hasChildNodes())
+ this._parse(id, c.childNodes);
+ }
+
+ // Step further
+ else if (c.hasChildNodes())
+ this._parse(parent, c.childNodes);
+ }
+
+ // Text node
+ else if (c.nodeType == 3)
+
+ if (c.nodeValue.match(/[-a-z0-9]/i)) {
+
+ // Add child node
+ var id = this._next++;
+ this._addNode(id, {
+ "class" : "leaf",
+ "label" : c.nodeValue
+ });
+
+ this._addEdge(parent, id);
+ };
+ };
+ return this;
+ },
+
+ /**
+ * Center the viewport of the canvas
+ */
+ center : function () {
+ if (this._element === undefined)
+ return;
+
+ var treeDiv = this._element.parentNode;
+
+ var cWidth = parseFloat(window.getComputedStyle(this._element).width);
+ var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
+ // Reposition:
+ if (cWidth > treeWidth) {
+ var scrollValue = (cWidth - treeWidth) / 2;
+ treeDiv.scrollLeft = scrollValue;
+ };
+ },
+
+ // Get element
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ var g = this._graph;
+
+ dagre.layout(g);
+
+ var canvas = document.createElementNS(svgXmlns, 'svg');
+ this._element = canvas;
+
+ canvas.setAttribute('height', g.graph().height);
+ canvas.setAttribute('width', g.graph().width);
+
+ // Create edges
+ g.edges().forEach(
+ function (e) {
+ var src = g.node(e.v);
+ var target = g.node(e.w);
+ var p = document.createElementNS(svgXmlns, 'path');
+ p.setAttributeNS(null, "d", _line(src, target));
+ p.classList.add('edge');
+ canvas.appendChild(p);
+ });
+
+ // Create nodes
+ g.nodes().forEach(
+ function (v) {
+ v = g.node(v);
+ var group = document.createElementNS(svgXmlns, 'g');
+ group.classList.add(v.class);
+
+ // Add node box
+ var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
+ rect.setAttributeNS(null, 'x', v.x - v.width / 2);
+ rect.setAttributeNS(null, 'y', v.y - v.height / 2);
+ rect.setAttributeNS(null, 'rx', 5);
+ rect.setAttributeNS(null, 'ry', 5);
+ rect.setAttributeNS(null, 'width', v.width);
+ rect.setAttributeNS(null, 'height', v.height);
+
+ if (v.class === 'root' && v.label === undefined) {
+ rect.setAttributeNS(null, 'width', v.height);
+ rect.setAttributeNS(null, 'x', v.x - v.height / 2);
+ rect.setAttributeNS(null, 'class', 'empty');
+ };
+
+ // Add label
+ if (v.label !== undefined) {
+ var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
+ text.setAttributeNS(null, 'x', v.x - v.width / 2);
+ text.setAttributeNS(null, 'y', v.y - v.height / 2);
+ text.setAttributeNS(
+ null,
+ 'transform',
+ 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
+ );
+
+ var tspan = document.createElementNS(svgXmlns, 'tspan');
+ tspan.appendChild(document.createTextNode(v.label));
+ text.appendChild(tspan);
+ };
+
+ canvas.appendChild(group);
+ }
+ );
+
+ return this._element;
+ }
+ };
+});
diff --git a/dev/js/src/match/treeitem.js b/dev/js/src/match/treeitem.js
new file mode 100644
index 0000000..f096861
--- /dev/null
+++ b/dev/js/src/match/treeitem.js
@@ -0,0 +1,49 @@
+define(['menu/item'], function (itemClass) {
+ /**
+ * Menu item for tree view choice.
+ */
+
+ return {
+ create : function (params) {
+ return Object.create(itemClass)
+ .upgradeTo(this)._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+
+ // The foundry attribute
+ foundry : function () {
+ return this._foundry;
+ },
+
+ // The layer attribute
+ layer : function () {
+ return this._layer;
+ },
+
+ // enter or click
+ onclick : function (e) {
+ var menu = this.menu();
+ menu.hide();
+ e.halt();
+ if (menu.info() !== undefined)
+ menu.info().addTree(this._foundry, this._layer);
+ },
+
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._foundry = params[1];
+ this._layer = params[2];
+ this._content = document.createTextNode(this._name);
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+ return this;
+ }
+ };
+});
diff --git a/dev/js/src/match/treemenu.js b/dev/js/src/match/treemenu.js
new file mode 100644
index 0000000..23341a4
--- /dev/null
+++ b/dev/js/src/match/treemenu.js
@@ -0,0 +1,26 @@
+ /**
+ * Menu to choose from for tree views.
+ */
+define(['menu', 'match/treeitem'], function (menuClass, itemClass) {
+ "use strict";
+
+ return {
+ create : function (info, params) {
+ var obj = Object.create(menuClass)
+ .upgradeTo(this)
+ ._init(itemClass, undefined, params);
+ obj.limit(6);
+ obj._info = info;
+
+ // This is only domspecific
+ obj.element().addEventListener('blur', function (e) {
+ this.menu.hide();
+ });
+
+ return obj;
+ },
+ info :function () {
+ return this._info;
+ }
+ };
+});
diff --git a/dev/js/src/menu.js b/dev/js/src/menu.js
index 78f9ea7..0d5e6ac 100644
--- a/dev/js/src/menu.js
+++ b/dev/js/src/menu.js
@@ -1,35 +1,35 @@
-var KorAP = KorAP || {};
-
/**
* Create scrollable drop-down menus.
*
* @author Nils Diewald
*/
-
/*
- * TODO: space is not a valid prefix!
+ * TODO: space is not a valid prefix!
*/
-(function (KorAP) {
- "use strict";
+define([
+ 'menu/item',
+ 'menu/prefix',
+ 'util'
+], function (defaultItemClass,
+ defaultPrefixClass) {
- // Don't let events bubble up
- if (Event.halt === undefined) {
- // Don't let events bubble up
- Event.prototype.halt = function () {
- this.stopPropagation();
- this.preventDefault();
- };
+ // Todo: This may not be necessary
+ // Default maximum number of menu items
+ var menuLimit = 8;
+
+ function _codeFromEvent (e) {
+ if (e.charCode && (e.keyCode == 0))
+ return e.charCode
+ return e.keyCode;
};
- // Default maximum number of menu items
- KorAP.menuLimit = 8;
/**
* List of items for drop down menu (complete).
* Only a sublist of the menu is filtered (live).
* Only a sublist of the filtered menu is visible (shown).
*/
- KorAP.Menu = {
+ return {
/**
* Create new Menu based on the action prefix
* and a list of menu items.
@@ -40,7 +40,7 @@
* @param {Array.<Array.<string>>} List of menu items
*/
create : function (params) {
- return Object.create(KorAP.Menu)._init(params);
+ return Object.create(this)._init(params);
},
/**
@@ -143,12 +143,12 @@
// Initialize list
_init : function (itemClass, prefixClass, params) {
var that = this;
- this._itemClass = itemClass;
+ this._itemClass = itemClass || defaultItemClass;
if (prefixClass !== undefined)
this._prefix = prefixClass.create();
else
- this._prefix = KorAP.MenuPrefix.create();
+ this._prefix = defaultPrefixClass.create();
this._prefix._menu = this;
@@ -188,14 +188,14 @@
// Initialize item list based on parameters
for (i in params) {
- var obj = itemClass.create(params[i]);
+ var obj = this._itemClass.create(params[i]);
// This may become circular
obj["_menu"] = this;
this._items.push(obj);
};
- this._limit = KorAP.menuLimit;
+ this._limit = menuLimit;
this._position = 0; // position in the active list
this._active = -1; // active item in the item list
this._reset();
@@ -654,318 +654,4 @@
this._element.removeChild(this._element.lastChild);
}
};
-
-
- /**
- * Item in the Dropdown menu
- */
- KorAP.MenuItem = {
-
- /**
- * Create a new MenuItem object.
- *
- * @constructor
- * @this {MenuItem}
- * @param {Array.<string>} An array object of name, action and
- * optionally a description
- */
- create : function (params) {
- return Object.create(KorAP.MenuItem)._init(params);
- },
-
- /**
- * Upgrade this object to another object,
- * while private data stays intact.
- *
- * @param {Object] An object with properties.
- */
- upgradeTo : function (props) {
- for (var prop in props) {
- this[prop] = props[prop];
- };
- return this;
- },
-
- content : function (content) {
- if (arguments.length === 1)
- this._content = document.createTextNode(content);
- return this._content;
- },
-
- lcField : function () {
- return this._lcField;
- },
-
- action : function (action) {
- if (arguments.length === 1)
- this._action = action;
- return this._action;
- },
-
- /**
- * Check or set if the item is active
- *
- * @param {boolean|null} State of activity
- */
- active : function (bool) {
- var cl = this.element().classList;
- if (bool === undefined)
- return cl.contains("active");
- else if (bool)
- cl.add("active");
- else
- cl.remove("active");
- },
-
- /**
- * Check or set if the item is
- * at the boundary of the menu
- * list
- *
- * @param {boolean|null} State of activity
- */
- noMore : function (bool) {
- var cl = this.element().classList;
- if (bool === undefined)
- return cl.contains("no-more");
- else if (bool)
- cl.add("no-more");
- else
- cl.remove("no-more");
- },
-
- /**
- * Get the document element of the menu item
- */
- element : function () {
- // already defined
- if (this._element !== undefined)
- return this._element;
-
- // Create list item
- var li = document.createElement("li");
-
- // Connect action
- if (this["onclick"] !== undefined) {
- li["onclick"] = this.onclick.bind(this);
- };
-
- // Append template
- li.appendChild(this.content());
-
- return this._element = li;
- },
-
- /**
- * Highlight parts of the item
- *
- * @param {string} Prefix string for highlights
- */
- highlight : function (prefix) {
- var children = this.element().childNodes;
- for (var i = children.length -1; i >= 0; i--) {
- this._highlight(children[i], prefix);
- };
- },
-
- // Highlight a certain substring of the menu item
- _highlight : function (elem, prefix) {
-
- if (elem.nodeType === 3) {
-
- var text = elem.nodeValue;
- var textlc = text.toLowerCase();
- var pos = textlc.indexOf(prefix);
- if (pos >= 0) {
-
- // First element
- if (pos > 0) {
- elem.parentNode.insertBefore(
- document.createTextNode(text.substr(0, pos)),
- elem
- );
- };
-
- // Second element
- var hl = document.createElement("mark");
- hl.appendChild(
- document.createTextNode(text.substr(pos, prefix.length))
- );
- elem.parentNode.insertBefore(hl, elem);
-
- // Third element
- var third = text.substr(pos + prefix.length);
- if (third.length > 0) {
- var thirdE = document.createTextNode(third);
- elem.parentNode.insertBefore(
- thirdE,
- elem
- );
- this._highlight(thirdE, prefix);
- };
-
- var p = elem.parentNode;
- p.removeChild(elem);
- };
- }
- else {
- var children = elem.childNodes;
- for (var i = children.length -1; i >= 0; i--) {
- this._highlight(children[i], prefix);
- };
- };
- },
-
-
- /**
- * Remove highlight of the menu item
- */
- lowlight : function () {
- var e = this.element();
-
- var marks = e.getElementsByTagName("mark");
- for (var i = marks.length - 1; i >= 0; i--) {
- // Create text node clone
- var x = document.createTextNode(
- marks[i].firstChild.nodeValue
- );
-
- // Replace with content
- marks[i].parentNode.replaceChild(
- x,
- marks[i]
- );
- };
-
- // Remove consecutive textnodes
- e.normalize();
- },
-
- // Initialize menu item
- _init : function (params) {
-
- if (params[0] === undefined)
- throw new Error("Missing parameters");
-
- this.content(params[0]);
-
- if (params.length === 2)
- this._action = params[1];
-
- this._lcField = ' ' + this.content().textContent.toLowerCase();
-
- return this;
- },
-
- /**
- * Return menu list.
- */
- menu : function () {
- return this._menu;
- }
- };
-
- KorAP.MenuPrefix = {
- create : function (params) {
- return Object.create(KorAP.MenuPrefix)._init();
- },
- _init : function () {
- this._string = '';
-
- // Add prefix span
- this._element = document.createElement('span');
- this._element.classList.add('pref');
- // Connect action
-
- if (this["onclick"] !== undefined)
- this._element["onclick"] = this.onclick.bind(this);
-
- return this;
- },
- _update : function () {
- this._element.innerHTML
- = this._string;
- },
-
- /**
- * Upgrade this object to another object,
- * while private data stays intact.
- *
- * @param {Object} An object with properties.
- */
- upgradeTo : function (props) {
- for (var prop in props) {
- this[prop] = props[prop];
- };
- return this;
- },
-
- active : function (bool) {
- var cl = this.element().classList;
- if (bool === undefined)
- return cl.contains("active");
- else if (bool)
- cl.add("active");
- else
- cl.remove("active");
- },
-
- element : function () {
- return this._element;
- },
-
- isSet : function () {
- return this._string.length > 0 ?
- true : false;
- },
-
- value : function (string) {
- if (arguments.length === 1) {
- this._string = string;
- this._update();
- };
- return this._string;
- },
-
- add : function (string) {
- this._string += string;
- this._update();
- },
-
- onclick : function () {},
-
- backspace : function () {
- if (this._string.length > 1) {
- this._string = this._string.substring(
- 0, this._string.length - 1
- );
- }
- else {
- this._string = '';
- };
-
- this._update();
- },
-
- /**
- * Return menu list.
- */
- menu : function () {
- return this._menu;
- }
- };
-
- function _codeFromEvent (e) {
- if (e.charCode && (e.keyCode == 0))
- return e.charCode
- return e.keyCode;
- };
-
-}(this.KorAP));
-
-/**
- * MenuItems may define:
- *
- * onclick: action happen on click and enter.
- * further: action happen on right arrow
- */
+});
diff --git a/dev/js/src/menu/item.js b/dev/js/src/menu/item.js
new file mode 100644
index 0000000..b80aa2a
--- /dev/null
+++ b/dev/js/src/menu/item.js
@@ -0,0 +1,213 @@
+/*
+ * MenuItems may define:
+ *
+ * onclick: action happen on click and enter.
+ * further: action happen on right arrow
+ */
+
+/**
+ * Item in the Dropdown menu
+ */
+define({
+ /**
+ * Create a new MenuItem object.
+ *
+ * @constructor
+ * @this {MenuItem}
+ * @param {Array.<string>} An array object of name, action and
+ * optionally a description
+ */
+ create : function (params) {
+ return Object.create(this)._init(params);
+ },
+
+ /**
+ * Upgrade this object to another object,
+ * while private data stays intact.
+ *
+ * @param {Object] An object with properties.
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ content : function (content) {
+ if (arguments.length === 1)
+ this._content = document.createTextNode(content);
+ return this._content;
+ },
+
+ lcField : function () {
+ return this._lcField;
+ },
+
+ action : function (action) {
+ if (arguments.length === 1)
+ this._action = action;
+ return this._action;
+ },
+
+ /**
+ * Check or set if the item is active
+ *
+ * @param {boolean|null} State of activity
+ */
+ active : function (bool) {
+ var cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("active");
+ else if (bool)
+ cl.add("active");
+ else
+ cl.remove("active");
+ },
+
+ /**
+ * Check or set if the item is
+ * at the boundary of the menu
+ * list
+ *
+ * @param {boolean|null} State of activity
+ */
+ noMore : function (bool) {
+ var cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("no-more");
+ else if (bool)
+ cl.add("no-more");
+ else
+ cl.remove("no-more");
+ },
+
+ /**
+ * Get the document element of the menu item
+ */
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+
+ // Connect action
+ if (this["onclick"] !== undefined) {
+ li["onclick"] = this.onclick.bind(this);
+ };
+
+ // Append template
+ li.appendChild(this.content());
+
+ return this._element = li;
+ },
+
+ /**
+ * Highlight parts of the item
+ *
+ * @param {string} Prefix string for highlights
+ */
+ highlight : function (prefix) {
+ var children = this.element().childNodes;
+ for (var i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefix);
+ };
+ },
+
+ // Highlight a certain substring of the menu item
+ _highlight : function (elem, prefix) {
+
+ if (elem.nodeType === 3) {
+
+ var text = elem.nodeValue;
+ var textlc = text.toLowerCase();
+ var pos = textlc.indexOf(prefix);
+ if (pos >= 0) {
+
+ // First element
+ if (pos > 0) {
+ elem.parentNode.insertBefore(
+ document.createTextNode(text.substr(0, pos)),
+ elem
+ );
+ };
+
+ // Second element
+ var hl = document.createElement("mark");
+ hl.appendChild(
+ document.createTextNode(text.substr(pos, prefix.length))
+ );
+ elem.parentNode.insertBefore(hl, elem);
+
+ // Third element
+ var third = text.substr(pos + prefix.length);
+ if (third.length > 0) {
+ var thirdE = document.createTextNode(third);
+ elem.parentNode.insertBefore(
+ thirdE,
+ elem
+ );
+ this._highlight(thirdE, prefix);
+ };
+
+ var p = elem.parentNode;
+ p.removeChild(elem);
+ };
+ }
+ else {
+ var children = elem.childNodes;
+ for (var i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefix);
+ };
+ };
+ },
+
+ /**
+ * Remove highlight of the menu item
+ */
+ lowlight : function () {
+ var e = this.element();
+
+ var marks = e.getElementsByTagName("mark");
+ for (var i = marks.length - 1; i >= 0; i--) {
+ // Create text node clone
+ var x = document.createTextNode(
+ marks[i].firstChild.nodeValue
+ );
+
+ // Replace with content
+ marks[i].parentNode.replaceChild(
+ x,
+ marks[i]
+ );
+ };
+
+ // Remove consecutive textnodes
+ e.normalize();
+ },
+
+ // Initialize menu item
+ _init : function (params) {
+
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this.content(params[0]);
+
+ if (params.length === 2)
+ this._action = params[1];
+
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+ return this;
+ },
+
+ /**
+ * Return menu list.
+ */
+ menu : function () {
+ return this._menu;
+ }
+});
diff --git a/dev/js/src/menu/prefix.js b/dev/js/src/menu/prefix.js
new file mode 100644
index 0000000..a4e428f
--- /dev/null
+++ b/dev/js/src/menu/prefix.js
@@ -0,0 +1,89 @@
+define({
+ create : function (params) {
+ return Object.create(this)._init();
+ },
+ _init : function () {
+ this._string = '';
+
+ // Add prefix span
+ this._element = document.createElement('span');
+ this._element.classList.add('pref');
+ // Connect action
+
+ if (this["onclick"] !== undefined)
+ this._element["onclick"] = this.onclick.bind(this);
+
+ return this;
+ },
+ _update : function () {
+ this._element.innerHTML
+ = this._string;
+ },
+
+ /**
+ * Upgrade this object to another object,
+ * while private data stays intact.
+ *
+ * @param {Object} An object with properties.
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ active : function (bool) {
+ var cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("active");
+ else if (bool)
+ cl.add("active");
+ else
+ cl.remove("active");
+ },
+
+ element : function () {
+ return this._element;
+ },
+
+ isSet : function () {
+ return this._string.length > 0 ?
+ true : false;
+ },
+
+ value : function (string) {
+ if (arguments.length === 1) {
+ this._string = string;
+ this._update();
+ };
+ return this._string;
+ },
+
+ add : function (string) {
+ this._string += string;
+ this._update();
+ },
+
+ onclick : function () {},
+
+ backspace : function () {
+ if (this._string.length > 1) {
+ this._string = this._string.substring(
+ 0, this._string.length - 1
+ );
+ }
+ else {
+ this._string = '';
+ };
+
+ this._update();
+ },
+
+ /**
+ * Return menu list.
+ */
+ menu : function () {
+ return this._menu;
+ }
+});
diff --git a/dev/js/src/session.js b/dev/js/src/session.js
index a10ce81..f81eb8f 100644
--- a/dev/js/src/session.js
+++ b/dev/js/src/session.js
@@ -4,77 +4,68 @@
*
* @author Nils Diewald
*/
-var KorAP = KorAP || {};
+define({
+ /**
+ * Create a new session.
+ * Expects a name or defaults to 'korap'
+ */
+ create : function (name) {
+ var obj = Object.create(this);
+ if (name === undefined)
+ name = 'korap';
+ obj._name = name.toLowerCase();
+ obj._hash = {};
+ obj._parse();
+ return obj;
+ },
-(function (KorAP) {
- "use strict";
+ /**
+ * Get a value based on a key.
+ * The value can be complex, as the value is stored as JSON.
+ */
+ get : function (key) {
+ return this._hash[key.toLowerCase()];
+ },
-
- KorAP.Session = {
+ /**
+ * Set a value based on a key.
+ * The value can be complex, as the value is stored as JSON.
+ */
+ set : function (key, value) {
+ this._hash[key] = value;
+ this._store();
+ },
- /**
- * Create a new session.
- * Expects a name or defaults to 'korap'
- */
- create : function (name) {
- var obj = Object.create(KorAP.Session);
- if (name === undefined)
- name = 'korap';
- obj._name = name.toLowerCase();
- obj._hash = {};
- obj._parse();
- return obj;
- },
+ /**
+ * Clears the session by removing the cookie
+ */
+ clear : function () {
+ document.cookie = this._name + '=; expires=-1';
+ },
- /**
- * Get a value based on a key.
- * The value can be complex, as the value is stored as JSON.
- */
- get : function (key) {
- return this._hash[key.toLowerCase()];
- },
+ /* Store cookie */
+ _store : function () {
+ /*
+ var date = new Date();
+ date.setYear(date.getFullYear() + 1);
+ */
+ document.cookie =
+ this._name + '=' + encodeURIComponent(JSON.stringify(this._hash)) + ';';
+ },
- /**
- * Set a value based on a key.
- * The value can be complex, as the value is stored as JSON.
- */
- set : function (key, value) {
- this._hash[key] = value;
- this._store();
- },
-
- /**
- * Clears the session by removing the cookie
- */
- clear : function () {
- document.cookie = this._name + '=; expires=-1';
- },
-
- /* Store cookie */
- _store : function () {
- /*
- var date = new Date();
- date.setYear(date.getFullYear() + 1);
- */
- document.cookie =
- this._name + '=' + encodeURIComponent(JSON.stringify(this._hash)) + ';';
- },
-
- /* Parse cookie */
- _parse : function () {
- var c = document.cookie;
- var part = document.cookie.split(';');
- for(var i = 0; i < part.length; i++) {
- var pair = part[i].split('=');
- var name = pair[0].trim().toLowerCase();
- if (name === this._name) {
- if (pair.length === 1 || pair[1].length === 0)
- return;
- this._hash = JSON.parse(decodeURIComponent(pair[1]));
+ /* Parse cookie */
+ _parse : function () {
+ var c = document.cookie;
+ var part = document.cookie.split(';');
+ for(var i = 0; i < part.length; i++) {
+ var pair = part[i].split('=');
+ var name = pair[0].trim().toLowerCase();
+ if (name === this._name) {
+ if (pair.length === 1 || pair[1].length === 0)
return;
- };
+ this._hash = JSON.parse(decodeURIComponent(pair[1]));
+ return;
};
- }
+ };
}
-
-}(this.KorAP));
+});
diff --git a/dev/js/src/tutorial.js b/dev/js/src/tutorial.js
index 03cf903..dae5fbf 100644
--- a/dev/js/src/tutorial.js
+++ b/dev/js/src/tutorial.js
@@ -2,31 +2,30 @@
* Open and close a tutorial page.
* The current page is stored and retrieved in a session cookie.
*/
-// Requires session.js
-var KorAP = KorAP || {};
-
// Todo: add query mechanism!
-(function (KorAP) {
+define(['session', 'util'], function (sessionClass) {
"use strict";
// Localization values
- var loc = (KorAP.Locale = KorAP.Locale || {} );
+ var loc = KorAP.Locale;
loc.CLOSE = loc.CLOSE || 'Close';
- KorAP.Tutorial = {
-
+ return {
/**
* Create new tutorial object.
* Accepts an element to bind the tutorial window to.
*/
create : function (obj) {
- return Object.create(KorAP.Tutorial)._init(obj);
+ if (!obj)
+ return null;
+ return Object.create(this)._init(obj);
},
// Initialize Tutorial object
_init : function (obj) {
- this._session = KorAP.Session.create();
+
+ this._session = sessionClass.create();
this._show = obj;
this.start = obj.getAttribute('href');
obj.removeAttribute('href');
@@ -58,9 +57,6 @@
var ul = document.createElement('ul');
ul.classList.add('action', 'right');
- // Use localization
- var loc = KorAP.Locale;
-
// Add close button
var close = document.createElement('li');
close.appendChild(document.createElement('span'))
@@ -80,7 +76,7 @@
info.classList.add('info');
info.setAttribute('title', loc.SHOWINFO);
*/
-
+
ul.appendChild(close);
element.appendChild(ul);
@@ -129,5 +125,5 @@
getPage : function () {
this._session.get('tutpage');
},
- }
-}(this.KorAP));
+ };
+});
diff --git a/dev/js/src/util.js b/dev/js/src/util.js
new file mode 100644
index 0000000..d183c36
--- /dev/null
+++ b/dev/js/src/util.js
@@ -0,0 +1,45 @@
+var KorAP = KorAP || {};
+
+// TODO: Make this part of util!
+// Don't let events bubble up
+if (Event.halt === undefined) {
+ // Don't let events bubble up
+ Event.prototype.halt = function () {
+ this.stopPropagation();
+ this.preventDefault();
+ };
+};
+
+// Add toggleClass method similar to jquery
+HTMLElement.prototype.toggleClass = function (c1, c2) {
+ var cl = this.classList;
+ if (cl.contains(c1)) {
+ cl.add(c2);
+ cl.remove(c1);
+ }
+ else {
+ cl.remove(c2);
+ cl.add(c1);
+ };
+};
+
+
+// Utility for removing all children of a node
+function _removeChildren (node) {
+ // Remove everything underneath
+ while (node.firstChild)
+ node.removeChild(node.firstChild);
+};
+
+
+define(function () {
+ KorAP.API = KorAP.API || {};
+ KorAP.Locale = KorAP.Locale || {};
+
+ // Default log message
+ KorAP.log = KorAP.log || function (type, msg) {
+ console.log(type + ": " + msg);
+ };
+
+ return KorAP;
+});
diff --git a/dev/js/src/vc.js b/dev/js/src/vc.js
index 2c8ad01..48c5694 100644
--- a/dev/js/src/vc.js
+++ b/dev/js/src/vc.js
@@ -6,9 +6,6 @@
/*
* Replaces a previous version written by Mengfei Zhou
*/
-var KorAP = KorAP || {};
-
-// Requires menu.js
/*
TODO: Implement a working localization solution!
@@ -33,131 +30,60 @@
815: "Rewrite expects source"
*/
-(function (KorAP) {
+define([
+ 'vc/unspecified',
+ 'vc/doc',
+ 'vc/docgroup',
+ 'util'
+], function (unspecDocClass, docClass, docGroupClass) {
"use strict";
- // Default log message
- KorAP.log = KorAP.log || function (type, msg) {
- console.log(type + ": " + msg);
- };
-
KorAP._validStringMatchRE = new RegExp("^(?:eq|ne|contains|excludes)$");
- KorAP._validRegexMatchRE = new RegExp("^(?:eq|ne)$");
+ // KorAP._validRegexMatchRE = new RegExp("^(?:eq|ne)$");
KorAP._validDateMatchRE = new RegExp("^[lg]?eq$");
KorAP._validDateRE = new RegExp("^(?:\\d{4})(?:-\\d\\d(?:-\\d\\d)?)?$");
- KorAP._validGroupOpRE = new RegExp("^(?:and|or)$");
- KorAP._validRewriteOpRE = new RegExp("^(?:injec|modifica)tion$");
- KorAP._quote = new RegExp("([\"\\\\])", 'g');
+ // KorAP._validGroupOpRE = new RegExp("^(?:and|or)$");
+ // KorAP._quote = new RegExp("([\"\\\\])", 'g');
// Localization values
var loc = (KorAP.Locale = KorAP.Locale || {} );
- loc.AND = loc.AND || 'and';
- loc.OR = loc.OR || 'or';
- loc.DEL = loc.DEL || '×';
- loc.EMPTY = loc.EMPTY || '⋯'
-
- // Utility for analysing boolean values
- function _bool (bool) {
- return (bool === undefined || bool === null || bool === false) ? false : true;
- };
-
-
- // Utility for removing all children of a node
- function _removeChildren (node) {
- // Remove everything underneath
- while (node.firstChild)
- node.removeChild(node.firstChild);
- };
-
-
- // Add new unspecified document
- KorAP._add = function (obj, type) {
- var ref = obj.parentNode.refTo;
- var parent = ref.parent();
-
- if (ref.ldType() === 'docGroup') {
-
- // Check that the action differs from the type
- if (ref.operation() === type)
- return;
-
- if (parent.ldType() !== null) {
- return parent.newAfter(ref);
- }
- else {
- // The group is on root - wrap
- return ref.wrapOnRoot();
- };
- }
- else if (ref.ldType() === 'doc') {
-
- if (parent.ldType() === null) {
- return ref.wrapOnRoot(type);
- }
- else if (parent.operation() === type) {
- return parent.newAfter(ref);
- }
- else {
- return ref.wrap(type);
- };
- };
- };
-
-
- // Add doc with 'and' relation
- KorAP._and = function () {
- return KorAP._add(this, 'and');
- };
-
-
- // Add doc with 'or' relation
- KorAP._or = function () {
- return KorAP._add(this, 'or');
- };
-
-
- // Remove doc or docGroup
- KorAP._delete = function () {
- var ref = this.parentNode.refTo;
- if (ref.parent().ldType() !== null) {
- return ref.parent().delOperand(ref).update();
- }
- else {
- ref.parent().clean();
- };
- };
-
+ /*
+ loc.AND = loc.AND || 'and';
+ loc.OR = loc.OR || 'or';
+ loc.DEL = loc.DEL || '×';
+ loc.EMPTY = loc.EMPTY || '⋯'
+ */
/**
* Virtual Collection
*/
- KorAP.VirtualCollection = {
+ return {
ldType : function () {
return null;
},
create : function () {
- return Object.create(KorAP.VirtualCollection);
+ return Object.create(this);
},
clean : function () {
if (this._root.ldType() !== "non") {
this._root.destroy();
- this.root(KorAP.UnspecifiedDoc.create(this));
+ this.root(unspecDocClass.create(this));
};
return this;
},
render : function (json) {
- var obj = Object.create(KorAP.VirtualCollection);
+ var obj = Object.create(this);
if (json !== undefined) {
// Root object
if (json['@type'] == 'koral:doc') {
- obj._root = KorAP.Doc.create(obj, json);
+ obj._root = docClass.create(obj, json);
}
else if (json['@type'] == 'koral:docGroup') {
- obj._root = KorAP.DocGroup.create(obj, json);
+ obj._root = docGroupClass.create(obj, json);
}
else {
KorAP.log(813, "Collection type is not supported");
@@ -225,1174 +151,4 @@
return this._root.toQuery();
}
};
-
-
- /**
- * Operators for criteria
- */
- KorAP.Operators = {
- create : function (and, or, del) {
- var op = Object.create(KorAP.Operators);
- op.and(and);
- op.or(or);
- op.del(del);
- return op;
- },
-
- update : function () {
- // Init the element
- if (this._element === undefined)
- return this.element();
-
- var op = this._element;
-
- op.refTo = this.parent();
-
- // Remove everything underneath
- _removeChildren(op);
-
- // Add and button
- if (this._and === true) {
- var andE = document.createElement('span');
- andE.setAttribute('class', 'and');
- andE.addEventListener('click', KorAP._and, false);
- andE.appendChild(
- document.createTextNode(KorAP.Locale.AND)
- );
- op.appendChild(andE);
- };
-
- // Add or button
- if (this._or === true) {
- var orE = document.createElement('span');
- orE.setAttribute('class', 'or');
- orE.addEventListener('click', KorAP._or, false);
- orE.appendChild(document.createTextNode(KorAP.Locale.OR));
- op.appendChild(orE);
- };
-
- // Add delete button
- if (this._del === true) {
- var delE = document.createElement('span');
- delE.setAttribute('class', 'delete');
- delE.appendChild(document.createTextNode(KorAP.Locale.DEL));
- delE.addEventListener('click', KorAP._delete, false);
- op.appendChild(delE);
- };
-
- return op;
- },
-
- // Be aware! This may be cyclic
- parent : function (obj) {
- if (arguments.length === 1)
- this._parent = obj;
- return this._parent;
- },
-
- element : function () {
-
- // Return existing element
- if (this._element !== undefined)
- return this._element;
-
- this._element = document.createElement('div');
- this._element.setAttribute('class', 'operators');
-
- // Init elements
- this.update();
- return this._element;
- },
-
- and : function (bool) {
- if (arguments.length === 1)
- this._and = _bool(bool);
- return this._and;
- },
-
- or : function (bool) {
- if (arguments.length === 1)
- this._or = _bool(bool);
- return this._or;
- },
-
- del : function (bool) {
- if (arguments.length === 1)
- this._del = _bool(bool);
- return this._del;
- }
- };
-
-
- /**
- * Unspecified criterion
- */
- KorAP.UnspecifiedDoc = {
- _ldType : "non",
- create : function (parent) {
- var obj = Object.create(KorAP.JsonLD).
- upgradeTo(KorAP.UnspecifiedDoc);
-
- if (parent !== undefined)
- obj._parent = parent;
-
- return obj;
- },
-
- // Set key - replace
- key : function (v) {
-
- // Not replaceable
- if (this._parent === undefined)
- return null;
-
- // Set JSON-LD type
- var newDoc = KorAP.Doc.create(this._parent, {
- "@type" : "koral:doc",
- "value" : "",
- "key" : v
- });
-
- // Unspecified document on root
- if (this._parent.ldType() === null) {
- this._parent.root(newDoc);
- this.destroy();
- }
-
- // Unspecified document in group
- else {
- this._parent.replaceOperand(this, newDoc);
- };
- this._parent.update();
- return newDoc;
- },
-
- update : function () {
-
- if (this._element === undefined)
- return this.element();
-
- // Remove element content
- _removeChildren(this._element);
-
- var ellipsis = document.createElement('span');
- ellipsis.appendChild(document.createTextNode(loc.EMPTY));
- this._element.appendChild(ellipsis);
-
- // Set ref - TODO: Cleanup!
- this._element.refTo = this;
-
- // Set operators
- if (this._parent !== undefined && this.parent().ldType() !== null) {
- var op = this.operators(
- false,
- false,
- true
- );
-
- this._element.appendChild(
- op.element()
- );
- };
-
- return this.element();
- },
-
- element : function () {
- if (this._element !== undefined)
- return this._element;
- this._element = document.createElement('div');
- this._element.setAttribute('class', 'doc unspecified');
- this.update();
- return this._element;
- },
-
-
- };
-
-
- /**
- * Document criterion
- */
- KorAP.Doc = {
- _ldType : "doc",
- _obj : function () { return KorAP.Doc },
-
- create : function (parent, json) {
- var obj = Object(KorAP.JsonLD).
- create().
- upgradeTo(KorAP.Doc).
- fromJson(json);
-
- if (parent !== undefined)
- obj._parent = parent;
-
- obj.__changed = true;
- return obj;
- },
-
- update : function () {
- if (this._element === undefined)
- return this.element();
-
- // Get element
- var e = this._element;
-
- // Set ref - TODO: Cleanup!
- e.refTo = this;
-
- // Check if there is a change
- if (this.__changed) {
-
- // Was rewritten
- if (this.rewrites() !== undefined) {
- e.classList.add("rewritten");
- };
-
- // Added key
- var key = document.createElement('span');
- key.setAttribute('class', 'key');
-
- // Change key
- key.addEventListener('click', KorAP._changeKey, false);
-
- if (this.key())
- key.appendChild(document.createTextNode(this.key()));
-
- // Added match operator
- var matchop = document.createElement('span');
- matchop.setAttribute('data-type', this.type());
- matchop.setAttribute('class', 'match');
- matchop.appendChild(
- document.createTextNode(this.matchop())
- );
-
- // Added match operator
- var value = document.createElement('span');
- value.setAttribute('data-type', this.type());
- value.setAttribute('class', 'value');
- if (this.value())
- value.appendChild(
- document.createTextNode(this.value())
- );
-
- // Remove all element children
- _removeChildren(e);
-
- // Add spans
- e.appendChild(key);
- e.appendChild(matchop);
- e.appendChild(value);
-
- this.__changed = false;
- };
-
- if (this._rewrites !== undefined) {
- e.appendChild(this._rewrites.element());
- };
-
- if (this._parent !== undefined) {
- // Set operators
- var op = this.operators(
- true,
- true,
- true
- );
-
- // Append new operators
- e.appendChild(op.element());
- };
-
- return e;
- },
-
- element : function () {
- if (this._element !== undefined)
- return this._element;
-
- this._element = document.createElement('div');
- this._element.setAttribute('class', 'doc');
-
- this.update();
- return this._element;
- },
-
- // Wrap a new operation around the doc element
- wrap : function (op) {
- var parent = this.parent();
- var group = KorAP.DocGroup.create(parent);
- group.operation(op);
- group.append(this);
- group.append();
- return parent.replaceOperand(this, group).update();
- },
-
- // Deserialize from json
- fromJson : function (json) {
- if (json === undefined)
- return this;
-
- if (json["@type"] === undefined) {
- KorAP.log(701, "JSON-LD group has no @type attribute");
- return;
- };
-
- if (json["value"] === undefined ||
- typeof json["value"] != 'string') {
- KorAP.log(805, "Value is invalid");
- return;
- };
-
- // There is a defined key
- if (json["key"] !== undefined &&
- typeof json["key"] === 'string') {
-
- // Set key
- this.key(json["key"]);
-
- // Set match operation
- if (json["match"] !== undefined) {
- if (typeof json["match"] === 'string') {
- this.matchop(json["match"]);
- }
- else {
- KorAP.log(802, "Match type is not supported by value type");
- return;
- };
- };
-
- // Key is a string
- if (json["type"] === undefined ||
- json["type"] == "type:string") {
- this.type("string");
-
- // Check match type
- if (!KorAP._validStringMatchRE.test(this.matchop())) {
- KorAP.log(802, "Match type is not supported by value type");
- return;
- };
-
- // Set string value
- this.value(json["value"]);
- }
-
- // Key is a date
- else if (json["type"] === "type:date") {
- this.type("date");
-
- if (json["value"] !== undefined &&
- KorAP._validDateRE.test(json["value"])) {
-
- if (!KorAP._validDateMatchRE.test(this.matchop())) {
- KorAP.log(802, "Match type is not supported by value type");
- return;
- };
-
- // Set value
- this.value(json["value"]);
- }
- else {
- KorAP.log(806, "Value is not a valid date string");
- return;
- };
- }
-
- // Key is a regular expression
- else if (json["type"] === "type:regex") {
- this.type("regex");
-
- try {
-
- // Try to create a regular expression
- var check = new RegExp(json["value"]);
-
- if (!KorAP._validRegexMatchRE.test(this.matchop())) {
- KorAP.log(802, "Match type is not supported by value type");
- return;
- };
-
- this.value(json["value"]);
- }
-
- catch (e) {
- KorAP.log(807, "Value is not a valid regular expression");
- return;
- };
- this.type("regex");
- }
-
- else {
- KorAP.log(804, "Unknown value type");
- return;
- };
-
- };
-
- if (json["rewrites"] !== undefined) {
- this._rewrites = KorAP.RewriteList.create(json["rewrites"]);
- };
-
- return this;
- },
-
- key : function (value) {
- if (arguments.length === 1) {
- this._key = value;
- this._changed();
- return this;
- };
- return this._key;
- },
-
- matchop : function (match) {
- if (arguments.length === 1) {
- this._matchop = match.replace(/^match:/, '');
- this._changed();
- return this;
- };
- return this._matchop || "eq";
- },
-
- type : function (type) {
- if (arguments.length === 1) {
- this._type = type;
- this._changed();
- return this;
- };
- return this._type || "string";
- },
-
- value : function (value) {
- if (arguments.length === 1) {
- this._value = value;
- this._changed();
- return this;
- };
- return this._value;
- },
-
- rewrites : function () {
- return this._rewrites;
- },
-
- _changed : function () {
- this.__changed = true;
-
- if (this._rewrites === undefined)
- return;
- delete this["_rewrites"];
- if (this._element === undefined)
- return;
- this._element.classList.remove("rewritten");
- },
-
- toJson : function () {
- if (!this.matchop() || !this.key())
- return {};
-
- return {
- "@type" : "koral:" + this.ldType(),
- "key" : this.key(),
- "match" : "match:" + this.matchop(),
- "value" : this.value() || '',
- "type" : "type:" + this.type()
- };
- },
-
- toQuery : function () {
- if (!this.matchop() || !this.key())
- return "";
-
- // Build doc string based on key
- var string = this.key() + ' ';
-
- // Add match operator
- switch (this.matchop()) {
- case "ne":
- string += '!=';
- break;
- case "contains":
- string += '~';
- break;
- case "excludes":
- string += '!~';
- break;
- case "geq":
- string += 'since';
- break;
- case "leq":
- string += 'until';
- break;
- default:
- string += (this.type() == 'date') ? 'in' : '=';
- break;
- };
-
- string += ' ';
-
- // Add value
- switch (this.type()) {
- case "date":
- return string + this.value();
- break;
- case "regex":
- return string + '/' + this.value() + '/';
- break;
- case "string":
- return string + '"' + this.value().replace(KorAP._quote, '\\$1') + '"';
- break;
- };
-
- return "";
- }
- };
-
-
- /**
- * Document group criterion
- */
- KorAP.DocGroup = {
- _ldType : "docGroup",
-
- create : function (parent, json) {
- var obj = Object.create(KorAP.JsonLD).upgradeTo(KorAP.DocGroup);
- obj._operands = [];
- obj.fromJson(json);
- if (parent !== undefined)
- obj._parent = parent;
- return obj;
- },
-
- newAfter : function (obj) {
- for (var i = 0; i < this._operands.length; i++) {
- if (this._operands[i] === obj) {
- var operand = KorAP.UnspecifiedDoc.create(this);
- this._operands.splice(i + 1, 0, operand);
- return this.update();
- };
- };
- },
-
- // The doc is already set in the group
- _duplicate : function (operand) {
- if (operand.ldType() !== 'doc')
- return null;
-
- for (var i = 0; i < this._operands.length; i++) {
- var op = this.getOperand(i);
- if (op.ldType() === 'doc'
- && operand.key() === op.key()
- && operand.matchop() === op.matchop()
- && operand.value() === op.value()) {
- return op;
- };
- };
- return null;
- },
-
- append : function (operand) {
-
- // Append unspecified object
- if (operand === undefined) {
-
- // Be aware of cyclic structures!
- operand = KorAP.UnspecifiedDoc.create(this);
- this._operands.push(operand);
- return operand;
- };
-
- switch (operand["@type"]) {
-
- case undefined:
- // No @type defined
- if (operand["ldType"] !== undefined) {
- if (operand.ldType() !== 'doc' &&
- operand.ldType() !== 'docGroup') {
- KorAP.log(812, "Operand not supported in document group");
- return;
- };
- // Be aware of cyclic structures!
- operand.parent(this);
-
- var dupl = this._duplicate(operand);
- if (dupl === null) {
- this._operands.push(operand);
- return operand;
- };
- return dupl;
- };
-
- KorAP.log(701, "JSON-LD group has no @type attribute");
- return;
-
- case "koral:doc":
- // Be aware of cyclic structures!
- var doc = KorAP.Doc.create(this, operand);
- if (doc === undefined)
- return;
- var dupl = this._duplicate(doc);
- if (dupl === null) {
- this._operands.push(doc);
- return doc;
- };
- return dupl;
-
- case "koral:docGroup":
- // Be aware of cyclic structures!
- var docGroup = KorAP.DocGroup.create(this, operand);
- if (docGroup === undefined)
- return;
-
- // Flatten group
- if (docGroup.operation() === this.operation()) {
- for (var op in docGroup.operands()) {
- op = docGroup.getOperand(op);
- var dupl = this._duplicate(op);
- if (dupl === null) {
- this._operands.push(op);
- op.parent(this);
- };
- };
- docGroup._operands = [];
- docGroup.destroy();
- return this;
- };
- this._operands.push(docGroup);
- return docGroup;
-
- default:
- KorAP.log(812, "Operand not supported in document group");
- return;
- };
- },
-
- update : function () {
- // There is only one operand in group
-
- if (this._operands.length === 1) {
-
- var parent = this.parent();
- var op = this.getOperand(0);
-
- // This will prevent destruction of
- // the operand
- this._operands = [];
-
- // Parent is a group
- if (parent.ldType() !== null)
- return parent.replaceOperand(this, op).update();
-
- // Parent is vc
- else {
- this.destroy();
- // Cyclic madness
- parent.root(op);
- op.parent(parent);
- return parent.root();
- };
- };
-
- if (this._element === undefined)
- return this;
-
- var group = this._element;
- group.setAttribute('data-operation', this.operation());
-
- _removeChildren(group);
-
- // Append operands
- for (var i = 0; i < this._operands.length; i++) {
- group.appendChild(
- this.getOperand(i).element()
- );
- };
-
- // Set operators
- var op = this.operators(
- this.operation() == 'and' ? false : true,
- this.operation() == 'or' ? false : true,
- true
- );
-
- group.appendChild(
- op.element()
- );
-
- return this;
- },
-
- element : function () {
- if (this._element !== undefined)
- return this._element;
-
- this._element = document.createElement('div');
- this._element.setAttribute('class', 'docGroup');
-
- // Update the object - including optimization
- this.update();
-
- return this._element;
- },
-
- operation : function (op) {
- if (arguments.length === 1) {
- if (KorAP._validGroupOpRE.test(op)) {
- this._op = op;
- }
- else {
- KorAP.log(810, "Unknown operation type");
- return;
- };
- };
- return this._op || 'and';
- },
-
- operands : function () {
- return this._operands;
- },
-
- getOperand : function (index) {
- return this._operands[index];
- },
-
- // Replace operand
- replaceOperand : function (oldOp, newOp) {
-
- for (var i = 0; i < this._operands.length; i++) {
- if (this._operands[i] === oldOp) {
-
- // Just insert a doc or ...
- if (newOp.ldType() === "doc" ||
- newOp.ldType() === "non" ||
- // ... insert a group of a different operation
- // (i.e. "and" in "or"/"or" in "and")
- newOp.operation() != this.operation()) {
- this._operands[i] = newOp;
- newOp.parent(this);
- }
-
- // Flatten group
- else {
- // Remove old group
- this._operands.splice(i, 1);
-
- // Inject new operands
- for (var op in newOp.operands().reverse()) {
- op = newOp.getOperand(op);
- this._operands.splice(i, 0, op);
- op.parent(this);
- };
- // Prevent destruction of operands
- newOp._operands = [];
- newOp.destroy();
- };
- oldOp.destroy();
- return this;
- }
- };
- return false;
- },
-
- // Delete operand from group
- delOperand : function (obj) {
- for (var i = 0; i < this._operands.length; i++) {
- if (this._operands[i] === obj) {
-
- // Delete identified operand
- this._operands.splice(i,1);
-
- // Destroy object for cyclic references
- obj.destroy();
-
- return this;
- };
- };
-
- // Operand not found
- return undefined;
- },
-
- // Deserialize from json
- fromJson : function (json) {
- if (json === undefined)
- return this;
-
- if (json["@type"] === undefined) {
- KorAP.log(701, "JSON-LD group has no @type attribute");
- return;
- };
-
- if (json["operation"] === undefined ||
- typeof json["operation"] !== 'string') {
- KorAP.log(811, "Document group expects operation");
- return;
- };
-
- var operation = json["operation"];
-
- this.operation(operation.replace(/^operation:/,''));
-
- if (json["operands"] === undefined ||
- !(json["operands"] instanceof Array)) {
- KorAP.log(704, "Operation needs operand list")
- return;
- };
-
- // Add all documents
- for (var i in json["operands"]) {
- var operand = json["operands"][i];
- this.append(operand);
- };
-
- return this;
- },
-
- toJson : function () {
- var opArray = new Array();
- for (var i = 0; i < this._operands.length; i++) {
- if (this._operands[i].ldType() !== 'non')
- opArray.push(this._operands[i].toJson());
- };
- return {
- "@type" : "koral:" + this.ldType(),
- "operation" : "operation:" + this.operation(),
- "operands" : opArray
- };
- },
-
- toQuery : function (brackets) {
- var list = this._operands
- .filter(function (op) {
- return op.ldType() !== 'non';
- })
- .map(function (op) {
- return (op.ldType() === 'docGroup') ?
- op.toQuery(true) :
- op.toQuery();
- });
-
- if (list.length === 1)
- return list.join('');
- else {
- var str = list.join(this.operation() === 'or' ? ' | ' : ' & ');
- return brackets ? '(' + str + ')' : str;
- };
- }
- };
-
-
- KorAP.RewriteList = {
- // Construction method
- create : function (json) {
- var obj = Object(KorAP.JsonLD).
- create().
- upgradeTo(KorAP.RewriteList).
- fromJson(json);
- return obj;
- },
- fromJson : function (json) {
- this._list = new Array();
- for (var i = 0; i < json.length; i++) {
- this._list.push(
- KorAP.Rewrite.create(json[i])
- );
- };
- return this;
- },
- element : function () {
- if (this._element !== undefined)
- return this._element;
-
- this._element = document.createElement('div');
- this._element.setAttribute('class', 'rewrite');
- for (var x in this._list) {
- var rewrite = this._list[x];
- var span = document.createElement('span');
-
- // Set class attribute
- span.setAttribute('class', rewrite.operation());
-
- // Append source information
- span.appendChild(document.createTextNode(rewrite.src()));
-
- // Append scope information
- if (rewrite.scope() !== undefined) {
- span.appendChild(
- document.createTextNode(
- ': ' + rewrite.scope()
- )
- );
- };
- this._element.appendChild(span);
- };
- return this._element;
- }
- };
-
-
- /**
- * Implementation of rewrite objects.
- */
- KorAP.Rewrite = {
-
- // Construction method
- create : function (json) {
- var obj = Object(KorAP.JsonLD).
- create().
- upgradeTo(KorAP.Rewrite).
- fromJson(json);
- return obj;
- },
-
- // Get or set source
- src : function (string) {
- if (arguments.length === 1)
- this._src = string;
- return this._src;
- },
-
- // Get or set operation
- operation : function (op) {
- if (arguments.length === 1) {
- if (KorAP._validRewriteOpRE.test(op)) {
- this._op = op;
- }
- else {
- KorAP.log(814, "Unknown rewrite operation");
- return;
- };
- };
- return this._op || 'injection';
- },
-
- // Get or set scope
- scope : function (attr) {
- if (arguments.length === 1)
- this._scope = attr;
- return this._scope;
- },
-
- // Serialize from Json
- fromJson : function (json) {
- if (json === undefined)
- return this;
-
- // Missing @type
- if (json["@type"] === undefined) {
- KorAP.log(701, "JSON-LD group has no @type attribute");
- return;
- };
-
- // Missing source
- if (json["src"] === undefined ||
- typeof json["src"] !== 'string') {
- KorAP.log(815, "Rewrite expects source");
- return;
- };
-
- // Set source
- this.src(json["src"]);
-
- // Set operation
- if (json["operation"] !== undefined) {
- var operation = json["operation"];
- this.operation(operation.replace(/^operation:/,''));
- };
-
- // Set scope
- if (json["scope"] !== undefined &&
- typeof json["scope"] === 'string')
- this.scope(json["scope"]);
-
- return this;
- },
-
- toString : function () {
- var str = '';
- var op = this.operation();
- str += op.charAt(0).toUpperCase() + op.slice(1);
- str += ' of ' + (
- this._scope === null ?
- 'object' :
- '"' +
- this.scope().replace(KorAP._quote, '\\$1') +
- '"'
- );
- str += ' by ' +
- '"' +
- this.src().replace(KorAP._quote, '\\$1') +
- '"';
- return str;
- }
- };
-
-
- /**
- * Abstract JsonLD criterion object
- */
- KorAP.JsonLD = {
- __changed : false,
-
- create : function () {
- return Object.create(KorAP.JsonLD);
- },
-
- /**
- * Upgrade this object to another object
- * while private data stays intact
- */
- upgradeTo : function (props) {
- for (var prop in props) {
- this[prop] = props[prop];
- };
- return this;
- },
-
- ldType : function (type) {
- if (arguments.length === 1)
- this._ldType = type;
- return this._ldType;
- },
-
- parent : function (obj) {
- if (arguments.length === 1) {
- this._parent = obj;
- this.__changed = true;
- };
- return this._parent;
- },
-
- // Destroy object - especially for
- // acyclic structures!
- // I'm paranoid!
- destroy : function () {
- if (this._ops != undefined) {
- this._ops._parent = undefined;
- if (this._ops._element !== undefined)
- this._ops._element.refTo = undefined;
- this._ops = undefined;
- };
- if (this._element !== undefined)
- this._element = undefined;
-
- // In case of a group, destroy all operands
- if (this._operands !== undefined) {
- for (var i = 0; i < this._operands.length; i++)
- this.getOperand(i).destroy();
- this._operands = [];
- };
- },
-
- // Wrap a new operation around the root group element
- wrapOnRoot : function (op) {
- var parent = this.parent();
-
- var group = KorAP.DocGroup.create(parent);
- if (arguments.length === 1)
- group.operation(op);
- else
- group.operation(
- this.operation() === 'and' ? 'or' : 'and'
- );
- group.append(this);
- this.parent(group);
- group.append();
- group.element(); // Init (seems to be necessary)
- parent.root(group);
- return this.parent();
- },
-
- // Be aware! This may be cyclic
- operators : function (and, or, del) {
- if (arguments === 0)
- return this._ops;
- this._ops = KorAP.Operators.create(
- and, or, del
- );
- this._ops.parent(this);
- return this._ops;
- },
-
- toJson : function () {
- return {
- // Unspecified object
- "@type" : "koral:" + this.ldType()
- };
- },
-
- toQuery : function () {
- return '';
- }
- };
-
-
- /**
- * Criterion in a KorAP.Doc
- */
- KorAP._changeKey = function () {
- var doc = this.parentNode.refTo;
- var key = doc.element().firstChild;
- key.appendChild(KorAP.FieldChooser.element());
- KorAP.FieldChooser.show();
- KorAP.FieldChooser.focus();
- // key, matchop, type, value
- };
-
- // Field menu
- KorAP.FieldMenu = {
- create : function (params) {
- return Object.create(KorAP.Menu)
- .upgradeTo(KorAP.FieldMenu)
- ._init(KorAP.FieldMenuItem, undefined, params)
- }
- };
-
-
- // Field menu item
- KorAP.FieldMenuItem = {
- create : function (params) {
- return Object.create(KorAP.MenuItem)
- .upgradeTo(KorAP.FieldMenuItem)
- ._init(params);
- },
- _init : function (params) {
- if (params[0] === undefined)
- throw new Error("Missing parameters");
-
- this._name = params[0];
- this._value = params[1];
- this._type = params[2];
-
- this._lcField = ' ' + this._name.toLowerCase();
-
- return this;
- },
- name : function () {
- return this._name;
- },
- type : function () {
- return this._type;
- },
- element : function () {
- // already defined
- if (this._element !== undefined)
- return this._element;
-
- // Create list item
- var li = document.createElement("li");
- li.setAttribute("data-type", this._type);
- li.setAttribute("data-value", this._value);
- li.appendChild(document.createTextNode(this._name));
- return this._element = li;
- }
- };
-
- KorAP.FieldChooser = KorAP.FieldMenu.create([
- ['Titel', 'title', 'string'],
- ['Untertitel', 'subTitle', 'string'],
- ['Veröffentlichungsdatum', 'pubDate', 'date'],
- ['Autor', 'author', 'string']
- ]);
- KorAP.FieldChooser.limit(5);
-
-}(this.KorAP));
+});
diff --git a/dev/js/src/vc/doc.js b/dev/js/src/vc/doc.js
new file mode 100644
index 0000000..61c6439
--- /dev/null
+++ b/dev/js/src/vc/doc.js
@@ -0,0 +1,363 @@
+/**
+ * Document criterion
+ */
+
+/**
+ * Criterion in a KorAP.Doc
+ */
+function _changeKey () {
+ var doc = this.parentNode.refTo;
+ var key = doc.element().firstChild;
+ key.appendChild(fieldMenu.element());
+ fieldMenu.show();
+ fieldMenu.focus();
+ // key, matchop, type, value
+};
+
+define([
+ 'vc/jsonld', 'vc/menu', 'vc/rewritelist'], function (jsonldClass, menuClass, rewriteListClass) {
+ var fieldMenu = menuClass.create([
+ ['Titel', 'title', 'string'],
+ ['Untertitel', 'subTitle', 'string'],
+ ['Veröffentlichungsdatum', 'pubDate', 'date'],
+ ['Autor', 'author', 'string']
+ ]);
+
+ fieldMenu.limit(5);
+
+ _validRegexMatchRE = new RegExp("^(?:eq|ne)$");
+
+ return {
+ _ldType : "doc",
+ _obj : function () { return '???'; /*KorAP.Doc*/ },
+
+ create : function (parent, json) {
+ var obj = Object(jsonldClass).
+ create().
+ upgradeTo(this).
+ fromJson(json);
+
+ if (parent !== undefined)
+ obj._parent = parent;
+
+ obj.__changed = true;
+ return obj;
+ },
+
+ update : function () {
+ if (this._element === undefined)
+ return this.element();
+
+ // Get element
+ var e = this._element;
+
+ // Set ref - TODO: Cleanup!
+ e.refTo = this;
+
+ // Check if there is a change
+ if (this.__changed) {
+
+ // Was rewritten
+ if (this.rewrites() !== undefined) {
+ e.classList.add("rewritten");
+ };
+
+ // Added key
+ var key = document.createElement('span');
+ key.setAttribute('class', 'key');
+
+ // Change key
+ key.addEventListener('click', _changeKey, false);
+
+ if (this.key())
+ key.appendChild(document.createTextNode(this.key()));
+
+ // Added match operator
+ var matchop = document.createElement('span');
+ matchop.setAttribute('data-type', this.type());
+ matchop.setAttribute('class', 'match');
+ matchop.appendChild(
+ document.createTextNode(this.matchop())
+ );
+
+ // Added match operator
+ var value = document.createElement('span');
+ value.setAttribute('data-type', this.type());
+ value.setAttribute('class', 'value');
+ if (this.value())
+ value.appendChild(
+ document.createTextNode(this.value())
+ );
+
+ // Remove all element children
+ _removeChildren(e);
+
+ // Add spans
+ e.appendChild(key);
+ e.appendChild(matchop);
+ e.appendChild(value);
+
+ this.__changed = false;
+ };
+
+ if (this._rewrites !== undefined) {
+ e.appendChild(this._rewrites.element());
+ };
+
+ if (this._parent !== undefined) {
+ // Set operators
+ var op = this.operators(
+ true,
+ true,
+ true
+ );
+
+ // Append new operators
+ e.appendChild(op.element());
+ };
+
+ return e;
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'doc');
+
+ this.update();
+ return this._element;
+ },
+
+ // Wrap a new operation around the doc element
+ wrap : function (op) {
+ var parent = this.parent();
+ var group = KorAP.DocGroup.create(parent);
+ group.operation(op);
+ group.append(this);
+ group.append();
+ return parent.replaceOperand(this, group).update();
+ },
+
+ // Deserialize from json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ if (json["value"] === undefined ||
+ typeof json["value"] != 'string') {
+ KorAP.log(805, "Value is invalid");
+ return;
+ };
+
+ // There is a defined key
+ if (json["key"] !== undefined &&
+ typeof json["key"] === 'string') {
+
+ // Set key
+ this.key(json["key"]);
+
+ // Set match operation
+ if (json["match"] !== undefined) {
+ if (typeof json["match"] === 'string') {
+ this.matchop(json["match"]);
+ }
+ else {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+ };
+
+ // Key is a string
+ if (json["type"] === undefined ||
+ json["type"] == "type:string") {
+ this.type("string");
+
+ // Check match type
+ if (!KorAP._validStringMatchRE.test(this.matchop())) {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+
+ // Set string value
+ this.value(json["value"]);
+ }
+
+ // Key is a date
+ else if (json["type"] === "type:date") {
+ this.type("date");
+
+ if (json["value"] !== undefined &&
+ KorAP._validDateRE.test(json["value"])) {
+
+ if (!KorAP._validDateMatchRE.test(this.matchop())) {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+
+ // Set value
+ this.value(json["value"]);
+ }
+ else {
+ KorAP.log(806, "Value is not a valid date string");
+ return;
+ };
+ }
+
+ // Key is a regular expression
+ else if (json["type"] === "type:regex") {
+ this.type("regex");
+
+ try {
+
+ // Try to create a regular expression
+ var check = new RegExp(json["value"]);
+
+ if (!_validRegexMatchRE.test(this.matchop())) {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+
+ this.value(json["value"]);
+ }
+
+ catch (e) {
+ KorAP.log(807, "Value is not a valid regular expression");
+ return;
+ };
+ this.type("regex");
+ }
+
+ else {
+ KorAP.log(804, "Unknown value type");
+ return;
+ };
+ };
+
+ if (json["rewrites"] !== undefined) {
+ this._rewrites = rewriteListClass.create(json["rewrites"]);
+ };
+
+ return this;
+ },
+
+ key : function (value) {
+ if (arguments.length === 1) {
+ this._key = value;
+ this._changed();
+ return this;
+ };
+ return this._key;
+ },
+
+ matchop : function (match) {
+ if (arguments.length === 1) {
+ this._matchop = match.replace(/^match:/, '');
+ this._changed();
+ return this;
+ };
+ return this._matchop || "eq";
+ },
+
+ type : function (type) {
+ if (arguments.length === 1) {
+ this._type = type;
+ this._changed();
+ return this;
+ };
+ return this._type || "string";
+ },
+
+ value : function (value) {
+ if (arguments.length === 1) {
+ this._value = value;
+ this._changed();
+ return this;
+ };
+ return this._value;
+ },
+
+ rewrites : function () {
+ return this._rewrites;
+ },
+
+ _changed : function () {
+ this.__changed = true;
+
+ if (this._rewrites === undefined)
+ return;
+
+ delete this["_rewrites"];
+
+ if (this._element === undefined)
+ return;
+ this._element.classList.remove("rewritten");
+ },
+
+ toJson : function () {
+ if (!this.matchop() || !this.key())
+ return {};
+
+ return {
+ "@type" : "koral:" + this.ldType(),
+ "key" : this.key(),
+ "match" : "match:" + this.matchop(),
+ "value" : this.value() || '',
+ "type" : "type:" + this.type()
+ };
+ },
+
+ toQuery : function () {
+ if (!this.matchop() || !this.key())
+ return "";
+
+ // Build doc string based on key
+ var string = this.key() + ' ';
+
+ // Add match operator
+ switch (this.matchop()) {
+ case "ne":
+ string += '!=';
+ break;
+ case "contains":
+ string += '~';
+ break;
+ case "excludes":
+ string += '!~';
+ break;
+ case "geq":
+ string += 'since';
+ break;
+ case "leq":
+ string += 'until';
+ break;
+ default:
+ string += (this.type() == 'date') ? 'in' : '=';
+ break;
+ };
+
+ string += ' ';
+
+ // Add value
+ switch (this.type()) {
+ case "date":
+ return string + this.value();
+ break;
+ case "regex":
+ return string + '/' + this.value() + '/';
+ break;
+ case "string":
+ return string + '"' + this.value().replace(KorAP._quote, '\\$1') + '"';
+ break;
+ };
+
+ return "";
+ }
+ };
+});
diff --git a/dev/js/src/vc/docgroup.js b/dev/js/src/vc/docgroup.js
new file mode 100644
index 0000000..5e5f010
--- /dev/null
+++ b/dev/js/src/vc/docgroup.js
@@ -0,0 +1,343 @@
+/**
+ * Document group criterion
+ */
+define([
+ 'vc/jsonld',
+ 'vc/unspecified',
+ 'vc/doc',
+ 'util'
+], function (jsonldClass,
+ unspecClass,
+ docClass) {
+
+ var _validGroupOpRE = new RegExp("^(?:and|or)$");
+
+ var docGroupClass = {
+ _ldType : "docGroup",
+
+ create : function (parent, json) {
+ var obj = Object.create(jsonldClass).upgradeTo(this);
+ obj._operands = [];
+ obj.fromJson(json);
+ if (parent !== undefined)
+ obj._parent = parent;
+ return obj;
+ },
+
+ newAfter : function (obj) {
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i] === obj) {
+ var operand = unspecClass.create(this);
+ this._operands.splice(i + 1, 0, operand);
+ return this.update();
+ };
+ };
+ },
+
+ // The doc is already set in the group
+ _duplicate : function (operand) {
+ if (operand.ldType() !== 'doc')
+ return null;
+
+ for (var i = 0; i < this._operands.length; i++) {
+ var op = this.getOperand(i);
+ if (op.ldType() === 'doc'
+ && operand.key() === op.key()
+ && operand.matchop() === op.matchop()
+ && operand.value() === op.value()) {
+ return op;
+ };
+ };
+ return null;
+ },
+
+ append : function (operand) {
+
+ // Append unspecified object
+ if (operand === undefined) {
+
+ // Be aware of cyclic structures!
+ operand = unspecClass.create(this);
+ this._operands.push(operand);
+ return operand;
+ };
+
+ switch (operand["@type"]) {
+
+ case undefined:
+ // No @type defined
+ if (operand["ldType"] !== undefined) {
+ if (operand.ldType() !== 'doc' &&
+ operand.ldType() !== 'docGroup') {
+ KorAP.log(812, "Operand not supported in document group");
+ return;
+ };
+
+ // Be aware of cyclic structures!
+ operand.parent(this);
+
+ var dupl = this._duplicate(operand);
+ if (dupl === null) {
+ this._operands.push(operand);
+ return operand;
+ };
+ return dupl;
+ };
+
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+
+ case "koral:doc":
+ // Be aware of cyclic structures!
+ var doc = docClass.create(this, operand);
+ if (doc === undefined)
+ return;
+ var dupl = this._duplicate(doc);
+ if (dupl === null) {
+ this._operands.push(doc);
+ return doc;
+ };
+ return dupl;
+
+ case "koral:docGroup":
+ // Be aware of cyclic structures!
+ var docGroup = docGroupClass.create(this, operand);
+ if (docGroup === undefined)
+ return;
+
+ // Flatten group
+ if (docGroup.operation() === this.operation()) {
+ for (var op in docGroup.operands()) {
+ op = docGroup.getOperand(op);
+ var dupl = this._duplicate(op);
+ if (dupl === null) {
+ this._operands.push(op);
+ op.parent(this);
+ };
+ };
+ docGroup._operands = [];
+ docGroup.destroy();
+ return this;
+ };
+ this._operands.push(docGroup);
+ return docGroup;
+
+ default:
+ KorAP.log(812, "Operand not supported in document group");
+ return;
+ };
+ },
+
+ update : function () {
+ // There is only one operand in group
+
+ if (this._operands.length === 1) {
+
+ var parent = this.parent();
+ var op = this.getOperand(0);
+
+ // This will prevent destruction of
+ // the operand
+ this._operands = [];
+
+ // Parent is a group
+ if (parent.ldType() !== null)
+ return parent.replaceOperand(this, op).update();
+
+ // Parent is vc
+ else {
+ this.destroy();
+ // Cyclic madness
+ parent.root(op);
+ op.parent(parent);
+ return parent.root();
+ };
+ };
+
+ if (this._element === undefined)
+ return this;
+
+ var group = this._element;
+ group.setAttribute('data-operation', this.operation());
+
+ _removeChildren(group);
+
+ // Append operands
+ for (var i = 0; i < this._operands.length; i++) {
+ group.appendChild(
+ this.getOperand(i).element()
+ );
+ };
+
+ // Set operators
+ var op = this.operators(
+ this.operation() == 'and' ? false : true,
+ this.operation() == 'or' ? false : true,
+ true
+ );
+
+ group.appendChild(op.element());
+
+ return this;
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'docGroup');
+
+ // Update the object - including optimization
+ this.update();
+
+ return this._element;
+ },
+
+ operation : function (op) {
+ if (arguments.length === 1) {
+ if (_validGroupOpRE.test(op)) {
+ this._op = op;
+ }
+ else {
+ KorAP.log(810, "Unknown operation type");
+ return;
+ };
+ };
+ return this._op || 'and';
+ },
+
+ operands : function () {
+ return this._operands;
+ },
+
+ getOperand : function (index) {
+ return this._operands[index];
+ },
+
+ // Replace operand
+ replaceOperand : function (oldOp, newOp) {
+
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i] === oldOp) {
+
+ // Just insert a doc or ...
+ if (newOp.ldType() === "doc" ||
+ newOp.ldType() === "non" ||
+ // ... insert a group of a different operation
+ // (i.e. "and" in "or"/"or" in "and")
+ newOp.operation() != this.operation()) {
+ this._operands[i] = newOp;
+ newOp.parent(this);
+ }
+
+ // Flatten group
+ else {
+ // Remove old group
+ this._operands.splice(i, 1);
+
+ // Inject new operands
+ for (var op in newOp.operands().reverse()) {
+ op = newOp.getOperand(op);
+ this._operands.splice(i, 0, op);
+ op.parent(this);
+ };
+ // Prevent destruction of operands
+ newOp._operands = [];
+ newOp.destroy();
+ };
+ oldOp.destroy();
+ return this;
+ }
+ };
+ return false;
+ },
+
+ // Delete operand from group
+ delOperand : function (obj) {
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i] === obj) {
+
+ // Delete identified operand
+ this._operands.splice(i,1);
+
+ // Destroy object for cyclic references
+ obj.destroy();
+
+ return this;
+ };
+ };
+
+ // Operand not found
+ return undefined;
+ },
+
+ // Deserialize from json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ if (json["operation"] === undefined ||
+ typeof json["operation"] !== 'string') {
+ KorAP.log(811, "Document group expects operation");
+ return;
+ };
+
+ var operation = json["operation"];
+
+ this.operation(operation.replace(/^operation:/,''));
+
+ if (json["operands"] === undefined ||
+ !(json["operands"] instanceof Array)) {
+ KorAP.log(704, "Operation needs operand list")
+ return;
+ };
+
+ // Add all documents
+ for (var i in json["operands"]) {
+ var operand = json["operands"][i];
+ this.append(operand);
+ };
+
+ return this;
+ },
+
+ toJson : function () {
+ var opArray = new Array();
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i].ldType() !== 'non')
+ opArray.push(this._operands[i].toJson());
+ };
+ return {
+ "@type" : "koral:" + this.ldType(),
+ "operation" : "operation:" + this.operation(),
+ "operands" : opArray
+ };
+ },
+
+ toQuery : function (brackets) {
+ var list = this._operands
+ .filter(function (op) {
+ return op.ldType() !== 'non';
+ })
+ .map(function (op) {
+ return (op.ldType() === 'docGroup') ?
+ op.toQuery(true) :
+ op.toQuery();
+ });
+
+ if (list.length === 1)
+ return list.join('');
+ else {
+ var str = list.join(this.operation() === 'or' ? ' | ' : ' & ');
+ return brackets ? '(' + str + ')' : str;
+ };
+ }
+ };
+ return docGroupClass;
+});
diff --git a/dev/js/src/vc/item.js b/dev/js/src/vc/item.js
new file mode 100644
index 0000000..bfefe38
--- /dev/null
+++ b/dev/js/src/vc/item.js
@@ -0,0 +1,44 @@
+// Field menu item
+define(['menu/item'], function (itemClass) {
+ return {
+ create : function (params) {
+ return Object.create(itemClass)
+ .upgradeTo(this)
+ ._init(params);
+ },
+
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._value = params[1];
+ this._type = params[2];
+
+ this._lcField = ' ' + this._name.toLowerCase();
+
+ return this;
+ },
+
+ name : function () {
+ return this._name;
+ },
+
+ type : function () {
+ return this._type;
+ },
+
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+ li.setAttribute("data-type", this._type);
+ li.setAttribute("data-value", this._value);
+ li.appendChild(document.createTextNode(this._name));
+ return this._element = li;
+ }
+ };
+});
diff --git a/dev/js/src/vc/jsonld.js b/dev/js/src/vc/jsonld.js
new file mode 100644
index 0000000..324e7db
--- /dev/null
+++ b/dev/js/src/vc/jsonld.js
@@ -0,0 +1,99 @@
+/**
+ * Abstract JsonLD criterion object
+ */
+define(['vc/operators'], function (operatorsClass) {
+ return {
+ __changed : false,
+
+ create : function () {
+ return Object.create(this);
+ },
+
+ /**
+ * Upgrade this object to another object
+ * while private data stays intact
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ ldType : function (type) {
+ if (arguments.length === 1)
+ this._ldType = type;
+ return this._ldType;
+ },
+
+ parent : function (obj) {
+ if (arguments.length === 1) {
+ this._parent = obj;
+ this.__changed = true;
+ };
+ return this._parent;
+ },
+
+ // Destroy object - especially for
+ // acyclic structures!
+ // I'm paranoid!
+ destroy : function () {
+ if (this._ops != undefined) {
+ this._ops._parent = undefined;
+ if (this._ops._element !== undefined)
+ this._ops._element.refTo = undefined;
+ this._ops = undefined;
+ };
+ if (this._element !== undefined)
+ this._element = undefined;
+
+ // In case of a group, destroy all operands
+ if (this._operands !== undefined) {
+ for (var i = 0; i < this._operands.length; i++)
+ this.getOperand(i).destroy();
+ this._operands = [];
+ };
+ },
+
+ // Wrap a new operation around the root group element
+ wrapOnRoot : function (op) {
+ var parent = this.parent();
+
+ var group = require('vc/docgroup').create(parent);
+ if (arguments.length === 1)
+ group.operation(op);
+ else
+ group.operation(
+ this.operation() === 'and' ? 'or' : 'and'
+ );
+ group.append(this);
+ this.parent(group);
+ group.append();
+ group.element(); // Init (seems to be necessary)
+ parent.root(group);
+ return this.parent();
+ },
+
+ // Be aware! This may be cyclic
+ operators : function (and, or, del) {
+ if (arguments === 0)
+ return this._ops;
+ this._ops = operatorsClass.create(
+ and, or, del
+ );
+ this._ops.parent(this);
+ return this._ops;
+ },
+
+ toJson : function () {
+ return {
+ // Unspecified object
+ "@type" : "koral:" + this.ldType()
+ };
+ },
+
+ toQuery : function () {
+ return '';
+ }
+ };
+});
diff --git a/dev/js/src/vc/menu.js b/dev/js/src/vc/menu.js
new file mode 100644
index 0000000..6f6d2bd
--- /dev/null
+++ b/dev/js/src/vc/menu.js
@@ -0,0 +1,10 @@
+// Field menu
+define(['menu', 'menu/item'], function (menuClass, itemClass) {
+ return {
+ create : function (params) {
+ return Object.create(menuClass)
+ .upgradeTo(this)
+ ._init(itemClass, undefined, params)
+ }
+ };
+});
diff --git a/dev/js/src/vc/operators.js b/dev/js/src/vc/operators.js
new file mode 100644
index 0000000..312f75d
--- /dev/null
+++ b/dev/js/src/vc/operators.js
@@ -0,0 +1,168 @@
+/**
+ * Operators for criteria
+ */
+define(['util'], function () {
+
+ var loc = KorAP.Locale;
+ loc.AND = loc.AND || 'and';
+ loc.OR = loc.OR || 'or';
+ loc.DEL = loc.DEL || '×';
+
+
+ // Utility for analysing boolean values
+ function _bool (bool) {
+ return (bool === undefined || bool === null || bool === false) ? false : true;
+ };
+
+
+ // Add new unspecified document
+ function _add (obj, type) {
+ var ref = obj.parentNode.refTo;
+ var parent = ref.parent();
+
+ if (ref.ldType() === 'docGroup') {
+
+ // Check that the action differs from the type
+ if (ref.operation() === type)
+ return;
+
+ if (parent.ldType() !== null) {
+ return parent.newAfter(ref);
+ }
+ else {
+ // The group is on root - wrap
+ return ref.wrapOnRoot();
+ };
+ }
+ else if (ref.ldType() === 'doc') {
+
+ if (parent.ldType() === null) {
+ return ref.wrapOnRoot(type);
+ }
+ else if (parent.operation() === type) {
+ return parent.newAfter(ref);
+ }
+ else {
+ return ref.wrap(type);
+ };
+ };
+ };
+
+
+ // Add doc with 'and' relation
+ var _and = function () {
+ return _add(this, 'and');
+ };
+
+
+ // Add doc with 'or' relation
+ var _or = function () {
+ return _add(this, 'or');
+ };
+
+
+ // Remove doc or docGroup
+ var _delete = function () {
+ var ref = this.parentNode.refTo;
+ if (ref.parent().ldType() !== null) {
+ return ref.parent().delOperand(ref).update();
+ }
+ else {
+ ref.parent().clean();
+ };
+ };
+
+
+ return {
+ create : function (and, or, del) {
+ var op = Object.create(this);
+ op.and(and);
+ op.or(or);
+ op.del(del);
+ return op;
+ },
+
+ update : function () {
+ // Init the element
+ if (this._element === undefined)
+ return this.element();
+
+ var op = this._element;
+
+ op.refTo = this.parent();
+
+ // Remove everything underneath
+ _removeChildren(op);
+
+ // Add and button
+ if (this._and === true) {
+ var andE = document.createElement('span');
+ andE.setAttribute('class', 'and');
+ andE.addEventListener('click', _and, false);
+ andE.appendChild(
+ document.createTextNode(loc.AND)
+ );
+ op.appendChild(andE);
+ };
+
+ // Add or button
+ if (this._or === true) {
+ var orE = document.createElement('span');
+ orE.setAttribute('class', 'or');
+ orE.addEventListener('click', _or, false);
+ orE.appendChild(document.createTextNode(loc.OR));
+ op.appendChild(orE);
+ };
+
+ // Add delete button
+ if (this._del === true) {
+ var delE = document.createElement('span');
+ delE.setAttribute('class', 'delete');
+ delE.appendChild(document.createTextNode(loc.DEL));
+ delE.addEventListener('click', _delete, false);
+ op.appendChild(delE);
+ };
+
+ return op;
+ },
+
+ // Be aware! This may be cyclic
+ parent : function (obj) {
+ if (arguments.length === 1)
+ this._parent = obj;
+ return this._parent;
+ },
+
+ element : function () {
+
+ // Return existing element
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'operators');
+
+ // Init elements
+ this.update();
+ return this._element;
+ },
+
+ and : function (bool) {
+ if (arguments.length === 1)
+ this._and = _bool(bool);
+ return this._and;
+ },
+
+ or : function (bool) {
+ if (arguments.length === 1)
+ this._or = _bool(bool);
+ return this._or;
+ },
+
+ del : function (bool) {
+ if (arguments.length === 1)
+ this._del = _bool(bool);
+ return this._del;
+ }
+ };
+});
diff --git a/dev/js/src/vc/rewrite.js b/dev/js/src/vc/rewrite.js
new file mode 100644
index 0000000..79ba4b3
--- /dev/null
+++ b/dev/js/src/vc/rewrite.js
@@ -0,0 +1,100 @@
+/**
+ * Implementation of rewrite objects.
+ */
+define(['vc/jsonld', 'util'], function (jsonldClass) {
+
+ var _validRewriteOpRE = new RegExp("^(?:injec|modifica)tion$");
+ var _quote = new RegExp("([\"\\\\])", 'g');
+
+ return {
+ // Construction method
+ create : function (json) {
+ var obj = Object(jsonldClass).
+ create().
+ upgradeTo(this).
+ fromJson(json);
+ return obj;
+ },
+
+ // Get or set source
+ src : function (string) {
+ if (arguments.length === 1)
+ this._src = string;
+ return this._src;
+ },
+
+ // Get or set operation
+ operation : function (op) {
+ if (arguments.length === 1) {
+ if (_validRewriteOpRE.test(op)) {
+ this._op = op;
+ }
+ else {
+ KorAP.log(814, "Unknown rewrite operation");
+ return;
+ };
+ };
+ return this._op || 'injection';
+ },
+
+ // Get or set scope
+ scope : function (attr) {
+ if (arguments.length === 1)
+ this._scope = attr;
+ return this._scope;
+ },
+
+ // Serialize from Json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ // Missing @type
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ // Missing source
+ if (json["src"] === undefined ||
+ typeof json["src"] !== 'string') {
+ KorAP.log(815, "Rewrite expects source");
+ return;
+ };
+
+ // Set source
+ this.src(json["src"]);
+
+ // Set operation
+ if (json["operation"] !== undefined) {
+ var operation = json["operation"];
+ this.operation(operation.replace(/^operation:/,''));
+ };
+
+ // Set scope
+ if (json["scope"] !== undefined &&
+ typeof json["scope"] === 'string')
+ this.scope(json["scope"]);
+
+ return this;
+ },
+
+ toString : function () {
+ var str = '';
+ var op = this.operation();
+ str += op.charAt(0).toUpperCase() + op.slice(1);
+ str += ' of ' + (
+ this._scope === null ?
+ 'object' :
+ '"' +
+ this.scope().replace(_quote, '\\$1') +
+ '"'
+ );
+ str += ' by ' +
+ '"' +
+ this.src().replace(_quote, '\\$1') +
+ '"';
+ return str;
+ }
+ };
+});
diff --git a/dev/js/src/vc/rewritelist.js b/dev/js/src/vc/rewritelist.js
new file mode 100644
index 0000000..af3c05a
--- /dev/null
+++ b/dev/js/src/vc/rewritelist.js
@@ -0,0 +1,49 @@
+define(['vc/jsonld', 'vc/rewrite'], function (jsonldClass, rewriteClass) {
+ return {
+ // Construction method
+ create : function (json) {
+ var obj = Object(jsonldClass).
+ create().
+ upgradeTo(this).
+ fromJson(json);
+ return obj;
+ },
+ fromJson : function (json) {
+ this._list = new Array();
+ for (var i = 0; i < json.length; i++) {
+ this._list.push(
+ rewriteClass.create(json[i])
+ );
+ };
+ return this;
+ },
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'rewrite');
+ for (var x in this._list) {
+ var rewrite = this._list[x];
+ var span = document.createElement('span');
+
+ // Set class attribute
+ span.setAttribute('class', rewrite.operation());
+
+ // Append source information
+ span.appendChild(document.createTextNode(rewrite.src()));
+
+ // Append scope information
+ if (rewrite.scope() !== undefined) {
+ span.appendChild(
+ document.createTextNode(
+ ': ' + rewrite.scope()
+ )
+ );
+ };
+ this._element.appendChild(span);
+ };
+ return this._element;
+ }
+ };
+});
diff --git a/dev/js/src/vc/unspecified.js b/dev/js/src/vc/unspecified.js
new file mode 100644
index 0000000..9efd007
--- /dev/null
+++ b/dev/js/src/vc/unspecified.js
@@ -0,0 +1,89 @@
+/**
+ * Unspecified criterion
+ */
+define(['vc/jsonld', 'vc/doc', 'util'], function (jsonldClass, docClass) {
+
+ var loc = KorAP.Locale;
+ loc.EMPTY = loc.EMPTY || '⋯';
+
+ return {
+ _ldType : "non",
+ create : function (parent) {
+ var obj = Object.create(jsonldClass).
+ upgradeTo(this);
+
+ if (parent !== undefined)
+ obj._parent = parent;
+
+ return obj;
+ },
+
+ // Set key - replace
+ key : function (v) {
+
+ // Not replaceable
+ if (this._parent === undefined)
+ return null;
+
+ // Set JSON-LD type
+ var newDoc = docClass.create(this._parent, {
+ "@type" : "koral:doc",
+ "value" : "",
+ "key" : v
+ });
+
+ // Unspecified document on root
+ if (this._parent.ldType() === null) {
+ this._parent.root(newDoc);
+ this.destroy();
+ }
+
+ // Unspecified document in group
+ else {
+ this._parent.replaceOperand(this, newDoc);
+ };
+ this._parent.update();
+ return newDoc;
+ },
+
+ update : function () {
+
+ if (this._element === undefined)
+ return this.element();
+
+ // Remove element content
+ _removeChildren(this._element);
+
+ var ellipsis = document.createElement('span');
+ ellipsis.appendChild(document.createTextNode(loc.EMPTY));
+ this._element.appendChild(ellipsis);
+
+ // Set ref - TODO: Cleanup!
+ this._element.refTo = this;
+
+ // Set operators
+ if (this._parent !== undefined && this.parent().ldType() !== null) {
+ var op = this.operators(
+ false,
+ false,
+ true
+ );
+
+ this._element.appendChild(
+ op.element()
+ );
+ };
+
+ return this.element();
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'doc unspecified');
+ this.update();
+ return this._element;
+ },
+ };
+});