/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* Starting using Exhibit.jQuery instead of jQuery or $
*/
(function() {
Exhibit.jQuery = jQuery;
if (!Exhibit._jQueryExists) {
jQuery.noConflict();
}
}());
/**
* @static
* @param {Exhibit.Database} database
* @returns {Exhibit._Impl}
*/
Exhibit.create = function(database) {
return new Exhibit._Impl(database);
};
/**
* Code to automatically create the database, load the data links in
*
, and then to create an exhibit if there's no Exhibit ondataload
* attribute on the body element.
*
* You can avoid running this code by adding the URL parameter
* autoCreate=false when you include exhibit-api.js.
* @public
* @see Exhibit.Database._LocalImpl.prototype._loadLinks
*/
Exhibit.autoCreate = function() {
var s, f, fDone;
fDone = function() {
window.exhibit = Exhibit.create();
window.exhibit.configureFromDOM();
// The semantics of dataload indicate it should wholly replace the
// Exhibit initialization steps above; but if autoCreate is true,
// perhaps it should run in parallel with them or be fired after
// them. It's unclear how widespread this is and how useful one
// alternative is over the other. If in the future the below block
// is eliminated as it should be, wholesale replacement of this fDone
// would currently not be possible.
};
try {
// Using functions embedded in elements is bad practice and support for
// it may disappear in the future. Convert instances of this usage to
// attach to the dataload.exhibit event triggered on your own, as this
// now does (see line below this try-catch block).
s = Exhibit.getAttribute(document.body, "ondataload");
if (s !== null && typeof s === "string" && s.length > 0) {
// eval is evil, which is why this is going to disappear.
f = eval(s);
if (typeof f === "function") {
fDone = f;
}
}
} catch (e) {
Exhibit.Debug.warn(Exhibit._("%general.error.dataloadExecution"));
Exhibit.Debug.warn(e);
}
Exhibit.jQuery(document.body).one("dataload.exhibit", fDone);
window.database = Exhibit.Database.create();
window.database.loadLinks();
};
/**
* Check for instances of ex:role and throw into backwards compatibility
* mode if any are found. Authors are responsible for converting or using
* the HTML5 attributes correctly; backwards compatibility is only applicable
* when used with unconverted Exhibits.
* @static
* @see Exhibit.Backwards
*/
Exhibit.checkBackwardsCompatibility = function() {
var exroles;
exroles = Exhibit.jQuery("*").filter(function() {
return typeof Exhibit.jQuery(this).attr("ex:role") !== "undefined";
});
if (exroles.length > 0) {
Exhibit.Backwards.enable("Attributes");
}
};
/**
* Retrieve an Exhibit-specific attribute from an element.
*
* @static
* @param {jQuery|Element} elmt
* @param {String} name Full attribute name or Exhibit attribute (without any
* prefix), e.g., "id" or "itemTypes". "item-types" or "data-ex-item-types"
* are equivalent to "itemTypes", but "itemTypes" is the preferred form.
* @param {String} splitOn Separator character to split a string
* representation into several values. Returns an array if used.
* @returns {String|Array}
*/
Exhibit.getAttribute = function(elmt, name, splitOn) {
var value, i, values;
try {
value = Exhibit.jQuery(elmt).attr(name);
if (typeof value === "undefined" || value === null || value.length === 0) {
value = Exhibit.jQuery(elmt).data("ex-"+name);
if (typeof value === "undefined" || value === null || value.length === 0) {
return null;
}
}
if (typeof value.toString !== "undefined") {
value = value.toString();
}
if (typeof splitOn === "undefined" || splitOn === null) {
return value;
}
values = value.split(splitOn);
for (i = 0; i < values.length; i++) {
values[i] = values[i].trim();
}
return values;
} catch(e) {
return null;
}
};
/**
* @static
* @param {Element} elmt
* @returns {String}
*/
Exhibit.getRoleAttribute = function(elmt) {
var role = Exhibit.getAttribute(elmt, "role") || "";
if (typeof role === "object") {
role = role[0];
}
role = role.replace(/^exhibit-/, "");
return role;
};
/**
* Process a DOM element's attribute name to see if it is an Exhibit
* attribute.
* @static
* @param {String} name
* @returns {Boolean}
*/
Exhibit.isExhibitAttribute = function(name) {
return name.length > "data-ex-".length
&& name.startsWith("data-ex-");
};
/**
* Process a DOM element's attribute and convert it into the name Exhibit
* uses internally.
* @static
* @param {String} name
* @returns {String}
*/
Exhibit.extractAttributeName = function(name) {
return name.substr("data-ex-".length);
};
/**
* Turn an internal attribute name into something that can be inserted into
* the DOM and correctly re-extracted later as an Exhibit attribute.
* @static
* @param {String} name
*/
Exhibit.makeExhibitAttribute = function(name) {
var exname;
switch (name) {
case "itemID":
exname = "itemid";
break;
default:
exname = "data-ex-" + name.replace(/([A-Z])/g, "-$1").toLowerCase();
break;
}
return exname;
};
/**
* @static
* @param {Element} elmt
* @returns {Object}
*/
Exhibit.getConfigurationFromDOM = function(elmt) {
var c, o;
c = Exhibit.getAttribute(elmt, "configuration");
if (typeof c !== "undefined" && c !== null && c.length > 0) {
try{
o = eval(c);
if (typeof o === "object") {
return o;
}
} catch (e) {}
}
return {};
};
/**
* This method is not commonly used. Consider using Exhibit.SettingsUtilties.
* @deprecated
* @static
* @param {Element} elmt
* @returns {Object}
*/
Exhibit.extractOptionsFromElement = function(elmt) {
var opts, dataset, i;
opts = {};
dataset = Exhibit.jQuery(elmt).data();
for (i in dataset) {
if (dataset.hasOwnProperty(i)) {
if (i.startsWith("ex")) {
opts[i.substring(2)] = dataset[i];
} else {
opts[i] = dataset[i];
}
}
}
return opts;
};
/**
* @public
* @class
* @constructor
* @param {Exhibit.Database} database
*/
Exhibit._Impl = function(database) {
this._database = (database !== null && typeof database !== "undefined") ?
database :
(typeof window.database !== "undefined" ?
window.database :
Exhibit.Database.create());
this._uiContext = Exhibit.UIContext.createRootContext({}, this);
this._registry = new Exhibit.Registry();
Exhibit.jQuery(document).trigger("registerComponents.exhibit", this._registry);
this._collectionMap = {};
};
/**
*
*/
Exhibit._Impl.prototype.dispose = function() {
var id;
for (id in this._collectionMap) {
if (this._collectionMap.hasOwnProperty(id)) {
try {
this._collectionMap[id].dispose();
} catch(ex2) {
Exhibit.Debug.exception(ex2, Exhibit._("%general.error.disposeCollection"));
}
}
}
this._uiContext.dispose();
this._collectionMap = null;
this._uiContext = null;
this._database = null;
this._registry.dispose();
this._registry = null;
};
/**
* @returns {Exhibit.Database}
*/
Exhibit._Impl.prototype.getDatabase = function() {
return this._database;
};
/**
* @returns {Exhibit.Registry}
*/
Exhibit._Impl.prototype.getRegistry = function() {
return this._registry;
};
/**
* @returns {Exhibit.UIContext}
*/
Exhibit._Impl.prototype.getUIContext = function() {
return this._uiContext;
};
/**
* @param {String} id
* @returns {Exhibit.Collection}
*/
Exhibit._Impl.prototype.getCollection = function(id) {
var collection = this._collectionMap[id];
if ((typeof collection === "undefined" || collection === null) && id === "default") {
collection = Exhibit.Collection.createAllItemsCollection(id, this._database);
this.setDefaultCollection(collection);
}
return collection;
};
/**
* @returns {Exhibit.Collection}
*/
Exhibit._Impl.prototype.getDefaultCollection = function() {
return this.getCollection("default");
};
/**
* @param {String} id
* @param {Exhibit.Collection} c
*/
Exhibit._Impl.prototype.setCollection = function(id, c) {
if (typeof this._collectionMap[id] !== "undefined") {
try {
this._collectionMap[id].dispose();
} catch(e) {
Exhibit.Debug.exception(e);
}
}
this._collectionMap[id] = c;
};
/**
* @param {Exhibit.Collection} c
*/
Exhibit._Impl.prototype.setDefaultCollection = function(c) {
this.setCollection("default", c);
};
/**
* @param {String} id
* @returns {Object}
*/
Exhibit._Impl.prototype.getComponent = function(id) {
return this.getRegistry().getID(id);
};
/**
* @param {Object} configuration
*/
Exhibit._Impl.prototype.configure = function(configuration) {
var i, config, id, component;
if (typeof configuration.collections !== "undefined") {
for (i = 0; i < configuration.collections.length; i++) {
config = configuration.collections[i];
id = config.id;
if (typeof id === "undefined" || id === null || id.length === 0) {
id = "default";
}
this.setCollection(id, Exhibit.Collection.create2(id, config, this._uiContext));
}
}
if (typeof configuration.components !== "undefined") {
for (i = 0; i < configuration.components.length; i++) {
config = configuration.components[i];
component = Exhibit.UI.create(config, config.elmt, this._uiContext);
}
}
};
/**
* Set up this Exhibit's view from its DOM configuration.
* @param {Node} [root] optional root node, below which configuration gets read
* (defaults to document.body, when none provided)
*/
Exhibit._Impl.prototype.configureFromDOM = function(root) {
var controlPanelElmts, collectionElmts, coderElmts, coordinatorElmts, lensElmts, facetElmts, otherElmts, f, uiContext, i, elmt, id, self, processElmts, panel, exporters, expr, exporter, hash, itemID;
collectionElmts = [];
coderElmts = [];
coordinatorElmts = [];
lensElmts = [];
facetElmts = [];
controlPanelElmts = [];
otherElmts = [];
f = function(elmt) {
var role, node;
role = Exhibit.getRoleAttribute(elmt);
if (role.length > 0) {
switch (role) {
case "collection": collectionElmts.push(elmt); break;
case "coder": coderElmts.push(elmt); break;
case "coordinator": coordinatorElmts.push(elmt); break;
case "lens":
case "submission-lens":
case "edit-lens": lensElmts.push(elmt); break;
case "facet": facetElmts.push(elmt); break;
case "controlPanel": controlPanelElmts.push(elmt); break;
default:
otherElmts.push(elmt);
}
} else {
node = elmt.firstChild;
while (typeof node !== "undefined" && node !== null) {
if (node.nodeType === 1) {
f(node);
}
node = node.nextSibling;
}
}
};
f(root || document.body);
uiContext = this._uiContext;
for (i = 0; i < collectionElmts.length; i++) {
elmt = collectionElmts[i];
id = elmt.id;
if (typeof id === "undefined" || id === null || id.length === 0) {
id = "default";
}
this.setCollection(id, Exhibit.Collection.createFromDOM2(id, elmt, uiContext));
}
self = this;
processElmts = function(elmts) {
var i, elmt;
for (i = 0; i < elmts.length; i++) {
elmt = elmts[i];
try {
Exhibit.UI.createFromDOM(elmt, uiContext);
} catch (ex1) {
Exhibit.Debug.exception(ex1);
}
}
};
processElmts(coordinatorElmts);
processElmts(coderElmts);
processElmts(lensElmts);
processElmts(facetElmts);
if (controlPanelElmts.length === 0) {
panel = Exhibit.ControlPanel.createFromDOM(
Exhibit.jQuery("
"
);
Exhibit.jQuery(dom.elmt).attr("class", "exhibit-focusDialog exhibit-ui-protection");
Exhibit.UI.setupDialog(dom, true);
itemLens = this._uiContext.getLensRegistry().createLens(itemID, dom.lensContainer, this._uiContext);
Exhibit.jQuery(dom.elmt).css("top", (document.body.scrollTop + 100) + "px");
Exhibit.jQuery(document.body).append(Exhibit.jQuery(dom.elmt));
Exhibit.jQuery(document).trigger("modalSuperseded.exhibit");
Exhibit.jQuery(dom.closeButton).bind("click", function(evt) {
dom.close();
});
};
/**
* @fileOverview General backwards compatibility material.
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Backwards = {
"enabled": {
"Attributes": false
}
};
/**
* Enable a backwards compatibility module.
* @param {String} module
*/
Exhibit.Backwards.enable = function(module) {
Exhibit.Backwards[module].enable();
};
/**
* @fileOverview Methods for handling older Exhibit attribute styles. Only
* load if the page-based config seems to contain a namespace / attributes
* that reflect the old style (e.g., ex:role) instead of the new style.
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Backwards.Attributes = {
"prefix": "ex:"
};
/**
* Call to switch Exhibit into backwards compatibility mode for Exhibit
* attributes.
* @static
*/
Exhibit.Backwards.Attributes.enable = function() {
Exhibit.Backwards.enabled.Attributes = true;
Exhibit.getAttribute = Exhibit.Backwards.Attributes.getAttribute;
Exhibit.extractOptionsFromElement = Exhibit.Backwards.Attributes.extractOptionsFromElement;
Exhibit.isExhibitAttribute = Exhibit.Backwards.Attributes.isExhibitAttribute;
Exhibit.makeExhibitAttribute = Exhibit.Backwards.Attributes.makeExhibitAttribute;
Exhibit.extractAttributeName = Exhibit.Backwards.Attributes.extractAttributeName;
};
/**
* A backwards compatible mechanism for retrieving an Exhibit attribute value.
* @static
* @param {jQuery|Element} elmt
* @param {String} name
* @param {String} splitOn
* @returns {String|Array}
*/
Exhibit.Backwards.Attributes.getAttribute = function(elmt, name, splitOn) {
var value, i, values;
try {
value = Exhibit.jQuery(elmt).attr(name);
if (typeof value === "undefined" || value === null || value.length === 0) {
value = Exhibit.jQuery(elmt).attr(Exhibit.Backwards.Attributes.prefix+name);
if (typeof value === "undefined" || value === null || value.length === 0) {
return null;
}
}
if (typeof splitOn === "undefined" || splitOn === null) {
return value;
}
values = value.split(splitOn);
for (i = 0; i < values.length; i++) {
values[i] = values[i].trim();
}
return values;
} catch(e) {
return null;
}
};
/**
* A backwards compatible mechanism for retrieving all Exhibit attributes
* on an element.
* @static
* @param {Element} elmt
* @returns {Object}
*/
Exhibit.Backwards.Attributes.extractOptionsFromElement = function(elmt) {
var opts, attrs, i, name, value;
opts = {};
attrs = elmt.attributes;
for (i in attrs) {
if (attrs.hasOwnProperty(i)) {
name = attrs[i].nodeName;
value = attrs[i].nodeValue;
if (name.indexOf(Exhibit.Backwards.Attributes.prefix) === 0) {
name = name.substring(Exhibit.Backwards.Attributes.prefix.length);
}
opts[name] = value;
}
}
return opts;
};
/**
* @static
* @param {String} name
* @returns {Boolean}
*/
Exhibit.Backwards.Attributes.isExhibitAttribute = function(name) {
var prefix = Exhibit.Backwards.Attributes.prefix;
return name.length > prefix.length
&& name.startsWith(prefix);
};
/**
* @static
* @param {String} name
*/
Exhibit.Backwards.Attributes.extractAttributeName = function(name) {
return name.substr(Exhibit.Backwards.Attributes.prefix.length);
};
/**
* @static
* @param {String} name
* @returns {String}
*/
Exhibit.Backwards.Attributes.makeExhibitAttribute = function(name) {
return Exhibit.Backwards.Attributes.prefix + name;
};
/**
* @fileOverview Centralized component registry for easier API access.
* @author Ryan Lee
*/
/**
* @namespace
* @class
*/
Exhibit.Registry = function(isStatic) {
this._registry = {};
this._idCache = {};
this._components = [];
this._isStatic = (typeof isStatic !== "undefined" && isStatic !== null) ?
isStatic :
false;
};
Exhibit.Registry.prototype.isStatic = function() {
return this._isStatic;
};
/**
* @param {String} component
* @returns {Boolean}
*/
Exhibit.Registry.prototype.createRegistry = function(component) {
this._registry[component] = {};
this._components.push(component);
};
/**
* @returns {Array}
*/
Exhibit.Registry.prototype.components = function() {
return this._components;
};
/**
* @param {String} component
* @returns {Boolean}
*/
Exhibit.Registry.prototype.hasRegistry = function(component) {
return typeof this._registry[component] !== "undefined";
};
/**
* @param {String} component
* @returns {Number}
*/
Exhibit.Registry.prototype.generateIdentifier = function(component) {
var branch, key, size;
size = 0;
branch = this._registry[component];
if (typeof branch !== "undefined") {
for (key in branch) {
if (branch.hasOwnProperty(key)) {
size++;
}
}
} else {
throw new Error(Exhibit._("%registry.error.noSuchRegistry", component));
}
return size;
};
/**
* @param {String} component
* @param {String} id
* @returns {Boolean}
*/
Exhibit.Registry.prototype.isRegistered = function(component, id) {
return (this.hasRegistry(component) &&
typeof this._registry[component][id] !== "undefined");
};
/**
* @param {String} component
* @param {String} id
* @param {Object} handler
* @returns {Boolean}
*/
Exhibit.Registry.prototype.register = function(component, id, handler) {
if (!this.isRegistered(component, id)) {
this._registry[component][id] = handler;
if (!this.isStatic() && typeof this._idCache[id] === "undefined") {
this._idCache[id] = handler;
}
return true;
} else {
return false;
}
};
/**
* @param {String} component
* @returns {Object}
*/
Exhibit.Registry.prototype.componentHandlers = function(component) {
if (this.hasRegistry(component)) {
return this._registry[component];
} else {
return null;
}
};
/**
* @param {String} component
* @returns {Array}
*/
Exhibit.Registry.prototype.getKeys = function(component) {
var hash, key, keys;
hash = this._registry[component];
keys = [];
for (key in hash) {
if (hash.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
};
/**
* Called when both the type of component and its ID are known to retrieve
* the object associated with both.
*
* @param {String} component
* @param {String} id
* @returns {Object}
* @see Exhibit.Registry.prototype.getID
*/
Exhibit.Registry.prototype.get = function(component, id) {
if (this.isRegistered(component, id)) {
return this._registry[component][id];
} else {
return null;
}
};
/**
* Called when the component type cannot be specified at the time of
* calling but the ID is expected to exist. Will always return null
* for a static registry.
*
* @param {String} id
* @returns {Object}
* @see Exhibit.Registry.prototype.get
*/
Exhibit.Registry.prototype.getID = function(id) {
if (!this.isStatic()) {
if (typeof this._idCache[id] !== "undefined") {
return this._idCache[id];
}
}
return null;
};
/**
* Typically called from within a dispose() method, removes component
* from Exhibit's awareness, and the handler should be garbage collected.
*
* @param {String} component
* @param {String} id
* @returns {Boolean}
*/
Exhibit.Registry.prototype.unregister = function(component, id) {
var c;
if (this.isRegistered(component, id)) {
c = this.get(component, id);
this._registry[component][id] = null;
delete this._registry[component][id];
if (!this.isStatic() && typeof this._idCache[id] !== "undefined") {
this._idCache[id] = null;
delete this._idCache[id];
}
}
};
/**
* @author David Huynh
* @author Ryan Lee
* @fileOverview Base for Exhibit utilities and native datatype modifications.
*/
/**
* @namespace For Exhibit utility classes and methods.
*/
Exhibit.Util = {};
/**
* Round a number n to the nearest multiple of precision (any positive value),
* such as 5000, 0.1 (one decimal), 1e-12 (twelve decimals), or 1024 (if you'd
* want "to the nearest kilobyte" -- so round(66000, 1024) == "65536"). You are
* also guaranteed to get the precision you ask for, so round(0, 0.1) == "0.0".
*
* @static
* @param {Number} n Original number.
* @param {Number} [precision] Rounding bucket, by default 1.
* @returns {String} Rounded number into the nearest bucket at the bucket's
* precision, in a form readable by users.
*/
Exhibit.Util.round = function(n, precision) {
var lg;
precision = precision || 1;
lg = Math.floor( Math.log(precision) / Math.log(10) );
n = (Math.round(n / precision) * precision).toString();
if (lg >= 0) {
return parseInt(n, 10).toString();
}
lg = -lg;
return parseFloat(n).toFixed(lg);
};
/**
* Modify the native String type.
*/
(function() {
if (typeof String.prototype.trim === "undefined") {
/**
* Removes leading and trailing spaces.
*
* @returns {String} Trimmed string.
*/
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
if (typeof String.prototype.startsWith === "undefined") {
/**
* Test if a string begins with a prefix.
*
* @param {String} prefix Prefix to check.
* @returns {Boolean} True if string starts with prefix, false if not.
*/
String.prototype.startsWith = function(prefix) {
return this.length >= prefix.length && this.substr(0, prefix.length) === prefix;
};
}
if (typeof String.prototype.endsWith === "undefined") {
/**
* Test if a string ends with a suffix.
*
* @param {String} suffix Suffix to check.
* @returns {Boolean} True if string ends with suffix, false if not.
*/
String.prototype.endsWith = function(suffix) {
return this.length >= suffix.length && this.substr(this.length - suffix.length) === suffix;
};
}
}());
/**
* @fileOverview Methods for generating and interpreting session state
* bookmarks.
* @author Ryan Lee
*/
/**
* @namespace Bookmarking the current state of a browsing session.
*/
Exhibit.Bookmark = {
/**
* Whether the History system should load the bookmarked state.
*
* @private
*/
_shouldRun: undefined,
/**
* The bookmark state read in by the bookmarking system.
*/
state: {},
/**
* Whether a bookmark was used at the start or not.
*/
run: undefined
};
/**
* Generate a string that can be used as the hash portion of a URI
* to be used for bookmarking the current state of an Exhibit browsing
* session.
*
* @static
* @param state {Object} An JSON serializable object fully describing
* the current state.
* @returns {String} The Base64-encoded string representing a JSON
* serialized object.
* @depends JSON
* @depends Base64
*/
Exhibit.Bookmark.generateBookmarkHash = function(state) {
if (typeof state === "undefined" ||
state === null ||
typeof state.data === "undefined" ||
state.data === null ||
typeof state.data.state === "undefined" ||
state.data.state === null) {
return "";
}
return Base64.encode(JSON.stringify(state));
};
/**
* Turn a bookmark hash into a representation of state.
*
* @static
* @param hash {String} A Base64-encoded string representing a JSON
* serialized object.
* @returns {Object} The deserialized object represented by the hash.
* @depends JSON
* @depends Base64
*/
Exhibit.Bookmark.interpretBookmarkHash = function(hash) {
if (typeof hash === "undefined" || hash === null || hash === "") {
return null;
} else {
return JSON.parse(Base64.decode(hash));
}
};
/**
* Given the current page state from Exhibit.History, make a bookmark URI.
*
* @static
* @returns {String} The bookmark URI
* @depends Exhibit.History
*/
Exhibit.Bookmark.generateBookmark = function() {
var hash;
hash = Exhibit.Bookmark.generateBookmarkHash(Exhibit.History.getState());
return document.location.href + ((hash === "") ? "": "#" + hash);
};
/**
* Change the state of the page given an interpreted bookmark hash.
*
* @static
* @param state {Object} The interpreted bookmark hash as the state
* object History.js uses.
* @depends Exhibit.History
*/
Exhibit.Bookmark.implementBookmark = function(state) {
if (typeof state !== "undefined" && state !== null) {
Exhibit.History.replaceState(state.data, state.title, state.url);
Exhibit.Bookmark.run = true;
}
};
/**
* Answer whether the bookmark system should run or not on the hash
* (if there is a hash) in the current URL.
*
* @returns {Boolean}
*/
Exhibit.Bookmark.runBookmark = function() {
return Exhibit.Bookmark._shouldRun;
};
/**
* When run, examine this page's URI for a hash and try to interpret and
* implement it.
*
* @static
*/
Exhibit.Bookmark.init = function() {
var hash, state;
hash = document.location.hash;
if (hash.length > 0) {
try {
state = Exhibit.Bookmark.interpretBookmarkHash(hash.substr(1));
if (typeof state === "object" &&
typeof state["data"] !== "undefined" &&
typeof state["title"] !== "undefined" &&
typeof state["url"] !== "undefined") {
Exhibit.Bookmark.state = state;
Exhibit.Bookmark._shouldRun = true;
} else {
Exhibit.Bookmark._shouldRun = false;
}
} catch (ex) {
Exhibit.Bookmark._shouldRun = false;
} finally {
Exhibit.Bookmark.run = false;
}
}
};
/**
* @fileOverview Default colors for color coders.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Coders = {
/**
* @constant
*/
"mixedCaseColor": "#fff",
/**
* @constant
*/
"othersCaseColor": "#aaa",
/**
* @constant
*/
"missingCaseColor": "#888"
};
/**
* @author David Huynh
* @author Ryan Lee
* @fileOverview A collection of date/time utility functions.
*/
/**
* @namespace A collection of date/time utility functions.
*/
Exhibit.DateTime = {};
/** @constant */
Exhibit.DateTime.MILLISECOND = 0; /** @constant */
Exhibit.DateTime.SECOND = 1; /** @constant */
Exhibit.DateTime.MINUTE = 2; /** @constant */
Exhibit.DateTime.HOUR = 3; /** @constant */
Exhibit.DateTime.DAY = 4; /** @constant */
Exhibit.DateTime.WEEK = 5; /** @constant */
Exhibit.DateTime.MONTH = 6; /** @constant */
Exhibit.DateTime.YEAR = 7; /** @constant */
Exhibit.DateTime.DECADE = 8; /** @constant */
Exhibit.DateTime.CENTURY = 9; /** @constant */
Exhibit.DateTime.MILLENNIUM = 10; /** @constant */
Exhibit.DateTime.QUARTER = 11; /** @constant */
Exhibit.DateTime.EPOCH = -1; /** @constant */
Exhibit.DateTime.ERA = -2;
/**
* An array of unit lengths, expressed in milliseconds, of various lengths of
* time. The array indices are predefined and stored as properties of the
* Exhibit.DateTime object, e.g. Exhibit.DateTime.YEAR.
* @constant
* @type {Array}
*/
Exhibit.DateTime.gregorianUnitLengths = [];
(function() {
var d = Exhibit.DateTime, a = d.gregorianUnitLengths;
a[d.MILLISECOND] = 1;
a[d.SECOND] = 1000;
a[d.MINUTE] = a[d.SECOND] * 60;
a[d.HOUR] = a[d.MINUTE] * 60;
a[d.DAY] = a[d.HOUR] * 24;
a[d.WEEK] = a[d.DAY] * 7;
a[d.MONTH] = a[d.DAY] * 31;
a[d.QUARTER] = a[d.MONTH] * 3;
a[d.YEAR] = a[d.DAY] * 365;
a[d.DECADE] = a[d.YEAR] * 10;
a[d.CENTURY] = a[d.YEAR] * 100;
a[d.MILLENNIUM] = a[d.YEAR] * 1000;
}());
/**
* @private
* @static
* @constant
*/
Exhibit.DateTime._dateRegexp = new RegExp(
"^(-?)([0-9]{4})(" + [
"(-?([0-9]{2})(-?([0-9]{2}))?)", // -month-dayOfMonth
"(-?([0-9]{3}))", // -dayOfYear
"(-?W([0-9]{2})(-?([1-7]))?)" // -Wweek-dayOfWeek
].join("|") + ")?$"
);
/**
* @private
* @static
* @constant
*/
Exhibit.DateTime._timezoneRegexp = /Z|(([\-+])([0-9]{2})(:?([0-9]{2}))?)$/;
/**
* @private
* @static
* @constant
*/
Exhibit.DateTime._timeRegexp = /^([0-9]{2})(:?([0-9]{2})(:?([0-9]{2})(\.([0-9]+))?)?)?$/;
/**
* Takes a date object and a string containing an ISO 8601 date and sets the
* the date using information parsed from the string. Note that this method
* does not parse any time information.
*
* @static
* @param {Date} dateObject The date object to modify.
* @param {String} string An ISO 8601 string to parse.
* @returns {Date} The modified date object.
*/
Exhibit.DateTime.setIso8601Date = function(dateObject, string) {
/*
* This function has been adapted from dojo.date, v.0.3.0
* http://dojotoolkit.org/.
*/
var d, sign, year, month, date, dayofyear, week, dayofweek, gd, day, offset;
d = string.match(Exhibit.DateTime._dateRegexp);
if (!d) {
throw new Error(Exhibit._("%datetime.error.invalidDate", string));
}
sign = (d[1] === "-") ? -1 : 1; // BC or AD
year = sign * d[2];
month = d[5];
date = d[7];
dayofyear = d[9];
week = d[11];
dayofweek = (d[13]) ? d[13] : 1;
dateObject.setUTCFullYear(year);
if (dayofyear) {
dateObject.setUTCMonth(0);
dateObject.setUTCDate(Number(dayofyear));
} else if (week) {
dateObject.setUTCMonth(0);
dateObject.setUTCDate(1);
gd = dateObject.getUTCDay();
day = (gd) ? gd : 7;
offset = Number(dayofweek) + (7 * Number(week));
if (day <= 4) {
dateObject.setUTCDate(offset + 1 - day);
} else {
dateObject.setUTCDate(offset + 8 - day);
}
} else {
if (month) {
dateObject.setUTCDate(1);
dateObject.setUTCMonth(month - 1);
}
if (date) {
dateObject.setUTCDate(date);
}
}
return dateObject;
};
/**
* Takes a date object and a string containing an ISO 8601 time and sets the
* the time using information parsed from the string. Note that this method
* does not parse any date information.
*
* @param {Date} dateObject The date object to modify.
* @param {String} string An ISO 8601 string to parse.
* @returns {Date} The modified date object.
*/
Exhibit.DateTime.setIso8601Time = function (dateObject, string) {
/*
* This function has been adapted from dojo.date, v.0.3.0
* http://dojotoolkit.org/.
*/
var d, hours, mins, secs, ms;
d = string.match(Exhibit.DateTime._timeRegexp);
if (!d) {
throw new Error(Exhibit._("%datetime.error.invalidTime", string));
}
hours = d[1];
mins = Number((d[3]) ? d[3] : 0);
secs = (d[5]) ? d[5] : 0;
ms = d[7] ? (Number("0." + d[7]) * 1000) : 0;
dateObject.setUTCHours(hours);
dateObject.setUTCMinutes(mins);
dateObject.setUTCSeconds(secs);
dateObject.setUTCMilliseconds(ms);
return dateObject;
};
/**
* The timezone offset in minutes in the user's browser.
* @type {Number}
*/
Exhibit.DateTime.timezoneOffset = new Date().getTimezoneOffset();
/**
* Takes a date object and a string containing an ISO 8601 date and time and
* sets the date object using information parsed from the string.
*
* @param {Date} dateObject The date object to modify.
* @param {String} string An ISO 8601 string to parse.
* @returns {Date} The modified date object.
*/
Exhibit.DateTime.setIso8601 = function (dateObject, string){
/*
* This function has been adapted from dojo.date, v.0.3.0
* http://dojotoolkit.org/.
*/
var offset, comps, d;
offset = null;
comps = (string.indexOf("T") === -1) ? string.split(" ") : string.split("T");
Exhibit.DateTime.setIso8601Date(dateObject, comps[0]);
if (comps.length === 2) {
// first strip timezone info from the end
d = comps[1].match(Exhibit.DateTime._timezoneRegexp);
if (d) {
if (d[0] === 'Z') {
offset = 0;
} else {
offset = (Number(d[3]) * 60) + Number(d[5]);
offset *= ((d[2] === '-') ? 1 : -1);
}
comps[1] = comps[1].substr(0, comps[1].length - d[0].length);
}
Exhibit.DateTime.setIso8601Time(dateObject, comps[1]);
}
if (typeof offset === "undefined" || offset === null) {
offset = dateObject.getTimezoneOffset(); // local time zone if no tz info
}
dateObject.setTime(dateObject.getTime() + offset * 60000);
return dateObject;
};
/**
* Takes a string containing an ISO 8601 date and returns a newly instantiated
* date object with the parsed date and time information from the string.
*
* @param {String} string An ISO 8601 string to parse.
* @returns {Date} A new date object created from the string.
*/
Exhibit.DateTime.parseIso8601DateTime = function (string) {
try {
return Exhibit.DateTime.setIso8601(new Date(0), string);
} catch (e) {
return null;
}
};
/**
* Takes a string containing a Gregorian date and time and returns a newly
* instantiated date object with the parsed date and time information from the
* string. If the param is actually an instance of Date instead of a string,
* simply returns the given date instead. Note the times are considered UTC
* by default, so, e.g., setting a year only may result in a different value
* if you subsequently use non-UTC getters.
*
* @param {Date|String} o An object, to either return or parse as a string.
* @returns {Date} The date object.
*/
Exhibit.DateTime.parseGregorianDateTime = function(o) {
var s, space, year, suffix, d;
if (typeof o === "undefined" || o === null) {
return null;
} else if (o instanceof Date) {
return o;
}
s = o.toString();
if (s.length > 0 && s.length < 8) {
space = s.indexOf(" ");
if (space > 0) {
year = parseInt(s.substr(0, space), 10);
suffix = s.substr(space + 1);
if (suffix.toLowerCase() === "bc") {
year = 1 - year;
}
} else {
year = parseInt(s, 10);
}
d = new Date(0);
d.setUTCFullYear(year);
return d;
}
try {
return new Date(Date.parse(s));
} catch (e) {
return null;
}
};
/**
* Rounds date objects down to the nearest interval or multiple of an interval.
* This method modifies the given date object, converting it to the given
* timezone if specified. NB, does not support Exhibit.DateTime.QUARTER.
*
* Rounding to the week is something of an odd concept, so the semantics are
* described here. Weeks are 1-index. The first week of the year goes from
* Jan 1 to the day before the first firstDayOfWeek, unless Jan 1 is the first
* firstDayOfWeek. There can be 54 weeks in a leap year, 53 in a non-leap
* year. Rounding is only done within a year; the farthest to round down is
* the first week of the year. The same day of week is retained unless it
* comes before the first of the year, in which case the first of the year is
* used.
*
* @param {Date} date The date object to round.
* @param {Number} intervalUnit A constant, integer index specifying an
* interval, e.g. Exhibit.DateTime.HOUR.
* @param {Number} timeZone A timezone shift, given in hours.
* @param {Number} multiple A multiple of the interval to round by.
* @param {Number} firstDayOfWeek An integer specifying the first day of the
* week, 0 corresponds to Sunday, 1 to Monday, etc.
*/
Exhibit.DateTime.roundDownToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) {
var timeShift, date2, clearInDay, clearInYear, x, first;
timeShift = timeZone *
Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.HOUR];
date2 = new Date(date.getTime() + timeShift);
clearInDay = Exhibit.DateTime.zeroTimeUTC;
clearInYear = function(d) {
clearInDay(d);
d.setUTCDate(1);
d.setUTCMonth(0);
};
switch(intervalUnit) {
case Exhibit.DateTime.MILLISECOND:
x = date2.getUTCMilliseconds();
date2.setUTCMilliseconds(x - (x % multiple));
break;
case Exhibit.DateTime.SECOND:
date2.setUTCMilliseconds(0);
x = date2.getUTCSeconds();
date2.setUTCSeconds(x - (x % multiple));
break;
case Exhibit.DateTime.MINUTE:
date2.setUTCMilliseconds(0);
date2.setUTCSeconds(0);
x = date2.getUTCMinutes();
date2.setTime(date2.getTime() -
(x % multiple) * Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.MINUTE]);
break;
case Exhibit.DateTime.HOUR:
date2.setUTCMilliseconds(0);
date2.setUTCSeconds(0);
date2.setUTCMinutes(0);
x = date2.getUTCHours();
date2.setUTCHours(x - (x % multiple));
break;
case Exhibit.DateTime.DAY:
clearInDay(date2);
x = date2.getUTCDate();
date2.setUTCDate(x - (x % multiple));
break;
case Exhibit.DateTime.WEEK:
first = new Date(date2.getUTCFullYear(), 0, 1);
clearInDay(date2);
clearInDay(first);
x = Math.ceil((((date2 - first) / Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.DAY]) - ((firstDayOfWeek - first.getUTCDay() + 7) % 7) + 1) / 7) + (first.getUTCDay() !== firstDayOfWeek ? 1 : 0);
date2.setTime(date2.getTime() - ((x % multiple) * Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.WEEK]));
if (date2 < first) {
date2 = first;
}
break;
case Exhibit.DateTime.MONTH:
clearInDay(date2);
date2.setUTCDate(1);
x = date2.getUTCMonth() + 1;
date2.setUTCMonth(Math.max(0, x - 1 - (x % multiple)));
break;
case Exhibit.DateTime.YEAR:
clearInYear(date2);
x = date2.getUTCFullYear();
date2.setUTCFullYear(x - (x % multiple));
break;
case Exhibit.DateTime.DECADE:
clearInYear(date2);
date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 10) * 10);
break;
case Exhibit.DateTime.CENTURY:
clearInYear(date2);
date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 100) * 100);
break;
case Exhibit.DateTime.MILLENNIUM:
clearInYear(date2);
date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 1000) * 1000);
break;
}
date.setTime(date2.getTime() - timeShift);
};
/**
* Rounds date objects up to the nearest interval or multiple of an interval.
* This method modifies the given date object, converting it to the given
* timezone if specified. NB, does not support Exhibit.DateTime.QUARTER.
*
* Unlike round down, round up for week and month may cross year boundaries.
* The semantics here are also weird and probably should not be used for
* anything other than multiples of one, but if you have October and want
* to round up to the nearest fifteenth month, you will get April of the
* next year. Caveat emptor.
*
* @param {Date} date The date object to round.
* @param {Number} intervalUnit A constant, integer index specifying an
* interval, e.g. Exhibit.DateTime.HOUR.
* @param {Number} timeZone A timezone shift, given in hours.
* @param {Number} multiple A multiple of the interval to round by.
* @param {Number} firstDayOfWeek An integer specifying the first day of the
* week, 0 corresponds to Sunday, 1 to Monday, etc.
* @see Exhibit.DateTime.roundDownToInterval
*/
Exhibit.DateTime.roundUpToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) {
var originalTime, useRoundDown, usedRoundDown, date2, first, x, clearInYear;
originalTime = date.getTime();
clearInYear = function(d) {
Exhibit.DateTime.zeroTimeUTC(d);
d.setUTCDate(1);
d.setUTCMonth(0);
};
usedRoundDown = false;
useRoundDown = function() {
Exhibit.DateTime.roundDownToInterval(date, intervalUnit, timeZone, multiple, firstDayOfWeek);
if (date.getTime() < originalTime) {
date.setTime(date.getTime() +
Exhibit.DateTime.gregorianUnitLengths[intervalUnit] * multiple);
}
usedRoundDown = true;
};
timeShift = timeZone *
Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.HOUR];
date2 = new Date(date.getTime() + timeShift);
switch(intervalUnit) {
case Exhibit.DateTime.MILLISECOND:
useRoundDown();
break;
case Exhibit.DateTime.SECOND:
useRoundDown();
break;
case Exhibit.DateTime.MINUTE:
useRoundDown();
break;
case Exhibit.DateTime.HOUR:
useRoundDown();
break;
case Exhibit.DateTime.DAY:
useRoundDown();
break;
case Exhibit.DateTime.WEEK:
first = new Date(date2.getUTCFullYear(), 0, 1);
Exhibit.DateTime.zeroTimeUTC(date2);
Exhibit.DateTime.zeroTimeUTC(first);
x = Math.ceil((((date2 - first) / Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.DAY]) - ((firstDayOfWeek - first.getUTCDay() + 7) % 7) + 1) / 7) + (first.getUTCDay() !== firstDayOfWeek ? 1 : 0);
date2.setTime(date2.getTime() + (((multiple - (x % multiple)) % multiple) * Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.WEEK]));
break;
case Exhibit.DateTime.MONTH:
Exhibit.DateTime.zeroTimeUTC(date2);
date2.setUTCDate(1);
x = date2.getUTCMonth() + 1;
date2.setUTCMonth(x - 1 + (multiple - (x % multiple)) % multiple);
break;
case Exhibit.DateTime.YEAR:
clearInYear(date2);
x = date2.getUTCFullYear();
date2.setUTCFullYear(x + (multiple - (x % multiple)) % multiple);
break;
case Exhibit.DateTime.DECADE:
clearInYear(date2);
date2.setUTCFullYear(Math.ceil(date2.getUTCFullYear() / 10) * 10);
break;
case Exhibit.DateTime.CENTURY:
clearInYear(date2);
date2.setUTCFullYear(Math.ceil(date2.getUTCFullYear() / 100) * 100);
break;
case Exhibit.DateTime.MILLENNIUM:
clearInYear(date2);
date2.setUTCFullYear(Math.ceil(date2.getUTCFullYear() / 1000) * 1000);
break;
}
if (!usedRoundDown) {
date.setTime(date2.getTime() - timeShift);
}
};
/**
* Increments a date object by a specified interval, taking into
* consideration the timezone.
*
* @param {Date} date The date object to increment.
* @param {Number} intervalUnit A constant, integer index specifying an
* interval, e.g. Exhibit.DateTime.HOUR.
* @param {Number} timeZone The timezone offset in hours.
*/
Exhibit.DateTime.incrementByInterval = function(date, intervalUnit, timeZone) {
timeZone = (typeof timeZone === 'undefined') ? 0 : timeZone;
var timeShift, date2;
timeShift = timeZone *
Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.HOUR];
date2 = new Date(date.getTime() + timeShift);
switch(intervalUnit) {
case Exhibit.DateTime.MILLISECOND:
date2.setTime(date2.getTime() + 1);
break;
case Exhibit.DateTime.SECOND:
date2.setTime(date2.getTime() + 1000);
break;
case Exhibit.DateTime.MINUTE:
date2.setTime(date2.getTime() +
Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.MINUTE]);
break;
case Exhibit.DateTime.HOUR:
date2.setTime(date2.getTime() +
Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.HOUR]);
break;
case Exhibit.DateTime.DAY:
date2.setUTCDate(date2.getUTCDate() + 1);
break;
case Exhibit.DateTime.WEEK:
date2.setUTCDate(date2.getUTCDate() + 7);
break;
case Exhibit.DateTime.MONTH:
date2.setUTCMonth(date2.getUTCMonth() + 1);
break;
case Exhibit.DateTime.YEAR:
date2.setUTCFullYear(date2.getUTCFullYear() + 1);
break;
case Exhibit.DateTime.DECADE:
date2.setUTCFullYear(date2.getUTCFullYear() + 10);
break;
case Exhibit.DateTime.CENTURY:
date2.setUTCFullYear(date2.getUTCFullYear() + 100);
break;
case Exhibit.DateTime.MILLENNIUM:
date2.setUTCFullYear(date2.getUTCFullYear() + 1000);
break;
}
date.setTime(date2.getTime() - timeShift);
};
/**
* Returns a new date object with the given time offset removed.
*
* @param {Date} date The starting date.
* @param {Number} timeZone A timezone specified in an hour offset to remove.
* @returns {Date} A new date object with the offset removed.
*/
Exhibit.DateTime.removeTimeZoneOffset = function(date, timeZone) {
return new Date(date.getTime() +
timeZone * Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime.HOUR]);
};
/**
* Returns the timezone of the user's browser. This is expressed as the
* number of hours one would have to add to the local time to equal GMT,
* NOT the numbers one would have to add to GMT to equal local time, as
* timezones are normally expressed.
*
* @returns {Number} The timezone in the user's locale in hours.
*/
Exhibit.DateTime.getTimezone = function() {
var d = new Date().getTimezoneOffset();
return d / -60;
};
/**
* Zeroes (UTC) all time components of the provided date object.
*
* @static
* @param {Date} date The Date object to modify.
* @returns {Date} The modified Date object.
*/
Exhibit.DateTime.zeroTimeUTC = function(date) {
date.setUTCHours(0);
date.setUTCMinutes(0);
date.setUTCSeconds(0);
date.setUTCMilliseconds(0);
return date;
};
/**
* @fileOverview Error reporting. This file is not localized in order to
* keep errors in that system from interfering with this one.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Debug = {
silent: false
};
/**
* @static
* @param {String} msg
*/
Exhibit.Debug.log = function(msg) {
var f;
if (typeof window["console"] !== "undefined" &&
typeof window.console["log"] === "function") {
f = function(msg2) {
console.log(msg2);
};
} else {
f = function(msg2) {
if (!Exhibit.Debug.silent) {
alert(msg2);
}
};
}
Exhibit.Debug.log = f;
f(msg);
};
/**
* @static
* @pararm {String} msg
*/
Exhibit.Debug.warn = function(msg) {
var f;
if (typeof window["console"] !== "undefined" &&
typeof window.console["warn"] === "function") {
f = function(msg2) {
console.warn(msg2);
};
} else {
f = function(msg2) {
if (!Exhibit.Debug.silent) {
alert(msg2);
}
};
}
Exhibit.Debug.warn = f;
f(msg);
};
/**
* @static
* @param {Exception} e
* @param {String} msg
*/
Exhibit.Debug.exception = function(e, msg) {
var f, params = Exhibit.parseURLParameters();
if (params.errors === "throw" || Exhibit.params.errors === "throw") {
f = function(e2, msg2) {
throw(e2);
};
} else if (typeof window["console"] !== "undefined" &&
typeof window.console["error"] !== "undefined") {
f = function(e2, msg2) {
if (typeof msg2 !== "undefined" && msg2 !== null) {
console.error(msg2 + " %o", e2);
} else {
console.error(e2);
}
throw(e2); // do not hide from browser's native debugging features
};
} else {
f = function(e2, msg2) {
if (!Exhibit.Debug.silent) {
alert("Caught exception: " + msg2 + "\n\nDetails: " + (typeof e2["description"] !== "undefined" ? e2.description : e2));
}
throw(e2); // do not hide from browser's native debugging features
};
}
Exhibit.Debug.exception = f;
f(e, msg);
};
/**
* @static
* @param {Object} o
* @returns {String}
*/
Exhibit.Debug.objectToString = function(o) {
return Exhibit.Debut._objectToString(o, "");
};
/**
* @static
* @param {Object} o
* @param {String} indent
* @returns {String}
*/
Exhibit.Debug._objectToString = function(o, indent) {
var indent2 = indent + " ", s, n;
if (typeof o === "object") {
s = "{";
for (n in o) {
if (o.hasOwnProperty(n)) {
s += indent2 + n + ": " + Exhibit.Debug._objectToString(o[n], indent2) + "\n";
}
}
s += indent + "}";
return s;
} else if (typeof o === "array") {
s = "[";
for (n = 0; n < o.length; n++) {
s += Exhibit.Debug._objectToString(o[n], indent2) + "\n";
}
s += indent + "]";
return s;
} else if (typeof o === "function") {
return indent + "{function}\n";
} else {
return o;
}
};
/**
* @fileOverview Facet building and interaction utilities.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.FacetUtilities = {};
/**
* @static
* @param {Exhibit.Facet} forFacet
* @param {Element} div
* @param {String} facetLabel
* @param {Function} onClearAllSelections
* @param {Exhibit.UIContext} uiContext
* @param {Boolean} collapsible
* @param {Boolean} collapsed
* @returns {Object}
*/
Exhibit.FacetUtilities.constructFacetFrame = function(forFacet, div, facetLabel, onClearAllSelections, uiContext, collapsible, collapsed) {
var dom, resizableDivWidget;
Exhibit.jQuery(div).attr("class", "exhibit-facet");
dom = Exhibit.jQuery.simileDOM("string", div,
'
" +
'' +
'',
{}
);
if (showSummary) {
dom.collectionSummaryWidget = Exhibit.CollectionSummaryWidget.create(
{},
dom.collectionSummaryDiv,
uiContext
);
}
dom.resizableDivWidget = Exhibit.ResizableDivWidget.create(
resizableDivWidgetSettings,
dom.resizableDiv,
uiContext
);
dom.plotContainer = dom.resizableDivWidget.getContentDiv();
dom.legendWidget = Exhibit.LegendWidget.create(
legendWidgetSettings,
dom.legendDiv,
uiContext
);
if (legendWidgetSettings.colorGradient === true) {
dom.legendGradientWidget = Exhibit.LegendGradientWidget.create(
dom.legendDiv,
uiContext
);
}
dom.setUnplottableMessage = function(totalCount, unplottableItems) {
Exhibit.ViewUtilities._setUnplottableMessage(dom, totalCount, unplottableItems, uiContext);
};
dom.dispose = function() {
if (showSummary) {
dom.collectionSummaryWidget.dispose();
}
dom.resizableDivWidget.dispose();
dom.legendWidget.dispose();
};
return dom;
};
/**
* @static
* @param {Object} dom
* @param {Number} totalCount
* @param {Array} unplottableItems
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.ViewUtilities._setUnplottableMessage = function(dom, totalCount, unplottableItems, uiContext) {
var div;
div = dom.unplottableMessageDiv;
if (unplottableItems.length === 0) {
Exhibit.jQuery(div).hide();
} else {
Exhibit.jQuery(div).empty();
dom = Exhibit.jQuery.simileDOM("string",
div,
Exhibit.ViewUtilities.unplottableMessageFormatter(totalCount, unplottableItems),
{}
);
Exhibit.jQuery(dom.unplottableCountLink).bind("click", function(evt) {
Exhibit.ViewUtilities.openBubbleForItems(evt.target, unplottableItems, uiContext);
});
Exhibit.jQuery(div).show();
}
};
/**
* @param {Number} totalCount
* @param {Array} unplottableItems
*/
Exhibit.ViewUtilities.unplottableMessageFormatter = function(totalCount, unplottableItems) {
var count = unplottableItems.length;
return Exhibit._("%views.unplottableTemplate", count, Exhibit._(count === 1 ? "%views.resultLabel" : "%views.resultsLabel"), totalCount);
};
/**
* Return labels for sort ordering based on value type; "text" is the base
* case that is assumed to always exist in the localization utiltiies.
* @param {String} valueType
* @returns {Object} An object of the form { "ascending": label,
* "descending": label}
*/
Exhibit.ViewUtilities.getSortLabels = function(valueType) {
var asc, desc, labels, makeKey;
makeKey = function(v, dir) {
return "%database.sortLabels." + v + "." + dir;
};
asc = Exhibit._(makeKey(valueType, "ascending"));
if (typeof asc !== "undefined" && asc !== null) {
labels = {
"ascending": asc,
"descending": Exhibit._(makeKey(valueType, "descending"))
};
} else {
labels = Exhibit.ViewUtilities.getSortLabels("text");
}
return labels;
};
/**
* @param {Number} index
* @returns {String}
*/
Exhibit.ViewUtilities.makePagingActionTitle = function(index) {
return Exhibit._("%orderedViewFrame.pagingActionTitle", index + 1);
};
/**
* @param {Number} index
* @returns {String}
*/
Exhibit.ViewUtilities.makePagingLinkTooltip = function(index) {
return Exhibit._("%orderedViewFrame.pagingLinkTooltip", index + 1);
};
/**
* @fileOverview Database interface and local implementation, with helper
* classes.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace Database layer of Exhibit.
*/
Exhibit.Database = {
defaultIgnoredProperties: [ "uri", "modified" ]
};
/**
* Instantiate an Exhibit database object.
*
* @static
* @returns {Object}
*/
Exhibit.Database.create = function(type) {
if (typeof Exhibit.Database[type] !== "undefined") {
return new Exhibit.Database[type]();
} else {
// warn?
return new Exhibit.Database._LocalImpl();
}
};
/**
* Add or initialize an array entry in a two-level hash, such that
* index[x][y].push(z), given z isn't already in index[x][y].
*
* @static
* @private
* @param {Object} index Base hash; may be modified as a side-effect.
* @param {String} x First tier entry key in the base hash.
* @param {String} y Second tier entry key in a subhash of the base hash.
* @param {String} z Value to put into an array in the subhash key.
*/
Exhibit.Database._indexPut = function(index, x, y, z) {
var hash, array, i;
hash = index[x];
if (typeof hash === "undefined") {
hash = {};
index[x] = hash;
}
array = hash[y];
if (typeof array === "undefined") {
array = [];
hash[y] = array;
} else {
for (i = 0; i < array.length; i++) {
if (z === array[i]) {
return;
}
}
}
array.push(z);
};
/**
* Add or initialize an array entry in a two-level hash, such that
* index[x][y] = list if undefined or index[x][y].concat(list) if already
* defined.
*
* @static
* @private
* @param {Object} index Base hash; may be modified as a side-effect.
* @param {String} x First tier entry key in the base hash.
* @param {String} y Second tier entry key in a subhash of the base hash.
* @param {Array} list List of values to add or assign to the subhash key.
*/
Exhibit.Database._indexPutList = function(index, x, y, list) {
var hash, array;
hash = index[x];
if (typeof hash === "undefined") {
hash = {};
index[x] = hash;
}
array = hash[y];
if (typeof array === "undefined") {
hash[y] = list;
} else {
hash[y] = hash[y].concat(list);
}
};
/**
* Remove the element z from the array index[x][y]; also remove
* index[x][y] if the array becomes empty and index[x] if the hash becomes
* empty as a result.
*
* @static
* @private
* @param {Object} index Base hash; may be modified as a side-effect.
* @param {String} x First tier entry key in the base hash.
* @param {String} y Second tier entry key in a subhash of the base hash.
* @param {String} z Value to remove from an array in the subhash key.
* @returns {Boolean} True if value removed, false if not.
*/
Exhibit.Database._indexRemove = function(index, x, y, z) {
var hash, array, i, prop, empty;
hash = index[x];
if (typeof hash === "undefined") {
return false;
}
array = hash[y];
if (typeof array === "undefined") {
return false;
}
for (i = 0; i < array.length; i++) {
if (z === array[i]) {
array.splice(i, 1);
if (array.length === 0) {
delete hash[y];
empty = true;
for (prop in hash) {
if (hash.hasOwnProperty(prop)) {
empty = false;
break;
}
}
if (empty) {
delete index[x];
}
}
return true;
}
}
};
/**
* Removes index[x][y] and index[x] if it becomes empty.
*
* @static
* @private
* @param {Object} index Base hash; may be modified as a side-effect.
* @param {String} x First tier entry key in the base hash.
* @param {String} y Second tier entry key in a subhash of the base hash.
* @returns {Array} The removed array, or null if nothing was removed.
*/
Exhibit.Database._indexRemoveList = function(index, x, y) {
var hash, array, prop, empty;
hash = index[x];
if (typeof hash === "undefined") {
return null;
}
array = hash[y];
if (typeof array === "undefined") {
return null;
}
delete hash[y];
empty = true;
for (prop in hash) {
if (hash.hasOwnProperty(prop)) {
empty = false;
break;
}
}
if (empty) {
delete index[x];
}
return array;
};
/**
* Local in-memory implementation of the Exhibit Database. Other
* implementations should fully implement the interface described by
* this class.
*
* @public
* @constructor
* @class
*/
Exhibit.Database._LocalImpl = function() {
this._types = {};
this._properties = {};
this._propertyArray = {};
this._spo = {};
this._ops = {};
this._items = new Exhibit.Set();
/*
* Predefined types and properties
*/
var itemType, labelProperty, typeProperty, uriProperty;
itemType = new Exhibit.Database.Type("Item");
itemType._custom = {
"label": Exhibit._("%database.itemType.label"),
"pluralLabel": Exhibit._("%database.itemType.pluralLabel"),
"uri": Exhibit.namespace + "Item"
};
this._types.Item = itemType;
labelProperty = new Exhibit.Database.Property("label", this);
labelProperty._uri = "http://www.w3.org/2000/01/rdf-schema#label";
labelProperty._valueType = "text";
labelProperty._label = Exhibit._("%database.labelProperty.label");
labelProperty._pluralLabel = Exhibit._("%database.labelProperty.pluralLabel");
labelProperty._reverseLabel = Exhibit._("%database.labelProperty.reverseLabel");
labelProperty._reversePluralLabel = Exhibit._("%database.labelProperty.reversePluralLabel");
labelProperty._groupingLabel = Exhibit._("%database.labelProperty.groupingLabel");
labelProperty._reverseGroupingLabel = Exhibit._("%database.labelProperty.reverseGroupingLabel");
this._properties.label = labelProperty;
typeProperty = new Exhibit.Database.Property("type", this);
typeProperty._uri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
typeProperty._valueType = "text";
typeProperty._label = Exhibit._("%database.typeProperty.label");
typeProperty._pluralLabel = Exhibit._("%database.typeProperty.pluralLabel");
typeProperty._reverseLabel = Exhibit._("%database.typeProperty.reverseLabel");
typeProperty._reversePluralLabel = Exhibit._("%database.typeProperty.reversePluralLabel");
typeProperty._groupingLabel = Exhibit._("%database.typeProperty.groupingLabel");
typeProperty._reverseGroupingLabel = Exhibit._("%database.typeProperty.reverseGroupingLabel");
this._properties.type = typeProperty;
uriProperty = new Exhibit.Database.Property("uri", this);
uriProperty._uri = "http://simile.mit.edu/2006/11/exhibit#uri";
uriProperty._valueType = "url";
uriProperty._label = Exhibit._("%database.uriProperty.label");
uriProperty._pluralLabel = Exhibit._("%database.uriProperty.pluralLabel");
uriProperty._reverseLabel = Exhibit._("%database.uriProperty.reverseLabel");
uriProperty._reversePluralLabel = Exhibit._("%database.uriProperty.reversePluralLabel");
uriProperty._groupingLabel = Exhibit._("%database.uriProperty.groupingLabel");
uriProperty._reverseGroupingLabel = Exhibit._("%database.uriProperty.reverseGroupingLabel");
this._properties.uri = uriProperty;
};
/**
* Creates a new database.
*
* @returns {Exhibit.Database} The new database.
*/
Exhibit.Database._LocalImpl.prototype.createDatabase = function() {
return Exhibit.Database.create();
};
/**
* Load an array of data links using registered importers into the database.
*/
Exhibit.Database._LocalImpl.prototype.loadLinks = function() {
var links = Exhibit.jQuery("head > link[rel='exhibit-data']")
.add("head > link[rel='exhibit/data']");
this._loadLinks(links.toArray(), this);
};
/**
* Load data from the given object into the database.
*
* @param {Object} o An object that reflects the Exhibit JSON form.
* @param {String} baseURI The base URI for normalizing URIs in the object.
*/
Exhibit.Database._LocalImpl.prototype.loadData = function(o, baseURI) {
if (typeof o === "undefined" || o === null) {
throw Error(Exhibit._("%database.error.unloadable"));
}
if (typeof baseURI === "undefined") {
baseURI = location.href;
}
if (typeof o.types !== "undefined") {
this.loadTypes(o.types, baseURI);
}
if (typeof o.properties !== "undefined") {
this.loadProperties(o.properties, baseURI);
}
if (typeof o.items !== "undefined") {
this.loadItems(o.items, baseURI);
}
};
/**
* Load just the types from a data object.
*
* @param {Object} typeEntries The "types" subsection of Exhibit JSON.
* @param {String} baseURI The base URI for normalizing URIs in the object.
*/
Exhibit.Database._LocalImpl.prototype.loadTypes = function(typeEntries, baseURI) {
Exhibit.jQuery(document).trigger('onBeforeLoadingTypes.exhibit');
var lastChar, typeID, typeEntry, type, p;
try {
lastChar = baseURI.substr(baseURI.length - 1);
if (lastChar === "#") {
baseURI = baseURI.substr(0, baseURI.length - 1) + "/";
} else if (lastChar !== "/" && lastChar !== ":") {
baseURI += "/";
}
for (typeID in typeEntries) {
if (typeEntries.hasOwnProperty(typeID)) {
if (typeof typeID === "string") {
typeEntry = typeEntries[typeID];
if (typeof typeEntry === "object") {
if (typeof this._types[typeID] !== "undefined") {
type = this._types[typeID];
} else {
type = new Exhibit.Database.Type(typeID);
this._types[typeID] = type;
}
for (p in typeEntry) {
if (typeEntry.hasOwnProperty(p)) {
type._custom[p] = typeEntry[p];
}
}
if (typeof type._custom.uri === "undefined") {
type._custom.uri = baseURI + "type#" + encodeURIComponent(typeID);
}
if (typeof type._custom.label === "undefined") {
type._custom.label = typeID;
}
}
}
}
}
Exhibit.jQuery(document).trigger('onAfterLoadingTypes.exhibit');
} catch(e) {
Exhibit.Debug.exception(e, Exhibit._("%database.error.loadTypesFailure"));
}
};
/**
* Load just the properties from the data. The valid valueType values can be:
* text, html, number, date, boolean, item, url.
*
* @param {Object} propertyEntries The "properties" subsection of Exhibit JSON.
* @param {String} baseURI The base URI for normalizing URIs in the object.
*/
Exhibit.Database._LocalImpl.prototype.loadProperties = function(propertyEntries, baseURI) {
Exhibit.jQuery(document).trigger("onBeforeLoadingProperties.exhibit");
var lastChar, propertyID, propertyEntry, property;
try {
lastChar = baseURI.substr(baseURI.length - 1);
if (lastChar === "#") {
baseURI = baseURI.substr(0, baseURI.length - 1) + "/";
} else if (lastChar !== "/" && lastChar !== ":") {
baseURI += "/";
}
for (propertyID in propertyEntries) {
if (propertyEntries.hasOwnProperty(propertyID)) {
if (typeof propertyID === "string") {
propertyEntry = propertyEntries[propertyID];
if (typeof propertyEntry === "object") {
if (typeof this._properties[propertyID] !== "undefined") {
property = this._properties[propertyID];
} else {
property = new Exhibit.Database.Property(propertyID, this);
this._properties[propertyID] = property;
}
property._uri = typeof propertyEntry.uri !== "undefined" ?
propertyEntry.uri :
(baseURI + "property#" + encodeURIComponent(propertyID));
property._valueType = typeof propertyEntry.valueType !== "undefined" ?
propertyEntry.valueType :
"text";
property._label = typeof propertyEntry.label !== "undefined" ?
propertyEntry.label :
propertyID;
property._pluralLabel = typeof propertyEntry.pluralLabel !== "undefined" ?
propertyEntry.pluralLabel :
property._label;
property._reverseLabel = typeof propertyEntry.reverseLabel !== "undefined" ?
propertyEntry.reverseLabel :
("!" + property._label);
property._reversePluralLabel = typeof propertyEntry.reversePluralLabel !== "undefined" ?
propertyEntry.reversePluralLabel :
("!" + property._pluralLabel);
property._groupingLabel = typeof propertyEntry.groupingLabel !== "undefined" ?
propertyEntry.groupingLabel :
property._label;
property._reverseGroupingLabel = typeof propertyEntry.reverseGroupingLabel !== "undefined" ?
propertyEntry.reverseGroupingLabel :
property._reverseLabel;
if (typeof propertyEntry.origin !== "undefined") {
property._origin = propertyEntry.origin;
}
}
}
}
}
this._propertyArray = null;
Exhibit.jQuery(document).trigger("onAfterLoadingProperties.exhibit");
} catch(e) {
Exhibit.Debug.exception(e, Exhibit._("%database.error.loadPropertiesFailure"));
}
};
/**
* Prevent browsers from spinning forever while loading data.
*
* @static
* @private
* @param {Function} worker Takes one argument, an item to work on
* @param {Array} data An array of items fit to pass to the worker function
* @param {Numeric} size Chunk size, the number of items to work on in one
* cycle
* @param {Numeric} timeout In milliseconds, the time between cycles
* @param {Function} [complete] Method to call when done with all data
*/
Exhibit.Database._LocalImpl._loadChunked = function(worker, data, size, timeout, complete) {
var index, length;
index = 0;
length = data.length;
(function() {
var remnant, currentSize;
remnant = length - index;
currentSize = (remnant >= size) ? size : remnant;
if (index < length) {
while (currentSize-- > 0) {
worker(data[index++]);
}
setTimeout(arguments.callee, timeout);
} else if (typeof complete === "function") {
complete();
}
}());
};
/**
* Load just the items from the data.
*
* @param {Object} itemEntries The "items" subsection of Exhibit JSON.
* @param {String} baseURI The base URI for normalizing URIs in the object.
*/
Exhibit.Database._LocalImpl.prototype.loadItems = function(itemEntries, baseURI) {
Exhibit.jQuery(document).trigger("onBeforeLoadingItems.exhibit");
var self, lastChar, spo, ops, indexPut, indexTriple, finish, loader;
self = this;
try {
lastChar = baseURI.substr(baseURI.length - 1);
if (lastChar === "#") {
baseURI = baseURI.substr(0, baseURI.length - 1) + "/";
} else if (lastChar !== "/" && lastChar !== ":") {
baseURI += "/";
}
spo = this._spo;
ops = this._ops;
indexPut = Exhibit.Database._indexPut;
indexTriple = function(s, p, o) {
indexPut(spo, s, p, o);
indexPut(ops, o, p, s);
};
finish = function() {
self._propertyArray = null;
Exhibit.jQuery(document).trigger("onAfterLoadingItems.exhibit");
};
loader = function(item) {
if (typeof item === "object") {
self._loadItem(item, indexTriple, baseURI);
}
};
Exhibit.Database._LocalImpl._loadChunked(loader, itemEntries, 1000, 10, finish);
} catch(e) {
Exhibit.Debug.exception(e, Exhibit._("%database.error.loadItemsFailure"));
}
};
/**
* Retrieve a database type given the type identifier, or null if the
* type does not exist.
*
* @param {String} typeID The type identifier.
* @returns {Exhibit.Database.Type} The corresponding database type.
*/
Exhibit.Database._LocalImpl.prototype.getType = function(typeID) {
return typeof this._types[typeID] ?
this._types[typeID] :
null;
};
/**
* Retrieve a database property given an identifier, or null if no such
* property exists.
*
* @param {String} propertyID The property identifier.
* @returns {Exhibit.Database._Property} The corresponding database property.
*/
Exhibit.Database._LocalImpl.prototype.getProperty = function(propertyID) {
return typeof this._properties[propertyID] !== "undefined" ?
this._properties[propertyID] :
null;
};
/**
* Retrieve all database property identifiers in an array.
*
* @returns {Array} The array of property identifiers.
*/
Exhibit.Database._LocalImpl.prototype.getAllProperties = function() {
var propertyID;
if (this._propertyArray === null) {
this._propertyArray = [];
for (propertyID in this._properties) {
if (this._properties.hasOwnProperty(propertyID)) {
this._propertyArray.push(propertyID);
}
}
}
return [].concat(this._propertyArray);
};
/**
* Retrieve all items in the database.
*
* @returns {Exhibit.Set} The set of all items.
*/
Exhibit.Database._LocalImpl.prototype.getAllItems = function() {
var items = new Exhibit.Set();
items.addSet(this._items);
return items;
};
/**
* Count the number of items in the database.
*
* @returns {Number} The number of items in the database.
*/
Exhibit.Database._LocalImpl.prototype.getAllItemsCount = function() {
return this._items.size();
};
/**
* Returns true if an item identifier exists in the database, false otherwise.
*
* @param {String} itemID The item identifier.
* @returns {Boolean} True if the item ID is in the database.
*/
Exhibit.Database._LocalImpl.prototype.containsItem = function(itemID) {
return this._items.contains(itemID);
};
/**
* Work through URIs of database properties and algorithmically extract
* the best guess at all URI namespaces used in the data. Modifies its
* arguments, does not return anything. idToQualifiedName is a hash for
* helping translate a full URI into a QName (prefix:localName), where
* idToQualifiedName[ID] = { base: baseURI, localName: localName, prefix:
* baseNickname }. prefixToBase maps the base nickname back to the prefix,
* where prefixToBase[prefix] = baseURI.
*
* @param {Object} idToQualifiedName Maps URIs to QNames.
* @param {Object} prefixToBase Maps prefixes to full base URIs.
*/
Exhibit.Database._LocalImpl.prototype.getNamespaces = function(idToQualifiedName, prefixToBase) {
var bases = {}, propertyID, property, uri, hash, base, slash,
baseToPrefix, letters, i, prefix, qname;
for (propertyID in this._properties) {
if (this._properties.hasOwnProperty(propertyID)) {
property = this._properties[propertyID];
uri = property.getURI();
hash = uri.indexOf("#");
slash = uri.lastIndexOf("/");
if (hash > 0) {
base = uri.substr(0, hash + 1);
bases[base] = true;
idToQualifiedName[propertyID] = {
base: base,
localName: uri.substr(hash + 1)
};
} else if (slash > 0) {
base = uri.substr(0, slash + 1);
bases[base] = true;
idToQualifiedName[propertyID] = {
base: base,
localName: uri.substr(slash + 1)
};
}
}
}
baseToPrefix = {};
letters = "abcdefghijklmnopqrstuvwxyz";
i = 0;
for (base in bases) {
if (bases.hasOwnProperty(base)) {
prefix = letters.substr(i++,1);
prefixToBase[prefix] = base;
baseToPrefix[base] = prefix;
}
}
for (propertyID in idToQualifiedName) {
if (idToQualifiedName.hasOwnProperty(propertyID)) {
qname = idToQualifiedName[propertyID];
qname.prefix = baseToPrefix[qname.base];
}
}
};
/**
* Fill a set with all objects for a given subject-predicate pair.
*
* @param {String} s The subject identifier.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include objects in this set.
* @returns {Exhibit.Set} The filled set of objects.
*/
Exhibit.Database._LocalImpl.prototype.getObjects = function(s, p, set, filter) {
return this._get(this._spo, s, p, set, filter);
};
/**
* Count the distinct, unique objects (any repeated objects count as one)
* for a subject-predicate pair.
*
* @param {String} s The subject identifier.
* @param {String} p The prediate identifier.
* @param {Exhibit.Set} [filter] Only include objects in this filter.
* @returns {Number} The count of distinct objects.
*/
Exhibit.Database._LocalImpl.prototype.countDistinctObjects = function(s, p, filter) {
return this._countDistinct(this._spo, s, p, filter);
};
/**
* Fill a set with all objects for all subject-predicate pairs from a set
* of subjects.
*
* @param {Exhibit.Set} subjects A set of subject identifiers.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include objects in this filter.
* @returns {Exhibit.Set} The filled set of objects.
*/
Exhibit.Database._LocalImpl.prototype.getObjectsUnion = function(subjects, p, set, filter) {
return this._getUnion(this._spo, subjects, p, set, filter);
};
/**
* Count the distinct, unique objects for subject-predicate pairs for all
* subjects in a set. Objects that repeat across subject-predicate pairs
* are counted for each appearance.
*
* @param {Exhibit.Set} subjects A set of subject identifiers.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [filter] Only include objects in this filter.
* @returns {Number} The count of distinct matching objects.
*/
Exhibit.Database._LocalImpl.prototype.countDistinctObjectsUnion = function(subjects, p, filter) {
return this._countDistinctUnion(this._spo, subjects, p, filter);
};
/**
* Fill a set with all the subjects with the matching object-predicate pair.
*
* @param {String} o The object.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include subjects in this filter.
* @returns {Exhibit.Set} The filled set of matching subject identifiers.
*/
Exhibit.Database._LocalImpl.prototype.getSubjects = function(o, p, set, filter) {
return this._get(this._ops, o, p, set, filter);
};
/**
* Count the distinct, unique subjects (any repeated subjects count as one)
* for an object-predicate pair.
*
* @param {String} o The object.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [filter] Only include subjects in this filter.
* @returns {Number} The count of matching, distinct subjects.
*/
Exhibit.Database._LocalImpl.prototype.countDistinctSubjects = function(o, p, filter) {
return this._countDistinct(this._ops, o, p, filter);
};
/**
* Fill a set with all subjects for all object-predicate pairs from a set
* of objects.
*
* @param {Exhibit.Set} objects The set of objects.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include subjects in this filter.
* @returns {Exhibit.Set} The filled set of subjects.
*/
Exhibit.Database._LocalImpl.prototype.getSubjectsUnion = function(objects, p, set, filter) {
return this._getUnion(this._ops, objects, p, set, filter);
};
/**
* Count the distinct, unique subjects for object-predicate pairs for all
* objects in a set.
*
* @param {Exhibit.Set} objects The set of objects.
* @param {String} p The predicate identifier.
* @param {Exhibit.Set} [filter] Only include subjects in this filter.
* @returns {Number} The count of matching subjects.
*/
Exhibit.Database._LocalImpl.prototype.countDistinctSubjectsUnion = function(objects, p, filter) {
return this._countDistinctUnion(this._ops, objects, p, filter);
};
/**
* Return one (and only one) object given a subject-predicate pair, or null
* if no such object exists.
*
* @param {String} s The subject identifier.
* @param {String} p The predicate identifier.
* @returns {String} One matching object.
*/
Exhibit.Database._LocalImpl.prototype.getObject = function(s, p) {
var hash, array;
hash = this._spo[s];
if (hash) {
array = hash[p];
if (array) {
return array[0];
}
}
return null;
};
/**
* Return one (and only one) subject from an object-predicate pair, or null
* if no such subject exists.
*
* @param {String} o The object.
* @param {String} p The predicate identifier.
* @returns {String} One matching subject identifier.
*/
Exhibit.Database._LocalImpl.prototype.getSubject = function(o, p) {
var hash, array;
hash = this._ops[o];
if (hash) {
array = hash[p];
if (array) {
return array[0];
}
}
return null;
};
/**
* Return an array of predicates from triples with the given subject.
*
* @param {String} s The subject identifier.
* @returns {Array} The predicate identifiers.
*/
Exhibit.Database._LocalImpl.prototype.getForwardProperties = function(s) {
return this._getProperties(this._spo, s);
};
/**
* Return an array of predicates from triples for the given object.
*
* @param {String} o The object identifier.
* @returns {Array} The predicate identifiers.
*/
Exhibit.Database._LocalImpl.prototype.getBackwardProperties = function(o) {
return this._getProperties(this._ops, o);
};
/**
* Fill a set with subjects whose property values for the given property
* fall within the min, max range.
*
* @param {String} p The predicate identifier.
* @param {Number} min The minimum value for the range.
* @param {Number} max The maximum value for the range.
* @param {Boolean} inclusive Whether the maximum is a limit or included.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include subjects in this filter.
* @returns {Exhibit.Set} The filled set of matching subject identifiers.
*/
Exhibit.Database._LocalImpl.prototype.getSubjectsInRange = function(p, min, max, inclusive, set, filter) {
var property, rangeIndex;
property = this.getProperty(p);
if (property !== null) {
rangeIndex = property.getRangeIndex();
if (rangeIndex !== null) {
return rangeIndex.getSubjectsInRange(min, max, inclusive, set, filter);
}
}
return (!set) ? new Exhibit.Set() : set;
};
/**
* Fill a set with all objects in the database of property "type" for
* the given set of subjects.
*
* @param {Exhibit.Set} set A set of subject identifiers.
* @returns {Exhibit.Set} A set of type identifiers.
*/
Exhibit.Database._LocalImpl.prototype.getTypeIDs = function(set) {
return this.getObjectsUnion(set, "type", null, null);
};
/**
* Add a triple to the database.
*
* @param {String} s The subject identifier.
* @param {String} p The predicate identifier.
* @param {String} o The object.
*/
Exhibit.Database._LocalImpl.prototype.addStatement = function(s, p, o) {
var indexPut = Exhibit.Database._indexPut;
indexPut(this._spo, s, p, o);
indexPut(this._ops, o, p, s);
};
/**
* Remove a triple from the database, returning either the object or the
* subject that was removed.
*
* @param {String} s The subject identifier.
* @param {String} p The predicate identifier.
* @param {String} o The object.
* @returns {String} Either the object or the subject.
*/
Exhibit.Database._LocalImpl.prototype.removeStatement = function(s, p, o) {
var indexRemove, removedObject, removedSubject;
indexRemove = Exhibit.Database._indexRemove;
removedObject = indexRemove(this._spo, s, p, o);
removedSubject = indexRemove(this._ops, o, p, s);
return removedObject || removedSubject;
};
/**
* Remove all objects associated with a subject-predicate pair,
* returning a boolean for success.
*
* @param {String} s The subject identifier.
* @param {String} p The predicate identifier.
* @returns {Boolean} True if removed.
*/
Exhibit.Database._LocalImpl.prototype.removeObjects = function(s, p) {
var indexRemove, indexRemoveList, objects, i;
indexRemove = Exhibit.Database._indexRemove;
indexRemoveList = Exhibit.Database._indexRemoveList;
objects = indexRemoveList(this._spo, s, p);
if (objects === null) {
return false;
} else {
for (i = 0; i < objects.length; i++) {
indexRemove(this._ops, objects[i], p, s);
}
return true;
}
};
/**
* Remove all subjects associated with an object-predicate pair,
* returning a boolean for success.
*
* @param {String} o The object.
* @param {String} p The predicate identifier.
* @returns {Boolean} True if removed.
*/
Exhibit.Database._LocalImpl.prototype.removeSubjects = function(o, p) {
var indexRemove, indexRemoveList, subjects, i;
indexRemove = Exhibit.Database._indexRemove;
indexRemoveList = Exhibit.Database._indexRemoveList;
subjects = indexRemoveList(this._ops, o, p);
if (subjects === null) {
return false;
} else {
for (i = 0; i < subjects.length; i++) {
indexRemove(this._spo, subjects[i], p, o);
}
return true;
}
};
/**
* Reset the entire database to its empty state.
*/
Exhibit.Database._LocalImpl.prototype.removeAllStatements = function() {
Exhibit.jQuery(document).trigger("onBeforeRemovingAllStatements.exhibit");
var propertyID;
try {
this._spo = {};
this._ops = {};
this._items = new Exhibit.Set();
for (propertyID in this._properties) {
if (this._properties.hasOwnProperty(propertyID)) {
this._properties[propertyID]._onNewData();
}
}
this._propertyArray = null;
Exhibit.jQuery(document).trigger("onAfterRemovingAllStatements.exhibit");
} catch(e) {
Exhibit.Debug.exception(e, Exhibit._("%database.error.removeAllStatementsFailure"));
}
};
/**
* Use registered importers to load a link based on its stated MIME type.
*
* @param {Array} links An array of DOM link elements.
* @param {Exhibit.Database} database The database to load into.
*/
Exhibit.Database._LocalImpl.prototype._loadLinks = function(links, database) {
var fNext, link, type, importer;
links = [].concat(links);
fNext = function() {
while (links.length > 0) {
link = links.shift();
type = Exhibit.jQuery(link).attr("type");
if (typeof type === "undefined" || type === null || type.length === 0) {
type = "application/json";
}
importer = Exhibit.Importer.getImporter(type);
if (typeof importer !== "undefined" && importer !== null) {
importer.load(link, database, fNext);
return;
} else {
Exhibit.Debug.log(Exhibit._("%database.error.noImporterFailure", type));
}
}
Exhibit.jQuery(document.body).trigger("dataload.exhibit");
};
fNext();
};
/**
* Called by data loading methods for each item being loaded. Checks
* viability and indexes when adding the item's triples to the database.
*
* @param {Object} itemEntry An object representing the item and its triples.
* @param {Function} indexFunction A function that indexes new triples.
* @param {String} baseURI The base URI to resolve URI fragments against.
*/
Exhibit.Database._LocalImpl.prototype._loadItem = function(itemEntry, indexFunction, baseURI) {
var id, label, uri, type, isArray, p, v, j;
if (typeof itemEntry.label === "undefined" &&
typeof itemEntry.id === "undefined") {
Exhibit.Debug.warn(Exhibit._("%database.error.itemSyntaxError",
JSON.stringify(itemEntry)));
itemEntry.label = "item" + Math.ceil(Math.random()*1000000);
}
if (typeof itemEntry.label === "undefined") {
id = itemEntry.id;
if (!this._items.contains(id)) {
Exhibit.Debug.warn(
Exhibit._("%database.error.itemMissingLabelFailure",
JSON.stringify(itemEntry))
);
}
} else {
label = itemEntry.label;
id = typeof itemEntry.id !== "undefined" ?
itemEntry.id :
label;
uri = typeof itemEntry.uri !== "undefined" ?
itemEntry.uri :
(baseURI + "item#" + encodeURIComponent(id));
type = typeof itemEntry.type !== "undefined" ?
itemEntry.type :
"Item";
isArray = function(obj) {
if (obj.constructor.toString().indexOf("Array") === -1) {
return false;
} else {
return true;
}
};
if (isArray(label)) {
label = label[0];
}
if (isArray(id)) {
id = id[0];
}
if (isArray(uri)) {
uri = uri[0];
}
if (isArray(type)) {
type = type[0];
}
this._items.add(id);
indexFunction(id, "uri", uri);
indexFunction(id, "label", label);
indexFunction(id, "type", type);
this._ensureTypeExists(type, baseURI);
}
for (p in itemEntry) {
if (itemEntry.hasOwnProperty(p)) {
if (typeof p === "string") {
if (p !== "uri" && p !== "label" && p !== "id" && p !== "type") {
this._ensurePropertyExists(p, baseURI)._onNewData();
v = itemEntry[p];
if (v instanceof Array) {
for (j = 0; j < v.length; j++) {
indexFunction(id, p, v[j]);
}
} else if (v !== undefined && v !== null) {
indexFunction(id, p, v);
}
}
}
}
}
};
/**
* Called during data load to make sure the schema for any types being added
* exists, adding it if not.
*
* @param {String} typeID The type identifier.
* @param {String} baseURI The base URI to resolve URI fragments against.
*/
Exhibit.Database._LocalImpl.prototype._ensureTypeExists = function(typeID, baseURI) {
var type;
if (typeof this._types[typeID] === "undefined") {
type = new Exhibit.Database.Type(typeID);
type._custom.uri = baseURI + "type#" + encodeURIComponent(typeID);
type._custom.label = typeID;
this._types[typeID] = type;
}
};
/**
* Called during data load to make sure the schema for any property
* being added exists, adding it if not.
*
* @param {String} propertyID The property identifier.
* @param {String} baseURI The base URI to resolve URI fragments against.
* @returns {Exhibit.Database.Property} The corresponding database property.
*/
Exhibit.Database._LocalImpl.prototype._ensurePropertyExists = function(propertyID, baseURI) {
var property;
if (typeof this._properties[propertyID] === "undefined") {
property = new Exhibit.Database.Property(propertyID, this);
property._uri = baseURI + "property#" + encodeURIComponent(propertyID);
property._valueType = "text";
property._label = propertyID;
property._pluralLabel = property._label;
property._reverseLabel = Exhibit._("%database.reverseLabel", property._label);
property._reversePluralLabel = Exhibit._("%database.reversePluralLabel", property._pluralLabel);
property._groupingLabel = property._label;
property._reverseGroupingLabel = property._reverseLabel;
this._properties[propertyID] = property;
this._propertyArray = null;
return property;
} else {
return this._properties[propertyID];
}
};
/**
* Fills a set with any values that are contained in the two-level index,
* index[x][y], only including those in the filter if the filter is provided.
*
* @param {Object} index The two-level index.
* @param {String} x The first level key.
* @param {String} y The second level key.
* @param {Exhibit.Set} set The set to fill.
* @param {Exhibit.Set} [filter] Only include values in this filter.
*/
Exhibit.Database._LocalImpl.prototype._indexFillSet = function(index, x, y, set, filter) {
var hash, array, i, z;
hash = index[x];
if (typeof hash !== "undefined") {
array = hash[y];
if (typeof array !== "undefined") {
if (filter) {
for (i = 0; i < array.length; i++) {
z = array[i];
if (filter.contains(z)) {
set.add(z);
}
}
} else {
for (i = 0; i < array.length; i++) {
set.add(array[i]);
}
}
}
}
};
/**
* Returns a count of the number of objects that would be returned from
* _indexFillSet.
*
* @param {Object} index The two-level index.
* @param {String} x The first-level key.
* @param {String} y The second-level key.
* @param {Exhibit.Set} [filter] Only include values in this filter.
* @returns {Number} The count of values.
*/
Exhibit.Database._LocalImpl.prototype._indexCountDistinct = function(index, x, y, filter) {
var count, hash, array, i;
count = 0;
hash = index[x];
if (hash) {
array = hash[y];
if (array) {
if (filter) {
for (i = 0; i < array.length; i++) {
if (filter.contains(array[i])) {
count++;
}
}
} else {
count = array.length;
}
}
}
return count;
};
/**
* Passes through to _indexFillSet, providing an empty set if no set is
* passed in as an argument.
*
* @param {Object} index The two-level index.
* @param {String} x The first-level key.
* @param {String} y The second-level key.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include values in this filter.
* @returns {Exhibit.Set}
*/
Exhibit.Database._LocalImpl.prototype._get = function(index, x, y, set, filter) {
if (typeof set === "undefined" || set === null) {
set = new Exhibit.Set();
}
this._indexFillSet(index, x, y, set, filter);
return set;
};
/**
* Given a set of items, return values of index[x][y] for all values
* of x in the set.
*
* @param {Object} index The two-level index.
* @param {Exhibit.Set} xSet A set of first-level keys.
* @param {String} y The second-level key.
* @param {Exhibit.Set} [set] The set to fill.
* @param {Exhibit.Set} [filter] Only include values in this filter.
* @returns {Exhibit.Set} The filled set.
*/
Exhibit.Database._LocalImpl.prototype._getUnion = function(index, xSet, y, set, filter) {
var database;
if (typeof set === "undefined" || set === null) {
set = new Exhibit.Set();
}
database = this;
xSet.visit(function(x) {
database._indexFillSet(index, x, y, set, filter);
});
return set;
};
/**
* Counts all distinct values in index[x][y] for all x in a set. Uniqueness
* is for x-y-value triples; common values across triples will still be
* counted.
*
* @param {Object} index The two-level index.
* @param {Exhibit.Set} xSet The set of first-level keys.
* @param {String} y the second-level key.
* @param {Exhibit.Set} [filter] Only include values in this filter.
* @returns {Number} The count of matching values.
*/
Exhibit.Database._LocalImpl.prototype._countDistinctUnion = function(index, xSet, y, filter) {
var count, database;
count = 0;
database = this;
xSet.visit(function(x) {
count += database._indexCountDistinct(index, x, y, filter);
});
return count;
};
/**
* Passed through to _indexCountDistinct.
*
* @param {Object} index The two-level index.
* @param {String} x The first-level key.
* @param {String} y The second-level key.
* @param {Exhibit.Set} [filter] Only include values in this filter.
* @returns {Number} The count of matching values.
*/
Exhibit.Database._LocalImpl.prototype._countDistinct = function(index, x, y, filter) {
return this._indexCountDistinct(index, x, y, filter);
};
/**
* Given an index, return all properties associated with index[x].
*
* @param {Object} index The two-level index.
* @param {String} x The first-level key.
* @returns {Array} An array of second-level keys, property identifiers.
*/
Exhibit.Database._LocalImpl.prototype._getProperties = function(index, x) {
var hash, properties, p;
hash = index[x];
properties = [];
if (typeof hash !== "undefined") {
for (p in hash) {
if (hash.hasOwnProperty(p)) {
properties.push(p);
}
}
}
return properties;
};
/**
* @param {Number} count
* @param {String} typeID
* @param {String} countStyleClass
* @returns {jQuery}
*/
Exhibit.Database._LocalImpl.prototype.labelItemsOfType = function(count, typeID, countStyleClass) {
var label, type, pluralLabel, span;
label = Exhibit._((count === 1) ? "" : "");
type = this.getType(typeID);
if (typeof type !== "undefined" && type !== null) {
label = type.getLabel();
if (count !== 1) {
pluralLabel = type.getProperty("pluralLabel");
if (typeof pluralLabel !== "undefined" && pluralLabel !== null) {
label = pluralLabel
}
}
}
span = Exhibit.jQuery("").append(
Exhibit.jQuery("")
.attr("class", countStyleClass)
.html(count)
).append(" " + label);
return span;
};
/**
* Extension point, a no-op by default. Clones and returns an item and
* the graph immediately surrounding it.
*
* @param {String} id Identifier of database item to clone and return.
*/
Exhibit.Database._LocalImpl.prototype.getItem = function(id) {
};
/**
* Extension point, a no-op by default. Clones the argument and inserts
* it into the database.
*
* @param {Object} item An object representing the item to add.
*/
Exhibit.Database._LocalImpl.prototype.addItem = function(item) {
};
/**
* Extension point, a no-op by default. Edit the object of a statement
* given the subject and property.
*
* @param {String} id The identifier of the subject.
* @param {String} prop The property identifier.
* @param {String} value The new value for the object.
*/
Exhibit.Database._LocalImpl.prototype.editItem = function(id, prop, value) {
};
/**
* Extension point, a no-op by default. Remove the item identified by
* identifier and all links in the graph from and to it.
*
* @param {String} id The identifier of the item to remove.
*/
Exhibit.Database._LocalImpl.prototype.removeItem = function(id) {
};
/**
* @fileOverview Database property definition.
* @author David Huynh
* @author Ryan Lee
*/
/**
* Represents a property within a database.
*
* @public
* @constructor
* @class
* @param {String} id Property identifier.
* @param {Exhibit.Database} database Database the property is grounded in.
*/
Exhibit.Database.Property = function(id, database) {
this._id = id;
this._database = database;
this._rangeIndex = null;
};
/**
* Return the property database identifier.
*
* @returns {String} The property database identifier.
*/
Exhibit.Database.Property.prototype.getID = function() {
return this._id;
};
/**
* Return the property's URI.
*
* @returns {String} The property URI.
*/
Exhibit.Database.Property.prototype.getURI = function() {
return this._uri;
};
/**
* Return the property value data type.
*
* @returns {String} The property value data type.
*/
Exhibit.Database.Property.prototype.getValueType = function() {
return this._valueType;
};
/**
* Return the user-friendly property label.
*
* @returns {String} The property label.
*/
Exhibit.Database.Property.prototype.getLabel = function() {
return this._label;
};
/**
* Return the user-friendly property label for plural values.
*
* @returns {String} The plural property label.
*/
Exhibit.Database.Property.prototype.getPluralLabel = function() {
return this._pluralLabel;
};
/**
* Return the user-friendly label when the object is the subject of a
* sentence (e.g., "is [property] of").
*
* @returns {String} The reverse property label.
*/
Exhibit.Database.Property.prototype.getReverseLabel = function() {
return this._reverseLabel;
};
/**
* Return the user-friendly label when the many objects are the subject of a
* sentence (e.g., "are [properties] of").
*
* @returns {String} The plural reverse property label.
*/
Exhibit.Database.Property.prototype.getReversePluralLabel = function() {
return this._reversePluralLabel;
};
/**
* Return the user-friendly label when grouping values together.
*
* @returns {String} The property grouping label.
*/
Exhibit.Database.Property.prototype.getGroupingLabel = function() {
return this._groupingLabel;
};
/**
* Return the user-friendly label when values grouped together are the
* subject of a sentence.
*
* @returns {String} The property reverse grouping label.
*/
Exhibit.Database.Property.prototype.getReverseGroupingLabel = function() {
return this._reverseGroupingLabel;
};
/**
* Return the origin of the property.
*
* @returns {String} The property origin.
*/
Exhibit.Database.Property.prototype.getOrigin = function() {
return this._origin;
};
/**
* Return the index for the range of property values.
*
* @returns {Exhibit.Database.RangeIndex} An index for the range of
* property values.
*/
Exhibit.Database.Property.prototype.getRangeIndex = function() {
if (this._rangeIndex === null) {
this._buildRangeIndex();
}
return this._rangeIndex;
};
/**
* Makes internal changes to representation if new data is added to
* the database.
*
* @private
*/
Exhibit.Database.Property.prototype._onNewData = function() {
this._rangeIndex = null;
};
/**
* Constructs the cached RangeIndex retrieved in getRangeIndex.
*
* @private
*/
Exhibit.Database.Property.prototype._buildRangeIndex = function() {
var getter, database, p;
database = this._database;
p = this._id;
switch (this.getValueType()) {
case "currency":
case "number":
getter = function(item, f) {
database.getObjects(item, p, null, null).visit(function(value) {
if (typeof value !== "number") {
value = parseFloat(value);
}
if (!isNaN(value)) {
f(value);
}
});
};
break;
case "date":
getter = function(item, f) {
database.getObjects(item, p, null, null).visit(function(value) {
if (typeof value !== "undefined" && value !== null && !(value instanceof Date)) {
value = Exhibit.DateTime.parseIso8601DateTime(value);
}
if (value instanceof Date) {
f(value.getTime());
}
});
};
break;
default:
getter = function(item, f) {};
}
this._rangeIndex = new Exhibit.Database.RangeIndex(
database.getAllItems(),
getter
);
};
/**
* @fileOverview Class for indexing sortable property value ranges.
* @author David Huynh
* @author Ryan Lee
*/
/**
* Builds a Exhibit.Database.RangeIndex object.
*
* @public
* @constructor
* @class
* @param {Exhibit.Set} items Subjects with values to index.
* @param {Function} getter Function to return a value given the item.
*/
Exhibit.Database.RangeIndex = function(items, getter) {
var pairs = [];
items.visit(function(item) {
getter(item, function(value) {
pairs.push({ item: item, value: value });
});
});
pairs.sort(function(p1, p2) {
var c = p1.value - p2.value;
return (isNaN(c) === false) ? c : p1.value.localeCompare(p2.value);
});
this._pairs = pairs;
};
/**
* Returns the number of values in the index.
*
* @returns {Number} The number of values in the index.
*/
Exhibit.Database.RangeIndex.prototype.getCount = function() {
return this._pairs.length;
};
/**
* Returns the smallest numeric value in the index, for numeric ranges.
*
* @returns {Number} The smallest value in the index.
*/
Exhibit.Database.RangeIndex.prototype.getMin = function() {
return this._pairs.length > 0 ?
this._pairs[0].value :
Number.POSITIVE_INFINITY;
};
/**
* Returns the largest numeric value in the index, for numeric ranges.
*
* @returns {Number} The largest value in the index.
*/
Exhibit.Database.RangeIndex.prototype.getMax = function() {
return this._pairs.length > 0 ?
this._pairs[this._pairs.length - 1].value :
Number.NEGATIVE_INFINITY;
};
/**
* Using a visitor function, provide it as an argument every item in
* this index that falls in the provided open or closed range.
*
* @param {Function} visitor Function to handle each item with values in range.
* @param {Number} min Lower bound of range.
* @param {Number} max Upper bound of range.
* @param {Boolean} inclusive Whether max is included in bounds or not.
*/
Exhibit.Database.RangeIndex.prototype.getRange = function(visitor, min, max, inclusive) {
var startIndex, pairs, l, pair, value;
startIndex = this._indexOf(min);
pairs = this._pairs;
l = pairs.length;
inclusive = !!inclusive;
while (startIndex < l) {
pair = pairs[startIndex++];
value = pair.value;
if (value < max || (value === max && inclusive)) {
visitor(pair.item);
} else {
break;
}
}
};
/**
* Build a visitor function to construct a set to hand to getRange,
* optionally with a filter of acceptable subjects.
*
* @param {Number} min Lower bound of range.
* @param {Number} max Upper bound of range.
* @param {Boolean} inclusive Whether max is included in bounds or not.
* @param {Exhibit.Set} [set] Result set
* @param {Exhibit.Set} [filter] Only include items in the filter
* @returns {Exhibit.Set} Filtered items in defined range.
*/
Exhibit.Database.RangeIndex.prototype.getSubjectsInRange = function(min, max, inclusive, set, filter) {
if (typeof set === "undefined" || set === null) {
set = new Exhibit.Set();
}
var f = (typeof filter !== "undefined" && filter !== null) ?
function(item) {
if (filter.contains(item)) {
set.add(item);
}
} :
function(item) {
set.add(item);
};
this.getRange(f, min, max, inclusive);
return set;
};
/**
* Count the number of elements having values in this range between the
* specified open or closed range of values.
*
* @param {Number} min Lower bound of range.
* @param {Number} max Upper bound of range.
* @param {Boolean} inclusive Whether max is included in bounds or not.
* @returns {Number} The number of items with values in the defined range.
*/
Exhibit.Database.RangeIndex.prototype.countRange = function(min, max, inclusive) {
var startIndex, endIndex, pairs, l;
startIndex = this._indexOf(min);
endIndex = this._indexOf(max);
if (inclusive) {
pairs = this._pairs;
l = pairs.length;
while (endIndex < l) {
if (pairs[endIndex].value === max) {
endIndex++;
} else {
break;
}
}
}
return endIndex - startIndex;
};
/**
* Find and return the closest preceding numeric index for a given value
* if it falls inside this range.
*
* @private
* @param {Number} v The value to find the closest index for.
* @returns {Number} The closest preceding index to the given value.
*/
Exhibit.Database.RangeIndex.prototype._indexOf = function(v) {
var pairs, from, to, middle, v2;
pairs = this._pairs;
if (pairs.length === 0 || pairs[0].value >= v) {
return 0;
}
from = 0;
to = pairs.length;
while (from + 1 < to) {
middle = (from + to) >> 1;
v2 = pairs[middle].value;
if (v2 >= v) {
to = middle;
} else {
from = middle;
}
}
return to;
};
/**
* @fileOverview Database item type definition.
* @author David Huynh
* @author Ryan Lee
*/
/**
* Represents an item type.
*
* @public
* @constructor
* @class
* @param {String} id Item type identifier.
*/
Exhibit.Database.Type = function(id) {
this._id = id;
this._custom = {};
};
/**
* Returns the item type identifier.
*
* @returns {String} The item type identifier.
*/
Exhibit.Database.Type.prototype.getID = function() {
return this._id;
};
/**
* Returns the item type URI.
*
* @returns {String} The item type URI.
*/
Exhibit.Database.Type.prototype.getURI = function() {
return this._custom["uri"];
};
/**
* Returns the item type user-friendly label.
*
* @returns {String} The item type label.
*/
Exhibit.Database.Type.prototype.getLabel = function() {
return this._custom["label"];
};
/**
* Returns the item type origin.
*
* @returns {String} The item type origin.
*/
Exhibit.Database.Type.prototype.getOrigin = function() {
return this._custom["origin"];
};
/**
* Returns a custom defined item type attribute's value.
*
* @param {String} p The property name.
* @returns {String} The item type attribute's value.
*/
Exhibit.Database.Type.prototype.getProperty = function(p) {
return this._custom[p];
};
/**
* @fileOverview Represents subsets of a database.
* @author David Huynh
* @author Ryan Lee
*/
/**
* Creates a new object with an identifier and the database it draws from.
*
* @class
* @constructor
* @param {String} id Collection identifier.
* @param {Exhibit.Database} database Database associated with the collection.
*/
Exhibit.Collection = function(id, database) {
this._id = id;
this._database = database;
this._elmt = null;
this._facets = [];
this._updating = false;
this._items = null;
this._restrictedItems = null;
};
/**
* Generator, includes all items in the database.
*
* @static
* @param {String} id Collection identifier.
* @param {Exhibit.Database} database Database associated with the collection.
* @returns {Exhibit.Collection} Newly created collection.
*/
Exhibit.Collection.createAllItemsCollection = function(id, database) {
var collection = new Exhibit.Collection(id, database);
collection._update = Exhibit.Collection._allItemsCollection_update;
Exhibit.Collection._initializeBasicCollection(collection, database);
collection._setElement();
return collection;
};
/**
* Generator, includes all items of certain type given in the
* configuration, or all items if not configured.
*
* @static
* @param {String} id Collection identifier.
* @param {Object} configuration A hash of configuration options.
* @param {Exhibit.Database} database Database associated with the collection.
* @returns {Exhibit.Collection} Newly created collection.
*/
Exhibit.Collection.create = function(id, configuration, database) {
var collection = new Exhibit.Collection(id, database);
collection._setElement();
if (typeof configuration["itemTypes"] !== "undefined") {
collection._itemTypes = configuration.itemTypes;
collection._update = Exhibit.Collection._typeBasedCollection_update;
} else {
collection._update = Exhibit.Collection._allItemsCollection_update;
}
Exhibit.Collection._initializeBasicCollection(collection, database);
return collection;
};
/**
* Generator, like create, but reads configuration from the DOM.
*
* @static
* @param {String} id Collection identifier.
* @param {Element} elmt DOM element configuring the collection.
* @param {Exhibit.Database} database Database associated with the collection.
* @returns {Exhibit.Collection} Newly created collection.
*/
Exhibit.Collection.createFromDOM = function(id, elmt, database) {
var collection, itemTypes;
collection = new Exhibit.Collection(id, database);
collection._setElement(elmt);
itemTypes = Exhibit.getAttribute(elmt, "itemTypes", ",");
if (typeof itemTypes !== "undefined" && itemTypes !== null && itemTypes.length > 0) {
collection._itemTypes = itemTypes;
collection._update = Exhibit.Collection._typeBasedCollection_update;
} else {
collection._update = Exhibit.Collection._allItemsCollection_update;
}
Exhibit.Collection._initializeBasicCollection(collection, database);
return collection;
};
/**
* Generator, like create, but locate the database from the UI context.
*
* @static
* @param {String} id Collection identifier.
* @param {Object} configuration Hash with configuration options.
* @param {Exhibit.UIContext} uiContext UI context for the collection.
* @returns {Exhibit.Collection} Newly created collection.
*/
Exhibit.Collection.create2 = function(id, configuration, uiContext) {
var database, collection;
database = uiContext.getDatabase();
if (typeof configuration["expression"] !== "undefined") {
collection = new Exhibit.Collection(id, database);
collection._setElement();
collection._expression = Exhibit.ExpressionParser.parse(configuration.expression);
collection._baseCollection = (typeof configuration["baseCollectionID"] !== "undefined") ?
uiContext.getMain().getCollection(configuration.baseCollectionID) :
uiContext.getCollection();
collection._restrictBaseCollection = (typeof configuration["restrictBaseCollection"] !== "undefined") ?
configuration.restrictBaseCollection : false;
if (collection._restrictBaseCollection) {
Exhibit.Collection._initializeRestrictingBasedCollection(collection);
} else {
Exhibit.Collection._initializeBasedCollection(collection);
}
return collection;
} else {
return Exhibit.Collection.create(id, configuration, database);
}
};
/**
* Generator, like createFromDOM, but locate the database from the UI context.
*
* @static
* @param {String} id Collection identifier.
* @param {Element} elmt DOM element configuring the collection.
* @param {Exhibit.UIContext} uiContext UI context for the collection.
* @returns {Exhibit.Collection} Newly created collection.
*/
Exhibit.Collection.createFromDOM2 = function(id, elmt, uiContext) {
var database, collection, expressionString, baseCollectionID;
database = uiContext.getDatabase();
expressionString = Exhibit.getAttribute(elmt, "expression");
if (typeof expressionString !== "undefined" && expressionString !== null && expressionString.length > 0) {
collection = new Exhibit.Collection(id, database);
collection._setElement(elmt);
collection._expression = Exhibit.ExpressionParser.parse(expressionString);
baseCollectionID = Exhibit.getAttribute(elmt, "baseCollectionID");
collection._baseCollection = (typeof baseCollectionID !== "undefined" && baseCollectionID !== null && baseCollectionID.length > 0) ?
uiContext.getMain().getCollection(baseCollectionID) :
uiContext.getCollection();
collection._restrictBaseCollection = Exhibit.getAttribute(elmt, "restrictBaseCollection") === "true";
if (collection._restrictBaseCollection) {
Exhibit.Collection._initializeRestrictingBasedCollection(collection, database);
} else {
Exhibit.Collection._initializeBasedCollection(collection);
}
} else {
collection = Exhibit.Collection.createFromDOM(id, elmt, database);
}
return collection;
};
/**
* Method called by most generators to fill the collection and add database
* listeners for its benefit.
*
* @static
* @private
* @param {Exhibit.Collection} collection Collection to initialize.
* @param {Exhibit.Database} database Source of data.
*/
Exhibit.Collection._initializeBasicCollection = function(collection, database) {
var update = function() { collection._update(); };
Exhibit.jQuery(document).bind('onAfterLoadingItems.exhibit', update);
Exhibit.jQuery(document).bind('onAfterRemovingAllStatements.exhibit', update);
collection._update();
};
/**
* Method called by a generator where a collection on which this collection
* is based exists.
*
* @static
* @private
* @param {Exhibit.Collection} collection Collection to initialize.
*/
Exhibit.Collection._initializeBasedCollection = function(collection) {
collection._update = Exhibit.Collection._basedCollection_update;
Exhibit.jQuery(this._elmt).bind('onItemsChanged.exhibit', function(evt) {
collection._update();
});
collection._update();
};
/**
* Used to initialize a collection based on another collection where
* updates to this collection should affect the base collection (they do
* not by default).
*
* @static
* @private
* @param {Exhibit.Collection} collection Collection to initialize.
* @param {Exhibit.Database} database Source of data.
*/
Exhibit.Collection._initializeRestrictingBasedCollection = function(collection, database) {
collection._cache = new Exhibit.FacetUtilities.Cache(
database,
collection._baseCollection,
collection._expression
);
collection._isUpdatingBaseCollection = false;
collection.onFacetUpdated = Exhibit.Collection._restrictingBasedCollection_onFacetUpdated;
collection.restrict = Exhibit.Collection._restrictingBasedCollection_restrict;
collection.update = Exhibit.Collection._restrictingBasedCollection_update;
collection.hasRestrictions = Exhibit.Collection._restrictingBasedCollection_hasRestrictions;
collection._baseCollection.addFacet(collection);
};
/**
* Assigned as a collection's update method when the collection is based
* on all items in the database. Not a static method.
*/
Exhibit.Collection._allItemsCollection_update = function() {
this.setItems(this._database.getAllItems());
this._onRootItemsChanged();
};
/**
* Assigned as a collection's update method when the collection is based
* on a set of item types. Not a static method.
*/
Exhibit.Collection._typeBasedCollection_update = function() {
var i, newItems = new Exhibit.Set();
for (i = 0; i < this._itemTypes.length; i++) {
this._database.getSubjects(this._itemTypes[i], "type", newItems);
}
this.setItems(newItems);
this._onRootItemsChanged();
};
/**
* Assigned as a collection's update method when the collection is based
* on another collection. Not a static method.
*/
Exhibit.Collection._basedCollection_update = function() {
this.setItems(this._expression.evaluate(
{ "value" : this._baseCollection.getRestrictedItems() },
{ "value" : "item" },
"value",
this._database
).values);
this._onRootItemsChanged();
};
/**
* Substitutes for the common implementation of onFacetUpdated to deal with
* the base collection. Not a static method.
*/
Exhibit.Collection._restrictingBasedCollection_onFacetUpdated = function() {
if (!this._updating) {
/*
* This is called when one of our own facets is changed.
*/
Exhibit.Collection.prototype.onFacetUpdated.call(this);
/*
* We need to restrict the base collection.
*/
this._isUpdatingBaseCollection = true;
this._baseCollection.onFacetUpdated();
this._isUpdatingBaseCollection = false;
}
};
/**
* Substitutes for the common restrict method to restrict the base
* collection. Not a static method.
*
* @param {Exhibit.Set} items Viable items.
* @returns {Exhibit.Set} Possibly further constrained set of items.
*/
Exhibit.Collection._restrictingBasedCollection_restrict = function(items) {
if (this._restrictedItems.size() === this._items.size()) {
return items;
}
return this._cache.getItemsFromValues(this._restrictedItems, items);
};
/**
* Assigned as the update method when a collection is based on another
* collection and restricts it. Not a static method.
*
* @param {Exhibit.Set} items Used to locate new items for the collection.
*/
Exhibit.Collection._restrictingBasedCollection_update = function(items) {
if (!this._isUpdatingBaseCollection) {
this.setItems(this._cache.getValuesFromItems(items));
this._onRootItemsChanged();
}
};
/**
* Adds a hasRestrictions method to the collection, like a Exhibit.Facet has.
* Not a static method.
*
* @returns {Boolean} True if this collection has restrictions.
*/
Exhibit.Collection._restrictingBasedCollection_hasRestrictions = function() {
return (this._items !== null) && (this._restrictedItems !== null) &&
(this._restrictedItems.size() !== this._items.size());
};
/**
* Getter for the collection identifier.
*
* @returns {String} Collection identifier.
*/
Exhibit.Collection.prototype.getID = function() {
return this._id;
};
/**
* Create an element to associate with the collection if none
* exists.
*
* @param {Element} el
*/
Exhibit.Collection.prototype._setElement = function(el) {
if (typeof el === "undefined" || el === null) {
if (this.getID() !== "default") {
this._elmt = Exhibit.jQuery("
")
.attr("id", this.getID())
.attr(Exhibit.makeExhibitAttribute("role"), "exhibit-collection")
.css("display", "none")
.appendTo(document.body)
.get(0);
} else {
this._elmt = document;
}
} else {
this._elmt = el;
}
};
/**
* Returns the element, commonly used to bind against, associated with
* the collection.
*
* @returns {Element}
*/
Exhibit.Collection.prototype.getElement = function() {
return this._elmt;
};
/**
* Explicitly set which items are in this collection.
*
* @param {Exhibit.Set} items The collection's items.
*/
Exhibit.Collection.prototype.setItems = function(items) {
this._items = items;
};
/**
* Compare collections for equality.
*
* @param {Exhibit.Collection} collection
* @returns {Boolean} True if collection is equal to this one.
*/
Exhibit.Collection.prototype.equals = function(collection) {
return (this.getID() === collection.getID());
};
/**
* Handle removing the collection from the local context.
*/
Exhibit.Collection.prototype.dispose = function() {
if (typeof this["_baseCollection"] !== "undefined") {
this._baseCollection = null;
this._expression = null;
}
this._database = null;
this._elmt = null;
this._items = null;
this._restrictedItems = null;
};
/**
* Register a facet with a collection.
*
* @param {Exhibit.Facet} facet The new facet.
*/
Exhibit.Collection.prototype.addFacet = function(facet) {
this._facets.push(facet);
if (facet.hasRestrictions()) {
this._computeRestrictedItems();
this._updateFacets();
Exhibit.jQuery(this._elmt).trigger("onItemsChanged.exhibit");
} else {
facet.update(this.getRestrictedItems());
}
};
/**
* Remove a previously registered facet from the collection. Generally
* called by the facet itself when it is disposed.
*
* @param {Exhibit.Facet} facet The facet to be removed.
*/
Exhibit.Collection.prototype.removeFacet = function(facet) {
var i;
for (i = 0; i < this._facets.length; i++) {
if (facet === this._facets[i]) {
this._facets.splice(i, 1);
if (facet.hasRestrictions()) {
this._computeRestrictedItems();
this._updateFacets();
Exhibit.jQuery(this._elmt).trigger("onItemsChanged.exhibit");
}
break;
}
}
};
/**
* Signal all registered facets to clear any currently set restrictions.
*
* @returns {Array} A list of objects returned by clearing restrictions.
*/
Exhibit.Collection.prototype.clearAllRestrictions = function() {
var i, state;
state = Exhibit.History.getState();
this._updating = true;
for (i = 0; i < this._facets.length; i++) {
Exhibit.History.setComponentState(
state,
this._facets[i],
Exhibit.Facet._registryKey,
this._facets[i].exportEmptyState(),
true
);
}
this._updating = false;
this.onFacetUpdated();
return state;
};
/**
* Given an array of restrictions, signal each registered facets to
* implement any applicable restriction.
*
* @param {Array} restrictions List of objects used for applying restrictions.
*/
Exhibit.Collection.prototype.applyRestrictions = function(restrictions) {
var i;
this._updating = true;
for (i = 0; i < this._facets.length; i++) {
this._facets[i].applyRestrictions(restrictions[i]);
}
this._updating = false;
this.onFacetUpdated();
};
/**
* Return all items in the collection regardless of restrictions.
*
* @returns {Exhibit.Set} All collection items.
*/
Exhibit.Collection.prototype.getAllItems = function() {
return new Exhibit.Set(this._items);
};
/**
* Return the count of all items in the collection regardless of restrictions.
*
* @returns {Number} The count of all collection items.
*/
Exhibit.Collection.prototype.countAllItems = function() {
return this._items.size();
};
/**
* Return only the items that match current restrictions.
*
* @returns {Exhibit.Set} Restricted items.
*/
Exhibit.Collection.prototype.getRestrictedItems = function() {
return new Exhibit.Set(this._restrictedItems);
};
/**
* Return the count of only the items that match current restrictions.
*
* @returns {Number} The count of restricted items.
*/
Exhibit.Collection.prototype.countRestrictedItems = function() {
return this._restrictedItems.size();
};
/**
* Modifies the set of items matching the restriction based on changes
* to a facet, sending the signal onItemsChanged when finished.
*/
Exhibit.Collection.prototype.onFacetUpdated = function() {
if (!this._updating) {
this._computeRestrictedItems();
this._updateFacets();
Exhibit.jQuery(this._elmt).trigger("onItemsChanged.exhibit");
}
};
/**
* Called when the base set of items the collection includes have changed,
* called by the update methods. Fires onRootItemsChanged and onItemsChanged
* during execution.
*
* @private
*/
Exhibit.Collection.prototype._onRootItemsChanged = function() {
Exhibit.jQuery(this._elmt).trigger("onRootItemsChanged.exhibit");
this._computeRestrictedItems();
this._updateFacets();
Exhibit.jQuery(this._elmt).trigger("onItemsChanged.exhibit");
};
/**
* Signals registered facets to make updates based on one facet's
* restrictions changing, called by onFacetUpdated.
*
* @private
*/
Exhibit.Collection.prototype._updateFacets = function() {
var restrictedFacetCount, i, facet, items, j;
restrictedFacetCount = 0;
for (i = 0; i < this._facets.length; i++) {
if (this._facets[i].hasRestrictions()) {
restrictedFacetCount++;
}
}
for (i = 0; i < this._facets.length; i++) {
facet = this._facets[i];
if (facet.hasRestrictions()) {
if (restrictedFacetCount <= 1) {
facet.update(this.getAllItems());
} else {
items = this.getAllItems();
for (j = 0; j < this._facets.length; j++) {
if (i !== j) {
items = this._facets[j].restrict(items);
}
}
facet.update(items);
}
} else {
facet.update(this.getRestrictedItems());
}
}
};
/**
* Calculates the new set of items based on restrictions, caching the
* result for later use.
*
* @private
*/
Exhibit.Collection.prototype._computeRestrictedItems = function() {
var i, facet;
this._restrictedItems = this._items;
for (i = 0; i < this._facets.length; i++) {
facet = this._facets[i];
if (facet.hasRestrictions()) {
this._restrictedItems = facet.restrict(this._restrictedItems);
}
}
};
/**
* @fileOverview Base class for database query language.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Expression = {};
/**
* @class
* @constructor
* @public
* @param {Exhibit.Expression.Path} rootNode
*/
Exhibit.Expression._Impl = function(rootNode) {
this._rootNode = rootNode;
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Object}
*/
Exhibit.Expression._Impl.prototype.evaluate = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
var collection = this._rootNode.evaluate(roots, rootValueTypes, defaultRootName, database);
return {
values: collection.getSet(),
valueType: collection.valueType,
size: collection.size
};
};
/**
* @param {String} itemID
* @param {Exhibit.Database} database
* @returns {Object}
*/
Exhibit.Expression._Impl.prototype.evaluateOnItem = function(itemID, database) {
return this.evaluate(
{ "value" : itemID },
{ "value" : "item" },
"value",
database
);
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Object}
*/
Exhibit.Expression._Impl.prototype.evaluateSingle = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
var collection, result;
collection = this._rootNode.evaluate(roots, rootValueTypes, defaultRootName, database);
result = { value: null,
valueType: collection.valueType };
collection.forEachValue(function(v) {
result.value = v;
return true;
});
return result;
};
/**
* @param {String} itemID
* @param {Exhibit.Database} database
* @returns {Object}
*/
Exhibit.Expression._Impl.prototype.evaluateSingleOnItem = function(itemID, database) {
return this.evaluateSingle(
{ "value" : itemID },
{ "value" : "item" },
"value",
database
);
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Boolean}
*/
Exhibit.Expression._Impl.prototype.testExists = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
return this.isPath() ?
this._rootNode.testExists(roots, rootValueTypes, defaultRootName, database) :
this.evaluate(roots, rootValueTypes, defaultRootName, database).values.size() > 0;
};
/**
* @returns {Boolean}
*/
Exhibit.Expression._Impl.prototype.isPath = function() {
return this._rootNode instanceof Exhibit.Expression.Path;
};
/**
* @returns {Exhibit.Expression.Path}
*/
Exhibit.Expression._Impl.prototype.getPath = function() {
return this.isPath() ?
this._rootNode :
null;
};
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @param {Array|Exhibit.Set} values
* @param {String} valueType
*/
Exhibit.Expression._Collection = function(values, valueType) {
this._values = values;
this.valueType = valueType;
if (values instanceof Array) {
this.forEachValue = Exhibit.Expression._Collection._forEachValueInArray;
this.getSet = Exhibit.Expression._Collection._getSetFromArray;
this.contains = Exhibit.Expression._Collection._containsInArray;
this.size = values.length;
} else {
this.forEachValue = Exhibit.Expression._Collection._forEachValueInSet;
this.getSet = Exhibit.Expression._Collection._getSetFromSet;
this.contains = Exhibit.Expression._Collection._containsInSet;
this.size = values.size();
}
};
/**
* @param {Function} f
*/
Exhibit.Expression._Collection._forEachValueInSet = function(f) {
this._values.visit(f);
};
/**
* @param {Function} f
*/
Exhibit.Expression._Collection._forEachValueInArray = function(f) {
var a, i;
a = this._values;
for (i = 0; i < a.length; i++) {
if (f(a[i])) {
break;
}
}
};
/**
* @returns {Exhibit.Set}
*/
Exhibit.Expression._Collection._getSetFromSet = function() {
return this._values;
};
/**
* @returns {Exhibit.Set}
*/
Exhibit.Expression._Collection._getSetFromArray = function() {
return new Exhibit.Set(this._values);
};
/**
* @param {String|Number} v
* @returns {Boolean}
*/
Exhibit.Expression._Collection._containsInSet = function(v) {
return this._values.contains(v);
};
/**
* @param {String|Number} v
* @returns {Boolean}
*/
Exhibit.Expression._Collection._containsInArray = function(v) {
var a, i;
a = this._values;
for (i = 0; i < a.length; i++) {
if (a[i] === v) {
return true;
}
}
return false;
};
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @public
* @param {String|Number} value
* @param {String} valueType
*/
Exhibit.Expression._Constant = function(value, valueType) {
this._value = value;
this._valueType = valueType;
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Exhibit.Expression._Collection}
*/
Exhibit.Expression._Constant.prototype.evaluate = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
return new Exhibit.Expression._Collection([ this._value ], this._valueType);
};
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @public
* @param {String} name
* @param {Array} args
*/
Exhibit.Expression._ControlCall = function(name, args) {
this._name = name;
this._args = args;
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Exhibit.Expression._Collection}
*/
Exhibit.Expression._ControlCall.prototype.evaluate = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
return Exhibit.Controls[this._name].f(this._args, roots, rootValueTypes, defaultRootName, database);
};
/**
* @fileOverview Implementation of query language control features.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Controls = {};
Exhibit.Controls["if"] = {
f: function(
args,
roots,
rootValueTypes,
defaultRootName,
database
) {
var conditionCollection = args[0].evaluate(roots, rootValueTypes, defaultRootName, database), condition;
condition = false;
conditionCollection.forEachValue(function(v) {
if (v) {
condition = true;
return true;
}
});
if (condition) {
return args[1].evaluate(roots, rootValueTypes, defaultRootName, database);
} else {
return args[2].evaluate(roots, rootValueTypes, defaultRootName, database);
}
}
};
Exhibit.Controls["foreach"] = {
f: function(
args,
roots,
rootValueTypes,
defaultRootName,
database
) {
var collection, oldValue, oldValueType, results, valueType;
collection = args[0].evaluate(roots, rootValueTypes, defaultRootName, database);
oldValue = roots["value"];
oldValueType = rootValueTypes["value"];
rootValueTypes["value"] = collection.valueType;
results = [];
valueType = "text";
collection.forEachValue(function(element) {
roots["value"] = element;
var collection2 = args[1].evaluate(roots, rootValueTypes, defaultRootName, database);
valueType = collection2.valueType;
collection2.forEachValue(function(result) {
results.push(result);
});
});
roots["value"] = oldValue;
rootValueTypes["value"] = oldValueType;
return new Exhibit.Expression._Collection(results, valueType);
}
};
Exhibit.Controls["default"] = {
f: function(
args,
roots,
rootValueTypes,
defaultRootName,
database
) {
var i, collection;
for (i = 0; i < args.length; i++) {
collection = args[i].evaluate(roots, rootValueTypes, defaultRootName, database);
if (collection.size > 0) {
return collection;
}
}
return new Exhibit.Expression._Collection([], "text");
}
};
Exhibit.Controls["filter"] = {
f: function(
args,
roots,
rootValueTypes,
defaultRootName,
database
) {
var collection, oldValue, oldValueType, results;
collection = args[0].evaluate(roots, rootValueTypes, defaultRootName, database);
oldValue = roots["value"];
oldValueType = rootValueTypes["value"];
results = new Exhibit.Set();
rootValueTypes["value"] = collection.valueType;
collection.forEachValue(function(element) {
roots["value"] = element;
var collection2 = args[1].evaluate(roots, rootValueTypes, defaultRootName, database);
if (collection2.size > 0 && collection2.contains("true")) {
results.add(element);
}
});
roots["value"] = oldValue;
rootValueTypes["value"] = oldValueType;
return new Exhibit.Expression._Collection(results, collection.valueType);
}
};
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @public
* @param {String} name
* @param {Array} args
*/
Exhibit.Expression._FunctionCall = function(name, args) {
this._name = name;
this._args = args;
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Exhibit.Expression._Collection}
* @throws Error
*/
Exhibit.Expression._FunctionCall.prototype.evaluate = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
var args = [], i;
for (i = 0; i < this._args.length; i++) {
args.push(this._args[i].evaluate(roots, rootValueTypes, defaultRootName, database));
}
if (typeof Exhibit.Functions[this._name] !== "undefined") {
return Exhibit.Functions[this._name].f(args);
} else {
throw new Error(Exhibit._("%expression.noSuchFunction", this._name));
}
};
/**
* @fileOverview Implementation of query language function features.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Functions = {};
/**
* @namespace
*/
Exhibit.FunctionUtilities = {};
/**
* @static
* @param {String} name
* @param {Function} f
* @param {String} valueType
*/
Exhibit.FunctionUtilities.registerSimpleMappingFunction = function(name, f, valueType) {
Exhibit.Functions[name] = {
f: function(args) {
var set = new Exhibit.Set(), i, fn;
fn = function() {
return function(v) {
var v2 = f(v);
if (typeof v2 !== "undefined") {
set.add(v2);
}
};
};
for (i = 0; i < args.length; i++) {
args[i].forEachValue(fn());
}
return new Exhibit.Expression._Collection(set, valueType);
}
};
};
Exhibit.Functions["union"] = {
f: function(args) {
var set, valueType, i, arg;
set = new Exhibit.Set();
valueType = null;
if (args.length > 0) {
valueType = args[0].valueType;
for (i = 0; i < args.length; i++) {
arg = args[i];
if (arg.size > 0) {
if (typeof valueType === "undefined" || valueType === null) {
valueType = arg.valueType;
}
set.addSet(arg.getSet());
}
}
}
return new Exhibit.Expression._Collection(set, (typeof valueType !== "undefined" && valueType !== null) ? valueType : "text");
}
};
Exhibit.Functions["contains"] = {
f: function(args) {
var result, set;
result = args[0].size > 0;
set = args[0].getSet();
args[1].forEachValue(function(v) {
if (!set.contains(v)) {
result = false;
return true;
}
});
return new Exhibit.Expression._Collection([ result ], "boolean");
}
};
Exhibit.Functions["exists"] = {
f: function(args) {
return new Exhibit.Expression._Collection([ args[0].size > 0 ], "boolean");
}
};
Exhibit.Functions["count"] = {
f: function(args) {
return new Exhibit.Expression._Collection([ args[0].size ], "number");
}
};
Exhibit.Functions["not"] = {
f: function(args) {
return new Exhibit.Expression._Collection([ !args[0].contains(true) ], "boolean");
}
};
Exhibit.Functions["and"] = {
f: function(args) {
var r = true, i;
for (i = 0; r && i < args.length; i++) {
r = r && args[i].contains(true);
}
return new Exhibit.Expression._Collection([ r ], "boolean");
}
};
Exhibit.Functions["or"] = {
f: function(args) {
var r = false, i;
for (i = 0; !r && i < args.length; i++) {
r = r || args[i].contains(true);
}
return new Exhibit.Expression._Collection([ r ], "boolean");
}
};
Exhibit.Functions["add"] = {
f: function(args) {
var total, i, fn;
total = 0;
fn = function() {
return function(v) {
if (typeof v !== "undefined" && v !== null) {
if (typeof v === "number") {
total += v;
} else {
var n = parseFloat(v);
if (!isNaN(n)) {
total += n;
}
}
}
};
};
for (i = 0; i < args.length; i++) {
args[i].forEachValue(fn());
}
return new Exhibit.Expression._Collection([ total ], "number");
}
};
// Note: arguments expanding to multiple items get concatenated in random order
Exhibit.Functions["concat"] = {
f: function(args) {
var result = [], i, fn;
fn = function() {
return function(v) {
if (typeof v !== "undefined" && v !== null) {
result.push(v);
}
};
};
for (i = 0; i < args.length; i++) {
args[i].forEachValue(fn());
}
return new Exhibit.Expression._Collection([ result.join('') ], "text");
}
};
Exhibit.Functions["multiply"] = {
f: function(args) {
var product = 1, i, fn;
fn = function() {
return function(v) {
var n;
if (typeof v !== "undefined" && v !== null) {
if (typeof v === "number") {
product *= v;
} else {
n = parseFloat(v);
if (!isNaN(n)) {
product *= n;
}
}
}
};
};
for (i = 0; i < args.length; i++) {
args[i].forEachValue(fn());
}
return new Exhibit.Expression._Collection([ product ], "number");
}
};
Exhibit.Functions["date-range"] = {
_parseDate: function (v) {
if (typeof v === "undefined" || v === null) {
return Number.NEGATIVE_INFINITY;
} else if (v instanceof Date) {
return v.getTime();
} else {
try {
return Exhibit.DateTime.parseIso8601DateTime(v).getTime();
} catch (e) {
return Number.NEGATIVE_INFINITY;
}
}
},
_computeRange: function(from, to, interval) {
var range = to - from;
if (isFinite(range)) {
if (typeof Exhibit.DateTime[interval.toUpperCase()] !== "undefined") {
range = Math.round(range / Exhibit.DateTime.gregorianUnitLengths[Exhibit.DateTime[interval.toUpperCase()]]);
}
return range;
}
return null;
},
f: function(args) {
var self = this, from, to, interval, range;
from = Number.POSITIVE_INFINITY;
args[0].forEachValue(function(v) {
from = Math.min(from, self._parseDate(v));
});
to = Number.NEGATIVE_INFINITY;
args[1].forEachValue(function(v) {
to = Math.max(to, self._parseDate(v));
});
interval = "day";
args[2].forEachValue(function(v) {
interval = v;
});
range = this._computeRange(from, to, interval);
return new Exhibit.Expression._Collection((typeof range !== "undefined" && range !== null) ? [ range ] : [], "number");
}
};
Exhibit.Functions["distance"] = {
_units: {
km: 1e3,
mile: 1609.344
},
_computeDistance: function(from, to, unit, roundTo) {
var range = from.distanceFrom(to);
if (!roundTo) {
roundTo = 1;
}
if (isFinite(range)) {
if (typeof this._units[unit] !== "undefined") {
range = range / this._units[unit];
}
return Exhibit.Util.round(range, roundTo);
}
return null;
},
f: function(args) {
var self = this, data, name, i, latlng, from, to, range, fn;
data = {};
name = ["origo", "lat", "lng", "unit", "round"];
fn = function(nm) {
return function(v) {
data[nm] = v;
};
};
for (i = 0, n = name[i]; i < name.length; i++) {
args[i].forEachValue(fn(n));
}
latlng = data.origo.split(",");
from = new GLatLng( latlng[0], latlng[1] );
to = new GLatLng( data.lat, data.lng );
range = this._computeDistance(from, to, data.unit, data.round);
return new Exhibit.Expression._Collection((typeof range !== "undefined" && range !== null) ? [ range ] : [], "number");
}
};
Exhibit.Functions["min"] = {
f: function(args) {
/** @ignore */
var returnMe = function (val) { return val; }, min, valueType, i, arg, currentValueType, parser, fn;
min = Number.POSITIVE_INFINITY;
valueType = null;
fn = function(p, c) {
return function(v) {
var parsedV = p(v, returnMe);
if (parsedV < min || min === Number.POSITIVE_INFINITY) {
min = parsedV;
valueType = (valueType === null) ? c :
(valueType === c ? valueType : "text") ;
}
};
};
for (i = 0; i < args.length; i++) {
arg = args[i];
currentValueType = arg.valueType ? arg.valueType : 'text';
parser = Exhibit.SettingsUtilities._typeToParser(currentValueType);
arg.forEachValue(fn(parser, currentValueType));
}
return new Exhibit.Expression._Collection([ min ], (typeof valueType !== "undefined" && valueType !== null) ? valueType : "text");
}
};
Exhibit.Functions["max"] = {
f: function(args) {
var returnMe = function (val) { return val; }, max, valueType, i, arg, currentValueType, parser, fn;
max = Number.NEGATIVE_INFINITY;
valueType = null;
fn = function(p, c) {
return function(v) {
var parsedV = p(v, returnMe);
if (parsedV > max || max === Number.NEGATIVE_INFINITY) {
max = parsedV;
valueType = (valueType === null) ? c :
(valueType === c ? valueType : "text") ;
}
};
};
for (i = 0; i < args.length; i++) {
arg = args[i];
currentValueType = arg.valueType ? arg.valueType : 'text';
parser = Exhibit.SettingsUtilities._typeToParser(currentValueType);
arg.forEachValue(fn(parser, c));
}
return new Exhibit.Expression._Collection([ max ], (typeof valueType !== "undefined" && valueType !== null) ? valueType : "text");
}
};
Exhibit.Functions["remove"] = {
f: function(args) {
var set, valueType, i, arg;
set = args[0].getSet();
valueType = args[0].valueType;
for (i = 1; i < args.length; i++) {
arg = args[i];
if (arg.size > 0) {
set.removeSet(arg.getSet());
}
}
return new Exhibit.Expression._Collection(set, valueType);
}
};
Exhibit.Functions["now"] = {
f: function(args) {
return new Exhibit.Expression._Collection([ new Date() ], "date");
}
};
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @public
* @param {String} operator
* @param {Array} args
*/
Exhibit.Expression._Operator = function(operator, args) {
this._operator = operator;
this._args = args;
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Exhibit.Expression._Collection}
*/
Exhibit.Expression._Operator.prototype.evaluate = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
var values = [], args = [], i, operator, f;
for (i = 0; i < this._args.length; i++) {
args.push(this._args[i].evaluate(roots, rootValueTypes, defaultRootName, database));
}
operator = Exhibit.Expression._operators[this._operator];
f = operator.f;
if (operator.argumentType === "number") {
args[0].forEachValue(function(v1) {
if (typeof v1 !== "number") {
v1 = parseFloat(v1);
}
args[1].forEachValue(function(v2) {
if (typeof v2 !== "number") {
v2 = parseFloat(v2);
}
values.push(f(v1, v2));
});
});
} else {
args[0].forEachValue(function(v1) {
args[1].forEachValue(function(v2) {
values.push(f(v1, v2));
});
});
}
return new Exhibit.Expression._Collection(values, operator.valueType);
};
/**
* @private
*/
Exhibit.Expression._operators = {
"+" : {
argumentType: "number",
valueType: "number",
/** @ignore */
f: function(a, b) { return a + b; }
},
"-" : {
argumentType: "number",
valueType: "number",
/** @ignore */
f: function(a, b) { return a - b; }
},
"*" : {
argumentType: "number",
valueType: "number",
/** @ignore */
f: function(a, b) { return a * b; }
},
"/" : {
argumentType: "number",
valueType: "number",
/** @ignore */
f: function(a, b) { return a / b; }
},
"=" : {
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a === b; }
},
"<>" : {
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a !== b; }
},
"><" : {
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a !== b; }
},
"<" : {
argumentType: "number",
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a < b; }
},
">" : {
argumentType: "number",
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a > b; }
},
"<=" : {
argumentType: "number",
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a <= b; }
},
">=" : {
argumentType: "number",
valueType: "boolean",
/** @ignore */
f: function(a, b) { return a >= b; }
}
};
/**
* @fileOverview Query language class representing graph paths.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @class
* @constructor
*/
Exhibit.Expression.Path = function() {
this._rootName = null;
this._segments = [];
};
/**
* @param {String} property
* @param {Boolean} forward
* @returns {Exhibit.Expression.Path}
*/
Exhibit.Expression.Path.create = function(property, forward) {
var path = new Exhibit.Expression.Path();
path._segments.push({ property: property,
forward: forward,
isArray: false });
return path;
};
/**
* @param {String} rootName
*/
Exhibit.Expression.Path.prototype.setRootName = function(rootName) {
this._rootName = rootName;
};
/**
* @param {String} property
* @param {String} hopOperator
*/
Exhibit.Expression.Path.prototype.appendSegment = function(property, hopOperator) {
this._segments.push({
property: property,
forward: hopOperator.charAt(0) === ".",
isArray: hopOperator.length > 1
});
};
/**
* @param {Number} index
* @returns {Object}
*/
Exhibit.Expression.Path.prototype.getSegment = function(index) {
var segment;
if (index < this._segments.length) {
segment = this._segments[index];
return {
property: segment.property,
forward: segment.forward,
isArray: segment.isArray
};
} else {
return null;
}
};
/**
* @returns {Object}
*/
Exhibit.Expression.Path.prototype.getLastSegment = function() {
return this.getSegment(this._segments.length - 1);
};
/**
* @returns {Number}
*/
Exhibit.Expression.Path.prototype.getSegmentCount = function() {
return this._segments.length;
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Exhibit.Expression._Collection}
* @throws Error
*/
Exhibit.Expression.Path.prototype.evaluate = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
var rootName, valueType, collection, root;
rootName = (typeof this._rootName !== "undefined" && this._rootName !== null) ?
this._rootName :
defaultRootName;
valueType = typeof rootValueTypes[rootName] !== "undefined" ?
rootValueTypes[rootName] :
"text";
collection = null;
if (typeof roots[rootName] !== "undefined") {
root = roots[rootName];
if (root instanceof Exhibit.Set || root instanceof Array) {
collection = new Exhibit.Expression._Collection(root, valueType);
} else {
collection = new Exhibit.Expression._Collection([root], valueType);
}
return this._walkForward(collection, database);
} else {
throw new Error(Exhibit._("%expression.error.noSuchVariable", rootName));
}
};
/**
* @param {String|Number} value
* @param {String} valueType
* @param {Exhibit.Set} filter
* @param {Exhibit.Database} database
*/
Exhibit.Expression.Path.prototype.evaluateBackward = function(
value,
valueType,
filter,
database
) {
var collection = new Exhibit.Expression._Collection([ value ], valueType);
return this._walkBackward(collection, filter, database);
};
/**
* @param {Exhibit.Set|Array} values
* @param {String} valueType
* @param {Exhibit.Database} database
*/
Exhibit.Expression.Path.prototype.walkForward = function(
values,
valueType,
database
) {
return this._walkForward(new Exhibit.Expression._Collection(values, valueType), database);
};
/**
* @param {Exhibit.Set|Array} values
* @param {String} valueType
* @param {Exhibit.Set} filter
* @param {Exhibit.Database} database
*/
Exhibit.Expression.Path.prototype.walkBackward = function(
values,
valueType,
filter,
database
) {
return this._walkBackward(new Exhibit.Expression._Collection(values, valueType), filter, database);
};
/**
* @private
* @param {Exhibit.Expression._Collection} collection
* @param {Exhibit.Database} database
* @returns {Exhibit.Expression._Collection}
*/
Exhibit.Expression.Path.prototype._walkForward = function(collection, database) {
var i, segment, a, valueType, property, values, makeForEach;
makeForEach = function(forward, as, s) {
var fn = forward ? database.getObjects : database.getSubjects;
return function(v) {
fn(v, s.property).visit(function(v2) {
as.push(v2);
});
};
};
for (i = 0; i < this._segments.length; i++) {
segment = this._segments[i];
if (segment.isArray) {
a = [];
collection.forEachValue(makeForEach(segment.forward, a, segment));
if (segment.forward) {
property = database.getProperty(segment.property);
valueType = (typeof property !== "undefined" && property !== null) ?
property.getValueType() :
"text";
} else {
valueType = "item";
}
collection = new Exhibit.Expression._Collection(a, valueType);
} else {
if (segment.forward) {
values = database.getObjectsUnion(collection.getSet(), segment.property);
property = database.getProperty(segment.property);
valueType = (typeof property !== "undefined" && property !== null) ?
property.getValueType() :
"text";
collection = new Exhibit.Expression._Collection(values, valueType);
} else {
values = database.getSubjectsUnion(collection.getSet(), segment.property);
collection = new Exhibit.Expression._Collection(values, "item");
}
}
}
return collection;
};
/**
* @private
* @param {Exhibit.Expression._Collection} collection
* @param {Exhiibt.Set} filter
* @param {Exhibit.Database} database
* @param {Exhibit.Expression._Collection}
*/
Exhibit.Expression.Path.prototype._walkBackward = function(collection, filter, database) {
var i, segment, a, valueType, property, values, makeForEach;
makeForEach = function(forward, as, s, idx) {
var fn = forward ? database.getObjects : database.getSubjects;
return function(v) {
fn(v, s.property).visit(function(v2) {
if (idx > 0 || typeof filter === "undefined" || filter === null || filter.contains(v2)) {
as.push(v2);
}
});
};
};
for (i = this._segments.length - 1; i >= 0; i--) {
segment = this._segments[i];
if (segment.isArray) {
a = [];
collection.forEachValue(makeForEach(segment.forward, a, segment, i));
if (segment.forward) {
property = database.getProperty(segment.property);
valueType = (typeof property !== "undefined" && property !== null) ?
property.getValueType() :
"text";
} else {
valueType = "item";
}
collection = new Exhibit.Expression._Collection(a, valueType);
} else {
if (segment.forward) {
values = database.getSubjectsUnion(collection.getSet(), segment.property, null, i === 0 ? filter : null);
collection = new Exhibit.Expression._Collection(values, "item");
} else {
values = database.getObjectsUnion(collection.getSet(), segment.property, null, i === 0 ? filter : null);
property = database.getProperty(segment.property);
valueType = (typeof property !== "undefined" && property !== null) ? property.getValueType() : "text";
collection = new Exhibit.Expression._Collection(values, valueType);
}
}
}
return collection;
};
/**
* @param {Number} from
* @param {Number} to
* @param {Boolean} inclusive
* @param {Exhibit.Set} filter
* @param {Exhibit.Database} database
* @returns {Object}
* @throws Error
*/
Exhibit.Expression.Path.prototype.rangeBackward = function(
from,
to,
inclusive,
filter,
database
) {
var set, valueType, segment, i, property;
set = new Exhibit.Set();
valueType = "item";
if (this._segments.length > 0) {
segment = this._segments[this._segments.length - 1];
if (segment.forward) {
database.getSubjectsInRange(segment.property, from, to, inclusive, set, this._segments.length === 1 ? filter : null);
} else {
throw new Error(Exhibit._("%expression.error.mustBeForward"));
}
for (i = this._segments.length - 2; i >= 0; i--) {
segment = this._segments[i];
if (segment.forward) {
set = database.getSubjectsUnion(set, segment.property, null, i === 0 ? filter : null);
valueType = "item";
} else {
set = database.getObjectsUnion(set, segment.property, null, i === 0 ? filter : null);
property = database.getProperty(segment.property);
valueType = (typeof property !== "undefined" && property !== null) ? property.getValueType() : "text";
}
}
}
return {
valueType: valueType,
values: set,
count: set.size()
};
};
/**
* @param {Object} roots
* @param {Object} rootValueTypes
* @param {String} defaultRootName
* @param {Exhibit.Database} database
* @returns {Boolean}
*/
Exhibit.Expression.Path.prototype.testExists = function(
roots,
rootValueTypes,
defaultRootName,
database
) {
return this.evaluate(roots, rootValueTypes, defaultRootName, database).size > 0;
};
/**
* @fileOverview All classes and support methods for parsing queries.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.ExpressionParser = {};
/**
* @static
* @param {String} s
* @param {Number} startIndex
* @param {Object} results
* @returns {Exhibit.Expression._Impl}
*/
Exhibit.ExpressionParser.parse = function(s, startIndex, results) {
startIndex = startIndex || 0;
results = results || {};
var scanner = new Exhibit.ExpressionScanner(s, startIndex);
try {
return Exhibit.ExpressionParser._internalParse(scanner, false);
} finally {
results.index = scanner.token() !== null ? scanner.token().start : scanner.index();
}
};
/**
* @static
* @param {String} s
* @param {Number} startIndex
* @param {Object} results
* @returns {Array}
*/
Exhibit.ExpressionParser.parseSeveral = function(s, startIndex, results) {
startIndex = startIndex || 0;
results = results || {};
var scanner = new Exhibit.ExpressionScanner(s, startIndex);
try {
return Exhibit.ExpressionParser._internalParse(scanner, true);
} finally {
results.index = scanner.token() !== null ? scanner.token().start : scanner.index();
}
};
/**
* @static
* @param {Exhibit.ExpressionScanner} scanner
* @param {Boolean} several
* @returns {Exhibit.Expression._Impl|Array}
*/
Exhibit.ExpressionParser._internalParse = function(scanner, several) {
var Scanner, token, next, makePosition, parsePath, parseFactor, parseTerm, parseSubExpression, parseExpression, parseExpressionList, roots, expressions, r;
Scanner = Exhibit.ExpressionScanner;
token = scanner.token();
next = function() { scanner.next(); token = scanner.token(); };
makePosition = function() { return token !== null ? token.start : scanner.index(); };
parsePath = function() {
var path = new Exhibit.Expression.Path(), hopOperator;
while (token !== null && token.type === Scanner.PATH_OPERATOR) {
hopOperator = token.value;
next();
if (token !== null && token.type === Scanner.IDENTIFIER) {
path.appendSegment(token.value, hopOperator);
next();
} else {
throw new Error(Exhibit._("%expression.error.missingPropertyID", makePosition()));
}
}
return path;
};
parseFactor = function() {
var result = null, identifier, args;
if (typeof token === "undefined" || token === null) {
throw new Error(Exhibit._("%expression.error.missingFactor"));
}
switch (token.type) {
case Scanner.NUMBER:
result = new Exhibit.Expression._Constant(token.value, "number");
next();
break;
case Scanner.STRING:
result = new Exhibit.Expression._Constant(token.value, "text");
next();
break;
case Scanner.PATH_OPERATOR:
result = parsePath();
break;
case Scanner.IDENTIFIER:
identifier = token.value;
next();
if (typeof Exhibit.Controls[identifier] !== "undefined") {
if (token !== null && token.type === Scanner.DELIMITER && token.value === "(") {
next();
args = (token !== null && token.type === Scanner.DELIMITER && token.value === ")") ?
[] :
parseExpressionList();
result = new Exhibit.Expression._ControlCall(identifier, args);
if (token !== null && token.type === Scanner.DELIMITER && token.value === ")") {
next();
} else {
throw new Error(Exhibit._("%expression.error.missingParenEnd", identifier, makePosition()));
}
} else {
throw new Error(Exhibit._("%expression.error.missingParenStart", identifier, makePosition()));
}
} else {
if (token !== null && token.type === Scanner.DELIMITER && token.value === "(") {
next();
args = (token !== null && token.type === Scanner.DELIMITER && token.value === ")") ?
[] :
parseExpressionList();
result = new Exhibit.Expression._FunctionCall(identifier, args);
if (token !== null && token.type === Scanner.DELIMITER && token.value === ")") {
next();
} else {
throw new Error(Exhibit._("%expression.error.missingParenFunction", identifier, makePosition()));
}
} else {
result = parsePath();
result.setRootName(identifier);
}
}
break;
case Scanner.DELIMITER:
if (token.value === "(") {
next();
result = parseExpression();
if (token !== null && token.type === Scanner.DELIMITER && token.value === ")") {
next();
break;
} else {
throw new Error(Exhibit._("%expression.error.missingParen", + makePosition()));
}
} else {
throw new Error(Exhibit._("%expression.error.unexpectedSyntax", token.value, makePosition()));
}
default:
throw new Error(Exhibit._("%expression.error.unexpectedSyntax", token.value, makePosition()));
}
return result;
};
parseTerm = function() {
var term = parseFactor(), operator;
while (token !== null && token.type === Scanner.OPERATOR &&
(token.value === "*" || token.value === "/")) {
operator = token.value;
next();
term = new Exhibit.Expression._Operator(operator, [ term, parseFactor() ]);
}
return term;
};
parseSubExpression = function() {
var subExpression = parseTerm(), operator;
while (token !== null && token.type === Scanner.OPERATOR &&
(token.value === "+" || token.value === "-")) {
operator = token.value;
next();
subExpression = new Exhibit.Expression._Operator(operator, [ subExpression, parseTerm() ]);
}
return subExpression;
};
parseExpression = function() {
var expression = parseSubExpression(), operator;
while (token !== null && token.type === Scanner.OPERATOR &&
(token.value === "=" || token.value === "<>" ||
token.value === "<" || token.value === "<=" ||
token.value === ">" || token.value === ">=")) {
operator = token.value;
next();
expression = new Exhibit.Expression._Operator(operator, [ expression, parseSubExpression() ]);
}
return expression;
};
parseExpressionList = function() {
var expressions = [ parseExpression() ];
while (token !== null && token.type === Scanner.DELIMITER && token.value === ",") {
next();
expressions.push(parseExpression());
}
return expressions;
};
if (several) {
roots = parseExpressionList();
expressions = [];
for (r = 0; r < roots.length; r++) {
expressions.push(new Exhibit.Expression._Impl(roots[r]));
}
return expressions;
} else {
return new Exhibit.Expression._Impl(parseExpression());
}
};
/**
* @class
* @constructor
* @param {String} text
* @param {Number} startIndex
*/
Exhibit.ExpressionScanner = function(text, startIndex) {
this._text = text + " "; // make it easier to parse
this._maxIndex = text.length;
this._index = startIndex;
this.next();
};
/** @constant */
Exhibit.ExpressionScanner.DELIMITER = 0;
/** @constant */
Exhibit.ExpressionScanner.NUMBER = 1;
/** @constant */
Exhibit.ExpressionScanner.STRING = 2;
/** @constant */
Exhibit.ExpressionScanner.IDENTIFIER = 3;
/** @constant */
Exhibit.ExpressionScanner.OPERATOR = 4;
/** @constant */
Exhibit.ExpressionScanner.PATH_OPERATOR = 5;
/**
* @returns {Object}
*/
Exhibit.ExpressionScanner.prototype.token = function() {
return this._token;
};
/**
* @returns {Number}
*/
Exhibit.ExpressionScanner.prototype.index = function() {
return this._index;
};
/**
* @throws Error
*/
Exhibit.ExpressionScanner.prototype.next = function() {
var c1, c2, i, c;
this._token = null;
while (this._index < this._maxIndex &&
" \t\r\n".indexOf(this._text.charAt(this._index)) >= 0) {
this._index++;
}
if (this._index < this._maxIndex) {
c1 = this._text.charAt(this._index);
c2 = this._text.charAt(this._index + 1);
if (".!".indexOf(c1) >= 0) {
if (c2 === "@") {
this._token = {
type: Exhibit.ExpressionScanner.PATH_OPERATOR,
value: c1 + c2,
start: this._index,
end: this._index + 2
};
this._index += 2;
} else {
this._token = {
type: Exhibit.ExpressionScanner.PATH_OPERATOR,
value: c1,
start: this._index,
end: this._index + 1
};
this._index++;
}
} else if ("<>".indexOf(c1) >= 0) {
if ((c2 === "=") || ("<>".indexOf(c2) >= 0 && c1 !== c2)) {
this._token = {
type: Exhibit.ExpressionScanner.OPERATOR,
value: c1 + c2,
start: this._index,
end: this._index + 2
};
this._index += 2;
} else {
this._token = {
type: Exhibit.ExpressionScanner.OPERATOR,
value: c1,
start: this._index,
end: this._index + 1
};
this._index++;
}
} else if ("+-*/=".indexOf(c1) >= 0) {
this._token = {
type: Exhibit.ExpressionScanner.OPERATOR,
value: c1,
start: this._index,
end: this._index + 1
};
this._index++;
} else if ("(),".indexOf(c1) >= 0) {
this._token = {
type: Exhibit.ExpressionScanner.DELIMITER,
value: c1,
start: this._index,
end: this._index + 1
};
this._index++;
} else if ("\"'".indexOf(c1) >= 0) { // quoted strings
i = this._index + 1;
while (i < this._maxIndex) {
if (this._text.charAt(i) === c1 && this._text.charAt(i - 1) !== "\\") {
break;
}
i++;
}
if (i < this._maxIndex) {
this._token = {
type: Exhibit.ExpressionScanner.STRING,
value: this._text.substring(this._index + 1, i).replace(/\\'/g, "'").replace(/\\"/g, '"'),
start: this._index,
end: i + 1
};
this._index = i + 1;
} else {
throw new Error(Exhibit._("%expression.error.unterminatedString", + this._index));
}
} else if (this._isDigit(c1)) { // number
i = this._index;
while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
i++;
}
if (i < this._maxIndex && this._text.charAt(i) === ".") {
i++;
while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
i++;
}
}
this._token = {
type: Exhibit.ExpressionScanner.NUMBER,
value: parseFloat(this._text.substring(this._index, i)),
start: this._index,
end: i
};
this._index = i;
} else { // identifier
i = this._index;
while (i < this._maxIndex) {
c = this._text.charAt(i);
if ("(),.!@ \t".indexOf(c) < 0) {
i++;
} else {
break;
}
}
this._token = {
type: Exhibit.ExpressionScanner.IDENTIFIER,
value: this._text.substring(this._index, i),
start: this._index,
end: i
};
this._index = i;
}
}
};
/**
* @private
* @static
* @param {String} c
* @returns {Boolean}
*/
Exhibit.ExpressionScanner.prototype._isDigit = function(c) {
return "0123456789".indexOf(c) >= 0;
};
/**
* @fileOverview General class for data exporter.
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @param {String} mimeType
* @param {String} label
* @param {Function} wrap Function taking at minimum a string and a database,
* returning a string.
* @param {Function} wrapOne Function taking at minimum a string, a boolean
* for first, and a boolean for last, returning a string.
* @param {Function} exportOne Function taking at minimum an item identifier,
* the item itself, and a hash of properties, returning a string.
* @param {Function} [exportMany] Function taking a set and a database,
* returning a string, which overrides the default exportMany that uses
* the other three functions in conjunction.
*/
Exhibit.Exporter = function(mimeType, label, wrap, wrapOne, exportOne, exportMany) {
this._mimeType = mimeType;
this._label = label;
this._wrap = wrap;
this._wrapOne = wrapOne;
this._exportOne = exportOne;
this._exportMany = exportMany;
this._registered = this.register();
};
/**
* @private
* @constant
*/
Exhibit.Exporter._registryKey = "exporter";
/**
* @private
*/
Exhibit.Exporter._registry = null;
/**
* @static
* @param {Exhibit._Impl} ex
*/
Exhibit.Exporter._registerComponent = function(evt, reg) {
Exhibit.Exporter._registry = reg;
if (!reg.hasRegistry(Exhibit.Exporter._registryKey)) {
reg.createRegistry(Exhibit.Exporter._registryKey);
Exhibit.jQuery(document).trigger("registerExporters.exhibit");
}
};
/**
* @returns {Boolean}
*/
Exhibit.Exporter.prototype.register = function() {
var reg = Exhibit.Exporter._registry;
if (!reg.isRegistered(
Exhibit.Exporter._registryKey,
this._mimeType
)) {
reg.register(
Exhibit.Exporter._registryKey,
this._mimeType,
this
);
return true;
} else {
return false;
}
};
/**
*
*/
Exhibit.Exporter.prototype.dispose = function() {
Exhibit.Exporter._registry.unregister(
Exhibit.Exporter._registryKey,
this._mimeType
);
};
/**
* @returns {Boolean}
*/
Exhibit.Exporter.prototype.isRegistered = function() {
return this._registered;
};
/**
* @returns {String}
*/
Exhibit.Exporter.prototype.getLabel = function() {
return this._label;
};
/**
* @param {String} itemID
* @param {Exhibit.Database} database
* @returns {Object}
*/
Exhibit.Exporter.prototype.exportOneFromDatabase = function(itemID, database) {
var allProperties, fn, i, propertyID, property, values, valueType, item;
fn = function(vt, s) {
if (vt === "item") {
return function(value) {
s.push(database.getObject(value, "label"));
};
} else if (vt === "url") {
return function(value) {
s.push(Exhibit.Persistence.resolveURL(value));
};
}
};
allProperties = database.getAllProperties();
item = {};
for (i = 0; i < allProperties.length; i++) {
propertyID = allProperties[i];
property = database.getProperty(propertyID);
values = database.getObjects(itemID, propertyID);
valueType = property.getValueType();
if (values.size() > 0) {
if (valueType === "item" || valueType === "url") {
strings = [];
values.visit(fn(valueType, strings));
} else {
strings = values.toArray();
}
item[propertyID] = strings;
}
}
return item;
};
/**
* @param {String} itemID
* @param {Exhibit.Database} database
* @returns {String}
*/
Exhibit.Exporter.prototype.exportOne = function(itemID, database) {
return this._wrap(
this._exportOne(
itemID,
this.exportOneFromDatabase(itemID, database),
Exhibit.Exporter._getPropertiesWithValueTypes(database)
),
database
);
};
/**
* @param {Exhibit.Set} set
* @param {Exhibit.Database} database
* @returns {String}
*/
Exhibit.Exporter.prototype.exportMany = function(set, database) {
if (typeof this._exportMany !== "undefined" && typeof this._exportMany === "function") {
this.exportMany = this._exportMany;
return this._exportMany(set, database);
}
var s = "", self = this, count = 0, size = set.size(), props;
props = Exhibit.Exporter._getPropertiesWithValueTypes(database);
set.visit(function(itemID) {
s += self._wrapOne(
self._exportOne(
itemID,
self.exportOneFromDatabase(itemID, database),
props)
,
count === 0,
count++ === size - 1
);
});
return this._wrap(s, database);
};
/**
* @private
* @static
* @param {Exhibit.Database} database
*/
Exhibit.Exporter._getPropertiesWithValueTypes = function(database) {
var properties, i, propertyID, property, valueType, map;
map = {};
properties = database.getAllProperties();
for (i = 0; i < properties.length; i++) {
propertyID = properties[i];
property = database.getProperty(propertyID);
valueType = property.getValueType();
map[propertyID] = { "valueType": valueType,
"uri": property.getURI() };
}
return map;
};
Exhibit.jQuery(document).one(
"registerStaticComponents.exhibit",
Exhibit.Exporter._registerComponent
);
/**
* @fileOverview Instance of Exhibit.Exporter for BibTex.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Exporter.BibTex = {
/**
* @private
* @constant
*/
_excludeProperties: {
"pub-type": true,
"type": true,
"uri": true,
"key": true
},
_mimeType: "application/x-bibtex",
exporter: null
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Exporter.BibTex.wrap = function(s) {
return s;
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Exporter.BibTex.wrapOne = function(s, first, last) {
return s + "\n";
};
/**
* @param {String} itemID
* @param {Object} o
* @returns {String}
*/
Exhibit.Exporter.BibTex.exportOne = function(itemID, o) {
var type, key, prop, s = "";
if (typeof o["pub-type"] !== "undefined") {
type = o["pub-type"];
} else if (typeof o.type !== "undefined") {
type = o.type;
}
if (typeof o.key !== "undefined") {
key = o.key;
} else {
key = itemID;
}
key = key.replace(/[\s,]/g, "-");
s += "@" + type + "{" + key + ",\n";
for (prop in o) {
if (o.hasOwnProperty(prop)) {
if (typeof Exhibit.Exporter.BibTex._excludeProperties[prop] === "undefined") {
s += "\t" + (prop === "label" ?
"title" :
prop) + " = \"";
s += o[prop].join(" and ") + "\",\n";
}
}
}
s += "\torigin = \"" + Exhibit.Persistence.getItemLink(itemID) + "\"\n";
s += "}\n";
return s;
};
/**
* @private
*/
Exhibit.Exporter.BibTex._register = function() {
Exhibit.Exporter.BibTex.exporter = new Exhibit.Exporter(
Exhibit.Exporter.BibTex._mimeType,
Exhibit._("%export.bibtexExporterLabel"),
Exhibit.Exporter.BibTex.wrap,
Exhibit.Exporter.BibTex.wrapOne,
Exhibit.Exporter.BibTex.exportOne
);
};
Exhibit.jQuery(document).one("registerExporters.exhibit",
Exhibit.Exporter.BibTex._register);
/**
* @fileOverview Instance of Exhibit.Exporter for Exhibit JSON.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Exporter.ExhibitJSON = {
_mimeType: "application/json",
exporter: null
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Exporter.ExhibitJSON.wrap = function(s) {
return "{\n" +
" \"items\": [\n" +
s +
" ]\n" +
"}\n";
};
/**
* @param {String} s
* @param {Boolean} first
* @param {Boolean} last
* @returns {String}
*/
Exhibit.Exporter.ExhibitJSON.wrapOne = function(s, first, last) {
return s + (last ? "" : ",") +"\n";
};
/**
* @param {String} itemID
* @param {Object} o
* @returns {String}
* @depends JSON
*/
Exhibit.Exporter.ExhibitJSON.exportOne = function(itemID, o) {
return JSON.stringify(o);
};
/**
* @private
*/
Exhibit.Exporter.ExhibitJSON._register = function() {
Exhibit.Exporter.ExhibitJSON.exporter = new Exhibit.Exporter(
Exhibit.Exporter.ExhibitJSON._mimeType,
Exhibit._("%export.exhibitJsonExporterLabel"),
Exhibit.Exporter.ExhibitJSON.wrap,
Exhibit.Exporter.ExhibitJSON.wrapOne,
Exhibit.Exporter.ExhibitJSON.exportOne
);
};
Exhibit.jQuery(document).one("registerExporters.exhibit",
Exhibit.Exporter.ExhibitJSON._register);
/**
* @fileOverview Instance of Exhibit.Exporter for RDF/XML.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Exporter.RDFXML = {
_mimeType: "application/rdf+xml",
exporter: null
};
/**
* @param {String} s
* @param {Object} prefixToBase
* @returns {String}
*/
Exhibit.Exporter.RDFXML.wrap = function(s, prefixToBase) {
var s2, prefix;
s2 = "\n" +
"\n" + s + "\n\n";
return s2;
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Exporter.RDFXML.wrapOne = function(s, first, last) {
return s + "\n";
};
/**
* @param {String} itemID
* @param {Object} o
* @param {Object} properties
* @param {Object} propertyIDToQualifiedName
* @param {Object} prefixToBase
* @returns {String}
*/
Exhibit.Exporter.RDFXML.exportOne = function(itemID, o, properties, propertyIDToQualifiedName, prefixToBase) {
var s = "", uri, i, propertyID, valueType, propertyString, j, values;
uri = o["uri"];
s += "\n";
for (propertyID in o) {
if (o.hasOwnProperty(propertyID) && typeof properties[propertyID] !== "undefined") {
valueType = properties[propertyID].valueType;
if (typeof propertyIDToQualifiedName[propertyID] !== "undefined") {
qname = propertyIDToQualifiedName[propertyID];
propertyString = qname.prefix + ":" + qname.localName;
} else {
propertyString = properties[propertyID].uri;
}
if (valueType === "item") {
values = o[propertyID];
for (j = 0; j < values.length; j++) {
s += "\t<" + propertyString + " rdf:resource=\"" + values[j] + "\" />\n";
}
} else if (propertyID !== "uri") {
values = o[propertyID];
for (j = 0; j < values.length; j++) {
s += "\t<" + propertyString + ">" + values[j] + "" + propertyString + ">\n";
}
}
}
}
s += "\t" + Exhibit.Persistence.getItemLink(itemID) + "\n";
s += "";
return s;
};
Exhibit.Exporter.RDFXML.exportMany = function(set, database) {
var propertyIDToQualifiedName, prefixToBase, s, self, properties, ps, i, p;
propertyIDToQualifiedName = {};
prefixToBase = {};
s = "";
self = this;
database.getNamespaces(propertyIDToQualifiedName, prefixToBase);
properties = {};
ps = database.getAllProperties();
for (i = 0; i < ps.length; i++) {
p = database.getProperty(ps[i]);
properties[ps[i]] = {}
properties[ps[i]].valueType = p.getValueType();
properties[ps[i]].uri = p.getURI();
}
set.visit(function(itemID) {
s += self._wrapOne(self._exportOne(
itemID,
self.exportOneFromDatabase(itemID, database),
properties,
propertyIDToQualifiedName,
prefixToBase
));
});
return this._wrap(s, prefixToBase);
};
/**
* @private
*/
Exhibit.Exporter.RDFXML._register = function() {
Exhibit.Exporter.RDFXML.exporter = new Exhibit.Exporter(
Exhibit.Exporter.RDFXML._mimeType,
Exhibit._("%export.rdfXmlExporterLabel"),
Exhibit.Exporter.RDFXML.wrap,
Exhibit.Exporter.RDFXML.wrapOne,
Exhibit.Exporter.RDFXML.exportOne,
Exhibit.Exporter.RDFXML.exportMany
);
};
Exhibit.jQuery(document).one("registerExporters.exhibit",
Exhibit.Exporter.RDFXML._register);
/**
* @fileOverview Instance of Exhibit.Exporter for Semantic MediaWiki text.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Exporter.SemanticWikiText = {
_type: "semantic-mediawiki",
exporter: null
};
/**
* @param {String} s
* @param {Exhibit.Database} database
* @returns {String}
*/
Exhibit.Exporter.SemanticWikiText.wrap = function(s, database) {
return s;
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Exporter.SemanticWikiText.wrapOne = function(s, first, last) {
return s + "\n";
};
/**
* @param {String} itemID
* @param {Object} o
* @param {Object} properties
* @returns {String}
*/
Exhibit.Exporter.SemanticWikiText.exportOne = function(itemID, o, properties) {
var uri, prop, valueType, values, i, s = "";
uri = o.uri;
s += uri + "\n";
for (prop in o) {
if (o.hasOwnProperty(prop) && typeof properties[prop] !== "undefined") {
valueType = properties[prop].valueType;
values = o[prop];
if (valueType === "item") {
for (i = 0; i < values.length; i++) {
s += "[[" + prop + "::" + values[i] + "]]\n";
}
} else {
for (i = 0; i < values.length; i++) {
s += "[[" + prop + ":=" + values[i] + "]]\n";
}
}
}
}
s += "[[origin:=" + Exhibit.Persistence.getItemLink(itemID) + "]]\n\n";
return s;
};
/**
* @private
*/
Exhibit.Exporter.SemanticWikiText._register = function() {
Exhibit.Exporter.SemanticWikiText.exporter = new Exhibit.Exporter(
Exhibit.Exporter.SemanticWikiText._type,
Exhibit._("%export.smwExporterLabel"),
Exhibit.Exporter.SemanticWikiText.wrap,
Exhibit.Exporter.SemanticWikiText.wrapOne,
Exhibit.Exporter.SemanticWikiText.exportOne
);
};
Exhibit.jQuery(document).one("registerExporters.exhibit",
Exhibit.Exporter.SemanticWikiText._register);
/**
* @fileOverview Instance of Exhibit.Exporter for tab-separated values.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Exporter.TSV = {
_mimeType: "text/tab-separated-values",
exporter: null
};
/**
* @param {String} s
* @param {Exhibit.Database} database
* @returns {String}
*/
Exhibit.Exporter.TSV.wrap = function(s, database) {
var header, i, allProperties, propertyID, property, valueType;
header = "";
allProperties = database.getAllProperties();
for (i = 0; i < allProperties.length; i++) {
propertyID = allProperties[i];
property = database.getProperty(propertyID);
valueType = property.getValueType();
header += propertyID + ":" + valueType + "\t";
}
return header + "\n" + s;
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Exporter.TSV.wrapOne = function(s, first, last) {
return s + "\n";
};
/**
* @param {String} itemID
* @param {Object} o
* @returns {String}
*/
Exhibit.Exporter.TSV.exportOne = function(itemID, o) {
var prop, s = "";
for (prop in o) {
if (o.hasOwnProperty(prop)) {
s += o[prop].join("; ") + "\t";
}
}
return s;
};
/**
* @private
*/
Exhibit.Exporter.TSV._register = function() {
Exhibit.Exporter.TSV.exporter = new Exhibit.Exporter(
Exhibit.Exporter.TSV._mimeType,
Exhibit._("%export.tsvExporterLabel"),
Exhibit.Exporter.TSV.wrap,
Exhibit.Exporter.TSV.wrapOne,
Exhibit.Exporter.TSV.exportOne
);
};
Exhibit.jQuery(document).one("registerExporters.exhibit",
Exhibit.Exporter.TSV._register);
/**
* @fileOverview General class for data importer.
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @param {String|Array} mimeType
* @param {String} loadType
* @param {String} label
* @param {Function} parse
*/
Exhibit.Importer = function(mimeType, loadType, parse) {
if (typeof mimeType === "string") {
this._mimeTypes = [mimeType];
} else {
this._mimeTypes = mimeType;
}
this._loadType = loadType;
this._parse = parse;
this._registered = this.register();
};
/**
* @private
* @constant
*/
Exhibit.Importer._registryKey = "importer";
/**
* @private
*/
Exhibit.Importer._registry = null;
/**
* @static
* @param {Exhibit._Impl} ex
*/
Exhibit.Importer._registerComponent = function(evt, reg) {
Exhibit.Importer._registry = reg;
if (!reg.hasRegistry(Exhibit.Importer._registryKey)) {
reg.createRegistry(Exhibit.Importer._registryKey);
Exhibit.jQuery(document).trigger("registerImporters.exhibit", reg);
}
};
/**
* @static
* @param {String} mimeType
* @returns {Exhibit.Importer}
*/
Exhibit.Importer.getImporter = function(mimeType) {
return Exhibit.Importer._registry.get(
Exhibit.Importer._registryKey,
mimeType
);
};
/**
* @static
* @string {String} url
* @returns {Boolean}
*/
Exhibit.Importer.checkFileURL = function(url) {
return url.startsWith("file:");
};
/**
* @returns {Boolean}
*/
Exhibit.Importer.prototype.register = function() {
var reg, i, registered;
reg = Exhibit.Importer._registry;
registered = false;
for (i = 0; i < this._mimeTypes.length; i++) {
if (!reg.isRegistered(
Exhibit.Importer._registryKey,
this._mimeTypes[i]
)) {
reg.register(
Exhibit.Importer._registryKey,
this._mimeTypes[i],
this
);
registered = registered || true;
} else {
registered = registered || false;
}
}
return registered;
};
/**
*
*/
Exhibit.Importer.prototype.dispose = function() {
var i;
for (i = 0; i < this._mimeTypes.length; i++) {
Exhibit.Importer._registry.unregister(
Exhibit.Importer._registryKey,
this._mimeTypes[i]
);
}
};
/**
* @returns {Boolean}
*/
Exhibit.Importer.prototype.isRegistered = function() {
return this._registered;
};
/**
* @param {String} type
* @param {Element|String} link
* @param {Exhibit.Database} database
* @param {Function} callback
*/
Exhibit.Importer.prototype.load = function(link, database, callback) {
var resolver, url, postLoad, postParse, self;
url = typeof link === "string" ? link : Exhibit.jQuery(link).attr("href");
url = Exhibit.Persistence.resolveURL(url);
switch(this._loadType) {
case "babel":
resolver = this._loadBabel;
break;
case "jsonp":
resolver = this._loadJSONP;
break;
default:
resolver = this._loadURL;
break;
}
postParse = function(o) {
try {
database.loadData(o, Exhibit.Persistence.getBaseURL(url));
} catch(e) {
Exhibit.Debug.exception(e);
Exhibit.jQuery(document).trigger("error.exhibit", [e, Exhibit._("%import.couldNotLoad", url)]);
} finally {
if (typeof callback === "function") {
callback();
}
}
};
self = this;
postLoad = function(s, textStatus, jqxhr) {
Exhibit.UI.hideBusyIndicator();
try {
self._parse(url, s, postParse);
} catch(e) {
Exhibit.jQuery(document).trigger("error.exhibit", [e, Exhibit._("%import.couldNotParse", url)]);
}
};
Exhibit.UI.showBusyIndicator();
resolver(url, database, postLoad, link);
};
/**
* @param {String} url
* @param {Exhibit.Database} database
* @param {Function} callback
*/
Exhibit.Importer.prototype._loadURL = function(url, database, callback) {
var self = this,
callbackOrig = callback,
fragmentStart = url.indexOf('#'),
fragmentId = url.substring(fragmentStart),
fError = function(jqxhr, textStatus, e) {
var msg;
if (Exhibit.Importer.checkFileURL(url) && jqxhr.status === 404) {
msg = Exhibit._("%import.missingOrFilesystem", url);
} else {
msg = Exhibit._("%import.httpError", url, jqxhr.status);
}
Exhibit.jQuery(document).trigger("error.exhibit", [e, msg]);
};
if ((fragmentStart >= 0) && (fragmentStart < url.length - 1)) {
url = url.substring(0, fragmentStart);
callback = function(data, status, jqxhr) {
var msg,
fragment = Exhibit.jQuery(data).find(fragmentId)
.andSelf()
.filter(fragmentId);
if (fragment.length < 1) {
msg = Exhibit._("%import.missingFragment", url);
Exhibit.jQuery(document).trigger("error.exhibit", [new Error(msg), msg]);
} else {
callbackOrig(fragment.text(), status, jqxhr);
}
};
}
Exhibit.jQuery.ajax({
"url": url,
"dataType": "text",
"error": fError,
"success": callback
});
};
/**
* @param {String} url
* @param {Exhibit.Database} database
* @param {Function} callback
* @param {Element} link
*/
Exhibit.Importer.prototype._loadJSONP = function(url, database, callback, link) {
var charset, convertType, jsonpCallback, converter, fDone, fError, ajaxArgs;
if (typeof link !== "string") {
convertType = Exhibit.getAttribute(link, "converter");
jsonpCallback = Exhibit.getAttribute(link, "jsonp-callback");
charset = Exhibit.getAttribute(link, "charset");
}
converter = Exhibit.Importer._registry.get(
Exhibit.Importer.JSONP._registryKey,
convertType
);
if (converter !== null && typeof converter.preprocessURL !== "undefined") {
url = converter.preprocessURL(url);
}
fDone = function(s, textStatus, jqxhr) {
var json;
if (converter !== null && typeof converter.transformJSON !== "undefined") {
json = converter.transformJSON(s);
} else {
json = s;
}
callback(json, textStatus, jqxhr);
};
fError = function(jqxhr, textStatus, e) {
var msg;
msg = Exhibit._(
"%import.failedAccess",
url,
(typeof jqxhr.status !== "undefined") ? Exhibit._("%import.failedAccessHttpStatus", jqxhr.status) : "");
Exhibit.jQuery(document).trigger("error.exhibit", [e, msg]);
};
ajaxArgs = {
"url": url,
"dataType": "jsonp",
"success": fDone,
"error": fError
};
if (jsonpCallback !== null) {
ajaxArgs.jsonp = false;
ajaxArgs.jsonpCallback = jsonpCallback;
}
if (charset !== null) {
ajaxArgs.scriptCharset = charset;
}
Exhibit.jQuery.ajax(ajaxArgs);
};
/**
* @param {String} url
* @param {Exhibit.Database} database
* @param {Function} callback
* @param {Element} link
*/
Exhibit.Importer.prototype._loadBabel = function(url, database, callback, link) {
var mimeType = null;
if (typeof link !== "string") {
mimeType = Exhibit.jQuery(link).attr("type");
}
this._loadJSONP(
Exhibit.Importer.BabelBased.makeURL(url, mimeType),
database,
callback,
link
);
};
Exhibit.jQuery(document).one(
"registerStaticComponents.exhibit",
Exhibit.Importer._registerComponent
);
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Importer.ExhibitJSON = {
_importer: null
};
/**
* @param {String} url
* @param {String} s
* @param {Function} callback
* @depends JSON
*/
Exhibit.Importer.ExhibitJSON.parse = function(url, s, callback) {
var o = null;
try {
o = JSON.parse(s);
} catch(e) {
Exhibit.UI.showJsonFileValidation(Exhibit._("%general.badJsonMessage", url, e.message), url);
}
if (typeof callback === "function") {
callback(o);
}
};
/**
* @private
*/
Exhibit.Importer.ExhibitJSON._register = function() {
Exhibit.Importer.ExhibitJSON._importer = new Exhibit.Importer(
"application/json",
"get",
Exhibit.Importer.ExhibitJSON.parse
);
};
Exhibit.jQuery(document).one("registerImporters.exhibit",
Exhibit.Importer.ExhibitJSON._register);
/**
* @fileOverview Handle generic JSONP importing.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Importer.JSONP = {
_importer: null,
_registryKey: "jsonpImporter"
};
/**
* @param {String} url
* @param {Object} s
* @param {Function} callback
*/
Exhibit.Importer.JSONP.parse = function(url, s, callback) {
if (typeof callback === "function") {
callback(s);
}
};
/**
* A generic JSONP feed converter to Exhibit JSON.
* Does 90% of the feed conversion for 90% of all (well designed) JSONP feeds.
* Pass the raw json object, an optional index to drill into to get at the
* array of items ("feed.entry", in the case of Google Spreadsheets -- pass
* null, if the array is already the top container, as in a Del.icio.us feed),
* an object mapping the wanted item property name to the properties to pick
* them up from, and an optional similar mapping with conversion callbacks to
* perform on the data value before storing it in the item property. These
* callback functions are invoked with the value, the object it was picked up
* from, its index in the items array, the items array and the feed as a whole
* (for the cases where you need to look up properties from the surroundings).
* Returning the undefined value your converter means the property is not set.
*
* @param {Object} json Result of JSONP call
* @param {String} index
* @param {Object} mapping
* @param {Object} converters
* @returns {Object}
*/
Exhibit.Importer.JSONP.transformJSON = function(json, index, mapping, converters) {
var objects, items, i, object, item, name, index, property;
objects = json;
items = [];
if (typeof index !== "undefined" && index !== null) {
index = index.split(".");
while (index.length > 0) {
objects = objects[index.shift()];
}
}
for (i = 0, object = objects[i]; i < objects.length; i++) {
item = {};
for (name in mapping) {
if (mapping.hasOwnProperty(name)) {
index = mapping[name];
// gracefully handle poisoned Object.prototype
if (!mapping.hasOwnProperty(name) ||
!object.hasOwnProperty(index)) {
continue;
}
property = object[index];
if (typeof converters !== "undefined"
&& converters !== null
&& converters.hasOwnProperty(name)) {
property = converters[name](property, object, i, objects, json);
}
if (typeof property !== "undefined") {
item[name] = property;
}
}
}
items.push(item);
}
return items;
};
/**
* @private
* @static
* @param {jQuery.Event} evt
* @param {Exhibit.Registry} reg
*/
Exhibit.Importer.JSONP._register = function(evt, reg) {
Exhibit.Importer.JSONP._importer = new Exhibit.Importer(
"application/jsonp",
"jsonp",
Exhibit.Importer.JSONP.parse
);
if (!reg.hasRegistry(Exhibit.Importer.JSONP._registryKey)) {
reg.createRegistry(Exhibit.Importer.JSONP._registryKey);
Exhibit.jQuery(document).trigger(
"registerJSONPImporters.exhibit",
reg
);
}
};
Exhibit.jQuery(document).one(
"registerImporters.exhibit",
Exhibit.Importer.JSONP._register
);
/**
* @fileOverview Read Google Docs Spreadsheet JSONP feed.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Importer.JSONP.GoogleSpreadsheet = {
_type: "googleSpreadsheets",
_dateRegex: /^\d{1,2}\/\d{1,2}\/\d{4}$/
};
/**
* @private
* @static
* @param {jQuery.Event} evt
* @param {Exhibit.Registry} reg
*/
Exhibit.Importer.JSONP.GoogleSpreadsheet._register = function(evt, reg) {
if (!reg.isRegistered(
Exhibit.Importer.JSONP._registryKey,
Exhibit.Importer.JSONP.GoogleSpreadsheet._type)) {
reg.register(
Exhibit.Importer.JSONP._registryKey,
Exhibit.Importer.JSONP.GoogleSpreadsheet._type,
Exhibit.Importer.JSONP.GoogleSpreadsheet
);
}
};
/**
* @static
* @param {Object} json
* @param {String} url
* @param {String|Element} link
* @returns {Object}
*/
Exhibit.Importer.JSONP.GoogleSpreadsheet.transformJSON = function(json, url, link) {
var separator, s, items, properties, types, valueTypes, entries, i, entry, id, c, r, cellIndex, getNextRow, propertyRow, propertiesByColumn, cell, fieldSpec, fieldName, fieldDetails, property, d, detail, row, fieldValues, v;
separator = ";";
if (typeof link !== "undefined" && link !== null && typeof link !== "string") {
s = Exhibit.getAttribute(link, "separator");
if (s !== null && s.length > 0) {
separator = s;
}
}
items = [];
properties = {};
types = {};
valueTypes = {
"text" : true,
"number" : true,
"item" : true,
"url" : true,
"boolean" : true,
"date" : true
};
entries = json.feed.entry || []; // if no entries in feed
for (i = 0; i < entries.length; i++) {
entry = entries[i];
id = entry.id.$t;
c = id.lastIndexOf("C");
r = id.lastIndexOf("R");
entries[i] = {
"row": parseInt(id.substring(r + 1, c), 10) - 1,
"col": parseInt(id.substring(c + 1), 10) - 1,
"val": entry.content.$t
};
};
cellIndex = 0;
getNextRow = function() {
var firstEntry, row, nextEntry;
if (cellIndex < entries.length) {
firstEntry = entries[cellIndex++];
row = [ firstEntry ];
while (cellIndex < entries.length) {
nextEntry = entries[cellIndex];
if (nextEntry.row == firstEntry.row) {
row.push(nextEntry);
cellIndex++;
} else {
break;
}
}
return row;
}
return null;
};
propertyRow = getNextRow();
if (propertyRow != null) {
propertiesByColumn = [];
for (i = 0; i < propertyRow.length; i++) {
cell = propertyRow[i];
fieldSpec = cell.val.trim().replace(/^\{/g, "").replace(/\}$/g, "").split(":");
fieldName = fieldSpec[0].trim();
fieldDetails = fieldSpec.length > 1 ? fieldSpec[1].split(",") : [];
property = { single: false };
for (d = 0; d < fieldDetails.length; d++) {
detail = fieldDetails[d].trim();
if (typeof valueTypes[detail] !== null) {
property.valueType = detail;
} else if (detail === "single") {
property.single = true;
}
}
propertiesByColumn[cell.col] = fieldName;
properties[fieldName] = property;
}
row = null;
while ((row = getNextRow()) !== null) {
item = {};
for (i = 0; i < row.length; i++) {
cell = row[i];
fieldName = propertiesByColumn[cell.col];
if (typeof fieldName === "string") {
// ensure round-trip iso8601 date strings through google docs
if (Exhibit.Importer.JSONP.GoogleSpreadsheet._dateRegex.exec(cell.val)) {
cell.val = Exhibit.Database.makeISO8601DateString(new Date(cell.val));
}
item[fieldName] = cell.val;
property = properties[fieldName];
if (!property.single) {
fieldValues = cell.val.split(separator);
for (v = 0; v < fieldValues.length; v++) {
fieldValues[v] = fieldValues[v].trim();
}
item[fieldName] = fieldValues;
} else {
item[fieldName] = cell.val.trim();
}
}
}
items.push(item);
}
}
return {
"types": types,
"properties": properties,
"items": items
};
};
/**
* @param {String} url
* @returns {String}
*/
Exhibit.Importer.JSONP.GoogleSpreadsheet.preprocessURL = function(url) {
return url.replace(/\/list\//g, "/cells/");
};
Exhibit.jQuery(document).one(
"registerJSONPImporters.exhibit",
Exhibit.Importer.JSONP.GoogleSpreadsheet._register
);
/**
* @fileOverview Babel service-based data conversion and import.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Importer.BabelBased = {
_importer: null,
_mimeTypeToReader: {
"application/rdf+xml" : "rdf-xml",
"application/n3" : "n3",
"application/msexcel" : "xls",
"application/x-msexcel" : "xls",
"application/x-ms-excel" : "xls",
"application/vnd.ms-excel" : "xls",
"application/x-excel" : "xls",
"application/xls" : "xls",
"application/x-xls" : "xls",
"application/x-bibtex" : "bibtex"
},
_translatorPrefix: (typeof Exhibit.babelPrefix !== "undefined") ?
Exhibit.babelPrefix + "translator?" :
undefined
};
/**
* @param {String} url
* @param {Object} s
* @param {Function} callback
*/
Exhibit.Importer.BabelBased.parse = function(url, s, callback) {
if (typeof callback === "function") {
callback(s);
}
};
/**
* @param {String} url
* @param {String} mimeType
* @returns {String}
*/
Exhibit.Importer.BabelBased.makeURL = function(url, mimeType) {
if (typeof Exhibit.Importer.BabelBased._translatorPrefix === "undefined") {
return null;
}
var reader, writer;
reader = Exhibit.Importer.BabelBased._defaultReader;
writer = Exhibit.Importer.BabelBased._defaultWriter;
if (typeof Exhibit.Importer.BabelBased._mimeTypeToReader[mimeType] !== "undefined") {
reader = Exhibit.Importer.BabelBased._mimeTypeToReader[mimeType];
}
if (reader === "bibtex") {
writer = "bibtex-exhibit-jsonp";
}
return Exhibit.Importer.BabelBased._translatorPrefix + [
"reader=" + reader,
"writer=" + writer,
"url=" + encodeURIComponent(url)
].join("&");
};
/**
* @private
* @static
* @param {jQuery.Event} evt
* @param {Exhibit.Registry} reg
*/
Exhibit.Importer.BabelBased._register = function(evt, reg) {
if (typeof Exhibit.Importer.BabelBased._translatorPrefix === "undefined") {
return;
}
var types, type;
types = [];
for (type in Exhibit.Importer.BabelBased._mimeTypeToReader) {
if (Exhibit.Importer.BabelBased._mimeTypeToReader.hasOwnProperty(type)) {
types.push(type);
}
}
Exhibit.Importer.BabelBased._importer = new Exhibit.Importer(
types,
"babel",
Exhibit.Importer.BabelBased.parse
);
};
Exhibit.jQuery(document).one(
"registerImporters.exhibit",
Exhibit.Importer.BabelBased._register
);
/**
* @fileOverview Provides a holding place for Exhibit-wide controls.
* @author Ryan Lee
*/
/**
* @class
* @constructor
* @param {Element} elmt
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.ControlPanel = function(elmt, uiContext) {
this._uiContext = uiContext;
this._widgets = [];
this._div = elmt;
this._settings = {};
this._hovering = false;
this._id = null;
this._registered = false;
this._childOpen = false;
this._createdAsDefault = false;
};
/**
* @static
* @private
*/
Exhibit.ControlPanel._settingSpecs = {
"showBookmark": { type: "boolean", defaultValue: true },
"developerMode": { type: "boolean", defaultvalue: false },
"hoverReveal": { type: "boolean", defaultValue: false }
};
/**
* @private
* @constant
*/
Exhibit.ControlPanel._registryKey = "controlPanel";
/**
* @static
* @param {Object} configuration
* @param {Element} elmt
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.ControlPanel}
*/
Exhibit.ControlPanel.create = function(configuration, elmt, uiContext) {
var panel = new Exhibit.ControlPanel(
elmt,
Exhibit.UIContext.create(configuration, uiContext)
);
Exhibit.ControlPanel._configure(panel, configuration);
panel._setIdentifier();
panel.register();
panel._initializeUI();
return panel;
};
/**
* @static
* @param {Element} div
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.Coordinator}
*/
Exhibit.ControlPanel.createFromDOM = function(configElmt, containerElmt, uiContext) {
var configuration, panel;
configuration = Exhibit.getConfigurationFromDOM(configElmt);
panel = new Exhibit.ControlPanel(
(typeof containerElmt !== "undefined" && containerElmt !== null) ?
containerElmt :
configElmt,
Exhibit.UIContext.createFromDOM(configElmt, uiContext)
);
Exhibit.ControlPanel._configureFromDOM(panel, configuration);
panel._setIdentifier();
panel.register();
panel._initializeUI();
return panel;
};
/**
* @static
* @private
* @param {Exhibit.ControlPanel} panel
* @param {Object} configuration
*/
Exhibit.ControlPanel._configure = function(panel, configuration) {
Exhibit.SettingsUtilities.collectSettings(
configuration,
Exhibit.ControlPanel._settingSpecs,
panel._settings
);
};
/**
* @static
* @private
* @param {Exhibit.ControlPanel} panel
* @param {Object} configuration
*/
Exhibit.ControlPanel._configureFromDOM = function(panel, configuration) {
Exhibit.SettingsUtilities.collectSettingsFromDOM(
panel._div,
Exhibit.ControlPanel._settingSpecs,
panel._settings
);
};
/**
* @private
* @param {jQuery.Event} evt
* @param {Exhibit.Registry} reg
*/
Exhibit.ControlPanel.registerComponent = function(evt, reg) {
if (!reg.hasRegistry(Exhibit.ControlPanel._registryKey)) {
reg.createRegistry(Exhibit.ControlPanel._registryKey);
}
};
/**
* @static
* @param {jQuery.Event} evt
* @param {jQuery} elmt
*/
Exhibit.ControlPanel.mouseOutsideElmt = function(evt, elmt) {
var coords = Exhibit.jQuery(elmt).offset();
return (
evt.pageX < coords.left
|| evt.pageX > coords.left + Exhibit.jQuery(elmt).outerWidth()
|| evt.pageY < coords.top
|| evt.pageY > coords.top + Exhibit.jQuery(elmt).outerHeight()
);
};
/**
* @private
*/
Exhibit.ControlPanel.prototype._initializeUI = function() {
var widget, self;
self = this;
if (this._settings.hoverReveal) {
Exhibit.jQuery(this.getContainer()).fadeTo(1, 0);
Exhibit.jQuery(this.getContainer()).bind("mouseover", function(evt) {
self._hovering = true;
Exhibit.jQuery(this).fadeTo("fast", 1);
});
Exhibit.jQuery(document.body).bind("mousemove", function(evt) {
if (self._hovering
&& !self._childOpen
&& Exhibit.ControlPanel.mouseOutsideElmt(
evt,
self.getContainer()
)) {
self._hovering = false;
Exhibit.jQuery(self.getContainer()).fadeTo("fast", 0);
}
});
}
if (this._settings.showBookmark) {
widget = Exhibit.BookmarkWidget.create(
{ },
this.getContainer(),
this._uiContext
);
this.addWidget(widget, true);
}
if (this._settings.developerMode) {
widget = Exhibit.ResetHistoryWidget.create(
{ },
this.getContainer(),
this._uiContext
);
this.addWidget(widget, true);
}
Exhibit.jQuery(this.getContainer()).addClass("exhibit-controlPanel");
};
/**
*
*/
Exhibit.ControlPanel.prototype._setIdentifier = function() {
this._id = Exhibit.jQuery(this._div).attr("id");
if (typeof this._id === "undefined" || this._id === null) {
this._id = Exhibit.ControlPanel._registryKey
+ "-"
+ this._uiContext.getCollection().getID()
+ "-"
+ this._uiContext.getMain().getRegistry().generateIdentifier(
Exhibit.ControlPanel._registryKey
);
}
};
/**
*
*/
Exhibit.ControlPanel.prototype.register = function() {
if (!this._uiContext.getMain().getRegistry().isRegistered(
Exhibit.ControlPanel._registryKey,
this.getID()
)) {
this._uiContext.getMain().getRegistry().register(
Exhibit.ControlPanel._registryKey,
this.getID(),
this
);
this._registered = true;
}
};
/**
*
*/
Exhibit.ControlPanel.prototype.unregister = function() {
this._uiContext.getMain().getRegistry().unregister(
Exhibit.ControlPanel._registryKey,
this.getID()
);
this._registered = false;
};
/**
* @returns {jQuery}
*/
Exhibit.ControlPanel.prototype.getContainer = function() {
return Exhibit.jQuery(this._div);
};
/**
* @returns {String}
*/
Exhibit.ControlPanel.prototype.getID = function() {
return this._id;
};
/**
*
*/
Exhibit.ControlPanel.prototype.childOpened = function() {
this._childOpen = true;
};
/**
*
*/
Exhibit.ControlPanel.prototype.childClosed = function() {
this._childOpen = false;
};
/**
*
*/
Exhibit.ControlPanel.prototype.setCreatedAsDefault = function() {
var self;
self = this;
this._createdAsDefault = true;
Exhibit.jQuery(this._div).hide();
Exhibit.jQuery(document).one("exhibitConfigured.exhibit", function(evt, ex) {
var keys, component, i, place;
component = Exhibit.ViewPanel._registryKey;
keys = ex.getRegistry().getKeys(component);
if (keys.length === 0) {
component = Exhibit.View._registryKey;
keys = ex.getRegistry().getKeys(component);
}
if (keys.length !== 0) {
// Places default control panel before the "first" one - ideally,
// this is the first of its kind presentationally to the user,
// but it may not be. If not, authors should be placing it
// themselves.
place = ex.getRegistry().get(component, keys[0]);
if (typeof place._div !== "undefined") {
Exhibit.jQuery(place._div).before(self._div);
Exhibit.jQuery(self._div).show();
}
}
});
};
/**
* @returns {Boolean}
*/
Exhibit.ControlPanel.prototype.createdAsDefault = function() {
return this._createdAsDefault;
};
/**
*
*/
Exhibit.ControlPanel.prototype.dispose = function() {
this.unregister();
this._uiContext.dispose();
this._uiContext = null;
this._div = null;
this._widgets = null;
this._settings = null;
};
/**
* @param {Object} widget
* @param {Boolean} initial
*/
Exhibit.ControlPanel.prototype.addWidget = function(widget, initial) {
this._widgets.push(widget);
if (typeof widget.setControlPanel === "function") {
widget.setControlPanel(this);
}
if (typeof initial === "undefined" || !initial) {
this.reconstruct();
}
};
/**
* @param {Object} widget
* @returns {Object}
*/
Exhibit.ControlPanel.prototype.removeWidget = function(widget) {
var i, removed;
removed = null;
for (i = 0; i < this._widgets.length; i++) {
if (this._widgets[i] === widget) {
removed = this._widgets.splice(i, 1);
break;
}
}
this.reconstruct();
return removed;
};
/**
*
*/
Exhibit.ControlPanel.prototype.reconstruct = function() {
var i;
Exhibit.jQuery(this._div).empty();
for (i = 0; i < this._widgets.length; i++) {
if (typeof this._widgets[i].reconstruct === "function") {
this._widgets[i].reconstruct(this);
}
}
};
Exhibit.jQuery(document).one(
"registerComponents.exhibit",
Exhibit.ControlPanel.registerComponent
);
/**
* @fileOverview Helps bind and trigger events between views.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @constructor
* @class
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Coordinator = function(uiContext) {
this._uiContext = uiContext;
this._listeners = [];
};
/**
* @static
* @param {Object} configuration
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.Coordinator}
*/
Exhibit.Coordinator.create = function(configuration, uiContext) {
return new Exhibit.Coordinator(uiContext);
};
/**
* @static
* @param {Element} div
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.Coordinator}
*/
Exhibit.Coordinator.createFromDOM = function(div, uiContext) {
return new Exhibit.Coordinator(Exhibit.UIContext.createFromDOM(div, uiContext, false));
};
/**
*
*/
Exhibit.Coordinator.prototype.dispose = function() {
this._uiContext.dispose();
this._uiContext = null;
};
/**
* @param {Function} callback
* @returns {Exhibit.Coordinator._Listener}
*/
Exhibit.Coordinator.prototype.addListener = function(callback) {
var listener = new Exhibit.Coordinator._Listener(this, callback);
this._listeners.push(listener);
return listener;
};
/**
* @param {Exhibit.Coordinator._Listener} listener
*/
Exhibit.Coordinator.prototype._removeListener = function(listener) {
var i;
for (i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
};
/**
* @param {Exhibit.Coordinator._Listener} listener
* @param {Object} o
*/
Exhibit.Coordinator.prototype._fire = function(listener, o) {
var i, listener2;
for (i = 0; i < this._listeners.length; i++) {
listener2 = this._listeners[i];
if (listener2 !== listener) {
listener2._callback(o);
}
}
};
/**
* @constructor
* @class
* @param {Exhibit.Coordinator} coordinator
* @param {Function} callback
*/
Exhibit.Coordinator._Listener = function(coordinator, callback) {
this._coordinator = coordinator;
this._callback = callback;
};
/**
*/
Exhibit.Coordinator._Listener.prototype.dispose = function() {
this._coordinator._removeListener(this);
};
/**
* @param {Object} o
*/
Exhibit.Coordinator._Listener.prototype.fire = function(o) {
this._coordinator._fire(this, o);
};
/**
* @fileOverview Format parsing
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.FormatParser = {};
/**
* @constant
*/
Exhibit.FormatParser._valueTypes = {
"list" : true,
"number" : true,
"date" : true,
"item" : true,
"text" : true,
"url" : true,
"image" : true,
"currency" : true
};
/**
* @static
* @param {Exhibit.UIContext} uiContext
* @param {String} s
* @param {Number} startIndex
* @param {Object} results
* @returns {Number}
*/
Exhibit.FormatParser.parse = function(uiContext, s, startIndex, results) {
startIndex = startIndex || 0;
results = results || {};
var scanner = new Exhibit.FormatScanner(s, startIndex);
try {
return Exhibit.FormatParser._internalParse(uiContext, scanner, results, false);
} finally {
results.index = scanner.token() !== null ? scanner.token().start : scanner.index();
}
};
/**
* @param {Exhibit.UIContext} uiContext
* @param {String} s
* @param {Number} startIndex
* @param {Object} results
* @returns {Number}
*/
Exhibit.FormatParser.parseSeveral = function(uiContext, s, startIndex, results) {
startIndex = startIndex || 0;
results = results || {};
var scanner = new Exhibit.FormatScanner(s, startIndex);
try {
return Exhibit.FormatParser._internalParse(uiContext, scanner, results, true);
} finally {
results.index = scanner.token() !== null ? scanner.token().start : scanner.index();
}
};
/**
* @param {Exhibit.UIContext} uiContext
* @param {Exhibit.FormatScanner} scanner
* @param {Object} results
* @param {Boolean} several
* @returns {}
*/
Exhibit.FormatParser._internalParse = function(uiContext, scanner, results, several) {
var Scanner, token, next, makePosition, enterSetting, checkKeywords, parseNumber, parseInteger, parseNonnegativeInteger, parseString, parseURL, parseExpression, parseExpressionOrString, parseChoices, parseFlags, parseSetting, parseSettingList, parseRule, parseRuleList;
Scanner = Exhibit.FormatScanner;
token = scanner.token();
next = function() { scanner.next(); token = scanner.token(); };
makePosition = function() {
return token !== null ?
token.start :
scanner.index();
};
enterSetting = function(valueType, settingName, value) {
uiContext.putSetting("format/" + valueType + "/" + settingName, value);
};
checkKeywords = function(valueType, settingName, keywords) {
if (token !== null &&
token.type !== Scanner.IDENTIFIER &&
typeof keywords[token.value] !== "undefined") {
enterSetting(valueType, settingName, keywords[token.value]);
next();
return false;
}
return true;
};
parseNumber = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || token.type !== Scanner.NUMBER) {
throw new Error(Exhibit._("%format.error.missingNumber", makePosition()));
}
enterSetting(valueType, settingName, token.value);
next();
}
};
parseInteger = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || token.type !== Scanner.NUMBER) {
throw new Error(Exhibit._("%format.error.missingInteger", makePosition()));
}
enterSetting(valueType, settingName, Math.round(token.value));
next();
}
};
parseNonnegativeInteger = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || token.type !== Scanner.NUMBER || token.value < 0) {
throw new Error(Exhibit._("%format.error.missingNonNegativeInteger", makePosition()));
}
enterSetting(valueType, settingName, Math.round(token.value));
next();
}
};
parseString = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || token.type !== Scanner.STRING) {
throw new Error(Exhibit._("%format.error.missingString", makePosition()));
}
enterSetting(valueType, settingName, token.value);
next();
}
};
parseURL = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || token.type !== Scanner.URL) {
throw new Error(Exhibit._("%format.error.missingURL", makePosition()));
}
enterSetting(valueType, settingName, token.value);
next();
}
};
parseExpression = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || token.type !== Scanner.EXPRESSION) {
throw new Error(Exhibit._("%format.error.missingExpression", makePosition()));
}
enterSetting(valueType, settingName, token.value);
next();
}
};
parseExpressionOrString = function(valueType, settingName, keywords) {
if (checkKeywords(valueType, settingName, keywords)) {
if (typeof token === "undefined" || token === null || (token.type !== Scanner.EXPRESSION && token.type !== Scanner.STRING)) {
throw new Error(Exhibit._("%format.error.missingExpressionOrString", makePosition()));
}
enterSetting(valueType, settingName, token.value);
next();
}
};
parseChoices = function(valueType, settingName, choices) {
var i;
if (typeof token === "undefined" || token === null || token.type !== Scanner.IDENTIFIER) {
throw new Error(Exhibit._("%format.error.missingOption", makePosition()));
}
for (i = 0; i < choices.length; i++) {
if (token.value === choices[i]) {
enterSetting(valueType, settingName, token.value);
next();
return;
}
}
throw new Error(Exhibit._("%format.error.unsupportedOption", token.value, settingName, valueType, makePosition()));
};
parseFlags = function(valueType, settingName, flags, counterFlags) {
var i, flagSet, counterFlagSet;
while (token !== null && token.type === Scanner.IDENTIFIER) {
flagSet = false;
counterFlagSet = false;
for (i = 0; i < flags.length && !flagSet; i++) {
if (token.value === flags[i]) {
enterSetting(valueType, settingName + "/" + token.value, true);
next();
flagSet = true;
}
}
if (!flagSet && typeof counterFlags[token.value] !== "undefined") {
enterSetting(valueType, settingName + "/" + counterFlags[token.value], false);
next();
counterFlagSet = true;
}
if (!counterFlagSet) {
throw new Error(Exhibit._("%format.error.unsupportedFlag", token.value, settingName, valueType, makePosition()));
}
}
};
parseSetting = function(valueType, settingName) {
switch (valueType) {
case "number":
switch (settingName) {
case "decimal-digits":
parseNonnegativeInteger(valueType, settingName, { "default": -1 });
return;
}
break;
case "date":
switch (settingName) {
case "time-zone":
parseNumber(valueType, settingName, { "default" : null });
return;
case "show":
parseChoices(valueType, settingName, [ "date", "time", "date-time" ]);
return;
case "mode":
parseChoices(valueType, settingName, [ "short", "medium", "long", "full" ]);
enterSetting(valueType, "template", null); // mode and template are exclusive
return;
case "template":
parseString(valueType, settingName, {});
enterSetting(valueType, "mode", null); // mode and template are exclusive
return;
}
break;
case "boolean":
break;
case "text":
switch (settingName) {
case "max-length":
parseInteger(valueType, settingName, { "none" : 0 });
return;
}
break;
case "image":
switch (settingName) {
case "tooltip":
parseExpressionOrString(valueType, settingName, { "none" : null });
return;
case "max-width":
case "max-height":
parseInteger(valueType, settingName, { "none" : -1 });
return;
}
break;
case "url":
switch (settingName) {
case "target":
parseString(valueType, settingName, { "none" : null });
return;
case "external-icon":
parseURL(valueType, settingName, { "none" : null });
return;
}
break;
case "item":
switch (settingName) {
case "title":
parseExpression(valueType, settingName, { "default" : null });
return;
}
break;
case "currency":
switch (settingName) {
case "negative-format":
parseFlags(valueType, settingName,
[ "red", "parentheses", "signed" ],
{ "unsigned" : "signed", "no-parenthesis" : "parentheses", "black" : "red" }
);
return;
case "symbol":
parseString(valueType, settingName, { "default" : "$", "none" : null });
return;
case "symbol-placement":
parseChoices(valueType, settingName, [ "first", "last", "after-sign" ]);
return;
case "decimal-digits":
parseNonnegativeInteger(valueType, settingName, { "default" : -1 });
return;
}
break;
case "list":
switch (settingName) {
case "separator":
case "last-separator":
case "pair-separator":
case "empty-text":
parseString(valueType, settingName, {});
return;
}
break;
}
throw new Error(Exhibit._("%format.error.unsupportedSetting", settingName, valueType, makePosition()));
};
parseSettingList = function(valueType) {
while (token !== null && token.type === Scanner.IDENTIFIER) {
var settingName = token.value;
next();
if (typeof token === "undefined" || token === null || token.type !== Scanner.DELIMITER || token.value !== ":") {
throw new Error(Exhibit._("%format.error.missingColon", makePosition()));
}
next();
parseSetting(valueType, settingName);
if (typeof token === "undefined" || token === null || token.type !== Scanner.DELIMITER || token.value !== ";") {
break;
} else {
next();
}
}
};
parseRule = function() {
if (typeof token === "undefined" || token === null || token.type !== Scanner.IDENTIFIER) {
throw new Error(Exhibit._("%format.error.missingValueType", makePosition()));
}
var valueType = token.value;
if (typeof Exhibit.FormatParser._valueTypes[valueType] === "undefined") {
throw new Error(Exhibit._("%format.error.unsupportedValueType", valueType, makePosition()));
}
next();
if (token !== null && token.type === Scanner.DELIMITER && token.value === "{") {
next();
parseSettingList(valueType);
if (typeof token === "undefined" || token === null || token.type !== Scanner.DELIMITER || token.value !== "}") {
throw new Error(Exhibit._("%format.error.missingBrace", makePosition()));
}
next();
}
return valueType;
};
parseRuleList = function() {
var valueType = "text";
while (token !== null && token.type === Scanner.IDENTIFIER) {
valueType = parseRule();
}
return valueType;
};
if (several) {
return parseRuleList();
} else {
return parseRule();
}
};
/**
* @class
* @constructor
* @param {String} text
* @param {Number} startIndex
*/
Exhibit.FormatScanner = function(text, startIndex) {
this._text = text + " "; // make it easier to parse
this._maxIndex = text.length;
this._index = startIndex;
this.next();
};
/**
* @constant
*/
Exhibit.FormatScanner.DELIMITER = 0;
/**
* @constant
*/
Exhibit.FormatScanner.NUMBER = 1;
/**
* @constant
*/
Exhibit.FormatScanner.STRING = 2;
/**
* @constant
*/
Exhibit.FormatScanner.IDENTIFIER = 3;
/**
* @constant
*/
Exhibit.FormatScanner.URL = 4;
/**
* @constant
*/
Exhibit.FormatScanner.EXPRESSION = 5;
/**
* @constant
*/
Exhibit.FormatScanner.COLOR = 6;
/**
* @returns {Object}
*/
Exhibit.FormatScanner.prototype.token = function() {
return this._token;
};
/**
* @returns {Number}
*/
Exhibit.FormatScanner.prototype.index = function() {
return this._index;
};
/**
*
*/
Exhibit.FormatScanner.prototype.next = function() {
this._token = null;
var self, skipSpaces, i, c1, c2, identifier, openParen, closeParen, j, o, expression;
self = this;
skipSpaces = function(x) {
while (x < self._maxIndex &&
" \t\r\n".indexOf(self._text.charAt(x)) >= 0) {
x++;
}
return x;
};
this._index = skipSpaces(this._index);
if (this._index < this._maxIndex) {
c1 = this._text.charAt(this._index);
c2 = this._text.charAt(this._index + 1);
if ("{}(),:;".indexOf(c1) >= 0) {
this._token = {
type: Exhibit.FormatScanner.DELIMITER,
value: c1,
start: this._index,
end: this._index + 1
};
this._index++;
} else if ("\"'".indexOf(c1) >= 0) { // quoted strings
i = this._index + 1;
while (i < this._maxIndex) {
if (this._text.charAt(i) === c1 && this._text.charAt(i - 1) !== "\\") {
break;
}
i++;
}
if (i < this._maxIndex) {
this._token = {
type: Exhibit.FormatScanner.STRING,
value: this._text.substring(this._index + 1, i).replace(/\\'/g, "'").replace(/\\"/g, '"'),
start: this._index,
end: i + 1
};
this._index = i + 1;
} else {
throw new Error(Exhibit._("%format.error.unterminatedString", this._index));
}
} else if (c1 === "#") { // color
i = this._index + 1;
while (i < this._maxIndex && this._isHexDigit(this._text.charAt(i))) {
i++;
}
this._token = {
type: Exhibit.FormatScanner.COLOR,
value: this._text.substring(this._index, i),
start: this._index,
end: i
};
this._index = i;
} else if (this._isDigit(c1)) { // number
i = this._index;
while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
i++;
}
if (i < this._maxIndex && this._text.charAt(i) === ".") {
i++;
while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
i++;
}
}
this._token = {
type: Exhibit.FormatScanner.NUMBER,
value: parseFloat(this._text.substring(this._index, i)),
start: this._index,
end: i
};
this._index = i;
} else { // identifier
i = this._index;
while (i < this._maxIndex) {
j = this._text.substr(i).search(/\W/);
if (j > 0) {
i += j;
} else if ("-".indexOf(this._text.charAt(i)) >= 0) {
i++;
} else {
break;
}
}
identifier = this._text.substring(this._index, i);
if (identifier === "url") {
openParen = skipSpaces(i);
if (this._text.charAt(openParen) === "(") {
closeParen = this._text.indexOf(")", openParen);
if (closeParen > 0) {
this._token = {
type: Exhibit.FormatScanner.URL,
value: this._text.substring(openParen + 1, closeParen),
start: this._index,
end: closeParen + 1
};
this._index = closeParen + 1;
} else {
throw new Error(Exhibit._("%format.error.missingCloseURL", this._index));
}
}
} else if (identifier === "expression") {
openParen = skipSpaces(i);
if (this._text.charAt(openParen) === "(") {
o = {};
expression = Exhibit.ExpressionParser.parse(this._text, openParen + 1, o);
closeParen = skipSpaces(o.index);
if (this._text.charAt(closeParen) === ")") {
this._token = {
type: Exhibit.FormatScanner.EXPRESSION,
value: expression,
start: this._index,
end: closeParen + 1
};
this._index = closeParen + 1;
} else {
throw new Error("Missing ) to close expression at " + o.index);
}
}
} else {
this._token = {
type: Exhibit.FormatScanner.IDENTIFIER,
value: identifier,
start: this._index,
end: i
};
this._index = i;
}
}
}
};
/**
* @param {String} c
* @returns {Boolean}
*/
Exhibit.FormatScanner.prototype._isDigit = function(c) {
return "0123456789".indexOf(c) >= 0;
};
/**
* @param {String} c
* @returns {Boolean}
*/
Exhibit.FormatScanner.prototype._isHexDigit = function(c) {
return "0123456789abcdefABCDEF".indexOf(c) >= 0;
};
/**
* @fileOverview
* @author David Huynh
* @author Ryan Lee
*/
/**
* @namespace
*/
Exhibit.Formatter = {
/**
* @constant
*/
"_lessThanRegex": //g
};
/**
* @static
* @param {jQuery} parentElmt
* @param {Number} count
* @param {Exhibit.UIContext} uiContext
* @returns {Function}
*/
Exhibit.Formatter.createListDelimiter = function(parentElmt, count, uiContext) {
var separator, lastSeparator, pairSeparator, f;
separator = uiContext.getSetting("format/list/separator");
lastSeparator = uiContext.getSetting("format/list/last-separator");
pairSeparator = uiContext.getSetting("format/list/pair-separator");
if (typeof separator !== "string") {
separator = Exhibit._("%formatter.listSeparator");
}
if (typeof lastSeparator !== "string") {
lastSeparator = Exhibit._("%formatter.listLastSeparator");
}
if (typeof pairSeparator !== "string") {
pairSeparator = Exhibit._("%formatter.listPairSeparator");
}
f = function() {
if (f.index > 0 && f.index < count) {
if (count > 2) {
Exhibit.jQuery(parentElmt).append(document.createTextNode(
(f.index === count - 1) ? lastSeparator : separator));
} else {
Exhibit.jQuery(parentElmt).append(document.createTextNode(pairSeparator));
}
}
f.index++;
};
f.index = 0;
return f;
};
/**
* @param {String} s
* @returns {String}
*/
Exhibit.Formatter.encodeAngleBrackets = function(s) {
return s.replace(Exhibit.Formatter._lessThanRegex, "<").
replace(Exhibit.Formatter._greaterThanRegex, ">");
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._ListFormatter = function(uiContext) {
this._uiContext = uiContext;
this._separator = uiContext.getSetting("format/list/separator");
this._lastSeparator = uiContext.getSetting("format/list/last-separator");
this._pairSeparator = uiContext.getSetting("format/list/pair-separator");
this._emptyText = uiContext.getSetting("format/list/empty-text");
if (typeof this._separator !== "string") {
this._separator = Exhibit._("%formatter.listSeparator");
}
if (typeof this._lastSeparator !== "string") {
this._lastSeparator = Exhibit._("%formatter.listLastSeparator");
}
if (typeof this._pairSeparator !== "string") {
this._pairSeparator = Exhibit._("%formatter.listPairSeparator");
}
};
/**
* @param {Exhibit.Set} values
* @param {Number} count
* @param {String} valueType
* @param {Function} appender
*/
Exhibit.Formatter._ListFormatter.prototype.formatList = function(values, count, valueType, appender) {
var uiContext, self, index;
uiContext = this._uiContext;
self = this;
if (count === 0) {
if (typeof this._emptyText !== "undefined" && this._emptyText !== null && this._emptyText.length > 0) {
appender(document.createTextNode(this._emptyText));
}
} else if (count === 1) {
values.visit(function(v) {
uiContext.format(v, valueType, appender);
});
} else {
index = 0;
if (count === 2) {
values.visit(function(v) {
uiContext.format(v, valueType, appender);
index++;
if (index === 1) {
appender(document.createTextNode(self._pairSeparator));
}
});
} else {
values.visit(function(v) {
uiContext.format(v, valueType, appender);
index++;
if (index < count) {
appender(document.createTextNode(
(index === count - 1) ? self._lastSeparator : self._separator));
}
});
}
}
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._TextFormatter = function(uiContext) {
this._maxLength = uiContext.getSetting("format/text/max-length");
if (typeof this._maxLength === "number") {
this._maxLength = Math.max(3, Math.round(this._maxLength));
} else {
this._maxLength = 0; // zero means no limit
}
};
/**
* @param {String} value
* @param {Function} appender
*/
Exhibit.Formatter._TextFormatter.prototype.format = function(value, appender) {
var span = Exhibit.jQuery("").html(this.formatText(value));
appender(span);
};
/**
* @param {String} value
* @returns {String}
*/
Exhibit.Formatter._TextFormatter.prototype.formatText = function(value) {
if (Exhibit.params.safe) {
value = Exhibit.Formatter.encodeAngleBrackets(value);
}
if (this._maxLength === 0 || value.length <= this._maxLength) {
return value;
} else {
return Exhibit._("%formatter.textEllipsis", value.substr(0, this._maxLength));
}
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._BooleanFormatter = function(uiContext) {
};
/**
* @param {String|Boolean} value
* @param {Function} appender
*/
Exhibit.Formatter._BooleanFormatter.prototype.format = function(value, appender) {
var span = Exhibit.jQuery("").html(this.formatText(value));
appender(span);
};
/**
* @param {String|Boolean} value
* @returns {String}
*/
Exhibit.Formatter._BooleanFormatter.prototype.formatText = function(value) {
return (typeof value === "boolean" ? value : (typeof value === "string" ? (value === "true") : false)) ?
Exhibit._("%formatter.booleanTrue") : Exhibit._("%formatter.booleanFalse");
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._NumberFormatter = function(uiContext) {
this._decimalDigits = uiContext.getSetting("format/number/decimal-digits");
if (typeof this._decimalDigits === "number") {
this._decimalDigits = Math.max(-1, Math.round(this._decimalDigits));
} else {
this._decimalDigits = -1; // -1 means no limit
}
};
/**
* @param {Number} value
* @param {Function} appender
*/
Exhibit.Formatter._NumberFormatter.prototype.format = function(value, appender) {
appender(document.createTextNode(this.formatText(value)));
};
/**
* @param {Number} value
* @returns {String}
*/
Exhibit.Formatter._NumberFormatter.prototype.formatText = function(value) {
if (this._decimalDigits === -1) {
return value.toString();
} else {
return value.toFixed(this._decimalDigits);
}
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._ImageFormatter = function(uiContext) {
this._uiContext = uiContext;
this._maxWidth = uiContext.getSetting("format/image/max-width");
if (typeof this._maxWidth === "number") {
this._maxWidth = Math.max(-1, Math.round(this._maxWidth));
} else {
this._maxWidth = -1; // -1 means no limit
}
this._maxHeight = uiContext.getSetting("format/image/max-height");
if (typeof this._maxHeight === "number") {
this._maxHeight = Math.max(-1, Math.round(this._maxHeight));
} else {
this._maxHeight = -1; // -1 means no limit
}
this._tooltip = uiContext.getSetting("format/image/tooltip");
};
/**
* @param {String} value
* @param {Function} appender
*/
Exhibit.Formatter._ImageFormatter.prototype.format = function(value, appender) {
if (Exhibit.params.safe) {
value = value.trim().startsWith("javascript:") ? "" : value;
}
var img = Exhibit.jQuery("").attr("src", value);
if (this._tooltip !== null) {
if (typeof this._tooltip === "string") {
img.attr("title", this._tootlip);
} else {
img.attr("title",
this._tooltip.evaluateSingleOnItem(
this._uiContext.getSetting("itemID"),
this._uiContext.getDatabase()
).value);
}
}
appender(img);
};
/**
* @param {String} value
* @returns {String}
*/
Exhibit.Formatter._ImageFormatter.prototype.formatText = function(value) {
return value;
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._URLFormatter = function(uiContext) {
this._target = uiContext.getSetting("format/url/target");
this._externalIcon = uiContext.getSetting("format/url/external-icon");
};
/**
* @param {String} value
* @param {Function} appender
*/
Exhibit.Formatter._URLFormatter.prototype.format = function(value, appender) {
var a = Exhibit.jQuery("a").attr("href", value).html(value);
if (this._target !== null) {
a.attr("target", this._target);
}
// Unused
//if (this._externalIcon !== null) {
//
//}
appender(a);
};
/**
* @param {String} value
* @returns {String}
*/
Exhibit.Formatter._URLFormatter.prototype.formatText = function(value) {
if (Exhibit.params.safe) {
value = value.trim().startsWith("javascript:") ? "" : value;
}
return value;
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._CurrencyFormatter = function(uiContext) {
this._decimalDigits = uiContext.getSetting("format/currency/decimal-digits");
if (typeof this._decimalDigits === "number") {
this._decimalDigits = Math.max(-1, Math.round(this._decimalDigits)); // -1 means no limit
} else {
this._decimalDigits = 2;
}
this._symbol = uiContext.getSetting("format/currency/symbol");
if (typeof this._symbol === "undefined" || this._symbol === null) {
this._symbol = Exhibit._("%formatter.currencySymbol");
}
this._symbolPlacement = uiContext.getSetting("format/currency/symbol-placement");
if (typeof this._symbolPlacement === "undefined" || this._symbolPlacement === null) {
this._symbol = Exhibit._("%formatter.currencySymbolPlacement");
}
this._negativeFormat = {
signed : uiContext.getBooleanSetting("format/currency/negative-format/signed", Exhibit._("%formatter.currencyShowSign")),
red : uiContext.getBooleanSetting("format/currency/negative-format/red", Exhibit._("%formatter.currencyShowRed")),
parentheses : uiContext.getBooleanSetting("format/currency/negative-format/parentheses", Exhibit._("%formatter.currencyShowParentheses"))
};
};
/**
* @param {Number} value
* @param {Function} appender
*/
Exhibit.Formatter._CurrencyFormatter.prototype.format = function(value, appender) {
var text, span;
text = this.formatText(value);
if (value < 0 && this._negativeFormat.red) {
span = Exhibit.jQuery("").html(text).css("color", "red");
appender(span);
} else {
appender(document.createTextNode(text));
}
};
/**
* @param {Number} value
* @returns {String}
*/
Exhibit.Formatter._CurrencyFormatter.prototype.formatText = function(value) {
var negative, text, sign;
negative = value < 0;
if (this._decimalDigits === -1) {
text = Math.abs(value);
} else {
text = Math.abs(value).toFixed(this._decimalDigits);
}
sign = (negative && this._negativeFormat.signed) ? "-" : "";
if (negative && this._negativeFormat.parentheses) {
text = "(" + text + ")";
}
switch (this._negativeFormat) {
case "first": text = this._symbol + sign + text; break;
case "after-sign": text = sign + this._symbol + text; break;
case "last": text = sign + text + this._symbol; break;
}
return text;
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._ItemFormatter = function(uiContext) {
this._uiContext = uiContext;
this._title = uiContext.getSetting("format/item/title");
};
/**
* @param {String} value
* @param {Function} appender
*/
Exhibit.Formatter._ItemFormatter.prototype.format = function(value, appender) {
var self, title, a, handler;
self = this;
title = this.formatText(value);
a = Exhibit.jQuery("" + title + "");
handler = function(evt) {
Exhibit.UI.showItemInPopup(value, a.get(0), self._uiContext);
evt.preventDefault();
evt.stopPropagation();
};
a.bind("click", handler);
appender(a);
};
/**
* @param {String} value
* @returns {String}
*/
Exhibit.Formatter._ItemFormatter.prototype.formatText = function(value) {
var database, title;
database = this._uiContext.getDatabase();
title = null;
if (typeof this._title === "undefined" || this._title === null) {
title = database.getObject(value, "label");
} else {
title = this._title.evaluateSingleOnItem(value, database).value;
}
if (typeof title === "undefined" || title === null) {
title = value;
}
return title;
};
/**
* @class
* @constructor
* @public
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Formatter._DateFormatter = function(uiContext) {
var mode, show, template, segments, placeholders, startIndex, p, placeholder, index, retriever;
this._timeZone = uiContext.getSetting("format/date/time-zone");
if (typeof this._timeZone !== "number") {
this._timeZone = -(new Date().getTimezoneOffset()) / 60;
}
this._timeZoneOffset = this._timeZone * 3600000;
mode = uiContext.getSetting("format/date/mode");
show = uiContext.getSetting("format/date/show");
template = null;
switch (mode) {
case "short":
template =
show === "date" ? Exhibit._("%formatter.dateShortFormat") :
(show === "time" ? Exhibit._("%formatter.timeShortFormat") :
Exhibit._("%formatter.dateTimeShortFormat"));
break;
case "medium":
template =
show === "date" ? Exhibit._("%formatter.dateMediumFormat") :
(show === "time" ? Exhibit._("%formatter.timeMediumFormat") :
Exhibit._("%formatter.dateTimeMediumFormat"));
break;
case "long":
template =
show === "date" ? Exhibit._("%formatter.dateLongFormat") :
(show === "time" ? Exhibit._("%formatter.timeLongFormat") :
Exhibit._("%formatter.dateTimeLongFormat"));
break;
case "full":
template =
show === "date" ? Exhibit._("%formatter.dateFullFormat") :
(show === "time" ? Exhibit._("%formatter.timeFullFormat") :
Exhibit._("%formatter.dateTimeFullFormat"));
break;
default:
template = uiContext.getSetting("format/date/template");
}
if (typeof template !== "string") {
template = Exhibit._("%formatter.dateTimeDefaultFormat");
}
segments = [];
placeholders = template.match(/\b\w+\b/g);
startIndex = 0;
for (p = 0; p < placeholders.length; p++) {
placeholder = placeholders[p];
index = template.indexOf(placeholder, startIndex);
if (index > startIndex) {
segments.push(template.substring(startIndex, index));
}
retriever = Exhibit.Formatter._DateFormatter._retrievers[placeholder];
if (typeof retriever === "function") {
segments.push(retriever);
} else {
segments.push(placeholder);
}
startIndex = index + placeholder.length;
}
if (startIndex < template.length) {
segments.push(template.substr(startIndex));
}
this._segments = segments;
};
/**
* @param {Date|String} value
* @param {Function} appender
*/
Exhibit.Formatter._DateFormatter.prototype.format = function(value, appender) {
appender(document.createTextNode(this.formatText(value)));
};
/**
* @param {Date|String} value
* @returns {String}
*/
Exhibit.Formatter._DateFormatter.prototype.formatText = function(value) {
var date, text, segments, i, segment;
date = (value instanceof Date) ? value : Exhibit.DateTime.parseIso8601DateTime(value);
if (typeof date === "undefined" || date === null) {
return value;
}
date.setTime(date.getTime() + this._timeZoneOffset);
text = "";
segments = this._segments;
for (i = 0; i < segments.length; i++) {
segment = segments[i];
if (typeof segment === "string") {
text += segment;
} else {
text += segment(date);
}
}
return text;
};
/**
* @param {Number} n
* @returns {String}
*/
Exhibit.Formatter._DateFormatter._pad = function(n) {
return n < 10 ? ("0" + n) : n.toString();
};
/**
* @param {Number} n
* @returns {String}
*/
Exhibit.Formatter._DateFormatter._pad3 = function(n) {
return n < 10 ? ("00" + n) : (n < 100 ? ("0" + n) : n.toString());
};
Exhibit.Formatter._DateFormatter._retrievers = {
// day of month
/**
* @param {Date} date
* @returns {String}
*/
"d": function(date) {
return date.getUTCDate().toString();
},
/**
* @param {Date} date
* @returns {String}
*/
"dd": function(date) {
return Exhibit.Formatter._DateFormatter._pad(date.getUTCDate());
},
// day of week
/**
* @param {Date} date
* @returns {String}
*/
"EEE": function(date) {
return Exhibit._("%formatter.shortDaysOfWeek")[date.getUTCDay()];
},
/**
* @param {Date} date
* @returns {String}
*/
"EEEE": function(date) {
return Exhibit._("%formatter.daysOfWeek")[date.getUTCDay()];
},
// month
/**
* @param {Date} date
* @returns {String}
*/
"MM": function(date) {
return Exhibit.Formatter._DateFormatter._pad(date.getUTCMonth() + 1);
},
/**
* @param {Date} date
* @returns {String}
*/
"MMM": function(date) {
return Exhibit._("%formatter.shortMonths")[date.getUTCMonth()];
},
/**
* @param {Date} date
* @returns {String}
*/
"MMMM": function(date) {
return Exhibit._("%formatter.months")[date.getUTCMonth()];
},
// year
/**
* @param {Date} date
* @returns {String}
*/
"yy": function(date) {
return Exhibit.Formatter._DateFormatter._pad(date.getUTCFullYear() % 100);
},
/**
* @param {Date} date
* @returns {String}
*/
"yyyy": function(date) {
var y = date.getUTCFullYear();
return y > 0 ? y.toString() : (1 - y);
},
// era
/**
* @param {Date} date
* @returns {String}
*/
"G": function(date) {
var y = date.getUTCYear();
return y > 0 ? Exhibit._("%formatter.commonEra") : Exhibit._("%formatter.beforeCommonEra");
},
// hours of day
/**
* @param {Date} date
* @returns {String}
*/
"HH": function(date) {
return Exhibit.Formatter._DateFormatter._pad(date.getUTCHours());
},
/**
* @param {Date} date
* @returns {String}
*/
"hh": function(date) {
var h = date.getUTCHours();
return Exhibit.Formatter._DateFormatter._pad(h === 0 ? 12 : (h > 12 ? h - 12 : h));
},
/**
* @param {Date} date
* @returns {String}
*/
"h": function(date) {
var h = date.getUTCHours();
return (h === 0 ? 12 : (h > 12 ? h - 12 : h)).toString();
},
// am/pm
/**
* @param {Date} date
* @returns {String}
*/
"a": function(date) {
return date.getUTCHours() < 12 ? Exhibit._("%formatter.beforeNoon") : Exhibit._("%formatter.afterNoon");
},
/**
* @param {Date} date
* @returns {String}
*/
"A": function(date) {
return date.getUTCHours() < 12 ? Exhibit._("%formatter.BeforeNoon") : Exhibit._("%formatter.AfterNoon");
},
// minutes of hour
/**
* @param {Date} date
* @returns {String}
*/
"mm": function(date) {
return Exhibit.Formatter._DateFormatter._pad(date.getUTCMinutes());
},
// seconds of minute
/**
* @param {Date} date
* @returns {String}
*/
"ss": function(date) {
return Exhibit.Formatter._DateFormatter._pad(date.getUTCSeconds());
},
// milliseconds of minute
/**
* @param {Date} date
* @returns {String}
*/
"S": function(date) {
return Exhibit.Formatter._DateFormatter._pad3(date.getUTCMilliseconds());
}
};
/**
* @constant
*/
Exhibit.Formatter._constructors = {
"number" : Exhibit.Formatter._NumberFormatter,
"date" : Exhibit.Formatter._DateFormatter,
"text" : Exhibit.Formatter._TextFormatter,
"boolean" : Exhibit.Formatter._BooleanFormatter,
"image" : Exhibit.Formatter._ImageFormatter,
"url" : Exhibit.Formatter._URLFormatter,
"item" : Exhibit.Formatter._ItemFormatter,
"currency" : Exhibit.Formatter._CurrencyFormatter
};
/**
* @fileOverview Lens registry for tracking lenses in a context.
* @author David Huynh
* @author Ryan Lee
*/
/**
* @constructor
* @class
* @param {Exhibit.LensRegistry} parentRegistry
*/
Exhibit.LensRegistry = function(parentRegistry) {
this._parentRegistry = parentRegistry;
this._defaultLens = null;
this._typeToLens = {};
this._editLensTemplates = {};
this._submissionLensTemplates = {};
this._lensSelectors = [];
};
/**
* @param {Element|String} elmtOrURL
*/
Exhibit.LensRegistry.prototype.registerDefaultLens = function(elmtOrURL) {
this._defaultLens = (typeof elmtOrURL === "string") ? elmtOrURL : elmtOrURL.cloneNode(true);
};
/**
* @param {Element|String} elmtOrURL
* @param {String} type
*/
Exhibit.LensRegistry.prototype.registerLensForType = function(elmtOrURL, type) {
if (typeof elmtOrURL === "string") {
this._typeToLens[type] = elmtOrURL;
}
var role = Exhibit.getRoleAttribute(elmtOrURL);
if (role === "lens") {
this._typeToLens[type] = elmtOrURL.cloneNode(true);
} else if (role === "edit-lens") {
this._editLensTemplates[type] = elmtOrURL.cloneNode(true);
} else {
Exhibit.Debug.warn(Exhibit._("%lens.error.unknownLensType", elmtOrURL));
}
};
/**
* @param {Function} lensSelector
*/
Exhibit.LensRegistry.prototype.addLensSelector = function(lensSelector) {
this._lensSelectors.unshift(lensSelector);
};
/**
* @param {String} itemID
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.Lens}
*/
Exhibit.LensRegistry.prototype.getLens = function(itemID, uiContext) {
return uiContext.isBeingEdited(itemID)
? this.getEditLens(itemID, uiContext)
: this.getNormalLens(itemID, uiContext);
};
/**
* @param {String} itemID
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.Lens}
*/
Exhibit.LensRegistry.prototype.getNormalLens = function(itemID, uiContext) {
var db, i, lens, type;
db = uiContext.getDatabase();
for (i = 0; i < this._lensSelectors.length; i++) {
lens = this._lensSelectors[i](itemID, db);
if (typeof lens !== "undefined" && lens !== null) {
return lens;
}
}
type = db.getObject(itemID, "type");
if (typeof this._typeToLens[type] !== "undefined") {
return this._typeToLens[type];
}
if (typeof this._defaultLens !== "undefined" &&
this._defaultLens !== null) {
return this._defaultLens;
}
if (typeof this._parentRegistry !== "undefined" &&
this._parenRegistry !== null) {
return this._parentRegistry.getLens(itemID, uiContext);
}
return null;
};
/**
* @param {String} itemID
* @param {Exhibit.UIContext} uiContext
* @returns {Exhibit.Lens}
*/
Exhibit.LensRegistry.prototype.getEditLens = function(itemID, uiContext) {
var type = uiContext.getDatabase().getObject(itemID, "type");
if (typeof this._editLensTemplates[type] !== "undefined") {
return this._editLensTemplates[type];
} else {
return this._parentRegistry && this._parentRegistry.getEditLens(itemID, uiContext);
}
};
/**
* @param {String} itemID
* @param {Element} div
* @param {Exhibit.UIContext} uiContext
* @param {Object} opts
* @param {Exhibit.Lens} opts.lensTemplate
* @returns {Exhibit.Lens}
*/
Exhibit.LensRegistry.prototype.createLens = function(itemID, div, uiContext, opts) {
var lens, lensTemplate;
lens = new Exhibit.Lens();
opts = opts || {};
lensTemplate = opts.lensTemplate || this.getLens(itemID, uiContext);
if (typeof lensTemplate === "undefined" || lensTemplate === null) {
lens._constructDefaultUI(itemID, div, uiContext);
} else if (typeof lensTemplate === "string") {
lens._constructFromLensTemplateURL(itemID, div, uiContext, lensTemplate, opts);
} else {
lens._constructFromLensTemplateDOM(itemID, div, uiContext, lensTemplate, opts);
}
return lens;
};
/**
* @param {String} itemID
* @param {Element} div
* @param {Exhibit.UIContext} uiContext
* @param {Object} opts
* @param {Exhibit.Lens} opts.lensTemplate
* @returns {Exhibit.Lens}
*/
Exhibit.LensRegistry.prototype.createEditLens = function(itemID, div, uiContext, opts) {
opts = opts || {};
opts.lensTemplate = this.getEditLens(itemID, uiContext);
return this.createLens(itemID, div, uiContext, opts);
};
/**
* @param {String} itemID
* @param {Element} div
* @param {Exhibit.UIContext} uiContext
* @param {Object} opts
* @param {Exhibit.Lens} opts.lensTemplate
* @returns {Exhibit.Lens}
*/
Exhibit.LensRegistry.prototype.createNormalLens = function(itemID, div, uiContext, opts) {
opts = opts || {};
opts.lensTemplate = this.getNormalLens(itemID, uiContext);
return this.createLens(itemID, div, uiContext, opts);
};
/**
* @fileOverview Lens class
* @author David Huynh
* @author Ryan Lee
*/
/**
* @constructor
* @class
*/
Exhibit.Lens = function() {
};
Exhibit.Lens._commonProperties = null;
/**
* @param {String} itemID
* @param {Element} div
* @param {Exhibit.UIContext} uiContext
*/
Exhibit.Lens.prototype._constructDefaultUI = function(itemID, div, uiContext) {
var database, properties, label, template, dom, pairs, j, pair, tr, tdValues, m;
database = uiContext.getDatabase();
if (typeof Exhibit.Lens._commonProperties === "undefined" || Exhibit.Lens._commonProperties === null) {
Exhibit.Lens._commonProperties = database.getAllProperties();
}
properties = Exhibit.Lens._commonProperties;
label = database.getObject(itemID, "label");
label = (typeof label !== "undefined" && label !== null) ? label : itemID;
if (Exhibit.params.safe) {
label = Exhibit.Formatter.encodeAngleBrackets(label);
}
template = {
elmt: div,
className: "exhibit-lens",
children: [
{ tag: "div",
className: "exhibit-lens-title",
title: label,
children: [
label + " (",
{ tag: "a",
href: Exhibit.Persistence.getItemLink(itemID),
target: "_blank",
children: [ Exhibit._("%general.itemLinkLabel") ]
},
")"
]
},
{ tag: "div",
className: "exhibit-lens-body",
children: [
{ tag: "table",
className: "exhibit-lens-properties",
field: "propertiesTable"
}
]
}
]
};
dom = Exhibit.jQuery.simileDOM("template", template);
Exhibit.jQuery(div).attr(Exhibit.makeExhibitAttribute("itemID"), itemID);
pairs = Exhibit.ViewPanel.getPropertyValuesPairs(
itemID, properties, database);
for (j = 0; j < pairs.length; j++) {
pair = pairs[j];
tr = Exhibit.jQuery("