can't fetch properly collection using backbone.js - javascript

Please read till the end (i make a reference to console.log at the end)
the model:
// Spot model:
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone){
var Spot = Backbone.Model.extend({
url: function(){
if (this.get('id') !== null){
return '/spot/' + this.get('id');
}
return '/spots';
},
idAttribute: "id",
defaults: {
id: null,
details:{
name: "school",
type: 4,
rank: 3,
comment: null
},
location:{
address: '',
lat: 70.345,
lng: 90.123
},
user:{
id: 12345,
name: "magneto"
}
},
parse: function(response){
/* var attrs = {
details: {},
location: {},
user: {}
};
attrs.id = response.id;
attrs.details.name = response.details.name;
attrs.details.type = response.details.type;
attrs.details.rank = response.details.rank;
attrs.location.lat = response.location.lat;
attrs.location.lng = response.location.lng;
return attrs; */
}
});
return Spot;
});
the collection:
// Spots collection:
define([
'jquery',
'underscore',
'backbone',
'models/Spot'
], function($, _, Backbone,spot_model){
var Spots = Backbone.Collection.extend({
model: spot_model,
url:
function() {
return '/projects/FE/spots';
},
parse: function(response){
/* var parsed = [],
key;
_.each(response, function (value) {
parsed.push(value);
}, this);
return parsed;*/
},
comparator: function(spot){
return spot.rank;
}
});
return Spots;
});
the view:
// inside our view named: SpotsView
define([
'jquery',
'underscore',
'backbone',
'mustache',
'icanhaz',
'views/spots/Spot',
'collections/Spots',
'text!../../../../templates/spots/spots.mustache!strip',
], function($,
_,
Backbone,
mustache,
ich,
SpotView,
Spots,
SpotsTemplate){
var SpotsView = Backbone.View.extend({
//el: $("#container"),
tagName: "ul",
initialize: function(){
console.log('initialize view');
console.log(this.collection);
_.bindAll(this,'render');
},
render: function(){
console.log(this.collection);
console.log('length: ' + this.collection.length);
console.log('models: ' + this.collection.models);
_.each(this.collection.models, function (spot) {
$(this.el).append(new SpotView({model:spot}).render().el);
}, this);
return this;
}
});
return SpotsView;
});
inside our app.js
var spots = new Spots({model: Spot}),
spotsview;
spots.reset();
console.log(spots.fetch());
console.log('spots:');
console.log(spots.length);
console.log(spots);
spotsview = new SpotsView({collection: spots});
Server outputs
// SERVER output(s) i tried:
{"id": 666,"details":{"name": "mark","type":4,"rank":3.1,"comment":"nothing"},"location":{"lat":70.123,"lng":90.321,"address":"5th avenue"},"user":{"id":333,"name":"wolverine"}}
// another one i tried:
[{"id": 666,"details":{"name": "mark","type":4,"rank":3.1,"comment":"nothing"},"location":{"lat":70.123,"lng":90.321,"address":"5th avenue"},"user":{"id":333,"name":"wolverine"}}]
// another one:
[{"id":"55","details":{"name":"Dan","type":"6","rank":"-9.9","comment":"myEx"},"location":{"lat":"40.780346","lng":"-73.957657","address":"78, West 86th Stree"}},{"id":"57","details":{"name":"Ron","type":"6","rank":"3.0","comment":"Butch"},"location":{"lat":"40.715569","lng":"-73.832428","address":"1344 Queens Boulevard"}},{"id":"58","details":{"name":"Monk's","type":"11","rank":"9.5","comment":"yesss"},"location":{"lat":"40.805443","lng":"-73.965561","address":"112th and broadway "}}]
// without using "parse" method in the collection and in the model (they are commented) i get:
d
_byCid: Object
_byId: Object
length: 0
models: Array[0]
__proto__: x
// when not commented (parse in both collection and model) i get:
_byCid: Object
_byId: Object
length: 6
models: Array[6]
__proto__: x
// despite it says 6, in the view you can see there are lines:
//console.log(this.collection); <--- returns the abovee
//console.log('length: ' + this.collection.length); <-- returns 0
//console.log('models: ' + this.collection.models); <--- none
I also tried removing all properties defenitions from the model. Still didn't work.
The return value content type is: application/json (verified) and is valid json.
I've read:
Backbonejs collection length always zero
but inspite of console.log , showing 0 length , also:
for(var i=0; i< this.collection.length; i++){
console.log(this.collection.get(i));
}
doens't work!
also i've read
Did backbone collection automatically parse the loaded data
Thanks a lot
Update:
I've probably solved it: i've removed even the "parse" declarations from the model and the collection and It worked: length: 6 SpotsView.js models: [object Object],[object Object],[object Object],[object Object],[object Object],[object Object] Either way i would like to know the proper use , if i did right and HOW to use PARSE in both situations the right way (collection and model wise), what do you return in both (!). Tnx

Collection.fetch is an asynchronous operation, therefore when you fetch a collection, the ajax call will be sent and then your code will continue being run just like nothing happened.
spots.reset(); // emptied your collections
console.log(spots.fetch()); // fetch your collection (why is this inside a console.log???)
console.log('spots:'); // log some text, this will be executed right after your fetch call has been made
console.log(spots.length); // here the fetch call probably hasn't returned yet, so returns 0
console.log(spots); // same as above, so returns an empty collection
spotsview = new SpotsView({collection: spots}); // the collection might or might not get populated by the time you get to rendering so there is probably variation from program run to another
So how to fix this? In the view bind the render function to the collection's reset event (launched after each successful fetch or forced reset). This way the view will render only when it has something to show.
// view's initialize method
initialize: function() {
...
this.collection.on('reset', this.render);
...
}

Related

Backbone app: Can't get model data in View

I'm building my first, relatively simple Backbone app. Right now, it's fetching JSON of local weather information:
Weather = Backbone.Model.extend({
url: '/api/v1/weather',
initialize: function(){
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
},
parse: function(response)
{
return response;
},
fetchSuccess: function (model, response) {
console.log('FETCH SUCCESS:', response);
},
fetchError: function (model, response) {
throw new Error("FETCH ERROR");
}
});
The above seems to work just fine, as the fetchSuccess console log returns the JSON response as expected.
The problem happens when I attempt to access this data from the view. Here's my code for that:
WeatherView = Backbone.View.extend({
el: $('#widget__weather'),
initialize: function()
{
_.bindAll(this, 'render');
this.model = new Weather();
this.model.bind('reset', this.render);
this.render();
},
render: function()
{
console.log(this.model.toJSON());
// var template = _.template(weatherTemplate, { weather : this.model });
// this.$el.html(template);
}
});
The console log for the view is an empty Object { }. My attempts to use this.model.get('timezone') result in undefined.
When I console.log(this.model) I get this:
s {cid: "c3", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
_changing: false
_events: Object
_pending: false
_previousAttributes: Object
attributes: Object
currently: Object
daily: Object
flags: Object
headers: Array[13]
hourly: Object
minutely: Object
offset: -4
timezone: "America/New_York"
__proto__: Object
changed: Object
cid: "c3"
__proto__: n
It seems that my JSON data is in the 'attributes' object of the model, but I don't know why it's there or how to access it.
To be clear, when I do console.log(this.model.toJSON()); I get an empty Object { }.
I feel like I'm missing something obvious here and could use any and all help. What am I doing wrong? Is it possible that the structure of the returned JSON data may be causing this? It's a pretty standard response from Forecast.io API.
Let me know if you need any more code / information.
EDIT:
With some help I fixed the issue. Turns out this was the culprit: this.model.bind('reset', this.render);. Changing that to this.model.bind('change', this.render); fixed the problem.
yes is correct.
If you write this:
console.log(this.model);
Uou get the instance of the model, every property that you have parsed are inside attributes.
Instead if you use
console.log(this.model.toJSON());
you transform the instance of the model to a json and you can get directly your property without passing by attributes
Then if you wanna print your data inside your template you need to do this:
if you use:
var template = _.template(weatherTemplate, { weather : this.model });
inside template you need to print your var in this way:
weather.attributes.timezone;
Instead if you use
var template = _.template(weatherTemplate, { weather : this.model.toJSON() });
inside template you need to print your var in this way:
weather.timezone;

TypeError trying to set model object in Backbone.js

I've been playing around with the backbone.js + cordova + require.js frameworks, based mainly off of Cristophe Coenraets' PhoneGap examples on GitHub. Displaying my model in a view seems to be straightforward, but I'm still unable to update the model via calls to set or save.
My model looks something like this:
SourcePhrase = Backbone.Model.extend({
// default values
defaults: {
id: null,
markers: "",
orig: null,
source: "",
target: ""
},
sync: function (method, model, options) {
if (method === "read") {
findById(this.id).done(function (data) {
options.success(data);
});
}
}
}),
// etc
I can pull objects out of my collection by making a call to get:
// find and update the model object
var strID = $(event.currentTarget.parentElement).attr('id');
var model = this.collection.get(strID);
So far, so good:
model.set('target', trimmedValue);
TypeError: 'undefined' is not a function (evaluating '(i=t[r]).callback.call(i.ctx,n,a)')
Hmm...that's not right. Any idea where I need to start looking to track this down?
EDIT: console output for model just before the call to set:
model: Object
_changing: false
_events: Object
_pending: false
_previousAttributes: Object
attributes: Object
id: "RUT001-10"
markers: "\hdr"
orig: null
source: "Ruth"
target: "Ruth"
__proto__: Object
changed: Object
cid: "c15"
collection: Object
id: "RUT001-10"
__proto__: Object
strID: "RUT001-10"
Yes it's the right method to use, and you can even change your code like this :
this.model.bind('change', this.render, this);
and it will work.
Ok, I think I might have tracked it down, maybe? I had in my View for a single item:
initialize: function () {
this.model.bind('change', this.render());
this.render();
},
The bind() call was causing the TypeError, which means I might have been running into a "this" issue? (backbone.js and binding "this".) At any rate, I've replaced the block with this one:
initialize: function () {
this.listenTo(this.model, 'change', this.render);
},
It seems to do the trick. If someone with more backbone.js expertise could comment on this approach, I'd very much appreciate it. Am I doing this correctly?

Calling Model from Collection

Collection
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone){
console.log("Loaded");
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://domain.com/api/jobs?page='+this.page+''
},
page: 1
});
return Jobs;
});
Model
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobFilterModel = Backbone.Model.extend({
defaults: {
T: '1',
PT: '1',
C: '1',
I: '1'
}
});
// Return the model for the module
return JobFilterModel;
});
In one of my view , i SET the models
var jobListFilterModelUpdate = new JobListFilterModel();
jobListFilterModelUpdate.set({value:isChecked});
I'm trying to retrieve the MODEL from Collection so i can send the correct QUERY with the URL.
Question 1
How do i retrieve from Model from Collection
Question 2
Will the retrieved collection be the updated Model with the data i Set in View?
When you declare a Collection you need to specify a model property, something like :
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobFilterModel
});
After creating a new model you need to add it to collection (assuming you have jobsCollection created):
var jobListFilterModelUpdate = new JobListFilterModel();
jobListFilterModelUpdate.set({value:isChecked});
jobsCollection.add(jobListFilterModelUpdate);
Answer 1
You can retrieve a model from a collection based on id, collection.get(id). Here JobFilterModel doesn't seem to have any id (you can set idAttribute property of Model to create custom id property). Backbone also creates unique ids at client side but I don't know how would they be of any help to you. If you want to retrieve a model based on any of model's property you can use collection.findWhere() or collection.where().
Answer 2
Yes. It will be but it depends on how link your View to Collection.

Backbone.js render collection in View, but can't access data

I have a Backbone Collection that represent a json objects and I'm trying to render this objects in the View that would iterate them into template. I think the breakpoint was that i can't read the json array into array
The collection return array
{
"calls": [
{
"callId": "173",
"company": "Company 1",
"status": "open",
"DueDate": "2013-06-10 00:00:00",
"EmployeeId": "12"
},
{
"callId": "170",
"company": "company 2",
"status": "done",
"DueDate": "2013-05-27 14:27:37",
"EmployeeId": "11"
},
{
"callId": "169",
"company": "Company 3",
"status": "open",
"DueDate": "2013-05-20 00:00:00",
"EmployeeId": "11"
}
]
}
Route
// Filename: router.js
define([
'jquery',
'underscore',
'backbone',
'app/collections/schedule',
'app/views/schedule',
'app/views/dashboard'
], function($, _, Backbone, ScheduleCollection, ScheduleView, DashboardView) {
var AppRouter = Backbone.Router.extend({
routes: {
// Define some URL routes
'dash': 'defaultRoute',
'schedule': 'scheduleRoute',
'accounts': 'accountsRoute',
'reports': 'reportsRoute',
// Default
'*actions': 'defaultRoute'
},
scheduleRoute: function() {
// Create a new instance of the collection
// You need to set the url in the collection as well to hit the server
var schedulecollection = new ScheduleCollection();
// Pass in the collection as the view expects it
var scheduleview = new ScheduleView({
collection: schedulecollection
});
//scheduleview.initialize();
// No need of calling render here
// as the render is hooked up to the reset event on collection
},
defaultRoute: function(actions) {
// We have no matching route, lets display the home page
DashboardView.render();
}
});
var initialize = function() {
var app_router = new AppRouter;
Backbone.history.start();
};
return {
initialize: initialize
};
});
Collection
// Filename: collections/schedule
define([
'jquery',
'underscore',
'backbone',
'app/models/schedule'
], function ($, _, Backbone, ScheduleModel) {
var ScheduleCollection = Backbone.Collection.extend({
model: ScheduleModel,
url: "http://sam-client:8888/sam-client/i/schedule",
initialize: function () {
//console.log('schedule collections loaded successfully');
}
});
return ScheduleCollection;
});
View
// Filename: views/schedule
define([
'jquery',
'underscore',
'backbone',
'text!templates/schedule.html'
], function ($, _, Backbone, ScheduleTemplate) {
var scheduleView = Backbone.View.extend({
el: $("#test"),
initialize: function () {
// Listen to the reset event which would call render
this.listenTo(this.collection, 'reset', this.render());
// Fetch the collection that will populate the collection
// with the response
this.collection.fetch();
},
render: function () {
var data = {
schedule: this.collection.models,
_: _
};
var compiledTemplate = _.template(ScheduleTemplate, data);
this.$el.html(compiledTemplate);
}
});
return scheduleView;
});
in The template
<ul>
<% _.each(schedule, function(call){ %>
<li><%= call.get("company") %> - <%= call.get("DueDate") %></li>
<% }); %>
</ul>
The problem is there is no data passed in the template in order to iterate if it
The behavior of Collection#fetch changed in Backbone 1.0:
fetch collection.fetch([options])
[...] When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, in which case the collection will be (efficiently) reset.
So just this:
this.collection.fetch()
will trigger 'add', 'remove', and 'change' events in Backbone 1.0+ rather than a single 'reset' event like older versions of Backbone. If you say:
this.collection.fetch({ reset: true })
then you'll get your 'reset' event.
The next problem is your JSON format. Backbone collections expect the incoming data to be a simple array of model attribute objects but your JSON has the array nested inside "calls". You can provide a parse method in your collection to unwrap things:
parse: function(response) {
return response.calls;
}
You'd want that method inside your ScheduleCollection.

Backbone.js collection fetch, can't retrieve items

I'm new to Backbone.js, I'm following a tutorial trying to adapt it to my needs.
I'm calling the fetch method from the main app view in order to retrieve multiple objects to be inserted in the collection.
I can see in chrome that the json data are returned, the fetch functions returns success but the collection is not populated.
I'm using IcanHaz for rendering. It prints out only the default model as I defined it in the Job model.
var Job = Backbone.Model.extend({
defaults: {
title: 'Not specified',
status: 0,
created_at: 'Not specified'
}
});
var JobView = Backbone.View.extend({
render: function () {
// template with ICanHaz.js (ich)
this.el = ich.jobRowTpl(this.model.toJSON());
return this;
}
});
// define the collection of jobs
var JobCollection = Backbone.Collection.extend({
model: Job,
url: '/api/1.0/jobs/'
});
// main app, the collection view
var AppView = Backbone.View.extend({
tagName: 'tbody',
initialize: function() {
this.jobs = new JobCollection();
this.jobs.on('all', this.render, this);
this.jobs.fetch({
error: function () {
console.log("error!!");
},
success: function () {
console.log("no error");
}
}).complete(function () {
console.log('done');
console.log('length1:' + this.jobs.length);
});
console.log('length2: '+ this.jobs.length);
},
render: function () {
this.jobs.each(function (job) {
var tmpjob = new JobView({model: job}).render().el;
$(this.el).append(tmpjob);
console.log('job': + this.tmpjob);
}, this);
return this;
}
});
var app = new AppView();
$('#app').append(app.render().el);
In the Chrome console I get this:
length2:0
job:undefined
no error
done
Uncaught TypeError: Cannot read property 'length' of undefined //referring to the lenght1 logging
These are the data that I fetch, from the chrome inspector under network/xhr/response:
{"count": 2, "next": null, "previous": null, "results": [{"id": 1, "title": "testAlbum", "status": 0, "created_at": "2012-12-31"}, {"id": 2, "title": "testAlbum2", "status": 0, "created_at": "2012-12-31"}]}
I don't understand why the 'jobs' collection exists after calling the fetch method but it is undefined inside the fetch block when the 'success' helper has been called.
And why the collection is not being populated despite it returns success and the json data are returned from the server?
I'm quite lost.
Add a parse method to your collection that just returns the results arrays. A collection needs to be an array of models, not your whole JSON response.
The Backbone docs explain how to use parse.

Categories

Resources