Delegating events - backbone.js

I've got a backbone view for an entire collection (a list of "clickable" categories). Can I delegate events on each item of the view so that I can find which category has been clicked?

Here's a post that might help. Basically you use a data-* attribute in the item view to store and then retrieve the id of item clicked:
Backbone.js: Getting The Model For A Clicked Element
If you'd rather go directly to code, here's the jsFiddle that's used in the post to demonstrate. Hope that helps.

I have no answer for your question (no, I think), but would like to share my approach: a general collection view component, which renders a collection using other view. It can be as simple as in the example below or more sophisticated (listening add/remove/reset events and react accordingly).
var CollectionView = Backbone.View.extend({
render : function() {
this.options.collection.each(function(model) {
this.$el.append((new this.options.view({model : model})).el);
}, this);
}
})

Related

how model and view are connected in backbone.js

I am new in Backbone.js and I keep failing to understand how the model and the view are connected.
I played with Angular where things are pretty clear there, how model, view and controller are connected.
I know Angular and Backbone are different and the latter is MV*.
In Backbone I can understand how model and view are created and work, but how are they connected? Seems to me they are seperated.
Please take a minute to explain or point me to a tutorial.
Thanks in advance
EDIT
OK, here is an example. It happens that I read the book that trolle suggests. This is a code from the book's github
I start reading. I understand the Todo model. I understand the TodoList collection. Then I get to the TodoView
creates a new li
uses Underscore template to compile html
defines some functions that imlements later in the same view
defines an initialize function
inside that function
what is this? this.model.bind('change', this.render, this);
how he can magically bind the action change to a model? How the code knows about the model? When he defined the model and how? Just because is there, the code knows that model = Todo model?
How does he do that bind? What am I missing.
This confuses me, so reading AppView view does not help me much
Thanks again
In backbone.js views are used for displaying models in browser.
For example you can have a model object, whose JSON representation resembles the following: {'firstName': 'foo', 'lastName': 'bar' }
And you use view object to map this model to browser DOM.
As a rule, you use view object along with certain template engine.
Templates allows for creating html chunks filled with model's data.
If you are using underscore template function, your template may look something like this:
<div>
<div>First Name: <%= firstName %></div>
<div>Last Name: <%= lastName%></div>
</div>
After merging template with model's data it would be:
<div>
<div>First Name: foo</div>
<div>Last Name: bar</div>
</div>
You can reuse this view object and its template to display another model object, for example {'firstName':'another foo', 'lastName':'another bar'}, so that the result html would be:
<div>
<div>First Name: another foo</div>
<div>Last Name: another bar</div>
</div>
That is one thing about connection between model and view.
Also view object can listen to changes in your model object to render immediately last updates. For example (inside view object):
initialize: function() {this.listenTo(this.model, 'change', this.render);}
In short, views are the logic behind the presentation of the model's data to the user. So in its simplest form, you bind a model to a view through the models change events, so you can update the presentation instantly whenever your data changes. So a simple view would take in a model, create HTML elements based on that models data, insert that html into the DOM and update that HTML whenever the data changes.
You can find a great book full of helpful examples here (free): http://addyosmani.github.io/backbone-fundamentals/
EDIT:
With regards to your updated question about how the view knows about the model, this.model is a reference to the actual model object. You can set the reference to the model when you create the view. That is, when you call your view-constructor to instantiate a view, you could pass in a model. You need to go all the way into the AppView object in the code example to see where this happens, in the addOne method:
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
}
The function gets a model as a parameter, and when the view is instantiated that model is referenced. So now you have a view that knows about the model, and when the view.render method is called, the view can render it's template with the model data. When you change the data in the model, for instance by using the set method, myModel.set({title: "March 20", content: "In his eyes she eclipses..."});, you trigger the change event for that model. You can see all the built in events for backbone here: http://backbonejs.org/#Events-catalog. The view is listening for that event, just like it could listen for a click event or any other event. In the code in your example the view listenes for a change event from the model. If it hears it it knows that the object behind this.model has changed, and it can then update the DOM with the new data or do something else. In the case of the example it calls this.render, which updates the DOM with the new model data.
I think you want to know about Backbone.Events (http://backbonejs.org/#Events), both Models and Views make use of this module and that's how the view learns about changes in the Model, if you want to learn how this is implemented you can always read the annotated source (http://backbonejs.org/docs/backbone.html#section-19), but more important I think you want to learn about the observer pattern: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript.

How do I design MarionetteJS ItemView to properly show loading message?

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.

Backbone per instance event bindings

I have a view that creates a sub-view per item in the list. Generically let's call them ListView and ListItemView. I have attached an event as follows on ListItemView:
events: {
"click .remove": "removeItem"
}
I have template-generated html for ListItemView that is approximately like the following (swapped lb/rb for {/} so you can see the "illegal" html):
{div class="entry" data-id="this_list_item_id"}
SOME STUFF HERE
{div class="meta"}
{a class="remove" href="javascript:;"}[x]{/a}
{/div}
{/div}
The problem is, when the click on any of the [x]'s, ALL of the ListItemViews trigger their removeItem function. If I have it go off of this model's id, then I drop all the items on the page. If I have it go off the clicked item's parent's parent element to grab the data-id, I get a delete for EACH ListItemView instance. Is there a way to create an instance-specific event that would only trigger a single removeItem?
If I have ListView hold a single instance of ListItemView and reassign the ListItem model and render for each item in the list it works. I only end up with one action (removeItem) being triggered. The problem is, I have to find the click target's parent's parent to find the data-id attr. Personally, I think the below snippet is rather ugly and want a better way.
var that = $($(el.target).parent()).parent();
Any help anyone gives will be greatly appreciated.
It seems like your events hash is on your ListView.
If it is, then you can move the events hash to ListItemView and your removeItem function can be the following
removeItem: function() {
this.model.collection.remove(this.model);
}
If this isn't the case, can you provide your ListView and ListItemView code so I can look at it.
A wild guess but possible; check that your rendered html is valid. It might be possible that the dom is getting in a tiz due to malformed html

Backbone.js - where does code to create a new post go?

I'm trying to make a quick sample Backbone.js app that has a Post model, a PostList collection, and a PostView + PostListView. Something simple where you can post in a form and it will add your post to the list of posts.
When someone clicks submit on the post form, it triggers an event in "PostListView", the view for the PostList collection. Where do I create a new post model and add it to the collection? Do I write this code in the View itself? Or does the view call a collection method that does this? Can you even write custom collection methods? If so, how do I call them from a view?
Coming from a Rails background, I naturally edge towards putting code in the collections/models rather than views (rails controllers), but I can't figure out how to call custom collection events from views.
Code is as below. Thanks so much for any help!
PostListView.coffee:
class forum.PostListView extends Backbone.View
tagName: 'section'
className: 'post-list'
events:
'click .post-form button': 'submit'
initialize: ->
#causes the view to render whenever the collection's data is loaded
#collection.bind 'reset', #render
#collection.bind 'add', #render
render: =>
$(#el).html JST['postList']()
$postList = this.$('.post-list')
#iterates through posts, renders, appends to <ul>
#collection.each (post) =>
view = new forum.PostView
model: post
collection: #collection
$postList.append view.render().el
return this
submit: ->
console.log "submitted!"
#collection.trigger 'newPost', this.$('.post-form textarea').val()
PostList.coffee:
class forum.PostList extends Backbone.Collection
model: forum.Post
url: '/posts'
initialize: ->
this.bind 'newPost', newPost
newPost: (postText) ->
console.log "Collection method called!!"
# post = new forum.Post
# content: postText
# #add post
You call a method in PostList, from the PostListView, to add a new model in the collection. Something like this in regular js:
this.collection.add( new forum.Post( this.$( ".post-form textarea").val() ) );
I don't do CoffeeScript so I guess the syntax for above in cs would be like this:
#collection.add new forum.Post
this.$( ".post-form textarea").val()
If it's more complicated to add a new Post to PostList, you could add a method for it in PostList
this.collection.complexAdd( params... );
You add custom methods to PostList in the place where you create the class.. you have done so with the newPost method already.
This is much simpler than your collection listening to view events because views are initialized last.
Edit: I guess it could also come down to opinion, some people prefer models/collections knowing about which backbone views are using them, but in a situation where there are more than one view for one model (for example, TabContent view and TabSelector view for a single TabModel), it makes stuff more complicated.
The following tips may help you...
It might separate things better if you have a separate view for NewPost, whose single responsibility is to manage the new post form. Your PostListView can create that and append it to itself.
Normally, you do not want to add a new model to a collection until after it is saved. So what you may do is give your NewPostForm a reference to the collection and have it add it once it is saved.
You may also want the PostList to dump and recreate the NewPost view after that, and get it ready to add a subsequent post.
Backbone "views" are sometimes more like controller actions in Rails. So creating models, moving them around, saving them, etc. are all perfectly ok to do in view code.

Correct way to insert a view with Backbone.js

I have a simple backbone.js app. I want to render a view into the DOM of the HTML page, this view is a detail view for a model. My HTML page already has the DIV element that I want to render the view into. If I try to render my view like this:
detailView = new RulesPanelView({model : #model})
$("#detail").html(detailView.render().el)
It fails and I get [Object HTMLDivElement] inserted into the DOM, not my rendered HTML.
This is the only way I can get it to work and it seems like a hack:
$("#detail").html('')
detailView = new RulesPanelView({model : #model})
$("#detail").append(detailView.render().el)
Having to empty the HTML of the DIV before rendering so I don't get multiple views rendered inside #detail which is what would happend with append.
Also aren't I creating way too many views this way, just seems cleaner to replace the HTML as in the first code segment?
What is the correct way to render this view?
What you want is to pass the already inserted DOM node to the view as a 'el' option to the constructor:
new RulesPanelView({el: $("#detail")});
This way, it won't render again. You still need to make sure your view's 'render' method will be able to render a correct view from an updated model, though.
The backbone documentation mentions this as a good way to avoid rendering too much stuff at once.
I actually append in the render method of the view. This doesn't work if you want to re-render when models change - but for that I've added a refresh method that render actually calls before appending. I then bind the refresh to the model change (if I need that). So in my View, I do this:
render: function(){
var markup = this.refresh();
$(markup).appendTo('#some-selector');
return this;
},
refresh: function(){
return $(this.el).html($.mustache(this.template, this.model.toJSON()));
},
Not sure if that's the "best", but I think it works pretty well. I've also seen where you have a collection bound to a view that loops through all of the models and renders "sub-views" of the collection view - this provides a nicer programmatic approach than hard-coding where you're going to append.

Resources