Backbone & working with non-JSON data - javascript

I have a static file that's just one date-time per line, e.g.:
2014-03-14T16:32
2014-03-15T13:04
2014-03-16T06:44
...
I want to use that as a read-only data source for a backbone collection. Obviously that's not in the expected format. I thought I could just use the parse method on my collection to just convert it into a proper array of objects. Unfortunately, that doesn't seem to be working.
First, if I don't override fetch, the parse method never gets called -- it goes into the error handler, though it's unlcear exactly why -- it's not actually throwing any errors. I'm guessing that's because it's expecting a different response type or something?
Second, if I override both the fetch & parse methods thus:
var MyColl = Backbone.Collection.extend({
model: MyModel,
url: 'date-list.txt',
parse: function(data) {
var result = _(data.split("\n")).compact().map(function(item, i) {
return { theDate: item, globalIndex: i };
});
return result;
},
fetch: function() {
$.get(this.url, this.parse);
}
});
it correctly goes into parse, and parse seems to build a valid object, but my collection has 0 models after the whole operation...
I suppose the collection is winding up empty because when I call parse, it's not part of the expected flow any more so nothing is done with the result. Any other ideas about how I can make fetch properly handle what the server is returning?
Obviously, I could make the server return JSON, or I could use my own fetching function outside of backbone, but I'm hoping there's a way to avoid those ideas.

I believe the default fetch method assumes you will be returning JSON from the endpoint, which the collection will then instantiate a new model for each object in the array from the JSON data.
With your current override, you just need to just build an array of Backbone models in your parse method:
parse: function(data) {
var model = this.model;
var result = _(data.split("\n")).compact().map(function(item, i) {
return new model({ theDate: item, globalIndex: i });
});
return result;
},

It looks like you are not passing correct function to $.get, or, to be more precise, correct function but not bound to specific object instance. One idea is to try this:
$.get(this.url, _.bind(this.parse, this));
But now, as you said, nothing is done with the result of parse method, so you can add elements to collection like this:
parse: function(data) {
var result = _(data.split("\n")).compact().map(function(item, i) {
return { theDate: item, globalIndex: i };
});
this.set(result);
}

Related

Ember.js find with plain object is not working

I use Ember.js to get items like this:
App.MyData.find()
And to get item like this:
App.MyData.find(itemId)
And then I use filter and return it in model function like this:
App.MyRoute = Ember.Route.extend({
model: function() {
return App.MyData.find().filter(function(a)
{
return a.get('desc') != null;
});
}
});
And it's working just fine.
Now I wanted to pass another parameter to underlying PHP script returning items. So I used "Querying For Records desc":
"If you provide a plain object as the second argument to find, Ember Data will make a GET request with the object serialized as query params. This method returns DS.PromiseArray in the same way as find with no second argument."
According to the documentation it should behave the same way like find with no plain object argument.
But it does not. My view is not displaying anymore.
I checked GET request. It returns exactly the same data.
I have no errors in JS.
What to do to pass parameter to PHP while getting items in a way it will work?
As you can see in this jsbin it does work. So if it doesn't work for you you either have a really old version or you're doing something else wrong.
I use this to get the model:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('thing', { beer: 'heineken' });
}
});
and that results in this request: GET /things?beer=heineken".

angularjs $resource converting query Resource list to array of objects

I'm trying to create an array of objects from the return of a $resource query as shown in this SO question: link. However, I keep getting the same list of Resources and other elements. I have a plunk: here (You have to open the developer console to see the output.)
var app = angular.module('plunker', ['ngResource']);
app.factory('NameResource', function($resource) {
var url = 'data.json';
var res = $resource(url, null, {
query: {
method: 'GET',
isArray: true,
transformResponse: function(data, headersGetter) {
var items = angular.fromJson(data);
var models = [];
angular.forEach(items, function(item) {
models.push(item);
});
console.log("models: ", models);
return models;
}
}
});
return res;
});
app.controller('MainCtrl', function($scope, NameResource) {
$scope.names = NameResource.query();
console.log('Inside controller: ', $scope.names);
setTimeout(function(){console.log('after some time names is:', $scope.names)}, 3000);
});
What am I doing wrong? Or have I misunderstood something. Also what is the difference between the two? It seems to work very similar for me. When will it cause an issue?
Resource.query returns an array (because of the isArray flag you created) with two properties, $promise which is the promise which when resolved will "copy" all the response values to the array returned from Resource.query magically updating the view and $resolved which is a flag telling if $promise was resolved already, to answer your question there's actually some additional transformation happening, the data returned from your transformation will actually go through another transform (which can't be disabled) and this is where your each object is transformed into a Resource instance.
So this is what you're expecting to happen:
promise
.then(function (rawData) {
// this is where your transformation is happening
// e.g. transformResponse is called with rawData
// you return your transformed data
})
.then(function (transformedData) {
// raw data has gone through 1 transformation
// you have to decide what to do with the data, like copying it to
// some variable for example $scope.names
})
But Resource is doing the following:
promise
.then(function (rawData) {
// this is where your transformation is happening
})
.then(function (transformedData) {
// Resource is adding this handler and doing the
// 'copy' to array operation here for you,
// but it will actually create a Resource instance
// in the process with each item of your array!
})
.then(function (transformedDataV2) {
// raw data has gone through 2 transformations here!
})
The additional transformation is where the magic happens and is where the Resource instance is created, if we take a look at the source code these are the lines which take care of this transformation, I'll copy them here:
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
if (typeof item === "object") {
value.push(new Resource(item));
} else {
// Valid JSON values may be string literals, and these should not be converted
// into objects. These items will not have access to the Resource prototype
// methods, but unfortunately there
value.push(item);
}
});
}
data is the data returned by your first transformation and as seen above it'll pass the typeof item === 'Object' check so value which is the array returned by Resource.query is updated with a new Resource item (not with item). You were worried about this strange Resource object, let's analyze the Resource constructor:
function Resource(value) {
shallowClearAndCopy(value || {}, this);
}
It's just copying each of the properties of the object value to this (this is the new Resource instance), so now we're dealing with Resource objects and not plain array objects
will it cause an issue?
I'm sure it will, if the transform function you define is a little bit more complex like having each object actually be an instance of something else whose __proto__ has some methods, e.g. a Person object instead of a plain object then the methods defined in Person.prototype won't be visible to the result of the whole operation since each object won't be a Person instance but a Resource instance! (see this error in this plunkr, make sure to read the comments and also look at the error raised in the console because of the undefined method)

Backbone.js model.fetch when server data is empty

I have an app where we are using model.fetch() to pull JSON from the server, and we have this as our render function that fires when the model changes:
if(_.isUndefined(this.model.get("id_number"))){
this.template = initialTemplate;
} else if(this.model.get("id_number") == 0) {
this.template = templateA;
} else {
this.template = templateB;
}
return BaseView.prototype.render.call(this);
On pageload, we don't do model.fetch() yet and get the initialTemplate. When a user changes an input, we fetch and get new model data that can have an ID of 0 or something else.
Now there is also a chance the server JSON might change to an empty {} and if so we need to revert to showing the initialTemplate. The problem is it appears that if that's the case, model.fetch() doesn't return anything and nothing changes. Same thing if id_number is undefined. (It does work if it's null.)
Is there a solution so Backbone will fetch an empty data set?
The solution for this was something Stephen mentioned: a success function. I added this to the model.fetch() function:
success: function(model, response /*jshint unused: false */) {
if (_.isEmpty(response)) {
view.model.set("id_number", undefined);
}
},...
FYI to anyone who uses this in the future: Backbone won't let you pass only response to the function because it expects model first. You can pass model and not use it, but you'll need the comment above to pass JSHint.
Set defaults in your model
Here is documentation.
http://backbonejs.org/#Model-defaults

Why is this object suddenly undefined?

here is my JavaScript code:
var Model =
{
get: function(id)
{
return this.data[id];
},
data: {},
init: function()
{
var self = this;
$.getJSON(urlToServer, function(data)
{
$.each(data, function(i, object)
{
self.data[object.id] = object;
console.log(object.id); // output is: 1, then 2, then 3
});
});
}
};
Model.init();
console.log(Model); // output is the initialized object with children objects 1, 2, 3
console.log(Model.get(1)); // output is undefined
As you can see from the console output i put in the comments, everything works fine until the last line of code. I define a Model and initialize it with some JSON objects provided by the server. But all of a sudden, when i try to access a single child object through the get() method, the Model appears to be undefined.
I just don't get it, please help me out.
Thanks.
Looking at the sample code you used, Model.get(1) will always return undefined.
$.getJSON is an AJAX call that does not necessarily return immediately (known as asynchronous). You will need to use the callback you supplied to $.getJSON to fire off any logic depending on Model.get(1), otherwise it will remain undefined.
$.getJSON is a asynchronous request, you must wait for the response before you call Model.get()
You trying to retrieve object's field "142". I guess you get from json only "1", "2" and "3" id's? If i'm right then get function return to you correct answer because no object field "142" exists.

Backbone Model.save() kills all bindings to events

I have been using Backbone on a new project and so far have loved it, but I have come to a blocker that I can't seem to get around.
Without trying to explain my whole domain model, I have found that when you save a model, the response comes back from the server and gets parsed again, creating new sub objects and therefore breaking and event bindings I had previously put on the object.
For instance, if I save ContentCollection (its a Backbone.Model not a collection) when it comes back from the server, the response gets parsed and creates a new Collection in this.contentItems, which breaks all the binding I had on this.contentItems. Is there any way to get around this? Tell backbone not to parse the response somehow? Grab the bindings off the original list, and then re-attach them to the new list?
App.ContentCollection = Backbone.Model.extend({
urlRoot: '/collection',
initialize: function() {
},
parse: function(resp, xhr) {
this.contentItems = new App.ContentList(resp.items)
this.subscriptions = new App.SubscriptionList(resp.subscriptions)
return resp
},
remove: function(model){
this.contentItems.remove(model)
this.save({'removeContentId':model.attributes.id})
},
setPrimaryContent: function(model){
this.save({'setPrimaryContent':model.attributes.id})
}
})
Has anyone run into this before?
I think the issue here is the way you're using the parse() method. Backbone just expects this method to take a server response and return a hash of attributes - not to change the object in any way. So Backbone calls this.parse() within save(), not expecting there to be any side-effects - but the way you've overridden .parse(), you're changing the model when the function is called.
The way I've dealt with this use case in the past is to initialize the collections when you first call fetch(), something like:
App.ContentCollection = Backbone.Model.extend({
initialize: function() {
this.bind('change', initCollections, this);
},
initCollections: function() {
this.contentItems = new App.ContentList(resp.items);
this.subscriptions = new App.SubscriptionList(resp.subscriptions);
// now you probably want to unbind it,
// so it only gets called once
this.unbind('change', initCollections, this)
},
// etc
});

Categories

Resources