Backbone collection fetch imported incorrectly - javascript

I have a collection which is fetched from a REST endpoint, where it receives a JSON.
So to be completely clear:
var Products = Backbone.Collection.extend({
model: Product,
url : 'restendpoint',
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var products = new Products();
products.fetch();
If I log this, then I have the data. However, the length of the object (initial) is 0, but it has 6 models. I think this difference has something to do with what is wrong, without me knowing what is actually wrong.
Now, if I try to filter this:
products.customFilter({title: "MyTitle"});
That returns 0, even though I know there is one of that specific title.
Now the funky part. If I take the ENTIRE JSON and copy it, as in literally copy/paste it into the code like this:
var TestCollection = Backbone.Collection.extend({
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var testCollectionInstance = new TestCollection(COPY PASTED HUGE JSON DATA);
testCollectionInstance.customFilter({title: "MyTitle"});
Now that returns the 1 model which I was expecting. The difference when I log the two collections can be seen below. Is there some funky behaviour in the .fetch() I am unaware of?
Edit 2: It may also be of value that using the .fetch() I have no problems actually using the models in a view. It's only the filtering part which is funky.
Edit 3: Added the view. It may very well be that I just don't get the flow yet. Basically I had it all working when I only had to fetch() the data and send it to the view, however, the fetch was hardcoded into the render function, so this.fetch({success: send to template}); This may be wrong.
What I want to do is be able to filter the collection and send ANY collection to the render method and then render the template with that collection.
var ProductList = Backbone.View.extend({
el: '#page',
render: function(){
var that = this; /* save the reference to this for use in anonymous functions */
var template = _.template($('#product-list-template').html());
that.$el.html(template({ products: products.models }));
//before the fetch() call was here and then I rendered the template, however, I needed to get it out so I can update my collection and re-render with a new one (so it's not hard-coded to fetch so to speak)
},
events: {
'keyup #search' : 'search'
},
search : function (ev){
var letters = $("#search").val();
}
});
Edit: New image added to clearify the problem

It's a bit tricky, you need to understand how the console works.
Logging objects or arrays is not like logging primitive values like strings or numbers.
When you log an object to the console, you are logging the reference to that object in memory.
In the first log that object has no models but once the models are fetched the object gets updated (not what you have previously logged!) and now that same object has 6 models. It's the same object but the console prints the current value/properties.
To answer your question, IO is asynchronous. You need to wait for that objects to be fetched from the server. That's what events are for. fetch triggers a sync event. Model emits the sync when the fetch is completed.
So:
var Products = Backbone.Collection.extend({
model: Product,
url : 'restendpoint',
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var products = new Products();
products.fetch();
console.log(products.length); // 0
products.on('sync',function(){
console.log(products.length); // 6 or whatever
products.customFilter({title: 'MyTitle'});
})

It seems like a response to your ajax request hasn't been received yet by the time you run customFilter. You should be able to use the following to ensure that the request has finished.
var that = this;
this.fetch({
success: function () {
newCollection = that.customFilter({ title: 'foo' });
}
});

Related

Querying a parse table and eagerly fetching Relations for matching

Currently, I have a table named Appointments- on appointments, I have a Relation of Clients.
In searching the parse documentation, I haven't found a ton of help on how to eagerly fetch all of the child collection of Clients when retrieving the Appointments. I have attempted a standard query, which looked like this:
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User",Parse.User.current());
query.include('Rate'); // a pointer object
query.find().then(function(appointments){
let appointmentItems =[];
for(var i=0; i < appointments.length;i++){
var appt = appointments[i];
var clientRelation = appt.relation('Client');
clientRelation.query().find().then(function(clients){
appointmentItems.push(
{
objectId: appt.id,
startDate : appt.get("Start"),
endDate: appt.get("End"),
clients: clients, //should be a Parse object collection
rate : appt.get("Rate"),
type: appt.get("Type"),
notes : appt.get("Notes"),
scheduledDate: appt.get("ScheduledDate"),
confirmed:appt.get("Confirmed"),
parseAppointment:appt
}
);//add to appointmentitems
}); //query.find
}
});
This does not return a correct Clients collection-
I then switched over to attempt to do this in cloud code- as I was assuming the issue was on my side for whatever reason, I thought I'd create a function that did the same thing, only on their server to reduce the amount of network calls.
Here is what that function was defined as:
Parse.Cloud.define("GetAllAppointmentsWithClients",function(request,response){
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User", request.user);
query.include('Rate');
query.find().then(function(appointments){
//for each appointment, get all client items
var apptItems = appointments.map(function(appointment){
var ClientRelation = appointment.get("Clients");
console.log(ClientRelation);
return {
objectId: appointment.id,
startDate : appointment.get("Start"),
endDate: appointment.get("End"),
clients: ClientRelation.query().find(),
rate : appointment.get("Rate"),
type: appointment.get("Type"),
notes : appointment.get("Notes"),
scheduledDate: appointment.get("ScheduledDate"),
confirmed:appointment.get("Confirmed"),
parseAppointment:appointment
};
});
console.log('apptItems Count is ' + apptItems.length);
response.success(apptItems);
})
});
and the resulting "Clients" returned look nothing like the actual object class:
clients: {_rejected: false, _rejectedCallbacks: [], _resolved: false, _resolvedCallbacks: []}
When I browse the data, I see the related objects just fine. The fact that Parse cannot eagerly fetch relational queries within the same call seems a bit odd coming from other data providers, but at this point I'd take the overhead of additional calls if the data was retrieved properly.
Any help would be beneficial, thank you.
Well, in your Cloud code example - ClientRelation.query().find() will return a Parse.Promise. So the output clients: {_rejected: false, _rejectedCallbacks: [], _resolved: false, _resolvedCallbacks: []} makes sense - that's what a promise looks like in console. The ClientRelation.query().find() will be an async call so your response.success(apptItems) is going to be happen before you're done anyway.
Your first example as far as I can see looks good though. What do you see as your clients response if you just output it like the following? Are you sure you're getting an array of Parse.Objects? Are you getting an empty []? (Meaning, do the objects with client relations you're querying actually have clients added?)
clientRelation.query().find().then(function(clients){
console.log(clients); // Check what you're actually getting here.
});
Also, one more helpful thing. Are you going to have more than 100 clients in any given appointment object? Parse.Relation is really meant for very large related collection of other objects. If you know that your appointments aren't going to have more than 100 (rule of thumb) related objects - a much easier way of doing this is to store your client objects in an Array within your Appointment objects.
With a Parse.Relation, you can't get around having to make that second query to get that related collection (client or cloud). But with a datatype Array you could do the following.
var query = new Parse.Query(Appointment);
query.equalTo("User", request.user);
query.include('Rate');
query.include('Clients'); // Assumes Client column is now an Array of Client Parse.Objects
query.find().then(function(appointments){
// You'll find Client Parse.Objects already nested and provided for you in the appointments.
console.log(appointments[0].get('Clients'));
});
I ended up solving this using "Promises in Series"
the final code looked something like this:
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User",Parse.User.current());
query.include('Rate');
var appointmentItems = [];
query.find().then(function(appointments){
var promise = Parse.Promise.as();
_.each(appointments,function(appointment){
promise = promise.then(function(){
var clientRelation = appointment.relation('Clients');
return clientRelation.query().find().then(function(clients){
appointmentItems.push(
{
//...object details
}
);
})
});
});
return promise;
}).then(function(result){
// return/use appointmentItems with the sub-collection of clients that were fetched within the subquery.
});
You can apparently do this in parallel, but that was really not needed for me, as the query I'm using seems to return instantaniously. I got rid of the cloud code- as it didnt seem to provide any performance boost. I will say, the fact that you cannot debug cloud code seems truly limiting and I wasted a bit of time waiting for console.log statements to show themselves on the log of the cloud code panel- overall the Parse.Promise object was the key to getting this to work properly.

Backbone/Marionette - Checking if a model exists

I have created a very basic backbone application, but I need to perform a slightly complex check which I am having trouble doing.
My code is below. What I'm doing here is creating a list of participants in a chat. In reality, I will be sending this list in through a javascript function.
Participant = Backbone.Model.extend({});
Participants = Backbone.Collection.extend({
model: Participant
});
ParticipantView = Backbone.Marionette.ItemView.extend({
template: "#participant-template",
tagName: 'div',
className: 'call-participant',
initialize: function () {
this.$el.prop("id", this.model.get("chatid") + "-" + this.model.get("participantName"));
},
});
ParticipantsView = Backbone.Marionette.CompositeView.extend({
template: "#participants-template",
tagName: 'div',
itemView: ParticipantView,
appendHtml: function(collectionView, itemView) {
collectionView.$el.append(itemView.el);
}
});
MyApp.addInitializer(function(options)) {
var participantsView = new ParticipantsView({
collection: options.participantNames
});
MyApp.participantContainer.show(participantsView);
var participantsModel = new Participants();
};
$(document).ready(function() {
MyApp.start({participantsModel: participantsModel});
})
The trouble I am having is that when people leave/join the chat, the message is being resent with a new participant list (e.g. some participantName values won't be there).
So to the question: In backbone.marionette, how and where do I tell it to compare the existing models to the new list of models (for a give chatid), and if the model is no longer in the list, remove the model, and if there's something in the list that isn't a model, add it.
You will see I am building my ID from the the chatid AND the participantName (note that the name is unique, it is actually the JID I'm sending without the #server). The reason the ID is formatted like this is that I will have around 5-10 lists on one page, so I don't want to start updating the wrong chats.
Thank you. Please ask if you require additional information.
jsFiddle: http://jsfiddle.net/966pG/175/
Kind Regards,
Gary Shergill
EDIT: I am aware of get and set, but in all honesty have no idea how to use them. I've tried reading about them "http://backbonejs.org/#Model-get".
EDIT: Live scenario below. I have a javascript function which listens for pubsub events, and when it receives the right one it creates an array of participant objects:
var participants = [];
$(iq).find('participants').each(function() {
var participantsNodes = this.childNodes;
for (var i = 0; i < participantsNodes.length; i++) {
var participantAttr = participantsNodes[i].attributes
var participant = participantAttr[0].nodeValue;
participants.push({"participantName": participant, "chatid": chatid});
}
});
var chatid = $(iq).find('chatid').text();
...
participantsModel.add(new Participants({
chatid : chatid,
participantArray : participants
}))
Based on conversation in the comments
Backbone's Collection.set function takes care of this for you. You can add a new set of participants and it will recognize which ones are new, which ones are old and will fire add and remove events appropriately.
You need to add an array of Participants though, you can't just add an array of names. You can handle that with map pretty easily though.
//array of names from sonewhere
arr = getNamesFromServer()
arrOfParticipants = arr.map(function(name) {
id = calculateChatID(name,/*whatever else you need */)
return new Participant(name:name,chatID: id);
}
participantNames.set(arrOfParticipants)
Exactly how you keep track of your participants and where you pull in updates is up to you.
Of course if you can get the information formatted into the correct format on the server, (an array of json objects matching Participant), you should just use Backbone's built in sync functions, which will handle all of this for you. Then, once you've set up a RESTful URL that matches your client side naming scheme, or overriden the url property on your model/collection, you can just call participantNames.fetch().

Right way to fetch and retrieve data in Backbone.js

I’m trying to understand how and where to use data after a fetch using Backbone.js but I’m a little confused.
I’ll explain the situation.
I have an app that, on the startup, get some data from a server. Three different kind of data.
Let’s suppose Airplanes, Bikes, Cars.
To do that, I’ve inserted inside the three collections (Airplanes, Cars, Bikes) the url where to get these data.
I’ve overwrited the parse method, so I can modify the string that I get, order it, and put it in an object and inside localstorage. I need it to be persistent because I need to use those 3 data structure.
So with a fetch i get all those data and put them inside localstorage. Is it correct doing it that way?
Now i need to make other calls to the server, like “get the nearest car”.
In the view i need to see the color, name and model of the car, all that informations are inside the object “Cars” in localstorage.
In my view “showcars.view” I just call a non-backbone js, (not a collection, model or view) where i get all the informations i need. In this js i do:
var carmodel = new Car(); //car is the backbone model of the cars
carmodel.url = '/get/nearest/car'; //that give id of the nearest car
carmodel.fetch ({
success: function () {}
//here i search the Cars object for a car with the same id
//and get name, color, model and put them in sessionstorage
})
So after that call, in the view I can get the data I need from the sessionstorage.
Is that a bad way of doing things? If so, how i should fetch and analyze those informations? I should do all the calls and operations inside the models?
Thanks
This would be the way that you might implement what you want.
var Car = Backbone.Model.extend();
var Cars = Backbone.Collection.extend({
model: Car,
url: '.../cars'
});
var NearestCar = Backbone.Model.extend({
url: '...nearest/car'
});
var cars = new Cars();
var nearestCar = new NeaerestCar();
cars.fetch({
success: function() {
nearestCar.fetch({
success: function(model) {
var oneYouWant = cars.get(model.get('id'));
// do something with your car
// e.g.:
// var carView = new CarView({model: oneYouWant});
// $('body').append(carView.render().el);
});
});
});
});
In general, Backbone keeps everything in memory (that is, the browser memory) so there is no need to save everything to local storage, as long as your Collection object is somehow reachable from the scope you are sitting in (to keep things simple let's say this is the global window scope).
So in your case I will have something like three collections:
window.Cars
window.Airplanes
window.Bikes
Now you want the nearest. Assuming you are in a Backbone View and are responding to an event, in your place I would do something like this (just shows the meaningful code):
var GeneralView = Backbone.View.extend({
events: { "click .getNearestCar": "_getNearestCar" },
_getNearestCar: function () {
$.getJson('/get/nearest/car', function (data) {
// suppose the data.id is the id of the nearest car
var nearestCar = window.Cars.get(data.id)
// do what you plase with nearestCar...
});
}
});

how to pass an id containing / in backbone.js

HI my basic model which fetches data from server is working perfect. I want to implement a search feature. When user enters any data the request goes to browser and desired model is returned.
var Book = Backbone.Model.extend({
urlRoot: '/books'
});
render: function(options) {
books = new Book({id:options.name});
books.fetch();
}
where
name = "search/"+dynamic_data;
Request URL that is being formed when i pass --> 'life' in variable dynamic_data
http://host/path/search%2Flife
Request URL that i want
http://host/path/search/life
How can I encode/escape my string to achieve the desired result. I have tried escape(), encodeURI(), encodeURIComponents
A workaround to solve this is create one more model with urlRoot as /books/search and pass just name . I don't think this is correct. Should I use this ?
According to your additionnal precisions stating that life is actually a book name...
It looks like Backbone is better integrated with RESTful API's. In REST, your urls should not contain verbs and to search books, you would do a GET /books/?name=life.
In that case, you would only have to define a Backbone.Collection like:
var BooksCollection = Backbone.Collection.extend({
model: Book,
url: '/books'
});
The to fetch books:
var books = new BooksCollection();
books.fetch({data : {name: 'life'}}); //GET /books/?name=life
If you really want your search operation to target /search/:name, you will have to check the Backbone.Collection api, but I think you will want to look at http://backbonejs.org/#Sync
You could override your collection's sync method to something like:
Backbone.Collection.extend({
...
sync: function (method, model, options) {
//for read operations, call the search url
if (method === 'read') {
options.url = '/search/' + options.data.name;
delete options.data.name;
}
//call the default sync implementation
return Backbone.Collection.prototype.sync.apply(this, arguments);
}
});
In this cased calling books.fetch({data : {name: 'life'}}); will result in GET /books/life.
Here's a fiddle that shows the example.
this would work:
books = new Book({id:options.name}, {url: options.name));
decodeURIComponent() will decode http://host/path/search%2Flife to http://host/path/search/life.

backbone.js - getting extra data along with the request

I have a collection which holds some of the users. Some information that is needed is how many total there are, how many pages, etc. How do I pass these back to the client? Or do they have to come from a separate view in which case I will need more than one ajax call? I'd like to have the collection fetch() and also receive some of this "meta data". What's a good way for handling this?
Generally, you need to handle this in the collection class' parse method. Its responsibility is to take the response and return back an array of model attributes. However, you could do more than that if you wished if you didn't mind the parse method having this additional responsibility.
UserList = Backbone.Collection.extend({
model: User,
url: '/users',
parse: function(data) {
if (!data) {
this.registered_users = 0;
return [];
}
this.registered_users = data.registered_users;
var users = _(data.users).map(
function(user_data) {
var user = {};
user['name'] = user_data.name;
return user;
}
);
return users;
    }
});
So in the trivial example above, presume the server returns a response which contains a count of registered users and and an array of user attributes. You would both parse through and return the user attributes and you would pick off the registered user count and just set it as a variable on the model.
The parse method would get called as part of a fetch. So no need to modify the fetch, just use the built-in hook method that you have.
Purists would say that you are giving the parse method a secondary responsibility which might surprise some people (e.g. returning something and modifying model state). However, I think this is okay.
One way to do this is to override the Collection::fetch() method so that it parses this metadata out of the response. You could have your backend return a response like this:
{
"collection": [
{ ... model 1 ... },
{ ... model 2 ... },
...
],
"total_rows": 98765,
"pages": 43
}
In your fetch method which overrides the original Backbone.Collection::fetch() method, you can handle each property of the object separately. Here's you could do the override with a slightly modified fetch method:
_.extend(Backbone.Collection.prototype, {
fetch : function(options) {
options || (options = {});
var collection = this;
var success = options.success;
options.success = function(resp) {
// Capture metadata
if (resp.total_rows) collection.total_rows = resp.total_rows;
if (resp.pages) collection.pages = resp.pages;
// Capture actual model data
collection[options.add ? 'add' : 'refresh'](
collection.parse(resp.collection), options);
// Call success callback if necessary
if (success) success(collection, resp);
};
options.error = wrapError(options.error, collection, options);
(this.sync || Backbone.sync).call(this, 'read', this, options);
return this;
});
Note that this approach using _.extend will affect all your classes which extend Backbone.Collection.
This way, you don't have to make 2 separate calls to the backend.
I would bootstrap the information at pagecreation. Write the information into the html document when the server creates the site. Like that you don't have to have an ajax call at all. I do that with the whole collection in ordner not to first load the page and then wait for the ajax call to return the information needed.
Code example with Python:
Line 64: https://github.com/ichbinadrian/maps/blob/master/python/main.py <- from here
Line 43: https://github.com/ichbinadrian/maps/blob/master/templates/index.html <- into here

Categories

Resources