Created
June 25, 2014 15:04
-
-
Save rtorr/7a0ba28badb533a5c9f6 to your computer and use it in GitHub Desktop.
Browser standalone ampersand-state
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(root, factory) { | |
if (typeof exports !== 'undefined') { | |
var _ = require('underscore'); | |
var BBEvents = require('backbone-events-standalone'); | |
var KeyTree = require('key-tree-store'); | |
var arrayNext = require('array-next'); | |
factory(root, exports, _, BBEvents, KeyTree, arrayNext); | |
} else { | |
root.AmpersandState = factory(root, {}, root._, (root.Backbone.Events || root.BackboneEvents), root.HJ_KeyTreeStore, root.HJ_arrayNext); | |
} | |
}(this, function(root, AmpersandState, _, BBEvents, KeyTree, arrayNext) { | |
var KeyTree = KeyTree; | |
var arrayNext = arrayNext; | |
var changeRE = /^change:/; | |
function Base(attrs, options) { | |
options || (options = {}); | |
this.cid = _.uniqueId('state'); | |
this._values = {}; | |
this._definition = Object.create(this._definition); | |
if (options.parse) attrs = this.parse(attrs, options); | |
this.parent = options.parent; | |
this.collection = options.collection; | |
this._keyTree = new KeyTree(); | |
this._initCollections(); | |
this._initChildren(); | |
this._cache = {}; | |
this._previousAttributes = {}; | |
this._events = {}; | |
if (attrs) this.set(attrs, _.extend({silent: true, initial: true}, options)); | |
this._changed = {}; | |
if (this._derived) this._initDerived(); | |
if (options.init !== false) this.initialize.apply(this, arguments); | |
} | |
_.extend(Base.prototype, BBEvents, { | |
// can be allow, ignore, reject | |
extraProperties: 'ignore', | |
idAttribute: 'id', | |
namespaceAttribute: 'namespace', | |
typeAttribute: 'modelType', | |
// Stubbed out to be overwritten | |
initialize: function () { | |
return this; | |
}, | |
// Get ID of model per configuration. | |
// Should *always* be how ID is determined by other code. | |
getId: function () { | |
return this[this.idAttribute]; | |
}, | |
// Get namespace of model per configuration. | |
// Should *always* be how namespace is determined by other code. | |
getNamespace: function () { | |
return this[this.namespaceAttribute]; | |
}, | |
// Get type of model per configuration. | |
// Should *always* be how type is determined by other code. | |
getType: function () { | |
return this[this.typeAttribute]; | |
}, | |
// A model is new if it has never been saved to the server, and lacks an id. | |
isNew: function () { | |
return this.getId() == null; | |
}, | |
// get HTML-escaped value of attribute | |
escape: function (attr) { | |
return _.escape(this.get(attr)); | |
}, | |
// Check if the model is currently in a valid state. | |
isValid: function (options) { | |
return this._validate({}, _.extend(options || {}, { validate: true })); | |
}, | |
// Parse can be used remap/restructure/rename incoming properties | |
// before they are applied to attributes. | |
parse: function (resp, options) { | |
return resp; | |
}, | |
// Serialize is the inverse of `parse` it lets you massage data | |
// on the way out. Before, sending to server, for example. | |
serialize: function () { | |
var res = this.getAttributes({props: true}, true); | |
_.each(this._children, function (value, key) { | |
res[key] = this[key].serialize(); | |
}, this); | |
_.each(this._collections, function (value, key) { | |
res[key] = this[key].serialize(); | |
}, this); | |
return res; | |
}, | |
// Main set method used by generated setters/getters and can | |
// be used directly if you need to pass options or set multiple | |
// properties at once. | |
set: function (key, value, options) { | |
var self = this; | |
var extraProperties = this.extraProperties; | |
var triggers = []; | |
var changing, changes, newType, newVal, def, cast, err, attr, | |
attrs, dataType, silent, unset, currentVal, initial, hasChanged, isEqual; | |
// Handle both `"key", value` and `{key: value}` -style arguments. | |
if (_.isObject(key) || key === null) { | |
attrs = key; | |
options = value; | |
} else { | |
attrs = {}; | |
attrs[key] = value; | |
} | |
options = options || {}; | |
if (!this._validate(attrs, options)) return false; | |
// Extract attributes and options. | |
unset = options.unset; | |
silent = options.silent; | |
initial = options.initial; | |
changes = []; | |
changing = this._changing; | |
this._changing = true; | |
// if not already changing, store previous | |
if (!changing) { | |
this._previousAttributes = this.attributes; | |
this._changed = {}; | |
} | |
// For each `set` attribute... | |
for (attr in attrs) { | |
newVal = attrs[attr]; | |
newType = typeof newVal; | |
currentVal = this._values[attr]; | |
def = this._definition[attr]; | |
if (!def) { | |
// if this is a child model or collection | |
if (this._children[attr] || this._collections[attr]) { | |
this[attr].set(newVal, options); | |
continue; | |
} else if (extraProperties === 'ignore') { | |
continue; | |
} else if (extraProperties === 'reject') { | |
throw new TypeError('No "' + attr + '" property defined on ' + (this.type || 'this') + ' model and extraProperties not set to "ignore" or "allow"'); | |
} else if (extraProperties === 'allow') { | |
def = this._createPropertyDefinition(attr, 'any'); | |
} | |
} | |
isEqual = this._getCompareForType(def.type); | |
dataType = this._dataTypes[def.type]; | |
// check type if we have one | |
if (dataType && dataType.set) { | |
cast = dataType.set(newVal); | |
newVal = cast.val; | |
newType = cast.type; | |
} | |
// If we've defined a test, run it | |
if (def.test) { | |
err = def.test.call(this, newVal, newType); | |
if (err) { | |
throw new TypeError('Property \'' + attr + '\' failed validation with error: ' + err); | |
} | |
} | |
// If we are required but undefined, throw error. | |
// If we are null and are not allowing null, throw error | |
// If we have a defined type and the new type doesn't match, and we are not null, throw error. | |
if (_.isUndefined(newVal) && def.required) { | |
throw new TypeError('Required property \'' + attr + '\' must be of type ' + def.type + '. Tried to set ' + newVal); | |
} | |
if (_.isNull(newVal) && def.required && !def.allowNull) { | |
throw new TypeError('Property \'' + attr + '\' must be of type ' + def.type + ' (cannot be null). Tried to set ' + newVal); | |
} | |
if ((def.type && def.type !== 'any' && def.type !== newType) && !_.isNull(newVal) && !_.isUndefined(newVal)) { | |
throw new TypeError('Property \'' + attr + '\' must be of type ' + def.type + '. Tried to set ' + newVal); | |
} | |
if (def.values && !_.contains(def.values, newVal)) { | |
throw new TypeError('Property \'' + attr + '\' must be one of values: ' + def.values.join(', ')); | |
} | |
hasChanged = !isEqual(currentVal, newVal, attr); | |
// enforce `setOnce` for properties if set | |
if (def.setOnce && currentVal !== undefined && hasChanged) { | |
throw new TypeError('Property \'' + key + '\' can only be set once.'); | |
} | |
// keep track of changed attributes | |
// and push to changes array | |
if (hasChanged) { | |
changes.push({prev: currentVal, val: newVal, key: attr}); | |
self._changed[attr] = newVal; | |
} else { | |
delete self._changed[attr]; | |
} | |
} | |
// actually update our values | |
_.each(changes, function (change) { | |
self._previousAttributes[change.key] = change.prev; | |
if (unset) { | |
delete self._values[change.key]; | |
} else { | |
self._values[change.key] = change.val; | |
} | |
}); | |
if (!silent && changes.length) self._pending = true; | |
if (!silent) { | |
_.each(changes, function (change) { | |
self.trigger('change:' + change.key, self, change.val, options); | |
}); | |
} | |
// You might be wondering why there's a `while` loop here. Changes can | |
// be recursively nested within `"change"` events. | |
if (changing) return this; | |
if (!silent) { | |
while (this._pending) { | |
this._pending = false; | |
this.trigger('change', this, options); | |
} | |
} | |
this._pending = false; | |
this._changing = false; | |
return this; | |
}, | |
get: function (attr) { | |
return this[attr]; | |
}, | |
// Toggle boolean properties or properties that have a `values` | |
// array in its definition. | |
toggle: function (property) { | |
var def = this._definition[property]; | |
if (def.type === 'boolean') { | |
// if it's a bool, just flip it | |
this[property] = !this[property]; | |
} else if (def && def.values) { | |
// If it's a property with an array of values | |
// skip to the next one looping back if at end. | |
this[property] = arrayNext(def.values, this[property]); | |
} else { | |
throw new TypeError('Can only toggle properties that are type `boolean` or have `values` array.'); | |
} | |
return this; | |
}, | |
// Get all of the attributes of the model at the time of the previous | |
// `"change"` event. | |
previousAttributes: function () { | |
return _.clone(this._previousAttributes); | |
}, | |
// 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 (attr == null) 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; | |
var old = this._changing ? this._previousAttributes : this.attributes; | |
var def, isEqual; | |
for (var attr in diff) { | |
def = this._definition[attr]; | |
isEqual = this._getCompareForType(def && def.type); | |
if (isEqual(old[attr], (val = diff[attr]))) continue; | |
(changed || (changed = {}))[attr] = val; | |
} | |
return changed; | |
}, | |
toJSON: function () { | |
return this.serialize(); | |
}, | |
unset: function (attr, options) { | |
var def = this._definition[attr]; | |
var type = def.type; | |
var val; | |
if (def.required) { | |
val = _.result(def, 'default'); | |
return this.set(attr, val, options); | |
} else { | |
return this.set(attr, val, _.extend({}, options, {unset: true})); | |
} | |
}, | |
clear: function (options) { | |
var self = this; | |
_.each(this.attributes, function (val, key) { | |
self.unset(key, options); | |
}); | |
return this; | |
}, | |
previous: function (attr) { | |
if (attr == null || !Object.keys(this._previousAttributes).length) return null; | |
return this._previousAttributes[attr]; | |
}, | |
// Get default values for a certain type | |
_getDefaultForType: function (type) { | |
var dataType = this._dataTypes[type]; | |
return dataType && dataType.default; | |
}, | |
// Determine which comparison algorithm to use for comparing a property | |
_getCompareForType: function (type) { | |
var dataType = this._dataTypes[type]; | |
if (dataType && dataType.compare) return _.bind(dataType.compare, this); | |
return _.isEqual; | |
}, | |
// Run validation against the next complete set of model attributes, | |
// returning `true` if all is well. Otherwise, fire an `"invalid"` event. | |
_validate: function (attrs, options) { | |
if (!options.validate || !this.validate) return true; | |
attrs = _.extend({}, this.attributes, attrs); | |
var error = this.validationError = this.validate(attrs, options) || null; | |
if (!error) return true; | |
this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); | |
return false; | |
}, | |
_createPropertyDefinition: function (name, desc, isSession) { | |
return createPropertyDefinition(this, name, desc, isSession); | |
}, | |
// just makes friendlier errors when trying to define a new model | |
// only used when setting up original property definitions | |
_ensureValidType: function (type) { | |
return _.contains(['string', 'number', 'boolean', 'array', 'object', 'date', 'any'].concat(_.keys(this._dataTypes)), type) ? type : undefined; | |
}, | |
getAttributes: function (options, raw) { | |
options || (options = {}); | |
_.defaults(options, { | |
session: false, | |
props: false, | |
derived: false | |
}); | |
var res = {}; | |
var val, item, def; | |
for (item in this._definition) { | |
def = this._definition[item]; | |
if ((options.session && def.session) || (options.props && !def.session)) { | |
val = (raw) ? this._values[item] : this[item]; | |
if (typeof val === 'undefined') val = _.result(def, 'default'); | |
if (typeof val !== 'undefined') res[item] = val; | |
} | |
} | |
if (options.derived) { | |
for (item in this._derived) res[item] = this[item]; | |
} | |
return res; | |
}, | |
_initDerived: function () { | |
var self = this; | |
_.each(this._derived, function (value, name) { | |
var def = self._derived[name]; | |
def.deps = def.depList; | |
var update = function (options) { | |
options = options || {}; | |
var newVal = def.fn.call(self); | |
if (self._cache[name] !== newVal || !def.cache) { | |
if (def.cache) { | |
self._previousAttributes[name] = self._cache[name]; | |
} | |
self._cache[name] = newVal; | |
self.trigger('change:' + name, self, self._cache[name]); | |
} | |
}; | |
def.deps.forEach(function (propString) { | |
self._keyTree.add(propString, update); | |
}); | |
}); | |
this.on('all', function (eventName) { | |
if (changeRE.test(eventName)) { | |
self._keyTree.get(eventName.split(':')[1]).forEach(function (fn) { | |
fn(); | |
}); | |
} | |
}, this); | |
}, | |
_getDerivedProperty: function (name, flushCache) { | |
// is this a derived property that is cached | |
if (this._derived[name].cache) { | |
//set if this is the first time, or flushCache is set | |
if (flushCache || !this._cache.hasOwnProperty(name)) { | |
this._cache[name] = this._derived[name].fn.apply(this); | |
} | |
return this._cache[name]; | |
} else { | |
return this._derived[name].fn.apply(this); | |
} | |
}, | |
_initCollections: function () { | |
var coll; | |
if (!this._collections) return; | |
for (coll in this._collections) { | |
this[coll] = new this._collections[coll]([], {parent: this}); | |
} | |
}, | |
_initChildren: function () { | |
var child; | |
if (!this._children) return; | |
for (child in this._children) { | |
this[child] = new this._children[child]({}, {parent: this}); | |
this.listenTo(this[child], 'all', this._getEventBubblingHandler(child)); | |
} | |
}, | |
// Returns a bound handler for doing event bubbling while | |
// adding a name to the change string. | |
_getEventBubblingHandler: function (propertyName) { | |
return _.bind(function (name, model, newValue) { | |
if (changeRE.test(name)) { | |
this.trigger('change:' + propertyName + '.' + name.split(':')[1], model, newValue); | |
} else if (name === 'change') { | |
this.trigger('change', this); | |
} | |
}, this); | |
}, | |
// Check that all required attributes are present | |
_verifyRequired: function () { | |
var attrs = this.attributes; // should include session | |
for (var def in this._definition) { | |
if (this._definition[def].required && typeof attrs[def] === 'undefined') { | |
return false; | |
} | |
} | |
return true; | |
} | |
}); | |
// getter for attributes | |
Object.defineProperties(Base.prototype, { | |
attributes: { | |
get: function () { | |
return this.getAttributes({props: true, session: true}); | |
} | |
}, | |
all: { | |
get: function () { | |
return this.getAttributes({ | |
session: true, | |
props: true, | |
derived: true | |
}); | |
} | |
}, | |
isState: { | |
value: true | |
} | |
}); | |
// helper for creating/storing property definitions and creating | |
// appropriate getters/setters | |
function createPropertyDefinition(object, name, desc, isSession) { | |
var def = object._definition[name] = {}; | |
var type; | |
if (_.isString(desc)) { | |
// grab our type if all we've got is a string | |
type = object._ensureValidType(desc); | |
if (type) def.type = type; | |
} else { | |
type = object._ensureValidType(desc[0] || desc.type); | |
if (type) def.type = type; | |
if (desc[1] || desc.required) def.required = true; | |
// set default if defined | |
def.default = !_.isUndefined(desc[2]) ? desc[2] : desc.default; | |
if (typeof def.default === 'object') { | |
throw new TypeError('The default value for ' + name + ' cannot be an object/array, must be a value or a function which returns a value/object/array'); | |
} | |
def.allowNull = desc.allowNull ? desc.allowNull : false; | |
if (desc.setOnce) def.setOnce = true; | |
if (def.required && _.isUndefined(def.default)) def.default = object._getDefaultForType(type); | |
def.test = desc.test; | |
def.values = desc.values; | |
} | |
if (isSession) def.session = true; | |
// define a getter/setter on the prototype | |
// but they get/set on the instance | |
Object.defineProperty(object, name, { | |
set: function (val) { | |
this.set(name, val); | |
}, | |
get: function () { | |
var result = this._values[name]; | |
var typeDef = this._dataTypes[def.type]; | |
if (typeof result !== 'undefined') { | |
if (typeDef && typeDef.get) { | |
result = typeDef.get(result); | |
} | |
return result; | |
} | |
return _.result(def, 'default'); | |
} | |
}); | |
return def; | |
} | |
// helper for creating derived property definitions | |
function createDerivedProperty(modelProto, name, definition) { | |
var def = modelProto._derived[name] = { | |
fn: _.isFunction(definition) ? definition : definition.fn, | |
cache: (definition.cache !== false), | |
depList: definition.deps || [] | |
}; | |
// add to our shared dependency list | |
_.each(def.depList, function (dep) { | |
modelProto._deps[dep] = _(modelProto._deps[dep] || []).union([name]); | |
}); | |
// defined a top-level getter for derived names | |
Object.defineProperty(modelProto, name, { | |
get: function () { | |
return this._getDerivedProperty(name); | |
}, | |
set: function () { | |
throw new TypeError('"' + name + '" is a derived property, it can\'t be set directly.'); | |
} | |
}); | |
} | |
var dataTypes = { | |
string: { | |
default: function () { | |
return ''; | |
} | |
}, | |
date: { | |
set: function (newVal) { | |
var newType; | |
if (!_.isDate(newVal)) { | |
try { | |
newVal = new Date(parseInt(newVal, 10)); | |
if (!_.isDate(newVal)) throw TypeError; | |
newVal = newVal.valueOf(); | |
if (_.isNaN(newVal)) throw TypeError; | |
newType = 'date'; | |
} catch (e) { | |
newType = typeof newVal; | |
} | |
} else { | |
newType = 'date'; | |
newVal = newVal.valueOf(); | |
} | |
return { | |
val: newVal, | |
type: newType | |
}; | |
}, | |
get: function (val) { | |
return new Date(val); | |
}, | |
default: function () { | |
return new Date(); | |
} | |
}, | |
array: { | |
set: function (newVal) { | |
return { | |
val: newVal, | |
type: _.isArray(newVal) ? 'array' : typeof newVal | |
}; | |
}, | |
default: function () { | |
return []; | |
} | |
}, | |
object: { | |
set: function (newVal) { | |
var newType = typeof newVal; | |
// we have to have a way of supporting "missing" objects. | |
// Null is an object, but setting a value to undefined | |
// should work too, IMO. We just override it, in that case. | |
if (newType !== 'object' && _.isUndefined(newVal)) { | |
newVal = null; | |
newType = 'object'; | |
} | |
return { | |
val: newVal, | |
type: newType | |
}; | |
}, | |
default: function () { | |
return {}; | |
} | |
}, | |
// the `state` data type is a bit special in that setting it should | |
// also bubble events | |
state: { | |
set: function (newVal) { | |
var isInstance = newVal instanceof Base || (newVal && newVal.isState); | |
if (isInstance) { | |
return { | |
val: newVal, | |
type: 'state' | |
}; | |
} else { | |
return { | |
val: newVal, | |
type: typeof newVal | |
}; | |
} | |
}, | |
compare: function (currentVal, newVal, attributeName) { | |
var isSame = currentVal === newVal; | |
// if this has changed we want to also handle | |
// event propagation | |
if (!isSame) { | |
this.stopListening(currentVal); | |
if (newVal != null) { | |
this.listenTo(newVal, 'all', this._getEventBubblingHandler(attributeName)); | |
} | |
} | |
return isSame; | |
} | |
} | |
}; | |
// the extend method used to extend prototypes, maintain inheritance chains for instanceof | |
// and allow for additions to the model definitions. | |
function extend(protoProps) { | |
var parent = this; | |
var child; | |
var args = [].slice.call(arguments); | |
var prop, item; | |
// 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 () { | |
return parent.apply(this, arguments); | |
}; | |
} | |
// Add static properties to the constructor function from parent | |
_.extend(child, parent); | |
// Set the prototype chain to inherit from `parent`, without calling | |
// `parent`'s constructor function. | |
var Surrogate = function () { this.constructor = child; }; | |
Surrogate.prototype = parent.prototype; | |
child.prototype = new Surrogate(); | |
// set prototype level objects | |
child.prototype._derived = _.extend({}, parent.prototype._derived); | |
child.prototype._deps = _.extend({}, parent.prototype._deps); | |
child.prototype._definition = _.extend({}, parent.prototype._definition); | |
child.prototype._collections = _.extend({}, parent.prototype._collections); | |
child.prototype._children = _.extend({}, parent.prototype._children); | |
child.prototype._dataTypes = _.extend({}, parent.prototype._dataTypes || dataTypes); | |
// Mix in all prototype properties to the subclass if supplied. | |
if (protoProps) { | |
args.forEach(function processArg(def) { | |
if (def.dataTypes) { | |
_.each(def.dataTypes, function (def, name) { | |
child.prototype._dataTypes[name] = def; | |
}); | |
delete def.dataTypes; | |
} | |
if (def.props) { | |
_.each(def.props, function (def, name) { | |
createPropertyDefinition(child.prototype, name, def); | |
}); | |
delete def.props; | |
} | |
if (def.session) { | |
_.each(def.session, function (def, name) { | |
createPropertyDefinition(child.prototype, name, def, true); | |
}); | |
delete def.session; | |
} | |
if (def.derived) { | |
_.each(def.derived, function (def, name) { | |
createDerivedProperty(child.prototype, name, def); | |
}); | |
delete def.derived; | |
} | |
if (def.collections) { | |
_.each(def.collections, function (constructor, name) { | |
child.prototype._collections[name] = constructor; | |
}); | |
delete def.collections; | |
} | |
if (def.children) { | |
_.each(def.children, function (constructor, name) { | |
child.prototype._children[name] = constructor; | |
}); | |
delete def.children; | |
} | |
_.extend(child.prototype, def); | |
}); | |
} | |
var toString = Object.prototype.toString; | |
// Set a convenience property in case the parent's prototype is needed | |
// later. | |
child.__super__ = parent.prototype; | |
return child; | |
} | |
Base.extend = extend; | |
return Base; | |
})); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(root, factory) { | |
if (typeof exports !== 'undefined') { | |
factory(root, exports); | |
} else { | |
root.HJ_arrayNext = factory(root, {}); | |
} | |
}(this, function(root, HJ_arrayNext) { | |
HJ_arrayNext = function arrayNext(array, currentItem) { | |
var len = array.length; | |
var newIndex = array.indexOf(currentItem) + 1; | |
if (newIndex > (len - 1)) newIndex = 0; | |
return array[newIndex]; | |
}; | |
return HJ_arrayNext; | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(root, factory) { | |
if (typeof exports !== 'undefined') { | |
factory(root, exports); | |
} else { | |
root.HJ_KeyTreeStore = factory(root, {}); | |
} | |
}(this, function(root, HJ_KeyTreeStore) { | |
HJ_KeyTreeStore = function() { | |
this.storage = {}; | |
}; | |
// add an object to the store | |
HJ_KeyTreeStore.prototype.add = function (keypath, obj) { | |
var arr = this.storage[keypath] || (this.storage[keypath] = []); | |
arr.push(obj); | |
}; | |
// remove an object | |
HJ_KeyTreeStore.prototype.remove = function (obj) { | |
var path, arr; | |
for (path in this.storage) { | |
arr = this.storage[path]; | |
arr.some(function (item, index) { | |
if (item === obj) { | |
arr.splice(index, 1); | |
return true; | |
} | |
}); | |
} | |
}; | |
// get array of all all relevant functions, without keys | |
HJ_KeyTreeStore.prototype.get = function (keypath) { | |
var res = []; | |
var key; | |
for (key in this.storage) { | |
if (!keypath || keypath === key || key.indexOf(keypath + '.') === 0) { | |
res = res.concat(this.storage[key]); | |
} | |
} | |
return res; | |
}; | |
// get all results that match keypath but still grouped by key | |
HJ_KeyTreeStore.prototype.getGrouped = function (keypath) { | |
var res = {}; | |
var key; | |
for (key in this.storage) { | |
if (!keypath || keypath === key || key.indexOf(keypath + '.') === 0) { | |
res[key] = slice.call(this.storage[key]); | |
} | |
} | |
return res; | |
}; | |
// get all results that match keypath but still grouped by key | |
HJ_KeyTreeStore.prototype.getAll = function (keypath) { | |
var res = {}; | |
var key; | |
for (key in this.storage) { | |
if (keypath === key || key.indexOf(keypath + '.') === 0) { | |
res[key] = slice.call(this.storage[key]); | |
} | |
} | |
return res; | |
}; | |
// run all matches with optional context | |
HJ_KeyTreeStore.prototype.run = function (keypath, context) { | |
var args = slice.call(arguments, 2); | |
this.get(keypath).forEach(function (fn) { | |
fn.apply(context || this, args); | |
}); | |
}; | |
return HJ_KeyTreeStore; | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment