Commit f1ebdd39 authored by JC Brand's avatar JC Brand

Add third party js on which converse.js depends

parent 6e372919
({
appDir: "../",
baseUrl: "scripts/",
dir: "../../webapp-build",
//Comment out the optimize line if you want
//the code minified by UglifyJS
optimize: "none",
paths: {
"jquery": "empty:"
},
modules: [
//Optimize the application files. jQuery is not
//included since it is already in require-jquery.js
{
name: "main"
}
]
})
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){
// Initial Setup
// -------------
// Save a reference to the global object (`window` in the browser, `global`
// on the server).
var root = this;
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone;
// Create a local reference to slice/splice.
var slice = Array.prototype.slice;
var splice = Array.prototype.splice;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
var Backbone;
if (typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.2';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
var $ = root.jQuery || root.Zepto || root.ender;
// Set the JavaScript library that will be used for DOM manipulation and
// Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
// Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
// alternate JavaScript library (or a mock library for testing your views
// outside of a browser).
Backbone.setDomLibrary = function(lib) {
$ = lib;
};
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object.
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
return this;
};
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
// set a `X-Http-Method-Override` header.
Backbone.emulateHTTP = false;
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
// `application/json` requests ... will encode the body as
// `application/x-www-form-urlencoded` instead and will send the model in a
// form param named `model`.
Backbone.emulateJSON = false;
// Backbone.Events
// -----------------
// Regular expression used to split event strings
var eventSplitter = /\s+/;
// 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, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
var Events = Backbone.Events = {
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
var calls, event, node, tail, list;
if (!callback) return this;
events = events.split(eventSplitter);
calls = this._callbacks || (this._callbacks = {});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while (event = events.shift()) {
list = calls[event];
node = list ? list.tail : {};
node.next = tail = {};
node.context = context;
node.callback = callback;
calls[event] = {tail: tail, next: list ? list.next : node};
}
return this;
},
// 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 `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
if (!(calls = this._callbacks)) return;
if (!(events || callback || context)) {
delete this._callbacks;
return this;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events = events ? events.split(eventSplitter) : _.keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) continue;
// Create a new list, omitting the indicated callbacks.
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
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(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
all = calls.all;
events = events.split(eventSplitter);
rest = slice.call(arguments, 1);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) {
var defaults;
attributes || (attributes = {});
if (options && options.parse) attributes = this.parse(attributes);
if (defaults = getValue(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
}
if (options && options.collection) this.collection = options.collection;
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
this.changed = {};
this._silent = {};
this._pending = {};
this.set(attributes, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
};
// Attach all inheritable methods to the Model prototype.
_.extend(Model.prototype, Events, {
// A hash of attributes whose current and previous value differ.
changed: null,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Return a copy of the model's `attributes` object.
toJSON: function(options) {
return _.clone(this.attributes);
},
// Get the value of an attribute.
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
if (!this._validate(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
// Remove an attribute from the model, firing `"change"` unless you choose
// to silence it. `unset` is a noop if the attribute doesn't exist.
unset: function(attr, options) {
(options || (options = {})).unset = true;
return this.set(attr, null, options);
},
// Clear all attributes on the model, firing `"change"` unless you choose
// to silence it.
clear: function(options) {
(options || (options = {})).unset = true;
return this.set(_.clone(this.attributes), options);
},
// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overriden,
// triggering a `"change"` event.
fetch: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp);
};
options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function(key, value, options) {
var attrs, current;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
options = options ? _.clone(options) : {};
// If we're "wait"-ing to set changed attributes, validate early.
if (options.wait) {
if (!this._validate(attrs, options)) return false;
current = _.clone(this.attributes);
}
// Regular saves `set` attributes before persisting to the server.
var silentOptions = _.extend({}, options, {silent: true});
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
var serverAttrs = model.parse(resp, xhr);
if (options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
if (!model.set(serverAttrs, options)) return false;
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
if (options.wait) this.set(current, silentOptions);
return xhr;
},
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
var triggerDestroy = function() {
model.trigger('destroy', model, model.collection, options);
};
if (this.isNew()) {
triggerDestroy();
return false;
}
options.success = function(resp) {
if (options.wait) triggerDestroy();
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
options.error = Backbone.wrapError(options.error, model, options);
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
if (!options.wait) triggerDestroy();
return xhr;
},
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse: function(resp, xhr) {
return resp;
},
// Create a new model with identical attributes to this one.
clone: function() {
return new this.constructor(this.attributes);
},
// A model is new if it has never been saved to the server, and lacks an id.
isNew: function() {
return this.id == null;
},
// Call this method to manually fire a `"change"` event for this model and
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
options || (options = {});
var changing = this._changing;
this._changing = true;
// Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true;
// Silent changes are triggered.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options);
}
if (changing) return this;
// Continue firing `"change"` events while there are pending changes.
while (!_.isEmpty(this._pending)) {
this._pending = {};
this.trigger('change', this, options);
// Pending and silent changes still remain.
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._changing = false;
return this;
},
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged: function(attr) {
if (!arguments.length) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
// Return an object containing all the attributes that have changed, or
// false if there are no changed attributes. Useful for determining what
// parts of a view need to be updated and/or what attributes need to be
// persisted to the server. Unset attributes will be set to undefined.
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
var val, changed = false, old = this._previousAttributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
},
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous: function(attr) {
if (!arguments.length || !this._previousAttributes) return null;
return this._previousAttributes[attr];
},
// Get all of the attributes of the model at the time of the previous
// `"change"` event.
previousAttributes: function() {
return _.clone(this._previousAttributes);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid: function() {
return !this.validate(this.attributes);
},
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
if (options && options.error) {
options.error(this, error, options);
} else {
this.trigger('error', this, error, options);
}
return false;
}
});
// Backbone.Collection
// -------------------
// Provides a standard collection class for our sets of models, ordered
// or unordered. If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
};
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON: function(options) {
return this.map(function(model){ return model.toJSON(options); });
},
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added.
for (i = 0, length = models.length; i < length; i++) {
if (!(model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
cids[cid] = ids[id] = model;
}
// Remove duplicates.
i = dups.length;
while (i--) {
models.splice(dups[i], 1);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
// Insert models into the collection, re-sorting if needed, and triggering
// `add` events unless silenced.
this.length += length;
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
if (this.comparator) this.sort({silent: true});
if (options.silent) return this;
for (i = 0, length = this.models.length; i < length; i++) {
if (!cids[(model = this.models[i]).cid]) continue;
options.index = i;
model.trigger('add', model, this, options);
}
return this;
},
// Remove a model, or a list of models from the set. Pass silent to avoid
// firing the `remove` event for every model removed.
remove: function(models, options) {
var i, l, index, model;
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
for (i = 0, l = models.length; i < l; i++) {
model = this.getByCid(models[i]) || this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byCid[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
this._removeReference(model);
}
return this;
},
// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, options);
return model;
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));
return model;
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
// Get a model from the set by client id.
getByCid: function(cid) {
return cid && this._byCid[cid.cid || cid];
},
// Get the model at the given index.
at: function(index) {
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
where: function(attrs) {
if (_.isEmpty(attrs)) return [];
return this.filter(function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort: function(options) {
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
var boundComparator = _.bind(this.comparator, this);
if (this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
this.models.sort(boundComparator);
}
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return _.map(this.models, function(model){ return model.get(attr); });
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) {
models || (models = []);
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
this._reset();
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of resetting.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
options.error = Backbone.wrapError(options.error, collection, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
create: function(model, options) {
var coll = this;
options = options ? _.clone(options) : {};
model = this._prepareModel(model, options);
if (!model) return false;
if (!options.wait) coll.add(model, options);
var success = options.success;
options.success = function(nextModel, resp, xhr) {
if (options.wait) coll.add(nextModel, options);
if (success) {
success(nextModel, resp);
} else {
nextModel.trigger('sync', model, resp, options);
}
};
model.save(null, options);
return model;
},
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
parse: function(resp, xhr) {
return resp;
},
// Proxy to _'s chain. Can't be proxied the same way the rest of the
// underscore methods are proxied because it relies on the underscore
// constructor.
chain: function () {
return _(this.models).chain();
},
// Reset all internal state. Called when the collection is reset.
_reset: function(options) {
this.length = 0;
this.models = [];
this._byId = {};
this._byCid = {};
},
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel: function(model, options) {
options || (options = {});
if (!(model instanceof Model)) {
var attrs = model;
options.collection = this;
model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) model = false;
} else if (!model.collection) {
model.collection = this;
}
return model;
},
// Internal method to remove a model's ties to a collection.
_removeReference: function(model) {
if (this == model.collection) {
delete model.collection;
}
model.off('all', this._onModelEvent, this);
},
// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent: function(event, model, collection, options) {
if ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
this._byId[model.id] = model;
}
this.trigger.apply(this, arguments);
}
});
// Underscore methods that we want to implement on the Collection.
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
};
});
// Backbone.Router
// -------------------
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var Router = Backbone.Router = function(options) {
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
this.initialize.apply(this, arguments);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function(query, num) {
// ...
// });
//
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
},
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes: function() {
if (!this.routes) return;
var routes = [];
for (var route in this.routes) {
routes.unshift([route, this.routes[route]]);
}
for (var i = 0, l = routes.length; i < l; i++) {
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters: function(route, fragment) {
return route.exec(fragment).slice(1);
}
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
};
// Cached regex for cleaning leading hashes and slashes .
var routeStripper = /^[#\/]/;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
// Has the history handling already been started?
History.started = false;
// Set up all inheritable **Backbone.History** properties and methods.
_.extend(History.prototype, Events, {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval: 50,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(windowOverride) {
var loc = windowOverride ? windowOverride.location : window.location;
var match = loc.href.match(/#(.*)$/);
return match ? match[1] : '';
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || forcePushState) {
fragment = window.location.pathname;
var search = window.location.search;
if (search) fragment += search;
} else {
fragment = this.getHash();
}
}
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
return fragment.replace(routeStripper, '');
},
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start: function(options) {
if (History.started) throw new Error("Backbone.history has already been started");
History.started = true;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
this.options = _.extend({}, {root: '/'}, this.options, options);
this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
$(window).bind('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
$(window).bind('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
window.location.replace(this.options.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
if (!this.options.silent) {
return this.loadUrl();
}
},
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function() {
$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
clearInterval(this._checkUrlInterval);
History.started = false;
},
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
route: function(route, callback) {
this.handlers.unshift({route: route, callback: callback});
},
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(this.getHash());
},
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
return matched;
},
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the history.
navigate: function(fragment, options) {
if (!History.started) return false;
if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag) return;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
this.fragment = frag;
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
// When replace is true, we don't want this.
if(!options.replace) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
window.location.assign(this.options.root + fragment);
}
if (options.trigger) this.loadUrl(fragment);
},
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
_updateHash: function(location, fragment, replace) {
if (replace) {
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
} else {
location.hash = fragment;
}
}
});
// Backbone.View
// -------------
// Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
// Set up all inheritable **Backbone.View** properties and methods.
_.extend(View.prototype, Events, {
// The default `tagName` of a View's element is `"div"`.
tagName: 'div',
// jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be prefered to global lookups where possible.
$: function(selector) {
return this.$el.find(selector);
},
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// **render** is the core function that your view should override, in order
// to populate its element (`this.el`), with the appropriate HTML. The
// convention is for **render** to always return `this`.
render: function() {
return this;
},
// Remove this view from the DOM. Note that the view isn't present in the
// DOM by default, so calling this method may be a no-op.
remove: function() {
this.$el.remove();
return this;
},
// For small amounts of DOM Elements, where a full-blown template isn't
// needed, use **make** to manufacture elements, one at a time.
//
// var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
//
make: function(tagName, attributes, content) {
var el = document.createElement(tagName);
if (attributes) $(el).attr(attributes);
if (content) $(el).html(content);
return el;
},
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
},
// Set callbacks, where `this.events` is a hash of
//
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save'
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) {
if (!(events || (events = getValue(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
if (selector === '') {
this.$el.bind(eventName, method);
} else {
this.$el.delegate(selector, eventName, method);
}
}
},
// Clears all callbacks previously bound to the view with `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
undelegateEvents: function() {
this.$el.unbind('.delegateEvents' + this.cid);
},
// Performs the initial configuration of a View with a set of options.
// Keys with special meaning *(model, collection, id, className)*, are
// attached directly to the view.
_configure: function(options) {
if (this.options) options = _.extend({}, this.options, options);
for (var i = 0, l = viewOptions.length; i < l; i++) {
var attr = viewOptions[i];
if (options[attr]) this[attr] = options[attr];
}
this.options = options;
},
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
_ensureElement: function() {
if (!this.el) {
var attrs = getValue(this, 'attributes') || {};
if (this.id) attrs.id = this.id;
if (this.className) attrs['class'] = this.className;
this.setElement(this.make(this.tagName, attrs), false);
} else {
this.setElement(this.el, false);
}
}
});
// The self-propagating extend function that Backbone classes use.
var extend = function (protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
// Set up inheritance for the model, collection, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync
// -------------
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read': 'GET'
};
// Override this function to change the manner in which Backbone persists
// models to the server. You will be passed the type of request, and the
// model in question. By default, makes a RESTful Ajax request
// to the model's `url()`. Some possible customizations could be:
//
// * Use `setTimeout` to batch rapid-fire updates into a single request.
// * Send up the models as XML instead of JSON.
// * Persist models via WebSockets instead of Ajax.
//
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with a `_method` parameter containing the true HTTP method,
// as well as all requests with the body as `application/x-www-form-urlencoded`
// instead of `application/json` with the model in a param named `model`.
// Useful when interfacing with server-side languages like **PHP** that make
// it difficult to read the body of `PUT` requests.
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
options || (options = {});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
if (!options.url) {
params.url = getValue(model, 'url') || urlError();
}
// Ensure that we have the appropriate request data.
if (!options.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (Backbone.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (Backbone.emulateHTTP) {
if (type === 'PUT' || type === 'DELETE') {
if (Backbone.emulateJSON) params.data._method = type;
params.type = 'POST';
params.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !Backbone.emulateJSON) {
params.processData = false;
}
// Make the request, allowing the user to override any Ajax options.
return $.ajax(_.extend(params, options));
};
// Wrap an optional error callback with a fallback error event.
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// 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 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;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// Throw an error when a URL is needed, and none is supplied.
var urlError = function() {
throw new Error('A "url" property or function must be specified');
};
}).call(this);
Copyright (c) 2012 Yiorgis Gozadinos, Riot AS
Postboks 2236, 3103 Tønsberg, Norway
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# burry.js
A simple caching layer on the browser's localStorage
## Usage
### Creation
Create a Burry `Store`, optionally passing a namespace. A default store is always available with no namespace:
```javascript
var burry = new Burry.Store('mystuff');
```
If you want to also set a default time-to-live on a namespaced store, pass the time-to-live as a second parameter. For instance,
```javascript
var burrywithttl = new Burry.Store('mystuff', 10);
```
will create a store where the default time-to-live when you set items is 10 minutes.
You can obtain all available stores, by invoking `stores()`:
```javascript
var stores = Burry.stores(); // stores is ['', 'mystuff']
```
### Getting/Setting
`set` and `get` JSON-serializable javascript objects easily to and from the cache.
```javascript
burry.set('foo', {bar: 'burry'});
var foo = burry.get('foo'); // foo is {bar: 'burry'}
foo = burry.get('unknown'); // foo is undefined
```
You can specify a time-to-live per key/value. This is expressed in minutes:
```javascript
burry.set('foo', {bar: 'burry'}, 10);
var foo = burry.get('foo'); // foo is {bar: 'burry'}
...
// Ten minutes later...
foo = burry.get('foo'); // foo is undefined and also removed from localStorage
```
Attempting to `set` when the `localStorage` is full, will try again after flushing expired key/values from the cache. If this does not succeed either, your `set` will be ignored.
### Counters
You can increment/decrement persistent counters. If the counter does not exist, it is initialized with the value 0.
```javascript
burry.incr('counter');
burry.incr('counter');
var counter = burry.get('counter'); // counter === 2
burry.decr('counter');
counter = burry.get('counter'); // counter === 1
```
### Helpers
The following more esoteric functions are also exposed:
* `burry.add(key, value, ttl)`, same as `set` except it will only add the key if it does not already exist, or it has already expired.
* `burry.replace(key, value, ttl)`, same as `set` except it will only add the key if it does already exist and has not expired.
* `burry.flush()`, removes from `localStorage` all Burry items.
* `burry.flushExpired()`, removes from `localStorage` all expired Burry items of the store.
* `Burry.flushExpired()`, removes from `localStorage` all expired Burry items of all stores.
* `burry.keys()`, returns all stored keys.
* `burry.expirableKeys()` return an dictionary of key/values where the values are the TTL of the keys from Epoch.
* `burry.hasExpired(key)`, returns whether a key has expired.
* `Burry.isSupported()`, returns whether `localStorage` and `JSON` serialization are supported on the browser.
## License
Backbone.xmpp.storage is Copyright (C) 2012 Yiorgis Gozadinos, Riot AS.
It is distributed under the MIT license.
\ No newline at end of file
// Burry.js Storage v0.1
// (c) 2012 Yiorgis Gozadinos, Riot AS.
// Burry.js is distributed under the MIT license.
// http://github.com/ggozad/burry.js
// AMD/global registrations
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function () {
return factory();
});
} else {
// Browser globals
root.Burry = factory();
}
}(this, function () {
// Construct a new Burry store with an optional `namespace` and an optional default `ttl`.
var Burry = {
Store: function (ns, default_ttl) {
var stores = Burry.stores();
if (ns) {
this._CACHE_SUFFIX = this._CACHE_SUFFIX + ns;
this._EXPIRY_KEY = this._EXPIRY_KEY + ns;
if (stores.indexOf(ns) === -1)
stores.push(ns);
}
localStorage.setItem('_burry_stores_', JSON.stringify(stores));
this.default_ttl = default_ttl;
},
// Time resolution in minutes
_EXPIRY_UNITS: 60 * 1000,
// Calculate the time since Epoch in minutes
_mEpoch: function () {
return Math.floor((new Date().getTime())/Burry._EXPIRY_UNITS);
},
stores: function () {
var stores = localStorage.getItem('_burry_stores_');
if (stores) {
stores = JSON.parse(stores);
} else {
stores = [''];
}
return stores;
},
// Checks for localStorage & JSON support.
isSupported: function () {
// If this has been called before we already know.
if (Burry._isSupported) return Burry._isSupported;
try {
localStorage.setItem('_burry_', '_burry_');
localStorage.removeItem('_burry_');
} catch (e) {
return Burry._isSupported = false;
}
if (!JSON) {
return Burry._isSupported = false;
}
return Burry._isSupported = true;
},
flushExpired: function () {
var i, match, key, val, ns,
remove = [],
now = Burry._mEpoch();
for (i=0; i< localStorage.length; i++) {
key = localStorage.key(i);
match = key.match(/(.+)-_burry_exp_(.*)/);
if (match) {
val = localStorage.getItem(key);
if (val < now) {
key = match[1]; ns = match[2];
remove.push(key + Burry.Store.prototype._CACHE_SUFFIX + ns);
remove.push(key + Burry.Store.prototype._EXPIRY_KEY + ns);
}
}
}
for (i=0; i< remove.length; i++) {
localStorage.removeItem(remove[i]);
}
}
};
// Instance methods
Burry.Store.prototype = {
// Suffix to all keys in the cache
_CACHE_SUFFIX: '-_burry_',
// Key used to store expiration data
_EXPIRY_KEY: '-_burry_exp_',
// Return the internally used suffixed key.
_internalKey: function (key) {
return key + this._CACHE_SUFFIX;
},
// Return the internally used suffixed expiration key.
_expirationKey: function (key) {
return key + this._EXPIRY_KEY;
},
// Check if a key is a valid internal key
_isInternalKey: function (key) {
if (key.slice(-this._CACHE_SUFFIX.length) === this._CACHE_SUFFIX)
return key.slice(0, -this._CACHE_SUFFIX.length);
return false;
},
// Check if a key is a valid expiration key
_isExpirationKey: function (key) {
if (key.slice(-this._EXPIRY_KEY.length) === this._EXPIRY_KEY)
return key.slice(0, -this._EXPIRY_KEY.length);
return false;
},
// Returns in how many minutes after Epoch the key expires,
// or `undefined` if it does not expire.
_expiresOn: function (key) {
var expires = localStorage.getItem(this._expirationKey(key));
if (expires) {
return parseInt(expires, 10);
}
},
// Parse the value of a key as an integer.
_getCounter: function (bkey) {
var value = localStorage.getItem(bkey);
if (value === null) return 0;
return parseInt(value, 10);
},
// Returns the value of `key` from the cache, `undefined` if the `key` has
// expired or is not stored.
get: function (key) {
var value = localStorage.getItem(this._internalKey(key));
if (value === null) {
return undefined;
}
if (this.hasExpired(key)) {
this.remove(key);
return undefined;
}
try {
value = JSON.parse(value);
} catch (e) {
return undefined;
}
return value;
},
// Sets a `key`/`value` on the cache. Optionally, sets the expiration in `ttl` minutes.
set: function (key, value, ttl) {
var i, bkey, expires = {};
ttl = ttl || this.default_ttl;
if (ttl) ttl = parseInt(ttl, 10);
if (typeof key === undefined || typeof value === undefined) return;
value = JSON.stringify(value);
try {
localStorage.setItem(this._internalKey(key), value);
if (ttl) {
localStorage.setItem(this._expirationKey(key), Burry._mEpoch() + ttl);
} else {
localStorage.removeItem(this._expirationKey(key));
}
} catch (e) {
if (e.name === 'QUOTA_EXCEEDED_ERR' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
// No space left on localStorage, let's flush expired items and try agagin.
Burry.flushExpired();
try {
localStorage.setItem(this._internalKey(key), value);
if (ttl) {
localStorage.setItem(this._expirationKey(key), Burry._mEpoch() + ttl);
} else {
localStorage.removeItem(this._expirationKey(key));
}
}
catch (e) {
// Oh well. Let's forget about it.
}
}
}
},
// Sets a `key`/`value` on the cache as does **set** but only if the key does not already exist or has expired.
add: function (key, value, ttl) {
if (localStorage.getItem(this._internalKey(key)) === null || this.hasExpired(key)) {
this.set(key, value, ttl);
}
},
// Sets a `key`/`value` on the cache as does **set** but only if the key already exist and has not expired.
replace: function (key, value, ttl) {
if (localStorage.getItem(this._internalKey(key)) !== null && !this.hasExpired(key)) {
this.set(key, value, ttl);
}
},
// Removes an item from the cache.
remove: function (key) {
localStorage.removeItem(this._internalKey(key));
localStorage.removeItem(this._expirationKey(key));
},
// Increments the integer value of `key` by 1
incr: function (key) {
var bkey = this._internalKey(key),
value = this._getCounter(bkey);
value++;
localStorage.setItem(bkey, value);
},
// Decrements the integer value of `key` by 1
decr: function (key) {
var bkey = this._internalKey(key),
value = this._getCounter(bkey);
value--;
localStorage.setItem(bkey, value);
},
// Returns whether `key` has expired.
hasExpired: function (key) {
var expireson = this._expiresOn(key);
if (expireson && (expireson < Burry._mEpoch())) {
return true;
}
return false;
},
// Returns a list of all the cached keys
keys: function () {
var i, bkey, key, results = [];
for (i=0; i < localStorage.length ; i++) {
bkey = localStorage.key(i);
key = this._isInternalKey(bkey);
if (key) {
results.push(key);
}
}
return results;
},
// Returns an object with all the expirable keys. The values are the ttl
// in minutes since Epoch.
expirableKeys: function () {
var i, bkey, key, results = {};
for (i=0; i < localStorage.length ; i++) {
bkey = localStorage.key(i);
key = this._isExpirationKey(bkey);
if (key) {
results[key] = parseInt(localStorage.getItem(bkey), 10);
}
}
return results;
},
// Removes all Burry items from `localStorage`.
flush: function () {
var i, key, remove = [];
for (i=0; i < localStorage.length ; i++) {
key = localStorage.key(i);
if (this._isInternalKey(key) || this._isExpirationKey(key)) {
remove.push(key);
}
}
for (i=0; i<remove.length; i++)
localStorage.removeItem(remove[i]);
},
// Removes all expired items.
flushExpired: function () {
var expirable = this.expirableKeys(), now = Burry._mEpoch(), key, val;
for (key in expirable) {
val = expirable[key];
if (val < now) this.remove(key);
}
}
};
return Burry;
}));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>burry.js</title>
<link rel="stylesheet" href="pycco.css">
</head>
<body>
<div id="background"></div>
<div id='container'>
<div class='section'>
<div class='docs'><h1>burry.js</h1></div>
</div>
<div class='clearall'>
<div class='section' id='section-0'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-0'>#</a>
</div>
<p>Burry.js Storage v0.1</p>
</div>
<div class='code'>
<div class="highlight"><pre></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-1'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-1'>#</a>
</div>
<p>(c) 2012 Yiorgis Gozadinos, Riot AS.
Burry.js is distributed under the MIT license.
http://github.com/ggozad/burry.js</p>
</div>
<div class='code'>
<div class="highlight"><pre></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-2'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-2'>#</a>
</div>
<p>AMD/global registrations</p>
</div>
<div class='code'>
<div class="highlight"><pre><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">root</span><span class="p">,</span> <span class="nx">factory</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">define</span> <span class="o">===</span> <span class="s1">&#39;function&#39;</span> <span class="o">&amp;&amp;</span> <span class="nx">define</span><span class="p">.</span><span class="nx">amd</span><span class="p">)</span> <span class="p">{</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-3'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-3'>#</a>
</div>
<p>AMD. Register as an anonymous module.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">define</span><span class="p">([],</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">factory</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-4'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-4'>#</a>
</div>
<p>Browser globals</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">root</span><span class="p">.</span><span class="nx">Burry</span> <span class="o">=</span> <span class="nx">factory</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}(</span><span class="k">this</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-5'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-5'>#</a>
</div>
<p>Construct a new Burry store with an optional <code>namespace</code> and an optional default <code>ttl</code>.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="kd">var</span> <span class="nx">Burry</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">Store</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">ns</span><span class="p">,</span> <span class="nx">default_ttl</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">stores</span> <span class="o">=</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">stores</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">ns</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span> <span class="o">+</span> <span class="nx">ns</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span> <span class="o">+</span> <span class="nx">ns</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">stores</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">ns</span><span class="p">)</span> <span class="o">===</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="nx">stores</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">ns</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;_burry_stores_&#39;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">stores</span><span class="p">));</span>
<span class="k">this</span><span class="p">.</span><span class="nx">default_ttl</span> <span class="o">=</span> <span class="nx">default_ttl</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-6'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-6'>#</a>
</div>
<p>Time resolution in minutes</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_EXPIRY_UNITS</span><span class="o">:</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-7'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-7'>#</a>
</div>
<p>Calculate the time since Epoch in minutes</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_mEpoch</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">((</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">getTime</span><span class="p">())</span><span class="o">/</span><span class="nx">Burry</span><span class="p">.</span><span class="nx">_EXPIRY_UNITS</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">stores</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">stores</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s1">&#39;_burry_stores_&#39;</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">stores</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">stores</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">stores</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">stores</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;&#39;</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">stores</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-8'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-8'>#</a>
</div>
<p>Checks for localStorage &amp; JSON support.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">isSupported</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;_burry_&#39;</span><span class="p">,</span> <span class="s1">&#39;_burry_&#39;</span><span class="p">);</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="s1">&#39;_burry_&#39;</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">JSON</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">},</span>
<span class="nx">flushExpired</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">match</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">val</span><span class="p">,</span> <span class="nx">ns</span><span class="p">,</span>
<span class="nx">remove</span> <span class="o">=</span> <span class="p">[],</span>
<span class="nx">now</span> <span class="o">=</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">_mEpoch</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">key</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">key</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="nx">match</span> <span class="o">=</span> <span class="nx">key</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/(.+)-_burry_exp_(.*)/</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">match</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">val</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">val</span> <span class="o">&lt;</span> <span class="nx">now</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">key</span> <span class="o">=</span> <span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span> <span class="nx">ns</span> <span class="o">=</span> <span class="nx">match</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="nx">remove</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">key</span> <span class="o">+</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">Store</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span> <span class="o">+</span> <span class="nx">ns</span><span class="p">);</span>
<span class="nx">remove</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">key</span> <span class="o">+</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">Store</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span> <span class="o">+</span> <span class="nx">ns</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span> <span class="nx">remove</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="nx">remove</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-9'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-9'>#</a>
</div>
<p>Instance methods</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">Burry</span><span class="p">.</span><span class="nx">Store</span><span class="p">.</span><span class="nx">prototype</span> <span class="o">=</span> <span class="p">{</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-10'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-10'>#</a>
</div>
<p>Suffix to all keys in the cache</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_CACHE_SUFFIX</span><span class="o">:</span> <span class="s1">&#39;-_burry_&#39;</span><span class="p">,</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-11'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-11'>#</a>
</div>
<p>Key used to store expiration data</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_EXPIRY_KEY</span><span class="o">:</span> <span class="s1">&#39;-_burry_exp_&#39;</span><span class="p">,</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-12'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-12'>#</a>
</div>
<p>Return the internally used suffixed key.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_internalKey</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">key</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-13'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-13'>#</a>
</div>
<p>Return the internally used suffixed expiration key.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_expirationKey</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">key</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-14'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-14'>#</a>
</div>
<p>Check if a key is a valid internal key</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_isInternalKey</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">key</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="k">this</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">===</span> <span class="k">this</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span><span class="p">)</span>
<span class="k">return</span> <span class="nx">key</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="k">this</span><span class="p">.</span><span class="nx">_CACHE_SUFFIX</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-15'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-15'>#</a>
</div>
<p>Check if a key is a valid expiration key</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_isExpirationKey</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">key</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="k">this</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">===</span> <span class="k">this</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span><span class="p">)</span>
<span class="k">return</span> <span class="nx">key</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="k">this</span><span class="p">.</span><span class="nx">_EXPIRY_KEY</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-16'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-16'>#</a>
</div>
<p>Returns in how many minutes after Epoch the key expires,
or <code>undefined</code> if it does not expire.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_expiresOn</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">expires</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_expirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expires</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">expires</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-17'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-17'>#</a>
</div>
<p>Parse the value of a key as an integer.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">_getCounter</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">bkey</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="nx">bkey</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">return</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-18'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-18'>#</a>
</div>
<p>Returns the value of <code>key</code> from the cache, <code>undefined</code> if the <code>key</code> has
expired or is not stored.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">get</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">hasExpired</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-19'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-19'>#</a>
</div>
<p>Sets a <code>key</code>/<code>value</code> on the cache. Optionally, sets the expiration in <code>ttl</code> minutes.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">set</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">ttl</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">bkey</span><span class="p">,</span> <span class="nx">expires</span> <span class="o">=</span> <span class="p">{};</span>
<span class="nx">ttl</span> <span class="o">=</span> <span class="nx">ttl</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">default_ttl</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">ttl</span><span class="p">)</span> <span class="nx">ttl</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">ttl</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">key</span> <span class="o">===</span> <span class="kc">undefined</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span> <span class="nx">value</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">ttl</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_expirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">_mEpoch</span><span class="p">()</span> <span class="o">+</span> <span class="nx">ttl</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_expirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="s1">&#39;QUOTA_EXCEEDED_ERR&#39;</span> <span class="o">||</span> <span class="nx">e</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="s1">&#39;NS_ERROR_DOM_QUOTA_REACHED&#39;</span><span class="p">)</span> <span class="p">{</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-20'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-20'>#</a>
</div>
<p>No space left on localStorage, let's flush expired items and try agagin.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">Burry</span><span class="p">.</span><span class="nx">flushExpired</span><span class="p">();</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span> <span class="nx">value</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">ttl</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_expirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">_mEpoch</span><span class="p">()</span> <span class="o">+</span> <span class="nx">ttl</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_expirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-21'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-21'>#</a>
</div>
<p>Oh well. Let's forget about it.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-22'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-22'>#</a>
</div>
<p>Sets a <code>key</code>/<code>value</code> on the cache as does <strong>set</strong> but only if the key does not already exist or has expired.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">add</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">ttl</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="o">===</span> <span class="kc">null</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">hasExpired</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">ttl</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-23'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-23'>#</a>
</div>
<p>Sets a <code>key</code>/<code>value</code> on the cache as does <strong>set</strong> but only if the key already exist and has not expired.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">replace</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">ttl</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">hasExpired</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">ttl</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-24'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-24'>#</a>
</div>
<p>Removes an item from the cache.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">remove</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_expirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-25'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-25'>#</a>
</div>
<p>Increments the integer value of <code>key</code> by 1</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">incr</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">bkey</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span>
<span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_getCounter</span><span class="p">(</span><span class="nx">bkey</span><span class="p">);</span>
<span class="nx">value</span><span class="o">++</span><span class="p">;</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="nx">bkey</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-26'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-26'>#</a>
</div>
<p>Decrements the integer value of <code>key</code> by 1</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">decr</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">bkey</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_internalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span>
<span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_getCounter</span><span class="p">(</span><span class="nx">bkey</span><span class="p">);</span>
<span class="nx">value</span><span class="o">--</span><span class="p">;</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="nx">bkey</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-27'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-27'>#</a>
</div>
<p>Returns whether <code>key</code> has expired.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">hasExpired</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">expireson</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_expiresOn</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expireson</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="nx">expireson</span> <span class="o">&lt;</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">_mEpoch</span><span class="p">()))</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-28'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-28'>#</a>
</div>
<p>Returns a list of all the cached keys</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">keys</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">bkey</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">results</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">length</span> <span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">bkey</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">key</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="nx">key</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_isInternalKey</span><span class="p">(</span><span class="nx">bkey</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">results</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">results</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-29'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-29'>#</a>
</div>
<p>Returns an object with all the expirable keys. The values are the ttl
in minutes since Epoch.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">expirableKeys</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">bkey</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">results</span> <span class="o">=</span> <span class="p">{};</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">length</span> <span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">bkey</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">key</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="nx">key</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_isExpirationKey</span><span class="p">(</span><span class="nx">bkey</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">results</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="nx">bkey</span><span class="p">),</span> <span class="mi">10</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">results</span><span class="p">;</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-30'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-30'>#</a>
</div>
<p>Removes all Burry items from <code>localStorage</code>.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">flush</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">remove</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">length</span> <span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">key</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">key</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_isInternalKey</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">_isExpirationKey</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">remove</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span><span class="nx">remove</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="nx">remove</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">},</span></pre></div>
</div>
</div>
<div class='clearall'></div>
<div class='section' id='section-31'>
<div class='docs'>
<div class='octowrap'>
<a class='octothorpe' href='#section-31'>#</a>
</div>
<p>Removes all expired items.</p>
</div>
<div class='code'>
<div class="highlight"><pre> <span class="nx">flushExpired</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">expirable</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">expirableKeys</span><span class="p">(),</span> <span class="nx">now</span> <span class="o">=</span> <span class="nx">Burry</span><span class="p">.</span><span class="nx">_mEpoch</span><span class="p">(),</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">val</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">key</span> <span class="k">in</span> <span class="nx">expirable</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">val</span> <span class="o">=</span> <span class="nx">expirable</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">val</span> <span class="o">&lt;</span> <span class="nx">now</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="k">return</span> <span class="nx">Burry</span><span class="p">;</span>
<span class="p">}));</span>
</pre></div>
</div>
</div>
<div class='clearall'></div>
</div>
</body>
/*--------------------- Layout and Typography ----------------------------*/
body {
font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
font-size: 16px;
line-height: 24px;
color: #252519;
margin: 0; padding: 0;
}
a {
color: #261a3b;
}
a:visited {
color: #261a3b;
}
p {
margin: 0 0 15px 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 40px 0 15px 0;
}
h2, h3, h4, h5, h6 {
margin-top: 0;
}
#container, div.section {
position: relative;
}
#background {
position: fixed;
top: 0; left: 580px; right: 0; bottom: 0;
background: #f5f5ff;
border-left: 1px solid #e5e5ee;
z-index: -1;
}
#jump_to, #jump_page {
background: white;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
font: 10px Arial;
text-transform: uppercase;
cursor: pointer;
text-align: right;
}
#jump_to, #jump_wrapper {
position: fixed;
right: 0; top: 0;
padding: 5px 10px;
}
#jump_wrapper {
padding: 0;
display: none;
}
#jump_to:hover #jump_wrapper {
display: block;
}
#jump_page {
padding: 5px 0 3px;
margin: 0 0 25px 25px;
}
#jump_page .source {
display: block;
padding: 5px 10px;
text-decoration: none;
border-top: 1px solid #eee;
}
#jump_page .source:hover {
background: #f5f5ff;
}
#jump_page .source:first-child {
}
div.docs {
float: left;
max-width: 500px;
min-width: 500px;
min-height: 5px;
padding: 10px 25px 1px 50px;
vertical-align: top;
text-align: left;
}
.docs pre {
margin: 15px 0 15px;
padding-left: 15px;
}
.docs p tt, .docs p code {
background: #f8f8ff;
border: 1px solid #dedede;
font-size: 12px;
padding: 0 0.2em;
}
.octowrap {
position: relative;
}
.octothorpe {
font: 12px Arial;
text-decoration: none;
color: #454545;
position: absolute;
top: 3px; left: -20px;
padding: 1px 2px;
opacity: 0;
-webkit-transition: opacity 0.2s linear;
}
div.docs:hover .octothorpe {
opacity: 1;
}
div.code {
margin-left: 580px;
padding: 14px 15px 16px 50px;
vertical-align: top;
}
.code pre, .docs p code {
font-size: 12px;
}
pre, tt, code {
line-height: 18px;
font-family: Monaco, Consolas, "Lucida Console", monospace;
margin: 0; padding: 0;
}
div.clearall {
clear: both;
}
/*---------------------- Syntax Highlighting -----------------------------*/
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
body .hll { background-color: #ffffcc }
body .c { color: #408080; font-style: italic } /* Comment */
body .err { border: 1px solid #FF0000 } /* Error */
body .k { color: #954121 } /* Keyword */
body .o { color: #666666 } /* Operator */
body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
body .cp { color: #BC7A00 } /* Comment.Preproc */
body .c1 { color: #408080; font-style: italic } /* Comment.Single */
body .cs { color: #408080; font-style: italic } /* Comment.Special */
body .gd { color: #A00000 } /* Generic.Deleted */
body .ge { font-style: italic } /* Generic.Emph */
body .gr { color: #FF0000 } /* Generic.Error */
body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
body .gi { color: #00A000 } /* Generic.Inserted */
body .go { color: #808080 } /* Generic.Output */
body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
body .gs { font-weight: bold } /* Generic.Strong */
body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
body .gt { color: #0040D0 } /* Generic.Traceback */
body .kc { color: #954121 } /* Keyword.Constant */
body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
body .kp { color: #954121 } /* Keyword.Pseudo */
body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
body .kt { color: #B00040 } /* Keyword.Type */
body .m { color: #666666 } /* Literal.Number */
body .s { color: #219161 } /* Literal.String */
body .na { color: #7D9029 } /* Name.Attribute */
body .nb { color: #954121 } /* Name.Builtin */
body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
body .no { color: #880000 } /* Name.Constant */
body .nd { color: #AA22FF } /* Name.Decorator */
body .ni { color: #999999; font-weight: bold } /* Name.Entity */
body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
body .nf { color: #0000FF } /* Name.Function */
body .nl { color: #A0A000 } /* Name.Label */
body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
body .nt { color: #954121; font-weight: bold } /* Name.Tag */
body .nv { color: #19469D } /* Name.Variable */
body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
body .w { color: #bbbbbb } /* Text.Whitespace */
body .mf { color: #666666 } /* Literal.Number.Float */
body .mh { color: #666666 } /* Literal.Number.Hex */
body .mi { color: #666666 } /* Literal.Number.Integer */
body .mo { color: #666666 } /* Literal.Number.Oct */
body .sb { color: #219161 } /* Literal.String.Backtick */
body .sc { color: #219161 } /* Literal.String.Char */
body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
body .s2 { color: #219161 } /* Literal.String.Double */
body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
body .sh { color: #219161 } /* Literal.String.Heredoc */
body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
body .sx { color: #954121 } /* Literal.String.Other */
body .sr { color: #BB6688 } /* Literal.String.Regex */
body .s1 { color: #219161 } /* Literal.String.Single */
body .ss { color: #19469D } /* Literal.String.Symbol */
body .bp { color: #954121 } /* Name.Builtin.Pseudo */
body .vc { color: #19469D } /* Name.Variable.Class */
body .vg { color: #19469D } /* Name.Variable.Global */
body .vi { color: #19469D } /* Name.Variable.Instance */
body .il { color: #666666 } /* Literal.Number.Integer.Long */
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css" href="./vendor/jasmine/jasmine.css"/>
<script src="./vendor/jquery.js"></script>
<script src="../burry.js"></script>
<script src="./vendor/jasmine/jasmine.js"></script>
<script src="./vendor/jasmine/jasmine-html.js"></script>
<script src="./specs/burry_spec.js"></script>
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
</body>
</html>
/*globals jasmine:false */
(function ($) {
$(function () {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
jasmineEnv.execute();
});
})(this.jQuery);
\ No newline at end of file
(function (Burry) {
describe('burry.js Storage', function () {
afterEach(function () {
localStorage.clear();
});
describe('Static methods', function () {
it('returns the stores that have been created', function () {
var burryfoo, burrybar;
burryfoo = new Burry.Store('foo');
burrybar = new Burry.Store('bar');
burrybar2 = new Burry.Store('bar');
expect(Burry.stores()).toEqual(['', 'foo', 'bar']);
});
it('calculates time elapsed since epoch in minutues', function () {
var datea = new Date(10 * 60 * 1000);
spyOn(window, 'Date').andReturn(datea);
expect(Burry._mEpoch()).toEqual(10);
});
it('supports localStorage', function () {
expect(Burry.isSupported()).toBeTruthy();
});
it('flushes expired key/values from all stores', function () {
burryfoo = new Burry.Store('foo');
burrybar = new Burry.Store('bar');
burryfoo.set('expired1', {foo: 'bar'}, -1);
burryfoo.set('expired2', {foo: 'bar'}, -2);
burryfoo.set('not-expired', {foo: 'bar'}, 10);
burrybar.set('expired1', {foo: 'bar'}, -1);
burrybar.set('expired2', {foo: 'bar'}, -2);
burrybar.set('not-expired', {foo: 'bar'}, 10);
Burry.flushExpired();
expect(localStorage.getItem(burryfoo._internalKey('expired1'))).toBeNull();
expect(localStorage.getItem(burryfoo._expirationKey('expired1'))).toBeNull();
expect(localStorage.getItem(burryfoo._internalKey('expired2'))).toBeNull();
expect(localStorage.getItem(burryfoo._expirationKey('expired2'))).toBeNull();
expect(burryfoo.get('not-expired')).toBeDefined();
expect(localStorage.getItem(burrybar._internalKey('expired1'))).toBeNull();
expect(localStorage.getItem(burrybar._expirationKey('expired1'))).toBeNull();
expect(localStorage.getItem(burrybar._internalKey('expired2'))).toBeNull();
expect(localStorage.getItem(burrybar._expirationKey('expired2'))).toBeNull();
expect(burrybar.get('not-expired')).toBeDefined();
});
});
describe('Instance methods', function () {
var burry;
beforeEach(function () {
burry = new Burry.Store('');
});
it('allows to set a default ttl', function () {
burry = new Burry.Store('', 10);
burry.set('akey', {foo: 'bar'});
expect(localStorage.getItem('akey-_burry_')).toEqual('{"foo":"bar"}');
expect(parseInt(localStorage.getItem('akey-_burry_exp_'), 10)).toEqual(Burry._mEpoch() + 10);
});
it('calculates the key used internally', function () {
expect(burry._internalKey('akey')).toEqual('akey-_burry_');
});
it('calculates the expiration key used internally', function () {
expect(burry._expirationKey(12345)).toEqual('12345-_burry_exp_');
});
it('decides whether a key is a "burry" key', function () {
expect(burry._isInternalKey('foo-_burry_')).toEqual('foo');
expect(burry._isInternalKey('foo-_burry_bar')).toBeFalsy();
});
it('decides whether a key is a "burry" expiration key', function () {
expect(burry._isExpirationKey('foo-_burry_exp_')).toEqual('foo');
expect(burry._isExpirationKey('foo-_burry_exp_bar')).toBeFalsy();
});
it('applies correctly the namespace on the keys on construction', function () {
var nsburry = new Burry.Store('testing');
expect(nsburry._isInternalKey('foo-_burry_testing')).toEqual('foo');
expect(nsburry._isInternalKey('foo-_burry_')).toBeFalsy();
expect(nsburry._isExpirationKey('foo-_burry_exp_testing')).toEqual('foo');
expect(nsburry._isExpirationKey('foo-_burry_exp_')).toBeFalsy();
});
it('stores a key/value to localStorage', function () {
burry.set('akey', {foo: 'bar'});
expect(localStorage.getItem('akey-_burry_')).toEqual('{"foo":"bar"}');
});
it('stores a key/value to localStorage with an expiration time', function () {
burry.set('akey', {foo: 'bar'}, 10);
expect(localStorage.getItem('akey-_burry_')).toEqual('{"foo":"bar"}');
expect(parseInt(localStorage.getItem('akey-_burry_exp_'), 10)).toEqual(Burry._mEpoch() + 10);
});
it('returns the value from a stored key', function () {
burry.set('akey', {foo: 'bar'});
expect(burry.get('akey')).toEqual({foo: 'bar'});
});
it('returns undefined for a non-existing key', function () {
expect(burry.get('akey')).toBeUndefined();
});
it('returns undefined for an expired key, and removes it from localStorage', function () {
burry.set('akey', {foo: 'bar'}, -1);
expect(localStorage.getItem('akey-_burry_')).toEqual('{"foo":"bar"}');
expect(parseInt(localStorage.getItem('akey-_burry_exp_'), 10)).toEqual(Burry._mEpoch() - 1);
expect(burry.get('akey')).toBeUndefined();
expect(localStorage.getItem('akey-_burry_')).toBeNull();
expect(localStorage.getItem('akey-_burry_exp_')).toBeNull();
expect(burry.get('akey')).toBeUndefined();
});
it('adds a key/value when the key does not already exist or has expired', function () {
burry.set('akey', {foo: 'bar'});
burry.add('akey', {bar: 'foo'});
expect(burry.get('akey')).toEqual({foo: 'bar'});
burry.add('otherkey', {foo: 'bar'});
expect(burry.get('otherkey')).toEqual({foo: 'bar'});
burry.set('akey', {foo: 'bar'}, -10);
burry.add('akey', {bar: 'foo'});
expect(burry.get('akey')).toEqual({bar: 'foo'});
});
it('replaces a key/value only when the key already exists and has not expired', function () {
burry.set('akey', {foo: 'bar'});
burry.replace('akey', {bar: 'foo'});
expect(burry.get('akey')).toEqual({bar: 'foo'});
burry.replace('otherkey', {foo: 'bar'});
expect(burry.get('otherkey')).not.toBeDefined();
burry.set('akey', {foo: 'bar'}, -10);
burry.replace('akey', {bar: 'foo'});
expect(burry.get('akey')).not.toBeDefined();
});
it('removes a key/value', function () {
burry.set('akey', {foo: 'bar'});
burry.remove('akey');
expect(burry.get('akey')).toBeUndefined();
expect(localStorage.getItem('akey-_burry_')).toBeNull();
expect(localStorage.getItem('akey-_burry_exp_')).toBeNull();
});
it('increments a counter', function () {
burry.incr('counter');
expect(burry.get('counter')).toEqual(1);
burry.set('counter', 0);
burry.incr('counter');
burry.incr('counter');
expect(burry.get('counter')).toEqual(2);
});
it('decrements a counter', function () {
burry.decr('counter');
expect(burry.get('counter')).toEqual(-1);
burry.set('counter', 0);
burry.decr('counter');
burry.decr('counter');
expect(burry.get('counter')).toEqual(-2);
});
it('determines if an item has expired', function () {
burry.set('akey', {foo: 'bar'});
expect(burry.hasExpired('akey')).toBeFalsy();
burry.set('akey', {foo: 'bar'}, 10);
expect(burry.hasExpired('akey')).toBeFalsy();
burry.set('akey', {foo: 'bar'}, -10);
expect(burry.hasExpired('akey')).toBeTruthy();
});
it('returns all cache keys', function () {
var keys;
burry.set('expirable1', {foo: 'bar'}, 10);
burry.set('expirable2', {foo: 'bar'}, -20);
burry.set('non-expirable', {foo: 'bar'});
expect(burry.keys().indexOf('expirable1')).not.toEqual(-1);
expect(burry.keys().indexOf('expirable2')).not.toEqual(-1);
expect(burry.keys().indexOf('non-expirable')).not.toEqual(-1);
});
it('returns all expirable keys', function () {
var expirable, fakedate = new Date(0);
spyOn(window, 'Date').andReturn(fakedate);
burry.set('expirable1', {foo: 'bar'}, 10);
burry.set('expirable2', {foo: 'bar'}, 20);
burry.set('non-expirable', {foo: 'bar'});
expect(burry.expirableKeys()).toEqual({expirable1: 10, expirable2: 20});
});
it('flushes all Burry items', function () {
burry.set('expirable2', {foo: 'bar'}, 20);
burry.set('non-expirable', {foo: 'bar'});
localStorage.setItem('foo', 'bar');
burry.flush();
expect(localStorage.length).toEqual(2);
expect(localStorage.key(0)).toEqual('_burry_stores_');
expect(localStorage.key(1)).toEqual('foo');
});
it('flushes expired key/values', function () {
burry.set('expired1', {foo: 'bar'}, -1);
burry.set('expired2', {foo: 'bar'}, -2);
burry.set('not-expired', {foo: 'bar'}, 10);
burry.flushExpired();
expect(localStorage.getItem(burry._internalKey('expired1'))).toBeNull();
expect(localStorage.getItem(burry._expirationKey('expired1'))).toBeNull();
expect(localStorage.getItem(burry._internalKey('expired2'))).toBeNull();
expect(localStorage.getItem(burry._expirationKey('expired2'))).toBeNull();
expect(burry.get('not-expired')).toBeDefined();
});
it('removes expired objects when setting a value that does not fit in localStorage', function () {
var biggie = Array(1024*1024 + 1).join('0'),
key = '';
while (true) {
try {
key += 'key';
localStorage.setItem(burry._internalKey(key), JSON.stringify(biggie));
localStorage.setItem(burry._expirationKey(key), '0');
} catch (e) {
// The storage is now full.
break;
}
}
expect(localStorage.length > 0).toBeTruthy();
burry.set('biggie', biggie);
expect(localStorage.length).toEqual(2);
expect(burry.get('biggie')).toEqual(biggie);
});
});
});
})(this.Burry);
\ No newline at end of file
Copyright (c) 2008-2011 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = doc.location.search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'}),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
var isCommonJS = typeof window == "undefined";
/**
* Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
*
* @namespace
*/
var jasmine = {};
if (isCommonJS) exports.jasmine = jasmine;
/**
* @private
*/
jasmine.unimplementedMethod_ = function() {
throw new Error("unimplemented method");
};
/**
* Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
* a plain old variable and may be redefined by somebody else.
*
* @private
*/
jasmine.undefined = jasmine.___undefined___;
/**
* Show diagnostic messages in the console if set to true
*
*/
jasmine.VERBOSE = false;
/**
* Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
*
*/
jasmine.DEFAULT_UPDATE_INTERVAL = 250;
/**
* Default timeout interval in milliseconds for waitsFor() blocks.
*/
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
jasmine.getGlobal = function() {
function getGlobal() {
return this;
}
return getGlobal();
};
/**
* Allows for bound functions to be compared. Internal use only.
*
* @ignore
* @private
* @param base {Object} bound 'this' for the function
* @param name {Function} function to find
*/
jasmine.bindOriginal_ = function(base, name) {
var original = base[name];
if (original.apply) {
return function() {
return original.apply(base, arguments);
};
} else {
// IE support
return jasmine.getGlobal()[name];
}
};
jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
jasmine.MessageResult = function(values) {
this.type = 'log';
this.values = values;
this.trace = new Error(); // todo: test better
};
jasmine.MessageResult.prototype.toString = function() {
var text = "";
for (var i = 0; i < this.values.length; i++) {
if (i > 0) text += " ";
if (jasmine.isString_(this.values[i])) {
text += this.values[i];
} else {
text += jasmine.pp(this.values[i]);
}
}
return text;
};
jasmine.ExpectationResult = function(params) {
this.type = 'expect';
this.matcherName = params.matcherName;
this.passed_ = params.passed;
this.expected = params.expected;
this.actual = params.actual;
this.message = this.passed_ ? 'Passed.' : params.message;
var trace = (params.trace || new Error(this.message));
this.trace = this.passed_ ? '' : trace;
};
jasmine.ExpectationResult.prototype.toString = function () {
return this.message;
};
jasmine.ExpectationResult.prototype.passed = function () {
return this.passed_;
};
/**
* Getter for the Jasmine environment. Ensures one gets created
*/
jasmine.getEnv = function() {
var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
return env;
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isArray_ = function(value) {
return jasmine.isA_("Array", value);
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isString_ = function(value) {
return jasmine.isA_("String", value);
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isNumber_ = function(value) {
return jasmine.isA_("Number", value);
};
/**
* @ignore
* @private
* @param {String} typeName
* @param value
* @returns {Boolean}
*/
jasmine.isA_ = function(typeName, value) {
return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
};
/**
* Pretty printer for expecations. Takes any object and turns it into a human-readable string.
*
* @param value {Object} an object to be outputted
* @returns {String}
*/
jasmine.pp = function(value) {
var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
stringPrettyPrinter.format(value);
return stringPrettyPrinter.string;
};
/**
* Returns true if the object is a DOM Node.
*
* @param {Object} obj object to check
* @returns {Boolean}
*/
jasmine.isDomNode = function(obj) {
return obj.nodeType > 0;
};
/**
* Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
*
* @example
* // don't care about which function is passed in, as long as it's a function
* expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
*
* @param {Class} clazz
* @returns matchable object of the type clazz
*/
jasmine.any = function(clazz) {
return new jasmine.Matchers.Any(clazz);
};
/**
* Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
* attributes on the object.
*
* @example
* // don't care about any other attributes than foo.
* expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
*
* @param sample {Object} sample
* @returns matchable object for the sample
*/
jasmine.objectContaining = function (sample) {
return new jasmine.Matchers.ObjectContaining(sample);
};
/**
* Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
*
* Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
* expectation syntax. Spies can be checked if they were called or not and what the calling params were.
*
* A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
*
* Spies are torn down at the end of every spec.
*
* Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
*
* @example
* // a stub
* var myStub = jasmine.createSpy('myStub'); // can be used anywhere
*
* // spy example
* var foo = {
* not: function(bool) { return !bool; }
* }
*
* // actual foo.not will not be called, execution stops
* spyOn(foo, 'not');
// foo.not spied upon, execution will continue to implementation
* spyOn(foo, 'not').andCallThrough();
*
* // fake example
* var foo = {
* not: function(bool) { return !bool; }
* }
*
* // foo.not(val) will return val
* spyOn(foo, 'not').andCallFake(function(value) {return value;});
*
* // mock example
* foo.not(7 == 7);
* expect(foo.not).toHaveBeenCalled();
* expect(foo.not).toHaveBeenCalledWith(true);
*
* @constructor
* @see spyOn, jasmine.createSpy, jasmine.createSpyObj
* @param {String} name
*/
jasmine.Spy = function(name) {
/**
* The name of the spy, if provided.
*/
this.identity = name || 'unknown';
/**
* Is this Object a spy?
*/
this.isSpy = true;
/**
* The actual function this spy stubs.
*/
this.plan = function() {
};
/**
* Tracking of the most recent call to the spy.
* @example
* var mySpy = jasmine.createSpy('foo');
* mySpy(1, 2);
* mySpy.mostRecentCall.args = [1, 2];
*/
this.mostRecentCall = {};
/**
* Holds arguments for each call to the spy, indexed by call count
* @example
* var mySpy = jasmine.createSpy('foo');
* mySpy(1, 2);
* mySpy(7, 8);
* mySpy.mostRecentCall.args = [7, 8];
* mySpy.argsForCall[0] = [1, 2];
* mySpy.argsForCall[1] = [7, 8];
*/
this.argsForCall = [];
this.calls = [];
};
/**
* Tells a spy to call through to the actual implemenatation.
*
* @example
* var foo = {
* bar: function() { // do some stuff }
* }
*
* // defining a spy on an existing property: foo.bar
* spyOn(foo, 'bar').andCallThrough();
*/
jasmine.Spy.prototype.andCallThrough = function() {
this.plan = this.originalValue;
return this;
};
/**
* For setting the return value of a spy.
*
* @example
* // defining a spy from scratch: foo() returns 'baz'
* var foo = jasmine.createSpy('spy on foo').andReturn('baz');
*
* // defining a spy on an existing property: foo.bar() returns 'baz'
* spyOn(foo, 'bar').andReturn('baz');
*
* @param {Object} value
*/
jasmine.Spy.prototype.andReturn = function(value) {
this.plan = function() {
return value;
};
return this;
};
/**
* For throwing an exception when a spy is called.
*
* @example
* // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
* var foo = jasmine.createSpy('spy on foo').andThrow('baz');
*
* // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
* spyOn(foo, 'bar').andThrow('baz');
*
* @param {String} exceptionMsg
*/
jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
this.plan = function() {
throw exceptionMsg;
};
return this;
};
/**
* Calls an alternate implementation when a spy is called.
*
* @example
* var baz = function() {
* // do some stuff, return something
* }
* // defining a spy from scratch: foo() calls the function baz
* var foo = jasmine.createSpy('spy on foo').andCall(baz);
*
* // defining a spy on an existing property: foo.bar() calls an anonymnous function
* spyOn(foo, 'bar').andCall(function() { return 'baz';} );
*
* @param {Function} fakeFunc
*/
jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
this.plan = fakeFunc;
return this;
};
/**
* Resets all of a spy's the tracking variables so that it can be used again.
*
* @example
* spyOn(foo, 'bar');
*
* foo.bar();
*
* expect(foo.bar.callCount).toEqual(1);
*
* foo.bar.reset();
*
* expect(foo.bar.callCount).toEqual(0);
*/
jasmine.Spy.prototype.reset = function() {
this.wasCalled = false;
this.callCount = 0;
this.argsForCall = [];
this.calls = [];
this.mostRecentCall = {};
};
jasmine.createSpy = function(name) {
var spyObj = function() {
spyObj.wasCalled = true;
spyObj.callCount++;
var args = jasmine.util.argsToArray(arguments);
spyObj.mostRecentCall.object = this;
spyObj.mostRecentCall.args = args;
spyObj.argsForCall.push(args);
spyObj.calls.push({object: this, args: args});
return spyObj.plan.apply(this, arguments);
};
var spy = new jasmine.Spy(name);
for (var prop in spy) {
spyObj[prop] = spy[prop];
}
spyObj.reset();
return spyObj;
};
/**
* Determines whether an object is a spy.
*
* @param {jasmine.Spy|Object} putativeSpy
* @returns {Boolean}
*/
jasmine.isSpy = function(putativeSpy) {
return putativeSpy && putativeSpy.isSpy;
};
/**
* Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
* large in one call.
*
* @param {String} baseName name of spy class
* @param {Array} methodNames array of names of methods to make spies
*/
jasmine.createSpyObj = function(baseName, methodNames) {
if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
}
var obj = {};
for (var i = 0; i < methodNames.length; i++) {
obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
}
return obj;
};
/**
* All parameters are pretty-printed and concatenated together, then written to the current spec's output.
*
* Be careful not to leave calls to <code>jasmine.log</code> in production code.
*/
jasmine.log = function() {
var spec = jasmine.getEnv().currentSpec;
spec.log.apply(spec, arguments);
};
/**
* Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
*
* @example
* // spy example
* var foo = {
* not: function(bool) { return !bool; }
* }
* spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
*
* @see jasmine.createSpy
* @param obj
* @param methodName
* @returns a Jasmine spy that can be chained with all spy methods
*/
var spyOn = function(obj, methodName) {
return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
};
if (isCommonJS) exports.spyOn = spyOn;
/**
* Creates a Jasmine spec that will be added to the current suite.
*
* // TODO: pending tests
*
* @example
* it('should be true', function() {
* expect(true).toEqual(true);
* });
*
* @param {String} desc description of this specification
* @param {Function} func defines the preconditions and expectations of the spec
*/
var it = function(desc, func) {
return jasmine.getEnv().it(desc, func);
};
if (isCommonJS) exports.it = it;
/**
* Creates a <em>disabled</em> Jasmine spec.
*
* A convenience method that allows existing specs to be disabled temporarily during development.
*
* @param {String} desc description of this specification
* @param {Function} func defines the preconditions and expectations of the spec
*/
var xit = function(desc, func) {
return jasmine.getEnv().xit(desc, func);
};
if (isCommonJS) exports.xit = xit;
/**
* Starts a chain for a Jasmine expectation.
*
* It is passed an Object that is the actual value and should chain to one of the many
* jasmine.Matchers functions.
*
* @param {Object} actual Actual value to test against and expected value
*/
var expect = function(actual) {
return jasmine.getEnv().currentSpec.expect(actual);
};
if (isCommonJS) exports.expect = expect;
/**
* Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
*
* @param {Function} func Function that defines part of a jasmine spec.
*/
var runs = function(func) {
jasmine.getEnv().currentSpec.runs(func);
};
if (isCommonJS) exports.runs = runs;
/**
* Waits a fixed time period before moving to the next block.
*
* @deprecated Use waitsFor() instead
* @param {Number} timeout milliseconds to wait
*/
var waits = function(timeout) {
jasmine.getEnv().currentSpec.waits(timeout);
};
if (isCommonJS) exports.waits = waits;
/**
* Waits for the latchFunction to return true before proceeding to the next block.
*
* @param {Function} latchFunction
* @param {String} optional_timeoutMessage
* @param {Number} optional_timeout
*/
var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
};
if (isCommonJS) exports.waitsFor = waitsFor;
/**
* A function that is called before each spec in a suite.
*
* Used for spec setup, including validating assumptions.
*
* @param {Function} beforeEachFunction
*/
var beforeEach = function(beforeEachFunction) {
jasmine.getEnv().beforeEach(beforeEachFunction);
};
if (isCommonJS) exports.beforeEach = beforeEach;
/**
* A function that is called after each spec in a suite.
*
* Used for restoring any state that is hijacked during spec execution.
*
* @param {Function} afterEachFunction
*/
var afterEach = function(afterEachFunction) {
jasmine.getEnv().afterEach(afterEachFunction);
};
if (isCommonJS) exports.afterEach = afterEach;
/**
* Defines a suite of specifications.
*
* Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
* are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
* of setup in some tests.
*
* @example
* // TODO: a simple suite
*
* // TODO: a simple suite with a nested describe block
*
* @param {String} description A string, usually the class under test.
* @param {Function} specDefinitions function that defines several specs.
*/
var describe = function(description, specDefinitions) {
return jasmine.getEnv().describe(description, specDefinitions);
};
if (isCommonJS) exports.describe = describe;
/**
* Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
*
* @param {String} description A string, usually the class under test.
* @param {Function} specDefinitions function that defines several specs.
*/
var xdescribe = function(description, specDefinitions) {
return jasmine.getEnv().xdescribe(description, specDefinitions);
};
if (isCommonJS) exports.xdescribe = xdescribe;
// Provide the XMLHttpRequest class for IE 5.x-6.x:
jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
function tryIt(f) {
try {
return f();
} catch(e) {
}
return null;
}
var xhr = tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
}) ||
tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
}) ||
tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP");
}) ||
tryIt(function() {
return new ActiveXObject("Microsoft.XMLHTTP");
});
if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
return xhr;
} : XMLHttpRequest;
/**
* @namespace
*/
jasmine.util = {};
/**
* Declare that a child class inherit it's prototype from the parent class.
*
* @private
* @param {Function} childClass
* @param {Function} parentClass
*/
jasmine.util.inherit = function(childClass, parentClass) {
/**
* @private
*/
var subclass = function() {
};
subclass.prototype = parentClass.prototype;
childClass.prototype = new subclass();
};
jasmine.util.formatException = function(e) {
var lineNumber;
if (e.line) {
lineNumber = e.line;
}
else if (e.lineNumber) {
lineNumber = e.lineNumber;
}
var file;
if (e.sourceURL) {
file = e.sourceURL;
}
else if (e.fileName) {
file = e.fileName;
}
var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
if (file && lineNumber) {
message += ' in ' + file + ' (line ' + lineNumber + ')';
}
return message;
};
jasmine.util.htmlEscape = function(str) {
if (!str) return str;
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
jasmine.util.argsToArray = function(args) {
var arrayOfArgs = [];
for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
return arrayOfArgs;
};
jasmine.util.extend = function(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
};
/**
* Environment for Jasmine
*
* @constructor
*/
jasmine.Env = function() {
this.currentSpec = null;
this.currentSuite = null;
this.currentRunner_ = new jasmine.Runner(this);
this.reporter = new jasmine.MultiReporter();
this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
this.lastUpdate = 0;
this.specFilter = function() {
return true;
};
this.nextSpecId_ = 0;
this.nextSuiteId_ = 0;
this.equalityTesters_ = [];
// wrap matchers
this.matchersClass = function() {
jasmine.Matchers.apply(this, arguments);
};
jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
};
jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
jasmine.Env.prototype.setInterval = jasmine.setInterval;
jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
/**
* @returns an object containing jasmine version build info, if set.
*/
jasmine.Env.prototype.version = function () {
if (jasmine.version_) {
return jasmine.version_;
} else {
throw new Error('Version not set');
}
};
/**
* @returns string containing jasmine version build info, if set.
*/
jasmine.Env.prototype.versionString = function() {
if (!jasmine.version_) {
return "version unknown";
}
var version = this.version();
var versionString = version.major + "." + version.minor + "." + version.build;
if (version.release_candidate) {
versionString += ".rc" + version.release_candidate;
}
versionString += " revision " + version.revision;
return versionString;
};
/**
* @returns a sequential integer starting at 0
*/
jasmine.Env.prototype.nextSpecId = function () {
return this.nextSpecId_++;
};
/**
* @returns a sequential integer starting at 0
*/
jasmine.Env.prototype.nextSuiteId = function () {
return this.nextSuiteId_++;
};
/**
* Register a reporter to receive status updates from Jasmine.
* @param {jasmine.Reporter} reporter An object which will receive status updates.
*/
jasmine.Env.prototype.addReporter = function(reporter) {
this.reporter.addReporter(reporter);
};
jasmine.Env.prototype.execute = function() {
this.currentRunner_.execute();
};
jasmine.Env.prototype.describe = function(description, specDefinitions) {
var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
var parentSuite = this.currentSuite;
if (parentSuite) {
parentSuite.add(suite);
} else {
this.currentRunner_.add(suite);
}
this.currentSuite = suite;
var declarationError = null;
try {
specDefinitions.call(suite);
} catch(e) {
declarationError = e;
}
if (declarationError) {
this.it("encountered a declaration exception", function() {
throw declarationError;
});
}
this.currentSuite = parentSuite;
return suite;
};
jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
if (this.currentSuite) {
this.currentSuite.beforeEach(beforeEachFunction);
} else {
this.currentRunner_.beforeEach(beforeEachFunction);
}
};
jasmine.Env.prototype.currentRunner = function () {
return this.currentRunner_;
};
jasmine.Env.prototype.afterEach = function(afterEachFunction) {
if (this.currentSuite) {
this.currentSuite.afterEach(afterEachFunction);
} else {
this.currentRunner_.afterEach(afterEachFunction);
}
};
jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
return {
execute: function() {
}
};
};
jasmine.Env.prototype.it = function(description, func) {
var spec = new jasmine.Spec(this, this.currentSuite, description);
this.currentSuite.add(spec);
this.currentSpec = spec;
if (func) {
spec.runs(func);
}
return spec;
};
jasmine.Env.prototype.xit = function(desc, func) {
return {
id: this.nextSpecId(),
runs: function() {
}
};
};
jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
return true;
}
a.__Jasmine_been_here_before__ = b;
b.__Jasmine_been_here_before__ = a;
var hasKey = function(obj, keyName) {
return obj !== null && obj[keyName] !== jasmine.undefined;
};
for (var property in b) {
if (!hasKey(a, property) && hasKey(b, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
}
for (property in a) {
if (!hasKey(b, property) && hasKey(a, property)) {
mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
}
}
for (property in b) {
if (property == '__Jasmine_been_here_before__') continue;
if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
}
}
if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
mismatchValues.push("arrays were not the same length");
}
delete a.__Jasmine_been_here_before__;
delete b.__Jasmine_been_here_before__;
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
for (var i = 0; i < this.equalityTesters_.length; i++) {
var equalityTester = this.equalityTesters_[i];
var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
if (result !== jasmine.undefined) return result;
}
if (a === b) return true;
if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
return (a == jasmine.undefined && b == jasmine.undefined);
}
if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
return a === b;
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() == b.getTime();
}
if (a.jasmineMatches) {
return a.jasmineMatches(b);
}
if (b.jasmineMatches) {
return b.jasmineMatches(a);
}
if (a instanceof jasmine.Matchers.ObjectContaining) {
return a.matches(b);
}
if (b instanceof jasmine.Matchers.ObjectContaining) {
return b.matches(a);
}
if (jasmine.isString_(a) && jasmine.isString_(b)) {
return (a == b);
}
if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
return (a == b);
}
if (typeof a === "object" && typeof b === "object") {
return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
}
//Straight check
return (a === b);
};
jasmine.Env.prototype.contains_ = function(haystack, needle) {
if (jasmine.isArray_(haystack)) {
for (var i = 0; i < haystack.length; i++) {
if (this.equals_(haystack[i], needle)) return true;
}
return false;
}
return haystack.indexOf(needle) >= 0;
};
jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
this.equalityTesters_.push(equalityTester);
};
/** No-op base class for Jasmine reporters.
*
* @constructor
*/
jasmine.Reporter = function() {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportSpecResults = function(spec) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.log = function(str) {
};
/**
* Blocks are functions with executable code that make up a spec.
*
* @constructor
* @param {jasmine.Env} env
* @param {Function} func
* @param {jasmine.Spec} spec
*/
jasmine.Block = function(env, func, spec) {
this.env = env;
this.func = func;
this.spec = spec;
};
jasmine.Block.prototype.execute = function(onComplete) {
try {
this.func.apply(this.spec);
} catch (e) {
this.spec.fail(e);
}
onComplete();
};
/** JavaScript API reporter.
*
* @constructor
*/
jasmine.JsApiReporter = function() {
this.started = false;
this.finished = false;
this.suites_ = [];
this.results_ = {};
};
jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
this.started = true;
var suites = runner.topLevelSuites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
this.suites_.push(this.summarize_(suite));
}
};
jasmine.JsApiReporter.prototype.suites = function() {
return this.suites_;
};
jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
var isSuite = suiteOrSpec instanceof jasmine.Suite;
var summary = {
id: suiteOrSpec.id,
name: suiteOrSpec.description,
type: isSuite ? 'suite' : 'spec',
children: []
};
if (isSuite) {
var children = suiteOrSpec.children();
for (var i = 0; i < children.length; i++) {
summary.children.push(this.summarize_(children[i]));
}
}
return summary;
};
jasmine.JsApiReporter.prototype.results = function() {
return this.results_;
};
jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
return this.results_[specId];
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
this.finished = true;
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
this.results_[spec.id] = {
messages: spec.results().getItems(),
result: spec.results().failedCount > 0 ? "failed" : "passed"
};
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.log = function(str) {
};
jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
var results = {};
for (var i = 0; i < specIds.length; i++) {
var specId = specIds[i];
results[specId] = this.summarizeResult_(this.results_[specId]);
}
return results;
};
jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
var summaryMessages = [];
var messagesLength = result.messages.length;
for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
var resultMessage = result.messages[messageIndex];
summaryMessages.push({
text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
passed: resultMessage.passed ? resultMessage.passed() : true,
type: resultMessage.type,
message: resultMessage.message,
trace: {
stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
}
});
}
return {
result : result.result,
messages : summaryMessages
};
};
/**
* @constructor
* @param {jasmine.Env} env
* @param actual
* @param {jasmine.Spec} spec
*/
jasmine.Matchers = function(env, actual, spec, opt_isNot) {
this.env = env;
this.actual = actual;
this.spec = spec;
this.isNot = opt_isNot || false;
this.reportWasCalled_ = false;
};
// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
jasmine.Matchers.pp = function(str) {
throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
};
// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
jasmine.Matchers.prototype.report = function(result, failing_message, details) {
throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
};
jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
for (var methodName in prototype) {
if (methodName == 'report') continue;
var orig = prototype[methodName];
matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
}
};
jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
return function() {
var matcherArgs = jasmine.util.argsToArray(arguments);
var result = matcherFunction.apply(this, arguments);
if (this.isNot) {
result = !result;
}
if (this.reportWasCalled_) return result;
var message;
if (!result) {
if (this.message) {
message = this.message.apply(this, arguments);
if (jasmine.isArray_(message)) {
message = message[this.isNot ? 1 : 0];
}
} else {
var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
if (matcherArgs.length > 0) {
for (var i = 0; i < matcherArgs.length; i++) {
if (i > 0) message += ",";
message += " " + jasmine.pp(matcherArgs[i]);
}
}
message += ".";
}
}
var expectationResult = new jasmine.ExpectationResult({
matcherName: matcherName,
passed: result,
expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
actual: this.actual,
message: message
});
this.spec.addMatcherResult(expectationResult);
return jasmine.undefined;
};
};
/**
* toBe: compares the actual to the expected using ===
* @param expected
*/
jasmine.Matchers.prototype.toBe = function(expected) {
return this.actual === expected;
};
/**
* toNotBe: compares the actual to the expected using !==
* @param expected
* @deprecated as of 1.0. Use not.toBe() instead.
*/
jasmine.Matchers.prototype.toNotBe = function(expected) {
return this.actual !== expected;
};
/**
* toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
*
* @param expected
*/
jasmine.Matchers.prototype.toEqual = function(expected) {
return this.env.equals_(this.actual, expected);
};
/**
* toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
* @param expected
* @deprecated as of 1.0. Use not.toEqual() instead.
*/
jasmine.Matchers.prototype.toNotEqual = function(expected) {
return !this.env.equals_(this.actual, expected);
};
/**
* Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
* a pattern or a String.
*
* @param expected
*/
jasmine.Matchers.prototype.toMatch = function(expected) {
return new RegExp(expected).test(this.actual);
};
/**
* Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
* @param expected
* @deprecated as of 1.0. Use not.toMatch() instead.
*/
jasmine.Matchers.prototype.toNotMatch = function(expected) {
return !(new RegExp(expected).test(this.actual));
};
/**
* Matcher that compares the actual to jasmine.undefined.
*/
jasmine.Matchers.prototype.toBeDefined = function() {
return (this.actual !== jasmine.undefined);
};
/**
* Matcher that compares the actual to jasmine.undefined.
*/
jasmine.Matchers.prototype.toBeUndefined = function() {
return (this.actual === jasmine.undefined);
};
/**
* Matcher that compares the actual to null.
*/
jasmine.Matchers.prototype.toBeNull = function() {
return (this.actual === null);
};
/**
* Matcher that boolean not-nots the actual.
*/
jasmine.Matchers.prototype.toBeTruthy = function() {
return !!this.actual;
};
/**
* Matcher that boolean nots the actual.
*/
jasmine.Matchers.prototype.toBeFalsy = function() {
return !this.actual;
};
/**
* Matcher that checks to see if the actual, a Jasmine spy, was called.
*/
jasmine.Matchers.prototype.toHaveBeenCalled = function() {
if (arguments.length > 0) {
throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
}
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
return [
"Expected spy " + this.actual.identity + " to have been called.",
"Expected spy " + this.actual.identity + " not to have been called."
];
};
return this.actual.wasCalled;
};
/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
/**
* Matcher that checks to see if the actual, a Jasmine spy, was not called.
*
* @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
*/
jasmine.Matchers.prototype.wasNotCalled = function() {
if (arguments.length > 0) {
throw new Error('wasNotCalled does not take arguments');
}
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
return [
"Expected spy " + this.actual.identity + " to not have been called.",
"Expected spy " + this.actual.identity + " to have been called."
];
};
return !this.actual.wasCalled;
};
/**
* Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
*
* @example
*
*/
jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
var expectedArgs = jasmine.util.argsToArray(arguments);
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
if (this.actual.callCount === 0) {
// todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
return [
"Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
"Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
];
} else {
return [
"Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
"Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
];
}
};
return this.env.contains_(this.actual.argsForCall, expectedArgs);
};
/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
jasmine.Matchers.prototype.wasNotCalledWith = function() {
var expectedArgs = jasmine.util.argsToArray(arguments);
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
return [
"Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
"Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
];
};
return !this.env.contains_(this.actual.argsForCall, expectedArgs);
};
/**
* Matcher that checks that the expected item is an element in the actual Array.
*
* @param {Object} expected
*/
jasmine.Matchers.prototype.toContain = function(expected) {
return this.env.contains_(this.actual, expected);
};
/**
* Matcher that checks that the expected item is NOT an element in the actual Array.
*
* @param {Object} expected
* @deprecated as of 1.0. Use not.toContain() instead.
*/
jasmine.Matchers.prototype.toNotContain = function(expected) {
return !this.env.contains_(this.actual, expected);
};
jasmine.Matchers.prototype.toBeLessThan = function(expected) {
return this.actual < expected;
};
jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
return this.actual > expected;
};
/**
* Matcher that checks that the expected item is equal to the actual item
* up to a given level of decimal precision (default 2).
*
* @param {Number} expected
* @param {Number} precision
*/
jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
if (!(precision === 0)) {
precision = precision || 2;
}
var multiplier = Math.pow(10, precision);
var actual = Math.round(this.actual * multiplier);
expected = Math.round(expected * multiplier);
return expected == actual;
};
/**
* Matcher that checks that the expected exception was thrown by the actual.
*
* @param {String} expected
*/
jasmine.Matchers.prototype.toThrow = function(expected) {
var result = false;
var exception;
if (typeof this.actual != 'function') {
throw new Error('Actual is not a function');
}
try {
this.actual();
} catch (e) {
exception = e;
}
if (exception) {
result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
}
var not = this.isNot ? "not " : "";
this.message = function() {
if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
} else {
return "Expected function to throw an exception.";
}
};
return result;
};
jasmine.Matchers.Any = function(expectedClass) {
this.expectedClass = expectedClass;
};
jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
if (this.expectedClass == String) {
return typeof other == 'string' || other instanceof String;
}
if (this.expectedClass == Number) {
return typeof other == 'number' || other instanceof Number;
}
if (this.expectedClass == Function) {
return typeof other == 'function' || other instanceof Function;
}
if (this.expectedClass == Object) {
return typeof other == 'object';
}
return other instanceof this.expectedClass;
};
jasmine.Matchers.Any.prototype.jasmineToString = function() {
return '<jasmine.any(' + this.expectedClass + ')>';
};
jasmine.Matchers.ObjectContaining = function (sample) {
this.sample = sample;
};
jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
var env = jasmine.getEnv();
var hasKey = function(obj, keyName) {
return obj != null && obj[keyName] !== jasmine.undefined;
};
for (var property in this.sample) {
if (!hasKey(other, property) && hasKey(this.sample, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
}
}
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
};
// Mock setTimeout, clearTimeout
// Contributed by Pivotal Computer Systems, www.pivotalsf.com
jasmine.FakeTimer = function() {
this.reset();
var self = this;
self.setTimeout = function(funcToCall, millis) {
self.timeoutsMade++;
self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
return self.timeoutsMade;
};
self.setInterval = function(funcToCall, millis) {
self.timeoutsMade++;
self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
return self.timeoutsMade;
};
self.clearTimeout = function(timeoutKey) {
self.scheduledFunctions[timeoutKey] = jasmine.undefined;
};
self.clearInterval = function(timeoutKey) {
self.scheduledFunctions[timeoutKey] = jasmine.undefined;
};
};
jasmine.FakeTimer.prototype.reset = function() {
this.timeoutsMade = 0;
this.scheduledFunctions = {};
this.nowMillis = 0;
};
jasmine.FakeTimer.prototype.tick = function(millis) {
var oldMillis = this.nowMillis;
var newMillis = oldMillis + millis;
this.runFunctionsWithinRange(oldMillis, newMillis);
this.nowMillis = newMillis;
};
jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
var scheduledFunc;
var funcsToRun = [];
for (var timeoutKey in this.scheduledFunctions) {
scheduledFunc = this.scheduledFunctions[timeoutKey];
if (scheduledFunc != jasmine.undefined &&
scheduledFunc.runAtMillis >= oldMillis &&
scheduledFunc.runAtMillis <= nowMillis) {
funcsToRun.push(scheduledFunc);
this.scheduledFunctions[timeoutKey] = jasmine.undefined;
}
}
if (funcsToRun.length > 0) {
funcsToRun.sort(function(a, b) {
return a.runAtMillis - b.runAtMillis;
});
for (var i = 0; i < funcsToRun.length; ++i) {
try {
var funcToRun = funcsToRun[i];
this.nowMillis = funcToRun.runAtMillis;
funcToRun.funcToCall();
if (funcToRun.recurring) {
this.scheduleFunction(funcToRun.timeoutKey,
funcToRun.funcToCall,
funcToRun.millis,
true);
}
} catch(e) {
}
}
this.runFunctionsWithinRange(oldMillis, nowMillis);
}
};
jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
this.scheduledFunctions[timeoutKey] = {
runAtMillis: this.nowMillis + millis,
funcToCall: funcToCall,
recurring: recurring,
timeoutKey: timeoutKey,
millis: millis
};
};
/**
* @namespace
*/
jasmine.Clock = {
defaultFakeTimer: new jasmine.FakeTimer(),
reset: function() {
jasmine.Clock.assertInstalled();
jasmine.Clock.defaultFakeTimer.reset();
},
tick: function(millis) {
jasmine.Clock.assertInstalled();
jasmine.Clock.defaultFakeTimer.tick(millis);
},
runFunctionsWithinRange: function(oldMillis, nowMillis) {
jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
},
scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
},
useMock: function() {
if (!jasmine.Clock.isInstalled()) {
var spec = jasmine.getEnv().currentSpec;
spec.after(jasmine.Clock.uninstallMock);
jasmine.Clock.installMock();
}
},
installMock: function() {
jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
},
uninstallMock: function() {
jasmine.Clock.assertInstalled();
jasmine.Clock.installed = jasmine.Clock.real;
},
real: {
setTimeout: jasmine.getGlobal().setTimeout,
clearTimeout: jasmine.getGlobal().clearTimeout,
setInterval: jasmine.getGlobal().setInterval,
clearInterval: jasmine.getGlobal().clearInterval
},
assertInstalled: function() {
if (!jasmine.Clock.isInstalled()) {
throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
}
},
isInstalled: function() {
return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
},
installed: null
};
jasmine.Clock.installed = jasmine.Clock.real;
//else for IE support
jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
if (jasmine.Clock.installed.setTimeout.apply) {
return jasmine.Clock.installed.setTimeout.apply(this, arguments);
} else {
return jasmine.Clock.installed.setTimeout(funcToCall, millis);
}
};
jasmine.getGlobal().setInterval = function(funcToCall, millis) {
if (jasmine.Clock.installed.setInterval.apply) {
return jasmine.Clock.installed.setInterval.apply(this, arguments);
} else {
return jasmine.Clock.installed.setInterval(funcToCall, millis);
}
};
jasmine.getGlobal().clearTimeout = function(timeoutKey) {
if (jasmine.Clock.installed.clearTimeout.apply) {
return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
} else {
return jasmine.Clock.installed.clearTimeout(timeoutKey);
}
};
jasmine.getGlobal().clearInterval = function(timeoutKey) {
if (jasmine.Clock.installed.clearTimeout.apply) {
return jasmine.Clock.installed.clearInterval.apply(this, arguments);
} else {
return jasmine.Clock.installed.clearInterval(timeoutKey);
}
};
/**
* @constructor
*/
jasmine.MultiReporter = function() {
this.subReporters_ = [];
};
jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
jasmine.MultiReporter.prototype.addReporter = function(reporter) {
this.subReporters_.push(reporter);
};
(function() {
var functionNames = [
"reportRunnerStarting",
"reportRunnerResults",
"reportSuiteResults",
"reportSpecStarting",
"reportSpecResults",
"log"
];
for (var i = 0; i < functionNames.length; i++) {
var functionName = functionNames[i];
jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
return function() {
for (var j = 0; j < this.subReporters_.length; j++) {
var subReporter = this.subReporters_[j];
if (subReporter[functionName]) {
subReporter[functionName].apply(subReporter, arguments);
}
}
};
})(functionName);
}
})();
/**
* Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
*
* @constructor
*/
jasmine.NestedResults = function() {
/**
* The total count of results
*/
this.totalCount = 0;
/**
* Number of passed results
*/
this.passedCount = 0;
/**
* Number of failed results
*/
this.failedCount = 0;
/**
* Was this suite/spec skipped?
*/
this.skipped = false;
/**
* @ignore
*/
this.items_ = [];
};
/**
* Roll up the result counts.
*
* @param result
*/
jasmine.NestedResults.prototype.rollupCounts = function(result) {
this.totalCount += result.totalCount;
this.passedCount += result.passedCount;
this.failedCount += result.failedCount;
};
/**
* Adds a log message.
* @param values Array of message parts which will be concatenated later.
*/
jasmine.NestedResults.prototype.log = function(values) {
this.items_.push(new jasmine.MessageResult(values));
};
/**
* Getter for the results: message & results.
*/
jasmine.NestedResults.prototype.getItems = function() {
return this.items_;
};
/**
* Adds a result, tracking counts (total, passed, & failed)
* @param {jasmine.ExpectationResult|jasmine.NestedResults} result
*/
jasmine.NestedResults.prototype.addResult = function(result) {
if (result.type != 'log') {
if (result.items_) {
this.rollupCounts(result);
} else {
this.totalCount++;
if (result.passed()) {
this.passedCount++;
} else {
this.failedCount++;
}
}
}
this.items_.push(result);
};
/**
* @returns {Boolean} True if <b>everything</b> below passed
*/
jasmine.NestedResults.prototype.passed = function() {
return this.passedCount === this.totalCount;
};
/**
* Base class for pretty printing for expectation results.
*/
jasmine.PrettyPrinter = function() {
this.ppNestLevel_ = 0;
};
/**
* Formats a value in a nice, human-readable string.
*
* @param value
*/
jasmine.PrettyPrinter.prototype.format = function(value) {
if (this.ppNestLevel_ > 40) {
throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
}
this.ppNestLevel_++;
try {
if (value === jasmine.undefined) {
this.emitScalar('undefined');
} else if (value === null) {
this.emitScalar('null');
} else if (value === jasmine.getGlobal()) {
this.emitScalar('<global>');
} else if (value.jasmineToString) {
this.emitScalar(value.jasmineToString());
} else if (typeof value === 'string') {
this.emitString(value);
} else if (jasmine.isSpy(value)) {
this.emitScalar("spy on " + value.identity);
} else if (value instanceof RegExp) {
this.emitScalar(value.toString());
} else if (typeof value === 'function') {
this.emitScalar('Function');
} else if (typeof value.nodeType === 'number') {
this.emitScalar('HTMLNode');
} else if (value instanceof Date) {
this.emitScalar('Date(' + value + ')');
} else if (value.__Jasmine_been_here_before__) {
this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
} else if (jasmine.isArray_(value) || typeof value == 'object') {
value.__Jasmine_been_here_before__ = true;
if (jasmine.isArray_(value)) {
this.emitArray(value);
} else {
this.emitObject(value);
}
delete value.__Jasmine_been_here_before__;
} else {
this.emitScalar(value.toString());
}
} finally {
this.ppNestLevel_--;
}
};
jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
for (var property in obj) {
if (property == '__Jasmine_been_here_before__') continue;
fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
obj.__lookupGetter__(property) !== null) : false);
}
};
jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
jasmine.StringPrettyPrinter = function() {
jasmine.PrettyPrinter.call(this);
this.string = '';
};
jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
this.append(value);
};
jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
this.append("'" + value + "'");
};
jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
this.append('[ ');
for (var i = 0; i < array.length; i++) {
if (i > 0) {
this.append(', ');
}
this.format(array[i]);
}
this.append(' ]');
};
jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
var self = this;
this.append('{ ');
var first = true;
this.iterateObject(obj, function(property, isGetter) {
if (first) {
first = false;
} else {
self.append(', ');
}
self.append(property);
self.append(' : ');
if (isGetter) {
self.append('<getter>');
} else {
self.format(obj[property]);
}
});
this.append(' }');
};
jasmine.StringPrettyPrinter.prototype.append = function(value) {
this.string += value;
};
jasmine.Queue = function(env) {
this.env = env;
this.blocks = [];
this.running = false;
this.index = 0;
this.offset = 0;
this.abort = false;
};
jasmine.Queue.prototype.addBefore = function(block) {
this.blocks.unshift(block);
};
jasmine.Queue.prototype.add = function(block) {
this.blocks.push(block);
};
jasmine.Queue.prototype.insertNext = function(block) {
this.blocks.splice((this.index + this.offset + 1), 0, block);
this.offset++;
};
jasmine.Queue.prototype.start = function(onComplete) {
this.running = true;
this.onComplete = onComplete;
this.next_();
};
jasmine.Queue.prototype.isRunning = function() {
return this.running;
};
jasmine.Queue.LOOP_DONT_RECURSE = true;
jasmine.Queue.prototype.next_ = function() {
var self = this;
var goAgain = true;
while (goAgain) {
goAgain = false;
if (self.index < self.blocks.length && !this.abort) {
var calledSynchronously = true;
var completedSynchronously = false;
var onComplete = function () {
if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
completedSynchronously = true;
return;
}
if (self.blocks[self.index].abort) {
self.abort = true;
}
self.offset = 0;
self.index++;
var now = new Date().getTime();
if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
self.env.lastUpdate = now;
self.env.setTimeout(function() {
self.next_();
}, 0);
} else {
if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
goAgain = true;
} else {
self.next_();
}
}
};
self.blocks[self.index].execute(onComplete);
calledSynchronously = false;
if (completedSynchronously) {
onComplete();
}
} else {
self.running = false;
if (self.onComplete) {
self.onComplete();
}
}
}
};
jasmine.Queue.prototype.results = function() {
var results = new jasmine.NestedResults();
for (var i = 0; i < this.blocks.length; i++) {
if (this.blocks[i].results) {
results.addResult(this.blocks[i].results());
}
}
return results;
};
/**
* Runner
*
* @constructor
* @param {jasmine.Env} env
*/
jasmine.Runner = function(env) {
var self = this;
self.env = env;
self.queue = new jasmine.Queue(env);
self.before_ = [];
self.after_ = [];
self.suites_ = [];
};
jasmine.Runner.prototype.execute = function() {
var self = this;
if (self.env.reporter.reportRunnerStarting) {
self.env.reporter.reportRunnerStarting(this);
}
self.queue.start(function () {
self.finishCallback();
});
};
jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
beforeEachFunction.typeName = 'beforeEach';
this.before_.splice(0,0,beforeEachFunction);
};
jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
afterEachFunction.typeName = 'afterEach';
this.after_.splice(0,0,afterEachFunction);
};
jasmine.Runner.prototype.finishCallback = function() {
this.env.reporter.reportRunnerResults(this);
};
jasmine.Runner.prototype.addSuite = function(suite) {
this.suites_.push(suite);
};
jasmine.Runner.prototype.add = function(block) {
if (block instanceof jasmine.Suite) {
this.addSuite(block);
}
this.queue.add(block);
};
jasmine.Runner.prototype.specs = function () {
var suites = this.suites();
var specs = [];
for (var i = 0; i < suites.length; i++) {
specs = specs.concat(suites[i].specs());
}
return specs;
};
jasmine.Runner.prototype.suites = function() {
return this.suites_;
};
jasmine.Runner.prototype.topLevelSuites = function() {
var topLevelSuites = [];
for (var i = 0; i < this.suites_.length; i++) {
if (!this.suites_[i].parentSuite) {
topLevelSuites.push(this.suites_[i]);
}
}
return topLevelSuites;
};
jasmine.Runner.prototype.results = function() {
return this.queue.results();
};
/**
* Internal representation of a Jasmine specification, or test.
*
* @constructor
* @param {jasmine.Env} env
* @param {jasmine.Suite} suite
* @param {String} description
*/
jasmine.Spec = function(env, suite, description) {
if (!env) {
throw new Error('jasmine.Env() required');
}
if (!suite) {
throw new Error('jasmine.Suite() required');
}
var spec = this;
spec.id = env.nextSpecId ? env.nextSpecId() : null;
spec.env = env;
spec.suite = suite;
spec.description = description;
spec.queue = new jasmine.Queue(env);
spec.afterCallbacks = [];
spec.spies_ = [];
spec.results_ = new jasmine.NestedResults();
spec.results_.description = description;
spec.matchersClass = null;
};
jasmine.Spec.prototype.getFullName = function() {
return this.suite.getFullName() + ' ' + this.description + '.';
};
jasmine.Spec.prototype.results = function() {
return this.results_;
};
/**
* All parameters are pretty-printed and concatenated together, then written to the spec's output.
*
* Be careful not to leave calls to <code>jasmine.log</code> in production code.
*/
jasmine.Spec.prototype.log = function() {
return this.results_.log(arguments);
};
jasmine.Spec.prototype.runs = function (func) {
var block = new jasmine.Block(this.env, func, this);
this.addToQueue(block);
return this;
};
jasmine.Spec.prototype.addToQueue = function (block) {
if (this.queue.isRunning()) {
this.queue.insertNext(block);
} else {
this.queue.add(block);
}
};
/**
* @param {jasmine.ExpectationResult} result
*/
jasmine.Spec.prototype.addMatcherResult = function(result) {
this.results_.addResult(result);
};
jasmine.Spec.prototype.expect = function(actual) {
var positive = new (this.getMatchersClass_())(this.env, actual, this);
positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
return positive;
};
/**
* Waits a fixed time period before moving to the next block.
*
* @deprecated Use waitsFor() instead
* @param {Number} timeout milliseconds to wait
*/
jasmine.Spec.prototype.waits = function(timeout) {
var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
this.addToQueue(waitsFunc);
return this;
};
/**
* Waits for the latchFunction to return true before proceeding to the next block.
*
* @param {Function} latchFunction
* @param {String} optional_timeoutMessage
* @param {Number} optional_timeout
*/
jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
var latchFunction_ = null;
var optional_timeoutMessage_ = null;
var optional_timeout_ = null;
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
switch (typeof arg) {
case 'function':
latchFunction_ = arg;
break;
case 'string':
optional_timeoutMessage_ = arg;
break;
case 'number':
optional_timeout_ = arg;
break;
}
}
var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
this.addToQueue(waitsForFunc);
return this;
};
jasmine.Spec.prototype.fail = function (e) {
var expectationResult = new jasmine.ExpectationResult({
passed: false,
message: e ? jasmine.util.formatException(e) : 'Exception',
trace: { stack: e.stack }
});
this.results_.addResult(expectationResult);
};
jasmine.Spec.prototype.getMatchersClass_ = function() {
return this.matchersClass || this.env.matchersClass;
};
jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
var parent = this.getMatchersClass_();
var newMatchersClass = function() {
parent.apply(this, arguments);
};
jasmine.util.inherit(newMatchersClass, parent);
jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
this.matchersClass = newMatchersClass;
};
jasmine.Spec.prototype.finishCallback = function() {
this.env.reporter.reportSpecResults(this);
};
jasmine.Spec.prototype.finish = function(onComplete) {
this.removeAllSpies();
this.finishCallback();
if (onComplete) {
onComplete();
}
};
jasmine.Spec.prototype.after = function(doAfter) {
if (this.queue.isRunning()) {
this.queue.add(new jasmine.Block(this.env, doAfter, this));
} else {
this.afterCallbacks.unshift(doAfter);
}
};
jasmine.Spec.prototype.execute = function(onComplete) {
var spec = this;
if (!spec.env.specFilter(spec)) {
spec.results_.skipped = true;
spec.finish(onComplete);
return;
}
this.env.reporter.reportSpecStarting(this);
spec.env.currentSpec = spec;
spec.addBeforesAndAftersToQueue();
spec.queue.start(function () {
spec.finish(onComplete);
});
};
jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
var runner = this.env.currentRunner();
var i;
for (var suite = this.suite; suite; suite = suite.parentSuite) {
for (i = 0; i < suite.before_.length; i++) {
this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
}
}
for (i = 0; i < runner.before_.length; i++) {
this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
}
for (i = 0; i < this.afterCallbacks.length; i++) {
this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
}
for (suite = this.suite; suite; suite = suite.parentSuite) {
for (i = 0; i < suite.after_.length; i++) {
this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
}
}
for (i = 0; i < runner.after_.length; i++) {
this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
}
};
jasmine.Spec.prototype.explodes = function() {
throw 'explodes function should not have been called';
};
jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
if (obj == jasmine.undefined) {
throw "spyOn could not find an object to spy upon for " + methodName + "()";
}
if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
throw methodName + '() method does not exist';
}
if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
throw new Error(methodName + ' has already been spied upon');
}
var spyObj = jasmine.createSpy(methodName);
this.spies_.push(spyObj);
spyObj.baseObj = obj;
spyObj.methodName = methodName;
spyObj.originalValue = obj[methodName];
obj[methodName] = spyObj;
return spyObj;
};
jasmine.Spec.prototype.removeAllSpies = function() {
for (var i = 0; i < this.spies_.length; i++) {
var spy = this.spies_[i];
spy.baseObj[spy.methodName] = spy.originalValue;
}
this.spies_ = [];
};
/**
* Internal representation of a Jasmine suite.
*
* @constructor
* @param {jasmine.Env} env
* @param {String} description
* @param {Function} specDefinitions
* @param {jasmine.Suite} parentSuite
*/
jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
var self = this;
self.id = env.nextSuiteId ? env.nextSuiteId() : null;
self.description = description;
self.queue = new jasmine.Queue(env);
self.parentSuite = parentSuite;
self.env = env;
self.before_ = [];
self.after_ = [];
self.children_ = [];
self.suites_ = [];
self.specs_ = [];
};
jasmine.Suite.prototype.getFullName = function() {
var fullName = this.description;
for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
fullName = parentSuite.description + ' ' + fullName;
}
return fullName;
};
jasmine.Suite.prototype.finish = function(onComplete) {
this.env.reporter.reportSuiteResults(this);
this.finished = true;
if (typeof(onComplete) == 'function') {
onComplete();
}
};
jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
beforeEachFunction.typeName = 'beforeEach';
this.before_.unshift(beforeEachFunction);
};
jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
afterEachFunction.typeName = 'afterEach';
this.after_.unshift(afterEachFunction);
};
jasmine.Suite.prototype.results = function() {
return this.queue.results();
};
jasmine.Suite.prototype.add = function(suiteOrSpec) {
this.children_.push(suiteOrSpec);
if (suiteOrSpec instanceof jasmine.Suite) {
this.suites_.push(suiteOrSpec);
this.env.currentRunner().addSuite(suiteOrSpec);
} else {
this.specs_.push(suiteOrSpec);
}
this.queue.add(suiteOrSpec);
};
jasmine.Suite.prototype.specs = function() {
return this.specs_;
};
jasmine.Suite.prototype.suites = function() {
return this.suites_;
};
jasmine.Suite.prototype.children = function() {
return this.children_;
};
jasmine.Suite.prototype.execute = function(onComplete) {
var self = this;
this.queue.start(function () {
self.finish(onComplete);
});
};
jasmine.WaitsBlock = function(env, timeout, spec) {
this.timeout = timeout;
jasmine.Block.call(this, env, null, spec);
};
jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
jasmine.WaitsBlock.prototype.execute = function (onComplete) {
if (jasmine.VERBOSE) {
this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
}
this.env.setTimeout(function () {
onComplete();
}, this.timeout);
};
/**
* A block which waits for some condition to become true, with timeout.
*
* @constructor
* @extends jasmine.Block
* @param {jasmine.Env} env The Jasmine environment.
* @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
* @param {Function} latchFunction A function which returns true when the desired condition has been met.
* @param {String} message The message to display if the desired condition hasn't been met within the given time period.
* @param {jasmine.Spec} spec The Jasmine spec.
*/
jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
this.timeout = timeout || env.defaultTimeoutInterval;
this.latchFunction = latchFunction;
this.message = message;
this.totalTimeSpentWaitingForLatch = 0;
jasmine.Block.call(this, env, null, spec);
};
jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
if (jasmine.VERBOSE) {
this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
}
var latchFunctionResult;
try {
latchFunctionResult = this.latchFunction.apply(this.spec);
} catch (e) {
this.spec.fail(e);
onComplete();
return;
}
if (latchFunctionResult) {
onComplete();
} else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
this.spec.fail({
name: 'timeout',
message: message
});
this.abort = true;
onComplete();
} else {
this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
var self = this;
this.env.setTimeout(function() {
self.execute(onComplete);
}, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
}
};
jasmine.version_= {
"major": 1,
"minor": 2,
"build": 0,
"revision": 1337005947
};
This source diff could not be displayed because it is too large. You can view the blob instead.
/*global $:false, document:false, window:false, portal_url:false,
$msg:false, Strophe:false, setTimeout:false, navigator:false, jarn:false, google:false, jarnxmpp:false, jQuery:false, sessionStorage:false, $iq:false, $pres:false, Image:false, */
(function (jarnxmpp, $, portal_url) {
portal_url = portal_url || '';
jarnxmpp.Storage = {
storage: null,
init: function () {
try {
if ('sessionStorage' in window && window.sessionStorage !== null && JSON in window && window.JSON !== null) {
jarnxmpp.Storage.storage = sessionStorage;
if (!('_user_info' in jarnxmpp.Storage.storage)) {
jarnxmpp.Storage.set('_user_info', {});
}
if (!('_vCards' in jarnxmpp.Storage.storage)) {
jarnxmpp.Storage.set('_vCards', {});
}
if (!('_subscriptions' in jarnxmpp.Storage.storage)) {
jarnxmpp.Storage.set('_subscriptions', null);
}
}
} catch (e) {}
},
get: function (key) {
if (key in sessionStorage) {
return JSON.parse(sessionStorage[key]);
}
return null;
},
set: function (key, value) {
sessionStorage[key] = JSON.stringify(value);
},
xmppGet: function (key, callback) {
var stanza = $iq({type: 'get'})
.c('query', {xmlns: 'jabber:iq:private'})
.c('jarnxmpp', {xmlns: 'http://jarn.com/ns/jarnxmpp:prefs:' + key})
.tree();
jarnxmpp.connection.sendIQ(stanza, function success(result) {
callback($('jarnxmpp ' + 'value', result).first().text());
});
},
xmppSet: function (key, value) {
var stanza = $iq({type: 'set'})
.c('query', {xmlns: 'jabber:iq:private'})
.c('jarnxmpp', {xmlns: 'http://jarn.com/ns/jarnxmpp:prefs:' + key})
.c('value', value)
.tree();
jarnxmpp.connection.sendIQ(stanza);
}
};
jarnxmpp.Storage.init();
jarnxmpp.Presence = {
online: {},
_user_info: {},
onlineCount: function () {
var me = Strophe.getNodeFromJid(jarnxmpp.connection.jid),
counter = 0,
user;
for (user in jarnxmpp.Presence.online) {
if ((jarnxmpp.Presence.online.hasOwnProperty(user)) && user !== me) {
counter += 1;
}
}
return counter;
},
getUserInfo: function (user_id, callback) {
// User info on browsers without storage
if (jarnxmpp.Storage.storage === null) {
if (user_id in jarnxmpp.Presence._user_info) {
callback(jarnxmpp.Presence._user_info[user_id]);
} else {
$.getJSON(portal_url + "/xmpp-userinfo?user_id=" + user_id, function (data) {
jarnxmpp.Presence._user_info[user_id] = data;
callback(data);
});
}
} else {
var _user_info = jarnxmpp.Storage.get('_user_info');
if (user_id in _user_info) {
callback(_user_info[user_id]);
} else {
$.getJSON(portal_url + "/xmpp-userinfo?user_id=" + user_id, function (data) {
_user_info[user_id] = data;
jarnxmpp.Storage.set('_user_info', _user_info);
callback(data);
});
}
}
}
};
jarnxmpp.vCard = {
_vCards: {},
_getVCard: function (jid, callback) {
var stanza =
$iq({type: 'get', to: jid})
.c('vCard', {xmlns: 'vcard-temp'}).tree();
jarnxmpp.connection.sendIQ(stanza, function (data) {
var result = {};
$('vCard[xmlns="vcard-temp"]', data).children().each(function (idx, element) {
result[element.nodeName] = element.textContent;
});
if (typeof (callback) !== 'undefined') {
callback(result);
}
});
},
getVCard: function (jid, callback) {
jid = Strophe.getBareJidFromJid(jid);
if (jarnxmpp.Storage.storage === null) {
if (jid in jarnxmpp.vCard._vCards) {
callback(jarnxmpp.vCard._vCards[jid]);
} else {
jarnxmpp.vCard._getVCard(jid, function (result) {
jarnxmpp.vCard._vCards[jid] = result;
callback(result);
});
}
} else {
var _vCards = jarnxmpp.Storage.get('_vCards');
if (jid in _vCards) {
callback(_vCards[jid]);
} else {
jarnxmpp.vCard._getVCard(jid, function (result) {
_vCards[jid] = result;
jarnxmpp.Storage.set('_vCards', _vCards);
callback(result);
});
}
}
},
setVCard: function (params, photoUrl) {
var key,
vCard = Strophe.xmlElement('vCard', [['xmlns', 'vcard-temp'], ['version', '2.0']]);
for (key in params) {
if (params.hasOwnProperty(key)) {
vCard.appendChild(Strophe.xmlElement(key, [], params[key]));
}
}
var send = function () {
var stanza = $iq({type: 'set'}).cnode(vCard).tree();
jarnxmpp.connection.sendIQ(stanza);
};
if (typeof (photoUrl) === 'undefined') {
send();
} else {
jarnxmpp.vCard.getBase64Image(photoUrl, function (base64img) {
base64img = base64img.replace(/^data:image\/png;base64,/, "");
var photo = Strophe.xmlElement('PHOTO');
photo.appendChild(Strophe.xmlElement('TYPE', [], 'image/png'));
photo.appendChild(Strophe.xmlElement('BINVAL', [], base64img));
vCard.appendChild(photo);
send();
});
}
},
getBase64Image: function (url, callback) {
// Create the element, then draw it on a canvas to get the base64 data.
var img = new Image();
$(img).load(function () {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
callback(canvas.toDataURL('image/png'));
}).attr('src', url);
}
};
jarnxmpp.onConnect = function (status) {
if ((status === Strophe.Status.ATTACHED) || (status === Strophe.Status.CONNECTED)) {
$(window).bind('beforeunload', function () {
$(document).trigger('jarnxmpp.disconnecting');
var presence = $pres({type: 'unavailable'});
jarnxmpp.connection.send(presence);
jarnxmpp.connection.disconnect();
jarnxmpp.connection.flush();
});
$(document).trigger('jarnxmpp.connected');
} else if (status === Strophe.Status.DISCONNECTED) {
$(document).trigger('jarnxmpp.disconnected');
}
};
jarnxmpp.rawInput = function (data) {
var event = jQuery.Event('jarnxmpp.dataReceived');
event.text = data;
$(document).trigger(event);
};
jarnxmpp.rawOutput = function (data) {
var event = jQuery.Event('jarnxmpp.dataSent');
event.text = data;
$(document).trigger(event);
};
$(document).bind('jarnxmpp.connected', function () {
// Logging
jarnxmpp.connection.rawInput = jarnxmpp.rawInput;
jarnxmpp.connection.rawOutput = jarnxmpp.rawOutput;
});
$(document).bind('jarnxmpp.disconnecting', function () {
if (jarnxmpp.Storage.storage !== null) {
jarnxmpp.Storage.set('online-count', jarnxmpp.Presence.onlineCount());
}
});
$(document).ready(function () {
var resource = jarnxmpp.Storage.get('xmppresource');
if (resource) {
data = {'resource': resource};
} else {
data = {};
}
$.ajax({
'url':portal_url + '/@@xmpp-loader',
'dataType': 'json',
'data': data,
'success': function (data) {
if (!(('rid' in data) && ('sid' in data) && ('BOSH_SERVICE' in data))) {
return;
}
if (!resource) {
jarnxmpp.Storage.set('xmppresource', Strophe.getResourceFromJid(data.jid));
}
jarnxmpp.BOSH_SERVICE = data.BOSH_SERVICE;
jarnxmpp.jid = data.jid;
jarnxmpp.connection = new Strophe.Connection(jarnxmpp.BOSH_SERVICE);
jarnxmpp.connection.attach(jarnxmpp.jid, data.sid, data.rid, jarnxmpp.onConnect);
}
});
});
})(window.jarnxmpp = window.jarnxmpp || {}, jQuery, portal_url);
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
Copyright (c) 2009 John Resig, http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This source diff could not be displayed because it is too large. You can view the blob instead.
"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
if(typeof module!="undefined"&&module.exports)module.exports=sjcl;
sjcl.cipher.aes=function(a){this.h[0][0][0]||this.z();var b,c,d,e,f=this.h[0][4],g=this.h[1];b=a.length;var h=1;if(b!==4&&b!==6&&b!==8)throw new sjcl.exception.invalid("invalid aes key size");this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(a%b===0||b===8&&a%b===4){c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255];if(a%b===0){c=c<<8^c>>>24^h<<24;h=h<<1^(h>>7)*283}}d[a]=d[a-b]^c}for(b=0;a;b++,a--){c=d[b&3?a:a-4];e[b]=a<=4||b<4?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^
g[3][f[c&255]]}};
sjcl.cipher.aes.prototype={encrypt:function(a){return this.I(a,0)},decrypt:function(a){return this.I(a,1)},h:[[[],[],[],[],[]],[[],[],[],[],[]]],z:function(){var a=this.h[0],b=this.h[1],c=a[4],d=b[4],e,f,g,h=[],i=[],k,j,l,m;for(e=0;e<0x100;e++)i[(h[e]=e<<1^(e>>7)*283)^e]=e;for(f=g=0;!c[f];f^=k||1,g=i[g]||1){l=g^g<<1^g<<2^g<<3^g<<4;l=l>>8^l&255^99;c[f]=l;d[l]=f;j=h[e=h[k=h[f]]];m=j*0x1010101^e*0x10001^k*0x101^f*0x1010100;j=h[l]*0x101^l*0x1010100;for(e=0;e<4;e++){a[e][f]=j=j<<24^j>>>8;b[e][l]=m=m<<24^m>>>8}}for(e=
0;e<5;e++){a[e]=a[e].slice(0);b[e]=b[e].slice(0)}},I:function(a,b){if(a.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var c=this.a[b],d=a[0]^c[0],e=a[b?3:1]^c[1],f=a[2]^c[2];a=a[b?1:3]^c[3];var g,h,i,k=c.length/4-2,j,l=4,m=[0,0,0,0];g=this.h[b];var n=g[0],o=g[1],p=g[2],q=g[3],r=g[4];for(j=0;j<k;j++){g=n[d>>>24]^o[e>>16&255]^p[f>>8&255]^q[a&255]^c[l];h=n[e>>>24]^o[f>>16&255]^p[a>>8&255]^q[d&255]^c[l+1];i=n[f>>>24]^o[a>>16&255]^p[d>>8&255]^q[e&255]^c[l+2];a=n[a>>>24]^o[d>>16&
255]^p[e>>8&255]^q[f&255]^c[l+3];l+=4;d=g;e=h;f=i}for(j=0;j<4;j++){m[b?3&-j:j]=r[d>>>24]<<24^r[e>>16&255]<<16^r[f>>8&255]<<8^r[a&255]^c[l++];g=d;d=e;e=f;f=a;a=g}return m}};
sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===undefined?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(a.length===0||b.length===0)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return d===32?a.concat(b):sjcl.bitArray.P(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;
if(b===0)return 0;return(b-1)*32+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(a.length*32<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b&=31;if(c>0&&b)a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1);return a},partial:function(a,b,c){if(a===32)return b;return(c?b|0:b<<32-a)+a*0x10000000000},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return false;var c=0,d;for(d=0;d<a.length;d++)c|=
a[d]^b[d];return c===0},P:function(a,b,c,d){var e;e=0;if(d===undefined)d=[];for(;b>=32;b-=32){d.push(c);c=0}if(b===0)return d.concat(a);for(e=0;e<a.length;e++){d.push(c|a[e]>>>b);c=a[e]<<32-b}e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,b+a>32?c:d.pop(),1));return d},k:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]}};
sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++){if((d&3)===0)e=a[d/4];b+=String.fromCharCode(e>>>24);e<<=8}return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++){d=d<<8|a.charCodeAt(c);if((c&3)===3){b.push(d);d=0}}c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a+="00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,d*4)}};
sjcl.codec.base64={F:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.F,g=0,h=sjcl.bitArray.bitLength(a);if(c)f=f.substr(0,62)+"-_";for(c=0;d.length*6<h;){d+=f.charAt((g^a[c]>>>e)>>>26);if(e<6){g=a[c]<<6-e;e+=26;c++}else{g<<=6;e-=6}}for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d=0,e=sjcl.codec.base64.F,f=0,g;if(b)e=e.substr(0,62)+"-_";for(b=0;b<a.length;b++){g=e.indexOf(a.charAt(b));
if(g<0)throw new sjcl.exception.invalid("this isn't base64!");if(d>26){d-=26;c.push(f^g>>>d);f=g<<32-d}else{d+=6;f^=g<<32-d}}d&56&&c.push(sjcl.bitArray.partial(d&56,f,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.a[0]||this.z();if(a){this.n=a.n.slice(0);this.i=a.i.slice(0);this.e=a.e}else this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.n=this.N.slice(0);this.i=[];this.e=0;return this},update:function(a){if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);var b,c=this.i=sjcl.bitArray.concat(this.i,a);b=this.e;a=this.e=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)this.D(c.splice(0,16));return this},finalize:function(){var a,b=this.i,c=this.n;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.e/
4294967296));for(b.push(this.e|0);b.length;)this.D(b.splice(0,16));this.reset();return c},N:[],a:[],z:function(){function a(e){return(e-Math.floor(e))*0x100000000|0}var b=0,c=2,d;a:for(;b<64;c++){for(d=2;d*d<=c;d++)if(c%d===0)continue a;if(b<8)this.N[b]=a(Math.pow(c,0.5));this.a[b]=a(Math.pow(c,1/3));b++}},D:function(a){var b,c,d=a.slice(0),e=this.n,f=this.a,g=e[0],h=e[1],i=e[2],k=e[3],j=e[4],l=e[5],m=e[6],n=e[7];for(a=0;a<64;a++){if(a<16)b=d[a];else{b=d[a+1&15];c=d[a+14&15];b=d[a&15]=(b>>>7^b>>>18^
b>>>3^b<<25^b<<14)+(c>>>17^c>>>19^c>>>10^c<<15^c<<13)+d[a&15]+d[a+9&15]|0}b=b+n+(j>>>6^j>>>11^j>>>25^j<<26^j<<21^j<<7)+(m^j&(l^m))+f[a];n=m;m=l;l=j;j=k+b|0;k=i;i=h;h=g;g=b+(h&i^k&(h^i))+(h>>>2^h>>>13^h>>>22^h<<30^h<<19^h<<10)|0}e[0]=e[0]+g|0;e[1]=e[1]+h|0;e[2]=e[2]+i|0;e[3]=e[3]+k|0;e[4]=e[4]+j|0;e[5]=e[5]+l|0;e[6]=e[6]+m|0;e[7]=e[7]+n|0}};
sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,i=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];if(i<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;f<4&&k>>>8*f;f++);if(f<15-i)f=15-i;c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.H(a,b,c,d,e,f);g=sjcl.mode.ccm.J(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),i=f.clamp(b,h-e),k=f.bitSlice(b,
h-e);h=(h-e)/8;if(g<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;b<4&&h>>>8*b;b++);if(b<15-g)b=15-g;c=f.clamp(c,8*(15-b));i=sjcl.mode.ccm.J(a,i,c,k,e,b);a=sjcl.mode.ccm.H(a,i.data,c,d,e,b);if(!f.equal(i.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return i.data},H:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,i=h.k;e/=8;if(e%2||e<4||e>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(d.length>0xffffffff||b.length>0xffffffff)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;if(c<=65279)g=[h.partial(16,c)];else if(c<=0xffffffff)g=h.concat([h.partial(16,65534)],[c]);g=h.concat(g,d);for(d=0;d<g.length;d+=4)f=a.encrypt(i(f,g.slice(d,d+4).concat([0,0,0])))}for(d=0;d<b.length;d+=4)f=a.encrypt(i(f,b.slice(d,d+4).concat([0,0,0])));return h.clamp(f,e*8)},J:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.k;var i=b.length,k=h.bitLength(b);c=h.concat([h.partial(8,
f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!i)return{tag:d,data:[]};for(g=0;g<i;g+=4){c[3]++;e=a.encrypt(c);b[g]^=e[0];b[g+1]^=e[1];b[g+2]^=e[2];b[g+3]^=e[3]}return{tag:d,data:h.clamp(b,k)}}};
sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.B,i=sjcl.bitArray,k=i.k,j=[0,0,0,0];c=h(a.encrypt(c));var l,m=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4){l=b.slice(g,g+4);j=k(j,l);m=m.concat(k(c,a.encrypt(k(c,l))));c=h(c)}l=b.slice(g);b=i.bitLength(l);g=a.encrypt(k(c,[0,0,0,b]));l=i.clamp(k(l.concat([0,0,0]),g),b);j=k(j,k(l.concat([0,0,0]),g));j=a.encrypt(k(j,k(c,h(c))));
if(d.length)j=k(j,f?d:sjcl.mode.ocb2.pmac(a,d));return m.concat(i.concat(l,i.clamp(j,e)))},decrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.B,h=sjcl.bitArray,i=h.k,k=[0,0,0,0],j=g(a.encrypt(c)),l,m,n=sjcl.bitArray.bitLength(b)-e,o=[];d=d||[];for(c=0;c+4<n/32;c+=4){l=i(j,a.decrypt(i(j,b.slice(c,c+4))));k=i(k,l);o=o.concat(l);j=g(j)}m=n-c*32;l=a.encrypt(i(j,[0,0,0,m]));l=i(l,h.clamp(b.slice(c),
m).concat([0,0,0]));k=i(k,l);k=a.encrypt(i(k,i(j,g(j))));if(d.length)k=i(k,f?d:sjcl.mode.ocb2.pmac(a,d));if(!h.equal(h.clamp(k,e),h.bitSlice(b,n)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return o.concat(h.clamp(l,m))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.B,e=sjcl.bitArray,f=e.k,g=[0,0,0,0],h=a.encrypt([0,0,0,0]);h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4){h=d(h);g=f(g,a.encrypt(f(h,b.slice(c,c+4))))}b=b.slice(c);if(e.bitLength(b)<128){h=f(h,d(h));b=e.concat(b,[2147483648|0,0,
0,0])}g=f(g,b);return a.encrypt(f(d(f(h,d(h))),g))},B:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^(a[0]>>>31)*135]}};sjcl.misc.hmac=function(a,b){this.M=b=b||sjcl.hash.sha256;var c=[[],[]],d=b.prototype.blockSize/32;this.l=[new b,new b];if(a.length>d)a=b.hash(a);for(b=0;b<d;b++){c[0][b]=a[b]^909522486;c[1][b]=a[b]^1549556828}this.l[0].update(c[0]);this.l[1].update(c[1])};
sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a,b){a=(new this.M(this.l[0])).update(a,b).finalize();return(new this.M(this.l[1])).update(a).finalize()};
sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E3;if(d<0||c<0)throw sjcl.exception.invalid("invalid params to pbkdf2");if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,i,k=[],j=sjcl.bitArray;for(i=1;32*k.length<(d||1);i++){e=f=a.encrypt(j.concat(b,[i]));for(g=1;g<c;g++){f=a.encrypt(f);for(h=0;h<f.length;h++)e[h]^=f[h]}k=k.concat(e)}if(d)k=j.clamp(k,d);return k};
sjcl.random={randomWords:function(a,b){var c=[];b=this.isReady(b);var d;if(b===0)throw new sjcl.exception.notReady("generator isn't seeded");else b&2&&this.U(!(b&1));for(b=0;b<a;b+=4){(b+1)%0x10000===0&&this.L();d=this.w();c.push(d[0],d[1],d[2],d[3])}this.L();return c.slice(0,a)},setDefaultParanoia:function(a){this.t=a},addEntropy:function(a,b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.q[c],h=this.isReady(),i=0;d=this.G[c];if(d===undefined)d=this.G[c]=this.R++;if(g===undefined)g=this.q[c]=
0;this.q[c]=(this.q[c]+1)%this.b.length;switch(typeof a){case "number":if(b===undefined)b=1;this.b[g].update([d,this.u++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if(c==="[object Uint32Array]"){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else{if(c!=="[object Array]")i=1;for(c=0;c<a.length&&!i;c++)if(typeof a[c]!="number")i=1}if(!i){if(b===undefined)for(c=b=0;c<a.length;c++)for(e=a[c];e>0;){b++;e>>>=1}this.b[g].update([d,this.u++,2,b,f,a.length].concat(a))}break;case "string":if(b===
undefined)b=a.length;this.b[g].update([d,this.u++,3,b,f,a.length]);this.b[g].update(a);break;default:i=1}if(i)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.j[g]+=b;this.f+=b;if(h===0){this.isReady()!==0&&this.K("seeded",Math.max(this.g,this.f));this.K("progress",this.getProgress())}},isReady:function(a){a=this.C[a!==undefined?a:this.t];return this.g&&this.g>=a?this.j[0]>80&&(new Date).valueOf()>this.O?3:1:this.f>=a?2:0},getProgress:function(a){a=
this.C[a?a:this.t];return this.g>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.m){if(window.addEventListener){window.addEventListener("load",this.o,false);window.addEventListener("mousemove",this.p,false)}else if(document.attachEvent){document.attachEvent("onload",this.o);document.attachEvent("onmousemove",this.p)}else throw new sjcl.exception.bug("can't attach event");this.m=true}},stopCollectors:function(){if(this.m){if(window.removeEventListener){window.removeEventListener("load",
this.o,false);window.removeEventListener("mousemove",this.p,false)}else if(window.detachEvent){window.detachEvent("onload",this.o);window.detachEvent("onmousemove",this.p)}this.m=false}},addEventListener:function(a,b){this.r[a][this.Q++]=b},removeEventListener:function(a,b){var c;a=this.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&a[c]===b&&d.push(c);for(b=0;b<d.length;b++){c=d[b];delete a[c]}},b:[new sjcl.hash.sha256],j:[0],A:0,q:{},u:0,G:{},R:0,g:0,f:0,O:0,a:[0,0,0,0,0,0,0,0],d:[0,0,0,0],s:undefined,
t:6,m:false,r:{progress:{},seeded:{}},Q:0,C:[0,48,64,96,128,192,0x100,384,512,768,1024],w:function(){for(var a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}return this.s.encrypt(this.d)},L:function(){this.a=this.w().concat(this.w());this.s=new sjcl.cipher.aes(this.a)},T:function(a){this.a=sjcl.hash.sha256.hash(this.a.concat(a));this.s=new sjcl.cipher.aes(this.a);for(a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}},U:function(a){var b=[],c=0,d;this.O=b[0]=(new Date).valueOf()+3E4;for(d=
0;d<16;d++)b.push(Math.random()*0x100000000|0);for(d=0;d<this.b.length;d++){b=b.concat(this.b[d].finalize());c+=this.j[d];this.j[d]=0;if(!a&&this.A&1<<d)break}if(this.A>=1<<this.b.length){this.b.push(new sjcl.hash.sha256);this.j.push(0)}this.f-=c;if(c>this.g)this.g=c;this.A++;this.T(b)},p:function(a){sjcl.random.addEntropy([a.x||a.clientX||a.offsetX||0,a.y||a.clientY||a.offsetY||0],2,"mouse")},o:function(){sjcl.random.addEntropy((new Date).valueOf(),2,"loadtime")},K:function(a,b){var c;a=sjcl.random.r[a];
var d=[];for(c in a)a.hasOwnProperty(c)&&d.push(a[c]);for(c=0;c<d.length;c++)d[c](b)}};try{var s=new Uint32Array(32);crypto.getRandomValues(s);sjcl.random.addEntropy(s,1024,"crypto['getRandomValues']")}catch(t){}
sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},encrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.c({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.c(f,c);c=f.adata;if(typeof f.salt==="string")f.salt=sjcl.codec.base64.toBits(f.salt);if(typeof f.iv==="string")f.iv=sjcl.codec.base64.toBits(f.iv);if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||typeof a==="string"&&f.iter<=100||f.ts!==64&&f.ts!==96&&f.ts!==128||f.ks!==128&&f.ks!==192&&f.ks!==0x100||f.iv.length<
2||f.iv.length>4)throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof a==="string"){g=sjcl.misc.cachedPbkdf2(a,f);a=g.key.slice(0,f.ks/32);f.salt=g.salt}if(typeof b==="string")b=sjcl.codec.utf8String.toBits(b);if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);g=new sjcl.cipher[f.cipher](a);e.c(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return e.encode(f)},decrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.c(e.c(e.c({},e.defaults),e.decode(b)),
c,true);var f;c=b.adata;if(typeof b.salt==="string")b.salt=sjcl.codec.base64.toBits(b.salt);if(typeof b.iv==="string")b.iv=sjcl.codec.base64.toBits(b.iv);if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||typeof a==="string"&&b.iter<=100||b.ts!==64&&b.ts!==96&&b.ts!==128||b.ks!==128&&b.ks!==192&&b.ks!==0x100||!b.iv||b.iv.length<2||b.iv.length>4)throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof a==="string"){f=sjcl.misc.cachedPbkdf2(a,b);a=f.key.slice(0,b.ks/32);b.salt=f.salt}if(typeof c===
"string")c=sjcl.codec.utf8String.toBits(c);f=new sjcl.cipher[b.cipher](a);c=sjcl.mode[b.mode].decrypt(f,b.ct,b.iv,c,b.ts);e.c(d,b);d.key=a;return sjcl.codec.utf8String.fromBits(c)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+
sjcl.codec.base64.fromBits(a[b],1)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");b[d[2]]=
d[3]?parseInt(d[3],10):d[2].match(/^(ct|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4])}return b},c:function(a,b,c){if(a===undefined)a={};if(b===undefined)return a;var d;for(d in b)if(b.hasOwnProperty(d)){if(c&&a[d]!==undefined&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},W:function(a,b){var c={},d;for(d in a)if(a.hasOwnProperty(d)&&a[d]!==b[d])c[d]=a[d];return c},V:function(a,b){var c={},d;for(d=0;d<b.length;d++)if(a[b[d]]!==undefined)c[b[d]]=
a[b[d]];return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.S={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.S,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=b.salt===undefined?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
*Plugin to implement the MUC extension.
http://xmpp.org/extensions/xep-0045.html
*Previous Author:
Nathan Zorn <nathan.zorn@gmail.com>
*Complete CoffeeScript rewrite:
Andreas Guth <guth@dbis.rwth-aachen.de>
*/
// AMD/global registrations
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([
"Libraries/strophe"
], function () {
return factory(jQuery, console);
}
);
}
}(this, function ($, console) {
(function() {
var Occupant, RoomConfig, XmppRoom,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Strophe.addConnectionPlugin('muc', {
_connection: null,
rooms: [],
/*Function
Initialize the MUC plugin. Sets the correct connection object and
extends the namesace.
*/
init: function(conn) {
this._connection = conn;
this._muc_handler = null;
Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin");
Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
return Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
},
/*Function
Join a multi-user chat room
Parameters:
(String) room - The multi-user chat room to join.
(String) nick - The nickname to use in the chat room. Optional
(Function) msg_handler_cb - The function call to handle messages from the
specified chat room.
(Function) pres_handler_cb - The function call back to handle presence
in the chat room.
(String) password - The optional password to use. (password protected
rooms only)
*/
join: function(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password) {
var msg, room_nick, _base,
_this = this;
room_nick = this.test_append_nick(room, nick);
msg = $pres({
from: this._connection.jid,
to: room_nick
}).c("x", {
xmlns: Strophe.NS.MUC
});
if (password != null) {
msg.cnode(Strophe.xmlElement("password", [], password));
}
if (this._muc_handler == null) {
this._muc_handler = this._connection.addHandler(function(stanza) {
var from, handler, handlers, id, roomname, x, xmlns, xquery, _i, _len;
from = stanza.getAttribute('from');
roomname = from.split("/")[0];
if (!_this.rooms[roomname]) return true;
room = _this.rooms[roomname];
handlers = {};
if (stanza.nodeName === "message") {
handlers = room._message_handlers;
} else if (stanza.nodeName === "presence") {
xquery = stanza.getElementsByTagName("x");
if (xquery.length > 0) {
for (_i = 0, _len = xquery.length; _i < _len; _i++) {
x = xquery[_i];
xmlns = x.getAttribute("xmlns");
if (xmlns && xmlns.match(Strophe.NS.MUC)) {
handlers = room._presence_handlers;
break;
}
}
}
}
for (id in handlers) {
handler = handlers[id];
if (!handler(stanza, room)) delete handlers[id];
}
return true;
});
}
if ((_base = this.rooms)[room] == null) {
_base[room] = new XmppRoom(this, room, nick, password);
}
if (pres_handler_cb) {
this.rooms[room].addHandler('presence', pres_handler_cb);
}
if (msg_handler_cb) this.rooms[room].addHandler('message', msg_handler_cb);
if (roster_cb) this.rooms[room].addHandler('roster', roster_cb);
return this._connection.send(msg);
},
/*Function
Leave a multi-user chat room
Parameters:
(String) room - The multi-user chat room to leave.
(String) nick - The nick name used in the room.
(Function) handler_cb - Optional function to handle the successful leave.
(String) exit_msg - optional exit message.
Returns:
iqid - The unique id for the room leave.
*/
leave: function(room, nick, handler_cb, exit_msg) {
var presence, presenceid, room_nick;
delete this.rooms[room];
if (this.rooms.length === 0) {
this._connection.deleteHandler(this._muc_handler);
this._muc_handler = null;
}
room_nick = this.test_append_nick(room, nick);
presenceid = this._connection.getUniqueId();
presence = $pres({
type: "unavailable",
id: presenceid,
from: this._connection.jid,
to: room_nick
});
if (exit_msg != null) presence.c("status", exit_msg);
if (handler_cb != null) {
this._connection.addHandler(handler_cb, null, "presence", null, presenceid);
}
this._connection.send(presence);
return presenceid;
},
/*Function
Parameters:
(String) room - The multi-user chat room name.
(String) nick - The nick name used in the chat room.
(String) message - The plaintext message to send to the room.
(String) html_message - The message to send to the room with html markup.
(String) type - "groupchat" for group chat messages o
"chat" for private chat messages
Returns:
msgiq - the unique id used to send the message
*/
message: function(room, nick, message, html_message, type) {
var msg, msgid, parent, room_nick;
room_nick = this.test_append_nick(room, nick);
type = type || (nick != null ? "chat" : "groupchat");
msgid = this._connection.getUniqueId();
msg = $msg({
to: room_nick,
from: this._connection.jid,
type: type,
id: msgid
}).c("body", {
xmlns: Strophe.NS.CLIENT
}).t(message);
msg.up();
if (html_message != null) {
msg.c("html", {
xmlns: Strophe.NS.XHTML_IM
}).c("body", {
xmlns: Strophe.NS.XHTML
}).h(html_message);
if (msg.node.childNodes.length === 0) {
parent = msg.node.parentNode;
msg.up().up();
msg.node.removeChild(parent);
} else {
msg.up().up();
}
}
msg.c("x", {
xmlns: "jabber:x:event"
}).c("composing");
this._connection.send(msg);
return msgid;
},
/*Function
Convenience Function to send a Message to all Occupants
Parameters:
(String) room - The multi-user chat room name.
(String) message - The plaintext message to send to the room.
(String) html_message - The message to send to the room with html markup.
Returns:
msgiq - the unique id used to send the message
*/
groupchat: function(room, message, html_message) {
return this.message(room, null, message, html_message);
},
/*Function
Send a mediated invitation.
Parameters:
(String) room - The multi-user chat room name.
(String) receiver - The invitation's receiver.
(String) reason - Optional reason for joining the room.
Returns:
msgiq - the unique id used to send the invitation
*/
invite: function(room, receiver, reason) {
var invitation, msgid;
msgid = this._connection.getUniqueId();
invitation = $msg({
from: this._connection.jid,
to: room,
id: msgid
}).c('x', {
xmlns: Strophe.NS.MUC_USER
}).c('invite', {
to: receiver
});
if (reason != null) invitation.c('reason', reason);
this._connection.send(invitation);
return msgid;
},
/*Function
Send a direct invitation.
Parameters:
(String) room - The multi-user chat room name.
(String) receiver - The invitation's receiver.
(String) reason - Optional reason for joining the room.
(String) password - Optional password for the room.
Returns:
msgiq - the unique id used to send the invitation
*/
directInvite: function(room, receiver, reason, password) {
var attrs, invitation, msgid;
msgid = this._connection.getUniqueId();
attrs = {
xmlns: 'jabber:x:conference',
jid: room
};
if (reason != null) attrs.reason = reason;
if (password != null) attrs.password = password;
invitation = $msg({
from: this._connection.jid,
to: receiver,
id: msgid
}).c('x', attrs);
this._connection.send(invitation);
return msgid;
},
/*Function
Queries a room for a list of occupants
(String) room - The multi-user chat room name.
(Function) success_cb - Optional function to handle the info.
(Function) error_cb - Optional function to handle an error.
Returns:
id - the unique id used to send the info request
*/
queryOccupants: function(room, success_cb, error_cb) {
var attrs, info;
attrs = {
xmlns: Strophe.NS.DISCO_ITEMS
};
info = $iq({
from: this._connection.jid,
to: room,
type: 'get'
}).c('query', attrs);
return this._connection.sendIQ(info, success_cb, error_cb);
},
/*Function
Start a room configuration.
Parameters:
(String) room - The multi-user chat room name.
(Function) handler_cb - Optional function to handle the config form.
Returns:
id - the unique id used to send the configuration request
*/
configure: function(room, handler_cb) {
var config, id, stanza;
config = $iq({
to: room,
type: "get"
}).c("query", {
xmlns: Strophe.NS.MUC_OWNER
});
stanza = config.tree();
id = this._connection.sendIQ(stanza);
if (handler_cb != null) {
this._connection.addHandler(function(stanza) {
handler_cb(stanza);
return false;
}, Strophe.NS.MUC_OWNER, "iq", null, id);
}
return id;
},
/*Function
Cancel the room configuration
Parameters:
(String) room - The multi-user chat room name.
Returns:
id - the unique id used to cancel the configuration.
*/
cancelConfigure: function(room) {
var config, stanza;
config = $iq({
to: room,
type: "set"
}).c("query", {
xmlns: Strophe.NS.MUC_OWNER
}).c("x", {
xmlns: "jabber:x:data",
type: "cancel"
});
stanza = config.tree();
return this._connection.sendIQ(stanza);
},
/*Function
Save a room configuration.
Parameters:
(String) room - The multi-user chat room name.
(Array) configarray - an array of form elements used to configure the room.
Returns:
id - the unique id used to save the configuration.
*/
saveConfiguration: function(room, configarray) {
var conf, config, stanza, _i, _len;
config = $iq({
to: room,
type: "set"
}).c("query", {
xmlns: Strophe.NS.MUC_OWNER
}).c("x", {
xmlns: "jabber:x:data",
type: "submit"
});
for (_i = 0, _len = configarray.length; _i < _len; _i++) {
conf = configarray[_i];
config.cnode(conf).up();
}
stanza = config.tree();
return this._connection.sendIQ(stanza);
},
/*Function
Parameters:
(String) room - The multi-user chat room name.
Returns:
id - the unique id used to create the chat room.
*/
createInstantRoom: function(room) {
var roomiq;
roomiq = $iq({
to: room,
type: "set"
}).c("query", {
xmlns: Strophe.NS.MUC_OWNER
}).c("x", {
xmlns: "jabber:x:data",
type: "submit"
});
return this._connection.sendIQ(roomiq.tree());
},
/*Function
Set the topic of the chat room.
Parameters:
(String) room - The multi-user chat room name.
(String) topic - Topic message.
*/
setTopic: function(room, topic) {
var msg;
msg = $msg({
to: room,
from: this._connection.jid,
type: "groupchat"
}).c("subject", {
xmlns: "jabber:client"
}).t(topic);
return this._connection.send(msg.tree());
},
/*Function
Internal Function that Changes the role or affiliation of a member
of a MUC room. This function is used by modifyRole and modifyAffiliation.
The modification can only be done by a room moderator. An error will be
returned if the user doesn't have permission.
Parameters:
(String) room - The multi-user chat room name.
(Object) item - Object with nick and role or jid and affiliation attribute
(String) reason - Optional reason for the change.
(Function) handler_cb - Optional callback for success
(Function) errer_cb - Optional callback for error
Returns:
iq - the id of the mode change request.
*/
_modifyPrivilege: function(room, item, reason, handler_cb, error_cb) {
var iq;
iq = $iq({
to: room,
type: "set"
}).c("query", {
xmlns: Strophe.NS.MUC_ADMIN
}).cnode(item.node);
if (reason != null) iq.c("reason", reason);
return this._connection.sendIQ(iq.tree(), handler_cb, error_cb);
},
/*Function
Changes the role of a member of a MUC room.
The modification can only be done by a room moderator. An error will be
returned if the user doesn't have permission.
Parameters:
(String) room - The multi-user chat room name.
(String) nick - The nick name of the user to modify.
(String) role - The new role of the user.
(String) affiliation - The new affiliation of the user.
(String) reason - Optional reason for the change.
(Function) handler_cb - Optional callback for success
(Function) errer_cb - Optional callback for error
Returns:
iq - the id of the mode change request.
*/
modifyRole: function(room, nick, role, reason, handler_cb, error_cb) {
var item;
item = $build("item", {
nick: nick,
role: role
});
return this._modifyPrivilege(room, item, reason, handler_cb, error_cb);
},
kick: function(room, nick, reason, handler_cb, error_cb) {
return this.modifyRole(room, nick, 'none', reason, handler_cb, error_cb);
},
voice: function(room, nick, reason, handler_cb, error_cb) {
return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb);
},
mute: function(room, nick, reason, handler_cb, error_cb) {
return this.modifyRole(room, nick, 'visitor', reason, handler_cb, error_cb);
},
op: function(room, nick, reason, handler_cb, error_cb) {
return this.modifyRole(room, nick, 'moderator', reason, handler_cb, error_cb);
},
deop: function(room, nick, reason, handler_cb, error_cb) {
return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb);
},
/*Function
Changes the affiliation of a member of a MUC room.
The modification can only be done by a room moderator. An error will be
returned if the user doesn't have permission.
Parameters:
(String) room - The multi-user chat room name.
(String) jid - The jid of the user to modify.
(String) affiliation - The new affiliation of the user.
(String) reason - Optional reason for the change.
(Function) handler_cb - Optional callback for success
(Function) errer_cb - Optional callback for error
Returns:
iq - the id of the mode change request.
*/
modifyAffiliation: function(room, jid, affiliation, reason, handler_cb, error_cb) {
var item;
item = $build("item", {
jid: jid,
affiliation: affiliation
});
return this._modifyPrivilege(room, item, reason, handler_cb, error_cb);
},
ban: function(room, jid, reason, handler_cb, error_cb) {
return this.modifyAffiliation(room, jid, 'outcast', reason, handler_cb, error_cb);
},
member: function(room, jid, reason, handler_cb, error_cb) {
return this.modifyAffiliation(room, jid, 'member', reason, handler_cb, error_cb);
},
revoke: function(room, jid, reason, handler_cb, error_cb) {
return this.modifyAffiliation(room, jid, 'none', reason, handler_cb, error_cb);
},
owner: function(room, jid, reason, handler_cb, error_cb) {
return this.modifyAffiliation(room, jid, 'owner', reason, handler_cb, error_cb);
},
admin: function(room, jid, reason, handler_cb, error_cb) {
return this.modifyAffiliation(room, jid, 'admin', reason, handler_cb, error_cb);
},
/*Function
Change the current users nick name.
Parameters:
(String) room - The multi-user chat room name.
(String) user - The new nick name.
*/
changeNick: function(room, user) {
var presence, room_nick;
room_nick = this.test_append_nick(room, user);
presence = $pres({
from: this._connection.jid,
to: room_nick,
id: this._connection.getUniqueId()
});
return this._connection.send(presence.tree());
},
/*Function
Change the current users status.
Parameters:
(String) room - The multi-user chat room name.
(String) user - The current nick.
(String) show - The new show-text.
(String) status - The new status-text.
*/
setStatus: function(room, user, show, status) {
var presence, room_nick;
room_nick = this.test_append_nick(room, user);
presence = $pres({
from: this._connection.jid,
to: room_nick
});
if (show != null) presence.c('show', show).up();
if (status != null) presence.c('status', status);
return this._connection.send(presence.tree());
},
/*Function
List all chat room available on a server.
Parameters:
(String) server - name of chat server.
(String) handle_cb - Function to call for room list return.
*/
listRooms: function(server, handle_cb) {
var iq;
iq = $iq({
to: server,
from: this._connection.jid,
type: "get"
}).c("query", {
xmlns: Strophe.NS.DISCO_ITEMS
});
return this._connection.sendIQ(iq, handle_cb);
},
test_append_nick: function(room, nick) {
return room + (nick != null ? "/" + (Strophe.escapeNode(nick)) : "");
}
});
XmppRoom = (function() {
XmppRoom.prototype.roster = {};
XmppRoom.prototype._message_handlers = {};
XmppRoom.prototype._presence_handlers = {};
XmppRoom.prototype._roster_handlers = {};
XmppRoom.prototype._handler_ids = 0;
function XmppRoom(client, name, nick, password) {
this.client = client;
this.name = name;
this.nick = nick;
this.password = password;
this._roomRosterHandler = __bind(this._roomRosterHandler, this);
this._addOccupant = __bind(this._addOccupant, this);
if (client.muc) this.client = client.muc;
this.name = Strophe.getBareJidFromJid(name);
this.client.rooms[this.name] = this;
this.addHandler('presence', this._roomRosterHandler);
}
XmppRoom.prototype.join = function(msg_handler_cb, pres_handler_cb) {
if (!this.client.rooms[this.name]) {
return this.client.join(this.name, this.nick, msg_handler_cb, pres_handler_cb, this.password);
}
};
XmppRoom.prototype.leave = function(handler_cb, message) {
this.client.leave(this.name, this.nick, handler_cb, message);
return delete this.client.rooms[this.name];
};
XmppRoom.prototype.message = function(nick, message, html_message, type) {
return this.client.message(this.name, nick, message, html_message, type);
};
XmppRoom.prototype.groupchat = function(message, html_message) {
return this.client.groupchat(this.name, message, html_message);
};
XmppRoom.prototype.invite = function(receiver, reason) {
return this.client.invite(this.name, receiver, reason);
};
XmppRoom.prototype.directInvite = function(receiver, reason) {
return this.client.directInvite(this.name, receiver, reason, this.password);
};
XmppRoom.prototype.configure = function(handler_cb) {
return this.client.configure(this.name, handler_cb);
};
XmppRoom.prototype.cancelConfigure = function() {
return this.client.cancelConfigure(this.name);
};
XmppRoom.prototype.saveConfiguration = function(configarray) {
return this.client.saveConfiguration(this.name, configarray);
};
XmppRoom.prototype.queryOccupants = function(success_cb, error_cb) {
return this.client.queryOccupants(this.name, success_cb, error_cb);
};
XmppRoom.prototype.setTopic = function(topic) {
return this.client.setTopic(this.name, topic);
};
XmppRoom.prototype.modifyRole = function(nick, role, reason, success_cb, error_cb) {
return this.client.modifyRole(this.name, nick, role, reason, success_cb, error_cb);
};
XmppRoom.prototype.kick = function(nick, reason, handler_cb, error_cb) {
return this.client.kick(this.name, nick, reason, handler_cb, error_cb);
};
XmppRoom.prototype.voice = function(nick, reason, handler_cb, error_cb) {
return this.client.voice(this.name, nick, reason, handler_cb, error_cb);
};
XmppRoom.prototype.mute = function(nick, reason, handler_cb, error_cb) {
return this.client.mute(this.name, nick, reason, handler_cb, error_cb);
};
XmppRoom.prototype.op = function(nick, reason, handler_cb, error_cb) {
return this.client.op(this.name, nick, reason, handler_cb, error_cb);
};
XmppRoom.prototype.deop = function(nick, reason, handler_cb, error_cb) {
return this.client.deop(this.name, nick, reason, handler_cb, error_cb);
};
XmppRoom.prototype.modifyAffiliation = function(jid, affiliation, reason, success_cb, error_cb) {
return this.client.modifyAffiliation(this.name, jid, affiliation, reason, success_cb, error_cb);
};
XmppRoom.prototype.ban = function(jid, reason, handler_cb, error_cb) {
return this.client.ban(this.name, jid, reason, handler_cb, error_cb);
};
XmppRoom.prototype.member = function(jid, reason, handler_cb, error_cb) {
return this.client.member(this.name, jid, reason, handler_cb, error_cb);
};
XmppRoom.prototype.revoke = function(jid, reason, handler_cb, error_cb) {
return this.client.revoke(this.name, jid, reason, handler_cb, error_cb);
};
XmppRoom.prototype.owner = function(jid, reason, handler_cb, error_cb) {
return this.client.owner(this.name, jid, reason, handler_cb, error_cb);
};
XmppRoom.prototype.admin = function(jid, reason, handler_cb, error_cb) {
return this.client.admin(this.name, jid, reason, handler_cb, error_cb);
};
XmppRoom.prototype.changeNick = function(nick) {
this.nick = nick;
return this.client.changeNick(this.name, nick);
};
XmppRoom.prototype.setStatus = function(show, status) {
return this.client.setStatus(this.name, this.nick, show, status);
};
/*Function
Adds a handler to the MUC room.
Parameters:
(String) handler_type - 'message', 'presence' or 'roster'.
(Function) handler - The handler function.
Returns:
id - the id of handler.
*/
XmppRoom.prototype.addHandler = function(handler_type, handler) {
var id;
id = this._handler_ids++;
switch (handler_type) {
case 'presence':
this._presence_handlers[id] = handler;
break;
case 'message':
this._message_handlers[id] = handler;
break;
case 'roster':
this._roster_handlers[id] = handler;
break;
default:
this._handler_ids--;
return null;
}
return id;
};
/*Function
Removes a handler from the MUC room.
This function takes ONLY ids returned by the addHandler function
of this room. passing handler ids returned by connection.addHandler
may brake things!
Parameters:
(number) id - the id of the handler
*/
XmppRoom.prototype.removeHandler = function(id) {
delete this._presence_handlers[id];
delete this._message_handlers[id];
return delete this._roster_handlers[id];
};
/*Function
Creates and adds an Occupant to the Room Roster.
Parameters:
(Object) data - the data the Occupant is filled with
Returns:
occ - the created Occupant.
*/
XmppRoom.prototype._addOccupant = function(data) {
var occ;
occ = new Occupant(data, this);
this.roster[occ.nick] = occ;
return occ;
};
/*Function
The standard handler that managed the Room Roster.
Parameters:
(Object) pres - the presence stanza containing user information
*/
XmppRoom.prototype._roomRosterHandler = function(pres) {
var data, handler, id, newnick, nick, _ref;
data = XmppRoom._parsePresence(pres);
nick = data.nick;
newnick = data.newnick || null;
switch (data.type) {
case 'error':
return;
case 'unavailable':
if (newnick) {
data.nick = newnick;
if (this.roster[nick] && this.roster[newnick]) {
this.roster[nick].update(this.roster[newnick]);
this.roster[newnick] = this.roster[nick];
}
if (this.roster[nick] && !this.roster[newnick]) {
this.roster[newnick] = this.roster[nick].update(data);
}
}
delete this.roster[nick];
break;
default:
if (this.roster[nick]) {
this.roster[nick].update(data);
} else {
this._addOccupant(data);
}
}
_ref = this._roster_handlers;
for (id in _ref) {
handler = _ref[id];
if (!handler(this.roster, this)) delete this._roster_handlers[id];
}
return true;
};
/*Function
Parses a presence stanza
Parameters:
(Object) data - the data extracted from the presence stanza
*/
XmppRoom._parsePresence = function(pres) {
var a, c, c2, data, _i, _j, _len, _len2, _ref, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8;
data = {};
a = pres.attributes;
data.nick = Strophe.getResourceFromJid(a.from.textContent);
data.type = ((_ref = a.type) != null ? _ref.textContent : void 0) || null;
data.states = [];
_ref2 = pres.childNodes;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
c = _ref2[_i];
switch (c.nodeName) {
case "status":
data.status = c.textContent || null;
break;
case "show":
data.show = c.textContent || null;
break;
case "x":
a = c.attributes;
if (((_ref3 = a.xmlns) != null ? _ref3.textContent : void 0) === Strophe.NS.MUC_USER) {
_ref4 = c.childNodes;
for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) {
c2 = _ref4[_j];
switch (c2.nodeName) {
case "item":
a = c2.attributes;
data.affiliation = ((_ref5 = a.affiliation) != null ? _ref5.textContent : void 0) || null;
data.role = ((_ref6 = a.role) != null ? _ref6.textContent : void 0) || null;
data.jid = ((_ref7 = a.jid) != null ? _ref7.textContent : void 0) || null;
data.newnick = ((_ref8 = a.nick) != null ? _ref8.textContent : void 0) || null;
break;
case "status":
if (c2.attributes.code) {
data.states.push(c2.attributes.code.textContent);
}
}
}
}
}
}
return data;
};
return XmppRoom;
})();
RoomConfig = (function() {
function RoomConfig(info) {
this.parse = __bind(this.parse, this); if (info != null) this.parse(info);
}
RoomConfig.prototype.parse = function(result) {
var attr, attrs, child, field, identity, query, _i, _j, _k, _len, _len2, _len3, _ref;
query = result.getElementsByTagName("query")[0].childNodes;
this.identities = [];
this.features = [];
this.x = [];
for (_i = 0, _len = query.length; _i < _len; _i++) {
child = query[_i];
attrs = child.attributes;
switch (child.nodeName) {
case "identity":
identity = {};
for (_j = 0, _len2 = attrs.length; _j < _len2; _j++) {
attr = attrs[_j];
identity[attr.name] = attr.textContent;
}
this.identities.push(identity);
break;
case "feature":
this.features.push(attrs["var"].textContent);
break;
case "x":
attrs = child.childNodes[0].attributes;
if ((!attrs["var"].textContent === 'FORM_TYPE') || (!attrs.type.textContent === 'hidden')) {
break;
}
_ref = child.childNodes;
for (_k = 0, _len3 = _ref.length; _k < _len3; _k++) {
field = _ref[_k];
if (!(!field.attributes.type)) continue;
attrs = field.attributes;
this.x.push({
"var": attrs["var"].textContent,
label: attrs.label.textContent || "",
value: field.firstChild.textContent || ""
});
}
}
}
return {
"identities": this.identities,
"features": this.features,
"x": this.x
};
};
return RoomConfig;
})();
Occupant = (function() {
function Occupant(data, room) {
this.room = room;
this.update = __bind(this.update, this);
this.admin = __bind(this.admin, this);
this.owner = __bind(this.owner, this);
this.revoke = __bind(this.revoke, this);
this.member = __bind(this.member, this);
this.ban = __bind(this.ban, this);
this.modifyAffiliation = __bind(this.modifyAffiliation, this);
this.deop = __bind(this.deop, this);
this.op = __bind(this.op, this);
this.mute = __bind(this.mute, this);
this.voice = __bind(this.voice, this);
this.kick = __bind(this.kick, this);
this.modifyRole = __bind(this.modifyRole, this);
this.update(data);
}
Occupant.prototype.modifyRole = function(role, reason, success_cb, error_cb) {
return this.room.modifyRole(this.nick, role, reason, success_cb, error_cb);
};
Occupant.prototype.kick = function(reason, handler_cb, error_cb) {
return this.room.kick(this.nick, reason, handler_cb, error_cb);
};
Occupant.prototype.voice = function(reason, handler_cb, error_cb) {
return this.room.voice(this.nick, reason, handler_cb, error_cb);
};
Occupant.prototype.mute = function(reason, handler_cb, error_cb) {
return this.room.mute(this.nick, reason, handler_cb, error_cb);
};
Occupant.prototype.op = function(reason, handler_cb, error_cb) {
return this.room.op(this.nick, reason, handler_cb, error_cb);
};
Occupant.prototype.deop = function(reason, handler_cb, error_cb) {
return this.room.deop(this.nick, reason, handler_cb, error_cb);
};
Occupant.prototype.modifyAffiliation = function(affiliation, reason, success_cb, error_cb) {
return this.room.modifyAffiliation(this.jid, affiliation, reason, success_cb, error_cb);
};
Occupant.prototype.ban = function(reason, handler_cb, error_cb) {
return this.room.ban(this.jid, reason, handler_cb, error_cb);
};
Occupant.prototype.member = function(reason, handler_cb, error_cb) {
return this.room.member(this.jid, reason, handler_cb, error_cb);
};
Occupant.prototype.revoke = function(reason, handler_cb, error_cb) {
return this.room.revoke(this.jid, reason, handler_cb, error_cb);
};
Occupant.prototype.owner = function(reason, handler_cb, error_cb) {
return this.room.owner(this.jid, reason, handler_cb, error_cb);
};
Occupant.prototype.admin = function(reason, handler_cb, error_cb) {
return this.room.admin(this.jid, reason, handler_cb, error_cb);
};
Occupant.prototype.update = function(data) {
this.nick = data.nick || null;
this.affiliation = data.affiliation || null;
this.role = data.role || null;
this.jid = data.jid || null;
this.status = data.status || null;
this.show = data.show || null;
return this;
};
return Occupant;
})();
}).call(this);
}));
/*
Copyright 2010, François de Metz <francois@2metz.fr>
*/
/**
* Roster Plugin
* Allow easily roster management
*
* Features
* * Get roster from server
* * handle presence
* * handle roster iq
* * subscribe/unsubscribe
* * authorize/unauthorize
* * roster versioning (xep 237)
*/
// AMD/global registrations
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([
"Libraries/strophe"
], function () {
return factory(jQuery, console);
}
);
}
}(this, function ($, console) {
Strophe.addConnectionPlugin('roster',
{
_connection: null,
_callbacks : [],
/** Property: items
* Roster items
* [
* {
* name : "",
* jid : "",
* subscription : "",
* ask : "",
* groups : ["", ""],
* resources : {
* myresource : {
* show : "",
* status : "",
* priority : ""
* }
* }
* }
* ]
*/
items : [],
/** Property: ver
* current roster revision
* always null if server doesn't support xep 237
*/
ver : null,
/** Function: init
* Plugin init
*
* Parameters:
* (Strophe.Connection) conn - Strophe connection
*/
init: function(conn)
{
this._connection = conn;
this.items = [];
// Override the connect and attach methods to always add presence and roster handlers.
// They are removed when the connection disconnects, so must be added on connection.
var oldCallback, roster = this, _connect = conn.connect, _attach = conn.attach;
var newCallback = function(status)
{
if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED)
{
try
{
// Presence subscription
conn.addHandler(roster._onReceivePresence.bind(roster), null, 'presence', null, null, null);
conn.addHandler(roster._onReceiveIQ.bind(roster), Strophe.NS.ROSTER, 'iq', "set", null, null);
}
catch (e)
{
Strophe.error(e);
}
}
if (oldCallback !== null)
oldCallback.apply(this, arguments);
};
conn.connect = function(jid, pass, callback, wait, hold)
{
oldCallback = callback;
if (typeof arguments[0] == "undefined")
arguments[0] = null;
if (typeof arguments[1] == "undefined")
arguments[1] = null;
arguments[2] = newCallback;
_connect.apply(conn, arguments);
};
conn.attach = function(jid, sid, rid, callback, wait, hold, wind)
{
oldCallback = callback;
if (typeof arguments[0] == "undefined")
arguments[0] = null;
if (typeof arguments[1] == "undefined")
arguments[1] = null;
if (typeof arguments[2] == "undefined")
arguments[2] = null;
arguments[3] = newCallback;
_attach.apply(conn, arguments);
};
Strophe.addNamespace('ROSTER_VER', 'urn:xmpp:features:rosterver');
},
/** Function: supportVersioning
* return true if roster versioning is enabled on server
*/
supportVersioning: function()
{
return (this._connection.features && this._connection.features.getElementsByTagName('ver').length > 0);
},
/** Function: get
* Get Roster on server
*
* Parameters:
* (Function) userCallback - callback on roster result
* (String) ver - current rev of roster
* (only used if roster versioning is enabled)
* (Array) items - initial items of ver
* (only used if roster versioning is enabled)
* In browser context you can use sessionStorage
* to store your roster in json (JSON.stringify())
*/
get: function(userCallback, ver, items)
{
var attrs = {xmlns: Strophe.NS.ROSTER};
this.items = [];
if (this.supportVersioning())
{
// empty rev because i want an rev attribute in the result
attrs.ver = ver || '';
this.items = items || [];
}
var iq = $iq({type: 'get', 'id' : this._connection.getUniqueId('roster')}).c('query', attrs);
this._connection.sendIQ(iq,
this._onReceiveRosterSuccess.bind(this, userCallback),
this._onReceiveRosterError.bind(this, userCallback));
},
/** Function: registerCallback
* register callback on roster (presence and iq)
*
* Parameters:
* (Function) call_back
*/
registerCallback: function(call_back)
{
this._callbacks.push(call_back);
},
/** Function: findItem
* Find item by JID
*
* Parameters:
* (String) jid
*/
findItem : function(jid)
{
for (var i = 0; i < this.items.length; i++)
{
if (this.items[i] && this.items[i].jid == jid)
{
return this.items[i];
}
}
return false;
},
/** Function: removeItem
* Remove item by JID
*
* Parameters:
* (String) jid
*/
removeItem : function(jid)
{
for (var i = 0; i < this.items.length; i++)
{
if (this.items[i] && this.items[i].jid == jid)
{
this.items.splice(i, 1);
return true;
}
}
return false;
},
/** Function: subscribe
* Subscribe presence
*
* Parameters:
* (String) jid
* (String) message
*/
subscribe: function(jid, message)
{
var pres = $pres({to: jid, type: "subscribe"});
if (message && message != "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: unsubscribe
* Unsubscribe presence
*
* Parameters:
* (String) jid
* (String) message
*/
unsubscribe: function(jid, message)
{
var pres = $pres({to: jid, type: "unsubscribe"});
if (message && message != "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: authorize
* Authorize presence subscription
*
* Parameters:
* (String) jid
* (String) message
*/
authorize: function(jid, message)
{
var pres = $pres({to: jid, type: "subscribed"});
if (message && message != "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: unauthorize
* Unauthorize presence subscription
*
* Parameters:
* (String) jid
* (String) message
*/
unauthorize: function(jid, message)
{
var pres = $pres({to: jid, type: "unsubscribed"});
if (message && message != "")
pres.c("status").t(message);
this._connection.send(pres);
},
/** Function: add
* Add roster item
*
* Parameters:
* (String) jid - item jid
* (String) name - name
* (Array) groups
* (Function) call_back
*/
add: function(jid, name, groups, call_back)
{
var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: jid,
name: name});
for (var i = 0; i < groups.length; i++)
{
iq.c('group').t(groups[i]).up();
}
this._connection.sendIQ(iq, call_back, call_back);
},
/** Function: update
* Update roster item
*
* Parameters:
* (String) jid - item jid
* (String) name - name
* (Array) groups
* (Function) call_back
*/
update: function(jid, name, groups, call_back)
{
var item = this.findItem(jid);
if (!item)
{
throw "item not found";
}
var newName = name || item.name;
var newGroups = groups || item.groups;
var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid,
name: newName});
for (var i = 0; i < newGroups.length; i++)
{
iq.c('group').t(newGroups[i]).up();
}
this._connection.sendIQ(iq, call_back, call_back);
},
/** Function: remove
* Remove roster item
*
* Parameters:
* (String) jid - item jid
* (Function) call_back
*/
remove: function(jid, call_back)
{
var item = this.findItem(jid);
if (!item)
{
throw "item not found";
}
var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid,
subscription: "remove"});
this._connection.sendIQ(iq, call_back, call_back);
},
/** PrivateFunction: _onReceiveRosterSuccess
*
*/
_onReceiveRosterSuccess: function(userCallback, stanza)
{
this._updateItems(stanza);
userCallback(this.items);
},
/** PrivateFunction: _onReceiveRosterError
*
*/
_onReceiveRosterError: function(userCallback, stanza)
{
userCallback(this.items);
},
/** PrivateFunction: _onReceivePresence
* Handle presence
*/
_onReceivePresence : function(presence)
{
// TODO: from is optional
var jid = presence.getAttribute('from');
var from = Strophe.getBareJidFromJid(jid);
var item = this.findItem(from);
// not in roster
if (!item)
{
return true;
}
var type = presence.getAttribute('type');
if (type == 'unavailable')
{
delete item.resources[Strophe.getResourceFromJid(jid)];
}
else if (!type)
{
// TODO: add timestamp
item.resources[Strophe.getResourceFromJid(jid)] = {
show : (presence.getElementsByTagName('show').length != 0) ? Strophe.getText(presence.getElementsByTagName('show')[0]) : "",
status : (presence.getElementsByTagName('status').length != 0) ? Strophe.getText(presence.getElementsByTagName('status')[0]) : "",
priority : (presence.getElementsByTagName('priority').length != 0) ? Strophe.getText(presence.getElementsByTagName('priority')[0]) : ""
};
}
else
{
// Stanza is not a presence notification. (It's probably a subscription type stanza.)
return true;
}
this._call_backs(this.items, item);
return true;
},
/** PrivateFunction: _call_backs
*
*/
_call_backs : function(items, item)
{
for (var i = 0; i < this._callbacks.length; i++) // [].forEach my love ...
{
this._callbacks[i](items, item);
}
},
/** PrivateFunction: _onReceiveIQ
* Handle roster push.
*/
_onReceiveIQ : function(iq)
{
var id = iq.getAttribute('id');
var from = iq.getAttribute('from');
// Receiving client MUST ignore stanza unless it has no from or from = user's JID.
if (from && from != "" && from != this._connection.jid && from != Strophe.getBareJidFromJid(this._connection.jid))
return true;
var iqresult = $iq({type: 'result', id: id, from: this._connection.jid});
this._connection.send(iqresult);
this._updateItems(iq);
return true;
},
/** PrivateFunction: _updateItems
* Update items from iq
*/
_updateItems : function(iq)
{
var query = iq.getElementsByTagName('query');
if (query.length != 0)
{
this.ver = query.item(0).getAttribute('ver');
var self = this;
Strophe.forEachChild(query.item(0), 'item',
function (item)
{
self._updateItem(item);
}
);
}
this._call_backs(this.items);
},
/** PrivateFunction: _updateItem
* Update internal representation of roster item
*/
_updateItem : function(item)
{
var jid = item.getAttribute("jid");
var name = item.getAttribute("name");
var subscription = item.getAttribute("subscription");
var ask = item.getAttribute("ask");
var groups = [];
Strophe.forEachChild(item, 'group',
function(group)
{
groups.push(Strophe.getText(group));
}
);
if (subscription == "remove")
{
this.removeItem(jid);
return;
}
var item = this.findItem(jid);
if (!item)
{
this.items.push({
name : name,
jid : jid,
subscription : subscription,
ask : ask,
groups : groups,
resources : {}
});
}
else
{
item.name = name;
item.subscription = subscription;
item.ask = ask;
item.groups = groups;
}
}
});
}));
// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.3';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, val, context) {
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
if (a === void 0) return 1;
if (b === void 0) return -1;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (_.isArguments(obj)) return slice.call(obj);
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var results = [];
// The `isSorted` flag is irrelevant if the array only contains two elements.
if (array.length < 3) isSorted = true;
_.reduce(initial, function (memo, value, index) {
if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
memo.push(value);
results.push(array[index]);
}
return memo;
}, []);
return results;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1), true);
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(null, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
result = func.apply(context, args);
}
whenDone();
throttling = true;
return result;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
if (immediate && !timeout) func.apply(context, args);
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj) {
var result = {};
each(_.flatten(slice.call(arguments, 1)), function(key) {
if (key in obj) result[key] = obj[key];
});
return result;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is a given object a finite number?
_.isFinite = function(obj) {
return _.isNumber(obj) && isFinite(obj);
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// If the value of the named property is a function then invoke it;
// otherwise, return it.
_.result = function(object, property) {
if (object == null) return null;
var value = object[property];
return _.isFunction(value) ? value.call(object) : value;
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
'\\': '\\',
"'": "'",
'r': '\r',
'n': '\n',
't': '\t',
'u2028': '\u2028',
'u2029': '\u2029'
};
for (var p in escapes) escapes[escapes[p]] = p;
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
settings = _.defaults(settings || {}, _.templateSettings);
// Compile the template source, taking care to escape characters that
// cannot be included in a string literal and then unescape them in code
// blocks.
var source = "__p+='" + text
.replace(escaper, function(match) {
return '\\' + escapes[match];
})
.replace(settings.escape || noMatch, function(match, code) {
return "'+\n_.escape(" + unescape(code) + ")+\n'";
})
.replace(settings.interpolate || noMatch, function(match, code) {
return "'+\n(" + unescape(code) + ")+\n'";
})
.replace(settings.evaluate || noMatch, function(match, code) {
return "';\n" + unescape(code) + "\n;__p+='";
}) + "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __p='';" +
"var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
source + "return __p;\n";
var render = new Function(settings.variable || 'obj', '_', source);
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for build time
// precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
source + '}';
return template;
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);
// Underscore.string
// (c) 2010 Esa-Matti Suuronen <esa-matti aet suuronen dot org>
// Underscore.string is freely distributable under the terms of the MIT license.
// Documentation: https://github.com/epeli/underscore.string
// Some code is borrowed from MooTools and Alexandru Marasteanu.
// Version '2.2.0rc'
!function(root, String){
'use strict';
// Defining helper functions.
var nativeTrim = String.prototype.trim;
var nativeTrimRight = String.prototype.trimRight;
var nativeTrimLeft = String.prototype.trimLeft;
var parseNumber = function(source) { return source * 1 || 0; };
var strRepeat = function(str, qty){
if (qty < 1) return '';
var result = '';
while (qty > 0) {
if (qty & 1) result += str;
qty >>= 1, str += str;
}
return result;
};
var slice = [].slice;
var defaultToWhiteSpace = function(characters) {
if (characters == null)
return '\\s';
else if (characters.source)
return characters.source;
else
return '[' + _s.escapeRegExp(characters) + ']';
};
var escapeChars = {
lt: '<',
gt: '>',
quot: '"',
apos: "'",
amp: '&'
};
var reversedEscapeChars = {};
for(var key in escapeChars){ reversedEscapeChars[escapeChars[key]] = key; }
// sprintf() for JavaScript 0.7-beta1
// http://www.diveintojavascript.com/projects/javascript-sprintf
//
// Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
// All rights reserved.
var sprintf = (function() {
function get_type(variable) {
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
}
var str_repeat = strRepeat;
var str_format = function() {
if (!str_format.cache.hasOwnProperty(arguments[0])) {
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
}
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
};
str_format.format = function(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
for (i = 0; i < tree_length; i++) {
node_type = get_type(parse_tree[i]);
if (node_type === 'string') {
output.push(parse_tree[i]);
}
else if (node_type === 'array') {
match = parse_tree[i]; // convenience purposes only
if (match[2]) { // keyword argument
arg = argv[cursor];
for (k = 0; k < match[2].length; k++) {
if (!arg.hasOwnProperty(match[2][k])) {
throw new Error(sprintf('[_.sprintf] property "%s" does not exist', match[2][k]));
}
arg = arg[match[2][k]];
}
} else if (match[1]) { // positional argument (explicit)
arg = argv[match[1]];
}
else { // positional argument (implicit)
arg = argv[cursor++];
}
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
throw new Error(sprintf('[_.sprintf] expecting number but found %s', get_type(arg)));
}
switch (match[8]) {
case 'b': arg = arg.toString(2); break;
case 'c': arg = String.fromCharCode(arg); break;
case 'd': arg = parseInt(arg, 10); break;
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
case 'o': arg = arg.toString(8); break;
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
case 'u': arg = Math.abs(arg); break;
case 'x': arg = arg.toString(16); break;
case 'X': arg = arg.toString(16).toUpperCase(); break;
}
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
pad_length = match[6] - String(arg).length;
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
output.push(match[5] ? arg + pad : pad + arg);
}
}
return output.join('');
};
str_format.cache = {};
str_format.parse = function(fmt) {
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
while (_fmt) {
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
parse_tree.push(match[0]);
}
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
parse_tree.push('%');
}
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1;
var field_list = [], replacement_field = match[2], field_match = [];
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else {
throw new Error('[_.sprintf] huh?');
}
}
}
else {
throw new Error('[_.sprintf] huh?');
}
match[2] = field_list;
}
else {
arg_names |= 2;
}
if (arg_names === 3) {
throw new Error('[_.sprintf] mixing positional and named placeholders is not (yet) supported');
}
parse_tree.push(match);
}
else {
throw new Error('[_.sprintf] huh?');
}
_fmt = _fmt.substring(match[0].length);
}
return parse_tree;
};
return str_format;
})();
// Defining underscore.string
var _s = {
VERSION: '2.2.0rc',
isBlank: function(str){
if (str == null) str = '';
return (/^\s*$/).test(str);
},
stripTags: function(str){
if (str == null) return '';
return String(str).replace(/<\/?[^>]+>/g, '');
},
capitalize : function(str){
str = str == null ? '' : String(str);
return str.charAt(0).toUpperCase() + str.slice(1);
},
chop: function(str, step){
if (str == null) return [];
str = String(str);
step = ~~step;
return step > 0 ? str.match(new RegExp('.{1,' + step + '}', 'g')) : [str];
},
clean: function(str){
return _s.strip(str).replace(/\s+/g, ' ');
},
count: function(str, substr){
if (str == null || substr == null) return 0;
return String(str).split(substr).length - 1;
},
chars: function(str) {
if (str == null) return [];
return String(str).split('');
},
swapCase: function(str) {
if (str == null) return '';
return String(str).replace(/\S/g, function(c){
return c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase();
});
},
escapeHTML: function(str) {
if (str == null) return '';
return String(str).replace(/[&<>"']/g, function(m){ return '&' + reversedEscapeChars[m] + ';'; });
},
unescapeHTML: function(str) {
if (str == null) return '';
return String(str).replace(/\&([^;]+);/g, function(entity, entityCode){
var match;
if (entityCode in escapeChars) {
return escapeChars[entityCode];
} else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) {
return String.fromCharCode(parseInt(match[1], 16));
} else if (match = entityCode.match(/^#(\d+)$/)) {
return String.fromCharCode(~~match[1]);
} else {
return entity;
}
});
},
escapeRegExp: function(str){
if (str == null) return '';
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
},
splice: function(str, i, howmany, substr){
var arr = _s.chars(str);
arr.splice(~~i, ~~howmany, substr);
return arr.join('');
},
insert: function(str, i, substr){
return _s.splice(str, i, 0, substr);
},
include: function(str, needle){
if (needle === '') return true;
if (str == null) return false;
return String(str).indexOf(needle) !== -1;
},
join: function() {
var args = slice.call(arguments),
separator = args.shift();
if (separator == null) separator = '';
return args.join(separator);
},
lines: function(str) {
if (str == null) return [];
return String(str).split("\n");
},
reverse: function(str){
return _s.chars(str).reverse().join('');
},
startsWith: function(str, starts){
if (starts === '') return true;
if (str == null || starts == null) return false;
str = String(str); starts = String(starts);
return str.length >= starts.length && str.slice(0, starts.length) === starts;
},
endsWith: function(str, ends){
if (ends === '') return true;
if (str == null || ends == null) return false;
str = String(str); ends = String(ends);
return str.length >= ends.length && str.slice(str.length - ends.length) === ends;
},
succ: function(str){
if (str == null) return '';
str = String(str);
return str.slice(0, -1) + String.fromCharCode(str.charCodeAt(str.length-1) + 1);
},
titleize: function(str){
if (str == null) return '';
return String(str).replace(/(?:^|\s)\S/g, function(c){ return c.toUpperCase(); });
},
camelize: function(str){
return _s.trim(str).replace(/[-_\s]+(.)?/g, function(match, c){ return c.toUpperCase(); });
},
underscored: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase();
},
dasherize: function(str){
return _s.trim(str).replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
},
classify: function(str){
return _s.titleize(String(str).replace(/_/g, ' ')).replace(/\s/g, '');
},
humanize: function(str){
return _s.capitalize(_s.underscored(str).replace(/_id$/,'').replace(/_/g, ' '));
},
trim: function(str, characters){
if (str == null) return '';
if (!characters && nativeTrim) return nativeTrim.call(str);
characters = defaultToWhiteSpace(characters);
return String(str).replace(new RegExp('\^' + characters + '+|' + characters + '+$', 'g'), '');
},
ltrim: function(str, characters){
if (str == null) return '';
if (!characters && nativeTrimLeft) return nativeTrimLeft.call(str);
characters = defaultToWhiteSpace(characters);
return String(str).replace(new RegExp('^' + characters + '+'), '');
},
rtrim: function(str, characters){
if (str == null) return '';
if (!characters && nativeTrimRight) return nativeTrimRight.call(str);
characters = defaultToWhiteSpace(characters);
return String(str).replace(new RegExp(characters + '+$'), '');
},
truncate: function(str, length, truncateStr){
if (str == null) return '';
str = String(str); truncateStr = truncateStr || '...';
length = ~~length;
return str.length > length ? str.slice(0, length) + truncateStr : str;
},
/**
* _s.prune: a more elegant version of truncate
* prune extra chars, never leaving a half-chopped word.
* @author github.com/rwz
*/
prune: function(str, length, pruneStr){
if (str == null) return '';
str = String(str); length = ~~length;
pruneStr = pruneStr != null ? String(pruneStr) : '...';
if (str.length <= length) return str;
var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; },
template = str.slice(0, length+1).replace(/.(?=\W*\w*$)/g, tmpl); // 'Hello, world' -> 'HellAA AAAAA'
if (template.slice(template.length-2).match(/\w\w/))
template = template.replace(/\s*\S+$/, '');
else
template = _s.rtrim(template.slice(0, template.length-1));
return (template+pruneStr).length > str.length ? str : str.slice(0, template.length)+pruneStr;
},
words: function(str, delimiter) {
if (_s.isBlank(str)) return [];
return _s.trim(str, delimiter).split(delimiter || /\s+/);
},
pad: function(str, length, padStr, type) {
str = str == null ? '' : String(str);
length = ~~length;
var padlen = 0;
if (!padStr)
padStr = ' ';
else if (padStr.length > 1)
padStr = padStr.charAt(0);
switch(type) {
case 'right':
padlen = length - str.length;
return str + strRepeat(padStr, padlen);
case 'both':
padlen = length - str.length;
return strRepeat(padStr, Math.ceil(padlen/2)) + str
+ strRepeat(padStr, Math.floor(padlen/2));
default: // 'left'
padlen = length - str.length;
return strRepeat(padStr, padlen) + str;
}
},
lpad: function(str, length, padStr) {
return _s.pad(str, length, padStr);
},
rpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'right');
},
lrpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'both');
},
sprintf: sprintf,
vsprintf: function(fmt, argv){
argv.unshift(fmt);
return sprintf.apply(null, argv);
},
toNumber: function(str, decimals) {
if (str == null || str == '') return 0;
str = String(str);
var num = parseNumber(parseNumber(str).toFixed(~~decimals));
return num === 0 && !str.match(/^0+$/) ? Number.NaN : num;
},
numberFormat : function(number, dec, dsep, tsep) {
if (isNaN(number) || number == null) return '';
number = number.toFixed(~~dec);
tsep = tsep || ',';
var parts = number.split('.'), fnums = parts[0],
decimals = parts[1] ? (dsep || '.') + parts[1] : '';
return fnums.replace(/(\d)(?=(?:\d{3})+$)/g, '$1' + tsep) + decimals;
},
strRight: function(str, sep){
if (str == null) return '';
str = String(str); sep = sep != null ? String(sep) : sep;
var pos = !sep ? -1 : str.indexOf(sep);
return ~pos ? str.slice(pos+sep.length, str.length) : str;
},
strRightBack: function(str, sep){
if (str == null) return '';
str = String(str); sep = sep != null ? String(sep) : sep;
var pos = !sep ? -1 : str.lastIndexOf(sep);
return ~pos ? str.slice(pos+sep.length, str.length) : str;
},
strLeft: function(str, sep){
if (str == null) return '';
str = String(str); sep = sep != null ? String(sep) : sep;
var pos = !sep ? -1 : str.indexOf(sep);
return ~pos ? str.slice(0, pos) : str;
},
strLeftBack: function(str, sep){
if (str == null) return '';
str += ''; sep = sep != null ? ''+sep : sep;
var pos = str.lastIndexOf(sep);
return ~pos ? str.slice(0, pos) : str;
},
toSentence: function(array, separator, lastSeparator, serial) {
separator = separator || ', '
lastSeparator = lastSeparator || ' and '
var a = array.slice(), lastMember = a.pop();
if (array.length > 2 && serial) lastSeparator = _s.rtrim(separator) + lastSeparator;
return a.length ? a.join(separator) + lastSeparator + lastMember : lastMember;
},
toSentenceSerial: function() {
var args = slice.call(arguments);
args[3] = true;
return _s.toSentence.apply(_s, args);
},
slugify: function(str) {
if (str == null) return '';
var from = "ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",
to = "aaaaaaaaceeeeeiiiilnoooooouuuunczz",
regex = new RegExp(defaultToWhiteSpace(from), 'g');
str = String(str).toLowerCase().replace(regex, function(c){
var index = from.indexOf(c);
return to.charAt(index) || '-';
});
return _s.dasherize(str.replace(/[^\w\s-]/g, ''));
},
surround: function(str, wrapper) {
return [wrapper, str, wrapper].join('');
},
quote: function(str) {
return _s.surround(str, '"');
},
exports: function() {
var result = {};
for (var prop in this) {
if (!this.hasOwnProperty(prop) || prop.match(/^(?:include|contains|reverse)$/)) continue;
result[prop] = this[prop];
}
return result;
},
repeat: function(str, qty, separator){
if (str == null) return '';
qty = ~~qty;
// using faster implementation if separator is not needed;
if (separator == null) return strRepeat(String(str), qty);
// this one is about 300x slower in Google Chrome
for (var repeat = []; qty > 0; repeat[--qty] = str) {}
return repeat.join(separator);
},
levenshtein: function(str1, str2) {
if (str1 == null && str2 == null) return 0;
if (str1 == null) return String(str2).length;
if (str2 == null) return String(str1).length;
str1 = String(str1); str2 = String(str2);
var current = [], prev, value;
for (var i = 0; i <= str2.length; i++)
for (var j = 0; j <= str1.length; j++) {
if (i && j)
if (str1.charAt(j - 1) === str2.charAt(i - 1))
value = prev;
else
value = Math.min(current[j], current[j - 1], prev) + 1;
else
value = i + j;
prev = current[j];
current[j] = value;
}
return current.pop();
}
};
// Aliases
_s.strip = _s.trim;
_s.lstrip = _s.ltrim;
_s.rstrip = _s.rtrim;
_s.center = _s.lrpad;
_s.rjust = _s.lpad;
_s.ljust = _s.rpad;
_s.contains = _s.include;
_s.q = _s.quote;
// CommonJS module is defined
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
// Export module
module.exports = _s;
}
exports._s = _s;
} else if (typeof define === 'function' && define.amd) {
// Register as a named module with AMD.
define('underscore.string', [], function() {
return _s;
});
} else {
// Integrate with Underscore.js if defined
// or create our own underscore object.
root._ = root._ || {};
root._.string = root._.str = _s;
}
}(this, String);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment