Parsing The Response From Fetch Backbone - backbone.js

I am not able to get the response string from the fetch method in my view render method.
The Collection Class Goes here.
Collection = (function(){
var Events;
Events = Backbone.Collection.extend({
url:function(){
//alert(urlOptions);
return this.urlParam;
// alert('API call :'+urlOptions);
},
initialize: function(models, options){
this.urlParam = options.urlParam || "";
},
parse: function( response ){
var parsed = Jath.parse(
[ '//notes', {
} ], response );
console.log(parsed);
return parsed;
}
});
return {
newInstance : function(models,options) { return new Events(models,options); }
};
})();
The View Goes Here
View = (function() {
'use strict';
var
htmlTemplate = _.template( $('#eventGridTemplate' ).html() ), // See templatesSearch.jsp
expanded = true, // By default the Events Grid extends to the bottom of the browser window.
BackboneView, applyStyles;
/**
* Apply CSS specific to this view
* Unfortunately, this View needs to modify its parent wrapper element.
* Otherwise the layout will break when it's resized. See templatesSearch.jsp.
* #param {Object} $elmt
* #param {Boolean} expand
*/
applyStyles = function( $elmt, expand ) {
var
top = '2px',
left = '2px',
pos = 'absolute',
right = '2px';
if ( expand ) {
$elmt.css({
"position" : pos,
"top" : top,
"left" : left,
"right" : right,
"bottom" : "2px"
});
$elmt.parent().css( 'bottom', '2px' );
} else {
$elmt.css({
"position" : pos,
"top" : top,
"left" : left,
"right" : right,
"bottom" : "50%"
});
$elmt.parent().css( 'bottom', '50%' );
}
};
// See 'http://backbonejs.org/#View-constructor' for more info
BackboneView = Backbone.View.extend({
onAiringsBtn : function( event ) {
// If the Events Grid container was expanded, contract it.
// If it was contracted, expand it.
expanded = expanded ? false : true;
applyStyles( this.$('div'), expanded );
},
initialize : function() {
this.render();
},
render : function() {
// this.$el is the jQuery version of this.el
// Which is populated by options.el
// Which is part of the options object passed into the constructor
//alert('Start Date:' +$('#datepicker').datepicker('getDate'));
var eventsCollection = Collection.newInstance([],{urlParam:'http://localhost:8080/adtglobal/2.0/api/events?startDate=2013-11-05T00:00:00-0400&endDate=2013-11-06T00:00:00-0400'});
//console.log(eventsCollection.url());
eventsCollection.fetch({
success : function(eventsCollection , response){
console.log(eventsCollection.toJSON());
alert(eventsCollection.toJSON());
}
});
this.$el.html( htmlTemplate );
applyStyles( this.$('div'), true );
}
});
//-------------------------------------------------------------------------------------------------------------------
// Public API
//-------------------------------------------------------------------------------------------------------------------
return {
newInstance : function(options) { return new BackboneView(options); }
};
})();
I get The response as success and i see the xml in the browser console, but how do i parse it here ??
The response is here
<Events xmlns="urn:api:2.0">
<PageNavigation showing="45"></PageNavigation>
<Event id="400515625" href="400515625" source="SDR">
<OrgHierarchy>
<level id="56" typeId="100" title="Soccer" description="Sport" href="../sporthierarchy?levelId=56&levelTypeId=100"></level>
<level id="2902" typeId="101" title="UEFA" description="Confederation" href="../sporthierarchy?levelId=2902&levelTypeId=101" parentId="56" parentType="100"></level>
</OrgHierarchy>
<EventType id="1">Standard Event</EventType>
<League id="1860">UEFA > Polish Orange Ekstraklasa</League>
<EventTitleText>Ruch Chorzow vs. Zawisa Bydgoszcz</EventTitleText>
<CompetitionType id="1">Team vs Team</CompetitionType>
<EventCompetitors>
<Teams>
<HomeTeam id="73960" href="../teams/73960">Ruch Chorzow</HomeTeam>
<AwayTeam id="107278" href="../teams/107278">Zawisa Bydgoszcz</AwayTeam>
</Teams>
</EventCompetitors>
</Event>
</Events>

well, it seems you are not using Jath correctly.
you need to define a template that matches your XML.
I have not used Jath before, but I will try to create a simple template for you:
//in your parse
var template = [ "//Events", {id: "#id", eventType: 'EventType'}];
return Jath.parse(template, response );
try this first and see if you have something in your collection.
it seems you have nested resources in your response, so you will have to complete the template.
or, if you don't have to use Jath, try something simpler: here's an example jsfiddle, you should be able to see the parsed JSON in the console.
http://jsfiddle.net/5j57T/
EDIT: xml to json code is from: http://davidwalsh.name/convert-xml-json
NOTE: you need to parse that json again into an array of objects you need, in order to make your app work!
=====================
EDIT: another example (works better than the previous fiddle)
http://jsfiddle.net/5j57T/1/
uses a jquery library http://www.fyneworks.com/jquery/xml-to-json/
there's only one issue, since there's only one event in your events, it cannot be parsed to an array by default, you will have to do that manually. If, there's always only one event per response, you can do something like this:
parsed = [$.xml2json(xmlDoc.getElementsByTagName('event')[0])]
or you can just do
parsed = $.map(xmlDoc.getElementsByTagName('event'), function(eventXml) {
return $.xml2json(eventXml);
});
working jsfidde: http://jsfiddle.net/5j57T/2/
==========================
EDIT:
I think by default, backbone is expecting a JSON response from the server. You will have to overwrite it's fetch() function so support XML. so in your collection definition, add this:
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
here's a working jsfiddle: http://jsfiddle.net/5j57T/4/
Ignore the 'data' and 'type' options I passed to fetch(), and the url, all of that are just to make Ajax request work on jsfiddle. You shouldn't have those in your app.
In this demo, when the XML is returned, it's parsed into the collection correctly. You should be able to see your collection in the console.
======
If all of your backend services return XML only. I'd suggest overwrite the Backbone.sync function to make it work for other methods like save().

Related

Backbone.Model: set collection as property

I'm new with backbone and faced the following problems. I'm trying to emulate some sort of "has many relation". To achieve this I'm adding following code to initialize method in the model:
defaults: {
name: '',
tags: []
},
initialize: function() {
var tags = new TagsCollection(this.get('tags'));
tags.url = this.url() + "/tags";
return this.set('tags', tags, {
silent: true
});
}
This code works great if I fetch models through collection. As I understand, first collection gets the data and after that this collection populates models with this data. But when I try to load single model I get my property being overridden with plain Javascript array.
m = new ExampleModel({id: 15})
m.fetch() // property tags get overridden after load
and response:
{
name: 'test',
tags: [
{name: 'tag1'},
{name: 'tag2'}
]
}
Anyone know how to fix this?
One more question. Is there a way to check if model is loaded or not. Yes, I know that we can add callback to the fetch method, but what about something like this model.isLoaded or model.isPending?
Thanks!
"when I try to load single model I get my property being overridden with plain Javascript array"
You can override the Model#parse method to keep your collection getting overwritten:
parse: function(attrs) {
//reset the collection property with the new
//tags you received from the server
var collection = this.get('tags');
collection.reset(attrs.tags);
//replace the raw array with the collection
attrs.tags = collection;
return attrs;
}
"Is there a way to check if model is loaded or not?"
You could compare the model to its defaults. If the model is at its default state (save for its id), it's not loaded. If it doesn't, it's loaded:
isLoaded: function() {
var defaults = _.result(this, 'defaults');
var current = _.wíthout(this.toJSON(), 'id');
//you need to convert the tags to an array so its is comparable
//with the default array. This could also be done by overriding
//Model#toJSON
current.tags = current.tags.toJSON();
return _.isEqual(current, defaults);
}
Alternatively you can hook into the request, sync and error events to keep track of the model syncing state:
initialize: function() {
var self = this;
//pending when a request is started
this.on('request', function() {
self.isPending = true;
self.isLoaded = false;
});
//loaded when a request finishes
this.on('sync', function() {
self.isPending = false;
self.isLoaded = true;
});
//neither pending nor loaded when a request errors
this.on('error', function() {
self.isPending = false;
self.isLoaded = false;
});
}

How to pass View Param to View Model DataSource in Kendo Mobile?

What is the correct way to pass a view variable from the URL to a View Model to filter the result?
For example:
dataSource: new kendo.DataSource( {
transport: {
read: {
url: 'http://api.endpoint.com/resource',
}
parameterMap: function(options,type) {
if (type === 'read') {
return {
FormID: view.params.FormID
};
}
}
});
In the example above, there's a parameter in the URL called "FormID" and I would like to pass that value right to the parameterMap function. There is no "view" object, so I'm just putting that as an example.
I tried hooking into to the "data-show" and "data-init" functions to set this value to use, but the datasource fetches the data before these functions run.
Thanks
The configuration option options.transport.read can be a function, so you can compose the url there:
dataSource: new kendo.DataSource({
transport: {
read: function (options) {
// get the id from wherever it is stored (e.g. your list view)
var resourceId = getResourceId();
$.ajax({
url: 'http://api.endpoint.com/resource/' + resourceId,
dataType: "jsonp",
success: function (result) {
options.success(result);
},
error: function (result) {
options.error(result);
}
});
}
}
});
To connect this with your list view, you could use the listview's change event:
data-bind="source: pnrfeedsDataSource, events: { change: onListViewChange }"
then in viewModel.onListViewChange you could set the appropriate resource id for the item that was clicked on:
// the view model you bind the list view to
var viewModel = kendo.observable({
// ..., your other properties
onListViewChange: function (e) {
var element = e.sender.select(); // clicked list element
var uid = $(element).data("uid");
var dataItem = this.dataSource.getByUid(uid);
// assuming your data item in the data source has the id
// in dataItem.ResourceId
this._selectedResource = dataItem.ResourceId;
}
});
Then getResourceId() could get it from viewModel._selectedResource (or it could be a getter on the viewModel itself). I'm not sure how all of this is structured in your code, so it's difficult to give more advice; maybe you could add a link to jsfiddle for illustration.
You may use a "global" variable or a field in the viewmodel for that purpose. Something like
var vm = kendo.observable({
FormID: null,
dataSource: new kendo.DataSource( {
transport: {
read: {
url: 'http://api.endpoint.com/resource',
}
parameterMap: function(options,type) {
if (type === 'read') {
return {
FormID: vm.FormID
};
}
}
})
});
function viewShow(e) {
vm.set("FormID", e.view.params.FormID);
// at this point it is usually a good idea to invoke the datasource read() method.
vm.dataSource.read();
}
The datasource will fetch the data before the view show event if a widget is bound to it. You can work around this problem by setting the widget autoBind configuration option to false - all data-bound Kendo UI widgets support it.

Nested backbone model results in infinite recursion when saving

This problem just seemed to appear while I updated to Backbone 1.1. I have a nested Backbone model:
var ProblemSet = Backbone.Model.extend({
defaults: {
name: "",
open_date: "",
due_date: ""},
parse: function (response) {
response.name = response.set_id;
response.problems = new ProblemList(response.problems);
return response;
}
});
var ProblemList = Backbone.Collection.extend({
model: Problem
});
I initially load in a ProblemSetList, which is a collection of ProblemSet models in my page. Any changes to the open_date or due_date fields of any ProblemSet, first go to the server and update that property, then returns. This fires another change event on the ProblemSet.
It appears that all subsequent returns from the server fires another change event and the changed attribute is the "problems" attribute. This results in infinite recursive calls.
The problem appears to come from the part of set method of Backbone.Model (code listed here from line 339)
// For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
// Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
}
The comparison on the problems attribute returns false from _.isEqual() and therefore fires a change event.
My question is: is this the right way to do a nested Backbone model? I had something similar working in Backbone 1.1. Other thoughts about how to proceed to avoid this issue?
You reinstantiate your problems attribute each time your model.fetch completes, the objects are different and thus trigger a new cycle.
What I usually do to handle nested models:
use a model property outside of the attributes handled by Backbone,
instantiate it in the initialize function,
set or reset this object in the parent parse function and return a response omitting the set data
Something like this:
var ProblemSet = Backbone.Model.extend({
defaults: {
name: "",
open_date: "",
due_date: ""
},
initialize: function (opts) {
var pbs = (opts && opts.problems) ? opts.problems : [];
this.problems = new ProblemList(pbs);
},
parse: function (response) {
response.name = response.set_id;
if (response.problems)
this.problems.set(response.problems);
return _.omit(response, 'problems');
}
});
parse gets called on fetch and save (according to backbone documentation), this might cause your infinite loop. I don't think that the parse function is the right place to create the new ProblemsList sub-collection, do it in the initialize function of your model instead.

How to initialise nested Backbone.js models

In my Application, I have the following JSON data format:
{
Item: {
property1: '',
...
}
}
Following the solution of this stackoverflow.com answer, I modeled my Backbond.js models the following way:
App.Models.Item = Backbone.Model.extend({
});
App.Models.ItemData = Backbone.Model.extend({
defaults: {
'Item': new App.Models.Item
}
});
I now want to bootstap the data to my App from the Backend system on the page load the following way:
var item = App.Models.ItemData({
{Item:
{property1: 'data'}
}
});
The problem I have now is that item.get('Item') returns a plain JavaScrip object and not a Backbone.Model object, because the defaults are overwritten. How can I create the Backbone.js object while ensuring that item.get('Item') is an App.Models.Item object?
I also have read that if you nest Backbone.Models, you should wirite custom getter methods, so the rest of your app dose not have to know about the internal data structure. If so, what is the right way to implement those setters and getters?
You can override the parse method on your ItemData model. No defaults required. The parse method will initialize an empty model, if one is not passed:
App.Models.ItemData = Backbone.Model.extend({
parse: function(attrs) {
attrs = attrs || {};
if(!(attrs.Item instanceof App.Models.Item))
attrs.Item = new App.Models.Item(attrs.Item);
return attrs;
}
});
And then initialize your ItemData model with the option parse:true:
var item = new App.Models.ItemData({Item:{property1: 'data'}}, {parse:true});

Backbone.js only creating first model, then returning the rest as undefined when parsing with Google Calendar

Having some issues with pulling calendar events from Google Calendar using Backbone.
When I call collection.fetch() I am only getting a length of 1 returned, when there are 13 objects in the json.
I had a look at the parse:function(response) method that I am overriding in the Collection, and it is returning all 13 objects. I had a look at the add method in backbone.js, and the issue appears to occur on line 591:
models = _.isArray(models) ? models.slice() : [models];
When I wrap the line with console.log to check the status of the models variable:
console.log(models);
models = _.isArray(models) ? models.slice() : [models];
console.log(models);
I get the following result:
[Object,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object] backbone.js:590
[child,undefined × 12]
I'm at a loss to explain why it would be failing on add. I have checked each model by changing the parse:function(response) method in the collection to return each object, and it works fine.:
parse: function(response) {
return response.feed.entry[5];
}
I have successfully parsed Google Calendar feeds with Backbone.js before, so I fear I am missing something really simple.
If I console.log response.feed the following is returned:
This is the full class:
/**
* Backbone
* #class
*/
var Gigs = Gigs || {};
Gigs.Backbone = {}
Gigs.Backbone.Model = Backbone.Model.extend();
Gigs.Backbone.Collection = Backbone.Collection.extend({
model: Gigs.Backbone.Model,
url: 'http://www.google.com/calendar/feeds/email#email.com/public/full?alt=json-in-script&orderby=starttime&callback=?',
sync: function(method, model, options) {
options.dataType = "jsonp";
return Backbone.sync(method, model, options);
},
parse: function(response) {
return response.feed.entry;
}
});
Gigs.Backbone.Controller = Backbone.View.extend({
initialize: function() {
var self = this;
this.collection = new Gigs.Backbone.Collection();
this.collection.on('reset', this.addElements, this);
this.collection.fetch();
},
addElements: function() {
log(this.collection);
}
});
var backbone = new Gigs.Backbone.Controller();
Apparently, Google Calendar provides its entries with an id wrapped in an object 1:
"id":{
"$t":"http://www.google.com/calendar/feeds/..."
}
which Backbone seems to dislike. A lot.
One simple solution would be to overwrite the id in your parse method:
parse: function(response) {
var entries=[];
_.each(response.feed.entry, function(entry,ix) {
entry.id=entry.id.$t;
entries.push(entry);
});
return entries;
}
And a Fiddle http://jsfiddle.net/bqzkT/
1 Check https://developers.google.com/gdata/docs/json to see how Google converts its XML data to JSON.
Edit : the problem comes from the way the data is returned with a straight XML to JSON conversion (requested via the alt=json-in-script parameter) wrapping the attributes in objects. Changing this parameter to alt=jsonc yields a much simpler JSON representation. Compare a jsonc output to the json-in-script equivalent.
See https://developers.google.com/youtube/2.0/developers_guide_jsonc#Comparing_JSON_and_JSONC for more info

Resources