Tuesday, May 26, 2009

Dynamic HTML SELECT forms for AppEngine

AppEngine
dynamically created HTML select forms for Django python templates

I didn't see this particular solution anywhere so I'll post it here, since it something that may come up for anyone trying to do a XMLHttpRequest() in Google AppEngine. Normally I use the Django forms in App Engine but I wanted to quickly add some query filters without having to create a model for them.
This could happen when the web app needs to interface with the user. In this example, I simply wanted to filter the dataset by the location. So I have one select box with all the states listed. I do have a Django model but it is used for creating new records, not filtering lists.

First step, put the forms in the html template (this uses built-in Django template tags ):


<form method="post" id="frmSearchBox">

<table>
<tr><td>
<SELECT name="states" id="states" onChange="selectState(this)">
{% for stt in states %}
<option name="op" value="{{ stt }}" >

{% endfor %}
</SELECT>
</td>
<table>
<input type="button" name="btnSearch" value="Submit" onclick="javascript:searchStates();">
<form>


Notice this form is a post. Because I want to add AJAX to only update a portion of the page.

function searchRoutes() {
//the submit button on the searchbox form triggers this function
//this will do an ajax post to the server and filter the routes according to the user selection

var states = document.getElementById('states')[document.getElementById('states').selectedIndex].innerHTML;

var body ="states=" + states ;
var req = createXMLHttpRequest();
req.open('POST', '/rpcRouteSearch/', true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.setRequestHeader("Content-length", body.length);
req.setRequestHeader("Connection", "close");
req.onreadystatechange = onAddSuccess;
req.send(body);

function onAddSuccess(response) {
//alert("in searchRoutes callback: " + req.readyState);
//replaces the #_nodes with the slideshow
if (req.readyState != 4) {return; }
var serverResponse = req.responseText;
var mg = document.getElementById("leftImages");
mg.innerHTML = serverResponse;

}
}



================
Now a peek at the python in views.py, our callback is waiting for the line that starts with request.method == 'POST':


def intro(request):
user = users.get_current_user()
statelist = ['All']
if request.method == 'GET':
routes = db.GqlQuery("SELECT * FROM Route" )
numroutes = routes.count()
"""Next sections prepare data for the search box forms """
for route in routes:
statelist.append(route.state)
"""this next trick removes duplicate values! """
statelist = list(set(statelist))
return respond(request, user, 'intro', {'routes' : routes, 'numroutes' : numroutes, 'states' : statelist })
if request.method == 'POST':
"""AJAX call, use post when filtering with the search box form """
post= request.POST.copy()
statefilter = post['states']
statefilter = statefilter.strip()
# The Query interface prepares a query using instance methods.
q = db.Query(models.Route)
"""q = models.Route.all()"""
q.filter('state = ',statefilter)
# The query is not executed until results are accessed.
routes = q.fetch(25)
numroutes = q.count()
statelist = ['All']
return respond(request, user, 'introRoutes', {'routes' : routes, 'numroutes' : numroutes, 'states' : statelist})


====

The lines that were nonstandard, are these

statefilter = post['states']
statefilter = statefilter.strip()

It was necessary to use the string function strip to get the results to work properly in the filter function. All it does is remove whitespace from the ends of the string (not inside) and now the variable is ready to be used by the filter.

======

The python code is going to use a template called "introRoutes.html" to build it's serverResponse that gets sent to the waiting Callback function in the javascript.

Since we are working with an HTML form select box, it would be nice to set the SELECTED value, to make sure the form is reset for the user, moreso important when there are many of these select options...otherwise the user has to reset them all manually. We want to set the form back to 'All' after every use, the 'All' setting is added at the beginning of the Python function,

statelist = ['All']

and then the other states available are added to it. To make sure ALL is always SELECTED, the attribute must be written to the form, in this case we are in an asychronous call to the server, but it works the same way.

Here is an extra $2 trick..use Django’s built-in template tags and filters! Anything in the {% isPython %} commands and the bracket tags {{ areVariables }}. These are more fully described at http://docs.djangoproject.com/en/dev/ref/templates/builtins/#ref-templates-builtins

Example of dynamically created HTML select forms for Django python templates.


<em>
<select name="states" id="states" style="width: 6em;"> <option name="op" value="{{ stt }}" ifequal="" stt="" all="" selected="selected" endifequal="">{{ stt }} {% endfor %} </option></select>
</table></form></em>

No comments: