I am making a basic trello clone. Except instead of signing in, projects have a slug(i.e. 'www.example.com/1d754b6c')
If a user visits the root, a new slug is created on the back end. The user is then routed to www..com/1d754b6c, which sends another ajax call to get the projects ID. A view is then started. However my view is getting started before the slug -> ID ajax call is finished. Whats the best way to fix this? (I currently have a setTimeout as a temporary patch, I know that is not a good way to accomplish this)
router.js
Buckets.Routers.PageRouter = Backbone.Router.extend({
routes: {
'': 'newProject',
':token': 'displayProject'
},
newProject: function () {
new Buckets.Models.Project({});
},
displayProject: function (token) {
var that = this;
var project = new Buckets.Models.Project({token: token});
setTimeout(function(){
new Buckets.Views.showProject({
model: project
});
}, 500);
}
});
project.js
Buckets.Models.Project = Backbone.Model.extend({
url: function() {
return Buckets.BASE_URL + '/api/projects/' + (this.id)
},
initialize: function(options) {
var that = this;
if (options && options.token) {
that.token = options.token
$.ajax({
url: Buckets.BASE_URL + '/' + that.token,
dataType: 'json',
success: function( data, status ){
that.id = data;
},
error: function(xhr, textStatus, err) {
console.log(xhr);
}
});
} else {
$.ajax({
url: Buckets.BASE_URL + '/api/projects/new',
dataType: 'json',
success: function( data, status ){
that.token = data.token;
that.id = data.id;
Buckets.Routers.router.navigate('/' + that.token, true);
},
error: function(xhr, textStatus, err) {
console.log(xhr);
}
});
}
return this;
},
});
Try to use Backbone.Model.sync. .sync() returns a Promise so you can take full advantage of the Deffered/Promises standard.
When I want to pass variable urls to the fetch I override the model.fetch(). For your implemenation I'd first scrap the $.ajax in initialize() and override fetch like this
Buckets.Models.Project = Backbone.Model.extend({
fetch: function(options) {
var that = this;
if (options && options.token) {
this.url = Buckets.BASE_URL + '/' + that.token;
else
this.url = Buckets.BASE_URL + '/api/projects/new';
return Backbone.Model.prototype.fetch.call(this, options);
}
.fetch() eventually returns the result of sync() which is a Promise. That means that in your Router you'd do this:
displayProject: function (token) {
var that = this;
var project = new Buckets.Models.Project();
$.when(project.fetch({token: token})
// deffered.done() replaces the success callback in your $.ajax
.done(function() {
project.id = data;
new Buckets.Views.showProject({ model: project });
})
// deffered.fail() replaces the error callback in your $.ajax
.fail(function( jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
});
}
And for completeness, you'd reweite newProject() similarly,
newProject: function () {
var project = new Buckets.Models.Project();
$.when(project.fetch({token: token})
.done(function( data, textStatus, jqXHR ) {
project.token = data.token;
project.id = data.id;
new Buckets.Views.showProject({ model: project });
})
.fail(function( jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
});
}
Try it out. I started using this method of fetching when it was recommended to me by a major contributor to MarionetteJS, one of the premier opinionated Backbone frameworks. This method is easy to maintain and very responsive.
Related
I want to edit a javascript variable based on whether or not a file exists. Can I achieve something like this?
var url="Content/Features/column1.html";
var url2="";
$.ajax({
url: url,
error: function()
{
url2 = "something";
},
success: function()
{
url2 = "something else";
}
});
My understanding is that the typical issue here is the async in ajax. Should I be using a callback method or a promise in some fashion?
Maybe something like:
var url="Content/Features/column1.html";
var url2="";
var result = $.ajax({
url: url,
error: function()
{
url2 = "something";
handleData(url2);
},
success: function()
{
url2 = "something else";
handleData(url2);
}
});
response = function handleData( responseData ) {
return responseData;
}
Or even:
var url="Content/Features/column1.html";
var url2="";
function test(url) {
return $.ajax(
{
url: url
}
);
}
test().done(function(url2) {
url2= "something"
}).fail(function() {
url2= "something else"
});
How can I change url2 at the same scope in which it was declared? Or is there a way to return a value from a callback function and change url2 based on that?
Alternatively, is there a better way to check if this file exists? (And before you tell me that browsers don't allow ajax requests to local files, please know that I am using node-webkit, not a browser)
None of these attempts worked quite right, but please let me know if I'm close. Thank you so much for your time.
Since AJAX is async in nature you cannot return a value from your function and instead need to implement a callback. You could do something like this:
function getURL2(callback) {
callback = callback || function() {};
var url="Content/Features/column1.html";
var url2="";
$.ajax({
url: url,
error: function()
{
callback("something");
},
success: function()
{
callback("something else");
}
});
}
getURL2(function(url2) {
// the call is complete and url2 is set correctly!
}
Try
var url1 = "Content/Features/column1.html"
, res = {"url2": void 0} // `this` `context` within `$.ajax` call at `request`
, request = function request(url1, res) {
return $.ajax({
context: res // set `this` to `res` object
, url: url1
, method: "GET"
, success: function(data, textStatus, jqxhr) {
this.url2 = "something"; // `success`
return this // return `this` : `res`
}
, error: function(jqxhr, textStatus, errorThrown) {
this.url2 = jqxhr.status === 404 // `404` , `Not Found`
? "somethingElse" // `error`
// if `error` other than `404`,
// return `jqxhr.status`
: jqxhr.status;
return this // return `this` : `res`
}
});
};
request(url1, res).always(function(response) {
// `this` : `res`
console.log(response.status
, response.statusText
, response.state()
, this.url2); // if `error` , `somethingElse` , else `something`
});
See jQuery.ajax( [settings ] ) at context , deferred.always()
var url1 = "https://gist.githubusercontent.com/anonymous/9a6997f09de9b68c59b2/"
+ "raw/f7d7b756005ad6d2b88cf0211f78a2990d7d2dc7" + "123" // `404` `error`
, res = {"url2":void 0}
, request = function request(url1, res) {
return $.ajax({
context: res
, url: url1
, method: "GET"
, success: function(data, textStatus, jqxhr) {
this.url2 = "something";
return this
}
, error: function(jqxhr, textStatus, errorThrown) {
this.url2 = jqxhr.status === 404
? "somethingElse"
: jqxhr.status;
return this
}
});
};
request(url1, res).always(function(response) {
console.log(response.status
, response.statusText
, response.state()
, this.url2);
document.write(JSON.stringify(this))
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
I have written a small Jquery plugin that makes it easy for me to implement Facebook like "likes" on any item in my application. My only issue now is that I struggle to implement the success / error callback of my plugin.
$('.user-like').like({
success: function(data, textStatus, jqXHR) {
$(this).text('Liked');
}
});
My issue with the above code is this line:
$(this).text('Liked');
I'm aware of what why the issue happens, I just can't find a good way to make it work like I want it. Let me explain how the script works and what my Goal is:
As you can see I'm passing the call along to the likeApi() function that executes an AJAX call. Further you see that I merge my Options with the defaults and that you can override the success and error callback of the AJAX object.
The issue is now that this in the above code is the scope of the AJAX call and not my original method. I want to allow the user to define his own success / error callback that depends on the result of the API call and allows me to do something based on the state if it was a success or failure so that I can change the like text for example. How can I do this?
(function ($) {
$.likeApi = function (action, options) {
if (action != 'like' && action != 'unlike') {
return false;
}
var options = jQuery.extend({}, jQuery.likeApi.defaults, options);
$.ajax({
type: "POST",
url: options.baseUrl + action + '.json',
data: {
data: {
Like: {
foreign_key: options.id,
model: options.model
}
}
},
success: options.success,
error: options.error,
dataType: 'json'
});
};
$.fn.like = function (options) {
var scopedOptions = options;
this.on('click', function(event) {
event.preventDefault();
$.likeApi('like', $.extend({}, scopedOptions,{
'id': $(event.target).data('like-fk'),
'model': $(event.target).data('like-model')
}));
});
return this;
};
$.fn.unlike = function (options) {
var scopedOptions = options;
this.on('click', function(event) {
event.preventDefault();
var result = $.likeApi('unlike', $.extend({}, scopedOptions,{
'id': $(event.target).data('like-fk'),
'model': $(event.target).data('like-model')
}));
alert(result);
});
return this;
};
$.likeApi.defaults = {
baseUrl: '/likes/likes/',
action: null,
model: null,
id: null,
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus);
},
success: function(data, textStatus, jqXHR) {
alert(textStatus);
}
};
}(jQuery));
Two options: you can maintain context by adding a variable that references the original this, or you can use jquery.proxy()
Option 1:
Maintain the context by adding a variable that references the original this like so:
(function ($) {
$.likeApi = function (action, options) {
var self = this;
Then you just call self whenever you are out of context.
If you want to keep self available externally, you can inject it using jquery extend.
http://api.jquery.com/jquery.extend/
options.success = $.extend(options.sucesss, {el: self});
inside your ajax call
$('.user-like').like({
success: function(data, textStatus, jqXHR) {
$(data.el).text('Liked');
}
});
Option 2:
Alternatively, you can use jQuery.proxy()
http://api.jquery.com/jquery.proxy/
proxy can change the this context for you...
I am looking for advice to ensure that I am using callbacks and javascript coding using generally accepted js guidelines. What is listed below is two functions which are chained together. Basically its a list of checks which need to be completed prior to creating the entity. I don't expect the final version to use a ajax POST but it is a good way to test all of the error handling.
Advice or recommendations would be appreciated!! I will give credit to the best explained and critiqued answer.
function relationship_check(app_label, model, company_id, params, form, callback_function){
// This will check to see if a relationship exists. This works even on new objects.
kwargs = $.extend({}, params);
kwargs['app_label'] = app_label;
kwargs['model'] = model;
kwargs['relationship__company'] = company_id;
kwargs['error_on_objects_exists_and_no_relation'] = true;
ajax_req = $.ajax({
url: "{% url 'api_get_discover' api_name='v1' resource_name='relationship' %}",
type: "GET",
data: kwargs,
success: function(data, textStatus, jqXHR) {
callback_function(form, params)
},
error: function(data, textStatus, jqXHR) {
results = $.parseJSON(data.responseText)
if (results['object_exists'] && ! results['relationships_exists']){
django_message(results['create_string'], "info");
} else {
django_message(results['error'], "error");
}
return false
}
})
return false
};
function create_community(form, data){
var self = $(this),
ajax_req = $.ajax({
url: self.attr("action"),
type: "POST",
data: data,
success: function(data, textStatus, jqXHR) {
django_message("Saved successfully.", "success");
},
error: function(data, textStatus, jqXHR) {
var errors = $.parseJSON(data.responseText);
$.each(errors, function(index, value) {
if (index === "__all__") {
console.log(index + " : " + value )
django_message(value[0], "error");
} else {
console.log(index + " : " + value )
apply_form_field_error(index, value);
}
});
}
});
}
$(document).on("submit", "#community_form", function(e) {
e.preventDefault();
clear_form_field_errors("#community_form");
var data = {
name: $(this).find("#id_name").val(),
city: $(this).find("#id_city").val(),
cross_roads: $(this).find("#id_cross_roads").val(),
website: $(this).find("#id_website").val(),
latitude: $(this).find("#id_latitude").val(),
longitude: $(this).find("#id_longitude").val(),
confirmed_address: $(this).find("#id_confirmed_address").val()
};
console.log(data)
relationship_check(
'community', 'community', '{{ request.user.company.id }}',
data, "#community_form", create_community);
});
I am trying to get a Twitter access token from their oauth api. The plugin I am using is this https://code.google.com/p/oauth/source/browse/#svn%2Fcode%2Fjavascript. So far I only get "401 failed to validate signature and token".
Strange thing is that my ajax call becomes 'GET' request even though I set type:'POST'. Seems like jquery is changing the type from POST to GET. I don't know why it does that. I am running it on my Mac. I appreciate your help/hints/suggestions/advises. Thanks!
$(function() {
function myCallback(resp) {
console.log(resp);
}
var TwitterAPI;
TwitterAPI = (function() {
var consumer_key = null;
var consumer_secret = null;
function TwitterAPI(cons_key, cons_secret) {
this.consumer_key = cons_key;
this.consumer_secret = cons_secret;
}
TwitterAPI.prototype._url = function (data) {
if (typeof data == 'array') {
return array_map([ // TODO
this, '_url'], data);
} else if ((/boolean|number|string/).test(typeof data)) {
return encodeURIComponent(data).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A');
} else {
return '';
}
}
TwitterAPI.prototype.myCallback = function(resp) {
console.log(resp);
}
TwitterAPI.prototype.getRequestToken = function() {
var accessor = {
consumerSecret: this.consumer_secret, //this.consumer.consumerSecret,
tokenSecret: ''
};
var message = {
method: "POST",
action: "https://api.twitter.com/oauth/request_token",
parameters: {
oauth_signature_method: "HMAC-SHA1",
oauth_consumer_key: this.consumer_key, //this.consumer.consumerKey
oauth_callback: this._url("http://127.0.0.1/foobar/libs/oauth/wtf.html"),
}
};
OAuth.setTimestampAndNonce(message);
OAuth.SignatureMethod.sign(message, accessor);
var target = OAuth.addToURL(message.action, message.parameters);
message.parameters.oauth_signature = this._url(message.parameters.oauth_signature);
console.log(message.parameters);
$.ajax("https://api.twitter.com/oauth/request_token",
{ url: "https://api.twitter.com/oauth/request_token",
type: 'POST',
dataType: 'jsonp',
jsonp: 'callback',
jsonpCallback: "myCallback",
data: message.parameters,
success: function(data, textResp, xhr) {
console.log(data);
},
error: function(xhr, text, err) {
console.log(text);
}
});
};
return TwitterAPI;
})();
api = new TwitterAPI(key, secret);
$('button#request').on('click', function(e) {
e.stopPropagation();
api.getRequestToken();
});
Creating a custom sync() method in backbone.
I would like to do this the "right" and interfere with Backbone's normal functions as little as possible.
This is the code that I have so far:
var CustomSyncModel = Backbone.Model.extend({
sync:function(method, model, options){
var params = {
type: 'POST'
url: model.url(),
error: function(jqXHR, textStatus, errorThrown){
alert('error');
},
success: function(data, textStatus, jqXHR){
model.parse(data);
}
};
// Got this from line 1359 in Backbone.js developement library
// version 0.9.2:
$.ajax(_.extend(params, options));
}
});
The issue that I am having is that the line: $.ajax(_.extend(params, options)); seems to be overwriting the custom success and error functions that I created. But I'm also concerned about interfering with any custom callbacks or other functionality that may have been specified elsewhere in the application that is using this model.
What is the "correct" way to go about overriding the Backbone's sync() method?
Thanks!
If you look at Model#fetch you'll see the usual approach that Backbone uses:
fetch: function(options) {
//...
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp);
};
//...
}
So Backbone just replaces the function with a new one that calls the original. In your case, you'd have something like this:
// We don't own options so we shouldn't modify it,
// but we can do whatever we want to a clone.
options = _(options).clone()
// Replace options.error with a wrapper.
var error = options.error;
options.error = function(jqXHR, textStatus, errorThrown) {
alert('error');
if(error)
error(jqXHR, textStatus, errorThrown);
};
// Replace options.success with a wrapper.
var success = options.success;
options.success = function(data, textStatus, jqXHR) {
model.parse(data);
if(success)
success(data, textStatus, jqXHR);
};
// We don't need error or success in here anymore.
var params = {
type: 'POST',
url: model.url()
};
$.ajax(_.extend(params, options));
BTW, your model.parse(data); in your success handler probably doesn't do anything useful, parse should just be a simple filter so you'd want to do something (such as a model.set call) with the model.parse(data) return value.