2 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/9eeb924b5204/ Changeset: 9eeb924b5204 User: dannon Date: 2015-02-05 13:12:54+00:00 Summary: Add lunr library for toolbox (and potentially more things in the future) search. Affected #: 5 files diff -r 85b1de6a6ebdfe30e14b96f8dc7d13105ae04c2b -r 9eeb924b5204ff10bac0998612549de468f9bc2a client/GruntFile.js --- a/client/GruntFile.js +++ b/client/GruntFile.js @@ -69,11 +69,11 @@ libraryLocations : { "jquery": [ "dist/jquery.js", "jquery/jquery.js" ], "jquery-migrate": [ "jquery-migrate.js", "jquery/jquery.migrate.js" ], - "traceKit": [ "tracekit.js", "tracekit.js" ], "ravenjs": [ "dist/raven.js", "raven.js" ], "underscore": [ "underscore.js", "underscore.js" ], - "handlebars": [ "handlebars.runtime.js", "handlebars.runtime.js" ] + "handlebars": [ "handlebars.runtime.js", "handlebars.runtime.js" ], + "lunr.js": [ "lunr.js", "lunr.js" ] //"backbone": [ "backbone.js", "backbone/backbone.js" ], // these need to be updated and tested diff -r 85b1de6a6ebdfe30e14b96f8dc7d13105ae04c2b -r 9eeb924b5204ff10bac0998612549de468f9bc2a client/bower.json --- a/client/bower.json +++ b/client/bower.json @@ -32,7 +32,8 @@ "jquery-ui": "git://github.com/jquery/jquery-ui.git#~1.11.2", "jquery.event.drag-drop": "~2.2.1", "handlebars": "~2.0.0", - "jquery-migrate": "~1.2.1" + "jquery-migrate": "~1.2.1", + "lunr.js": "git://github.com/olivernn/lunr.js.git#~0.5.7" }, "resolutions": { "jquery": "~1.11.1" diff -r 85b1de6a6ebdfe30e14b96f8dc7d13105ae04c2b -r 9eeb924b5204ff10bac0998612549de468f9bc2a client/galaxy/scripts/libs/lunr.js --- /dev/null +++ b/client/galaxy/scripts/libs/lunr.js @@ -0,0 +1,1910 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.7 + * Copyright (C) 2014 Oliver Nightingale + * MIT Licensed + * @license + */ + +(function(){ + +/** + * Convenience function for instantiating a new lunr index and configuring it + * with the default pipeline functions and the passed config function. + * + * When using this convenience function a new index will be created with the + * following functions already in the pipeline: + * + * lunr.StopWordFilter - filters out any stop words before they enter the + * index + * + * lunr.stemmer - stems the tokens before entering the index. + * + * Example: + * + * var idx = lunr(function () { + * this.field('title', 10) + * this.field('tags', 100) + * this.field('body') + * + * this.ref('cid') + * + * this.pipeline.add(function () { + * // some custom pipeline function + * }) + * + * }) + * + * @param {Function} config A function that will be called with the new instance + * of the lunr.Index as both its context and first parameter. It can be used to + * customize the instance of new lunr.Index. + * @namespace + * @module + * @returns {lunr.Index} + * + */ +var lunr = function (config) { + var idx = new lunr.Index + + idx.pipeline.add( + lunr.trimmer, + lunr.stopWordFilter, + lunr.stemmer + ) + + if (config) config.call(idx, idx) + + return idx +} + +lunr.version = "0.5.7" +/*! + * lunr.utils + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * A namespace containing utils for the rest of the lunr library + */ +lunr.utils = {} + +/** + * Print a warning message to the console. + * + * @param {String} message The message to be printed. + * @memberOf Utils + */ +lunr.utils.warn = (function (global) { + return function (message) { + if (global.console && console.warn) { + console.warn(message) + } + } +})(this) + +/*! + * lunr.EventEmitter + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers. + * + * @constructor + */ +lunr.EventEmitter = function () { + this.events = {} +} + +/** + * Binds a handler function to a specific event(s). + * + * Can bind a single function to many different events in one call. + * + * @param {String} [eventName] The name(s) of events to bind this function to. + * @param {Function} handler The function to call when an event is fired. + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.addListener = function () { + var args = Array.prototype.slice.call(arguments), + fn = args.pop(), + names = args + + if (typeof fn !== "function") throw new TypeError ("last argument must be a function") + + names.forEach(function (name) { + if (!this.hasHandler(name)) this.events[name] = [] + this.events[name].push(fn) + }, this) +} + +/** + * Removes a handler function from a specific event. + * + * @param {String} eventName The name of the event to remove this function from. + * @param {Function} handler The function to remove from an event. + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.removeListener = function (name, fn) { + if (!this.hasHandler(name)) return + + var fnIndex = this.events[name].indexOf(fn) + this.events[name].splice(fnIndex, 1) + + if (!this.events[name].length) delete this.events[name] +} + +/** + * Calls all functions bound to the given event. + * + * Additional data can be passed to the event handler as arguments to `emit` + * after the event name. + * + * @param {String} eventName The name of the event to emit. + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.emit = function (name) { + if (!this.hasHandler(name)) return + + var args = Array.prototype.slice.call(arguments, 1) + + this.events[name].forEach(function (fn) { + fn.apply(undefined, args) + }) +} + +/** + * Checks whether a handler has ever been stored against an event. + * + * @param {String} eventName The name of the event to check. + * @private + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.hasHandler = function (name) { + return name in this.events +} + +/*! + * lunr.tokenizer + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * A function for splitting a string into tokens ready to be inserted into + * the search index. + * + * @module + * @param {String} obj The string to convert into tokens + * @returns {Array} + */ +lunr.tokenizer = function (obj) { + if (!arguments.length || obj == null || obj == undefined) return [] + if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() }) + + var str = obj.toString().replace(/^\s+/, '') + + for (var i = str.length - 1; i >= 0; i--) { + if (/\S/.test(str.charAt(i))) { + str = str.substring(0, i + 1) + break + } + } + + return str + .split(/(?:\s+|\-)/) + .filter(function (token) { + return !!token + }) + .map(function (token) { + return token.toLowerCase() + }) +} +/*! + * lunr.Pipeline + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Pipelines maintain an ordered list of functions to be applied to all + * tokens in documents entering the search index and queries being ran against + * the index. + * + * An instance of lunr.Index created with the lunr shortcut will contain a + * pipeline with a stop word filter and an English language stemmer. Extra + * functions can be added before or after either of these functions or these + * default functions can be removed. + * + * When run the pipeline will call each function in turn, passing a token, the + * index of that token in the original list of all tokens and finally a list of + * all the original tokens. + * + * The output of functions in the pipeline will be passed to the next function + * in the pipeline. To exclude a token from entering the index the function + * should return undefined, the rest of the pipeline will not be called with + * this token. + * + * For serialisation of pipelines to work, all functions used in an instance of + * a pipeline should be registered with lunr.Pipeline. Registered functions can + * then be loaded. If trying to load a serialised pipeline that uses functions + * that are not registered an error will be thrown. + * + * If not planning on serialising the pipeline then registering pipeline functions + * is not necessary. + * + * @constructor + */ +lunr.Pipeline = function () { + this._stack = [] +} + +lunr.Pipeline.registeredFunctions = {} + +/** + * Register a function with the pipeline. + * + * Functions that are used in the pipeline should be registered if the pipeline + * needs to be serialised, or a serialised pipeline needs to be loaded. + * + * Registering a function does not add it to a pipeline, functions must still be + * added to instances of the pipeline for them to be used when running a pipeline. + * + * @param {Function} fn The function to check for. + * @param {String} label The label to register this function with + * @memberOf Pipeline + */ +lunr.Pipeline.registerFunction = function (fn, label) { + if (label in this.registeredFunctions) { + lunr.utils.warn('Overwriting existing registered function: ' + label) + } + + fn.label = label + lunr.Pipeline.registeredFunctions[fn.label] = fn +} + +/** + * Warns if the function is not registered as a Pipeline function. + * + * @param {Function} fn The function to check for. + * @private + * @memberOf Pipeline + */ +lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { + var isRegistered = fn.label && (fn.label in this.registeredFunctions) + + if (!isRegistered) { + lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) + } +} + +/** + * Loads a previously serialised pipeline. + * + * All functions to be loaded must already be registered with lunr.Pipeline. + * If any function from the serialised data has not been registered then an + * error will be thrown. + * + * @param {Object} serialised The serialised pipeline to load. + * @returns {lunr.Pipeline} + * @memberOf Pipeline + */ +lunr.Pipeline.load = function (serialised) { + var pipeline = new lunr.Pipeline + + serialised.forEach(function (fnName) { + var fn = lunr.Pipeline.registeredFunctions[fnName] + + if (fn) { + pipeline.add(fn) + } else { + throw new Error ('Cannot load un-registered function: ' + fnName) + } + }) + + return pipeline +} + +/** + * Adds new functions to the end of the pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {Function} functions Any number of functions to add to the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.add = function () { + var fns = Array.prototype.slice.call(arguments) + + fns.forEach(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + this._stack.push(fn) + }, this) +} + +/** + * Adds a single function after a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {Function} existingFn A function that already exists in the pipeline. + * @param {Function} newFn The new function to add to the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.after = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + 1 + this._stack.splice(pos, 0, newFn) +} + +/** + * Adds a single function before a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {Function} existingFn A function that already exists in the pipeline. + * @param {Function} newFn The new function to add to the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.before = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + this._stack.splice(pos, 0, newFn) +} + +/** + * Removes a function from the pipeline. + * + * @param {Function} fn The function to remove from the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.remove = function (fn) { + var pos = this._stack.indexOf(fn) + this._stack.splice(pos, 1) +} + +/** + * Runs the current list of functions that make up the pipeline against the + * passed tokens. + * + * @param {Array} tokens The tokens to run through the pipeline. + * @returns {Array} + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.run = function (tokens) { + var out = [], + tokenLength = tokens.length, + stackLength = this._stack.length + + for (var i = 0; i < tokenLength; i++) { + var token = tokens[i] + + for (var j = 0; j < stackLength; j++) { + token = this._stack[j](token, i, tokens) + if (token === void 0) break + }; + + if (token !== void 0) out.push(token) + }; + + return out +} + +/** + * Resets the pipeline by removing any existing processors. + * + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.reset = function () { + this._stack = [] +} + +/** + * Returns a representation of the pipeline ready for serialisation. + * + * Logs a warning if the function has not been registered. + * + * @returns {Array} + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.toJSON = function () { + return this._stack.map(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + + return fn.label + }) +} +/*! + * lunr.Vector + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Vectors implement vector related operations for + * a series of elements. + * + * @constructor + */ +lunr.Vector = function () { + this._magnitude = null + this.list = undefined + this.length = 0 +} + +/** + * lunr.Vector.Node is a simple struct for each node + * in a lunr.Vector. + * + * @private + * @param {Number} The index of the node in the vector. + * @param {Object} The data at this node in the vector. + * @param {lunr.Vector.Node} The node directly after this node in the vector. + * @constructor + * @memberOf Vector + */ +lunr.Vector.Node = function (idx, val, next) { + this.idx = idx + this.val = val + this.next = next +} + +/** + * Inserts a new value at a position in a vector. + * + * @param {Number} The index at which to insert a value. + * @param {Object} The object to insert in the vector. + * @memberOf Vector. + */ +lunr.Vector.prototype.insert = function (idx, val) { + var list = this.list + + if (!list) { + this.list = new lunr.Vector.Node (idx, val, list) + return this.length++ + } + + var prev = list, + next = list.next + + while (next != undefined) { + if (idx < next.idx) { + prev.next = new lunr.Vector.Node (idx, val, next) + return this.length++ + } + + prev = next, next = next.next + } + + prev.next = new lunr.Vector.Node (idx, val, next) + return this.length++ +} + +/** + * Calculates the magnitude of this vector. + * + * @returns {Number} + * @memberOf Vector + */ +lunr.Vector.prototype.magnitude = function () { + if (this._magniture) return this._magnitude + var node = this.list, + sumOfSquares = 0, + val + + while (node) { + val = node.val + sumOfSquares += val * val + node = node.next + } + + return this._magnitude = Math.sqrt(sumOfSquares) +} + +/** + * Calculates the dot product of this vector and another vector. + * + * @param {lunr.Vector} otherVector The vector to compute the dot product with. + * @returns {Number} + * @memberOf Vector + */ +lunr.Vector.prototype.dot = function (otherVector) { + var node = this.list, + otherNode = otherVector.list, + dotProduct = 0 + + while (node && otherNode) { + if (node.idx < otherNode.idx) { + node = node.next + } else if (node.idx > otherNode.idx) { + otherNode = otherNode.next + } else { + dotProduct += node.val * otherNode.val + node = node.next + otherNode = otherNode.next + } + } + + return dotProduct +} + +/** + * Calculates the cosine similarity between this vector and another + * vector. + * + * @param {lunr.Vector} otherVector The other vector to calculate the + * similarity with. + * @returns {Number} + * @memberOf Vector + */ +lunr.Vector.prototype.similarity = function (otherVector) { + return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude()) +} +/*! + * lunr.SortedSet + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.SortedSets are used to maintain an array of uniq values in a sorted + * order. + * + * @constructor + */ +lunr.SortedSet = function () { + this.length = 0 + this.elements = [] +} + +/** + * Loads a previously serialised sorted set. + * + * @param {Array} serialisedData The serialised set to load. + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.load = function (serialisedData) { + var set = new this + + set.elements = serialisedData + set.length = serialisedData.length + + return set +} + +/** + * Inserts new items into the set in the correct position to maintain the + * order. + * + * @param {Object} The objects to add to this set. + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.add = function () { + Array.prototype.slice.call(arguments).forEach(function (element) { + if (~this.indexOf(element)) return + this.elements.splice(this.locationFor(element), 0, element) + }, this) + + this.length = this.elements.length +} + +/** + * Converts this sorted set into an array. + * + * @returns {Array} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.toArray = function () { + return this.elements.slice() +} + +/** + * Creates a new array with the results of calling a provided function on every + * element in this sorted set. + * + * Delegates to Array.prototype.map and has the same signature. + * + * @param {Function} fn The function that is called on each element of the + * set. + * @param {Object} ctx An optional object that can be used as the context + * for the function fn. + * @returns {Array} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.map = function (fn, ctx) { + return this.elements.map(fn, ctx) +} + +/** + * Executes a provided function once per sorted set element. + * + * Delegates to Array.prototype.forEach and has the same signature. + * + * @param {Function} fn The function that is called on each element of the + * set. + * @param {Object} ctx An optional object that can be used as the context + * @memberOf SortedSet + * for the function fn. + */ +lunr.SortedSet.prototype.forEach = function (fn, ctx) { + return this.elements.forEach(fn, ctx) +} + +/** + * Returns the index at which a given element can be found in the + * sorted set, or -1 if it is not present. + * + * @param {Object} elem The object to locate in the sorted set. + * @param {Number} start An optional index at which to start searching from + * within the set. + * @param {Number} end An optional index at which to stop search from within + * the set. + * @returns {Number} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.indexOf = function (elem, start, end) { + var start = start || 0, + end = end || this.elements.length, + sectionLength = end - start, + pivot = start + Math.floor(sectionLength / 2), + pivotElem = this.elements[pivot] + + if (sectionLength <= 1) { + if (pivotElem === elem) { + return pivot + } else { + return -1 + } + } + + if (pivotElem < elem) return this.indexOf(elem, pivot, end) + if (pivotElem > elem) return this.indexOf(elem, start, pivot) + if (pivotElem === elem) return pivot +} + +/** + * Returns the position within the sorted set that an element should be + * inserted at to maintain the current order of the set. + * + * This function assumes that the element to search for does not already exist + * in the sorted set. + * + * @param {Object} elem The elem to find the position for in the set + * @param {Number} start An optional index at which to start searching from + * within the set. + * @param {Number} end An optional index at which to stop search from within + * the set. + * @returns {Number} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.locationFor = function (elem, start, end) { + var start = start || 0, + end = end || this.elements.length, + sectionLength = end - start, + pivot = start + Math.floor(sectionLength / 2), + pivotElem = this.elements[pivot] + + if (sectionLength <= 1) { + if (pivotElem > elem) return pivot + if (pivotElem < elem) return pivot + 1 + } + + if (pivotElem < elem) return this.locationFor(elem, pivot, end) + if (pivotElem > elem) return this.locationFor(elem, start, pivot) +} + +/** + * Creates a new lunr.SortedSet that contains the elements in the intersection + * of this set and the passed set. + * + * @param {lunr.SortedSet} otherSet The set to intersect with this set. + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.intersect = function (otherSet) { + var intersectSet = new lunr.SortedSet, + i = 0, j = 0, + a_len = this.length, b_len = otherSet.length, + a = this.elements, b = otherSet.elements + + while (true) { + if (i > a_len - 1 || j > b_len - 1) break + + if (a[i] === b[j]) { + intersectSet.add(a[i]) + i++, j++ + continue + } + + if (a[i] < b[j]) { + i++ + continue + } + + if (a[i] > b[j]) { + j++ + continue + } + }; + + return intersectSet +} + +/** + * Makes a copy of this set + * + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.clone = function () { + var clone = new lunr.SortedSet + + clone.elements = this.toArray() + clone.length = clone.elements.length + + return clone +} + +/** + * Creates a new lunr.SortedSet that contains the elements in the union + * of this set and the passed set. + * + * @param {lunr.SortedSet} otherSet The set to union with this set. + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.union = function (otherSet) { + var longSet, shortSet, unionSet + + if (this.length >= otherSet.length) { + longSet = this, shortSet = otherSet + } else { + longSet = otherSet, shortSet = this + } + + unionSet = longSet.clone() + + unionSet.add.apply(unionSet, shortSet.toArray()) + + return unionSet +} + +/** + * Returns a representation of the sorted set ready for serialisation. + * + * @returns {Array} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.toJSON = function () { + return this.toArray() +} +/*! + * lunr.Index + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Index is object that manages a search index. It contains the indexes + * and stores all the tokens and document lookups. It also provides the main + * user facing API for the library. + * + * @constructor + */ +lunr.Index = function () { + this._fields = [] + this._ref = 'id' + this.pipeline = new lunr.Pipeline + this.documentStore = new lunr.Store + this.tokenStore = new lunr.TokenStore + this.corpusTokens = new lunr.SortedSet + this.eventEmitter = new lunr.EventEmitter + + this._idfCache = {} + + this.on('add', 'remove', 'update', (function () { + this._idfCache = {} + }).bind(this)) +} + +/** + * Bind a handler to events being emitted by the index. + * + * The handler can be bound to many events at the same time. + * + * @param {String} [eventName] The name(s) of events to bind the function to. + * @param {Function} handler The serialised set to load. + * @memberOf Index + */ +lunr.Index.prototype.on = function () { + var args = Array.prototype.slice.call(arguments) + return this.eventEmitter.addListener.apply(this.eventEmitter, args) +} + +/** + * Removes a handler from an event being emitted by the index. + * + * @param {String} eventName The name of events to remove the function from. + * @param {Function} handler The serialised set to load. + * @memberOf Index + */ +lunr.Index.prototype.off = function (name, fn) { + return this.eventEmitter.removeListener(name, fn) +} + +/** + * Loads a previously serialised index. + * + * Issues a warning if the index being imported was serialised + * by a different version of lunr. + * + * @param {Object} serialisedData The serialised set to load. + * @returns {lunr.Index} + * @memberOf Index + */ +lunr.Index.load = function (serialisedData) { + if (serialisedData.version !== lunr.version) { + lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version) + } + + var idx = new this + + idx._fields = serialisedData.fields + idx._ref = serialisedData.ref + + idx.documentStore = lunr.Store.load(serialisedData.documentStore) + idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore) + idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens) + idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline) + + return idx +} + +/** + * Adds a field to the list of fields that will be searchable within documents + * in the index. + * + * An optional boost param can be passed to affect how much tokens in this field + * rank in search results, by default the boost value is 1. + * + * Fields should be added before any documents are added to the index, fields + * that are added after documents are added to the index will only apply to new + * documents added to the index. + * + * @param {String} fieldName The name of the field within the document that + * should be indexed + * @param {Number} boost An optional boost that can be applied to terms in this + * field. + * @returns {lunr.Index} + * @memberOf Index + */ +lunr.Index.prototype.field = function (fieldName, opts) { + var opts = opts || {}, + field = { name: fieldName, boost: opts.boost || 1 } + + this._fields.push(field) + return this +} + +/** + * Sets the property used to uniquely identify documents added to the index, + * by default this property is 'id'. + * + * This should only be changed before adding documents to the index, changing + * the ref property without resetting the index can lead to unexpected results. + * + * @param {String} refName The property to use to uniquely identify the + * documents in the index. + * @param {Boolean} emitEvent Whether to emit add events, defaults to true + * @returns {lunr.Index} + * @memberOf Index + */ +lunr.Index.prototype.ref = function (refName) { + this._ref = refName + return this +} + +/** + * Add a document to the index. + * + * This is the way new documents enter the index, this function will run the + * fields from the document through the index's pipeline and then add it to + * the index, it will then show up in search results. + * + * An 'add' event is emitted with the document that has been added and the index + * the document has been added to. This event can be silenced by passing false + * as the second argument to add. + * + * @param {Object} doc The document to add to the index. + * @param {Boolean} emitEvent Whether or not to emit events, default true. + * @memberOf Index + */ +lunr.Index.prototype.add = function (doc, emitEvent) { + var docTokens = {}, + allDocumentTokens = new lunr.SortedSet, + docRef = doc[this._ref], + emitEvent = emitEvent === undefined ? true : emitEvent + + this._fields.forEach(function (field) { + var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name])) + + docTokens[field.name] = fieldTokens + lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens) + }, this) + + this.documentStore.set(docRef, allDocumentTokens) + lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray()) + + for (var i = 0; i < allDocumentTokens.length; i++) { + var token = allDocumentTokens.elements[i] + var tf = this._fields.reduce(function (memo, field) { + var fieldLength = docTokens[field.name].length + + if (!fieldLength) return memo + + var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length + + return memo + (tokenCount / fieldLength * field.boost) + }, 0) + + this.tokenStore.add(token, { ref: docRef, tf: tf }) + }; + + if (emitEvent) this.eventEmitter.emit('add', doc, this) +} + +/** + * Removes a document from the index. + * + * To make sure documents no longer show up in search results they can be + * removed from the index using this method. + * + * The document passed only needs to have the same ref property value as the + * document that was added to the index, they could be completely different + * objects. + * + * A 'remove' event is emitted with the document that has been removed and the index + * the document has been removed from. This event can be silenced by passing false + * as the second argument to remove. + * + * @param {Object} doc The document to remove from the index. + * @param {Boolean} emitEvent Whether to emit remove events, defaults to true + * @memberOf Index + */ +lunr.Index.prototype.remove = function (doc, emitEvent) { + var docRef = doc[this._ref], + emitEvent = emitEvent === undefined ? true : emitEvent + + if (!this.documentStore.has(docRef)) return + + var docTokens = this.documentStore.get(docRef) + + this.documentStore.remove(docRef) + + docTokens.forEach(function (token) { + this.tokenStore.remove(token, docRef) + }, this) + + if (emitEvent) this.eventEmitter.emit('remove', doc, this) +} + +/** + * Updates a document in the index. + * + * When a document contained within the index gets updated, fields changed, + * added or removed, to make sure it correctly matched against search queries, + * it should be updated in the index. + * + * This method is just a wrapper around `remove` and `add` + * + * An 'update' event is emitted with the document that has been updated and the index. + * This event can be silenced by passing false as the second argument to update. Only + * an update event will be fired, the 'add' and 'remove' events of the underlying calls + * are silenced. + * + * @param {Object} doc The document to update in the index. + * @param {Boolean} emitEvent Whether to emit update events, defaults to true + * @see Index.prototype.remove + * @see Index.prototype.add + * @memberOf Index + */ +lunr.Index.prototype.update = function (doc, emitEvent) { + var emitEvent = emitEvent === undefined ? true : emitEvent + + this.remove(doc, false) + this.add(doc, false) + + if (emitEvent) this.eventEmitter.emit('update', doc, this) +} + +/** + * Calculates the inverse document frequency for a token within the index. + * + * @param {String} token The token to calculate the idf of. + * @see Index.prototype.idf + * @private + * @memberOf Index + */ +lunr.Index.prototype.idf = function (term) { + var cacheKey = "@" + term + if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey] + + var documentFrequency = this.tokenStore.count(term), + idf = 1 + + if (documentFrequency > 0) { + idf = 1 + Math.log(this.tokenStore.length / documentFrequency) + } + + return this._idfCache[cacheKey] = idf +} + +/** + * Searches the index using the passed query. + * + * Queries should be a string, multiple words are allowed and will lead to an + * AND based query, e.g. `idx.search('foo bar')` will run a search for + * documents containing both 'foo' and 'bar'. + * + * All query tokens are passed through the same pipeline that document tokens + * are passed through, so any language processing involved will be run on every + * query term. + * + * Each query term is expanded, so that the term 'he' might be expanded to + * 'hello' and 'help' if those terms were already included in the index. + * + * Matching documents are returned as an array of objects, each object contains + * the matching document ref, as set for this index, and the similarity score + * for this document against the query. + * + * @param {String} query The query to search the index with. + * @returns {Object} + * @see Index.prototype.idf + * @see Index.prototype.documentVector + * @memberOf Index + */ +lunr.Index.prototype.search = function (query) { + var queryTokens = this.pipeline.run(lunr.tokenizer(query)), + queryVector = new lunr.Vector, + documentSets = [], + fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0) + + var hasSomeToken = queryTokens.some(function (token) { + return this.tokenStore.has(token) + }, this) + + if (!hasSomeToken) return [] + + queryTokens + .forEach(function (token, i, tokens) { + var tf = 1 / tokens.length * this._fields.length * fieldBoosts, + self = this + + var set = this.tokenStore.expand(token).reduce(function (memo, key) { + var pos = self.corpusTokens.indexOf(key), + idf = self.idf(key), + similarityBoost = 1, + set = new lunr.SortedSet + + // if the expanded key is not an exact match to the token then + // penalise the score for this key by how different the key is + // to the token. + if (key !== token) { + var diff = Math.max(3, key.length - token.length) + similarityBoost = 1 / Math.log(diff) + } + + // calculate the query tf-idf score for this token + // applying an similarityBoost to ensure exact matches + // these rank higher than expanded terms + if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost) + + // add all the documents that have this key into a set + Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) }) + + return memo.union(set) + }, new lunr.SortedSet) + + documentSets.push(set) + }, this) + + var documentSet = documentSets.reduce(function (memo, set) { + return memo.intersect(set) + }) + + return documentSet + .map(function (ref) { + return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) } + }, this) + .sort(function (a, b) { + return b.score - a.score + }) +} + +/** + * Generates a vector containing all the tokens in the document matching the + * passed documentRef. + * + * The vector contains the tf-idf score for each token contained in the + * document with the passed documentRef. The vector will contain an element + * for every token in the indexes corpus, if the document does not contain that + * token the element will be 0. + * + * @param {Object} documentRef The ref to find the document with. + * @returns {lunr.Vector} + * @private + * @memberOf Index + */ +lunr.Index.prototype.documentVector = function (documentRef) { + var documentTokens = this.documentStore.get(documentRef), + documentTokensLength = documentTokens.length, + documentVector = new lunr.Vector + + for (var i = 0; i < documentTokensLength; i++) { + var token = documentTokens.elements[i], + tf = this.tokenStore.get(token)[documentRef].tf, + idf = this.idf(token) + + documentVector.insert(this.corpusTokens.indexOf(token), tf * idf) + }; + + return documentVector +} + +/** + * Returns a representation of the index ready for serialisation. + * + * @returns {Object} + * @memberOf Index + */ +lunr.Index.prototype.toJSON = function () { + return { + version: lunr.version, + fields: this._fields, + ref: this._ref, + documentStore: this.documentStore.toJSON(), + tokenStore: this.tokenStore.toJSON(), + corpusTokens: this.corpusTokens.toJSON(), + pipeline: this.pipeline.toJSON() + } +} + +/** + * Applies a plugin to the current index. + * + * A plugin is a function that is called with the index as its context. + * Plugins can be used to customise or extend the behaviour the index + * in some way. A plugin is just a function, that encapsulated the custom + * behaviour that should be applied to the index. + * + * The plugin function will be called with the index as its argument, additional + * arguments can also be passed when calling use. The function will be called + * with the index as its context. + * + * Example: + * + * var myPlugin = function (idx, arg1, arg2) { + * // `this` is the index to be extended + * // apply any extensions etc here. + * } + * + * var idx = lunr(function () { + * this.use(myPlugin, 'arg1', 'arg2') + * }) + * + * @param {Function} plugin The plugin to apply. + * @memberOf Index + */ +lunr.Index.prototype.use = function (plugin) { + var args = Array.prototype.slice.call(arguments, 1) + args.unshift(this) + plugin.apply(this, args) +} +/*! + * lunr.Store + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Store is a simple key-value store used for storing sets of tokens for + * documents stored in index. + * + * @constructor + * @module + */ +lunr.Store = function () { + this.store = {} + this.length = 0 +} + +/** + * Loads a previously serialised store + * + * @param {Object} serialisedData The serialised store to load. + * @returns {lunr.Store} + * @memberOf Store + */ +lunr.Store.load = function (serialisedData) { + var store = new this + + store.length = serialisedData.length + store.store = Object.keys(serialisedData.store).reduce(function (memo, key) { + memo[key] = lunr.SortedSet.load(serialisedData.store[key]) + return memo + }, {}) + + return store +} + +/** + * Stores the given tokens in the store against the given id. + * + * @param {Object} id The key used to store the tokens against. + * @param {Object} tokens The tokens to store against the key. + * @memberOf Store + */ +lunr.Store.prototype.set = function (id, tokens) { + if (!this.has(id)) this.length++ + this.store[id] = tokens +} + +/** + * Retrieves the tokens from the store for a given key. + * + * @param {Object} id The key to lookup and retrieve from the store. + * @returns {Object} + * @memberOf Store + */ +lunr.Store.prototype.get = function (id) { + return this.store[id] +} + +/** + * Checks whether the store contains a key. + * + * @param {Object} id The id to look up in the store. + * @returns {Boolean} + * @memberOf Store + */ +lunr.Store.prototype.has = function (id) { + return id in this.store +} + +/** + * Removes the value for a key in the store. + * + * @param {Object} id The id to remove from the store. + * @memberOf Store + */ +lunr.Store.prototype.remove = function (id) { + if (!this.has(id)) return + + delete this.store[id] + this.length-- +} + +/** + * Returns a representation of the store ready for serialisation. + * + * @returns {Object} + * @memberOf Store + */ +lunr.Store.prototype.toJSON = function () { + return { + store: this.store, + length: this.length + } +} + +/*! + * lunr.stemmer + * Copyright (C) 2014 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.stemmer is an english language stemmer, this is a JavaScript + * implementation of the PorterStemmer taken from http://tartaurs.org/~martin + * + * @module + * @param {String} str The string to stem + * @returns {String} + * @see lunr.Pipeline + */ +lunr.stemmer = (function(){ + var step2list = { + "ational" : "ate", + "tional" : "tion", + "enci" : "ence", + "anci" : "ance", + "izer" : "ize", + "bli" : "ble", + "alli" : "al", + "entli" : "ent", + "eli" : "e", + "ousli" : "ous", + "ization" : "ize", + "ation" : "ate", + "ator" : "ate", + "alism" : "al", + "iveness" : "ive", + "fulness" : "ful", + "ousness" : "ous", + "aliti" : "al", + "iviti" : "ive", + "biliti" : "ble", + "logi" : "log" + }, + + step3list = { + "icate" : "ic", + "ative" : "", + "alize" : "al", + "iciti" : "ic", + "ical" : "ic", + "ful" : "", + "ness" : "" + }, + + c = "[^aeiou]", // consonant + v = "[aeiouy]", // vowel + C = c + "[^aeiouy]*", // consonant sequence + V = v + "[aeiou]*", // vowel sequence + + mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 + meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 + mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 + s_v = "^(" + C + ")?" + v; // vowel in stem + + var re_mgr0 = new RegExp(mgr0); + var re_mgr1 = new RegExp(mgr1); + var re_meq1 = new RegExp(meq1); + var re_s_v = new RegExp(s_v); + + var re_1a = /^(.+?)(ss|i)es$/; + var re2_1a = /^(.+?)([^s])s$/; + var re_1b = /^(.+?)eed$/; + var re2_1b = /^(.+?)(ed|ing)$/; + var re_1b_2 = /.$/; + var re2_1b_2 = /(at|bl|iz)$/; + var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$"); + var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var re_1c = /^(.+?[^aeiou])y$/; + var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + + var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + + var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + var re2_4 = /^(.+?)(s|t)(ion)$/; + + var re_5 = /^(.+?)e$/; + var re_5_1 = /ll$/; + var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var porterStemmer = function porterStemmer(w) { + var stem, + suffix, + firstch, + re, + re2, + re3, + re4; + + if (w.length < 3) { return w; } + + firstch = w.substr(0,1); + if (firstch == "y") { + w = firstch.toUpperCase() + w.substr(1); + } + + // Step 1a + re = re_1a + re2 = re2_1a; + + if (re.test(w)) { w = w.replace(re,"$1$2"); } + else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } + + // Step 1b + re = re_1b; + re2 = re2_1b; + if (re.test(w)) { + var fp = re.exec(w); + re = re_mgr0; + if (re.test(fp[1])) { + re = re_1b_2; + w = w.replace(re,""); + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = re_s_v; + if (re2.test(stem)) { + w = stem; + re2 = re2_1b_2; + re3 = re3_1b_2; + re4 = re4_1b_2; + if (re2.test(w)) { w = w + "e"; } + else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); } + else if (re4.test(w)) { w = w + "e"; } + } + } + + // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say) + re = re_1c; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + w = stem + "i"; + } + + // Step 2 + re = re_2; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step2list[suffix]; + } + } + + // Step 3 + re = re_3; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step3list[suffix]; + } + } + + // Step 4 + re = re_4; + re2 = re2_4; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + if (re.test(stem)) { + w = stem; + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = re_mgr1; + if (re2.test(stem)) { + w = stem; + } + } + + // Step 5 + re = re_5; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + re2 = re_meq1; + re3 = re3_5; + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { + w = stem; + } + } + + re = re_5_1; + re2 = re_mgr1; + if (re.test(w) && re2.test(w)) { + re = re_1b_2; + w = w.replace(re,""); + } + + // and turn initial Y back to y + + if (firstch == "y") { + w = firstch.toLowerCase() + w.substr(1); + } + + return w; + }; + + return porterStemmer; +})(); + +lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') +/*! + * lunr.stopWordFilter + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.stopWordFilter is an English language stop word list filter, any words + * contained in the list will not be passed through the filter. + * + * This is intended to be used in the Pipeline. If the token does not pass the + * filter then undefined will be returned. + * + * @module + * @param {String} token The token to pass through the filter + * @returns {String} + * @see lunr.Pipeline + */ +lunr.stopWordFilter = function (token) { + if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token +} + +lunr.stopWordFilter.stopWords = new lunr.SortedSet +lunr.stopWordFilter.stopWords.length = 119 +lunr.stopWordFilter.stopWords.elements = [ + "", + "a", + "able", + "about", + "across", + "after", + "all", + "almost", + "also", + "am", + "among", + "an", + "and", + "any", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "do", + "does", + "either", + "else", + "ever", + "every", + "for", + "from", + "get", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "in", + "into", + "is", + "it", + "its", + "just", + "least", + "let", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "only", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "where", + "which", + "while", + "who", + "whom", + "why", + "will", + "with", + "would", + "yet", + "you", + "your" +] + +lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') +/*! + * lunr.trimmer + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.trimmer is a pipeline function for trimming non word + * characters from the begining and end of tokens before they + * enter the index. + * + * This implementation may not work correctly for non latin + * characters and should either be removed or adapted for use + * with languages with non-latin characters. + * + * @module + * @param {String} token The token to pass through the filter + * @returns {String} + * @see lunr.Pipeline + */ +lunr.trimmer = function (token) { + return token + .replace(/^\W+/, '') + .replace(/\W+$/, '') +} + +lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') +/*! + * lunr.stemmer + * Copyright (C) 2014 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.TokenStore is used for efficient storing and lookup of the reverse + * index of token to document ref. + * + * @constructor + */ +lunr.TokenStore = function () { + this.root = { docs: {} } + this.length = 0 +} + +/** + * Loads a previously serialised token store + * + * @param {Object} serialisedData The serialised token store to load. + * @returns {lunr.TokenStore} + * @memberOf TokenStore + */ +lunr.TokenStore.load = function (serialisedData) { + var store = new this + + store.root = serialisedData.root + store.length = serialisedData.length + + return store +} + +/** + * Adds a new token doc pair to the store. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to store the doc under + * @param {Object} doc The doc to store against the token + * @param {Object} root An optional node at which to start looking for the + * correct place to enter the doc, by default the root of this lunr.TokenStore + * is used. + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.add = function (token, doc, root) { + var root = root || this.root, + key = token[0], + rest = token.slice(1) + + if (!(key in root)) root[key] = {docs: {}} + + if (rest.length === 0) { + root[key].docs[doc.ref] = doc + this.length += 1 + return + } else { + return this.add(rest, doc, root[key]) + } +} + +/** + * Checks whether this key is contained within this lunr.TokenStore. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to check for + * @param {Object} root An optional node at which to start + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.has = function (token) { + if (!token) return false + + var node = this.root + + for (var i = 0; i < token.length; i++) { + if (!node[token[i]]) return false + + node = node[token[i]] + } + + return true +} + +/** + * Retrieve a node from the token store for a given token. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to get the node for. + * @param {Object} root An optional node at which to start. + * @returns {Object} + * @see TokenStore.prototype.get + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.getNode = function (token) { + if (!token) return {} + + var node = this.root + + for (var i = 0; i < token.length; i++) { + if (!node[token[i]]) return {} + + node = node[token[i]] + } + + return node +} + +/** + * Retrieve the documents for a node for the given token. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to get the documents for. + * @param {Object} root An optional node at which to start. + * @returns {Object} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.get = function (token, root) { + return this.getNode(token, root).docs || {} +} + +lunr.TokenStore.prototype.count = function (token, root) { + return Object.keys(this.get(token, root)).length +} + +/** + * Remove the document identified by ref from the token in the store. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to get the documents for. + * @param {String} ref The ref of the document to remove from this token. + * @param {Object} root An optional node at which to start. + * @returns {Object} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.remove = function (token, ref) { + if (!token) return + var node = this.root + + for (var i = 0; i < token.length; i++) { + if (!(token[i] in node)) return + node = node[token[i]] + } + + delete node.docs[ref] +} + +/** + * Find all the possible suffixes of the passed token using tokens + * currently in the store. + * + * @param {String} token The token to expand. + * @returns {Array} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.expand = function (token, memo) { + var root = this.getNode(token), + docs = root.docs || {}, + memo = memo || [] + + if (Object.keys(docs).length) memo.push(token) + + Object.keys(root) + .forEach(function (key) { + if (key === 'docs') return + + memo.concat(this.expand(token + key, memo)) + }, this) + + return memo +} + +/** + * Returns a representation of the token store ready for serialisation. + * + * @returns {Object} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.toJSON = function () { + return { + root: this.root, + length: this.length + } +} + + + /** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ + ;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like enviroments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + root.lunr = factory() + } + }(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + return lunr + })) +})() This diff is so big that we needed to truncate the remainder. https://bitbucket.org/galaxy/galaxy-central/commits/19079eea7d22/ Changeset: 19079eea7d22 User: dannon Date: 2015-02-05 13:32:25+00:00 Summary: Add require, TODO for where to integrate lunr. Affected #: 3 files diff -r 9eeb924b5204ff10bac0998612549de468f9bc2a -r 19079eea7d2285d701866b0df77beeff20a677e8 client/galaxy/scripts/mvc/tools.js --- a/client/galaxy/scripts/mvc/tools.js +++ b/client/galaxy/scripts/mvc/tools.js @@ -2,7 +2,7 @@ * Model, view, and controller objects for Galaxy tools and tool panel. */ - define( ["libs/underscore", "viz/trackster/util", "mvc/data" ], + define( ["libs/underscore", "viz/trackster/util", "mvc/data", "libs/lunr.js" ], function(_, util, data) { /** @@ -14,7 +14,7 @@ show: function() { this.set("hidden", false); }, - + hide: function() { this.set("hidden", true); }, @@ -22,7 +22,7 @@ toggle: function() { this.set("hidden", !this.get("hidden")); }, - + is_visible: function() { return !this.attributes.hidden; } @@ -88,7 +88,7 @@ } }); -/** +/** * A select tool parameter. */ var SelectToolParameter = ToolParameter.extend({ @@ -157,7 +157,7 @@ }); tool.get('inputs').remove(incompatible_inputs); }, - + /** * Returns object copy, optionally including only inputs that can be sampled. */ @@ -177,12 +177,12 @@ return copy; }, - + apply_search_results: function(results) { ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() ); return this.is_visible(); }, - + /** * Set a tool input's value. */ @@ -191,24 +191,24 @@ return input.get('name') === name; }).set('value', value); }, - + /** * Set many input values at once. */ set_input_values: function(inputs_dict) { var self = this; _.each(_.keys(inputs_dict), function(input_name) { - self.set_input_value(input_name, inputs_dict[input_name]); + self.set_input_value(input_name, inputs_dict[input_name]); }); }, - + /** * Run tool; returns a Deferred that resolves to the tool's output(s). */ run: function() { return this._run(); }, - + /** * Rerun tool using regions and a target dataset. */ @@ -230,7 +230,7 @@ }); return input_dict; }, - + /** * Run tool; returns a Deferred that resolves to the tool's output(s). * NOTE: this method is a helper method and should not be called directly. @@ -259,7 +259,7 @@ return response !== "pending"; } }); - + // Run job and resolve run_deferred to tool outputs. $.when(ss_deferred.go()).then(function(result) { run_deferred.resolve(new data.DatasetCollection().reset(result)); @@ -296,20 +296,20 @@ elems: [], open: false }, - + clear_search_results: function() { _.each(this.attributes.elems, function(elt) { elt.show(); }); - + this.show(); this.set("open", false); }, - + apply_search_results: function(results) { var all_hidden = true, cur_label; - _.each(this.attributes.elems, function(elt) { + _.each(this.attributes.elems, function(elt) { if (elt instanceof ToolSectionLabel) { cur_label = elt; cur_label.hide(); @@ -323,7 +323,7 @@ } } }); - + if (all_hidden) { this.hide(); } @@ -340,6 +340,11 @@ * indicates that query was not run; if not null, results are from search using * query. */ + +/** + * TODO: Integrate lunr search here with tools from API instead of making + * repeated requests. + */ var ToolSearch = Backbone.Model.extend({ defaults: { search_hint_string: "search tools", @@ -353,23 +358,23 @@ // ESC (27) will clear the input field and tool search filters clear_key: 27 }, - + initialize: function() { this.on("change:query", this.do_search); }, - + /** * Do the search and update the results. */ do_search: function() { var query = this.attributes.query; - + // If query is too short, do not search. if (query.length < this.attributes.min_chars_for_search) { this.set("results", null); return; } - + // Do search via AJAX. var q = query + '*'; // Stop previous ajax-request @@ -388,12 +393,12 @@ }, "json" ); }, 200 ); }, - + clear_search: function() { this.set("query", ""); this.set("results", null); } - + }); _.extend(ToolSearch.prototype, VisibilityMixin); @@ -433,7 +438,7 @@ return new ToolSectionLabel(elt_dict); } }; - + return _.map(response, parse_elt); }, @@ -448,14 +453,14 @@ } }); }, - + apply_search_results: function() { var results = this.get('tool_search').get('results'); if (results === null) { this.clear_search_results(); return; } - + var cur_label = null; this.get('layout').each(function(panel_elt) { if (panel_elt instanceof ToolSectionLabel) { @@ -479,13 +484,13 @@ }); /** - * View classes for Galaxy tools and tool panel. - * + * View classes for Galaxy tools and tool panel. + * * Views use precompiled Handlebars templates for rendering. Views update as needed * based on (a) model/collection events and (b) user interactions; in this sense, * they are controllers are well and the HTML is the real view in the MVC architecture. */ - + /** * Base view that handles visibility based on model's hidden attribute. */ @@ -496,9 +501,9 @@ }, update_visible: function() { ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() ); - } + } }); - + /** * Link to a tool. */ @@ -509,7 +514,7 @@ // create element var $link = $('<div/>'); $link.append(Handlebars.templates.tool_link(this.model.toJSON())); - + // open upload dialog for upload tool if (this.model.id === 'upload1') { $link.find('a').on('click', function(e) { @@ -517,7 +522,7 @@ Galaxy.upload.show(); }); } - + // add element this.$el.append($link); return this; @@ -543,7 +548,7 @@ var ToolSectionView = BaseView.extend({ tagName: 'div', className: 'toolSectionWrapper', - + initialize: function() { BaseView.prototype.initialize.call(this); this.model.on("change:open", this.update_open, this); @@ -552,7 +557,7 @@ render: function() { // Build using template. this.$el.append( Handlebars.templates.panel_section(this.model.toJSON()) ); - + // Add tools to section. var section_body = this.$el.find(".toolSectionBody"); _.each(this.model.attributes.elems, function(elt) { @@ -572,25 +577,25 @@ }); return this; }, - + events: { 'click .toolSectionTitle > a': 'toggle' }, - - /** + + /** * Toggle visibility of tool section. */ toggle: function() { this.model.set("open", !this.model.attributes.open); }, - + /** * Update whether section is open or close. */ update_open: function() { (this.model.attributes.open ? this.$el.children(".toolSectionBody").slideDown("fast") : - this.$el.children(".toolSectionBody").slideUp("fast") + this.$el.children(".toolSectionBody").slideUp("fast") ); } }); @@ -599,13 +604,13 @@ tagName: 'div', id: 'tool-search', className: 'bar', - + events: { 'click': 'focus_and_select', 'keyup :input': 'query_changed', 'click #search-clear-btn': 'clear' }, - + render: function() { this.$el.append( Handlebars.templates.tool_search(this.model.toJSON()) ); if (!this.model.is_visible()) { @@ -614,18 +619,18 @@ this.$el.find('[title]').tooltip(); return this; }, - + focus_and_select: function() { this.$el.find(":input").focus().select(); }, - + clear: function() { this.model.clear_search(); this.$el.find(":input").val(this.model.attributes.search_hint_string); this.focus_and_select(); return false; }, - + query_changed: function( evData ) { // check for the 'clear key' (ESC) first if( ( this.model.attributes.clear_key ) && @@ -644,22 +649,22 @@ var ToolPanelView = Backbone.View.extend({ tagName: 'div', className: 'toolMenu', - + /** * Set up view. */ initialize: function() { this.model.get('tool_search').on("change:results", this.handle_search_results, this); }, - + render: function() { var self = this; - + // Render search. var search_view = new ToolSearchView( { model: this.model.get('tool_search') } ); search_view.render(); self.$el.append(search_view.$el); - + // Render panel. this.model.get('layout').each(function(panel_elt) { if (panel_elt instanceof ToolSection) { @@ -678,20 +683,20 @@ self.$el.append(label_view.$el); } }); - + // Setup tool link click eventing. self.$el.find("a.tool-link").click(function(e) { // Tool id is always the first class. - var + var tool_id = $(this).attr('class').split(/\s+/)[0], tool = self.model.get('tools').get(tool_id); - + self.trigger("tool_link_click", e, tool); }); - + return this; }, - + handle_search_results: function() { var results = this.model.get('tool_search').get('results'); if (results && results.length === 0) { @@ -708,7 +713,7 @@ */ var ToolFormView = Backbone.View.extend({ className: 'toolForm', - + render: function() { this.$el.children().remove(); this.$el.append( Handlebars.templates.tool_form(this.model.toJSON()) ); @@ -720,22 +725,22 @@ */ var IntegratedToolMenuAndView = Backbone.View.extend({ className: 'toolMenuAndView', - + initialize: function() { this.tool_panel_view = new ToolPanelView({collection: this.collection}); this.tool_form_view = new ToolFormView(); }, - + render: function() { // Render and append tool panel. this.tool_panel_view.render(); this.tool_panel_view.$el.css("float", "left"); this.$el.append(this.tool_panel_view.$el); - + // Append tool form view. this.tool_form_view.$el.hide(); this.$el.append(this.tool_form_view.$el); - + // On tool link click, show tool. var self = this; this.tool_panel_view.on("tool_link_click", function(e, tool) { @@ -745,7 +750,7 @@ self.show_tool(tool); }); }, - + /** * Fetch and display tool. */ diff -r 9eeb924b5204ff10bac0998612549de468f9bc2a -r 19079eea7d2285d701866b0df77beeff20a677e8 static/scripts/mvc/tools.js --- a/static/scripts/mvc/tools.js +++ b/static/scripts/mvc/tools.js @@ -2,7 +2,7 @@ * Model, view, and controller objects for Galaxy tools and tool panel. */ - define( ["libs/underscore", "viz/trackster/util", "mvc/data" ], + define( ["libs/underscore", "viz/trackster/util", "mvc/data", "libs/lunr.js" ], function(_, util, data) { /** @@ -14,7 +14,7 @@ show: function() { this.set("hidden", false); }, - + hide: function() { this.set("hidden", true); }, @@ -22,7 +22,7 @@ toggle: function() { this.set("hidden", !this.get("hidden")); }, - + is_visible: function() { return !this.attributes.hidden; } @@ -88,7 +88,7 @@ } }); -/** +/** * A select tool parameter. */ var SelectToolParameter = ToolParameter.extend({ @@ -157,7 +157,7 @@ }); tool.get('inputs').remove(incompatible_inputs); }, - + /** * Returns object copy, optionally including only inputs that can be sampled. */ @@ -177,12 +177,12 @@ return copy; }, - + apply_search_results: function(results) { ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() ); return this.is_visible(); }, - + /** * Set a tool input's value. */ @@ -191,24 +191,24 @@ return input.get('name') === name; }).set('value', value); }, - + /** * Set many input values at once. */ set_input_values: function(inputs_dict) { var self = this; _.each(_.keys(inputs_dict), function(input_name) { - self.set_input_value(input_name, inputs_dict[input_name]); + self.set_input_value(input_name, inputs_dict[input_name]); }); }, - + /** * Run tool; returns a Deferred that resolves to the tool's output(s). */ run: function() { return this._run(); }, - + /** * Rerun tool using regions and a target dataset. */ @@ -230,7 +230,7 @@ }); return input_dict; }, - + /** * Run tool; returns a Deferred that resolves to the tool's output(s). * NOTE: this method is a helper method and should not be called directly. @@ -259,7 +259,7 @@ return response !== "pending"; } }); - + // Run job and resolve run_deferred to tool outputs. $.when(ss_deferred.go()).then(function(result) { run_deferred.resolve(new data.DatasetCollection().reset(result)); @@ -296,20 +296,20 @@ elems: [], open: false }, - + clear_search_results: function() { _.each(this.attributes.elems, function(elt) { elt.show(); }); - + this.show(); this.set("open", false); }, - + apply_search_results: function(results) { var all_hidden = true, cur_label; - _.each(this.attributes.elems, function(elt) { + _.each(this.attributes.elems, function(elt) { if (elt instanceof ToolSectionLabel) { cur_label = elt; cur_label.hide(); @@ -323,7 +323,7 @@ } } }); - + if (all_hidden) { this.hide(); } @@ -340,6 +340,11 @@ * indicates that query was not run; if not null, results are from search using * query. */ + +/** + * TODO: Integrate lunr search here with tools from API instead of making + * repeated requests. + */ var ToolSearch = Backbone.Model.extend({ defaults: { search_hint_string: "search tools", @@ -353,23 +358,23 @@ // ESC (27) will clear the input field and tool search filters clear_key: 27 }, - + initialize: function() { this.on("change:query", this.do_search); }, - + /** * Do the search and update the results. */ do_search: function() { var query = this.attributes.query; - + // If query is too short, do not search. if (query.length < this.attributes.min_chars_for_search) { this.set("results", null); return; } - + // Do search via AJAX. var q = query + '*'; // Stop previous ajax-request @@ -388,12 +393,12 @@ }, "json" ); }, 200 ); }, - + clear_search: function() { this.set("query", ""); this.set("results", null); } - + }); _.extend(ToolSearch.prototype, VisibilityMixin); @@ -433,7 +438,7 @@ return new ToolSectionLabel(elt_dict); } }; - + return _.map(response, parse_elt); }, @@ -448,14 +453,14 @@ } }); }, - + apply_search_results: function() { var results = this.get('tool_search').get('results'); if (results === null) { this.clear_search_results(); return; } - + var cur_label = null; this.get('layout').each(function(panel_elt) { if (panel_elt instanceof ToolSectionLabel) { @@ -479,13 +484,13 @@ }); /** - * View classes for Galaxy tools and tool panel. - * + * View classes for Galaxy tools and tool panel. + * * Views use precompiled Handlebars templates for rendering. Views update as needed * based on (a) model/collection events and (b) user interactions; in this sense, * they are controllers are well and the HTML is the real view in the MVC architecture. */ - + /** * Base view that handles visibility based on model's hidden attribute. */ @@ -496,9 +501,9 @@ }, update_visible: function() { ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() ); - } + } }); - + /** * Link to a tool. */ @@ -509,7 +514,7 @@ // create element var $link = $('<div/>'); $link.append(Handlebars.templates.tool_link(this.model.toJSON())); - + // open upload dialog for upload tool if (this.model.id === 'upload1') { $link.find('a').on('click', function(e) { @@ -517,7 +522,7 @@ Galaxy.upload.show(); }); } - + // add element this.$el.append($link); return this; @@ -543,7 +548,7 @@ var ToolSectionView = BaseView.extend({ tagName: 'div', className: 'toolSectionWrapper', - + initialize: function() { BaseView.prototype.initialize.call(this); this.model.on("change:open", this.update_open, this); @@ -552,7 +557,7 @@ render: function() { // Build using template. this.$el.append( Handlebars.templates.panel_section(this.model.toJSON()) ); - + // Add tools to section. var section_body = this.$el.find(".toolSectionBody"); _.each(this.model.attributes.elems, function(elt) { @@ -572,25 +577,25 @@ }); return this; }, - + events: { 'click .toolSectionTitle > a': 'toggle' }, - - /** + + /** * Toggle visibility of tool section. */ toggle: function() { this.model.set("open", !this.model.attributes.open); }, - + /** * Update whether section is open or close. */ update_open: function() { (this.model.attributes.open ? this.$el.children(".toolSectionBody").slideDown("fast") : - this.$el.children(".toolSectionBody").slideUp("fast") + this.$el.children(".toolSectionBody").slideUp("fast") ); } }); @@ -599,13 +604,13 @@ tagName: 'div', id: 'tool-search', className: 'bar', - + events: { 'click': 'focus_and_select', 'keyup :input': 'query_changed', 'click #search-clear-btn': 'clear' }, - + render: function() { this.$el.append( Handlebars.templates.tool_search(this.model.toJSON()) ); if (!this.model.is_visible()) { @@ -614,18 +619,18 @@ this.$el.find('[title]').tooltip(); return this; }, - + focus_and_select: function() { this.$el.find(":input").focus().select(); }, - + clear: function() { this.model.clear_search(); this.$el.find(":input").val(this.model.attributes.search_hint_string); this.focus_and_select(); return false; }, - + query_changed: function( evData ) { // check for the 'clear key' (ESC) first if( ( this.model.attributes.clear_key ) && @@ -644,22 +649,22 @@ var ToolPanelView = Backbone.View.extend({ tagName: 'div', className: 'toolMenu', - + /** * Set up view. */ initialize: function() { this.model.get('tool_search').on("change:results", this.handle_search_results, this); }, - + render: function() { var self = this; - + // Render search. var search_view = new ToolSearchView( { model: this.model.get('tool_search') } ); search_view.render(); self.$el.append(search_view.$el); - + // Render panel. this.model.get('layout').each(function(panel_elt) { if (panel_elt instanceof ToolSection) { @@ -678,20 +683,20 @@ self.$el.append(label_view.$el); } }); - + // Setup tool link click eventing. self.$el.find("a.tool-link").click(function(e) { // Tool id is always the first class. - var + var tool_id = $(this).attr('class').split(/\s+/)[0], tool = self.model.get('tools').get(tool_id); - + self.trigger("tool_link_click", e, tool); }); - + return this; }, - + handle_search_results: function() { var results = this.model.get('tool_search').get('results'); if (results && results.length === 0) { @@ -708,7 +713,7 @@ */ var ToolFormView = Backbone.View.extend({ className: 'toolForm', - + render: function() { this.$el.children().remove(); this.$el.append( Handlebars.templates.tool_form(this.model.toJSON()) ); @@ -720,22 +725,22 @@ */ var IntegratedToolMenuAndView = Backbone.View.extend({ className: 'toolMenuAndView', - + initialize: function() { this.tool_panel_view = new ToolPanelView({collection: this.collection}); this.tool_form_view = new ToolFormView(); }, - + render: function() { // Render and append tool panel. this.tool_panel_view.render(); this.tool_panel_view.$el.css("float", "left"); this.$el.append(this.tool_panel_view.$el); - + // Append tool form view. this.tool_form_view.$el.hide(); this.$el.append(this.tool_form_view.$el); - + // On tool link click, show tool. var self = this; this.tool_panel_view.on("tool_link_click", function(e, tool) { @@ -745,7 +750,7 @@ self.show_tool(tool); }); }, - + /** * Fetch and display tool. */ diff -r 9eeb924b5204ff10bac0998612549de468f9bc2a -r 19079eea7d2285d701866b0df77beeff20a677e8 static/scripts/packed/mvc/tools.js --- a/static/scripts/packed/mvc/tools.js +++ b/static/scripts/packed/mvc/tools.js @@ -1,1 +1,1 @@ -define(["libs/underscore","viz/trackster/util","mvc/data"],function(x,a,y){var g={hidden:false,show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},toggle:function(){this.set("hidden",!this.get("hidden"))},is_visible:function(){return !this.attributes.hidden}};var e=Backbone.Model.extend({defaults:{name:null,label:null,type:null,value:null,html:null,num_samples:5},initialize:function(z){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new e(this.toJSON())},set_value:function(z){this.set("value",z||"")}});var i=Backbone.Collection.extend({model:e});var k=e.extend({});var d=e.extend({set_value:function(z){this.set("value",parseInt(z,10))},get_samples:function(){return d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}});var f=d.extend({set_value:function(z){this.set("value",parseFloat(z))}});var t=e.extend({get_samples:function(){return x.map(this.get("options"),function(z){return z[0]})}});e.subModelTypes={integer:d,"float":f,data:k,select:t};var j=Backbone.Model.extend({defaults:{id:null,name:null,description:null,target:null,inputs:[],outputs:[]},urlRoot:galaxy_config.root+"api/tools",initialize:function(z){this.set("inputs",new i(x.map(z.inputs,function(A){var B=e.subModelTypes[A.type]||e;return new B(A)})))},toJSON:function(){var z=Backbone.Model.prototype.toJSON.call(this);z.inputs=this.get("inputs").map(function(A){return A.toJSON()});return z},remove_inputs:function(A){var z=this,B=z.get("inputs").filter(function(C){return(A.indexOf(C.get("type"))!==-1)});z.get("inputs").remove(B)},copy:function(A){var B=new j(this.toJSON());if(A){var z=new Backbone.Collection();B.get("inputs").each(function(C){if(C.get_samples()){z.push(C)}});B.set("inputs",z)}return B},apply_search_results:function(z){(x.indexOf(z,this.attributes.id)!==-1?this.show():this.hide());return this.is_visible()},set_input_value:function(z,A){this.get("inputs").find(function(B){return B.get("name")===z}).set("value",A)},set_input_values:function(A){var z=this;x.each(x.keys(A),function(B){z.set_input_value(B,A[B])})},run:function(){return this._run()},rerun:function(A,z){return this._run({action:"rerun",target_dataset_id:A.id,regions:z})},get_inputs_dict:function(){var z={};this.get("inputs").each(function(A){z[A.get("name")]=A.get("value")});return z},_run:function(B){var C=x.extend({tool_id:this.id,inputs:this.get_inputs_dict()},B);var A=$.Deferred(),z=new a.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(C),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(D){return D!=="pending"}});$.when(z.go()).then(function(D){A.resolve(new y.DatasetCollection().reset(D))});return A}});x.extend(j.prototype,g);var q=Backbone.View.extend({});var n=Backbone.Collection.extend({model:j});var v=Backbone.Model.extend(g);var l=Backbone.Model.extend({defaults:{elems:[],open:false},clear_search_results:function(){x.each(this.attributes.elems,function(z){z.show()});this.show();this.set("open",false)},apply_search_results:function(A){var B=true,z;x.each(this.attributes.elems,function(C){if(C instanceof v){z=C;z.hide()}else{if(C instanceof j){if(C.apply_search_results(A)){B=false;if(z){z.show()}}}}});if(B){this.hide()}else{this.show();this.set("open",true)}}});x.extend(l.prototype,g);var c=Backbone.Model.extend({defaults:{search_hint_string:"search tools",min_chars_for_search:3,spinner_url:"",clear_btn_url:"",search_url:"",visible:true,query:"",results:null,clear_key:27},initialize:function(){this.on("change:query",this.do_search)},do_search:function(){var B=this.attributes.query;if(B.length<this.attributes.min_chars_for_search){this.set("results",null);return}var A=B+"*";if(this.timer){clearTimeout(this.timer)}$("#search-clear-btn").hide();$("#search-spinner").show();var z=this;this.timer=setTimeout(function(){$.get(z.attributes.search_url,{query:A},function(C){z.set("results",C);$("#search-spinner").hide();$("#search-clear-btn").show()},"json")},200)},clear_search:function(){this.set("query","");this.set("results",null)}});x.extend(c.prototype,g);var o=Backbone.Model.extend({initialize:function(z){this.attributes.tool_search=z.tool_search;this.attributes.tool_search.on("change:results",this.apply_search_results,this);this.attributes.tools=z.tools;this.attributes.layout=new Backbone.Collection(this.parse(z.layout))},parse:function(A){var z=this,B=function(E){var D=E.model_class;if(D.indexOf("Tool")===D.length-4){return z.attributes.tools.get(E.id)}else{if(D==="ToolSection"){var C=x.map(E.elems,B);E.elems=C;return new l(E)}else{if(D==="ToolSectionLabel"){return new v(E)}}}};return x.map(A,B)},clear_search_results:function(){this.get("layout").each(function(z){if(z instanceof l){z.clear_search_results()}else{z.show()}})},apply_search_results:function(){var A=this.get("tool_search").get("results");if(A===null){this.clear_search_results();return}var z=null;this.get("layout").each(function(B){if(B instanceof v){z=B;z.hide()}else{if(B instanceof j){if(B.apply_search_results(A)){if(z){z.show()}}}else{z=null;B.apply_search_results(A)}}})}});var s=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){(this.model.attributes.hidden?this.$el.hide():this.$el.show())}});var m=s.extend({tagName:"div",render:function(){var z=$("<div/>");z.append(Handlebars.templates.tool_link(this.model.toJSON()));if(this.model.id==="upload1"){z.find("a").on("click",function(A){A.preventDefault();Galaxy.upload.show()})}this.$el.append(z);return this}});var b=s.extend({tagName:"div",className:"toolPanelLabel",render:function(){this.$el.append($("<span/>").text(this.model.attributes.text));return this}});var r=s.extend({tagName:"div",className:"toolSectionWrapper",initialize:function(){s.prototype.initialize.call(this);this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(Handlebars.templates.panel_section(this.model.toJSON()));var z=this.$el.find(".toolSectionBody");x.each(this.model.attributes.elems,function(A){if(A instanceof j){var B=new m({model:A,className:"toolTitle"});B.render();z.append(B.$el)}else{if(A instanceof v){var C=new b({model:A});C.render();z.append(C.$el)}else{}}});return this},events:{"click .toolSectionTitle > a":"toggle"},toggle:function(){this.model.set("open",!this.model.attributes.open)},update_open:function(){(this.model.attributes.open?this.$el.children(".toolSectionBody").slideDown("fast"):this.$el.children(".toolSectionBody").slideUp("fast"))}});var p=Backbone.View.extend({tagName:"div",id:"tool-search",className:"bar",events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){this.$el.append(Handlebars.templates.tool_search(this.model.toJSON()));if(!this.model.is_visible()){this.$el.hide()}this.$el.find("[title]").tooltip();return this},focus_and_select:function(){this.$el.find(":input").focus().select()},clear:function(){this.model.clear_search();this.$el.find(":input").val(this.model.attributes.search_hint_string);this.focus_and_select();return false},query_changed:function(z){if((this.model.attributes.clear_key)&&(this.model.attributes.clear_key===z.which)){this.clear();return false}this.model.set("query",this.$el.find(":input").val())}});var w=Backbone.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.model.get("tool_search").on("change:results",this.handle_search_results,this)},render:function(){var z=this;var A=new p({model:this.model.get("tool_search")});A.render();z.$el.append(A.$el);this.model.get("layout").each(function(C){if(C instanceof l){var B=new r({model:C});B.render();z.$el.append(B.$el)}else{if(C instanceof j){var D=new m({model:C,className:"toolTitleNoSection"});D.render();z.$el.append(D.$el)}else{if(C instanceof v){var E=new b({model:C});E.render();z.$el.append(E.$el)}}}});z.$el.find("a.tool-link").click(function(D){var C=$(this).attr("class").split(/\s+/)[0],B=z.model.get("tools").get(C);z.trigger("tool_link_click",D,B)});return this},handle_search_results:function(){var z=this.model.get("tool_search").get("results");if(z&&z.length===0){$("#search-no-results").show()}else{$("#search-no-results").hide()}}});var u=Backbone.View.extend({className:"toolForm",render:function(){this.$el.children().remove();this.$el.append(Handlebars.templates.tool_form(this.model.toJSON()))}});var h=Backbone.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new w({collection:this.collection});this.tool_form_view=new u()},render:function(){this.tool_panel_view.render();this.tool_panel_view.$el.css("float","left");this.$el.append(this.tool_panel_view.$el);this.tool_form_view.$el.hide();this.$el.append(this.tool_form_view.$el);var z=this;this.tool_panel_view.on("tool_link_click",function(B,A){B.preventDefault();z.show_tool(A)})},show_tool:function(A){var z=this;A.fetch().done(function(){z.tool_form_view.model=A;z.tool_form_view.render();z.tool_form_view.$el.show();$("#left").width("650px")})}});return{ToolParameter:e,IntegerToolParameter:d,SelectToolParameter:t,Tool:j,ToolCollection:n,ToolSearch:c,ToolPanel:o,ToolPanelView:w,ToolFormView:u}}); \ No newline at end of file +define(["libs/underscore","viz/trackster/util","mvc/data","libs/lunr.js"],function(x,a,y){var g={hidden:false,show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},toggle:function(){this.set("hidden",!this.get("hidden"))},is_visible:function(){return !this.attributes.hidden}};var e=Backbone.Model.extend({defaults:{name:null,label:null,type:null,value:null,html:null,num_samples:5},initialize:function(z){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new e(this.toJSON())},set_value:function(z){this.set("value",z||"")}});var i=Backbone.Collection.extend({model:e});var k=e.extend({});var d=e.extend({set_value:function(z){this.set("value",parseInt(z,10))},get_samples:function(){return d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}});var f=d.extend({set_value:function(z){this.set("value",parseFloat(z))}});var t=e.extend({get_samples:function(){return x.map(this.get("options"),function(z){return z[0]})}});e.subModelTypes={integer:d,"float":f,data:k,select:t};var j=Backbone.Model.extend({defaults:{id:null,name:null,description:null,target:null,inputs:[],outputs:[]},urlRoot:galaxy_config.root+"api/tools",initialize:function(z){this.set("inputs",new i(x.map(z.inputs,function(A){var B=e.subModelTypes[A.type]||e;return new B(A)})))},toJSON:function(){var z=Backbone.Model.prototype.toJSON.call(this);z.inputs=this.get("inputs").map(function(A){return A.toJSON()});return z},remove_inputs:function(A){var z=this,B=z.get("inputs").filter(function(C){return(A.indexOf(C.get("type"))!==-1)});z.get("inputs").remove(B)},copy:function(A){var B=new j(this.toJSON());if(A){var z=new Backbone.Collection();B.get("inputs").each(function(C){if(C.get_samples()){z.push(C)}});B.set("inputs",z)}return B},apply_search_results:function(z){(x.indexOf(z,this.attributes.id)!==-1?this.show():this.hide());return this.is_visible()},set_input_value:function(z,A){this.get("inputs").find(function(B){return B.get("name")===z}).set("value",A)},set_input_values:function(A){var z=this;x.each(x.keys(A),function(B){z.set_input_value(B,A[B])})},run:function(){return this._run()},rerun:function(A,z){return this._run({action:"rerun",target_dataset_id:A.id,regions:z})},get_inputs_dict:function(){var z={};this.get("inputs").each(function(A){z[A.get("name")]=A.get("value")});return z},_run:function(B){var C=x.extend({tool_id:this.id,inputs:this.get_inputs_dict()},B);var A=$.Deferred(),z=new a.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(C),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(D){return D!=="pending"}});$.when(z.go()).then(function(D){A.resolve(new y.DatasetCollection().reset(D))});return A}});x.extend(j.prototype,g);var q=Backbone.View.extend({});var n=Backbone.Collection.extend({model:j});var v=Backbone.Model.extend(g);var l=Backbone.Model.extend({defaults:{elems:[],open:false},clear_search_results:function(){x.each(this.attributes.elems,function(z){z.show()});this.show();this.set("open",false)},apply_search_results:function(A){var B=true,z;x.each(this.attributes.elems,function(C){if(C instanceof v){z=C;z.hide()}else{if(C instanceof j){if(C.apply_search_results(A)){B=false;if(z){z.show()}}}}});if(B){this.hide()}else{this.show();this.set("open",true)}}});x.extend(l.prototype,g);var c=Backbone.Model.extend({defaults:{search_hint_string:"search tools",min_chars_for_search:3,spinner_url:"",clear_btn_url:"",search_url:"",visible:true,query:"",results:null,clear_key:27},initialize:function(){this.on("change:query",this.do_search)},do_search:function(){var B=this.attributes.query;if(B.length<this.attributes.min_chars_for_search){this.set("results",null);return}var A=B+"*";if(this.timer){clearTimeout(this.timer)}$("#search-clear-btn").hide();$("#search-spinner").show();var z=this;this.timer=setTimeout(function(){$.get(z.attributes.search_url,{query:A},function(C){z.set("results",C);$("#search-spinner").hide();$("#search-clear-btn").show()},"json")},200)},clear_search:function(){this.set("query","");this.set("results",null)}});x.extend(c.prototype,g);var o=Backbone.Model.extend({initialize:function(z){this.attributes.tool_search=z.tool_search;this.attributes.tool_search.on("change:results",this.apply_search_results,this);this.attributes.tools=z.tools;this.attributes.layout=new Backbone.Collection(this.parse(z.layout))},parse:function(A){var z=this,B=function(E){var D=E.model_class;if(D.indexOf("Tool")===D.length-4){return z.attributes.tools.get(E.id)}else{if(D==="ToolSection"){var C=x.map(E.elems,B);E.elems=C;return new l(E)}else{if(D==="ToolSectionLabel"){return new v(E)}}}};return x.map(A,B)},clear_search_results:function(){this.get("layout").each(function(z){if(z instanceof l){z.clear_search_results()}else{z.show()}})},apply_search_results:function(){var A=this.get("tool_search").get("results");if(A===null){this.clear_search_results();return}var z=null;this.get("layout").each(function(B){if(B instanceof v){z=B;z.hide()}else{if(B instanceof j){if(B.apply_search_results(A)){if(z){z.show()}}}else{z=null;B.apply_search_results(A)}}})}});var s=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){(this.model.attributes.hidden?this.$el.hide():this.$el.show())}});var m=s.extend({tagName:"div",render:function(){var z=$("<div/>");z.append(Handlebars.templates.tool_link(this.model.toJSON()));if(this.model.id==="upload1"){z.find("a").on("click",function(A){A.preventDefault();Galaxy.upload.show()})}this.$el.append(z);return this}});var b=s.extend({tagName:"div",className:"toolPanelLabel",render:function(){this.$el.append($("<span/>").text(this.model.attributes.text));return this}});var r=s.extend({tagName:"div",className:"toolSectionWrapper",initialize:function(){s.prototype.initialize.call(this);this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(Handlebars.templates.panel_section(this.model.toJSON()));var z=this.$el.find(".toolSectionBody");x.each(this.model.attributes.elems,function(A){if(A instanceof j){var B=new m({model:A,className:"toolTitle"});B.render();z.append(B.$el)}else{if(A instanceof v){var C=new b({model:A});C.render();z.append(C.$el)}else{}}});return this},events:{"click .toolSectionTitle > a":"toggle"},toggle:function(){this.model.set("open",!this.model.attributes.open)},update_open:function(){(this.model.attributes.open?this.$el.children(".toolSectionBody").slideDown("fast"):this.$el.children(".toolSectionBody").slideUp("fast"))}});var p=Backbone.View.extend({tagName:"div",id:"tool-search",className:"bar",events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){this.$el.append(Handlebars.templates.tool_search(this.model.toJSON()));if(!this.model.is_visible()){this.$el.hide()}this.$el.find("[title]").tooltip();return this},focus_and_select:function(){this.$el.find(":input").focus().select()},clear:function(){this.model.clear_search();this.$el.find(":input").val(this.model.attributes.search_hint_string);this.focus_and_select();return false},query_changed:function(z){if((this.model.attributes.clear_key)&&(this.model.attributes.clear_key===z.which)){this.clear();return false}this.model.set("query",this.$el.find(":input").val())}});var w=Backbone.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.model.get("tool_search").on("change:results",this.handle_search_results,this)},render:function(){var z=this;var A=new p({model:this.model.get("tool_search")});A.render();z.$el.append(A.$el);this.model.get("layout").each(function(C){if(C instanceof l){var B=new r({model:C});B.render();z.$el.append(B.$el)}else{if(C instanceof j){var D=new m({model:C,className:"toolTitleNoSection"});D.render();z.$el.append(D.$el)}else{if(C instanceof v){var E=new b({model:C});E.render();z.$el.append(E.$el)}}}});z.$el.find("a.tool-link").click(function(D){var C=$(this).attr("class").split(/\s+/)[0],B=z.model.get("tools").get(C);z.trigger("tool_link_click",D,B)});return this},handle_search_results:function(){var z=this.model.get("tool_search").get("results");if(z&&z.length===0){$("#search-no-results").show()}else{$("#search-no-results").hide()}}});var u=Backbone.View.extend({className:"toolForm",render:function(){this.$el.children().remove();this.$el.append(Handlebars.templates.tool_form(this.model.toJSON()))}});var h=Backbone.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new w({collection:this.collection});this.tool_form_view=new u()},render:function(){this.tool_panel_view.render();this.tool_panel_view.$el.css("float","left");this.$el.append(this.tool_panel_view.$el);this.tool_form_view.$el.hide();this.$el.append(this.tool_form_view.$el);var z=this;this.tool_panel_view.on("tool_link_click",function(B,A){B.preventDefault();z.show_tool(A)})},show_tool:function(A){var z=this;A.fetch().done(function(){z.tool_form_view.model=A;z.tool_form_view.render();z.tool_form_view.$el.show();$("#left").width("650px")})}});return{ToolParameter:e,IntegerToolParameter:d,SelectToolParameter:t,Tool:j,ToolCollection:n,ToolSearch:c,ToolPanel:o,ToolPanelView:w,ToolFormView:u}}); \ No newline at end of file Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.