ingenta-lens
Version:
A novel way of seeing content.
571 lines (481 loc) • 16.3 kB
JavaScript
"use strict";
// Imports
// ====
var _ = require('underscore');
// Module
// ====
var util = {};
// UUID Generator
// -----------------
/*!
Math.uuid.js (v1.4)
http://www.broofa.com
mailto:robert@broofa.com
Copyright (c) 2010 Robert Kieffer
Dual licensed under the MIT and GPL licenses.
*/
util.uuid = function (prefix, len) {
var chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
uuid = [],
radix = 16,
idx;
len = len || 32;
if (len) {
// Compact form
for (idx = 0; idx < len; idx++) uuid[idx] = chars[0 | Math.random()*radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (idx = 0; idx < 36; idx++) {
if (!uuid[idx]) {
r = 0 | Math.random()*16;
uuid[idx] = chars[(idx == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return (prefix ? prefix : "") + uuid.join('');
};
// creates a uuid function that generates counting uuids
util.uuidGen = function(defaultPrefix) {
var id = 1;
defaultPrefix = (defaultPrefix !== undefined) ? defaultPrefix : "uuid_";
return function(prefix) {
prefix = prefix || defaultPrefix;
return prefix+(id++);
};
};
// Events
// ---------------
// Taken from Backbone.js
//
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
//
// var object = {};
// _.extend(object, util.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
// A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
}
};
// Regular expression used to split event strings.
var eventSplitter = /\s+/;
// Implement fancy features of the Events API such as multiple event
// names `"change blur"` and jQuery-style event maps `{change: action}`
// in terms of the existing API.
var eventsApi = function(obj, action, name, rest) {
if (!name) return true;
// Handle event maps.
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
}
// Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
}
return true;
};
util.Events = {
// Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
on: function(name, callback, context) {
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
this._events = this._events || {};
var events = this._events[name] || (this._events[name] = []);
events.push({callback: callback, context: context, ctx: context || this});
return this;
},
// Bind an event to only be triggered a single time. After the first time
// the callback is invoked, it will be removed.
once: function(name, callback, context) {
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
var self = this;
var once = _.once(function() {
self.off(name, once);
callback.apply(this, arguments);
});
once._callback = callback;
return this.on(name, once, context);
},
// Remove one or many callbacks. If `context` is null, removes all
// callbacks with that function. If `callback` is null, removes all
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
if (!name && !callback && !context) {
this._events = {};
return this;
}
names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
events = this._events[name];
if (events) {
this._events[name] = retain = [];
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
retain.push(ev);
}
}
}
if (!retain.length) delete this._events[name];
}
}
return this;
},
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(name) {
if (!this._events) return this;
var args = Array.prototype.slice.call(arguments, 1);
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
var allEvents = this._events.all;
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, arguments);
return this;
},
triggerLater: function() {
var self = this;
var _arguments = arguments;
window.setTimeout(function() {
self.trigger.apply(self, _arguments);
}, 0);
},
// Tell this object to stop listening to either specific events ... or
// to every object it's currently listening to.
stopListening: function(obj, name, callback) {
var listeners = this._listeners;
if (!listeners) return this;
var deleteListener = !name && !callback;
if (typeof name === 'object') callback = this;
if (obj) (listeners = {})[obj._listenerId] = obj;
for (var id in listeners) {
listeners[id].off(name, callback, this);
if (deleteListener) delete this._listeners[id];
}
return this;
}
};
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
// listen to an event in another object ... keeping track of what it's
// listening to.
_.each(listenMethods, function(implementation, method) {
util.Events[method] = function(obj, name, callback) {
var listeners = this._listeners || (this._listeners = {});
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
listeners[id] = obj;
if (typeof name === 'object') callback = this;
obj[implementation](name, callback, this);
return this;
};
});
// Aliases for backwards compatibility.
util.Events.bind = util.Events.on;
util.Events.unbind = util.Events.off;
util.Events.Listener = {
listenTo: function(obj, name, callback) {
if (!_.isFunction(callback)) {
throw new Error("Illegal argument: expecting function as callback, was: " + callback);
}
// initialize container for keeping handlers to unbind later
this._handlers = this._handlers || [];
obj.on(name, callback, this);
this._handlers.push({
unbind: function() {
obj.off(name, callback);
}
});
return this;
},
stopListening: function() {
if (this._handlers) {
for (var i = 0; i < this._handlers.length; i++) {
this._handlers[i].unbind();
}
}
}
};
util.propagate = function(data, cb) {
if(!_.isFunction(cb)) {
throw "Illegal argument: provided callback is not a function";
}
return function(err) {
if (err) return cb(err);
cb(null, data);
};
};
// shamelessly stolen from backbone.js:
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var ctor = function(){};
util.inherits = function(parent, protoProps, staticProps) {
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ parent.apply(this, arguments); };
}
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
// Util to read seed data from file system
// ----------
util.getJSON = function(resource, cb) {
if (typeof window === 'undefined' || typeof nwglobal !== 'undefined') {
var fs = require('fs');
var obj = JSON.parse(fs.readFileSync(resource, 'utf8'));
cb(null, obj);
} else {
//console.log("util.getJSON", resource);
var $ = window.$;
$.getJSON(resource)
.done(function(obj) { cb(null, obj); })
.error(function(err) { cb(err, null); });
}
};
util.prototype = function(that) {
/*jshint proto: true*/ // supressing a warning about using deprecated __proto__.
return Object.getPrototypeOf ? Object.getPrototypeOf(that) : that.__proto__;
};
util.inherit = function(Super, Self) {
var super_proto = _.isFunction(Super) ? new Super() : Super;
var proto;
if (_.isFunction(Self)) {
Self.prototype = super_proto;
proto = new Self();
} else {
var TmpClass = function(){};
TmpClass.prototype = super_proto;
proto = _.extend(new TmpClass(), Self);
}
return proto;
};
util.pimpl = function(pimpl) {
var Pimpl = function(self) {
this.self = self;
};
Pimpl.prototype = pimpl;
return function(self) { self = self || this; return new Pimpl(self); };
};
util.parseStackTrace = function(err) {
var SAFARI_STACK_ELEM = /([^@]*)@(.*):(\d+)/;
var CHROME_STACK_ELEM = /\s*at ([^(]*)[(](.*):(\d+):(\d+)[)]/;
var idx;
var stackTrace = err.stack.split('\n');
// parse the stack trace: each line is a tuple (function, file, lineNumber)
// Note: unfortunately this is interpreter specific
// safari: "<function>@<file>:<lineNumber>"
// chrome: "at <function>(<file>:<line>:<col>"
var stack = [];
for (idx = 0; idx < stackTrace.length; idx++) {
var match = SAFARI_STACK_ELEM.exec(stackTrace[idx]);
if (!match) match = CHROME_STACK_ELEM.exec(stackTrace[idx]);
var entry;
if (match) {
entry = {
func: match[1],
file: match[2],
line: match[3],
col: match[4] || 0
};
if (entry.func === "") entry.func = "<anonymous>";
} else {
entry = {
func: "",
file: stackTrace[idx],
line: "",
col: ""
};
}
stack.push(entry);
}
return stack;
};
util.callstack = function(k) {
var err;
try { throw new Error(); } catch (_err) { err = _err; }
var stack = util.parseStackTrace(err);
k = k || 0;
return stack.splice(k+1);
};
util.stacktrace = function (err) {
var stack = (arguments.length === 0) ? util.callstack().splice(1) : util.parseStackTrace(err);
var str = [];
_.each(stack, function(s) {
str.push(s.file+":"+s.line+":"+s.col+" ("+s.func+")");
});
return str.join("\n");
};
util.printStackTrace = function(err, N) {
if (!err.stack) return;
var stack;
// Substance errors have a nice stack already
if (err.__stack !== undefined) {
stack = err.__stack;
}
// built-in errors have the stack trace as one string
else if (_.isString(err.stack)) {
stack = util.parseStackTrace(err);
}
else return;
N = N || stack.length;
N = Math.min(N, stack.length);
for (var idx = 0; idx < N; idx++) {
var s = stack[idx];
console.log(s.file+":"+s.line+":"+s.col, "("+s.func+")");
}
};
// computes the difference of obj1 to obj2
util.diff = function(obj1, obj2) {
var diff;
if (_.isArray(obj1) && _.isArray(obj2)) {
diff = _.difference(obj2, obj1);
// return null in case of equality
if (diff.length === 0) return null;
else return diff;
}
if (_.isObject(obj1) && _.isObject(obj2)) {
diff = {};
_.each(Object.keys(obj2), function(key) {
var d = util.diff(obj1[key], obj2[key]);
if (d) diff[key] = d;
});
// return null in case of equality
if (_.isEmpty(diff)) return null;
else return diff;
}
if(obj1 !== obj2) return obj2;
};
// Deep-Clone a given object
// --------
// Note: this is currently done via JSON.parse(JSON.stringify(obj))
// which is in fact not optimal, as it depends on `toJSON` implementation.
util.deepclone = function(obj) {
if (obj === undefined) return undefined;
if (obj === null) return null;
return JSON.parse(JSON.stringify(obj));
};
// Clones a given object
// --------
// Calls obj's `clone` function if available,
// otherwise clones the obj using `util.deepclone()`.
util.clone = function(obj) {
if (obj === null || obj === undefined) {
return obj;
}
if (_.isFunction(obj.clone)) {
return obj.clone();
}
return util.deepclone(obj);
};
util.freeze = function(obj) {
var idx;
if (_.isObject(obj)) {
if (Object.isFrozen(obj)) return obj;
var keys = Object.keys(obj);
for (idx = 0; idx < keys.length; idx++) {
var key = keys[idx];
obj[key] = util.freeze(obj[key]);
}
return Object.freeze(obj);
} else if (_.isArray(obj)) {
var arr = obj;
for (idx = 0; idx < arr.length; idx++) {
arr[idx] = util.freeze(arr[idx]);
}
return Object.freeze(arr);
} else {
return obj; // Object.freeze(obj);
}
};
util.later = function(f, context) {
return function() {
var _args = arguments;
window.setTimeout(function() {
f.apply(context, _args);
}, 0);
};
};
// Returns true if a string doesn't contain any real content
util.isEmpty = function(str) {
return !str.match(/\w/);
};
// Create a human readable, but URL-compatible slug from a string
util.slug = function(str) {
str = str.replace(/^\s+|\s+$/g, ''); // trim
str = str.toLowerCase();
// remove accents, swap ñ for n, etc
var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
var to = "aaaaeeeeiiiioooouuuunc------";
for (var i=0, l=from.length ; i<l ; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-'); // collapse dashes
return str;
};
util.getReadableFileSizeString = function(fileSizeInBytes) {
var i = -1;
var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
do {
fileSizeInBytes = fileSizeInBytes / 1024;
i++;
} while (fileSizeInBytes > 1024);
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
};
// Export
// ====
module.exports = util;