6bd3073f by Adam Heath

First pass of monkey-patching nested model support. No tests yet, but

it passes jshint and build targets.
1 parent 40915018
{
"directory": "src/lib"
}
.*.swp
.tmp/
bin/coverage/
dist/
node_modules/
src/lib/
.grunt/
_SpecRunner.html
This diff is collapsed. Click to expand it.
{
"name": "backbone-nested-models",
"version": "0.0.0",
"authors": [
"Adam Heath <doogie@brainfood.com>"
],
"private": true,
"ignore": [
"**/.*",
"node_modules",
"src/lib",
"test"
],
"dependencies": {
"underscore": "~1.6.0",
"backbone": "~1.1.0",
"backbone-validation": "0.9.1",
"requirejs": "~2.1.10"
}
}
{
"name": "backbone-nested-models",
"version": "0.0.0",
"main": [
"src/scripts/backbone-nested-models.js"
],
"dependencies": {
"backbone": "~1.1.0",
"backbone-validation": "0.9.1",
"requirejs": "~2.1.10"
},
"devDependencies": {
"bower-requirejs": "~0.9.2",
"grunt": "~0.4.1",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-uglify": "~0.2.0",
"grunt-contrib-jshint": "~0.7.0",
"grunt-contrib-cssmin": "~0.7.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-htmlmin": "~0.1.3",
"grunt-bower-install": "~0.7.0",
"grunt-contrib-imagemin": "~0.2.0",
"grunt-contrib-watch": "~0.5.2",
"grunt-rev": "~0.1.0",
"grunt-autoprefixer": "~0.5.0",
"grunt-usemin": "~0.1.10",
"grunt-mocha": "~0.4.0",
"grunt-newer": "~0.6.0",
"grunt-svgmin": "~0.2.0",
"grunt-concurrent": "~0.4.0",
"load-grunt-tasks": "~0.2.0",
"time-grunt": "~0.2.0",
"jshint-stylish": "~0.1.3",
"grunt-contrib-requirejs": "~0.4.0",
"grunt-bower-requirejs": "~0.8.4",
"grunt-template-jasmine-istanbul": "~0.2.6",
"grunt-template-jasmine-requirejs": "~0.1.10",
"grunt-contrib-jasmine": "~0.5.3"
},
"engines": {
"node": ">=0.8.0"
}
}
define(
[
'underscore',
'backbone',
'backbone-validation',
],
function(
_,
Backbone
) {
'use strict';
function validateNestedValue(attrValue, attrName) {
attrValue.validate();
var isValid = attrValue.isValid();
return isValid ? null : (attrName + ' is invalid');
}
function updateValidation(model) {
var oldValidation = model.validation;
var allKeys = _.uniq(model.keys().concat(_.keys(oldValidation)));
var validation = _.extend({}, oldValidation);
var found;
var f = function(value) {
if (value === validateNestedValue) {
found = true;
}
};
for (var i = 0; i < allKeys.length; i++) {
var key = allKeys[i];
var value = model.get(key);
var validators = validation[key];
if (validators) {
if(_.isArray(validators)) {
validators = validators.concat();
} else {
validators = [validators];
}
} else {
validators = [];
}
validation[key] = validators;
if (value instanceof Backbone.Model) {
found = false;
_.each(validators, f);
if (!found) {
validators.push(validateNestedValue);
}
}
}
model.validation = validation;
return oldValidation;
}
function wrapValidationFunction(modelClass, methodName) {
var originalMethod = modelClass.prototype[methodName];
modelClass.prototype[methodName] = function() {
var oldValidation = updateValidation(this);
try {
if (originalMethod) {
return originalMethod.apply(this, arguments);
} else {
return modelClass.__super__[methodName].apply(this, arguments);
}
} finally {
this.validation = oldValidation;
}
};
}
function wrapSetFunction(modelClass) {
var originalMethod = modelClass.prototype.set;
modelClass.prototype.set = function(key, val, options) {
var attr, attrs, curVal, nestedOptions, newVal;
if (key === null) {
return this;
}
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if (options && options.merge) {
nestedOptions = {silent: false, merge: true};
for (attr in attrs) {
curVal = this.get(attr);
newVal = attrs[attr];
if (curVal instanceof Backbone.Model && newVal instanceof Backbone.Model) {
delete attrs[attr];
curVal.set(newVal.attributes, nestedOptions);
}
}
}
if (originalMethod) {
return originalMethod.call(this, attrs, options);
} else {
return modelClass.__super__.set.call(this, attrs, options);
}
};
}
function wrapToJSONFunction(modelClass) {
var originalMethod = modelClass.prototype.toJSON;
modelClass.prototype.toJSON = function(options) {
var result;
if (originalMethod) {
result = originalMethod.apply(this, arguments);
} else {
result = modelClass.__super__.toJSON.apply(this, arguments);
}
if (options && options.deep) {
_.each(result, function(value, key) {
if (value instanceof Backbone.Model) {
result[key] = value.toJSON(options);
}
});
}
return result;
};
}
var NestedModels = {
validateNestedValue: validateNestedValue,
wrapSetFunction: wrapSetFunction,
wrapToJSONFunction: wrapToJSONFunction,
wrapValidationFunction: wrapValidationFunction,
mixin: function(modelClass) {
wrapSetFunction(modelClass);
wrapToJSONFunction(modelClass);
wrapValidationFunction(modelClass, 'isValid');
wrapValidationFunction(modelClass, 'validate');
wrapValidationFunction(modelClass, 'preValidate');
return modelClass;
},
};
return NestedModels;
}
);
/* global require:true */
var require;
require = (function() {
'use strict';
var require = {
baseUrl: 'scripts',
shim: {
},
paths: {
'backbone-validation': '../lib/backbone-validation/dist/backbone-validation-amd',
backbone: '../lib/backbone/backbone',
underscore: '../lib/underscore/underscore'
}
};
return require;
})();
define([], {});
/* global require */
require(
[],
function() {
'use strict';
}
);