ive got a problem which im not sure how to bite.
Ive got a search form with a lots of filters. I store all current fitlers
in global namespage ie: window.NM.CurrentSearchParams = {} and i update the hash from filters. Each time the hash is updated, updating uri event is fired to
replate the window.localtion.search with current params. Everything is workin here fine.
Now ive got a problem after entering page to deserialize window.location.search and
update CurrentSearchParams with parameters. Thats fine also, but i would like
to forms to repopulate based on those params.
Ive got a little mess in code and not sure the best way to do it.
How to bind form population in different views to the parameters?
make the CurrentSearchParams a Backbone.Model so you can subscribe to change events on it.
NM.CurrentSearchParams = new Backbone.Model();
MyView = Backbone.View.extend({
initialize: function(){
NM.CurrentSearchParams.on("change", this.doStuff, this);
},
doStuff: function(){
// do stuff here
},
close: function(){
NM.CurrentSearchParams.off("change", this.doStuff, this);
}
});
Note the "close" method that I added. This is very important and you need to call this when your view is done and ready to be closed, or you will end up with a lot of memory leaks and events triggering on zombie view instances: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
Related
Note: we are using backbone 1.0.0
I am relatively new to Backbone, and was going to through some of the code a ex co-worker wrote. Rather than copy pasting stuff blindly, I wanted to understand how he did things, and that's when I started wondering about the best way to handle zombie views.
var view = new editItemView({ model: this.model });
this.ui.editItemPopup.html(view.render().el).modal({ modalOverflow: true });
This creates an instance of view and pops it up in a boostrap modal. The model has Save Changes, Cancel & Delete buttons. We will look at the clean work that is done on Save changes and delete.
onDelete: function() {
this.stopListening(this.model);
this.$el.parent().modal('hide');
this.$el.remove();
},
onApplyChangesClick: function () {
this.stopListening(this.model);
this.close();
},
close: function () {
this.$el.parent().modal('hide');
}
As far as I can tell, this code won't discard the view. And if I were to add another listener to the aforementioned view
this.listenTo(this.model.AnotherItem, 'change', this.doSomething);
and then trigger the change event on this.model.AnotherItem, this.doSomething will still fire. Correct?
I did some reading on Zombie views prior to posting this question. http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
And based on that article wouldn't I be better off if I just did
onDelete: function() {
this.close()
},
onApplyChangesClick: function () {
this.close();
},
close: function () {
this.$el.parent().modal('hide');
this.remove();
}
his.remove() will automatically call stopListening and also remove the dom element(Same as this.$el.remove)
The article that I posted also uses this.unbind()
this.unbind()will unbind any events that our view triggers directly – that is, anytime we may have calledthis.trigger(…)` from within our view, in order to have our view raise an event.
Is that still necessary in Backbone 1.0.0 (or latest version)? The article is 3 years old, so I was wondering and I couldn't find any mention of view.unbind in backbone documentation. The documentation mentions that unbind is an alias of off. So should I be doing
this.remove();
this.off();
Ok, first off let me state the obvious: a few zombie views here or there are not going to cause you any problems. All any given zombie view will do is eat up a small amount of memory and then go away when the user hits refresh or navigates away. So, if you're a little sloppy about cleaning up your references in general things will still work fine. Where you will run in to problems is when you have a lot of zombie views, say because you rendered a 20x100 table where every cell has its own View.
Now, to truly understand how to avoid zombie views you have to understand how memory works in Javascript. I encourage you to read more about that elsewhere, but here's the cliff notes version: anything you "stop using" will get cleaned up by the browser's garbage collector, and since the garbage collector can't tell exactly when you "stop using" something it actually goes by whether or not that thing has any references to it on other objects.
This is where event bindings come in to play, because they can create references that prevent a view from being garbage collected. One of the features of Backbone is that it handles cleaning up these bindings if they are made as part of a Backbone.View's initialization (ie. the events you put in an events property of the View class). So if you remove a View's element from the page, it will get garbage collected ...
... unless it has some other reference to it, like another object that uses it, or an event binding that you created using jQuery. So, as long as your Views don't have any other references you are correct: simply removing the element will be enough. But if you do have any other references, you will need to clean them up or else the View won't get garbage collected and will become a zombie view.
Usually I have a View on which I set listeners to model-changes, like so (conflated):
var jsonModel = {bla: 'interesting stuff'}; //some model in json, probably rendered in a dom-element and passed from the server to the client
var someModelType = Backbone.RelationalModel.extend({
bla: "String"
});
var someModelInstance = new someModelType(jsonModel);
var someViewType = Backbone.View.extend({
initialize: function(){
this.listenTo(this.model,'change:bla', function(model){
//update view to sync with model change here
}
}
});
var someViewInstance = new someViewType({
model: someModelInstance
});
I'm looking for the preferred / backbone - way of bootstrapping the view, i.e: I want my view (form fields or what have you) to be synced with the model on creation. With the above this is impossible since the model is created before the view is attached, which results in model-updates/changes to be fired before listeners from the view were initialized.
Of course I could code some custom bootstapping logic which would call the listener-functions manually, but since this must be such a common problem, I'm looking on some best practice advice, or even better, a Backbone-switch I need to set to get this to work.
This will sound silly, but what I usually do is do a render() after the view creation.
For me, the whole point of the change event is to notify of a change. The fact that it's triggered also on creation (as a "changed compared to the default") is more a side effect than anything deep...
In that sense, when I create the views, and give it an existing model, I expect the model to be somewhat ready and so I should be able to do a render() right away. So my bootstrapping code would be more like:
var someViewInstance = new someViewType({
model: someModelInstance
});
someViewInstance.render()
Or do it in the initialize() after bindings if you feel adventurous (I personally don't like that because when dealing with subviews, it's hard to know exactly when you want the render to happen, especially when dependent on libraries who need the DOM to be ready, and can't work on detached nodes).
So to sum up, although your problem is common, to me, it doesn't need any kind of complex solution: you have a render() function to transform your model into DOM-stuff, your model may or may not be ready but it exists, so you pass it to the view, and you do the first rendering after creating the view, manually, because you know it needs to happen. Then you bind to changes, in case your model is updated, to update the DOM accordingly.
side note: in case of a collection of models, when the Model isn't ready at ALL, then it probably shouldn't even have an instance, and you should let the Collection give you some add/remove events for when it pops into existence.
I currently started using Firebase as my backend solution for persistance.
I found it easy to create new objects and persist it to Firebase with Backfire with a simple
collection.add(obj)
The issue comes when I try to get a Firebase collection from the server.
For example when i try
console.log(collection);
I get this output:
=> {length: 0, models: Array[0], _byId: Object, _events: Object, constructor: function…}
Which result in an empty models array
console.log(collection.models);
=> []
After some searching, I figured out that Backbone Collections aren't yet loaded at the time I try to log it to the console (see this previous question).
I also tried using
Backbone.Collection.extend({
model: Todo,
firebase: new Backbone.Firebase("https://<your-namespace>.firebaseio.com")
});
To explicitly call fetch from the server and use success callback with no success either.
My question is: How can I get the Firebase Collection and populate the DOM from it?
When you call Backbone.Firebase.Collection.add, it does not get added to the collection synchronously. Rather, it sends the request to Firebase and then waits for the return event. See the code here
Thus, if you immediately try to read the collection, you will see zero elements. However, if you try something like this:
collection.once('add', function() { console.log(collection.length); });
You'll see the element you have added.
Remember that we're dealing with real-time data here, so when you want to populate the DOM, you shouldn't think in terms of a single transaction, but instead rely on events and take everything as you get it (in real time).
So to populate the DOM, do something like this in your view:
Backbone.View.extend({
render: function() {
this.listenTo(this.collection, 'add', this.rowAdded);
},
rowAdded: function(m) {
/* use `m` here to create your new DOM element */
}
});
Additionally, you'll probably want to check out a nice binding library like ModelBinder to help you deal with the constantly changing DOM, so you don't have to re-invent any wheels.
It seems you have to use a Backbone.Firebase.Collection and not a Backbone.Collection which will tell you that your calls to fetch or sync are silently ignored.
Also, Backbone.Firebase's got a read and a readall methods that should get you started. It seems Backbone.Firebase.Collection doesn't inherit this method, but I'm not sure though.
Edit:
As Kato stated in his comment, it seems you don't have to do anything. Just use Backbone.Backfire.Collection and Backbone.Backfire.Model.
First off - I am a MarionetteJS noob.
I am having trouble making an ItemView display a loading message or throbber while it is being fetched. This is especially problematic when this ItemView is being displayed from a deep link in Backbone's history (i.e. the ItemView is the first page of the app being displayed since the user linked directly to it). I want to indicate that the page is loading (fetching), preferably with a simple view, and then show the real templated view with the fetched model.
I have seen other answers on SO like Marionette.async, (which has been deprecated) and changing the template during ItemView.initalize().
Anybody (Derrick?) got any suggestions or best practices here?
UPDATE:
I am getting the model from the collection using collection.get(id), not using model.fetch() directly.
After thinking about this, the real question is where should this be implemented:
I could change my controller to see if the model exists in the collection (and if the collection is loaded) and decide which view to show accordingly. this seems like a lot of boilerplate everywhere since this could happen with any ItemView and any controller.
I could change my ItemView initialize to test for existence of the model (and a loaded collection), but same comment here: every ItemView could have this problem.
UPDATE 2:
This is what I ended up with, in case anybody else want this solution:
app.ModelLayout = Backbone.Marionette.Layout.extend({
constructor: function(){
var args = Array.prototype.slice.apply(arguments);
Backbone.Marionette.Layout.prototype.constructor.apply(this, args);
// we want to know when the collection is loaded or changed significantly
this.listenTo(this.collection, "reset sync", this.resetHandler);
},
resetHandler: function () {
// whenever the collection is reset/sync'ed, we try to render the selected model
if (this.collection.length) {
// when there is no model, check to see if the collection is loaded and then try to get the
// specified id to render into this view
this.model = this.collection.get(this.id);
}
this.render();
},
getTemplate: function(){
// getTemplate will be called during render() processing.
// return a template based on state of collection, and state of model
if (this.model){
// normal case: we have a valid model, return the normal template
return this.template;
} else if (this.collection && this.collection.isSyncing) {
// collection is still syncing, tell the user that it is Loading
return this.loadingView;
} else {
// we're not syncing and we don't have a model, therefore, not found
return this.emptyView;
}
}
});
And here is how to use it:
// display a single model on a page
app.Access.Layout.CardLayout = app.ModelLayout.extend({
regions: {
detailsRegion:"#detailsRegion",
eventsRegion:"#eventsRegion"
},
template:"CardLayout", // this is the normal template with a loaded model
loadingView:"LoadingView", // this is a template to show while loading the collection
emptyView:"PageNotFoundView", // this is a template to show when the model is not found
onRender : function() {
this.detailsRegion.show( blah );
this.eventsRegion.show( blah );
}
});
thanks!
For the ItemView
I think you can add a spinner in your initialize function, I really like spin.js http://fgnass.github.io/spin.js/ because its pretty easy and simple to use, and you can hide the spinner in the onRender function of the Itemview
For The CollectionView
in the CollectionView you could handle it like this....
Take a look at the solution that Derick posted..
https://github.com/marionettejs/backbone.marionette/wiki/Displaying-A-%22loading-...%22-Message-For-A-Collection-Or-Composite-View
I'd suggest using jQuery deferreds:
Start fetching your data, and store the return value (which is a jQuery promise)
Instanciate your content view
Show your loading view
When the promise is done, show the view containing the content
I've talked about implementing this technique on my blog:
http://davidsulc.com/blog/2013/04/01/using-jquery-promises-to-render-backbone-views-after-fetching-data/
http://davidsulc.com/blog/2013/04/02/rendering-a-view-after-multiple-async-functions-return-using-promises/
The issue with the solution linked by Rayweb_on, is that your loading view will be displayed any time your collection is empty (i.e. not just when it's being fetched). Besides, the ItemView doesn't have an emptyView attribute, so it won't be applicable to your case anyway.
Update:
Based on your updated question, you should still be able to apply the concept by dynamically specifying which template to use: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#change-which-template-is-rendered-for-a-view
Then, when/if the data has been fetched successfully, trigger a rerender in the view.
I've been learning a lot about backbone, backbone-relational and building web apps from reading through stackexchange - so first a thank you to the community.
Now, I am stuck at trying to understand this current issue involving nested models and sub views in what seems to me to be is a pretty common usecase.
I am trying to extend this tutorial to learn about backbone-relational, views/subviews and event handling by keepin track of "CheckIns" for each wine.
I have extended the server side to return appropriate JSON and backbone-relational model for checkIns like so:
window.CheckInModel = Backbone.RelationalModel.extend({
defaults:{
"_id":null,
"Did":"true",
"dateOf":"",
}
});
window.CheckInCollection = Backbone.Collection.extend({
model : CheckInModel
});
And the Wine Model like so:
relations: [{
type: Backbone.HasMany,
key:'CheckIn',
relatedModel: 'CheckInModel',
collectionType: CheckInCollection,
}]
I've created a CheckInListView and CheckInItemView (same as WineListView and WineListItemView) and use the WineView Render function to render the CheckIns like so:
render:function (eventName) {
console.log("wine View Render");
$(this.el).html(this.template(this.model.toJSON()));
this.myCheckInListView = new CheckInListView({model: this.model.attributes.CheckIn});
this.$el.append(this.myCheckInListView.render().el);
return this;
},
I've also created a new function within wineview that creates a checkin and associated with the given event:
logcheckin: function (event){
var todate = new Date();
newCheckIn = new CheckInModel ({'Did':"true", 'dateOf': todate.toISOString()});
console.log ("Logcheckin - About to push newCheckIn onto Model.");
this.model.attributes.CheckIn.push (newCheckIn);
console.log ("Just pushed newCheckIn onto Model.");
this.saveWine();
}
Ok - if you haven't TL/DRed yet - This all seems to work fine from a UI perspective -ie. everything renders correctly and saves to the Db.
But I notice in the console that when I push a new CheckIn (between the console.logs above) the CheckInListView's Add binding gets called multiple times for wach button press - Which makes me think something is wrong with how I'm doing views or that I am not understanding something fundamental about event propagation.
Why is this happening ? Is it expected behavior ? Am I approaching what I am trying to do correctly ?
Thanks for reading if not your help.
==
Here are the relevant parts of the CheckinListView and CheckInList Item views that are bound to the add (and other) events.
window.CheckInListView = Backbone.View.extend({
initialize:function () {
this.model.bind("reset", this.render, this);
this.model.bind("change", this.render, this);
var self = this;
this.model.bind("add", function (CheckIn) {
console.log ("Adding to CheckInListView - a CheckIn List Item", CheckIn);
self.$el.append(new CheckInListItemView({model:CheckIn}).render().el);
});
},
close:function () {
$(this.el).unbind();
$(this.el).remove();
}
});
window.CheckInListItemView = Backbone.View.extend({
initialize:function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
});
==============================================
The comment about event binding and closing views were the right hints for debugging this.
1) I was not closing and unbinding the nested views properly which left some ghost event consumers even though there was nothing in the DOM
2) You only need to bind events if we want to do something only in the subview.
For Example - If I have checkbox in a subview I can bind a subview change event in the main view and handle the event there since the mainview has model there anyway. I don't know if this is the "right" way but it works for what I need to do. (mm.. spaghetti code tastes so good)
3) Struggling with this helped me think through the UX some more and helped me simplify UI.
4) I was trying to "save" calls to the server by nesting all the data into on JSON call. And if I were to re-do this - I would not nest the data at all but handle it on the back end by associating the wine ID with checkIn ID and then having a separate collection that gets populated with the collection once a task is selected - I thought this would not be a the preferred way but it seems to be the way that a lot of people.
Still welcome any thoughts on the "right" way questions above or if anyone can point to a tutorial that goes beyond the "simple backbone app"
I'm not sure about everything that's happening, but, I've run into the problem of events firing multiple times before. If you're rendering multiple models using the same view, there's a chance that they're all being bound to the same event.
Perhaps this answer might apply:
Cleaning views with backbone.js?
If not, you should respond to Edward M Smith's comment and show how your events are being bound.