The ajax
function
In web2py.js, web2py defines a function called ajax
which is based on, but should not be confused with, the jQuery function $.ajax
. The latter is much more powerful than the former, and for its usage, we refer you to ref.[jquery] and ref.[jquery-b]. However, the former function is sufficient for many complex tasks, and is easier to use.
The ajax
function is a JavaScript function that has the following syntax:
ajax(url, [name1, name2, ...], target, options)
It asynchronously calls the url (first argument), passes the values of the field inputs with the name equal to one of the names in the list (second argument), then stores the response in the innerHTML of the tag with the id equal to target (the third argument).
Here is an example of a default
controller:
def one():
return dict()
def echo():
return request.vars.name
and the associated “default/one.html” view:
{{extend 'layout.html'}}
<form>
<input name="name" onkeyup="ajax('{{=URL('default', 'echo')}}', ['name'], 'target')" />
</form>
<div id="target"></div>
When you type something in the INPUT field, as soon as you release a key (onkeyup), the ajax
function is called, and the value of the name="name"
field is passed to the action “echo”, which sends the text back to the view. The ajax
function receives the response and displays the echo response in the “target” DIV.
You can also pass options (the fourth argument) directly into the internal $.ajax
call. This is useful in the event that a fail callback is required, or a content type must be specified.
Eval target
The third argument of the ajax
function can be the string “:eval”. This means that the string returned by server will not be embedded in the document but it will be evaluated instead.
Here is an example of a default
controller:
def one():
return dict()
def echo():
return "jQuery('#target').html(%s);" % repr(request.vars.name)
and the associated “default/one.html” view:
{{extend 'layout.html'}}
<form>
<input name="name" onkeyup="ajax('{{=URL('default', 'echo')}}', ['name'], ':eval')" />
</form>
<div id="target"></div>
This allows for more complex responses that can update multiple targets.
Auto-completion
Web2py contains a built-in autocomplete widget, described in the Forms chapter. Here we will build a simpler one from scratch.
Another application of the above ajax
function is auto-completion. Here we wish to create an input field that expects a month name and, when the visitor types an incomplete name, performs auto-completion via an Ajax request. In response, an auto-completion drop-box appears below the input field.
This can be achieved via the following default
controller:
def month_input():
return dict()
def month_selector():
if not request.vars.month: return ''
months = ['January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September' , 'October',
'November', 'December']
month_start = request.vars.month.capitalize()
selected = [m for m in months if m.startswith(month_start)]
return DIV(*[DIV(k,
_onclick="jQuery('#month').val('%s')" % k,
_onmouseover="this.style.backgroundColor='yellow'",
_onmouseout="this.style.backgroundColor='white'"
) for k in selected])
and the corresponding “default/month_input.html” view:
{{extend 'layout.html'}}
<style>
#suggestions { position: relative; }
.suggestions { background: white; border: solid 1px #55A6C8; }
.suggestions DIV { padding: 2px 4px 2px 4px; }
</style>
<form>
<input type="text" id="month" name="month" style="width: 250px" /><br />
<div style="position: absolute;" id="suggestions"
class="suggestions"></div>
</form>
<script>
jQuery("#month").keyup(function(){
ajax('{{=URL('default', 'month_selector')}}', ['month'], 'suggestions')});
</script>
The jQuery script in the view triggers the Ajax request each time the visitor types something in the “month” input field. The value of the input field is submitted with the Ajax request to the “month_selector” action. This action finds a list of month names that start with the submitted text (selected), builds a list of DIVs (each one containing a suggested month name), and returns a string with the serialized DIVs. The view displays the response HTML in the “suggestions” DIV. The “month_selector” action generates both the suggestions and the JavaScript code embedded in the DIVs that must be executed when the visitor clicks on each suggestion. For example when the visitor types “M” the callback action returns:
<div>
<div onclick="jQuery('#month').val('March')"
onmouseout="this.style.backgroundColor='white'"
onmouseover="this.style.backgroundColor='yellow'">March</div>
<div onclick="jQuery('#month').val('May')"
onmouseout="this.style.backgroundColor='white'"
onmouseover="this.style.backgroundColor='yellow'">May</div>
</div>
Here is the final effect:
If the months are stored in a database table such as:
db.define_table('month', Field('name'))
then simply replace the month_selector
action with:
def month_input():
return dict()
def month_selector():
if not request.vars.month:
return ''
pattern = request.vars.month.capitalize() + '%'
selected = [row.name for row in db(db.month.name.like(pattern)).select()]
return ''.join([DIV(k,
_onclick="jQuery('#month').val('%s')" % k,
_onmouseover="this.style.backgroundColor='yellow'",
_onmouseout="this.style.backgroundColor='white'"
).xml() for k in selected])
jQuery provides an optional Auto-complete Plugin with additional functionalities, but that is not discussed here.
Ajax form submission
Here we consider a page that allows the visitor to submit messages using Ajax without reloading the entire page. Using the LOAD helper, web2py provides a better mechanism for doing it than described here, which will be described in Chapter 12. Here we want to show you how to do it simply using jQuery.
It contains a form “myform” and a “target” DIV. When the form is submitted, the server may accept it (and perform a database insert) or reject it (because it did not pass validation). The corresponding notification is returned with the Ajax response and displayed in the “target” DIV.
Build a test
application with the following model:
db = DAL('sqlite://storage.sqlite')
db.define_table('post', Field('your_message', 'text'))
db.post.your_message.requires = IS_NOT_EMPTY()
Notice that each post has a single field “your_message” that is required to be not-empty.
Edit the default.py
controller and write two actions:
def index():
return dict()
def new_post():
form = SQLFORM(db.post)
if form.accepts(request, formname=None):
return DIV("Message posted")
elif form.errors:
return TABLE(*[TR(k, v) for k, v in form.errors.items()])
The first action does nothing other than return a view.
The second action is the Ajax callback. It expects the form variables in request.vars
, processes them and returns DIV("Message posted")
upon success or a TABLE
of error messages upon failure.
Now edit the “default/index.html” view:
{{extend 'layout.html'}}
<div id="target"></div>
<form id="myform">
<input name="your_message" id="your_message" />
<input type="submit" />
</form>
<script>
jQuery('#myform').submit(function() {
ajax('{{=URL('new_post')}}',
['your_message'], 'target');
return false;
});
</script>
Notice how in this example the form is created manually using HTML, but it is processed by the SQLFORM in a different action than the one that displays the form. The SQLFORM object is never serialized in HTML. SQLFORM.accepts
in this case does not take a session and sets formname=None
, because we chose not to set the form name and a form key in the manual HTML form.
The script at the bottom of the view connects the “myform” submit button to an inline function which submits the INPUT with id="your_message"
using the web2py ajax
function, and displays the answer inside the DIV with id="target"
.
Voting and rating
Another Ajax application is voting or rating items in a page. Here we consider an application that allows visitors to vote on posted images. The application consists of a single page that displays the images sorted according to their vote. We will allow visitors to vote multiple times, although it is easy to change this behavior if visitors are authenticated, by keeping track of the individual votes in the database and associating them with the request.env.remote_addr
of the voter.
Here is a sample model:
db = DAL('sqlite://images.db')
db.define_table('item',
Field('image', 'upload'),
Field('votes', 'integer', default=0))
Here is the default
controller:
def list_items():
items = db().select(db.item.ALL, orderby=db.item.votes)
return dict(items=items)
def download():
return response.download(request, db)
def vote():
item = db.item[request.vars.id]
new_votes = item.votes + 1
item.update_record(votes=new_votes)
return str(new_votes)
The download action is necessary to allow the list_items view to download images stored in the “uploads” folder. The votes action is used for the Ajax callback.
Here is the “default/list_items.html” view:
{{extend 'layout.html'}}
<form><input type="hidden" id="id" name="id" value="" /></form>
{{for item in items:}}
<p>
<img src="{{=URL('download', args=item.image)}}"
width="200px" />
<br />
Votes=<span id="item{{=item.id}}">{{=item.votes}}</span>
[<span onclick="jQuery('#id').val('{{=item.id}}');
ajax('{{=URL('default', 'vote')}}', ['id'], 'item{{=item.id}}');">vote up</span>]
</p>
{{pass}}
When the visitor clicks on “[vote up]“ the JavaScript code stores the item.id in the hidden “id” INPUT field and submits this value to the server via an Ajax request. The server increases the votes counter for the corresponding record and returns the new vote count as a string. This value is then inserted in the target item{{=item.id}}
SPAN.
Ajax callbacks can be used to perform computations in the background, but we recommend using cron or a background process instead (discussed in chapter 4), since the web server enforces a timeout on threads. If the computation takes too long, the web server kills it. Refer to your web server parameters to set the timeout value.