THE AJAX FUNCTION 275 and the associated "default/one.html" view: 1 {{extend 'layout.html'}} 2 <form> 3 <input id="name" onkeyup="ajax('echo', ['name'], ':eval')" /> 4 </form> 5 <div id="target"></div> This allows for more articulated responses than simple strings. Auto-completion 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: 1 def month_input(): 2 return dict() 3 4 def month_selector(): 5 if not request.vars.month: 6 return '' 7 months = ['January', 'February', 'March', 'April', 'May', 8 'June', 'July', 'August', 'September' ,'October', 9 'November', 'December'] 10 selected = [m for m in months \ 11 if m.startswith(request.vars.month.capitalize())] 12 return ''.join([DIV(k, 13 _onclick="jQuery('#month').val('%s')" % k, 14 _onmouseover="this.style.backgroundColor='yellow'", 15 _onmouseout="this.style.backgroundColor='white'" 16 ).xml() for k in selected]) and the corresponding "default/month input.html" view: 1 {{extend 'layout.html'}} 2 <style> 3 #suggestions { position: relative; } 4 .suggestions { background: white; border: solid 1px #55A6C8; } 5 .suggestions DIV { padding: 2px 4px 2px 4px; } 6 </style> 7 8 <form> 9 <input type="text" id="month" style="width: 250px" /><br /> 10 <div style="position: absolute;" id="suggestions" 11 class="suggestions"></div> 12 </form> 13 <script> 14 jQuery("#month").keyup(function(){ 15 ajax('complete', ['month'], 'suggestions')}); 16 </script> 276 AJAX RECIPES 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 "Ma" the callback action returns: 1 <div onclick="jQuery('#month').val('February')" 2 onmouseout="this.style.backgroundColor='white'" 3 onmouseover="this.style.backgroundColor='yellow'">February</div> Here is the final effect: If the months are stored in a database table such as: 1 db.define_table('month', Field('name')) then simply replace the month selector action with: 1 def month_input(): 2 return dict() 3 4 def month_selector(): 5 it not request.vars.month: 6 return '' 7 pattern = request.vars.month.capitalize() + '%' 8 selected = [row.name for row in db(db.month.name.like(pattern)). select()] 9 return ''.join([DIV(k, 10 _onclick="jQuery('#month').val('%s')" % k, 11 _onmouseover="this.style.backgroundColor='yellow'", 12 _onmouseout="this.style.backgroundColor='white'" 13 ).xml() for k in selected]) jQuery provides an optional Auto-complete Plugin with additional func- tionalities, but that is not discussed here. THE AJAX FUNCTION 277 Form Submission Here we consider a page that allows the visitor to submit messages using Ajax without reloading the entire page. 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: 1 db = DAL('sqlite://db.db') 2 db.define_table('post', Field('your_message', 'text')) 3 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: 1 def index(): 2 return dict() 3 4 def new_post(): 5 form = SQLFORM(db.post) 6 if form.accepts(request.vars, formname=None): 7 return DIV("Message posted") 8 elif form.errors: 9 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: 1 {{extend 'layout.html'}} 2 3 <div id="target"></div> 4 5 <form id="myform"> 6 <input name="your_message" id="your_message" /> 7 <input type="submit" /> 8 </form> 9 10 <script> 11 jQuery('#myform').submit(function() { 12 ajax('{{=URL(r=request, f='new_post')}}', 13 ['your_message'], 'target'); 14 return false; 15 }); 16 </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 278 AJAX RECIPES 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 ofasinglepagethatdisplaystheimagessortedaccordingtotheirvote. 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: 1 db = DAL('sqlite://images.db') 2 db.define_table('item', 3 Field('image', 'upload'), 4 Field('votes', 'integer', default=0)) Here is the default controller: 1 def list_items(): 2 items = db().select(db.item.ALL, orderby=˜db.item.votes) 3 return dict(items=items) 4 5 def download(): 6 return response.download(request, db) 7 8 def vote(): 9 item = db(db.item.id==request.vars.id).select()[0] 10 new_votes = item.votes + 1 11 item.update_record(votes=new_votes) 12 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: 1 {{extend 'layout.html'}} 2 3 <form><input type="hidden" id="id" value="" /></form> 4 {{for item in items:}} THE AJAX FUNCTION 279 5 <p> 6 <img src="{{=URL(r=request, f='download', args=item.image)}}" 7 width="200px" /> 8 <br /> 9 Votes=<span id="item{{=item.id}}">{{=item.votes}}</span> 10 [<span onclick="jQuery('#id').val('{{=item.id}}'); 11 ajax('vote', ['id'], 'item{{=item.id}}');">vote up</span>] 12 </p> 13 {{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 back- ground, but we reccomment using CRON 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. . and the web application. In mod python, all hosted applications run under the same user-id/group-id, which presents security issues. web2 py provides a file cgihandler .py to interface to mod python. In. than Apache with mod wsgi. web2 py provides a file fcgihandler .py to interface to FastCGI. web2 py also includes a gaehandler .py to interface with the Google App Engine (GAE). On GAE, web applications. and Apachepackagesareinstalled by typing the following shell commands: 1 sudo apt-get update 2 sudo apt-get -y upgrade 3 sudo apt-get -y install openssh-server 4 sudo apt-get -y install python 5