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):

index.html | repository | view raw
 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):

jsonp.js | repository | view raw
 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):

jsonp.py | repository | view raw
 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):

app.yaml | repository | view raw
 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