JQuery UI Widgets with AJAX requests - javascript

I'm doing some geolocation/map work and building a JQuery widget so that the code is nice and portable for future projects.
I've run into a wall as far as making an AJAX request though; here's a couple of the methods from my widget:
getGeocodeForAddress: function(address) {
req = this._googleMapsApiRequest('geocode','json','address='+address);
//We need 'req' to be the response from the API request so we can do work with it.
},
/**
* Private Maps API request method. This will help to construct a call to Google Maps API.
*
* #param service
* #param output
* #param params
*/
_googleMapsApiRequest: function(service,output,params) {
var widget = this;
var protocol = (this.options.useHttps) ? 'https://' : 'http://';
if (this.options.googleMapsApiKey != '') {
params += '&key' + this.options.googleMapsApiKey;
}
var uri = protocol + 'maps.googleapis.com/maps/api/' + service + '/' + output + '?' + params;
this._sendToLog("Google Maps API Request: " + uri);
$.ajax({
async: false,
type: "GET",
cache: false,
url: encodeURI(uri),
success: function(response) {
//We need the contents of response to be available to the method that called this one.
},
error: function() {
widget._sendToLog('AJAX error');
},
});
},
The specific problem is that once the ajax request is made and returns its success, I can't get the data back into the method that calls it.
I've tried setting an internal option using widget.options.ajaxResponse in _googleMapsApiRequest but that seems to only be 'null' in the calling method, I've tried returning the response from inside the AJAX method but that doesn't work either.
I'm sure I need a callback in the _googleMapsApiRequest method so that it will wait for that method to complete and I can then execute code based on that, but how can I do that within a widget?

A break to think about something else and then a bit more research and I have come up with a callback solution.... it seems a bit clunky but it seems also to do the trick, can anyone improve on it?
getGeocodeForAddress: function(address) {
this._googleMapsApiRequest('geocode','json','address='+address, function(response)
{
//I can access response within this callback.
});
},
/**
* Private Maps API request method. This will help to construct a call to Google Maps API.
*
* #param service
* #param output
* #param params
*/
_googleMapsApiRequest: function(service, output, params, callback) {
var widget = this;
var protocol = (this.options.useHttps) ? 'https://' : 'http://';
if (this.options.googleMapsApiKey != '') {
params += '&key' + this.options.googleMapsApiKey;
}
var uri = protocol + 'maps.googleapis.com/maps/api/' + service + '/' + output + '?' + params;
this._sendToLog("Google Maps API Request: " + uri);
$.ajax({
async: false,
type: "GET",
cache: false,
url: encodeURI(uri),
success: function(response) {
widget._sendToLog('AJAX success, response follows');
widget._sendToLog(response);
},
error: function() {
widget._sendToLog('AJAX error');
}
}).done(function(response) {
if (typeof callback == "function") {
callback(response);
}
});
},
I haven't tested yet to see how this handles unsuccessful ajax requests, but it does the trick at least when the request works.

Related

How to use one AJAX handler for multiple purposes with callbacks?

I have a piece of code written in an object. You can see there is a customers object with a function for adding a new customer and a method for making AJAX calls
var sys = {
customers: {
addNew: function(ref, cb = null) {
if (!cb) { // so it can check if the call to this method was for requesting ajax request or handling its response . note i am sending the callback function reference same as the current
core.request({
d: $('form').serialize()
}, 'sys.customers.addNew', ref);
} else {
if (ref.status) {
$('.customers-list').append('<li>' + ref.customer.name + '</li>');
alert('success')
}
}
},
updateRowAfterAdd: function() {
// or i could use this for handling callback by passing its reference instead of the upper same function
}
},
request: function(p = {}, c = null, e = false) {
$.ajax({
url: "/to/my/server",
data: {
p: p
},
type: 'post',
dataType: 'json',
beforeSend: function() {
},
success: function(r) {
if (c != null)
(e ? eval("(" + c + "(r,e));") : eval("(" + c + "(r));"));
}
});
}
}
$(document).on('click', '.addNew', function() {
sys.customers.addNew($(this));
});
The idea in this example is to call the AJAX method by passing a callback function reference for handling the success response.
If you look at the addNew() method it is working in two ways. With the help of the second parameter, cb, it is determining that the call to this function was for sending an AJAX request or handling its response back.
I'm using eval() in the success callback which I know is evil, so I want to understand how I can do this without using eval()?
I have multiple things running on my page which need AJAX calls and I don't want to rewrite each of them.
I also need this for AJAX's beforeSuccess() method as well.
The design pattern you're using seems to be a needless abstraction which is causing more problems that it solves.
A better idea would be to have a central 'service' layer which makes the requests to your server side and handles the responses. If you wanted to abstract this further you could have other domain logic abstractions to handle AJAX requests and responses through a single class, however at that stage I would argue you're far better off using an existing framework to do this for you.
A strong recommendation would be to use Angular, given that its MVC pattern is where you're heading anyway.
If you did want to roll your own simplistic version, then a simple example would look something like this:
$(document).on('click', '.addNew', function() {
services.customers.save($('form').serialize());
});
// in a service layer JS file, far away from UI logic...
let services = {
customers: {
save: requestData => {
$.ajax({
url: '/to/my/server',
type: 'post',
dataType: 'json',
data: $('form').serialize(),
success: services.customers.renderUi
});
},
renderCustomerUi: customerData => {
// optional: extract the UI update logic to your UI layer and pass in the callback as an argument
if (customerData.status) {
$('.customers-list').append('<li>' + customerData.customer.name + '</li>');
}
}
}
}

Javascript does not call GET methods more than once due to Caching issues in browser. ResponseCaching does not fix issue

I made an application that uses Javascript (Vue.Js) to call my API and update lists on the page when I add a new Object to that list (in this case a list of users).
I made the mistake of only testing on Edge, where it updated itself perfectly if I made a get-call after I had updated the list. However, every other browser seems to cache it somehow, and does not call GET api/users more than once (noticed that by adding breakpoints).
In the past ASP.NET version adding [OutputCache(NoStore = true, Duration = 0)] most likely would have solved the Issue, but that does not work in ASP.NET Core 2.0.
I read up and found that ResponseCaching Is supposed to replace it. So I added it to my project and followed the official docs on how to implement it, but no luck.
To Startup.cs (as well as all required code to add the interface to my scope) I add:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.CacheProfiles.Add("Default",
new CacheProfile()
{
Duration = 60
});
options.CacheProfiles.Add("Never",
new CacheProfile()
{
Location = ResponseCacheLocation.None,
NoStore = true
});
});
}
Then to the top of each controller in my API I add:
[ResponseCache(CacheProfileName = "Never")]
public class UserController : Controller
{
This does not solve my issues, instead it seems to clear the accesstoken I use for my users so I can't keep users signed in anymore. Is this not the solution I need to fix my problem? Am I implementing it in the wrong way? Not sure where to go from here.
Here is what My Ajax calls in JS looks like:
addAdmin: function () {
var self = this;
$.ajax({
url: 'api/admins',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(self.adminCreate),
success: function (response) {
self.getAdmins(false);
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Error: ' + textStatus + '\n' + errorThrown);
}
});
}
//gets called on Start as well as after each AddAdmin call.
getAdmins: function (toggle) {
var self = this;
if (!self.adminOpen || !toggle) {
$.ajax({
url: 'api/admins',
type: 'GET',
success: function (response) {
if (toggle) {
self.toggleAdminOpen();
}
self.admins = response;
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Error: ' + textStatus + '\n' + errorThrown);
}
});
}
else if (self.adminOpen) {
self.toggleAdminOpen();
}
}
If the browser is not making the call again, then try adding this JavaScript (before anything else):
$.ajaxSetup({cache: false}});
The effect of this is that jQuery will add the current time as a URL parameter in every AJAX call. It will be ignored by the server, but it makes the URL "different" so that the browser sees every call as unique and thus unable to use its own cache.

jquery Autocomplete - not filtering

I am trying to user JQuery autocomplete function with an ajax call in it, which will return the list of Strings..when i am trying to type, it is showing all the list of strings and not filtering out based on the input..Not sure where i am going wrong..Below is my autocomplete function..
$("#domainNameId").autocomplete({
source : function(request, response) {
console.log("in ajax ");
$.ajax({
url : "getAllDomains",
type : "GET",
contentType : "application/json",
data : {
env : $("#environment").val()
},
dataType : "json",
success : function(data) {
response(data); // list of strings..
},
error : function(x, t, m) {
console.trace();
if (!(console == 'undefined')) {
console.log("ERROR: " + x + t + m);
}
console.log(" At the end");
}
});
},
});
appreciate the help..
Your backend seems to always return the entire data and not do any filtering ( The service name itself is getAllDomains). In that case there is no need to use the function form of source option to make ajax calls.
What you're doing is sending multiple AJAX requests to the server as the user types. If your backend always returns the same data, there is no point in sending multiple requests to it. You can simply fetch the data once and then initialize the autocomplete with the response as source, then the widget will do the filtering as user types.
The docs says:
A response callback, which expects a single argument: the data to suggest to the user. This data should be filtered based on the provided term.
So if your server doesn't do the filtering, don't use the function form to make AJAX requests.
do something like:
$(function() {
// make a one-time request
$.ajax({
url: "getAllDomains",
type: "GET",
contentType: "application/json",
dataType: "json",
success: function(data) {
// init the widget with response data and let it do the filtering
$("#domainNameId").autocomplete({
source: data
});
},
error: function(x, t, m) {
console.trace();
if (!(console == 'undefined')) {
console.log("ERROR: " + x + t + m);
}
console.log(" At the end");
}
});
});
In the success callback, you will need to filter the data yourself using the request.term passed to the source function.
There is more information on the jQuery Autocomplete source here: https://api.jqueryui.com/autocomplete/#option-source.

AJAX response text for success

Right now I'm modifying my AJAX request to be asynchronous but I wanted to know if there was something similar to var reponse = $.ajax({ in success. Before I had my code as:
var response = $.ajax({
type : "GET",
url : url,
data : parameters,
cache : false,
async : false
}).responseText;
return response;
I tried doing using the first data argument but that just returns the parameters. Is there something similar I can use in success?
success : function(response) {
callBack(response);
}
Because the request is asynchronous you cannot just return the response.
jQuery uses something called "promises", which you can return instead:
function getUser(id) {
return $.ajax({
url: "/user",
data: { id:id },
});
}
So, whenever you want to get a user you just call the function:
var userRequest = getUser(123);
The userRequest variable now contains a "future promise". In other words, sometime in the future it will be ready for you to use it.
You cannot use it straight away but you can create a function that will run when it finally is ready. That is done using the .done() method:
userRequest.done(function (user) {
console.log("The user " + user.name + " has been loaded!");
});
If you, for example, also want to load the user's profile alongside the user then you can create two requests and then use the $.when() method:
var userRequest = getUser(123).
profileRequest = getProfileForUser(123);
$.when(userRequest, profileRequest).done(function (user, profile) {
console.log(user.name + " is " + profile.age + " years old");
});
Read more about promises over at jQuery.

How to override Backbone.sync so it adds the apikey and username at the end?

I am using backbone-tastypie, but I am having the toughest time getting it to work properly. In Tastypie, I am using ApiKeyAuthentication for my resources, so every ajax request, I need to append the apikey and username to the end of a request or send additional headers that add on the username and api key.
I am trying to remove a view and its model using backbone with the following code:
// Remove the goal update view from the DOM
removeItem: function() {
this.model.destroy({wait: true, success: function() {
console.log("success");
}, error: function() {
console.log("error");
}});
},
After the function executes, the browser tries to do a GET request on the following URL:
:8000/api/v1/update/2/
It does not include the api_key or username at the end, and it has a trailing slash at the end of the url. I think it is trying to use Backbone.oldSync to do the GET request. How would I make it so the sync does include the username/api key at the end and removes the trailing slash?
In all of the other requests, I have made it so the api key and username is appended to the end of the http request by adding the following code to backbone-tastypie:
if ( !resp && ( xhr.status === 201 || xhr.status === 202 || xhr.status === 204 ) ) { // 201 CREATED, 202 ACCEPTED or 204 NO CONTENT; response null or empty.
var location = xhr.getResponseHeader( 'Location' ) || model.id;
return $.ajax( {
url: location + "?" + "username=" + window.app.settings.credentials.username + "&api_key=" + window.app.settings.credentials.api_key,
success: dfd.resolve,
error: dfd.reject,
});
}
Let's explore the possibilities
Using headers
Backbone.sync still just uses jQuery ajax so you can override ajaxSend and use headers to send information along.
$(document).ajaxSend(function(e, xhr, options)
{
xhr.setRequestHeader("username", window.app.settings.credentials.username);
xhr.setRequestHeader("api_key", window.app.settings.credentials.api_key);
});
Using Ajax Options
If you need to send the information in just one or two locations, remember that the destroy, fetch, update and save methods are just shortcuts to the ajax caller. So you can add all jQuery ajax parameters to these methods as such:
// Remove the goal update view from the DOM
removeItem: function ()
{
this.model.destroy({
wait: true,
success: function ()
{
console.log("success");
},
error: function ()
{
console.log("error");
},
data:
{
username: window.app.settings.credentials.username,
api_key: window.app.settings.credentials.api_key
}
});
}
Overriding jQuery's ajax method
Depending on your needs, this might be the better implementation (note that this is no production code, you may need to modify this to fit your needs and test this before using it)
(function ($) {
var _ajax = $.ajax;
$.extend(
{
ajax: function (options)
{
var data = options.data || {};
data = _.defaults(data, {
username: window.app.settings.credentials.username,
api_key: window.app.settings.credentials.api_key
});
options.data = data;
return _ajax.call(this, options);
}
});
})(jQuery);
Just for future readers of this post, when you do a model.destroy() you can't pass any data because the delete request doesn't have a body, see this issue for more info:
https://github.com/documentcloud/backbone/issues/789

Categories

Resources