How can I apply attribute types in the Backbone model?
I want to have a Backbone model with float attributes, but not worry too much about variable types.
I would like to encapsulate the parsing right in the model, so I am thinking about overriding the function set
:
var Place = Backbone.Model.extend({
set: function(attributes, options) {
if (!_.isEmpty(attributes.latitude)){
attributes.latitude == parseFloat(attributes.latitude);
}
if (!_.isEmpty(attributes.longitude)){
attributes.longitude == parseFloat(attributes.longitude);
}
Backbone.Model.prototype.set.call(this, attributes, options);
}
});
However, this seems cumbersome as I would have similar logic in the validator method and possibly repeated for multiple models. I don't think View should care about these conversions.
So what's the best way to do it?
source to share
Use an authentication plugin for your model so you can validate input in general.
There are several of them, including the one I wrote:
Then you don't have to worry about doing data validation elsewhere - your model does it and sends a message and a error
message that you can listen to and provide appropriate feedback.
In addition, the lat / lng pair can on rare occasions be an integer such as Greenwich England: 0,0 or North Pole: 90 180. And since JavaScript only has a "number", any valid input for parseFloat is also valid for parseInt.
But parseFloat will always return float.
source to share
My solution was to replace Backbone.Model.prototype.set
with a preprocessor proxy:
/**
* Intercept calls to Backbone.Model.set and preprocess attribute values.
*
* If the model has a <code>preprocess</code> property, that property will be
* used for mapping attribute names to preprocessor functions. This is useful
* for automatically converting strings to numbers, for instance.
*
* @param Backbone
* the global Backbone object.
*/
(function(Backbone) {
var originalSet = Backbone.Model.prototype.set;
_.extend(Backbone.Model.prototype, {
set: function(key, val, options) {
if(!this.preprocess) {
return originalSet.apply(this, arguments);
}
// If-else copied from Backbone source
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
for(attr in this.preprocess) {
if(_.has(attrs, attr)) {
attrs[attr] = this.preprocess[attr](attrs[attr]);
}
}
return originalSet.call(this, attrs, options);
},
});
})(Backbone);
The models with the property preprocess
will then use it to map attribute names to preprocessor functions. For example, preprocess: { age: parseInt }
means that whenever an attribute is set age
, the value will be passed through parseInt
before setting it. Attributes without a matching entry preprocess
will not be affected.
Usage example:
var Thing = Backbone.Model.extend({
preprocess: {
mass: parseInt,
created: function(s) { return new Date(s); },
},
});
var t = new Thing({
label: '42',
mass: '42',
created: '1971-02-03T12:13:14+02:00',
});
console.log(t.get('label')+3); // 423
console.log(t.get('mass')+3); // 45
console.log(t.get('created').toLocaleString('ja-JP', { weekday: 'short' })); // ζ°΄
Pros
- Functionality is available in all models without code duplication
- Don't need to send
{ validate: true }
in every callset
- No need to duplicate preprocessing in
validate
, as this happens before it gets calledvalidate
(it could also be con, se below)
Against
- Some duplication of baseline code
- Powerful validation because preprocessing occurs before the call
validate
. JavaScript analysis methods typically return invalid values, instead of exceptions exceptions, though (ieparseInt('foo')
returnsNaN
), so you have to find it.
source to share