Use Object Instance Function as JSONP Callback Function
In my previous posts, a basic JSONP example [1], and another example of JSONP with anonymous function as callback [2] are shown. In this post, an interesting example of using object instance function (or method) as callback function in JSONP will be shown. The is helpful if we want to make the code more object-oriented.
The following is complete sample code.
index.html (run on client side, i.e., browser):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!doctype html> <html> <head> <meta charset="utf-8"> <title>JSONP with object instance function as callback</title> </head> <body onload="var myObj = new demoObj('demoForm');"> <br style="line-height: 3em;" /> <form id="demoForm" style="text-align: center;"> <input id="input1" type="text" value=""><br /> <input id="input2" type="text" value=""><br /> <input type="submit" value="Submit"> </form> <br style="line-height: 3em;" /> <div id="info" style="text-align: center;"> try to input something and then submit. a JSONP request will be sent to Google App Engine Python server and then response will be sent back. </div> <script src="jsonp.js"></script> </body> </html> |
Note
The action property of form element is not set in HTML. And we initialize an object at the onload event the body element. The form onsubmit event will be set in the constructor of the object.
jsonp.js (run on client side, i.e., browser):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // object contructor method demoObj = function(formId) { var form = document.getElementById(formId); form.action = "javascript:void(0);"; form.onsubmit = this.jsonp.bind(this); window['demoObjInGlobal'] = this; }; demoObj.prototype.jsonp = function() { /* send data to Google App Engine Python server */ var input1value = document.getElementById('input1').value; var input2value = document.getElementById('input2').value; var url = '/jsonp?callback=' + encodeURIComponent('demoObjInGlobal["callback"]') + '&input1=' + encodeURIComponent(input1value) + '&input2=' + encodeURIComponent(input2value); var ext = document.createElement('script'); ext.setAttribute('src', url); document.getElementsByTagName("head")[0].appendChild(ext); }; demoObj.prototype.callback = function(JSONdata) { /* In order to parse data, we have to know the structure of data from server in advance */ /* show data returned from server */ var infoElm = document.getElementById('info'); infoElm.innerHTML = 'input1: ' + JSONdata[0]['input1'] + '<br />'; infoElm.innerHTML += 'input2: ' + JSONdata[1]['input2'] + '<br />'; infoElm.innerHTML += JSONdata[2] + '<br />'; infoElm.innerHTML += JSONdata[3] + '<br />'; }; |
Note
form.onsubmit = this.jsonp.bind(this);
The bind function is used to keep this keyword refering to the object self when jsonp function is called.
window['demoObjInGlobal'] = this;
an alias name of the object instance in global scope. This is used to refer to this object instance in the communication of HTTP GET request. A better way here is to generate an un-used random name as alias name. If more than one instance of the object is needed, you have to generate different names for each object instances to prevent name conflict. For simplicity of the example, I use fixed name for the alias.
var url = '/jsonp?callback=' +
encodeURIComponent('demoObjInGlobal["callback"]') +
'&input1=' + encodeURIComponent(input1value) +
'&input2=' + encodeURIComponent(input2value);
The alias name of the object instance is supplied to make the callback function of the object instance being executed after the script tag is appended to the head element. This is the main trick to make our object instance function to execute.
jsonp.py (run on server side, i.e., GAE Python):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #!/usr/bin/env python # -*- coding:utf-8 -*- import webapp2 import urllib2 import json class JSONPPage(webapp2.RequestHandler): def get(self): input1 = urllib2.unquote(self.request.get('input1')) input2 = urllib2.unquote(self.request.get('input2')) result = [{ 'input1': input1 }, { 'input2': input2 }, ('message #1', 'from', 'server'), ('message #2', 'from', 'server')] self.response.headers['Content-Type'] = 'application/javascript' self.response.out.write( "(%s)(%s);" % (urllib2.unquote(self.request.get('callback')), json.dumps(result)) ) application = webapp2.WSGIApplication([ ('/jsonp', JSONPPage), ], debug=True) |
Note
The above code on server side is basically the same as that in [2].
app.yaml (on server side, GAE Python config file):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | application: jsonp-object-instance-callback version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: / static_files: index.html upload: index.html - url: /jsonp.js static_files: jsonp.js upload: jsonp.js - url: /jsonp script: jsonp.application |
In summary, the benefit of wrapping JSONP functionality inside an object is to make the code modular, reusable, and more object-oriented.
Tested on: Ubuntu Linux 14.10, Google App Engine Python SDK 1.9.18
References:
[1] | JSONP on Google App Engine Python |
[2] | (1, 2) JSONP with Anonymous Callback Function |