Backbone model not having attribute after fetch - backbone.js

So I'm having a problem after I'm fetching a model and trying to render it to a template in this view below. I've searched around and found out that I have to do a _.bindAll etc, but still it doesn't work. Inside the first console.log, where I'm trying to get the User's name, it returns undefined. I tried putting this.render() (due to the async nature of fetch) in the success callback but it didn't work. When I inspect what console.log(data) yields, I do see the values I want, but it seems that nothing is being passed to the template.
define([
'jquery',
'underscore',
'backbone',
'models/UserModel',
'text!/assets/templates/dashboard.html'
], function($, _, Backbone, UserModel, dashboardTemplate) {
window.DashboardView = Backbone.View.extend({
el: $(".content"),
initialize: function() {
_.bindAll(this, 'render');
this.user = new UserModel();
this.user.fetch({
success: console.log(this.user.get("name")),
error: function(model, response) {
console.log("error fetching model");
console.log(model);
console.log(response);
}
});
},
render: function() {
console.log(this);
var data = {
user: this.user,
_: _
};
console.log(data);
var compiledTemplate = _.template(dashboardTemplate, data);
$(this.el).html(compiledTemplate);
}
});
return DashboardView;
});
Could anyone please shed some light?

Your first issue is that your console.log runs right away, not on success.
this.user.fetch({
success: console.log(this.user.get("name")),
// ...
means that you are calling log and then passing the return value of that as the success callback. You need to pass an anonymous function.
var view = this;
this.user.fetch({
success: function(){
console.log(view.user.get("name")),
view.render();
},
// ...
Second of all, when you render the template, you need to pass it the model's attributes, but currently you are passing the model itself. For this you can use toJSON to convert the model to a standard object.
var data = {
user: this.user.toJSON(),
// ...

You might want to check what the value of this is inside of your success callback, I doubt that it is the View as you expect which is why you are getting an undefined. Inside your template you can call console.log for extra debugging.
The main problem I see with your code is that _.template() returns a function not static content. Therefore you should be calling $(this.el).html(compiledTemplate());.
Passing data in the compiledTemplate setting will embed the data and make your template static. You generally should only pass your template code to _.template and then call the compiled function like so with the current data: compiledTemplate(this.user.toJSON());

Related

add a custom method to a backbone model calling server, returning a simple data type

Looking for some guidance on properly implementing custom logic on a model ("foo") using backbone.
We have a general requirement to avoid invocation direct .ajax calls (see below), and try to do everything consistently through the Backbone.js MV* framework.
I have implemented a method on the view (for lack of better place to put it), but I feel like this utility method would better be suited as a method on the model.
I've read articles suggesting implementation of over-riding the backbone .sync method to add custom methods on a model, but the trouble is, that I'm not sure it's "correct" to do so, when the model is expecting an object, the REST service method I'm actually invoking returns a simple boolean (true/false) value.
Here is the code I have implemented:
var myView = Backbone.View.extend({
initialize: function (options) {
...
},
canDelete: function (parmOne, parmTWo){
var okToDelete;
var url = '/url/to/check/if/delete/is/ok/parmOne/ParmTwo';
$.ajax({
async: false,
type: "GET",
url: url,
success: function (valBool, response, jqXHR) {
okToDelete = valBool;
},
error: function (data, textStatus, jqXHR) {
notifyError(data.responseText)
}
});
return okToDelete;
}
});
Looking for suggestions on implementation to make this cleaner?
Thanks
You are right about not overwriting Backbone.sync. As per the docs:
By default, it uses jQuery.ajax to make a RESTful JSON request and returns a jqXHR. You can override it in order to use a different persistence strategy, such as WebSockets, XML transport, or Local Storage.
You'd override sync if you want to define a different peristence strategy.
Model-specific utility functions belong in the model
For a utility function like yours the correct place to implement it would be on the model.
var myModel = Backbone.Model.extend({
canDelete: function (parmOne, parmTWo){
var url = '/url/to/check/if/delete/is/ok/parmOne/ParmTwo';
return $.ajax({
async: false,
type: "GET",
url: url
});
}
});
In your view, you'd probably have an even bound to a delete function, say, deleteModel. Since we wired the model.canDelete method to return the $.ajax promise we can do the error checking in the view, like this:
var myView = = Backbone.View.extend({
initialize: function (options) {
...
},
deleteModel: function(event) {
var that = this;
this.model.canDelete()
.done(function (data) {
// Do some stuff if ok to delete like:
that.model.destroy();
})
.fail(function (data) {
// Do some stuff if not ok to delete like:
that.model.notifyError(data.responseText)
});
}
});
Of course you can keep your success/fail callbacks like you had them, if the model is only going to be used in a very limited manner.

fetch triggers second time

the problem actually started, when template was loaded before fetch was complited. So I had to add
$.when(this.model.fetch()).done(function () {
that.$el.html(that.template({
model: that.model.toJSON()
}));
that.fjskdflsfk();
that.sdjfksfj();
});
inside render function.
and now I have to fetch data from another url in some cases( after initialization, so first load goes fine)
this.model.fetch({
url: getUrl() + changeableUrl,
success: function () {
console.log("success");
that.render();
},
error: function (model, response) {
showMessage("error", response.responseText);
}
});
Success trigers render, and initial fetch trigers in render, which is unwanted in this case.
Maybe someone has a solution?
You shouldn't call model.fetch from your view's 'render'. Instead, have your view listen to the model's sync event and react by rendering.
// in view:
initialize: function() {
this.listenTo(this.model, 'sync', this.render);
}
If this is the only trigger for calling the view's render, it won't be called until the model has data, and you won't get extra fetch calls because you can remove that $.when stuff from the view's render.

angularfireCollection: know when the data is fully loaded

I am writing a small Angular web application and have run into problems when it comes to loading the data. I am using Firebase as datasource and found the AngularFire project which sounded nice. However, I am having trouble controlling the way the data is being displayed.
At first I tried using the regular implicit synchronization by doing:
angularFire(ref, $scope, 'items');
It worked fine and all the data was displayed when I used the model $items in my view. However, when the data is arriving from the Firebase data source it is not formatted in a way that the view supports, so I need to do some additional structural changes to the data before it is displayed. Problem is, I won't know when the data has been fully loaded. I tried assigning a $watch to the $items, but it was called too early.
So, I moved on and tried to use the angularfireCollection instead:
$scope.items = angularFireCollection(new Firebase(url), optionalCallbackOnInitialLoad);
The documentation isn't quite clear what the "optionalCallbackOnInitialLoad" does and when it is called, but trying to access the first item in the $items collection will throw an error ("Uncaught TypeError: Cannot read property '0' of undefined").
I tried adding a button and in the button's click handler I logged the content of the first item in the $items, and it worked:
console.log($scope.items[0]);
There it was! The first object from my Firebase was displayed without any errors ... only problem is that I had to click a button to get there.
So, does anyone know how I can know when all the data has been loaded and then assign it to a $scope variable to be displayed in my view? Or is there another way?
My controller:
app.controller('MyController', ['$scope', 'angularFireCollection',
function MyController($scope, angularFireCollection) {
$scope.start = function()
{
var ref = new Firebase('https://url.firebaseio.com/days');
console.log("start");
console.log("before load?");
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
console.log("start() out");
};
$scope.start();
//wait for changes
$scope.$watch('items', function() {
console.log("items watch");
console.log($scope.items[0]); //undefined
});
$scope.testData = function()
{
console.log($scope.items[0].properties); //not undefined
};
}
]);
My view:
<button ng-click="testData()">Is the data loaded yet?</button>
Thanks in advance!
So, does anyone know how I can know when all the data has been loaded
and then assign it to a $scope variable to be displayed in my view? Or
is there another way?
Remember that all Firebase calls are asynchronous. Many of your problems are occurring because you're trying to access elements that don't exist yet. The reason the button click worked for you is because you clicked the button (and accessed the elements) after they had been successfully loaded.
In the case of the optionalCallbackOnInitialLoad, this is a function that will be executed once the initial load of the angularFireCollection is finished. As the name implies, it's optional, meaning that you don't have to provide a callback function if you don't want to.
You can either use this and specify a function to be executed after it's loaded, or you can use $q promises or another promise library of your liking. I'm partial to kriskowal's Q myself. I'd suggest reading up a bit on asynchronous JavaScript so you get a deeper understanding of some of these issues.
Be wary that this:
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
does correctly specify a callback function, but $scope.items doesn't get assigned until after you've ran the callback. So, it still won't exist.
If you just want to see when $scope.items has been loaded, you could try something like this:
$scope.$watch('items', function (items) {
console.log(items)
});
In my project I needed to know too when the data has been loaded. I used the following approach (implicit bindings):
$scope.auctionsDiscoveryPromise = angularFire(firebaseReference.getInstance() + "/auctionlist", $scope, 'auctionlist', []);
$scope.auctionsDiscoveryPromise.then(function() {
console.log("AuctionsDiscoverController auctionsDiscoveryPromise resolved");
$timeout(function() {
$scope.$broadcast("AUCTION_INIT");
}, 500);
}, function() {
console.error("AuctionsDiscoverController auctionsDiscoveryPromise rejected");
});
When the $scope.auctionsDiscoveryPromise promise has been resolved I'm broadcasting an event AUCTION_INIT which is being listened in my directives. I use a short timeout just in case some services or directives haven't been initialized yet.
I'm using this if it would help anyone:
function getAll(items) {
var deferred = $q.defer();
var dataRef = new Firebase(baseUrl + items);
var returnData = angularFireCollection(dataRef, function(data){
deferred.resolve(data.val());
});
return deferred.promise;
}

Backbone JS - Model save method and error callback

I am attempting to override a models save method and set an error callback. I am using a mixture of localStorage and server side data so in the event that the app can't connect to the server, I want to save the model to local storage. Here is my model code:
var Project = Backbone.Model.extend({
urlRoot: Settings.urls.projects.project,
save: function(attributes, options){
options || (options = {});
this.set("last_updated", new Date().toISOString(), {silent: true});
options.error = function(){
console.log("Error callback");
}
return this.constructor.__super__.save.apply(this, arguments);
},
As you can see, I am attempting to set options.error within the save method and then call the super method to actually action the save. For some reason it is ignoring the function and the console log statement is not getting called. Anyone have any ideas?
Check this reference:
http://backbonejs.org/#Model-extend
You need to do something like this:
return Backbone.Model.prototype.save.call(this, attributes, options);

Returning data from JSONP callback

I have a class that fetches jsonp data. I would like to simply return a value but am unable to do so. The callback does not update the class property - not sure if it is a timing or scope issue
Ext.define("App.ContentManager", {
extend: "Ext.util.Observable",
singleton: true,
data: 'empty',
getData: function() {
this.doLoad();
console.log(a.data) //not correct - shows original value
return this.data;
},
doLoad: function(url) {
var a = this;
Ext.util.JSONP.request({
url: "http://example.com/somejson",
callbackKey: "callback",
callback: function(c) {
a.data = c;
console.log(a.data) //correct json data is here
}
})
},
});
Of course is timing issue. With doLoad() in the getDate function you just start the request and you don't wait for it to complete in order to have the data ready in the next line. You only have the data ready in the callback: function of the doLoad method. So whatever you need to do it's best to send the request in one function and then in the callback:function have a function that will use the data returned.
Update
Also I just noticed is a scope issue too. You shout pass the scope in the JSONP request with adding a
url: "http://example.com/somejson",
scope: this, //this line
callbackKey: "callback",
And then in the getDate you should: this.data since a is local variable in the doLoad method.
As noted in existing comments, since the doLoad method retrieves the data asynchronously, there is no way to return the result directly from the method call. Your two options would be:
Define your method with a callback parameter (this is done commonly in Ext itself) so that inside the JSONP callback you would execute the passed callback function, passing the returned data.
Define a custom event that your class could fire from the JSONP callback, passing the data as an event argument. This would be more suitable in the case where multiple components might be interested in the result of the JSONP call.
Or you could even do both (untested):
doLoad: function(options) {
var me = this;
Ext.util.JSONP.request({
url: options.url,
scope: me,
callbackKey: "callback",
callback: function(data) {
me.fireEvent('dataloaded', me, data);
if (options.callback) {
options.callback.apply(options.scope || me, [data]);
}
}
})
}

Resources