Right way to fetch and retrieve data in Backbone.js - javascript

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...
});
}
});

Related

Backbone.js dynamic URL problems

I have this Backbone.js model and view code, where I am trying to get a value from a text field, and fetch data from REST api based on this value. I am having problems modifying the base URL.
Model with base URL:
var TodoItem = Backbone.Model.extend({
urlRoot : 'http://localhost/Codeigniter/index.php/testcontroller',
initialize: function(){
this.set('id', 1);
},
defaults: {
name: '',
age: ''
}
});
var todoitem = new TodoItem({name: "name"});
Function where I am setting new URL:
getUrl: function(celebname){
var urlstr = "http://localhost/Codeigniter/index.php/testcontroller/getdatabasedata?searchvalue="+celebname;
return urlstr;
},
Function that fetches data from the REST api.
getdata: function (event) {
var celebname = $('#celebname').val();
this.model.set({name: celebname});
this.model.save({}, { urlRoot: this.getUrl(celebname)});
this.model.fetch();
},
At the moment I am getting this error:
GET http://localhost/Codeigniter/index.php/testcontroller/1
I cannot change the base url using the getURL function to search for the value from input field.Instead is using the base url and the id at the end.
If I am not setting out the ID in the initialize function of the model, then I get this error:
POST http://localhost/Codeigniter/index.php/testcontroller/getdatabasedata?searchvalue=Rome
From what I have read online this is because there is no id assigned to the model.
How can I get the input field value, build the URL, and fetch data using the GET method?
Thank you
Backbone works incredibly well with true RESTful APIs. Part of the issue here is that the API you have does not really map well with the Backbone model i.e. it's not exactly RESTful. As a result, you're going to have to resort to "hacks" to have the client and server get along. Dynamically modifying the route of a model is an example of one such hack.
What might help get you better answers is if you could elaborate a bit more on the use-case you have in mind.
From what I can tell, you're not really trying to persist a TodoItem. Rather, you're trying to pre-populate it with some base data. If this is true, then you really should not be doing a save -- you should just be doing a fetch.
getdata: function (event) {
var celebname = $('#celebname').val();
var id = this.model.id;
this.model.id = 'getdatabasedata';
this.model.fetch({data: {searchvalue: celebname}});
this.model.id = id;
}
Passing the data option will tell jQuery to use it as a query string param.
Again, this is very hacky and I would not recommend it, but it will accomplish what you're trying to do.

Backbone collection fetch imported incorrectly

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' });
}
});

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().

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.

How to send information to a newly instantiated Backbone collection?

I have an API that filters a set of objects based on the ID present in the URL, like so:
http://localhost:8000/api/v1/goal_update/?goal__id=12&format=json
I have a collection that does a GET request on the above URL. Here is the code for the collection:
var GoalUpdateList = Backbone.Collection.extend({
// Reference the Goal Update model
model: GoalUpdate,
// Do HTTP requests on this endpoint
url: function() {
return "http://localhost:8000/api/v1/goal_update/?goal__id=" + this.goal_id + "&format=json";
},
// Set the goal ID that the goal update list corresponds to
initialize: function(goal_id) {
this.goal_id = goal_id;
},
});
I want to pass in the ID when I create a new instance of the collection, so I created a view that has this code:
this.collection = new GoalUpdateList(this.model.get("id"));
It thinks that I am passing in model parameters, though. I am trying to pass in information that tells the collection what URL to use. So it runs the ID through a validate function and messes up the rest of the backbone code.
Backbone's collections expect model data as the first parameter, as you've already noted. But you can specify a second parameter as an object literal, like other Backbone objects. Just pass null or undefined as the first parameter, and then in your collection initialize method, get the options as the second parameter.
GoalUpdateList = Backbone.Collection.extend({
initialize: function(data, options){
this.goal_id = options.goal_id;
}
});
new GoalUpdateList(null, model.get("goal_id"));

Categories

Resources