If i want to add a custom property to my backbone model, is this the best was to do this? Is there a better way or a completely different approach to the functionality i want to achieve?
var myModel = Backbone.Model.extend({
defaults:{
monthly_amount: 100
},
initialize: function(model, options){
var m = this;
Object.defineProperty(this,"yearly_amount",{
get: function() {
return (m.get("monthly_amount") * 12);
},
set: function(value) {
m.set("monthly_amount", (value/12) );
}
});
}
});
Thanks!
Edit: The property is just "virtual", i do not want it to be within the model attributes when saving the model to the server.
So the general problem here is often referred to as "computed properties" and there are plugins for backbone that provide this (see below). Backbone uses get/set method calling style as opposed to defineProperty style so your approach would make the value computation not transparent to views and thus be a pretty strong departure from backbone's design. A plugin that maintains the proper get/set and change interfaces is going to maintain the basic Model API so the view doesn't have to treat this particular model attribute differently.
See also the backbone wiki of plugins.
Available plugins:
Backbone.ModelMorph
Backbone.ComputedModel
Backbone.Spark
Backbone.ComputedFields
When you set your property to defaults oject, it goes to attributes, but it doesn't suit us.
Since Backbone model is an regular object, you can access to it's properties as to objects properties:
var myModel = Backbone.Model.extend({}),
model = new myModel();
model.monthly_amount = 50;
console.log(model.monthly_amount)
or create setter and getter like this:
var myModel = Backbone.Model.extend({
setMonthlyAmount: function (value) {
this.monthly_amount = value;
},
getMonthlyAmount: function () {
return this.monthly_amount;
}
});
//access with getter/setter
var model = new myModel();
model.setMonthlyAmount(20);
var result = model.getMonthlyAmount();
console.log(result);
Working example on jsfiddle
Related
I think I am missing something very trivial here. I have created a Backbone View as follows(without extending Backbone.View):
var PlayersView = new Backbone.View({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
But it doesn't log anything when I run this code. It doesn't work when I explicitly do: PlayersView.render(); as well.
But the following code works :
var PlayersView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
var playersview = new PlayersView();
The View constructor does not accept properties to add to the constructed object. It accepts a few special options like 'model', 'tagName', and so on. But the initialize(...) and render(...) properties in your first code snippet are effectively ignored.
The proper way to provide initialize, render, is to use Backbone.View.extend({...}).
From http://backbonejs.org/#View-extend
extend
Backbone.View.extend(properties, [classProperties]) Get started with
views by creating a custom view class. You'll want to override the
render function, specify your declarative events, and perhaps the
tagName, className, or id of the View's root element.
In other words, your first view's render method was not overridden by your custom render/initialize function.
When using extend you actually let Backbone understand you wish to use your own methods instead of the "default" ones.
What is the preferred way, if any, to instantiate a Backbone model from an existing model, assuming that the model to be instantiated is of a derived type of the existing model?
The case I have in mind arises when dealing with nested models. For instance, let's say I am using DeepModel and I define a function on my "parent" model that returns this.get("childModel"). Now the child model is likely of type Backbone.Model but I would like it to be of type ChildModel which extends Backbone.Model. I have been doing this by literally copying over the interesting attributes one at a time. Surely there must be a better way...
You can create new instance of the same model by using Backbone.Model#clone() method or just using new model.constructor().
var ChildModel = Backbone.Model.extend({
...
});
var child = new ChildModel({ key: "value" });
var new_child = child.clone();
If we see the source of clone method:
clone: function() {
return new this.constructor(this.attributes);
},
we can use the same approach to create new instance but with our data
var new_child = new child.constructor({ new_key: "new_value" });
I've been hoping to use inheritance in Meteor, but I couldn't find anything about it in the documentation or on Stack Overflow.
Is it possible to have templates inheriting properties and methods from another abstract template, or class?
I think the short answer is no, but here's a longer answer:
One thing I've done to share functionality among templates is to define an object of helpers, and then assign it to multiple templates, like so:
var helpers = {
displayName: function() {
return Meteor.user().profile.name;
},
};
Template.header.helpers(helpers);
Template.content.helpers(helpers);
var events = {
'click #me': function(event, template) {
// handle event
},
'click #you': function(event, template) {
// handle event
},
};
Template.header.events(events);
Template.content.events(events);
It's not inheritance, exactly, but it does enable you to share functionality between templates.
If you want all templates to have access to a helper, you can define a global helper like so (see https://github.com/meteor/meteor/wiki/Handlebars):
Handlebars.registerHelper('displayName',function(){return Meteor.user().profile.name;});
I've answered this question here. While the solution doesn't use inheritance, it allow you to share events and helpers across templates with ease.
In a nutshell, I define an extendTemplate function which takes in a template and an object with helpers and events as arguments:
extendTemplate = (template, mixin) ->
helpers = ({name, method} for name, method of mixin when name isnt "events")
template[obj.name] = obj.method for obj in helpers
if mixin.events?
template.events?.call(template, mixin.events)
template
For more details and an example see my other answer.
Recently, I needed the same functionality in my app so I've decided to create my own package that will do that job out of the box. Although it's still work in progress, you can give it a go.
Basically, the entire method is as follows:
// Defines new method /extend
Template.prototype.copyAs = function (newTemplateName) {
var self = this;
// Creating new mirror template
// Copying old template render method to keep its template
var newTemplate = Template.__define__(newTemplateName, self.__render);
newTemplate.__initView = self.__initView;
// Copying helpers
for (var h in self) {
if (self.hasOwnProperty(h) && (h.slice(0, 2) !== "__")) {
newTemplate[h] = self[h];
}
}
// Copying events
newTemplate.__eventMaps = self.__eventMaps;
// Assignment
Template[newTemplateName] = newTemplate;
};
In your new template (new_template.js) in which you want to extend your abstract one, write following:
// this copies your abstract template to your new one
Template.<your_abstract_template_name>.copyAs('<your_new_template_name>');
Now, you can simply either overwrite your helpers or events (in my case it's photos helper), by doing following:
Template.<your_new_template_name>.photos = function () {
return [];
};
Your will refer to overwritten helper methods and to abstract ones that are not overwritten.
Note that HTML file for new template is not necessary as we refer to abstract one all the time.
Source code is available on Github here!
If I have a collection of objects, but also want to store some higher-level information about those objects, is it appropriate to add some model behavior to the collection?
In my situation, I'm looking for a collection of application paths to have a bool field called 'curPath'. If it's changed, the collection should set a flag that indicates the current page. This way outside observers only have to observe one field, not every model in the path collection.
Here's what that might look like:
var PathModel = Backbone.Model.extend({})
var PathCollection = Backbone.Collection.extend({
initialize: function(){ this.model = PathModel }
})
// I want to be able to set observable properties on the collection, so...
var PathManager = _.extend(Backbone.Model, PathCollection)
// Then maybe I can do something like this?
PathManager.each(function(pathModel){
pathModel.on('change:curPath', function(m, value, o){
// I mean for 'this'.set to point to the instance of the PathManager
if (value === true){ this.set('curPath', pathModel.get('id')) }
}, this)
}, this)
Is it appropriate to add observable behavior to a collection (collection+model > model), or do I need to add a wrapping model to the whole thing (model > collection > model), or is there some other solution?
As some of the methods on Model and Collection have the same names that will cause conflicts and possible problems with updating to future versions of Backbone could occur I would advise against this pattern. Rather then doing that create a PathManager class and have PathCollection be initialized and set it as paths property on this model. If from what you are saying
var PathManager = Backbone.Model.extend({
initialize: function() {
this.paths = new PathCollection();
}
});
var pathManager = new PathManager();
pathManager.paths.add({ /* new path */ });
pathManager.on('change', doSomething);
pathManager.paths.on('add', doSomethingElse);
I'd do it something like this
I want to overwrite the default Backbone Model variable (Backbone.Model) to use my own custom validation methods (isValid, validate) and to add some properties.
_.extend(Backbone.Model, {
isValid: function() { // custom function },
validate: function() { // custom logic }
});
var myModel = Backbone.Model.extend({
// this adds for example properties to my modified Backbone model.
});
Unfortunatly this doesn't work... when I load the "wrapper, extending"-module with requirejs and create a new Model instance and than call validate. It says that it doesn't know any validate function...
you have to extend Backbone.Model.prototype rather then Backbone.Model itself as all the methods are prototype methods of the constructor function rather then properties on the model. Though it might be better idea to create a custom BaseModel that will extend the Backbone.Model and implement your custom logic so that if backbone gets updated etc. you will avoid possible conflicts even if in this case they are rather unlikely it's still considered a better practice to extend base Backbone classes rather then to modify them.
I prefer to do custom Models for the project instead of "overwrite" that is not OOP. (at the end of the way could be the same but if we try to emulate OOP I feel that is more easy to understand).
Check my example, I define YourAbstractModel that it is the kind of extension of the Backbone.Model, then all of my Models extend that abstract Model instead of the Backbone Model.
var YourProject = {};
YourProject.YourAbstractModel = Backbone.Model.extend({
customProperty1 : null,
customProperty2 : null,
isValid : function(){ },
specificUtility : function(){ }
});
var YourModel1 = YourProject.YourAbstractModel.extend({
customProperty1 : 'aaaaa'
});
var YourModel2 = YourProject.YourAbstractModel.extend({
customProperty1 : 'bbbbb'
});
console.log( new YourModel1().customProperty1 );
console.log( new YourModel2().customProperty1 );
My way is also flexible to have more than one abstract Model, does not conflict with updates of Backbone and I feel is more close to inheritable process.
You can use the Backbone.Validation plugin by thedersen
https://github.com/thedersen/backbone.validation
Its pretty helpful and easy to use. You can also bind your view to the model, so that your view has your custom-error attribute for the specific model property which failed validation.