Backbone.js model.fetch when server data is empty - javascript

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

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".

Backbone & working with non-JSON data

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

How to get a model attribute after fetch operation

I am a newbie in backbone . I wanted to know when i do the following operation how i can get the model fetched values.
For example if i do something like following
this.model.fetch();
and i want to get a value for example
this.model.get("VALUE");
How i can make sure i get the right value which is fetched right now from the server. I am trying to do something like following but ofcourse this.model is not recognized inside the complete block.
this.model.fetch().complete(function(){
window.localStorage.setItem("VALUE", this.model.get("VALUE"));
});
I am stuck here. Does anybody have any ideas.
Well, I see two options: 1. just get rid of the complete block and use the functions separately.
this.model.fetch();
var value = this.model.get('value');
//or, if you want all of the values
// var values = this.model.toJSON();
// values.value -> the specific value
And I'm not very experienced with local storage, but why are you fetching a value and then setting it to local storage?
Or, 2. You could put your inner statements in a function, and bind it to the this:
initialize: function () {
_.bind('setItem', this);
},
setItem: function() {
// assuming this code works
window.localStorage.setItem("VALUE", this.model.get("VALUE"));
}
// elsewhere, and not familiar with complete, so I'm not certain how this works
this.model.fetch().complete(setItem);

Passing data into the render Backbone.js

is it possible to pass the questions variable into the view render?
Ive attempted calling this.render inside the success on the fetch however I got an error, presumably it's because this. is not at the correct scope.
app.AppView = Backbone.View.extend({
initialize: function(){
var inputs = new app.Form();
inputs.fetch({
success: function() {
var questions = inputs.get(0).toJSON().Questions;
app.validate = new Validate(questions);
app.validate.questions();
}, // End Success()
error: function(err){
console.log("Couldn't GET the service " + err);
}
}); // End Input.fetch()
this.render();
}, // End Initialize
render: function(){
el: $('#finder')
var template = _.template( $("#form_template").html(), {} );
this.$el.html(template);
}
The success callback is called with a different this object than your View instance.
The easiest way to fix it is to add something like this before you call inputs.fetch:
var self = this;
And then inside the success callback:
self.render();
I'm not quite sure what you're trying to achieve, but if your problem is calling render from the success callback, you have two options, Function#bind or assigning a self variable.
For more information about "self" variable, see var self = this? . An example:
var self = this;
inputs.fetch({
success: function () {
self.render();
},
...
});
You should probably do some reading on JavaScript scopes, for example "Effective Javascript" or search the topic ( for example this MDN article ) online to get a better idea what happens there.
For Function#bind(), see the MDN article about it. With Backbone I suggest you use Underscore/LoDash's _.bind instead though, to make sure it works even where Function#bind() is not supported.
As for more high-level concepts, the fetching operation looks like it belongs to the model or router level and instead you should assign the questions variable as the model of your view. Ideally views don't do data processing / fetching, they're just given a model that has the methods necessary to perform any data transformations you might need.
The views shouldn't even need to worry about where the model comes from, this is normally handled by a router in case of single page applications or some initialization code on the page.

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