Hello I have some functionality in my backbone application, that allows the user to make a selection via a select menu and that choice fires a get request, the response is added to a collection,
this.archived.fetch({
success:function() {
this.archived.each(function(model){
if(model.get('organisation_id') == $('.js-filter-by-organisation').val()){
model.set('visible', true);
} else if($('.js-filter-by-organisation').val() == "all") {
model.set('visible', true);
}
this.collection.add(model);
});
}
});
My problem is that when I then try and use the models added to the collection some of the attributes are the wrong type. For example in an archived model I have an attribute called organisations this should be a model, but when I run the fetch and then look at the results in the collection organisations is an object, however on my model I set this attribute to be an model on initialization.
Am I doing something wrong when I fetch from server, and add the results to my collection?
If you're using plain Backbone, you need to implement the 'parse( )' function in your Model on which you invoke fetch.
Per definition, this function is used to parse the data returned from your back-end after invoking fetch() or save().
See the docs for more details
If you're willing to try out some Backbone plugins that could take this burden in charge, I suggest you take a look at Backbone Relational.
Related
I have a Meteor method that returns all user accounts on my application
returnUsers: function(){
return Meteor.users.find().fetch();
}
I'm using new ReactiveVar to pass the return value of the Meteor method into my template helper:
Template.listViewTemplate.created = function (){
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from serv er...");
Meteor.call('returnUsers', function (err, users) {
if (err)
console.log(err);
else
self.myAsyncValue.set(users);
});
}
Template.listViewTemplate.helpers({
userCollection: function(){
return Template.instance().myAsyncValue.get();
}
});
But when I go to render the users into the view, I get a console error that reads
{{#each}} currently only accepts arrays
When I render without the #each iterator, using
<ul id='usersList'>
{{userCollection}}
</ul>
the output on my web-page accurately reflects the number of users (2), but reads
[object Object],[object Object]
I'm pretty sure that there is some funkiness going on here because I'm using a global Meteor collection (Meteor.users.find().fetch(), as opposed to having defined my own collection), but I'm not sure how to get around it.
I want to display a list of all users so the current user can click another user and share a document with them--not sure how to get around this.
You don't need to use a reactive variable for this. The function at Template.listViewTemplate.created is not container in an autorun, which means: It won't get recomputed.
The best approach for your scenario is: Use a variable to get the status ( loading, loaded, error) and another variable to save the array itself attach to self. Reactivity is cool but you should only use it when needed.
About:
[object Object],[object Object]
This is happening because you're not extracting any value form the object provided nor looping using {{#each}}.
Your solutions for listing users is dangerous and inefficient. You're sending to the client all the fields from the user collection, including login tokens.
The best approach is to create a subscription that send only the necessaries fields like: _id, info.firstName. You should also have some criteria to the list users and use pagination. Consider also a search feature for such purpose.
ReactiveVar doesn't like arrays. You could install the ReactiveArray package which should accomplish exactly what you want.
Update
Based on comment of mper
In the latest versions of Meteor you can put an array in a ReactiveVar.
Tested on
meteor#1.6.0
reactive-var#1.0.11
I have several remarks about your question:
Do not fetch
You don't need .fetch() on your method. When you call find() on collections, such as Meteor.users a cursor is returned. The template (and #each in particular) can iterate through cursors. Cursors are usually better because you don't load the entire collection into memory at once - fetch does.
Meteor collections are reactive
Meteor collections are already reactive, meaning that if they change, they will trigger changes on your templates as well. So, you don't need to use a ReactiveVar to wrap your collection.
Query your local database
You don't need to use a method to get the users and in fact, you shouldn't, because usually you want to make queries to the database stored locally, not make calls to the server. Just call Meteor.users.find() directly in your template helper. You can (and should) control what is available locally through subscriptions.
Use #each with else
You can use the following in your template:
{{#each userCollection}}
...
{{else}}
Waiting for response from server...
{{/each}}
If userCollection is empty, the template will render the else block, just like you wanted.
Summarizing
Delete your method and onCreated with everything inside, change whatever is inside your template helper to only return Meteor.users.find() and use {{#each userCollection}}...{{else}}Waiting for response from server...{{/else}}
By the way
In the latest versions of Meteor you can put an array in a ReactiveVar.
Template.onCreated(function(){}) only gets run once and meteor methods only run once
You need reactivity here.
Collections sre reactive meaning pub/sub.
You need to create a publish function that allows certain users to fetch other users in the database. So all uses with maybe if the currentUser has permission to read all user info. Id limit the fields too.
I have a collection (an object list) in database. I can fetch it like: collectionModel.fetch()
But then user changes something on that collection. When user clickes on save button, the whole collection list must be update in database. I thought maybe i can delete() the old one first and then create() it with new one but i could'n achive it. I can't use the update() method because in this case i should find which collection elements has changed but i want to update whole list. How can i do that? Thanks for help.
Do you have a REST api in front of that database? That's how Backbone is made to work with. When your JavaScript code runs model.save(); a PUT request is made to your api for that model.
You question is about saving the whole collection, for that if you want to remain within the default implementation of Backbone you will have to go over all the models in the collection and call save for each of them.
If you want to make one single request to your server you will have to implement a custom method inside your collection. Something like:
MyCollection = Backbone.Collection.extend({
saveAll: function() {
var data = this.toJSON();
return Backbone.$.ajax({
data: { objects: data },
url: '/url/in/your/server/to/update/db'
});
}
});
That's going to send the array of all models in your collection converted to JSON to your server.
Again, you want to have a RESTful API on the server side if you want to make your life with Backbone easy.
If you want to reset collection you have to specify "reset" attribute.
collectionList.fetch({
reset: true,
...
});
But I think it's better to just update it:
collectionList.fetch({
remove: false,
update: true,
merge: true,
...
});
This is a very old question, but I had another approach so I thought I'd post it.
Sometimes my collections have a lot of data and the server doesn't get it all. I solved this by using one of the underscore methods that backbone collections have, invoke (also relies on jquery):
MyCollection = Backbone.Collection.extend({
update: function(callback) {
// Invoke the update method on all models
$.when.apply($, this.invoke('update')).then(() => {
// After complete call the callback method (if passsed)
if(callback) {
callback();
}
});
}
});
You can use it by calling collection.update() when the collection has models in it. A similar method can be used for creating or deleting collections, and this should be modifiable to catch errors but I didn't account for that.
I have a collection that can potentially contain thousands of models. I have a view that displays a table with 50 rows for each page.
Now I want to be able to cache my data so that when a user loads page 1 of the table and then clicks page 2, the data for page 1 (rows #01-50) will be cached so that when the user clicks page 1 again, backbone won't have to fetch it again.
Also, I want my collection to be able to refresh updated data from the server without performing a RESET, since RESET will delete all the models in a collection, including references of existing model that may exist in my app. Is it possible to fetch data from the server and only update or add new models if necessary by comparing the existing data and the new arriving data?
In my app, I addressed the reset question by adding a new method called fetchNew:
app.Collection = Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
// added this conditional block
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) {
collection.trigger('reset', collection, options);
}
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is pretty much identical to the standard fetch() method, except for the conditional statement checking for item existence, and using add() by default, rather than reset. Unlike simply passing {add: true} in the options argument, it allows you to retrieve sets of models that may overlap with what you already have loaded - using {add: true} will throw an error if you try to add the same model twice.
This should solve your caching problem, assuming your collection is set up so that you can pass some kind of page parameter in options to tell the server what page of options to send back. You'll probably want to add some sort of data structure within your collection to track which pages you've loaded, to avoid doing unnecessary requests, e.g.:
app.BigCollection = app.Collection.extend({
initialize: function() {
this.loadedPages = {};
},
loadPage: function(pageNumber) {
if (!this.loadedPages[pageNumber]) {
this.fetchNew({
page: pageNumber,
success: function(collection) {
collection.loadedPages[pageNumber] = true;
}
})
}
}
});
Backbone.Collection.fetch has an option {add:true} which will add models into a collection instead of replacing the contents.
myCollection.fetch({add:true})
So, in your first scenario, the items from page2 will get added to the collection.
As far as your 2nd scenario, there's currently no built in way to do that.
According to Jeremy that's something you're supposed to do in your App, and not part of Backbone.
Backbone seems to have a number of issues when being used for collaborative apps where another user might be updating models which you have client side. I get the feeling that Jeremy seems to focus on single-user applications, and the above ticket discussion exemplifies that.
In your case, the simplest way to handle your second scenario is to iterate over your collection and call fetch() on each model. But, that's not very good for performance.
For a better way to do it, I think you're going to have to override collection._add, and go down the line dalyons did on this pull request.
I managed to get update in Backbone 0.9.9 core. Check it out as it's exactly what you need http://backbonejs.org/#Collection-update.
Say I have a route setup:
'photos/:id' : 'showPhoto'
and somebody shares the url: www.mysite.com/photos/12345 with a friend.
When their friend clicks on the shared link, showPhoto gets called back with 12345 passed as the id. I cant figure out how to fetch the model from the server, because even when setting its id property and calling fetch(), backbone thinks that the model isNew and so the ajax request url is just /photos instead of /photos/12345:
showPhoto: (id) ->
photo = new models.Photo _id:id
photo.fetch #does a GET to /photos, I would have expected it to request /photos/12345
success: () ->
render photo view etc...
Photo = Backbone.Model.extend
idAttribute: '_id'
urlRoot: '/photos'
The model Photo is usually part of a collection, but in this scenario someone is visiting the site directly and only expects to see data for one photo, so the collection is not instantiated in this state of the app.
Is the solution to load the entire collection of photos and then use collection.getById(id)? This just seems way too inefficient when I just want to load the properties for one model.
if you don't have the model as part of a collection, you have to tell the model the full url manually. it won't auto-append the id to the urlRoot that you've specified. you can specify a function as the urlRoot to do this:
Photo = Backbone.Model.extend({
urlRoot: function(){
if (this.isNew()){
return "/photos";
} else {
return "/photos/" + this.id;
}
}
});
Backbone uses the id of the model to determine if it's new or not, so once you set that, this code should work correctly. if it doesn't, you could always check for an id in the if-statement instead of checking isNew.
You do not need to tell backbone whether or not to append the id the url. Per the documentation: http://backbonejs.org/#Model-fetch, you may simply set the urlRoot to the equivalent of the url in a collection.
Backbone will automatically append the desired id to the url, provided you use one of the following methods:
model.set("id", 5); //After initialized
model = new Backbone.Model({id: 5}); //New model
If you manually set the id in the attributes hash or directly on the model, backbone won't be aware of it.
model.id = 5; //Don't do this!
there's already a similar question: "How do I fetch a single model in Backbone?"
my answer there should work for you (and it's in coffeescript)
also remember to check Backbone Model#url documentation, it's all explained there
I would bootstrap the collection (by rendering the following to the page) with just one model in it like this:
photos = new PhotoCollection();
photos.reset([ #Html.ToJson(Model) ]);
Note that the server side code that I use is ASP.Net MVC so use something specific to your server side architecture. Also note that the square brackets are important as they take your singular model and wrap it in an array.
Hope that's helpful.
I've run into a headache with Backbone. I have a collection of specified records, which have subrecords, for example: surgeons have scheduled procedures, procedures have equipment, some equipment has consumable needs (gasses, liquids, etc). If I have a Backbone collection surgeons, then each surgeon has a model-- but his procedures and equipment and consumables will all be plain ol' Javascript arrays and objects after being unpacked from JSON.
I suppose I could, in the SurgeonsCollection, use the parse() to make new ProcedureCollections, and in turn make new EquipmentCollections, but after a while this is turning into a hairball. To make it sensible server-side there's a single point of contact that takes one surgeon and his stuff as a POST-- so propagating the 'set' on a ConsumableModel automagically to trigger a 'save' down the hierarchy also makes the whole hierarchical approach fuzzy.
Has anyone else encountered a problem like this? How did you solve it?
This can be helpful in you case: https://github.com/PaulUithol/Backbone-relational
You specify the relations 1:1, 1:n, n:n and it will parse the JSON accordingly. It also create a global store to keep track of all records.
So, one way I solved this problem is by doing the following:
Have all models inherit from a custom BaseModel and put the following function in BaseModel:
convertToModel: function(dataType, modelType) {
if (this.get(dataType)) {
var map = { };
map[dataType] = new modelType(this.get(dataType));
this.set(map);
}
}
Override Backbone.sync and at first let the Model serialize as it normally would:
model.set(response, { silent: true });
Then check to see if the model has an onUpdate function:
if (model.onUpdate) {
model.onUpdate();
}
Then, whenever you have a model that you want to generate submodels and subcollections, implement onUpdate in the model with something like this:
onUpdate: function() {
this.convertToModel('nameOfAttribute1', SomeCustomModel1);
this.convertToModel('nameOfAttribute2', SomeCustomModel2);
}
I would separate out the different surgeons, procedures, equipment, etc. as different resources in your web service. If you only need to update the equipment for a particular procedure, you can update that one procedure.
Also, if you didn't always need all the information, I would also lazy-load data as needed, but send down fully-populated objects where needed to increase performance.