How to manually trigger a `change` event on a Backbone model? - backbone.js

I am trying to manually trigger a change event on a Backbone model that belongs to a collection. Here is a minimal example: (JSFiddle)
var PersonModel = Backbone.Model.extend({});
var PersonCollection = Backbone.Collection.extend({
model: PersonModel
});
var person = new PersonModel({ name: 'Spike' });
var people = new PersonCollection([person]);
person.trigger('change'); // throws error
console.log('triggered event successfully');
But I get the following error when I trigger the event: (In JSFiddle, open the Javascipt Console to see the full error)
Uncaught TypeError: Cannot read property 'previousAttributes' of undefined
How can I fix this?
(The reason I'm doing this is that in the actual code the model has a collection -- for example person.things = new ThingCollection(); -- and I want to manually trigger the change event on person when its collection changes)

The change event is expected to have parameters, as described in the event catalog.
The event should have (model, options) as parameters, where model is the model itself and options is the options object used with set. I can't recall off the top of my head if options is optional.
In your case, since you're just triggering the event manually you'll have to provide those arguments. The error your seeing is from some other code trying to access model.previousAttributes with model being undefined.
TL;DR:
person.trigger('change', person, {});
And you might be able to omit the {}.

Related

Backbone Model is a function?

I have the following function, which fails when called:
getAll: function() {
return _todos.toJSON();
}
_todos.toJSON is not a function it tells me.
Printing _todos gives me a function for some reason function child().
Why is that the case?
Here's my Backbone.Model:
var _todos = Backbone.Model.extend();
The getAll() function is sitting in my Collection:
var TodoCollection = Backbone.Collection.extend({
model: _todos,
getAll: ...
});
Edit:
I'm actually connecting Backbone with React, so that might change how I do this.
In addition to getAll I have this:
areAllComplete: function() {
return _.every(this.pluck('complete'), true);
}
An example I've been following seems to put getAll and areAllComplete in the Model and doesn't use Collection at all. I couldn't make sense of it, and since I want this to be restful down the road, I added the Collection and moved getAll and other one inside of it.
Perhaps, this is not what I want.
Edit 2:
According to a warning, the output of getAll is expected to be an Object not an array. I should probably add those function to Model not Collection. Need to think about this more.
I think this turned into another question...
The collection model property is used to specify what model class the collection contains and is used to create the proper models when you pass the raw data to your collection. Additionally based on your code if it did work you would have had a collection with just one model.
Aside from that in order to get the JSON of all the models in your collection you can call it's toJSON method
for example todoCollection.toJSON();
Or if you specifically want it in a getAll function (maybe you want to do something else before returning the data) you can do the following
var TodoCollection = Backbone.Collection.extend({
model: Todo,
getAll: function () {
//do something
return this.toJSON();
}
});
//here we are passing in the data directly, but you might get it with a fetch call
var todoStore = new TodoCollection(models);
var todosJson = todoStore.getAll();
Another thing to note is the backbone naming convention is to use PascalCase for classes and camelCase for instances .
That's because Backbone.Model.extend returns a constructor function. When you pass it to a collection via the model property you're just letting the collection know which kind of models it should hold.
To get the JSON for an entire collection, call toJSON() on the collection instance.
var collection = new TodosCollection();
// add models
collection.toJSON();
If you want JSON for one specific model then get a reference to it via the collection API (at, findWhere, get etc) and call toJSON() on that reference.
var model = collection.at(0);
model.toJSON();

Using a literal object as a Backbone.Model

I need to send a literal object to a view. Is there any better alternative to the following code:
var model= (new (Backbone.Model)).set({'foo':1,'bar':3})
just simply
var model = new Backbone.Model({'foo':1,'bar':3})
Yes, if you're just passing in some variables that are not relevant to the view data, then you should not be passing in a model. I can think of two other, more performant, ways to get data into your view.
Passing in data on instantiation
Every view will take parameter passed into the View constructor and hand them off the the view initialize (Backbone does a bit more with passed in parameters, but they all end up in initialize). Let me show you a trivial example.
var MyView = Backbone.View.extend({
initialize: function(options) {
// All the parameter you pass into the view construct with will be
// in the initialize options
// Attach options to the view so you can access your options
// from all view properties (bound to the view)
this.options = options || {};
}
}
Now get your object literal into your view:
var aView = new MyView({'foo':1,'bar':3});
aView.options.foo; // 1
aView.options.bar; // 3
Making data available after instantiation
If you want to merge an object after instantiation, then simply use _.extend:
_.extend(aView, {'foo':1,'bar':3});
aView.foo; // 1
aView.bar; // 3

Backbone relational fetching a model that already exists

I would like to know if there is a way to override an existing relational model after a fetch.
Example:
I have a method on an API that returns a random model. So I created a new instance of the model client side and performed a fetch:
var x = new MyModel();
x.url = 'random';
x.fetch();
// If it exists it will throw "Uncaught Error: Cannot instantiate more than one Backbone.RelationalModel with the same id per type! "
This example works fine unless I have already have an instance of that model client side. Is there a way for me to determine if that model already exists client side after a fetch and update that model instead?
backbone-relational has a built in method for this in 'findModel' which returns the model if found:
backbone-relational docs
You should be able to add a conditional statement to catch
if( x = MyModel.findModel({id: id}) ) {}
else {
x = new myModel();
}

Breeze Angular (Defining Client Side Properties)

Gurus,
Here is my scenario:
I am defining a new client-side property (i.e. fullName) on one my entities using breeze's registerEntityTypeCtor function. The fullName property is coded to check the values of the firstName and lastName properties on the entity to determine it's value. It works when I am doing a query and receiving entities back from the db.
However, when I create a new entity on the client side (calling breeze's createEntity function) or make changes to the firstName or LastName properties without doing a save, then the custom fullName property is never updated until I perform another db pull. With breeze change tracking shouldn't the fullName property update any time either of the name properties changes?
During debug, I noticed that when I use a getter in code: (i.e. var fullName = entity.fullName) -- as I step through the code the ctor is hits the "backingStore" value of my entity which is either default value (using the createEntity) or the last db value, but never the current value of the entity.
What am I missing? Thanks
Here is an example I used for setting up the property:
function registerSpmoleSurvey(metadataStore) {
metadataStore.registerEntityTypeCtor('SpmoleSurvey', spmoleSurvey);
function spmoleSurvey() { }
Object.defineProperty(spmoleSurvey.prototype, 'fullName', {
get: function () {
var ln = this.lastName;
var fn = this.firstName;
return ln ? fn + ' ' + ln : fn;
}
});
}
Look at this page for examples of adding computeds to your Breeze entities -
http://www.breezejs.com/documentation/extending-entities
Pass in an anonymous function as the third parameter that extends the entity.
HI figure out what I was doing wrong...seems that I was a victim of camelCasing and I capitalize the property name inappropriately. Wrong as advertise now :}

How to update model by id in Backbone.js?

I've tried simple example from backbone tutorial and can't get this working
var Person = Backbone.Model.extend({});
var People = Backbone.Collection.extend({
model: Person,
url: "http://localhost:3002/people"
});
var people = new People();
var person = new Person({
id: 3
});
person.fetch({
success: function () {
person.set({age: 23});
person.save();
}
});
I just want to update existing record with id equals to 3 but got an error A "url" property or function must be specified. I'm sure that I didn't make mistake when typing this example but it works in tutorial and doesn't work for me. Is it because of some version changes?
As the error and comments have indicated, you need to specify the url property for the model or add the person model the people collection.
If you would like to fetch your model using the same url as the people collection. You need to add person to the people collection by doing:
var people = new People(person);
// or
people.add(person);
// The fetch url for a person would look like
// GET http://localhost:3002/people/3 Assuming the id of the person is 3.
If you need to use a different url than your collection has specifed the person model. You can specify the url or urlRoot attribute in your Person model.
var Person = Backbone.Model.extend({
urlRoot:'http://localhost:3002/person'
});
// The fetch url for a person would look like
// GET http://localhost:3002/person/3 The number will match id of model.

Resources