Struggling to make the following code work in a view that manages a backbone collection:
class VGStream.Views.Scenarios.ScheduleRisks extends Backbone.View
template: JST['backbone/templates/scenarios/schedule_risks']
initialize: (options) ->
super
Backbone.pubsub.on 'allSchedulesRemoved', #removeAllScheduleRisks, #
removeAllScheduleRisks: =>
for risk in #risks.models
risk.destroy
#render()
render: ->
super
#risks.fetch()
#$el.html #template #
#
I am just showing the relevant code snippet from the Coffeescript class view. This does not delete the 'risk' models either from the database or the list view itself. How should I do it? The event allSchedulesRemoved fires correctly when all schedules are removed. I want the associated risks to be removed as well from both the view as well as the database.
You don't always have to call super
There are built-in #risks.each() method to loop through collections.
Put fetch() in render() is not recommended.
To clear the collection, you could do several #risks.remove(risk) or, after destroyed everything, reset the collection with an empty object.
risk.destroy actually send a delete request to your backend, so whether it's deleted in server or not depends on your backend code.
Related
I have a CompositeView that shows a list of models that I requested from a server, something like (in CoffeeScript):
class List.Stories extends Marionette.CompositeView
template: "stories-list-body"
itemView: List.Story
itemViewContainer: "#stories-list"
class List.Story extends Marionette.ItemView
template: "stories-list-story"
triggers:
"click .js-show-button": "show:button:clicked"
The views are correctly created passing the collection as an argument for the constructor, I can see the elements and when I click on the button it triggers the appropriate event and it's handled. The thing is, when the handler creates a new view showing the model and closes the old one, the collection is still referenced in model.collection taking up some memory.
What would be the correct way to eliminate this reference? Simply using delete model.collection in the handler before replacing the view?
Try doing something like
var model = myCollection.remove(viewModel, { silent: true })
// create new view using `model`
In the example above, viewModel would refer to the view's model (so it would be this.model from within the view).
By removing the model from the collection, it should be garbage collected (assuming it's not referenced anywhere else...).
If it all happens within same controller, i.e. the controller is still open and will be responsible for the event and no Application.vent will be triggered, I think this situation is acceptable if memory leak won't be big. The reason is the controller will be finally closed so no need in a hurry.
If App level vent/request/command will be triggered, you need to take it seriously. Assume you have such code in controller:
#listenTo storiesView, 'itemview:show:button:clicked', (itemView) ->
App.vent.trigger 'show:another:view:with:this:model', itemView.model
Stop here. The model is the old model and won't be garbage collected.
I will use below code instead:
#listenTo storiesView, 'itemview:show:button:clicked', (itemView) ->
model = _.clone itemView.model
App.vent.trigger 'show:another:view:with:this:model', model
The new model is a totally new object and then has nothing related to current view/model/controller.
I am using Backbone, Marionette, and Backbone.localStorage to persist a list of items. I have a remove button on the ItemViews which fires the model's destroy method. This removes the model from the collection successfully and fires the remove event.
However, the CollectionView does not remove its ItemView automatically (as I think? it should), nor does it remove the model from local storage, so on reloading the page, it's back in the list.
Edit:
The model is removed by this method on the view:
confirmRemove : function() {
this._setRemoveState(false);
this.model.destroy({success: function() { console.log('model destroyed!');}});
return false;
}
The success callback does fire.
The problems:
I overwrote the ItemView's remove method unintentionally, so it wasn't removed from the DOM.
destroy only runs its sync actions on models with ids, assuming that only models with ids are persisted to the server. However, while Backbone.localStorage creates internal ids, it does not persist them back to the models, so there was no id property and Backbone skipped removing it from the server.
I have 2 post collections and a model as follows.
# router file
#posts = new MyApp.Collections.PostsCollection()
#posts.reset options.posts
#followed_posts = new MyApp.Collections.PostsCollection()
#followed_posts.reset options.followed_posts
# Post model file
class MyApp.Models.Post extends Backbone.Model
paramRoot: 'post'
follow_post: ->
# ajax call
console.log "_________Index:#{this.collection.indexOf(this);}"
console.log this.collection
console.log "_________Followed:"
console.log #followed_posts
class MyApp.Collections.PostsCollection extends Backbone.Collection
model: MyApp.Models.Post
url: '/posts_all'
What I am trying to do is when one of the model changed in one collection, I want to update the other model in other collection too.
These collections may or may not hold same models.
So let's say if a model in #posts changed in my Post model, I want to update that model in #followed_posts too. If #followed_posts doesn't have that model, I need to add a duplicate of the model to #followed_posts collection.
I can access the collection that model belongs, but I cannot access the other collection.
Any ideas appreciated, thanks.
If the two collections are antisocial and can't talk directly to each other, which is usually good design, then you'll need an intermediary -- a global event dispatcher. When a model changes, propagate that event to the dispatcher along with a reference to the model. Listen for the event in the other collection and use the model passed to check for existence and respond as needed.
EDIT:
Backbone's documentation mentions this pattern:
For example, to make a handy event dispatcher that can coordinate
events among different areas of your application: var dispatcher =
_.clone(Backbone.Events)
But in fact, this is such a common pattern that the Backbone object itself is extended with Events. So you can just do:
// In your Post model
#on "change", -> Backbone.trigger "post:change", this, #collection
// And then something like this in the collection class definition:
#listenTo Backbone, "post:change", (model, collection) =>
if post = #get model.cid
post.set model.toJSON()
else
#add model
Also, is followed posts a subset of posts? If so, why not put an attribute on the model designating it as followed? Then you could find all followed posts with a simple filter function.
I would strongly suggest that you should consider having a single collection and add some kind of attribute in the model to differentiate between what kind of posts they are.
What is the best way to bind events to a Backbone boilerplate application? I've been trying to bind my events directly to the models associated with my views, in my views, but it doesn't seem to be working. I see within 'namespace.js', that there is an app key that extends Backbone.Events like so:
// Keep active application instances namespaced under an app object.
app: _.extend({}, Backbone.Events)
I don't fully understand how to use it...
I was able to get things working without the boilerplate, but it does provide some very cool functionality, so I'd love to be able to use it. Thanks!
ADDED
the code I was using was with the underscore bind method like so:
this.module.bind('change', this.render);
But then, I realized that 'this.model' is returning undefined, and so this doesn't work. I really am not sure how the boilerplate wants me to reference my model from the view.
I'm not sure if it is a typo that you copied from your code or a typo you only entered here, but I believe this.module (which IS undefined) should be this.model, which you also must be sure to pass in when you instantiate your view, of course, as so:
myView = new BBView({model: myModel});
then you can say this.model.bind('change', this.render); or this.model.on('change', this.render); for the most recent version of Backbone
I frequently bind my views to change events on my models in this way.
As for extending Backbone.Events, the way I have used it is to create an independent "event aggregator" that will help connect events between different views on your page. Let's say for example you have some action on one view that needs to set off an action on another view. In this case, you can pass your event aggregator object as an option to each of your views when you instantiate them, so that they can both trigger events or bind to events on a common object (i.e. your event aggregator).
whatsUp = _.extend({}, Backbone.Events) // the aggregator
myFirstView = new FirstBBView ({whatsUp: whatsUp});
(the aggregator shows up as this.options.whatsUp inside the view)
mySecondView = new SecondBBView2 ({whatsUp: whatsUp});
inside FirstBBView:
this.options.whatsUp.bind('specialEvent', function(arg1,arg2) {
// do stuff
});
inside SecondBBView, when something important happens:
this.options.whatsUp.trigger('specialEvent', {arg1: 'some data', arg2: 'more data'});
For a great explanation, see this great article by Derick Bailey
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.