Wednesday, July 2, 2008

GSOC 2008: Week 5

So, it's time for an update. This week got off to a sketchy start, but it's gained momentum. Let me enumerate what I have done thus far with the project. First, I've added AJAX using jquery. I figured I would use jquery mainly because it provided painless AJAX goodness. Prior to even thinking of using jquery, I wrote my own AJAX code using a tutorial that I found while googling. I had to tweak it a bit, but for the most part, it seemed very standard. However, after I wrote the AJAX equivalent code, it didn't feel too elegant. So, first I'm going to show the AJAX code I wrote; then I'll show you the jquery version; finally, I'm going to show you the servlet which handles the AJAX on the server-side.

First, the AJAX I wrote:

function AjaxValidation(url, callback) {
var req = init()
req.onreadystatechange = processRequest

function init() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest()
} else if (window.ActiveXObject) {
return new ActiveXObject("Mircosoft.XMLHTTP")
}
}

function processRequest() {
if (req.readyState == 4) {
if (req.status == 200) {
if (callback) {
chkSyntaxCallBack(req.responseXML)
}
}
}
}

this.doGet = function() {
req.open("GET", url, true)
req.send(null)
}
}
function chkSyntaxCallBack(responseXML) {
var res = responseXML.getElementsByTagName("result")[0].firstChild.nodeValue
document.getElementById("out").innerHTML = res
}

function checkSyntax() {
var target = document.getElementById("groovyModel")
var url = "${pageContext.request.contextPath}/moduleServlet/groovyforms/createGroovyForm?groovyModel=" + escape(target.value)
var ajax = new AjaxValidation(url, chkSyntaxCallBack)
ajax.doGet()
}

I warned you that it wasn't too elegant. Understanding this isn't too hard. Here is the call sequence: init() -> doGet() -> processRequest() -> chkSyntaxCallBack(). Not that bad. Now let's see the jquery version.

$(window).ready(function () {
$("#groovyModel").bind("blur", function () {
$.ajax({
type: 'POST',
data: { groovyModel: $("#groovyModel").val() } ,
url: "${pageContext.request.contextPath}/moduleServlet/groovyforms/createGroovyForm" ,
cache: false ,
success: function(data) {
var res = $(data).find("result").text()
$("#out").html(res)

}
})
});
})

Okay, that's much better. A few things are still happening here. When the window is finished loading, I bind my textarea element which has the CSS id of "groovyModel" to the blur event (lost focus). Then you see the AJAX. Now this is very straight forward. We're using the POST method, we're sending whatever value is inside of the textarea at the time the event is fired, we're posting to a servlet, not going to cache, and when we're done, it's printed to the screen. Very straight forward.

Now, like I said, this is all backed on the server-side by a servlet, which is written in Groovy. So here we go:

/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License") you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.module.groovyforms.web

import javax.servlet.ServletException
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.apache.commons.logging.LogFactory
import org.codehaus.groovy.control.CompilationFailedException

class CreateGroovyFormServlet extends HttpServlet {
def classLoader
static final def log = LogFactory.getLog(CreateGroovyFormServlet.class)
private static final long serialVersionUID = 066373513262051L

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
def generateTemplate = request.getParameter("template")
def generateController = request.getParameter("controller")
def finalMarkup = request.getParameter("markup")
def clazz = URLDecoder.decode(request.getParameter("groovyModel"))
def name = request.getParameter("formName")
def version = request.getParameter("version")
def res = this.checkSyntax(clazz)
if (clazz) {
if (checkSyntax(clazz)) {
response.contentType = "text/xml"
response.setHeader "Cache-Conrol", "no-cache"
response.writer.write "\n\t$res\n"
} else {
response.contentType = "text/xml"
response.setHeader "Cache-Control", "no-cache"
response.writer.write "true"
}
} else {
response.contentType = "text/xml"
response.setHeader "Cache-Control", "no-cache"
response.writer.write "\n\tPlease fill in the Form Model\n"
}
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response)
}

@Override
void init() throws ServletException {
if (log.infoEnabled)
log.info("Initializing...")
classLoader = getClassLoader()
}

def getClassLoader() {
def gcl = new GroovyClassLoader(this.getClass().getClassLoader())
gcl
}

/**
* This method is used to relay errors to the user
* @param clazz the class
* @return the exception message or null if it was successful
*/
def checkSyntax(clazz) {
def sb = new StringBuilder()
sb << "import org.openmrs.*\n\n\n"
sb << clazz
def res = null
try {
getClassLoader().parseClass(sb.toString())

} catch (CompilationFailedException e) {
res = "Exception: ${e.message}"
}
res

}

/**
* Check if it is result groovy code.
* @param clazz the class
* @return whether or not it is result groovy code
*/
def isValidGroovy(clazz) {
def sb = new StringBuilder()
sb << "import org.openmrs.*\n\n\n"
sb << clazz
try {
getClassLoader().parseClass(sb.toString())
} catch (CompilationFailedException e) {
return false
}
return true
}
}


This servlet contains a lot of utility methods. One compiles, one initializes/returns the GroovyClassLoader, and of course doGet(), doPost() and init(). doGet() and doPost() both do the same thing, with doPost() simply delegating to doGet(). The code should be reasonably easy to understand. checkSyntax() returns null if it was parsed cleanly, otherwise it returns the exception message, the stack track wouldn't be useful in my case. It returns an XML tag <result> with "true" if it was successful, the exception message if it was not, and a message stating that the field must be filled in if it's empty or just not passed in. I implicitly import org.openmrs.* to allow for easier access to the OpenMRS domain model classes. The parent classloader for the GroovyClassLoader is set the servlet container's classloader. This gives me access to the classpath when loading Groovy classes.

I still have a bit to do, templating needs to be written in, for the most part it's done. Just have a few problems I'm facing, but I'll get through it. Too many people are relying on me succeeding. I feel that this project could help a lot of people, so i feel pressure to succeed. I still need to write in the "Edit" functionality of the "Manage Groovy Forms" page.

I'm definately making progress. More updates to come, that's for sure.