Archive for the ‘Jaxer’ Category

MapQuest Proxy for Jaxer

Friday, January 16th, 2009

If you’re a MapQuest developer chances are you’re pretty handy with JavaScript on the client. It’s been the sad case that you leave these skills behind when you switch over to server side programming. Well, happy day, things are changing as support for Server Side JavaScript (SSJS) takes off. With SSJS you can break out your JavaScript Ninja skills on the server side. I’ve been looking at the Jaxer SSJS platform and worked up this example to show off some of the possibilities.

With the MapQuest JavaScript API one can get tiled maps on a page with only client JavaScript. As soon as you use other MapQuest Services such geocoding, routing, or search, you need to supply a proxy on the web server. The proxy is need to allow the MapQuest client library to call home while avoiding the browser’s same-domain security policy.

The example I’m presenting here is an implementation of such a proxy for the Jaxer platform. I think a SSJS proxy is an illuminating and useful example. It’s a useful example since you need a proxy for putting MapQuest and Jaxer together. Also, it’s an illuminating example since it is a typical server side task, fetching data from a different source. The source could be files, in database, RSS feeds, or, as in this case, a server in a different domain. MapQuest (MQ) supplies proxies in common languages such PHP, Java, etc., so this example will also allow comparison of the SSJS implementation to implementations in other languages.

The example consists of two HTML pages, one of which is served to the client and one which runs on the Jaxer server and acts as the proxy.

The Client Side

The client page is a hello world type of example for MapQuest – create a tiled map, geocode an address, and place a Point of Interest marker at the geocoded address. The complete source code for this example is available here. Here’s a screenshot of the client page in action:

The text boxes on the right are a debug log provided the MQ client library that shows interaction between the MQ client library and the proxy. Specifically, the box labeled “Request URL” is address where the MapQuest client library is configured to find the proxy. The Request XML box shows the data the client library is sending, which in this case is a request for geocoding per the MQ API (see the MQ XML Interface Reference). The “Response XML” is the XML returned by the MapQuest geocoding service via the proxy.

Here’s the startMap() method that is called when the client page loads:

<script src="http://btilelog.access.mapquest.com/tilelog/transaction?transaction=script&amp;amp;amp;amp;amp;key=your-MQ-key=true&amp;amp;amp;amp;amp;v=5.3.s&amp;amp;amp;amp;amp;ipkg=controls1" type="text/javascript"></script>
<script src="lib/mapquest/mqcommon.js"></script>
<script src="lib/mapquest/mqutils.js"></script>
<script src="lib/mapquest/mqobjects.js"></script>
<script src="lib/mapquest/mqexec.js"></script>
<script>
var g_proxyServerName = 'localhost';
var g_proxyServerPort = '8000';
var g_proxyServerPath = 'jaxer-mapquest/proxy.html'

var g_serverName = 'geocode.dev.mapquest.com';
var g_serverPort = '80';
var g_serverPath = 'mq';

var g_geoExec = new MQExec(g_serverName, g_serverPath, g_serverPort, g_proxyServerName, g_proxyServerPath, g_proxyServerPort);

function startMap(){
var g_mqMap = new MQA.TileMap(document.getElementById('mapWindow'), 2, new MQA.LatLng(40, -95), "map");

var address = new MQAddress();
address.setCity('Gobles');
address.setState('MI');
address.setCountry('USA');

var gaCollection = new MQLocationCollection("MQGeoAddress");
g_geoExec.geocode(address, gaCollection);
var mqAddress = gaCollection.get(0);

var poi = new MQA.Poi(mqAddress.getMQLatLng());
poi.setInfoTitleHTML('Hello World');
poi.setInfoContentHTML('From Gobles, MI');
g_mqMap.addPoi(poi);
}
</script>

The MQExec object interacts with the proxy. The location of the proxy on localhost is specified as well as the details of the proxied MapQuest server (geocode.dev.mapquest.com). When the MQ client library needs to call home for geocoding services (via g_geoExec.geocode()), it will form a GET or POST with the request details and send the request to http://localhost:8000/jaxer-mapquest/proxy.html.

The Server Side

The sever side proxy is implemented in a page named proxy.html. The page receives the post from the MQ client library, extracts the data, forwards it along to the MQ server, and then returns the MQ response back to the client.

The HTML page, proxy.html, is processed by the Jaxer server. We’ll use a slightly different processing model than the typical Jaxer page lifecycle. In the typical lifecycle a page is parsed on the Jaxer server into a DOM model, scripts that are tagged to be ran on the server are invoked, client side proxies are mixed-in for scripts that are tagged to be proxied, and then the page is serialized and sent off to the client. In this case, we use server side scripts to overwrite the the DOM on the server side prior to the serialization for returning the page to the client.

This is a handy Jaxer technique. Since you set the content type and the response content you can pull stunts like serializing arbitrary JavaScript objects and returning them as JSON to the client. In this case, I’m overwriting the HTML page and returning the content and content type from the map quest server (XML). To me, this technique is the more similar to a J2EE Servlet than the typical Jaxer lifecycle, which feels more like a JSP.

The proxy.html page consist primarily of JavaScript that get executes in the server context. The server side script execution is triggered with a onserverload tag:

<body onserverload="BCC.mqproxy.proxify();">

The proxify() method shows the general steps that the proxy executes:

proxify: function(){
parseClientRequest();
makeMQRequest();
replyToClient();
}

In order to overwrite the page’s DOM with arbitrary content, one calls the Jaxer.Response.setContent() method. In this case, we’ll overwrite the page’s entire DOM with the response and content type obtained from the MQ response. The property Jaxer.response is an instance of Jaxer.Response that points to the current response:

var replyToClient = function(){
Jaxer.response.headers['Content-Type'] = mqResponse.headers['Content-Type'];
Jaxer.response.setContents(mqResponse.text);
}

And that’s all it takes to send back arbitrary content from a Jaxer HTML page.

Before we can reply to the client, this page has to parse the client request and call the server at MapQuest. The following function reads the data posted by the client from Jaxer.request (an instance of Jaxer.Request):

var parseClientRequest = function(){
getMqServerUrlParams();
if (Jaxer.request.method == 'POST') {
getPostData();
}
else {
getUrlParms();
}
};

The MQ client sends the target server, port and path as URL parameters and the getMQServerUrlParams() method picks these off. Jaxer makes the parsed URL available as a property of the request object:

var serverParams = {
sname: '',
sport: '',
spath: ''
};

var getMqServerUrlParams = function(){
logger.debug('getMqServerUrlParams');
for (param in serverParams) {
serverParams[param] = Jaxer.request.parsedUrl.queryParts[param];
logger.debug('getMqServerUrlParams: ' + param + ': ' + serverParams[param]);
}
};

The remainder of the request data comes in as either other URL parameters or as POSTed XML. In the case of XML data, the proxy needs to add a couple of XML elements for a client ID and password. Here’s the routine to get the XML from the request, parse it, modify the DOM and serialize back to XML:


// Fetch the XML form data and add credentials
var getPostData = function(){
logger.debug('getPostData');
logger.debug('getPostData: postdata:          ' + Jaxer.request.postData);

var doc = new DOMParser().parseFromString(Jaxer.request.postData, 'text/xml');
if (doc.documentElement.nodeName == "parsererror") {
throw new Error("People we have an issue: XML parse error");
}
var authenticalNodeList = doc.documentElement.getElementsByTagName("Authentication");
if (authenticalNodeList.length > 0) {
authenticatedRequest = true;
var authenticalNode = authenticalNodeList[0];

// add password
var passwordEl = doc.createElement('Password');
var passwordTextEl = doc.createTextNode(mqPassword);
authenticalNode.appendChild(passwordEl).appendChild(passwordTextEl);

// add client ID
var clientIdEl = doc.createElement('ClientId');
var clientIdTextEl = doc.createTextNode(mqClientId);
authenticalNode.appendChild(clientIdEl).appendChild(clientIdTextEl);
}

// back to a string
postData = new XMLSerializer().serializeToString(doc);
logger.debug('getPostData: fixed up postdata: ' + postData);
};

Notice that there is no cross-browser monkey business to get a parser and serializer. On the server side, Jaxer uses Mozilla so you can count the available features. This example also makes use of the Jaxer.Log facility which lets you write to server side logs and specify the level of logging detail.

The last code snippet show how to place a synchronous server side HTTP request with Jaxer.Web.send(). The URL is assembled and a POST or GET is made per the client request:

var makeMQRequest = function(){
mqUrl = 'http://' + serverParams.sname + ':' + serverParams.sport + '/' + serverParams.spath;
sendOptions.extendedResponse = true;
if (Jaxer.request.method == 'POST') {
if (authenticatedRequest == true) {
mqUrl += '/mqserver.dll?e=5';
}
sendOptions.contentType = 'application/x-www-form-urlencoded';
}
else { // GET
var urlQParams = '';
for (p in urlParams) {
urlQParams += p + '=' + urlParams[p];
}
if (urlParams.length > 0) {
mqUrl += '?' + urlQParams;
}
}
// Call home
mqResponse = Jaxer.Web.send(mqUrl, Jaxer.request.method, postData, sendOptions);
};

Wrap up

This example shows how to proxy web content in other domains using Jaxer. The same basic approach can be used to access other server side resources (files, database, etc).

I’m a big fan of scripting languages, and JavaScript is my current favorite. I’ve been using it not only in web browsers but also as domain specific language in a Business Process Modeling application I’m working with. I’m at the peak of my JavaScript skills, so, I’m really happy to be able to break out JavaScript on the server side.

Jaxer 1.0 RC Support for REST and RPC

Thursday, October 9th, 2008

Jaxer 1.0 RC is out and getting ready to go. To me, the big news is that server side APIs such as REST and RPC protocols have been promoted to first class citizens.

Up to now, the Jaxer application server has been HTML template oriented. Jaxer is a Server Side JavaScript (SSJS) port of Mozilla, and of course, that’s a really cool thing. If you’re a JavaScript Ninja, you finally have an application server which speaks your native language.

However, up to 1.0 RC, the server side processing model has been page/template oriented. On the server side, you’ve had to provide a concrete HTML page on the server to trigger Jaxer processing. With the 1.0 RC, you can now map an URL to JavaScript files that provide the HTTP response. This feature makes is easier to provide REST or RPC interfaces on top of server side JavaScript code.

The configuration I’ve been using is Apache and the standalone Jaxer server. Jaxer comes bundled with Aptana’s Studio IDE which is the easiest configuration for getting started on Jaxer. However, you’ll have to come to terms with deploying Jaxer under Apache when you go to production (OK, sure, you can deploy to Aptana’s Cloud which deploys direct from Studio, but that’s another beta topic).

Recent releases (prior to 1.0) have allowed you to interrupt the normal template processing of page and substitute arbitrary content and content types to be returned to the client. In order to make this work, you’ve had to provide a file that Apache is configured to hand off to the Jaxer processor.

I’m working on an Jaxer application that responds to REST URLs. Here is the approach I was using for releases prior to 1.0. I wrote Apache mod_rewrite rule to hit a front controller, where the front controller is an HTML page that Jaxer processes. I added the following to my httpd.conf file:

RewriteEngine on
RewriteRule /world.* /world/index.html

My plan is that index.html will respond to REST URLs for POST, GET, PUT, and DELTE request such as :

http://<host>/world/<entity>/<id>

The one issue I ran into was that the Apache configuration that tells Apache to hand off a request to Jaxer for filtering needs a file extension and a content type. The above URL has no file extension. My solution was to place the font controller file in it’s own directory and set Jaxer filter handling to all content in the specified directory (in my httpd.conf file):

<Directory "c:/opt/xampp/htdocs/world">
JaxerFilter *
JaxerFilterContentType text/html
Order Deny,Allow
Deny from all
Allow from all
</Directory>

The good news is that the 1.0 RC allows direct specification of JavaScript files to handle REST type URLs. Tricks like the above rewrite rules won’t be need for REST. This is a big change as server side code stands alone, without a HTML page template. To me, this is the difference between a Servlet and a JSP.

With the 1.0 RC it appears that you can match URLs to execution of server side JavaScript files, which feels much more like a J2EE web.xml file which matches URLs to invocation of server side code (servlets).

I’m working up some examples of all this with 1.0 RC which aren’t quite baked yet. If you’re in a hurry to make REST work with 1.0, here are the files to look at from the standalone release:

jaxer\confs\jaxer-*.httpd.conf
jaxer\default_local_jaxer\conf\configApps.js
jaxer\framework\extensions\serviceDispatcher.js

Jaxer, E4X and Prototype

Monday, August 4th, 2008

The ECMAScript for XML standard a.k.a “E4X” is a cool addition to JavaScript that provides for native support of XML. E4X adds nifty features such as simplified XML traversal and XML literals to the language. E4X is supported in Mozilla and Adobe JavaScript implementations. The lack of support in IE hinders the widespread adoption of E4X. However, E4x is supported on Aptana Jaxer.

I’ve been using Jaxer for web development lately. Jaxer is a web application server which uses JavaScript on the server side. The server-side JavaScript implementation is based on Mozilla, and E4X is supported. So, if you’re using Jaxer, there is nothing holding you back from using E4X on the server side.

With this in mind, I’ve cooked up an example intended to whet your appetite for E4X. The source code is shown below. The example executes a query against a database and renders each returned row as a HTML table. I’m using E4X for two tasks: storing configuration data and filling in a HTML template.

Note how various scripts or functions are tagged with runat='server'. This is how Jaxer knows which scripts to process on the server and which not to. All of the scripts in this example run on the server. The execution of the scripts is started by the onserverload attribute of the body tag. This is the server-side equivalent to the onload attribute in the browser context.

For the configuration example, I stored SQL statements in an XML document. I needed a select query to execute against the database, and then of course I realized I needed some DDL to create the table and some more SQL to insert records. I decided to store all of my queries as E4X XML literals in JavaScript embedded in my HTML page. The embedded XML is seen only on the Jaxer Server, and not in the client browser. On lines 12-37 I used an XML literal to declare an XML document containing 3 SQL statements.

A couple of points to note. First I embedded this configuration into a HTML page. With the Jaxer server-side facilities for accessing the file system, one could store the XML in a file and share the configuration across HTML pages. Second, with a CDATA section (lines 27-29), I don’t have to worry about escaping my SQL to make valid XML.

IMHO, the coolest E4X feature is simplified DOM access. The code to pull out a particular SQL statement by id is:

var sqlToRun = sqlStatements.sql.(@id == 'select-persons-no-heavier-than');

After executing the select query, I render each row as a little HTML table. On line 50 I access each record via the Jaxer.DB.ResultSet API. I create a table for each record with an XML literal on lines 52-69. One nice feature of E4X is the ability to execute arbitrary JavaScript code within the XML literal. For example, line 67:

<td>{(dbRecord.weight_kg / 0.454).toFixed(2)}</td>

reads a JavaScript object property, divides by .45, renders the number with 2 decimal places, and then places that value in the td tag. Finally on line 74, I insert the table into the page’s DOM tree. The insert is done with Prototype’s Element.insert method which takes a string or DOM Node as an argument and inserts the content into the page’s DOM tree.

Don’t forget, that all of the scripts tags (for prototype.js as well) have a runat=”server” attribute, so all of this action happens on the Jaxer server before the page is render as text and sent to the client.

One interesting point is that I used Prototype on the server side. My usage was pretty modest here; I take it as a good sign that the server side JavaScript environment closely matches the JavaScript environment in a browser.

I’m pretty happy with this example code. I think the templating of HTML turned out to be very transparent and easy to understand code. Several factors contribute to clean code: XML literals avoid cluttering the code with quoted string and also allow value interpolation, and insertion of the XML strings into the DOM tree with Prototype. This is a nice illustration of the handy features that are available in the Jaxer server side programming environment.

 <html>
     <head>
   <title>Jaxer E4X Example</title>

     /*
    * All of the JavaScript is running on the server due to the
    * runat="server" attribute of the script tags
    */
     <script runat="server" src="lib/prototype/prototype.js"></script>
     <script runat="server">

   // An XML literal holding template SQL
     var sqlStatements =
       <sql-statements>
         <sql id='create-table'>
           create table if not exists person (
             id      integer,
             first_name  varchar(20),
             last_name   varchar(20),
             birth_date  datetime,
             weight_kg   decimal(5,2),
             height_m    decimal(5,2),
             constraint person_pk primary key(id)
           );
         </sql>
         <sql id='insert-person'>
           insert into person
             (id, first_name, last_name, birth_date, weight_kg, height_m)
           values
             (?, ?, ?, ?, ?, ?)
         </sql>
         <sql id='select-persons-no-heavier-than'>
           <![CDATA[
             select * from person where weight_kg <= ?
           ]]>
         </sql>
       </sql-statements>;

     // Called on server side page load.
     // Triggered by the onserverload attribute of the body tag
       function showPersonDetails() {

     // Find a SQL statement by name
         var sqlToRun = sqlStatements.sql.(@id == 'select-persons-no-heavier-than');

     // Execute the SQL with the Jaxer.DB API
         var resultSet = Jaxer.DB.execute(sqlToRun, 200);

     // For each record returned from the DB...
     resultSet.rows.forEach(function(dbRecord, index) {
       // Create some markup using a XML Literal and value interpolation
           var tbl =
           <table border='1'>
             <tr>
               <td colspan="2">Person Detail</td>
             </tr>
             <tr>
               <td>First name:</td>
               <td>{dbRecord.first_name}</td>
             </tr>
             <tr>
               <td>First name:</td>
               <td>{dbRecord.last_name}</td>
             </tr>
             <tr>
               <td>Weight in pounds:</td>
               <td>{(dbRecord.weight_kg / 0.454).toFixed(2)}</td>
             </tr>
           </table>;

           tbl += <br/>;

       // add the new markup to the page DOM
           $('person-details').insert(tbl.toXMLString());
     });
       }

     </script>
   </head>
   <body onserverload="showPersonDetails();">
       <div id='person-details'>
       </div>
   </body>
 </html>

Jaxer CRUD

Sunday, August 3rd, 2008

Aptana’s Jaxer is a Web Application platform that uses JavaScript on the server side. I’m starting to develop a real taste for the JavaScript language, so Jaxer is a nice place to try out new modes of JavaScript programming.

Server side programming for Web Applications typically involves a good bit of interaction with a database. As a Java developer, I typically use JDBC or an Object Relational Mapping (ORM) framework such as Hibernate for database interaction. If you look around, you’ll find JavaScript lacks such established API/Frameworks for DB interaction (though it looks like some JavaScript ORMs are being actively developed). At this point in time, things are pretty much DIY.

With this in mind, I’ve worked up some code example for CRUD operations using the Jaxer database API. This is a simple example, intended to help folks climb up the Jaxer learning curve. The complete source code for the example is at the bottom of this post.

The example reads and writes from elements on a HTML page and also a database table. In the example, there are HTML tables which describe a person, and likewise, I’ve created a table in the database that has the same attributes (with slightly different names, just to keep this real. The table DDL is in the example code). Here’s a screen shot of the example:

Jaxer CRUD Example screenshoot

The flow of the example is:

1) Read attributes from the first (from the top) HTML table and then create a corresponding record in the database.

2) Read the same attributes back from the database and then populate the next HTML table

3) Read attributes from the third HTML table and update the database

4) Delete the record from the database.

Line 108 also shows an interesting capability of Jaxer: The script block there is set to runat=”server-proxy”. This tells Jaxer to run these scripts at the server, but let them be callable from the browser. So when I click the CRUD button on the client, it calls runExample on line 14, which in turn calls the four CRUD functions which run at the server. I’m actually calling a function on the server directly from the client! Note too that this example uses blocking calls from the browser to the Jaxer server. It is very easy to change this code to the Jaxer asynchronous API, which is likely topic for my next post.

Without further ado – the code:

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
 <html>
   <head>
     <title>Jaxer CRUD</title>
     <script type="text/javascript" src="prototype.js"></script>

     /*
      *  Scripts that run in the client browser
      */
     <script runat='client'>
       //
       //  Run the example on button click
       //
       function runExample() {
         // server side method that creates a table if needed
         createTable();
         // Do the CRUD operations
         createPerson();
         readPerson();
         updatePerson();
         deletePerson();
       }

       // Create a person in the DB based on properties read from a HTML table
       function createPerson() {
         // pull properties from HTML table
         var p = objectFromTable('create-table');
         // write to DB via server proxy call
         createPerson_Server(p);
       }

       // Read a person from the DB and write to a HTML table
       function readPerson() {
         // read the person from the DB via a server proxy method
         var p = readPerson_Server(1);
         // populate the HTML table
         tableFromObject('read-table', p);
       }

       // Update a person in the DB based on properties read from an HTML table
       function updatePerson() {
         // read the person properties from an HTML table
         var p = objectFromTable('update-table');
         // update the DB via a server proxy call
         updatePerson_Server(p);
       }

       // Delete a person in the DB
       function deletePerson() {
         // Server call to delete in the DB
         deletePerson_Server(1);
       }

       //
       // Utility functions
       //

       // populate an object from values in a HTML table
       function objectFromTable(tableId) {
         var o = {};
         var propNames = $$('#' + tableId + ' th');
         var props = $$('#' + tableId + ' td');
         var propNum = 0;
         propNames.each(function(item) {
           o[item.innerHTML] = props[propNum++].innerHTML;
         });
         o.birthDate = dateFromStr(o.birthDate);
         return o;
       }

       // populate a HTML table from values in an object
       function tableFromObject(tableId, anObject) {
         anObject.birthDate = strFromDate(anObject.birthDate);
         var propNames = $$('#' + tableId + ' th');
         var props = $$('#' + tableId + ' td');
         var propNum = 0;
         propNames.each(function(item) {
           props[propNum++].innerHTML = anObject[item.innerHTML];
         });
       }

       // convert from string 'yyyy-mm-dd' to JS Date
       function dateFromStr(dateStr) {
         if(dateStr) {
           var dateParts = dateStr.split('-');
           var date = new Date();
           date.setFullYear(dateParts[0]);
           date.setMonth(dateParts[1] - 1);
           date.setDate(dateParts[2]);
         }
         return date;
       }

       // convert from JS Date to string 'yyyy-mm-dd'
       function strFromDate(aDate) {
         var str = '';
         if(aDate) {
           str +=  (aDate.getFullYear() +  (aDate.getMonth() + 1) + aDate.getDate());
         }
         return str;
       }
     </script>

     /*
      *  Scripts that run on the server.
      *  Jaxer generated stubs are used to call these from the client.
      */
     <script runat='server-proxy'>

       //
       // Server side persistence functions
       //

       // Write a new object to the DB
       function createPerson_Server(person) {
         // execute insert statement, binding to properties of passed in person
         Jaxer.DB.execute(
           'insert into person ( '+
             '  id, first_name, last_name, birth_date, weight_kg, height_m ' +
           ' ) values (?, ?, ?, ?, ?, ?);',
           person.id, person.firstName,
           person.lastName, person.birthDate,
           person.weightInKilograms, person.heightInMeters
         );
       }

       // Read an existing record and return a JavaScript object
       function readPerson_Server(id) {
         // execute query and receive returned a Jaxer.DB.ResultSet
         var resultSet = Jaxer.DB.execute('select * from person where id = ?;', id);
         // select the first row since weonly care about 1 person
         var aRow = resultSet.rows[0];
         // map DB column names to front end column names
         var person = {
           id: aRow.id,
           firstName: aRow.first_name,
           lastName: aRow.last_name,
           birthDate: aRow.birth_date,
           weightInKilograms: aRow.weight_kg,
           heightInMeters: aRow.height_m
         };
         // return to client for display
         return person;
       }

       // From a JS Object, update a row in the DB
       function updatePerson_Server(person) {
         // Update all fields for the given primary key value
         Jaxer.DB.execute(
           'update person set  '+
             '  first_name = ?, last_name = ?, birth_date = ?, weight_kg = ?, height_m = ? ' +
           ' where id = ?;',
           person.firstName, person.lastName, person.birthDate,
           person.weightInKilograms, person.heightInMeters,
           person.id
         );
       }

       // Delete a record in the DB
       function deletePerson_Server(id) {
         Jaxer.DB.execute('delete from person where id = ?;', id);
       }

       //  Create a table if the table is not already in the DB
       function createTable(){
         Jaxer.DB.execute(
           'create table if not exists person (   ' +
           ' id      integer,   ' +
           ' first_name  varchar(20),   ' +
           ' last_name   varchar(20),   ' +
           ' birth_date  datetime,   ' +
           ' weight_kg   decimal(5,2),   ' +
           ' height_m    decimal(5,2),   ' +
           ' constraint person_pk primary key(id)); '
         );
       }
     </script>
     <style>
       .big-cap {
         color: blue;
         font-size:larger;
         font-style:italic;
       }
       body {
         font-family:Arial;
         font-size: 10pt;
       }
     </style>
   </head>
   <body>
     <h3><span class="big-cap">C</span>reate a person from this table</h3>
     <table border="1" style="text-align: center" id="create-table">
       <tr>
         <th>id</th>
         <th>firstName</th>
         <th>lastName</th>
         <th>birthDate</th>
         <th>weightInKilograms</th>
         <th>heightInMeters</th>
       </tr>
       <tr>
         <td>1</td>
         <td>John</td>
         <td>Doe</td>
         <td>1975-11-20</td>
         <td>90.7</td>
         <td>1.82</td>
       </tr>
     </table>
     <hr/>
     <h3><span class="big-cap">R</span>ead a person and populate this table</h3>
     <table border="1" style="text-align: center" id="read-table">
       <tr>
         <th>id</th>
         <th>firstName</th>
         <th>lastName</th>
         <th>birthDate</th>
         <th>weightInKilograms</th>
         <th>heightInMeters</th>
       </tr>
       <tr>
         <td></td>
         <td></td>
         <td></td>
         <td></td>
         <td></td>
         <td></td>
       </tr>
     </table>
     <hr/>
     <h3><span class="big-cap">U</span>pdate a person from this table</h3>
     <table border="1" style="text-align: center" id="update-table">
       <tr>
         <th>id</th>
         <th>firstName</th>
         <th>lastName</th>
         <th>birthDate</th>
         <th>weightInKilograms</th>
         <th>heightInMeters</th>
       </tr>
       <tr>
         <td>1</td>
         <td>Jane</td>
         <td>Smith</td>
         <td>1978-1-1</td>
         <td>89.2</td>
         <td>1.76</td>
       </tr>
     </table>
     <hr/>
     <h3><span class="big-cap">D</span>elete person with id = 1</h3>
     <hr/>
     <button onclick="runExample();">Crud</button>
   </body>
 </html>