Backbone View property not being set - javascript

I have a view with a property which i want to update its value when i make a fetch request.
define(['underscore','backbone','models/taskCollection'],
function( _,Backbone,TaskCollection) {
var UserTasksView = Backbone.View.extend({
el:"#user-task-list",
cur_task: undefined,
initialize: function() {
this.collection = new TaskCollection;
this.model = this.collection._model;
_.bindAll(this,'render');
this.collection.bind('reset',this.render);
},
view_task: function( event ) {
var el = $(event.currentTarget);
var task_id = el.attr('data-taskid');
var row = el.parents('td').parents('tr.task-row');
row.addClass("active");
el.hide();
el.next('a').show();
var task = this.collection.fetch({
data: {id:task_id},
silent:true,
success:this._task_fetch_success
});
this._show_task_detail();
event.preventDefault();
},
_task_fetch_success: function(response,status,xhr) {
this.cur_task = JSON.stringify(status);
return status;
},
/**
* Displays the details of a task
**/
_show_task_detail: function() {
var main = $('.app-content');
var detail_view = $('.app-extra');
var task_detail_view = $("#task-detail-view");
//Reduce task list view width
main.animate({
"width":"50%"
},2000);
//Display app extra bar
detail_view.show();
//show task detail view
detail_view.children('active-page').hide().removeClass('active-page').addClass('inactive-page');
task_detail_view.show().removeClass('inactive-page').addClass('active-page');
console.log(this.cur_task);
var template = ich.task_detail(this.cur_task)
$('div.task-details').html(template);
}
The ajax request trigger by the fetch is successful and success callback executes, but when i try to log "cur_task" property, it shows up as undefined;
What am i doing wrong

You have a couple problems that start right here:
var task = this.collection.fetch({
data: {id:task_id},
silent:true,
success:this._task_fetch_success
});
and here:
_task_fetch_success: function(response,status,xhr) {
this.cur_task = JSON.stringify(status);
return status;
}
First of all, the success callback is not a jQuery success callback and doesn't receive the usual jQuery parameters; from the fine manual:
The options hash takes success and error callbacks which will be passed (collection, response) as arguments.
so your _task_fetch_success function is called as f(collection, response) not as f(response, status, xhr) as you're expecting; this is why you have to treat the status parameter as JSON: the status is actually the response.
Your next problem is that this isn't what you think it is inside your _task_fetch_success function. Backbone's fetch just calls success as a plain old function:
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp, options); // <--------------- Right here
collection.trigger('sync', collection, resp, options);
};
That means that this will be window, not your view. The easiest way to work around this problem is to add _task_fetch_success to your _.bindAll list in initialize:
initialize: function() {
//...
_.bindAll(this, 'render', '_task_fetch_success');
//...
}

Related

jQuery: $.when().done() doesn't wait / never fires [duplicate]

I want to first render a view with Backbone.js that displays an Article pulled from the server. I then want to mark this as "seen" and return the count of unseen messages to the router as it needs to be made available to other views.
So in my Router, I have:
getArticle: function (id) {
require(["app/models/article", "app/views/Article"], function (models, Article) {
var article = new models.Article({id: id});
article.fetch({
success: function (data) {
var articleView = new Article({model: data, message_count:that.message_count});
slider.slidePage(articleView.$el);
$.when(articleView.saveView()).done(function(data){
console.log('in when and data is ');
console.log(data);
});
},
error: function(){
console.log('failed to fecth artcie');
}
});
});
},
saveView() in the Article view is:
saveView: function(){
var viewDetails = [];
viewDetails.device_id = this.options.device_id;
viewDetails.article_id = this.model.id;
viewDetails.project_title = project_title;
var article_view = new models.ArticleView();
article_view.save(viewDetails,
{
success: function(data) {
var count = data.get('count');
console.log('in saveView() success and count is ');
console.log(count);
return count;
},
error: function(model, xhr, options){
console.log(xhr.responseText);
},
});
},
This hits a REST API, records the viewing of the article and then returns a count of unseen Articles. This results in a console output of:
in when and data is router.js:286 undefined router.js:287 in
saveView() success and count is Article.js:45 4
So, somehow, $.when is not working as it's not waiting for the Ajax request to send before executing the .done script. Any ideas?
You need to return a jQuery Deferred object for $.when to work properly:
return article_view.save(viewDetails,
{
success: function(data) {
var count = data.get('count');
console.log('in saveView() success and count is ');
console.log(count);
return count;
},
error: function(model, xhr, options){
console.log(xhr.responseText);
},
});
Backbone's save() method returns a jqXHR object which behaves the same way as a Deferred object in this case. Simply chain the return call as above. This should get $.when() to wait for the request to finish.

Cannnot overwrite Backbone save success handler

Im using backbone 1.1.0. Its been a while since I used Backbone but I'm sure I used to be able to easily overwrite the success handler for the save method. However, now i cannot seem to do it! My code is:
model.save({}, {
successs: function() {
console.log('in my custom success handler');
}
});
My custom handler doesnt not execute and the default success handler does, triggering the sync event.
I've looked at the question here and tried EACH of the solutions, none of which have worked. These included passing in success handler object at the third parameter, second parameter, passing in null as the first parameter etc etc etc.
The Backbone library code (v1.1.0) for the Model save method is:
save: function(key, val, options) {
var attrs, method, xhr, attributes = this.attributes;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.extend({validate: true}, options);
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;
}
// Set temporary attributes if `{wait: true}`.
if (attrs && options.wait) {
this.attributes = _.extend({}, attributes, attrs);
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options);
// Restore attributes.
if (attrs && options.wait) this.attributes = attributes;
return xhr;
},
2 things puzzle me:
1/ How could it ever have been possible to overwrite the success handler(i'm sure I used to be able to do that) because when you pass in a success handler, it gets a assigned to a local var success, and then overridden anyway:
var success = options.success;
options.success = function(resp) {
....
2/ Why doesnt my handler also execute? It should get assigned to the local succss var:
var success = options.success;
and then executed in options.success:
if (success) success(model, resp, options);
When I debug via chrome developer tools, success is undefined. Yet I can see on the line:
var success = options.success;
that options.success contains my customer handler method. However the local var success is, somehow, undefined....
I think your code should be:
model.save({}, {
success: function(){
//^-----this-----^
console.log('in my custom success handler');
}
});

Javascript functions order

I'm making a jquery library to use an application with the json rpc protocol but I'm stuck with a little problem.
This is the fiddle that shows the code (obviously it can't work): https://jsfiddle.net/L9qkkxLe/3/.
;(function($) {
$.lib = function(options) {
var outputHTML = [],
plugin = this;
var APIcall = function(api_method, api_params) {
request = {};
request.id = Math.floor((Math.random() * 100) + 1);
request.jsonrpc = '2.0';
request.method = api_method;
request.params = (api_params) ? api_params : [];
$.ajax({
type: "POST",
url: "http://localhost:8898/jsonrpc",
data: JSON.stringify(request),
timeout: 3000,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', window.btoa(options.username + ":" + options.password));
},
success: function(data) {
handleData(data, api_method);
},
error: function(jqXHR, textStatus, errorThrown) {
log("Connection time out: can't reach it. Try changing the settings.");
isConnected = "false";
},
dataType: "json"
});
}
var handleData = function(data, method) {
if (method == "getgenres") {
outputHTML = data.result.genres; //I need data.result.genres to return in getgenres function
}
}
var log = function(msg) {
if (options.debug == true) console.log(msg);
}
plugin.getgenres = function() {
APIcall("getgenres");
return outputHTML; //This is sadly empty.
}
};
}(jQuery));
var init = new $.lib();
console.log(init.getgenres());
I need that the getgenres function returns data.result.genres but actually it returns an empty array because getgenres is called for first and only after the handleData function gives to outputHTML the value that I need.
You are performing an asynchronous AJAX request, which means you can't actually get back the data immediately. There are two ways to solve your issue: making it synchronous (easy but ill advised) or using a callback (a little bit more complex but generally accepted):
In your getgenres function, you could accept one more parameter: callback
plugin.getgenres = function(callback) {
/* Dont forget APIcall already took two parameters in, so callback has to be the third in line! */
APIcall("getgenres", false, callback);
}
Now modify your APIcall function to accept your callback:
var APIcall = function(api_method, api_params, callback) { ... }
And call the callback from the successful completion call - instead of having a handler method in between wrapped in a function, you can simply pass the anonymous function. So instead of success: function(data){ handle(data); }, just use:
success: callback
The anonymous function that we will pass to it will receive as its first parameter the data you were passing to the handler. Now you can do the following:
var myGenres = [];
var init = new $.lib();
init.getgenres(function(data){
/* Now your data is actually loaded and available here. */
myGenres = data;
console.log(myGenres);
});
I would like to point out that there are many better ways to handle this, including turning this into a Constructor (More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) instead of the strange amalgamation of functions and variables you have now, as well as using JS Promises (here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to make this easier. But the basic gist should be here.
Update (potential implementation)
Because I mentioned that this could be done in a way that I think is clearer to read and use. I do not know all use cases for this, but from the provided example I would change the code to something looking like the following. Please also note I am not an expert on jQuery plugins, so I am avoiding plugging into jQuery and just using it as an easy AJAX call.
function getAjax(){
if(!window.jQuery || !window.$) throw("jQuery is required for this plugin to function.");
this.data = [];
this.request = '';
return this;
}
getAjax.prototype = {
createRequest : function(method, parameters){
this.request = {};
this.request.id = Math.floor((Math.random() * 100) + 1);
this.request.jsonrpc = '2.0';
this.request.method = method;
this.request.params = parameters || [];
return this;
},
callRequest : function(options, callback, error){
var self = this;
// We could also `throw` here as you need to set up a request before calling it.
if(!this.request) return this;
else {
$.ajax({
// We will allow passing a type and url using the options and use sensible defaults.
type: options.type || "POST",
url: options.url || "http://localhost:8898/jsonrpc",
// Here we use the request we made earlier.
data: JSON.stringify(this.request),
timeout: options.timeout || 3000,
beforeSend: function(xhr){
xhr.setRequestHeader(
'Authorization',
window.btoa( options.username + ":" + options.password)
);
},
// We will also store all the made request in this object. That could be useful later, but it's not necessary. After that, we call the callback.
success: function(data){
var store = {request:self.request, data: data};
self.data.push(store);
// Call the callback and bind `this` to it so we can use `this` to access potentially pther data. Also, pass the results as arguments.
callback(data, self.request.id).bind(self);
},
// Error function!
error: error,
dataType: options.dataType || "json"
});
}
return this;
}
}
// Example use
new getAjax().createRequest('getgenres').callRequest({
username: 'myusername',
password: 'mypassword'
}, function(data, id){
// Success! Do with your data what you want.
console.log(data);
}, function(e){
// Error!
alert('An error has occurred: ' + e.statusText);
console.log(e);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
What I do in those occasions is this:
You are supplying a method. So put a reference to the a callback function. In this case plugin.getGenresFinalize. When handleData is called it will fire that callBack function. This way you can pass multiple methods to the api call for different types of data.
plugin.getgenres = function() {
APIcall(this.getgenresFinalize);
}
plugin.getgenresFinalize = function(data) {
console.log(data);
}
var handleData = function(data, method) {
method(data);
}

Fetch data on different server with backbone.js

I can't see what the problem with this is.
I'm trying to fetch data on a different server, the url within the collection is correct but returns a 404 error. When trying to fetch the data the error function is triggered and no data is returned. The php script that returns the data works and gives me the output as expected. Can anyone see what's wrong with my code?
Thanks in advance :)
// function within view to fetch data
fetchData: function()
{
console.log('fetchData')
// Assign scope.
var $this = this;
// Set the colletion.
this.collection = new BookmarkCollection();
console.log(this.collection)
// Call server to get data.
this.collection.fetch(
{
cache: false,
success: function(collection, response)
{
console.log(collection)
// If there are no errors.
if (!collection.errors)
{
// Set JSON of collection to global variable.
app.userBookmarks = collection.toJSON();
// $this.loaded=true;
// Call function to render view.
$this.render();
}
// END if.
},
error: function(collection, response)
{
console.log('fetchData error')
console.log(collection)
console.log(response)
}
});
},
// end of function
Model and collection:
BookmarkModel = Backbone.Model.extend(
{
idAttribute: 'lineNavRef'
});
BookmarkCollection = Backbone.Collection.extend(
{
model: BookmarkModel,
//urlRoot: 'data/getBookmarks.php',
urlRoot: 'http://' + app.Domain + ':' + app.serverPort + '/data/getBookmarks.php?fromCrm=true',
url: function()
{
console.log(this.urlRoot)
return this.urlRoot;
},
parse: function (data, xhr)
{
console.log(data)
// Default error status.
this.errors = false;
if (data.responseCode < 1 || data.errorCode < 1)
{
this.errors = true;
}
return data;
}
});
You can make the requests using JSONP (read about here: http://en.wikipedia.org/wiki/JSONP).
To achive it using Backbone, simply do this:
var collection = new MyCollection();
collection.fetch({ dataType: 'jsonp' });
You backend must ready to do this. The server will receive a callback name generated by jQuery, passed on the query string. So the server must respond:
name_of_callback_fuction_generated({ YOUR DATA HERE });
Hope I've helped.
This is a cross domain request - no can do. Will need to use a local script and use curl to access the one on the other domain.

How to wait to render view in backbone.js until fetch is complete?

I'm trying to understand how a portion of backbone.js works. I have to fetch a collection of models once the app begins. I need to wait until fetch is complete to render each view.
I'm not 100% sure the best approach to take in this instance.
var AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"customer/:id": "customer"
},
home: function () {
console.log("Home");
},
customer: function (id) {
if (this.custromers == null)
this.init();
var customer = this.customers.at(2); //This is undefined until fetch is complete. Log always says undefined.
console.log(customer);
},
init: function () {
console.log("init");
this.customers = new CustomerCollection();
this.customers.fetch({
success: function () {
console.log("success");
// I need to be able to render view on success.
}
});
console.log(this.customers);
}
});
The method I use is the jQuery complete callback like this:
var self = this;
this.model.fetch().done(function(){
self.render();
});
This was recommended in a Backbone bug report. Although the bug report recommends using complete, that callback method has since been deprecated in favor of done.
You can also do this with jquery 1.5+
$.when(something1.fetch(), something2.fetch()...all your fetches).then(function() {
initialize your views here
});
You can send your own options.success to the collections fetch method which runs only when the fetch is complete
EDIT (super late!)
From the backbone source (starting line 624 in 0.9.1)
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
Note the second to last line. If you've passed in a function in the options object as the success key it will call it after the collection has been parsed into models and added to the collection.
So, if you do:
this.collection.fetch({success: this.do_something});
(assuming the initialize method is binding this.do_something to this...), it will call that method AFTER the whole shebang, allowing you trigger actions to occur immediately following fetch/parse/attach
Another useful way might be to bootstrap in the data directly on page load. This if from the
FAQ:
Loading Bootstrapped Models
When your app first loads, it's common to have a set of initial models that you know you're going to need, in order to render the page. Instead of firing an extra AJAX request to fetch them, a nicer pattern is to have their data already bootstrapped into the page. You can then use reset to populate your collections with the initial data. At DocumentCloud, in the ERB template for the workspace, we do something along these lines:
<script>
var Accounts = new Backbone.Collection;
Accounts.reset(<%= #accounts.to_json %>);
var Projects = new Backbone.Collection;
Projects.reset(<%= #projects.to_json(:collaborators => true) %>);
</script>
Another option is to add the following inside of your collections initialize method:
this.listenTo(this.collection, 'change add remove update', this.render);
This will fire off the render method whenever the fetch is complete and/or the collection is updated programmatically.
You Can Use on and Off Methods
if you want to add trigger method like suppose if you want on success you want to call render method so please follow below example.
_this.companyList.on("reset", _this.render, _this);
_this.companyList.fetchCompanyList({firstIndex: 1, maxResult: 10}, _this.options);
in Model js please use like
fetchCompanyList: function(data, options) {
UIUtils.showWait();
var collection = this;
var condition = "firstIndex=" + data.firstIndex + "&maxResult=" + data.maxResult;
if (notBlank(options)) {
if (notBlank(options.status)) {
condition += "&status=" + options.status;
}
}
$.ajax({
url: "webservices/company/list?" + condition,
type: 'GET',
dataType: 'json',
success: function(objModel, response) {
UIUtils.hideWait();
collection.reset(objModel);
if (notBlank(options) && notBlank(options.triggerEvent)) {
_this.trigger(options.triggerEvent, _this);
}
}
});
}

Categories