ampersand-state
Advanced tools
Comparing version 0.3.2 to 0.4.0
@@ -1,9 +0,4 @@ | ||
/* jshint expr: true, eqnull: true */ | ||
// (c) 2013 Henrik Joreteg | ||
// MIT Licensed | ||
// For all details and documentation: | ||
// https://github.com/HenrikJoreteg/human-model | ||
// | ||
var _ = require('underscore'); | ||
var BBEvents = require('backbone-events-standalone'); | ||
var arrayNext = require('array-next'); | ||
@@ -18,3 +13,2 @@ | ||
if (options.parse) attrs = this.parse(attrs, options); | ||
options._attrs = attrs; | ||
this._initted = false; | ||
@@ -34,18 +28,4 @@ this._values = {}; | ||
var accessors = { | ||
attributes: { | ||
get: function () { | ||
return this._getAttributes(true); | ||
} | ||
}, | ||
derived: { | ||
get: function () { | ||
var res = {}; | ||
for (var item in this._derived) res[item] = this._derived[item].fn.apply(this); | ||
return res; | ||
} | ||
} | ||
}; | ||
var prototypeMixins = { | ||
_.extend(Base.prototype, BBEvents, { | ||
idAttribute: 'id', | ||
@@ -290,13 +270,2 @@ | ||
// Returns `true` if the attribute contains a value that is not null | ||
// or undefined. | ||
has: function (attr) { | ||
return this.get(attr) != null; | ||
}, | ||
// return copy of model | ||
clone: function () { | ||
return new this.constructor(this._getAttributes(true)); | ||
}, | ||
unset: function (attr, options) { | ||
@@ -354,42 +323,3 @@ var def = this._definition[attr]; | ||
_createPropertyDefinition: function (name, desc, isSession) { | ||
var self = this; | ||
var def = this._definition[name] = {}; | ||
var type; | ||
if (_.isString(desc)) { | ||
// grab our type if all we've got is a string | ||
type = this._ensureValidType(desc); | ||
if (type) def.type = type; | ||
} else { | ||
type = this._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; | ||
def.allowNull = desc.allowNull ? desc.allowNull : false; | ||
if (desc.setOnce) def.setOnce = true; | ||
if (def.required && _.isUndefined(def.default)) def.default = this._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(self, name, { | ||
set: function (val) { | ||
this.set(name, val); | ||
}, | ||
get: function () { | ||
var result = this._values[name]; | ||
if (typeof result !== 'undefined') { | ||
if (dataTypes[def.type] && dataTypes[def.type].get) { | ||
result = dataTypes[def.type].get(result); | ||
} | ||
return result; | ||
} | ||
return def.default; | ||
} | ||
}); | ||
return def; | ||
return createPropertyDefinition(this, name, desc, isSession); | ||
}, | ||
@@ -449,18 +379,156 @@ | ||
} | ||
}); | ||
// getter for attributes | ||
Object.defineProperties(Base.prototype, { | ||
attributes: { | ||
get: function () { | ||
return this._getAttributes(true); | ||
} | ||
}, | ||
derived: { | ||
get: function () { | ||
var res = {}; | ||
for (var item in this._derived) res[item] = this._derived[item].fn.apply(this); | ||
return res; | ||
} | ||
} | ||
}); | ||
// helper for creating/storing property definitions and creating | ||
// appropriate getters/setters | ||
var createPropertyDefinition = function (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; | ||
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]; | ||
if (typeof result !== 'undefined') { | ||
if (dataTypes[def.type] && dataTypes[def.type].get) { | ||
result = dataTypes[def.type].get(result); | ||
} | ||
return result; | ||
} | ||
return def.default; | ||
} | ||
}); | ||
return def; | ||
}; | ||
// Underscore methods we want to add | ||
_.each(['keys', 'values', 'pairs', 'invert', 'pick', 'omit'], function (method) { | ||
prototypeMixins[method] = function () { | ||
var args = Array.prototype.slice.call(arguments); | ||
args.unshift(this.attributes); | ||
return _[method].apply(_, args); | ||
// helper for creating derived property definitions | ||
var createDerivedProperty = function (modelProto, name, definition) { | ||
var def = modelProto._derived[name] = { | ||
fn: _.isFunction(definition) ? definition : definition.fn, | ||
cache: (definition.cache !== false), | ||
depList: definition.deps || [] | ||
}; | ||
}); | ||
// add event methods | ||
BBEvents.mixin(prototypeMixins); | ||
// add to our shared dependency list | ||
_.each(def.depList, function (dep) { | ||
modelProto._deps[dep] = _(modelProto._deps[dep] || []).union([name]); | ||
}); | ||
// our dataTypes | ||
var dataTypes = { | ||
// 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.'); | ||
} | ||
}); | ||
}; | ||
// the extend method used to extend prototypes, maintain inheritance chains for instanceof | ||
// and allow for additions to the model definitions. | ||
var extend = function (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); | ||
// Mix in all prototype properties to the subclass if supplied. | ||
if (protoProps) { | ||
args.forEach(function processArg(def) { | ||
_.each(def, function (value, key) { | ||
if (key === 'props' || key === 'session') { | ||
_.each(value, function (def, name) { | ||
createPropertyDefinition(child.prototype, name, def, key === 'session'); | ||
}); | ||
} else if (key === 'derived') { | ||
_.each(value, function (def, name) { | ||
createDerivedProperty(child.prototype, name, def); | ||
}); | ||
} else if (key === 'collections') { | ||
_.each(value, function (constructor, name) { | ||
child.prototype._collections[name] = constructor; | ||
}); | ||
} | ||
}); | ||
delete def.props; | ||
delete def.session; | ||
delete def.derived; | ||
delete def.collections; | ||
_.extend(child.prototype, def); | ||
}); | ||
} | ||
// Set a convenience property in case the parent's prototype is needed | ||
// later. | ||
child.__super__ = parent.prototype; | ||
return child; | ||
}; | ||
// also expose data types in our export | ||
var dataTypes = Base.dataTypes = { | ||
date: { | ||
@@ -514,114 +582,2 @@ set: function (newVal) { | ||
}; | ||
var arrayNext = function (array, currentItem) { | ||
var len = array.length; | ||
var newIndex = array.indexOf(currentItem) + 1; | ||
if (newIndex > (len - 1)) newIndex = 0; | ||
return array[newIndex]; | ||
}; | ||
var createDerivedProperty = function (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 extend = function (spec) { | ||
spec = spec || {}; | ||
var parent = this; | ||
var BaseClass = this._super || Base; | ||
var props, session, derived, collections; | ||
function State() { | ||
BaseClass.apply(this, arguments); | ||
} | ||
// Add our special accessor properties | ||
Object.defineProperties(State.prototype, accessors); | ||
// Mix in our methods | ||
_.extend(State.prototype, prototypeMixins, { | ||
// storage for our rules about derived properties | ||
_derived: {}, | ||
_deps: {}, | ||
_definition: {} | ||
}); | ||
// Copy any methods from existing prototype | ||
_.each(BaseClass.prototype, function (value, key) { | ||
if (value instanceof Function) { | ||
State.prototype[key] = value; | ||
} | ||
}); | ||
// Pull out previous model spec | ||
if (this._spec) { | ||
props = this._spec.props; | ||
session = this._spec.session; | ||
derived = this._spec.derived; | ||
collections = this._spec.collections; | ||
} | ||
// Extend previous with new special attributes | ||
State._spec = { | ||
props: _.extend({}, props, spec.props), | ||
session: _.extend({}, session, spec.session), | ||
derived: _.extend({}, derived, spec.derived), | ||
collections: _.extend({}, collections, spec.collections) | ||
}; | ||
// remove handled references before we loop | ||
delete spec.props; | ||
delete spec.session; | ||
delete spec.derived; | ||
delete spec.collections; | ||
// Extend spec with any new proto methods we may have just added | ||
_.extend(State._spec, spec); | ||
_.each(State._spec, function (value, key) { | ||
if (key === 'props' || key === 'session') { | ||
_.each(value, function (def, name) { | ||
State.prototype._createPropertyDefinition.call(State.prototype, name, def, key === 'session'); | ||
}); | ||
} else if (key === 'derived') { | ||
_.each(value, function (def, name) { | ||
createDerivedProperty(State.prototype, name, def); | ||
}); | ||
} else if (key === 'collections') { | ||
State.prototype._collections = value; | ||
} else { | ||
State.prototype[key] = value; | ||
} | ||
}); | ||
// Keep reference to super human™ | ||
State._super = State; | ||
// Maintain ability to further extend | ||
State.extend = extend; | ||
State.dataTypes = dataTypes; | ||
return State; | ||
}; | ||
// also expose data types in our export | ||
Base.dataTypes = dataTypes; | ||
Base.extend = extend; | ||
@@ -628,0 +584,0 @@ |
{ | ||
"name": "ampersand-state", | ||
"description": "An observable, extensible state object with derived watchable properties.", | ||
"version": "0.3.2", | ||
"version": "0.4.0", | ||
"author": "Henrik Joreteg <henrik@andyet.net>", | ||
@@ -11,6 +11,10 @@ "bugs": { | ||
"backbone-events-standalone": "~0.2.1", | ||
"underscore": "~1.6.0" | ||
"underscore": "~1.6.0", | ||
"array-next": "~0.0.1" | ||
}, | ||
"devDependencies": { | ||
"tape": "~2.11.0" | ||
"ampersand-registry": "0.x.x", | ||
"run-browser": "~1.2.0", | ||
"tape": "~2.11.0", | ||
"precommit-hook": "~0.3.10" | ||
}, | ||
@@ -30,3 +34,5 @@ "homepage": "https://github.com/ampersandjs/ampersand-state", | ||
"scripts": { | ||
"test": "node test/main.js" | ||
"test": "node test/index.js", | ||
"validate": "jshint .", | ||
"start": "run-browser test/index.js" | ||
}, | ||
@@ -33,0 +39,0 @@ "testling": { |
@@ -261,4 +261,4 @@ # ampersand-state | ||
// *BUT* it doesn't maintain the prototype chain | ||
// so instanceof checks will fail up the chain | ||
// it also maintains the prototype chain | ||
// so instanceof checks will work up the chain | ||
@@ -268,4 +268,4 @@ // so this is true | ||
// but this is false | ||
awesome instanceof Person; // false | ||
// and so is this | ||
awesome instanceof Person; // true | ||
@@ -276,2 +276,3 @@ ``` | ||
- 0.1.0 - lots of cleanup, grabbing tests from human-model, now maintains the prototype chain so `instanceof` checks pass no matter how many times it's been extended. | ||
- 0.0.2 - improved doc | ||
@@ -278,0 +279,0 @@ - 0.0.1 - initial publish |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
53004
9
1260
287
3
4
1
+ Addedarray-next@~0.0.1
+ Addedarray-next@0.0.1(transitive)