Adding object to a Collection in the first position with backbone? - backbone.js

I have this simple function to add a Folder to a collection called 'Folders'.
folderAdded: function(folder) {
this.folders.add(folder);
this.$el.empty();
this.render();
}
By default this will add the object in the first position, so it will appear the first one. As my query is order by created, if I refresh the page, the object will go the first place.
How can I add the object at the beginning of the collection?

You can use unshift method of the collection.
Add a model at the beginning of a collection. Takes the same options as add

Related

Restangular how to update item in collection

I am currently fiddeling around with restangular and ui-router.
I do resolve a collection of items in my router which makes it available to the underlying controllers. I have a list of todos and i want to edit a todo. So i load a view where i can edit the item.
I get the model by $scope.todo = todos.get(id) I make a change to it and then i do $scope.todo.save() which updates the model on the server. But now i have the old item still in the collection of todos.
I want my collection to reflect the changes in the single item. I could delete the item from the collection and reinsert it afterwards, but this seems a little bit too complicated. Is there no easy way to update a model within a collection?
Update: Adding some Code
Note: The todos property gets resolved if the state is called.
If i edit a single todo i resolve it by
resolve : {
todo : function($stateParams, todos) {
return todos.get($stateParams.id);
}
}
I do some changes and then i call todo.save(). No changes will happen on the collection this way. I tried to do a todos.patch(todo) but that actually did a request to weird url and i guess it is intended to patch the whole collection (?)
I am sure there is a way to change a model within a collection, but i dont know how
After trying some stuff i ended up with replacing the item inside the collection. I created a little helper to lodash which i want to show here:
var replaceItemById = function(list, element) {
var index = _.indexOf(list, _.find(list, { id : element.id }));
list.splice(index, 1 , element);
return list;
};
_.mixin({'replaceItemById' : replaceItemById});
When i want to update a model inside a collection i do step by step:
Fetch the collection
Get a single item from the collection and edit it
Call save on the item
//The server returns the updated model
todo.save().then(function(editedTodo) {
_.replaceItemById(todos, editedTodo);
$state.go('todos.index');
});
This way i do not need to fetch the collection again (even if in most cases this is what you would do) and it is up to date after updating a single item.

Prevent itemView from being added to CompositeView after collection "add"

I have a problem with Backbone and Marionette. I have a CompositeView with a collection where people can a comment, this all works nicely, the comment is added and saved to the server but I don't want the view to update and to show the newly added comment. I have tried this:
App.Views.CommentsView = Backbone.Marionette.CompositeView.extend({
template: '#article-comment-container',
itemViewContainer: 'ul',
itemView: App.Views.CommentView,
collectionEvents: {
"add": "modelAdded"
},
modelAdded: function(){
console.log('Please do nothing!');
}
});
But the item is still rendered into the page on top of my modelAdded function being called. Can I prevent that from happening at some point?
In a different scenario I would like new items to be added to the top of the list and not the bottom. Do I have to override the entire appendHtml method achieve this?
Setting the collection event add simply adds another handler to the queue for that event; it doesn't replace any other events so the default marionette behaviour will still occur.
I assume you're calling the create method on the collection to create your new comment model. If this is the case you simply need to set the silent option to true. Now the add event will not fire and Marionette will not create and render the view for that model. You can do it like this:
commentCollection.create(commentModel, {silent: true});
As for you second question about prepending, yes I would override appendHtml method. Or to keep the method names consistent with what actually happens, create a method called prependHtml and then override the renderItemView method to call prependHtml.

Collection create function firing add event too quickly

(Using Backbone 0.9.10)
I have a list view of sorts where the user can click a button to show a modal view. The list view has a counter that shows the amount of items contained in the list. Clicking a button in the modal view executes create on a collection that is passed into both views. This fires the add event in the list view, which in turn runs this function:
renderCount: function () {
// Container for model count
var $num = this.$el.find('.num');
if ($num.length > 0) {
// 'count' is a value returned from a service that
// always contains the total amount of models in the
// collection. This is necessary as I use a form of
// pagination. It's essentially the same as
// this.collection.length had it returned all models
// at once.
$num.html(this.collection.count);
}
}
However, add seems to be fired immediately (as it should be, according to the docs), before the collection has a chance to update the model count. I looked into sync but it didn't seem to do much of anything.
How can I make sure the collection is updated before calling renderCount?
Here's the list view initialize function, for reference:
initialize: function (options) {
this.collection = options.collection;
this.listenTo(this.collection, 'add remove reset', this.renderCount);
this.render();
}
EDIT:
Turns out I was forgetting to refetch the collection on success in the modal view.
$num.html(this.collection.count);
shoule be:
$num.html(this.collection.size());
Backbone.Collection uses methods imported from underscore, here is list: http://backbonejs.org/#Collection-Underscore-Methods
Turns out I was forgetting to refetch the collection on success in the modal view.

How to update model in collection?

I have the following Backbone.js collection:
var Tags = Backbone.Collection.extend({
url: "/api/v1/tags/"
}),
How do I update one of the models in the collection so that it posts to /api/v1/tags/id and saves the data for that model.
So if I change name of model with id 2 in the collection
It should PUT to
/api/v1/tags/2 with the following data:
name: new name id: 2
I've also recently wanted to update particular model in the collection. The problem was that if I did use just model.save it didn't update the collection. The goal was to change the model in collection, change it on the server, update collection accordingly and not use the sync method. So for example I have my variable collection and I want to change the model with id = 2. So the first thing, I will create an instance model, like this: var model = collection.get(2)Then I will update the attributes on this particular model:model.set({name: 'new name'})Then I will save it to the server:model.save({}, {url:'/api/v1/tags/'+model.get('id')})Then we have to update collection accordingly to the changes:collection.set({model},{remove: false})set method - it's a 'smart' update of the collection with the list of the models you passed in parameters. remove: false parameter - it's a restriction for a collection to remove existing models in collection. More here.
The first thing you can miss is that in your corresponding Tag model you'll need to set "urlRoot" to match the Collection's "url". Otherwise it doesn't know about the collection at all:
var Tag = Backbone.Model.extend({
urlRoot: "/api/v1/tags"
});
var Tags = Backbone.Collection.Extend({
model: Tag,
url: "/api/v1/tags"
});
This is useful if you want to save the tag separately:
var tag = collection.get(2);
tag.set({key: "something"});
tag.save(); // model.save works because you set "urlRoot"
On the collection, "create()" is also "update()" if id is not null. That's not confusing. :) Therefore, this is pretty much equivalent to the previous sample:
collection.create({id: 2; key: "something"});
This will update the existing tag with id=2 and then trigger a PUT.
This is an ancient question; answering because I was searching for the same answer--you've probably long since solved this problem and moved on. :)
You can pass variables to the save method. It accepts all the options which jQuery's ajax method uses (unless you overrided Backbone.Sync)
You could do something like:
model.save( { name:'new name' } );
The id and PUT method will automatically be added by Backbone for you.

Can Backbone render a collection in reverse order?

I'm using a Signalr hub to subscribe to events on the server. What an event is dispatched to a hub, its successfully adding the item to a Marionette CollectionView. This, in turn, is rendered to a table.
Because the table of events is essentially a blotter, I'd like the events in reverse order and preferably only keep n-number of events.
Can Backbone 'automatically' re-render a collection in reverse order?
To go through collection in the reverse order I usually use a construction like this:
_.each(collection.last(collection.length).reverse(), function(model){ });
There is a thread on this topic at https://github.com/marionettejs/backbone.marionette/issues/78
Although Backbone keeps the collection sorted once you define a comparator, as #breischl pointed out, Marionette does not automatically re-render the CollectionView when order changes. In fact, Marionette listens to the add event on the collection and appends a new ItemView.
If you want your CollectionView to always display items in reverse chronological order, and you want new items added to be prepended instead of appended, then override the appendHtml method in your CollectionView as follows:
var MyCollectionView = Backbone.Marionette.CollectionView.extend({
appendHtml: function(collectionView, itemView){
collectionView.$el.prepend(itemView.el);
}
});
If you want to be able to insert at a particular location as #dira mentioned in the comment, there is a solution posted at the link above on github by sberryman that I reproduce here for convenience (disclaimer: I haven't tested the code below personally):
Change appendHtml to:
appendHtml: function(collectionView, itemView) {
var itemIndex;
itemIndex = collectionView.collection.indexOf(itemView.model);
return collectionView.$el.insertAt(itemIndex, itemView.$el);
}
And add the following to extend jQuery to provide insertAt function:
(function($) {
return jQuery.fn.insertAt = function(index, element) {
var lastIndex;
if (index <= 0) return this.prepend(element);
lastIndex = this.children().size();
if (index >= lastIndex) return this.append(element);
return $(this.children()[index - 1]).after(element);
};
})(jQuery);
Usually you'll have the rendering take place in your Backbone.View 'subclass'. So you have something like:
render: function() {
this.collection.each( function(model) {
// some rendering of each element
}, this );
}
this.collection is presumably a Backbone.Collection subclass, and so you can just use underscore.js methods on it to get it in whatever order you like:
this.collection.reverse().each( ... )
this.collection.sort( function(m) { ... } ).each( ... )
Etc.
Of course, you are getting a single element from your backend, and you want to insert it in the right place without re-rendering the whole thing! So in that case just go old school and insert your sort key as a rel attribute or data attribute on the elements, and use that to insertAfter or similar with jQuery in your renderNewItem (or similar) method.
Backbone automatically keeps Collections in sorted order. If you want to use a non-default sort, define a comparator() function on your Collection and it will use that instead. The comparator can take either one or two arguments, see the Backbone documentation for details.
You can then render your collection in an .each() loop, and it will come out in the correct order. Adding new items to the view in sorted order is up to you, though.
From what you describe, you don't need to re-render the collection in reverse order. Just add an event for add on your collection in that view and have it call a function that renders the item just added and prepends it to the table.
this.collection.on('add', this.addItem);
You can reverse your models in a collection like so...
this.collection.models = this.collection.models.reverse()
If you use lodash instead of underscore you can also do this:
_(view.collection.models).reverse();
As the BackBone doesn't support reverse iteration of the collection (and it's just waste of resources to reverse or worse sort the collection) the easiest and fastest approach is to use the for loop with decreasing index over models in the collection.
for (var i = collection.length - 1; i >= 0; i--) {
var model = collection.models[i];
// your logic
}
It's not that elegant as sorting or reversing the collection using Underscore but the performace is much better. Try to compare different loops here just to know what costs you to write foreach instead of classic for.

Resources