Backbone Model Binding - javascript

Trying to setup a successful binding on a model.
Binding works when adding/removing/updating values to the model. But once I recreate the model, binding stops working. I need to create new model so it gets new id/cid when saved to Collection.
I tried this.model.clear() but that doesn't assign new id/cid to the model.
Hope it makes sense. Thank you!
app.View = Backbone.View.extend({
initialize: function() {
this.model = new app.Model();
this.listenTo(this.model,'change', this.semaphore);
},
start: function(value) {
// Create new model unless running app first time
if( this.model.attributes.title != null ) this.model = new app.Model();
this.model.set({title: value});
},
semaphore: function() {
// Doesn't get call when new model gets reassigned
// Do stuff...
}
}

Nothing fancy but it works:
initialize: function() {
// Leave it blank
},
// New element gets created within the view
start: function(value) {
// Creates new model
this.model = new app.Word();
// Binds it to a function
this.listenTo(this.model,'change', this.semaphore);
// Set value(s) so binding function reacts to it
this.model.set({title: value});
},

Related

View doesn't listen to Model Event Backbone.js

For some reason I cannot comprehend, events are not being listened to by my View. The model IS changing, but the view doesn't seem to acknowledge these changes. Here's my code.
var playerSet = 1;
var bone = function(){
var app = {};
app.BoardModel = Backbone.Model.extend({
defaults: function(){
return{
board:[0,0,0,0,0,0,0,0,0],
allDisabled: false,
p1Score: 0,
p2Score: 0
}
},
setSlot: function(slot, ct){
var b = this.get("board");
b[slot] = ct;
this.set("board", b);
console.log("CHANGED");
}
});
app.Board = new app.BoardModel;
app.BoardView = Backbone.View.extend({
el: $("#ttt-board"),
initialize: function(){
this.listenTo(app.Board, "change", this.renderBoard);
},
renderBoard: function(){
console.log("HELLO THERE");
}
});
var tictac = new app.BoardView;
app.Board.setSlot(0,1);
};
bone();
When I fire setSlot, the model does change as the console outputs CHANGED, however I never see the renderBoard function being called.
This is probably incredibly simple, but it eludes me.
Your problem is that you are only changing the internal components of the array object, not the attribute on your model. Even though you are manually calling set on the model, this is not an actual change and the set logic only triggers a change event if the equality check between the old and new values fails (which in your case it doesn't).
Since you are calling a custom function anyways, why not just use a custom event?
setSlot: function(slot, ct){
this.get("board")[slot] = ct;
this.trigger("custom:change:board", slot, ct);
}
Now listen for the custom event (or both) instead of just change.
initialize: function(){
this.listenTo(app.Board, "change custom:change:board", this.renderBoard);
}

Clarification - Dealing with additonal information in Backbone.js

I'm fairly new to Backbone and I'm working on a project that is probably not ideal for Backbone really, but I'm trying anyway! I'm not working with a RESTful API, so all of my data is fetched on page load and that is it, it is then not updated, at all. The data is sent through as a single lump of JSON.
I have set up a new model CookingItem which gets created for every item in the JSON and is then added to the CookingItemList collection, this is fine. However, I need to store information about the data set somewhere so I have created another 'Properties model' that stores this information (state information, start items etc). This properties model will also have several methods that manipulate the data.. these are then called from the view (is this OK?).
This is working fine, but I just want to be sure what I'm doing is considered a good way of doing things. I'm sure there may be a better way to handle the Properties model?
// Models
var CookingItem = Backbone.Model.extend(),
CookingProperties = Backbone.Model.extend({
defaults:{
startItem: 'anything',
currentItems:[]
},
setStartItem:function (val) {
// Code Here to edit this models properties
},
addRemoveItem:function (val) {
// Code Here to edit this models properties
}
}),
// Collection
CookingItemList = Backbone.Collection.extend({
model:CookingItem,
url:function () {
return "json.js";
}
}),
// View
CookingView = Backbone.View.extend({
el:'.page',
events:{
// Buttons in the UI
'click .link1':function () {
this.cookingProperties.addRemoveItem('item name');
},
'click .link2':function () {
this.cookingProperties.addRemoveItem('item name');
}
},
initialize:function () {
// Scope
var _this = this;
// Instantiate
this.cookingItemList = new CookingItemList(); // PlayListItems Collection.
this.cookingProperties = new CookingProperties(); // Properties Model.
// Bindings
this.cookingProperties.on('change:startItem', function () {
_this.customMethod1();
_this.customMethod2();
});
// Render the view
this.render();
},
render:function () {
var _this = this;
this.cookingItemList.fetch({
success:function () {
_this.cookingProperties.setStartItem('item');
_this.customMethod1();
}
});
return this;
},
customMethod1:function () {
// Do something
},
customMethod2:function () {
// Do something
}
}),
// Start
cookingView = new CookingView({
collection:CookingItemList
});

Backbone.js, cannot set context on a callback

Ok, so I am working on a method to override the fetch method on a model. I want to be able to pass it a list of URL's and have it do a fetch on each one, apply some processing to the results, then update its own attributes when they have all completed. Here's the basic design:
A Parent "wrapper" Model called AllVenues has a custom fetch function which reads a list of URL's it is given when it is instantiated
For each URL, it creates a Child Model and calls fetch on it specifying that URL as well as a success callback.
The AllVenues instance also has a property progress which it needs to update inside the success callback, so that it will know when all Child fetch's are complete.
And that's the part I'm having problems with. When the Child Model fetch completes, the success callback has no context of the Parent Model which originally called it. I've kind of hacked it because I have access to the Module and have stored the Parent Model in a variable, but this doesn't seem right to me. The Parent Model executed the Child's fetch so it should be able to pass the context along somehow. I don't want to hardcode the reference in there.
TL;DR
Here's my jsFiddle illustrating the problem. The interesting part starts on line 13. http://jsfiddle.net/tonicboy/64XpZ/5/
The full code:
// Define the app and a region to show content
// -------------------------------------------
var App = new Marionette.Application();
App.addRegions({
"mainRegion": "#main"
});
App.module("SampleModule", function (Mod, App, Backbone, Marionette, $, _) {
var MainView = Marionette.ItemView.extend({
template: "#sample-template"
});
var AllVenues = Backbone.Model.extend({
progress: 0,
join: function (model) {
this.progress++;
// do some processing of each model
if (this.progress === this.urls.length) this.finish();
},
finish: function() {
// do something when all models have completed
this.progress = 0;
console.log("FINISHED!");
},
fetch: function() {
successCallback = function(model) {
console.log("Returning from the fetch for a model");
Mod.controller.model.join(model);
};
_.bind(successCallback, this);
$.each(this.urls, function(key, val) {
var venue = new Backbone.Model();
venue.url = val;
venue.fetch({
success: successCallback
});
});
}
});
var Venue = Backbone.Model.extend({
toJSON: function () {
return _.clone(this.attributes.response);
}
});
var Controller = Marionette.Controller.extend({
initialize: function (options) {
this.region = options.region;
this.model = options.model;
this.listenTo(this.model, 'change', this.renderRegion);
},
show: function () {
this.model.fetch();
},
renderRegion: function () {
var view = new MainView({
model: this.model
});
this.region.show(view);
}
});
Mod.addInitializer(function () {
var allVenues = new AllVenues();
allVenues.urls = [
'https://api.foursquare.com/v2/venues/4a27485af964a52071911fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
'https://api.foursquare.com/v2/venues/4afc4d3bf964a520512122e3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
'https://api.foursquare.com/v2/venues/49cfde17f964a520d85a1fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811'
];
Mod.controller = new Controller({
region: App.mainRegion,
model: allVenues
});
Mod.controller.show();
});
});
App.start();
I think you're misunderstanding how _.bind works. _.bind returns the bound function, it doesn't modify it in place. In truth, the documentation could be a bit clearer on this.
So this:
_.bind(successCallback, this);
is pointless as you're ignoring the bound function that _.bind is returning. I think you want to say this:
var successCallback = _.bind(function(model) {
console.log("Returning from the fetch for a model");
Mod.controller.model.join(model);
}, this);
Also note that I added a missing var, presumably you don't want successCallback to be global.

Strange issue binding events with backbone, "this" is not being updated

I had a strange issue working with backbone and binding events. I'll see if I can explain it in a clear way (it's a cropped example...)
In a view, I had the following code in the initialize method
var MyView = Backbone.View.extend({
initialize: function(options) {
//[...]
this.items = [];
this.collection.on('reset', this.updateItems, this);
this.fetched = false;
},
render: function() {
if (!this.fetched) {
this.collection.fetch(); // fetch the collection and fire updateItems
return this;
}
this.$el = $('#my-element');
this.$el.html(this.template(this.items));
},
updateItems: function() {
this.fetched = true;
this.loadItems();
this.render(); // call render with the items array ready to be displayed
}
}
The idea is that I have to fetch the collection, process the items (this.loadItems), and then I set this.$el.
The problem I was facing, is that inside updateItems, I couldn't see any property added after the binding (this.collection.on...)
It seemed like the binding was done against a frozen version of the view. I tried adding properties to test it, but inside updateItems (and inside render if being fired by the collection reset event) I could not see the added properties.
I solved it binding the collection just before fetching it, like this:
render: function() {
if (!this.fetched) {
this.collection.on('reset', this.updateItems, this);
this.collection.fetch();
return this;
}
But it's a strange behavior. Seems like when binding, a copy of 'this' is made, instead of a reference.
Am I right? or there's anything wrong I'm doing?
You should perform your binding in the initialization phase of your collection view:
// View of collection
initialize: function() {
this.model.bind('reset', this.updateItems);
}
now when fetch is finished on the collection updateItems method will be invoked.
Of course you need to bind the model and view before doing this:
var list = new ListModel();
var listView = new ListView({model: list});
list.fetch();

Backbone.js Persisting child variables to parent

What is the correct way to persist an inherited variable, on action to the parent in Backbone.js?
I can see some logical ways to do this but they seem inefficient and thought it might be worth asking for another opinion.
The two classes are both views which construct a new model to be saved to a collection, the parent passing a variable through to a popup window where this variable can be set.
I'm not sure there's enough detail in your question to answer, but there are a few ways to to do this:
Share a common model. As you describe it, you're using two views to construct a model, so the easiest way is probably to pass the model itself to the child view and have the child view modify the model, rather than passing any variables between views:
var MyModel = Backbone.Model.extend({});
var ParentView = Backbone.View.extend({
// initialize the new model
initialize: function() {
this.model = new MyModel();
},
// open the pop-up on click
events: {
'click #open_popup': 'openPopUp'
},
openPopUp: function() {
// pass the model
new PopUpView({ model: this.model })
}
});
var PopUpView = Backbone.View.extend({
events: {
'change input#someProperty': 'changeProperty'
},
changeProperty: function() {
var value = $('input#someProperty').val();
this.model.set({ someProperty : value });
}
});
Trigger an event on the parent. If for some reason you can't just pass the value via the model, you can just pass a reference to the parent and trigger an event:
var ParentView = Backbone.View.extend({
initialize: function() {
// bind callback to event
this.on('updateProperty', this.updateProperty, this);
},
updateProperty: function(value) {
// do whatever you need to do with the value here
},
// open the pop-up on click
events: {
'click #open_popup': 'openPopUp'
},
openPopUp: function() {
// pass the model
new PopUpView({ parent: this })
}
});
var PopUpView = Backbone.View.extend({
events: {
'change input#someProperty': 'changeProperty'
},
changeProperty: function() {
var value = $('input#someProperty').val();
this.options.parent.trigger('updateProperty', value);
}
});

Categories