Bug in basic Ajax library - javascript

Been trying to create a basic Ajax library using JavaScript based upon a tutorial in the book "Build your own AJAX Web Applications" by Matthew Eernise (see here) as I want to get more in-depth knowledge of AJAX XML-RPC and REST. Based on the book I have created a JavaScript constructor function to get AJAX or an XMLHttpRequest going, but somehow I seem to suffer from an out-of-scope issue and the Ajax class is not defined in the following script:
function Ajax() {
// properties
this.req = null;
this.url = null;
this.method = 'GET';
this.asynch = true;
this.status = null;
this.statusText = '';
this.postData = null;
this.readyState = null;
this.responseText = null;
this.responseXML = null;
this.handleResp = null;
this.responseFormat = 'text',
// 'text', 'html', 'xml' or 'object'
this.mimeType = null;
} // End Constructor
//Create XMLHttpRequest method with XMLHttpRequest object
this.init = function() {
if (!this.req) {
try {
//Try to create objects for Firefox, Safari, IE7, etc.
this.req = newXMLHttpRequest();
}
catch(e) {
try {
//Try to create object for later versions of IE.
this.req = new ActiveXObject('MSXML2.XMLHTTP');
}
catch(e) {
try {
//Try to create for early versions of IE.
this.req = new ActiveXObject('Microsoft.XMLHTTP');
}
catch(e) {
//Could not create XMLHttpRequest object.
return false;
}
}
}
}
return this.req;
};
//Sending a Request method
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
//Setting up a request
//open methods with method, url and asycn yes or no
this.req.open(this.method, this.url, this.asynch);
//Make sure mimetype is OK
if (this.mimeType) {
try {
req.overrideMimeType(this.mimeType);
}
catch(e) {
//couldn't override MIME type ... IE6 or Opera?
}
}
var self = this; // fix loss-of-scope in inner function
this.req.onreadystatechange = function() {
var resp = null;
if (self.req.readyState == 4) {
//do stuff to handle response
switch (self.reponseFormat) {
case 'text':
resp = self.req.responseText;
break;
case 'xml':
resp = self.req.responseXML;
break;
case 'object':
resp = req;
break;
}
if (self.req.status >= 200 && self.req.status <= 299) {
self.handleResp(resp);
}
else {
self.handleErr(resp);
}
}
};
this.req.send(this.postData);
};
this.handleErr = function() {
var errorWin;
try {
errorWin = window.open('', 'errorWin');
errorWin.document.body.innerHTML = this.responseText;
}
catch(e) {
alert('An error occured, but this error message cannot be '
+ 'displayed. This is probably because of your browser\'s '
+ 'pop-up blocker. \n'
+ 'Please allow pop-ups from this website if you want to '
+ 'see the full error messages. \n'
+ '\n'
+ 'Status Code: ' + this.req.status + '\n'
+ 'Status description: ' + this.req.statusText);
}
};
this.abort = function() {
if (this.req) {
this.req.onreadystatechange = function() {};
this.req.abort();
this.req = null;
}
};
this.doGet = function (url, hand, format) {
this.url = url;
this.handleResp = hand;
this.responseFormat = format || 'text' ;
this.doReq();
};
The error I get on the page that loads this script with
var hand = function (str) {
alert(str);
}
var Ajax = new Ajax(); // new instance as can ben done with PHP5 constructor classes
ajax.doGet ('/fakeserverpage.php', hand);
and starts up a new instance of Ajax get the error ajax is not defined even though I did add var self = this; // fix loss-of-scope in inner function
to fix the scope issue. What am I missing?
Update 1
Thanks to a tip here Gave new instance a different name so they don't clash:
var hand = function (str) {
alert(str);
}
var ajax = new Ajax(); // new instance as can ben done with PHP5 constructor classes
ajax.doGet ('/fakeserverpage.php', hand);
Now I am a little further. Now I get a new error: Uncaught TypeError: Object #<Ajax> has no method 'doGet'
Update 2
I tried using Ajax.prototype.init instead of this.init as recommended by a co dev here, but I still have the same error..
Update 3
Thanks to #Soufiana Hassou I improved the code by adding Ajax.prototype to all methods. Did not know it was necessary for all to work with the constructor, but it is. Code is here http://pastebin.com/g86k0z8d . I now get this pop-up saying Could not create XMLHttpRequest object. This error message is built into the method so it is working, but it cannot create the object somehow. This means there must be an error in my request for an XMLHttpRequest as I covered all cases and tested this in Firefox 11 for Mac using code on my local MacPorts MAMP. Either that or there is something else I do not know about..
Update 4
Fixed a typo. Then I got a 404 loading the fake server page. Corrected path ajax.doGet ('/ajax/fakeserverpage.php', hand); so now OK. Only I need to get the PHP to generate the code so I get an OK. The header response is OK, but I do not see the AJAX alert yet. Then I checked the console and found this error:
self.req is undefined
http://localhost/ajax/ajax.js
Line 78
See latest code: http://pastebin.com/g86k0z8d . I added some more Ajax.prototype where I thought they were still needed. Now I get:
this.req is null
http://localhost/ajax/ajax.js
Line 100
Update 5
Made some more changes removing some selfs used initially for the out-of-scope issue using var self = this. Code is still the same pastebin, but I have updated it. Now I have:
Ajax.prototype.handleResp is not a function
http://localhost/ajax/ajax.js
Line 92
Update 6
I cleaned up some of the mistakes I made in the req.onreadystatechange = function() function and now I does run. I turned of Firefox pop-up blocker for localhost and on reload it opened another tab and showed the text undefined. So almost there. No errors, just no pop-up with OK. Chrome showed a pop-up with the undefined in the body. Updated code here: http://pastebin.com/g86k0z8d as usual

You are using the same name for your instance and the class itself.
Also, you are declaring Ajax and using ajax, Javascript is case-sensitive.

First, you have var Ajax = new Ajax(); You should have var ajax = new Ajax(); instead.
Secondly, using this outside of the constructor isn't referring to the Ajax object. Try using its prototype instead:
function Ajax() {
// Set properties here
}
Ajax.prototype.init = function() {
// ...
}
See this article on Javascript classes for more information.

Related

Vue error: In strict mode code, functions can only be declared at top level or inside a block

I'm running a Vue script with a text box and submit button, I'm calling an api to POST what I write in the textbox to the api and to return information back from the API, I'm getting this error mentioned in the title eventhough I've written the Javascript functions in vue as it should be?
With the script I'm first setting up a new XMLHttpRequest, initiating the header and api key for both GET and POST methods. I've then created 2 functions to get the data from the textbox and send them to the API, then making another button with the other function to send back the data.
I went through this approach because I kept getting a CORS issue and the API needed me to declare an access control origin header, is there anything I've done wrong with this code? Any help would be greatly appreciated
<script>
export default {
name: 'ProperForm'
}
methods: {
StartClient: function () {
this.get = function(Url, Callback){
var aHttpRequest = new XMLHttpRequest();
aHttpRequest.onreadystatechange = function() {
if (aHttpRequest.readyState == 4 && aHttpRequest.status == 200)
Callback(aHttpRequest.responseText);
}
aHttpRequest.open("GET", Url, true);
aHttpRequest.setRequestHeader("X-Api-Key", "eVnbxBPfn01kuoJIdfgi46TiYNv8AIip1r3WbjsX");
aHttpRequest.send(null);
}
this.post = function(Url, message, Callback) {
var aHttpRequest = new XMLHttpRequest();
aHttpRequest.onreadystatechange = function() {
if (aHttpRequest.readyState == 4 && aHttpRequest.status == 200)
Callback(aHttpRequest.responseText);
}
aHttpRequest.open("POST", Url, true);
aHttpRequest.setRequestHeader("x-api-key", "eVnbxBPfn01kuoJIdfgi46TiYNv8AIip1r3WbjsX");
aHttpRequest.send(message);
}
}
var client = new StartClient();
submitData: function () {
document.getElementById('inputBox').disabled = true;
var targetInputButton = document.getElementById("inputBox").value;
var message = '{"targetInputButton":"' + targetInputButton + '"}';
client.post('https://le75bkfcmg.execute-api.eu-west-2.amazonaws.com/dev/start-trace', message, function(response) {
document.getElementById('jobId').innerHTML = response;
});
}
sendBackData: function () {
var jobId = document.getElementById("jobId").innerHTML;
var message = '{"jobId":"' + jobId + '"}';
client.post('https://le75bkfcmg.execute-api.eu-west-2.amazonaws.com/dev/check-trace', message, function(response) {
document.getElementById('report').innerHTML = response;
});
}
}
</script>
New way I wrote var client:
StartClient: function () {
var client
},
You need put your methods object inside export and split the methods to comma
<script>
export default {
name: 'name',
methods:{
foo(){
},
bar(){
}
}
}
UPD: var client = new StartClient();
defined outside the method

How to remote debug console.log

Problem: when developing an Ionic2 app, I would like to see the console.log messages generated on my IPhone but I do not have a Mac or I do have one but found that the web inspector feature sucks.
Note this is applicable to any kind of remote javascript, not only to Angular/ionic.
This is a Q&A style question, meaning I'll provide the answer below because I think it's very useful for lots of people.
The solution is a hook into your javascript that will intercept all console.log and errors and send them to a server.
Place the following code into your index.html page:
<script>
// Function that will call your webserver
logToServer = function(consoleMsg) {
// only do this if on device
if (window.cordova) {
let jsonTxt = customStringify(consoleMsg);
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", 'http://yourserver/console2server.php?msg=' + jsonTxt, true); //async
xmlHttp.send(null);
}
}
// Test if you receive this on the server
logToServer("OPENING IONIC APP");
// intercept console logs
(function () {
var oldLog = console.log;
console.log = function (message) {
// DO MESSAGE HERE.
logToServer(message);
oldLog.apply(console, arguments);
};
})();
// intecept errors
if (window && !window.onerror) {
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
logToServer(errorMsg);
logToServer(errorObj);
return false;
}
}
// this is optional, but it avoids 'converting circular structure' errors
customStringify = function (inp) {
return JSON.stringify(inp, function (key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
console.log("circular dep found!!");
return;
}
// Store value in our collection
cache.push(value);
}
return value;
});
}
</script>
On the server side, I use PHP but you could use whatever you want:
<?php
//allow CORS request
header('Access-Control-Allow-Origin: *');
if(isset($_GET['msg'])) {
//you can also log to a file or whatever, I just log to standard logs
error_log("[CONSOLE.LOG] ".json_decode($_GET['msg'], true));
}
?>
Happy debugging!

ag-grid + templateUrl crashes in IE9

I just implemented ag-grid, but found that IE9 crashes when using cellTemplates with angular compiled templates inside.
Did any of you encounter this and maybe found a workaround?
How to reproduce:
Head here (http://www.ag-grid.com/angular-grid-cell-template/index.php) with IE, and from DevTools, select IE9.
It will crash because of the angular compiled templates. Not sure what I can do about it.
(I also opened an issue on GitHub on this: https://github.com/ceolter/ag-grid/issues/521 )
EDIT:
Debugged it, there's an infinite loop because an update to an array from one method, is not visible to another method somehow...
The infinite loop is:
getTemplate, (wait in line until the call ends), call ends, template added to cache, run callback, callback doesn't see the template in templateCache, creates another callback, adds it to the queue, and so on.
(code from ag-grid below).
// returns the template if it is loaded, or null if it is not loaded
// but will call the callback when it is loaded
TemplateService.prototype.getTemplate = function (url, callback) {
var templateFromCache = this.templateCache[url];
if (templateFromCache) {
return templateFromCache;
}
var callbackList = this.waitingCallbacks[url];
var that = this;
if (!callbackList) {
// first time this was called, so need a new list for callbacks
callbackList = [];
this.waitingCallbacks[url] = callbackList;
// and also need to do the http request
var client = new XMLHttpRequest();
client.onload = function () {
that.handleHttpResult(this, url);
};
client.open("GET", url);
client.send();
}
// add this callback
if (callback) {
callbackList.push(callback);
}
// caller needs to wait for template to load, so return null
return null;
};
TemplateService.prototype.handleHttpResult = function (httpResult, url) {
if (httpResult.status !== 200 || httpResult.response === null) {
console.warn('Unable to get template error ' + httpResult.status + ' - ' + url);
return;
}
// response success, so process it
this.templateCache[url] = httpResult.response;
// inform all listeners that this is now in the cache
var callbacks = this.waitingCallbacks[url];
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
// we could pass the callback the response, however we know the client of this code
// is the cell renderer, and it passes the 'cellRefresh' method in as the callback
// which doesn't take any parameters.
callback();
}
if (this.$scope) {
var that = this;
setTimeout(function () {
that.$scope.$apply();
}, 0);
}
};
return TemplateService;
})();
I eventually found the issue.
In IE9, the template is on responseText inside the response.
In IE10+ and all other browsers it's on response.
So in order to fix it, in the above code, instead of:
// response success, so process it
this.templateCache[url] = httpResult.response;
I added:
// response success, so process it
//in IE9 the response is in - responseText
this.templateCache[url] = httpResult.response || httpResult.responseText;
For future reference, adding the answer here.
Had nothing to do with Angular. :)
UPDATE:
https://github.com/ceolter/ag-grid/issues/521
Code got into the repo :)
Thanks Niall Crosby (ceolter).

Monkey patch XMLHTTPRequest.onreadystatechange

How would go about monkey patching the XMLHTTPRequest's onreadystatechange function. I'm trying to add a function that would be called when every ajax request made from a page come back.
I know this sounds like a terrible idea, but the use case is quite peculiar. I want to use a certain SDK with a console (jqconsole) but show status and results from ajax calls within the console without modifying the external SDK.
I've looked at this post which had great info, but nothing on monkey patching the callback which seem to exceed my JavaScript skills.
P.S Can't use jQuery since it only supports ajax calls made from jQuery not from XMLHTTPRequests directly which is the case here.
To monkey-patch XMLHttpRequests, you need to know how an AJAX request is generally constructed:
Constructor invocation
Preparation the request (setRequestHeader(), open())
Sending the request (.send).
General-purpose patch
(function(xhr) {
function banana(xhrInstance) { // Example
console.log('Monkey RS: ' + xhrInstance.readyState);
}
// Capture request before any network activity occurs:
var send = xhr.send;
xhr.send = function(data) {
var rsc = this.onreadystatechange;
if (rsc) {
// "onreadystatechange" exists. Monkey-patch it
this.onreadystatechange = function() {
banana(this);
return rsc.apply(this, arguments);
};
}
return send.apply(this, arguments);
};
})(XMLHttpRequest.prototype);
The previous assumed that onreadystatechange was assigned to the onreadystatechange handler. For simplicity, I didn't include the code for other events, such as onload. Also, I did not account for events added using addEventListener.
The previous patch runs for all requests. But what if you want to limit the patch to a specific request only? A request with a certain URL or async flag and a specific request body?
Conditional monkey-patch
Example: Intercepting all POST requests whose request body contains "TEST"
(function(xhr) {
function banana(xhrInstance) { // Example
console.log('Monkey RS: ' + xhrInstance.readyState);
}
//
var open = xhr.open;
xhr.open = function(method, url, async) {
// Test if method is POST
if (/^POST$/i.test(method)) {
var send = this.send;
this.send = function(data) {
// Test if request body contains "TEST"
if (typeof data === 'string' && data.indexOf('TEST') >= 0) {
var rsc = this.onreadystatechange;
if (rsc) {
// Apply monkey-patch
this.onreadystatechange = function() {
banana(this);
return rsc.apply(this, arguments);
};
}
}
return send.apply(this, arguments);
};
}
return open.apply(this, arguments);
};
})(XMLHttpRequest.prototype);
The main techniques used is the transparent rewrite using...
var original = xhr.method;
xhr.method = function(){
/*...*/;
return original.apply(this, arguments);
};
My examples are very basic, and can be extended to meet your exact wishes. That's up to you, however.
Assuming you can ignore IE...
//Totally untested code, typed at the SO <textarea>... but the concept *should* work, let me know if it doesn't.
var OldXMLRequest = XMLHttpRequest;
// Create a new instance
function XMLHttpRequest() {
var ajax = new OldXMLRequest();
// save old function
var f = ajax.onreadystatechange;
ajax.onreadystatechange = function() {
console.log("Whatever!");
f(); // Call the old function
}
return ajax;
}
you can learn from Ajax-hook written by chinese!
it is a advanced js to enable Monkey patch XMLHTTPRequest

How to set my local javascript variable as a json data on remote website

I have a javascript code on my website, there is a variable:
var remoteJsonVar;
On the other hand there is a json file on a remote website
https://graph.facebook.com/?ids=http://www.stackoverflow.com
I need to set the variable remoteJsonVar to this remote jason data.
I am sure that it is very simple, but I can't find the solution.
A small working example would be nice.
Because you're trying to get the data from a different origin, if you want to do this entirely client-side, you'd use JSON-P rather than just JSON because of the Same Origin Policy. Facebook supports this if you just add a callback parameter to your query string, e.g.:
https://graph.facebook.com/?ids=http://www.stackoverflow.com?callback=foo
Then you define a function in your script (at global scope) which has the name you give in that callback parameter, like this:
function foo(data) {
remoteJsonVar = data;
}
You trigger it by creating a script element and setting the src to the desired URL, e.g.:
var script = document.createElement('script');
script.src = "https://graph.facebook.com/?ids=http://www.stackoverflow.com?callback=foo";
document.documentElement.appendChild(script);
Note that the call to your function will be asynchronous.
Now, since you may want to have more than one outstanding request, and you probably don't want to leave that callback lying around when you're done, you may want to be a bit more sophisticated and create a random callback name, etc. Here's a complete example:
Live copy | Live source
(function() {
// Your variable; if you prefer, it could be a global,
// but I try to avoid globals where I can
var responseJsonVar;
// Hook up the button
hookEvent(document.getElementById("theButton"),
"click",
function() {
var callbackName, script;
// Get a random name for our callback
callbackName = "foo" + new Date().getTime() + Math.floor(Math.random() * 10000);
// Create it
window[callbackName] = function(data) {
responseJsonVar = data;
display("Got the data, <code>shares = " +
data["http://www.stackoverflow.com"].shares +
"</code>");
// Remove our callback (`delete` with `window` properties
// fails on some versions of IE, so we fall back to setting
// the property to `undefined` if that happens)
try {
delete window[callbackName];
}
catch (e) {
window[callbackName] = undefined;
}
}
// Do the JSONP request
script = document.createElement('script');
script.src = "https://graph.facebook.com/?ids=http://www.stackoverflow.com&callback=" + callbackName;
document.documentElement.appendChild(script);
display("Request started");
});
// === Basic utility functions
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
function hookEvent(element, eventName, handler) {
// Very quick-and-dirty, recommend using a proper library,
// this is just for the purposes of the example.
if (typeof element.addEventListener !== "undefined") {
element.addEventListener(eventName, handler, false);
}
else if (typeof element.attachEvent !== "undefined") {
element.attachEvent("on" + eventName, function(event) {
return handler(event || window.event);
});
}
else {
throw "Browser not supported.";
}
}
})();
Note that when you use JSONP, you're putting a lot of trust in the site at the other end. Technically, JSONP isn't JSON at all, it's giving the remote site the opportunity to run code on your page. If you trust the other end, great, but just remember the potential for abuse.
You haven't mentioned using any libraries, so I haven't used any above, but I would recommend looking at a good JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others. A lot of the code above has already been written for you with a good library. For instance, here's the above using jQuery:
Live copy | Live source
jQuery(function($) {
// Your variable
var responseJsonVar;
$("#theButton").click(function() {
display("Sending request");
$.get("https://graph.facebook.com/?ids=http://www.stackoverflow.com&callback=?",
function(data) {
responseJsonVar = data;
display("Got the data, <code>shares = " +
data["http://www.stackoverflow.com"].shares +
"</code>");
},
"jsonp");
});
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
});

Categories