Restangular how to update item in collection - angularjs

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.

Related

Appending new data to the Backbone Relational Relation

I'm trying to design a load more type of system where every time you press load more you add data to the existing collection. This is a rough sketch of how the UI looks.
Everything works pretty great except as you would except everytime I re-run the
items.fetch
What it does: It overrides the entire collection with the new data
What I want it to do: I want the new records returned to be added to the records collection not override old 'records'
How can I make this work so that the newly fetched records are appended to existing records and not overridden?
Add { remove: false } to your fetch call:
items.fetch({ remove: false, data: { nextId: this.info.get('nextId') } });
What's happening here is Collection#fetch will synchronize its models with the data the server returns. That includes adding new models, updating models already present, and removing models that are not included in the response.
The behavior of fetch can be customized by using the available set options. For example, to fetch a collection, getting an "add" event for every new model, and a "change" event for every changed existing model, without removing anything: collection.fetch({remove: false})
The available set options are add, remove, and merge. Setting one (or all) to false will disable that functionality in the fetch.
It sounds like you just want { remove: false }, leaving the add and merge functionality.
I'm not familiar with backbone-relational,
But with a normal collection, you can do the following in parse method:
Backbone.Collection.extend({
parse: function(response){
// you can update the info here like this.info = response.info or similar
return _.union(this.toJSON(), response.records);
}
});
Basically we combine the response with existing data and return it so that we maintain the previous data when the collection updates

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 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 model toJSON strangely cache itself

I am still new to backbone:
Here is my problem that I find hard to explain:
on initialisation there is a model something like this:
Model:
{
Id:xxx,
Questions:
[
{
Id: "yy",
Selections:
[
{OptionId:"aaa"},
...,
{OptionId:"zzz"}
]
},
....
]
}
there is a event method updates Selections Collection.
After event triggers I got two different result by with two code below:
window.pkg.Questions.get(this.Id).Selections.reset(selectedoptions);
console.log(window.pkg.Questions.get(this.Id).Selections.toJSON());
console.log(window.pkg.Questions.get(this.Id).toJSON().Selections);
The first log shows the updated model, however the later shows initial default value.
why it is working like this?
They are two different copies. Your QuestionModel has a SelectionsCollection property called Selections, and a Backbone attribute, also called Selections. I assume you want to use the SelectionsCollection, and the attribute will not stay in sync with that. The attribute is the original json which I assume was inadvertently added to the model.
Hard to say exactly how to fix it, because you did not show the code where you create and fetch these models and collections. Wherever you take the Selections JSON and and initially reset it into the collection, you can remove it from the JSON and/or unset it on the QuestionModel if it is already there....
This would also print the wrong, original data:
console.log(window.pkg.Questions.get(this.Id).get('Selections')); // print original JSON
You can remove it in QuestionModel's parse:
parse: function(data) {
// removing Selections from data here will prevent
// it from being added as an attribute.
delete data.Selections;
return data;
}
If you do this, and you want the Selections to be included in the toJSON output of QuestionModel, you would also need to override toJSON.
toJSON: function() {
// get json for Question
var json = Backbone.Model.prototype.toJSON.call(this);
// add updated json for selections
json.Selections = this.Selections.toJSON();
return json;
}

Backbone - Using fetched data inside the view

I have fatched the data to Collection variable, but I am not sure what should I do next. How to use this data and to fill the template with it? Here is my code from the render function inside the View.
Collection.url = "../data";
Collection.fetch();
var compiled = _.template(self.data);
self.$el.prepend(compiled(/*MY JSON SHOULD GO HERE*/));
I am a newbie to backbone, so every help is appreaciated.
Here is a Collection definition:
var MainCollection = Backbone.Collection.extend({
model: MainModel,
//localStorage: new Backbone.LocalStorage("kitchen"),
initialize: function (models,options) { }
}), Collection = new MainCollection;
Here is a log of Collection and Collection coverted to JSON:
Assuming Collection is your collection's name (that's pretty confusing I have to say), this is what you're looking for:
self.$el.prepend(compiled(Collection.toJSON()));
Edit:
Don't forget you're fetching the data asynchronously. So when you're evaluating your template, the data hasn't come back yet, and your collection's still empty. Listen to the end of the request ('sync' event I think) or some other events so you know when the collection's populated OR use the success option of the fetch method to specify a callback :)
As for your logs. When you log an object, it will be automatically updated until you check the details. So you logged it while it was empty, but checked it after it was populated (a few ms afterwards).

Resources