I am working with a RESTful API, and my Javascript code is making REST queries via jQuery's $.ajax() call.
I have implemented a javascript Rest class, which I will show below (greatly simplified):
var Rest = function (baseUrlPath, errorMessageHandler) {
...
};
// Declare HTTP response codes as constants
Rest.prototype.STATUS_OK = 200;
Rest.prototype.STATUS_BAD_REQUEST = 400;
... // other rest methods
Rest.prototype.post = function (params) {
$.ajax({
type: 'POST',
url: params.url,
data: params.data,
dataType: 'json',
contentType: 'application/json; charset=utf-8',
beforeSend: this._authorize,
success: params.success,
error: params.error || this._getAjaxErrorHandler(params.errorMessage)
});
};
... // more rest methods
Rest.prototype.executeScenario = function (scenarioRef) {
var self = this;
this.post({
url: 'myurlgoeshere',
data: 'mydatagoeshere',
success: function (data, textStatus, xhr) {
if (xhr.status == 200) {
console.log("everything went ok");
}
},
error: function (xhr, textStatus, errorMsg) {
// TODO: constants
if (404 == xhr.status) {
self.errorMessageHandler("The scenario does not exist or is not currently queued");
} else if (403 == xhr.status) {
self.errorMessageHandler("You are not allowed to execute scenario: " + scenarioRef.displayName);
} else if(423 == xhr.status) {
self.errorMessageHandler("Scenario: " + scenarioRef.displayName + " is already in the queue");
}
}
});
};
The code works as intended, however I have decided to add some constants to help beautify the code and improve readability. I have for example several places in my code where I am checking for xhr.status == 200 or xhr.status == 400 and so on.
I can declare class variables as Rest.prototype.STATUS_OK = 200;
But variable is editable, and I cannot think of how to make them constant. In my code for example I can do a this.STATUS_OK = 123; and this will modify the variable. I have played around with the const keyword, with no luck.
i have seen this: Where to declare class constants?, but it was not much help.
Can someone point me in the right direction as to how to make these fields a constant literal instead of a variable?
Using ECMAScript 5's Object.defineProperty you can make a value un-settable:
Object.defineProperty(Rest, "STATUS_OK", {
enumerable: false, // optional; if you care about your enumerated keys
configurable: false,
writable: false,
value: 200
});
Or, since those are the default values, simply do:
Object.defineProperty(Rest, "STATUS_OK", { value: 200 });
This makes Rest.STATUS_OK yield 200 when accessed, but it will not respond to attempts to redefine it or delete it. Furthermore, configurable: false will prevent any attempt to redefine the property with a subsequent defineProperty call.
However, this doesn't work in older browsers that don't support ES5's defineProperty (notably IE8 and below).
This will not be possible in Javascript. Best thing you could probably do, is create some closure like stuff:
var StatusCode = (function() {
var STATUS_OK = 200,
STATUS_BAD_REQUEST = 400;
return {
getOk: function() {
return STATUS_OK;
},
getBadRequest: function() {
return STATUS_BAD_REQUEST;
}
}
});
And use it like StatusCode.getOk() === 200. This would help you to not be able to change those 'constants', but will again be bad for your readability (this is probably opinion based).
I would just keep those constants all uppercase to mark them as constant, although they could be changed.
You could define the statuses as getters, but AFAIK this won't work in IE8 and older.
var Rest = function (baseUrlPath, errorMessageHandler) {
this.STATUS_OK = 123; // trying to override.
};
// Declare HTTP response codes as constants
Rest.prototype = {
get STATUS_OK(){ return 200; },
get STATUS_BAD_REQUEST(){ return 400; }
}
var client = new Rest();
console.log( client.STATUS_OK ); // 200!
client.STATUS_OK = 123;
console.log( client.STATUS_OK ); // still 200!
More on getters and setters: http://ejohn.org/blog/javascript-getters-and-setters/
Javascript doesn't have a good support to create immutable constants.
Even the const keyword isn't recommended because doesn't work in some browsers.
I think the best way todo it is using Object.freeze:
Rest.Status = {};
Rest.Status.Ok = "Ok";
Object.freeze(Rest.Status);
Object.freeze will silent ignore changes in the Status object.
By example:
Rest.Status.Ok = "foo";
Rest.Status.Ok; //=> "Ok"
But just work in ECMAScript 5 or above.
Above I have placed the status in a Status object, I think that it is more interesting than prototype, because prototype is more close to instance methods, properties etc.
And the Status object seen like a enumeration.
Related
I'm learning JS & keep running into the problem of how to put documentation together to do this or that. e.g. Greasemonkey is documented, but you have to know a lot of other context not even referred to to use the greasepot wiki very well.
I've been trying various combinations of the following, for example, and I can only ever get "undefined" from the GM_xmhttprequest function, though:
var url = "https://en.wikipedia.org/wiki/CURL?action=render";
var fetchContent = console.log( function getContent(url) {
if (url.length < 0) {
return 0 ;
}
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: {
"User-Agent": "Mozilla/5.0", // If not specified, navigator.userAgent will be used.
//"Accept": "text/xml" // If not specified, browser defaults will be used.
},
onload: function(response) {
//var responseXML = null;
alert(response.responseText);
// Inject responseXML into existing Object (only appropriate for XML content).
/*
if (!response.responseXML) {
responseXML = new DOMParser()
.parseFromString(response.responseText, "text/xml");
}
GM_log([
response.status,
response.statusText,
response.readyState,
response.responseHeaders,
response.responseText,
response.finalUrl,
responseXML
].join("\n"));
*/
}
});
} )
Yet I'm not sure I'm using it correctly:
Need to define something in 'onload'??
Need to create a var prior? (e.g. var responseHoldingObject = new Object(); ?) ?
etc.
And any advice to get the page-fetching I'm looking for going is appreciated. Goal is to fetch content and ultimately append it within another page (e.g. such as within textarea or div).
Learning JS with GreaseMonkey might be a bit advanced.
fetchContent will be assigned the return value of console.log, which is undefined because console.log does not return a value. And what you're logging is the function getContent itself. getContent is never evaluated.
Finally, you can never get a response from an asynchronous function to use outside of the callback reliably (except with polling). Basically it should be something like this (untested):
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: {
"User-Agent": "Mozilla/5.0", // If not specified, navigator.userAgent will be used.
//"Accept": "text/xml" // If not specified, browser defaults will be used.
},
onload: function(response) {
/* use response here */
}
});
/* can't use response here */
without the fetchContent = console.log thing.
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);
}
I want to build up an flexible small API to backend for the client. To provide some convenience in using default success-/error- handlers or writing own ones, I planned to support the user with the option to pass both handlers one or none. I tried as follows.
Snippet of file "API" to backend:
function someRPCcall(method, url, data, successHandler, failedHandler) {
// checking if successHandler and failedHandler are defined and passed functions
if (paramType1 === 'undefined' || paramType1 === 'null') {
successHandlerHelper = defaultSuccessRESTHandler;
} else if (paramType1 === 'function') {
successHandlerHelper = successHandler;
}
if (paramType2 === 'undefined' || paramType2 === 'null') {
failedHandlerHelper = defaultFailedRESTHandler;
} else if (paramType2 === 'function') {
failedHandlerHelper = failedHandler;
}
ajaxCall(method, url, data, successHandlerHelper, failedHandlerHelper);
}
function ajaxCall(method, url, data, success, failed) {
console.log("in ajaxCcall");
$.ajax({
type: method,
contentType: "application/json",
data: data,
url: url,
success: success,
error: failed
dataType: "json"
});
console.log("ajaxCall - call done");
}
Snippet of file of client code
someRPCcall will be indirectly called by functions in another file:
someFunctionRPCcall("bla", null, errorHandler);
whereas searchWordOccurrenceRPCcall is calling someRPCcall
Above the failureHandler would be a selfdefined Handler, but following calls also should be possible:
someFunctionRPCcall("bla", null, successHandler, errorHandler);
someFunctionRPCcall("bla", null, successHandler);
someFunctionRPCcall("bla", null);
I heard about taking an object, in which the functions would be defined....
This code does not call the self defined handlers, but I guess this is another problem (using apply is missing or something like that)
The question I wanted to put here is:
Is there a way to pass both functions as optional parameters? How about that proposal useing objects?
Thanks
As you mentioned, you can get your function to take an object, eg:
function someRPCcall(args) {
var method = args.method || "POST";
var url = args.url || "default";
var data = args.data || {};
var successHandler = args.success || defaultSuccessRESTHandler;
var failedHandler = args.failed || defaultFailedRESTHandler;
ajaxCall(method, url, data, successHandler, failedHandler);
}
You will notice above that if a property is not present on the args object then it is given a default.
Then call it as follows:
someRPCcall({
url : "the url",
failed: function() { ... }
});
Of course, you could throw an exception if some values are not set, eg url:
function someRPCcall(args) {
if(!args.url) {
throw "url must be set";
}
}
i faced issue while writing a class specially for handling Ajax calls & its response.
Purpose of below code : Based on user records exist in database, i need to set the flag "isPersonalDivRequiredFlag" for further use.
// New ajax request class
function ajaxRequestHandler(){
this.needOutPutInJSON = 0;
this.url = "/libraries/ajaxHelper.php";
this.requestType = "POST";
this.isPersonalDivRequiredFlag = 0;
};
// Methods written for Class ajaxRequestHandler
ajaxRequestHandler.prototype = {
sentRequest : function(userData, typeID){
var options = {type: this.requestType, context: this, url: this.url, data: { data: userData, type: typeID }};
if(this.needOutPutInJSON){
options.dataType = "json";
}
// Check whether user want to see the response
options.success = function(rec){
if(rec == "1"){
this.isPersonalDivRequiredFlag = 1;
}
};
//Jquery Ajax
$.ajax(options);
},
enableJSONoutput : function(){
this.needOutPutInJSON = 1;
},
getFlagValue : function(){
return this.isPersonalDivRequiredFlag;
},
setFlagValue : function(){
console.log('Setflag Func called.');
this.isPersonalDivRequiredFlag = 1;
}
};
And i use the code as below.
var newRequest = new ajaxRequestHandler();
console.log('Before change [Value of isPersonalDivRequiredFlag variable] : ' + newRequest.getFlagValue()); // Output 0
newRequest.sentRequest({}, "recordExist");
console.log('After change [Value of isPersonalDivRequiredFlag variable] : ' + newRequest.getFlagValue()); // Output 0
And when i set the flag "isPersonalDivRequiredFlag " to 1 inside the Success method of Ajax call but its unable to retain this value when it will be accessed through its own method "getFlagValue" function.
The whole piece of code will work fine if i remove Ajax call function & made it a normal prototype method. So i know the cause but not able to find any solution :(
what the things i tried already?
I found some techniques people suggested while Google it over internet.
a) Used this configuration inside Ajax call but no luck :(
context:this
b) inside Success method :
var myclass = this;
And called the prototype function as below
myclass.setFlagValue();
but no luck :(
I'm using backbone and I have this code:
render: function() {
var displayData = this.model;
fetchData('checkComplete', this.model.id, function(checkCompleteData) {
displayData.complete = checkCompleteData; // Value is true
});
fetchData('showGraphicAssets', this.model.id, function(showGraphicsData) {
$.each(showGraphicsData, function(index, value) {
if (value.type === "GRAPHIC_A") {
displayData.assetId = value.id; // Value is 808
}
});
});
console.log(displayData.complete); // Valus is undefined
console.log(displayData.assetId); // Value is undefined
var html = this.template.tmpl(displayData.toJSON());
$(this.el).html(html);
}
fetchData = function(dataRequest, id, callback) {
request = '/show/' + id + '/completed.json';
$.ajax({
url: '/app' + request,
type: 'GET',
dataType: 'json',
success: function(data) {
callback(data);
}
});
};
As you can see, I have the variable displayData defined outside of the fetchData functions. When I try to add properties inside the fetchData context, it works, and when I try to use it outside the fetchData context, it results to undefined. How can I fix this?
It is the most common problem people have with JavaScript. You do not honor the asynchronous nature of your callbacks.
You can not set a value in a callback function (like you do with displayData.assetId) and assume that it must be available a few lines later. It's not. It will never be.
It will be available when the callbacks are done. Since you have two of them, use jQuery's $.when()function (and its companions .then(), .fail() and .always()) which have been made specifically for handling multiple asynchronous calls:
render: function () {
var self = this, model = self.model, template = self.template;
$.when(
$.get('/app/show/checkComplete/' + model.id + '/completed.json'),
$.get('/app/show/showGraphicAssets/' + model.id + '/completed.json')
)
.then(function (checkCompleteData, showGraphicsData) {
// this will receive the response data in the correct order
model.complete = checkCompleteData;
$.each(showGraphicsData, function (index, value) {
if (value.type === "GRAPHIC_A") {
model.assetId = value.id;
return false; // break the $.each()
}
});
$(self.el).html( template.tmpl(model.toJSON()) );
})
.fail(function () {
// this will be called if either HTTP request fails
// also check out the arguments and react accordingly to errors
});
}
Note how the code also gets shorter as you need less boilerplate.
If you've made sure that your views use the correct Content-Type (application/json) then $.get() is enough; avoiding $.ajax() is visually much cleaner.