HT-AJAX is a small Common Lisp framework that is designed to ease dynamic interaction of your web pages with the server. It runs under the Hunchentoot web server by Dr. Edi Weitz.
Basically it allows 'exporting' of your lisp functions so that they can be easily called from the Javascript code on your web page. The details of the data transfer can be handled by different backends, or as we'll call them 'AJAX processors'. At the moment three such processors are supported: one simple, built-in, that generates code inside the web page and does not require external libraries. The others are using a javascript library, such as Prototype or Dojo (full list).
The code comes with a BSD-style license so you can basically do with it whatever you want.
Download shortcut: http://85.65.214.241/misc/ht-ajax.tar.gz.
(require 'asdf-install) (asdf-install:install :ht-ajax)
(require 'asdf) (asdf:oos 'asdf:load-op :ht-ajax)
Questions, bug reports, requests, criticism and just plain information that you found this package useful (or useless) are to be sent to urym@two-bytes.com
In this tutorial we assume that the reader is reasonably familiar with Hunchentoot and AJAX.
So, let's suppose we already have some lisp code working under Hunchentoot and start modifying it to use AJAX. Note that normally we'll (use-package :ht-ajax), so that we won't have to prefix the symbols with ht-ajax:, but here we'll still do it to show clearly which symbols come from the HT-AJAX package.
At first some setup:
(defparameter *ajax-handler-url* "/hunchentoot/some/suitable/uri/ajax-hdlr")
Here we select some URL that will handle our AJAX requests. Later we'll need to arrange for an appropriate handler to be called when we get a request for this URL (and all URLs starting with it). Replace the URL with whatever makes sense for your application
After this we create an instance of so-called ajax-processor that will handle all our AJAX needs. One ajax-processor per application should be enough. We pass the following parameters: :type :lokris to select which backend to use, in this case it's the Lokris library. Also we pass the :server-uri that we've selected and the :js-file-uris that shows where to find the appropriate library file, lokris.js in this case (the URL may be relative to the URL of the page):
(defparameter *ajax-processor* (ht-ajax:make-ajax-processor :type :lokris :server-uri *ajax-handler-url* :js-file-uris "static/lokris.js"))
Now we create the function that we want to be called from the web page:
(ht-ajax:defun-ajax testfunc (command) (*ajax-processor* :method :post) (prin1-to-string (eval (read-from-string command nil))))
We've used here the defun-ajax macro that performs two tasks: defines the function as per defun and 'exports' it - makes available to the web page's javascript. This fragment could've been written more verbosely as:
(defun testfunc (command) (prin1-to-string (eval (read-from-string command nil)))) (ht-ajax:export-func *ajax-processor* 'testfunc :method :post)
The function itself contains the code to evaluate the string parameter command and return the result as a string. (It's possible to return more complex objects to the Javascript code by using JSON). While processig the request HT-AJAX will call Hunchentoot's function no-cache to make sure the browser will make a request to the server each time and not cache the results, so we don't have to do it here. If we want to manually control the caching we can pass :allow-cache t parameter when exporting the function.
The only thing left to prepare the server side of the things is to create the dispatcher for our *ajax-handler-url* and to add it to Hunchentoot's dispatch table. The specifics of this can vary, but it might include something like:
(create-prefix-dispatcher *ajax-handler-url* (ht-ajax:get-handler *ajax-processor*)
The call to (ht-ajax:get-handler *ajax-processor*) returns the handler that the handler URL needs to be dispatched to.[1]
Now we need to make sure that the dynamic web pages we generate are correctly set up. This means that the result of the call (ht-ajax:generate-prologue *ajax-processor*) needs to be inserted somewhere in the HTML page (right after the <body> tag seems like a good place). Once again how to do this depends on the facilities that are used for HTML generation. For example when using HTML-TEMPLATE we'll have something like the following in our template:
<body> <!-- TMPL_VAR prologue -->
and then pass the output of (ht-ajax:get-handler *ajax-processor*) as the prologue parameter to the fill-and-print-template call.[2]
After that, whatever means for HTML generation we're using, let's put the following HTML somewhere in the page:
<table width="50%"> <tr> <td colspan="2"> <span id="result"> <i>no results yet</i> </span> </td> </tr> <tr> <td width="70%"> <input type="text" size="70" name="command" id="command" /> </td> <td> <input type="button" value="Eval" onclick="javascript:command_clicked();"/> </td> </tr> </table>
This will produce something like:
no results yet | |
<script type="text/javascript"> function command_clicked(txt) { // get the current value of the text input field var command = document.getElementById('command').value; // call function testfunc on the server with the parameter // command and set the element with the id 'result' to the return // value of the function ajax_testfunc_set_element('result', command); } </script>
The function ajax_testfunc_set_element that we call here was generated for us by HT-AJAX. It takes one required parameter - the id of the element that we want to be set to the result of the remote call. All other parameters will be passed to corresponding exported lisp function, testfunc in this case (watch out for the number of arguments). The resulting string will be assigned to the .innerHTML property of the element with the id 'result'.
This is it. Now to save the files, compile the lisp code, make sure the Hunchentoot server is started and open the web page in the browser. Enter something like (+ 1 42) in the text field and click 'Eval'. If all's well the results of the evaluation will be displayed.
If one of the supported libraries (like Prototype) is already used in Javascript code then the decision is is easy - use the appropriate ajax-processor. Otherwise consider if you need to make server calls using HTTP GET or POST method. If GET works for you then SIMPLE may be enough, otherwise for POST method use Lokris.
Exporting the function (and later including the result of the GENERATE-PROLOGUE call in the web page) makes available two functions for the Javascript code on the page. Assuming the exported function was called TESTFUNC and the standard prefix (ajax_) was used they are:
ajax_testfunc_callback(callback_specification, [params for the server's TESTFUNC....])and
ajax_testfunc_set_element(element_id, [params for the server's TESTFUNC....])
Both functions will call the server-side function TESTFUNC, the ajax_testfunc_callback
version will call the provided callback function with the result of the server call
as a single parameter, the ajax_testfunc_set_element version with find the document
element with the id element_id and set it's innerHTML to the result of the
server call.
The result of the server call is normally a string and is passed to the callback as-is,
unless the Content-Type header was set to application/json which is the
official IANA
media type. In case of JSON the result is evaluated using "unsafe"
[3] eval
call and the resulting object is passed to the callback.
The callback_specification parameter can be used
to specify two kinds of callacks (at the same time). The success callback function will
be called
after a successful interaction with the server and passed the server call result
as a parameter. The error callback function will be called in case of an error
and passed a string with the information about the error.
So the callback_specification can take the following forms:
[Function]
make-ajax-processor &rest rest &key type &allow-other-keys => new-ajax-processor
Creates an ajax-processor object. Parameters:
TYPE - selects the kind of ajax-processor to use (should be one of:SIMPLE or :LOKRIS, :PROTOTYPE, :YIU or :DOJO) (required).
SERVER-URI - url that the ajax function calls will use (required).
JS-FILE-URIS - a list of URLs on your server of the .js files that the used library requires , such as lokris.js or prototype.js (parameter required for all processors except :SIMPLE). If only one file needs to be included then instead of a list a single string may be passed. Also if this parameter is a string that ends in a forward slash ( #\/ ) then it is assumed to be a directory and the default file names for the processor are appended to it.
AJAX-FUNCTION-PREFIX - the string to be prepended to the generated js functions, (default prefix is "ajax_").
JS-DEBUG - enable the Javascript debugging function debug_alert(). Overrides such parameters as JS-COMPRESSION and VIRTUAL-FILES.
JS-COMPRESSION - enable Javascript compression of the generated code to minimize the download size.
VIRTUAL-JS-FILE - enable creation of virtual Javascript file instead of inline Javascript code that may be cached on the client to minimize traffic.
[Generic function]
export-func processor funcallable &key method name content-type allow-cache =>|
Makes the function designated by FUNCALLABLE exported (available to call from js) Parameters:
METHOD - :get (default) or :post (:post is not supported under SIMPLE processor).
NAME - export the function under a different name.
CONTENT-TYPE - Value of Content-Type header so set on the reply (default: text/plain).
ALLOW-CACHE - (default nil) if true then HT-AJAX will not call NO-CACHE function and allow to control cache manually.
JSON - (default nil) if true, the function returns a JSON-encoded object that will be decoded on the client and passed to the callback as an object
Exporting the function (and later including the result of the GENERATE-PROLOGUE call in the web page) makes available two functions for the Javascript code on the page: ajax_testfunc_callback and ajax_testfunc_set_element. See "Using the generated Javascript functions" for more details.
[Generic function]
unexport-func processor symbol-or-name =>|
Removes the previously exported function, should be called with either the name (string) under which it was exported or the symbol designating the function
[Macro]
defun-ajax name params (processor &rest export-args) declaration* statement*
Macro, defining a function exported to AJAX Example: (defun-ajax func1 (arg1 arg2) (*ajax-processor*) (do-stuff))
[Generic function]
generate-prologue processor &key use-cache => html-prologue
Generates the necessary HTML+JS to be included in the web page. Provides caching if USE-CACHE is true (default).
[Generic function]
get-handler processor => handler
Get the hunchentoot handler for AJAX url. The url that was passed as the SERVER-URI parameter (and all URLs starting with it) should be dispatched to this handler.
At the moment HT-AJAX is known to run on SBCL and Lispworks, but it aims to be portable across all the implementations Hunchentoot runs on. Please report all incompatibilities.
[1] When not using CREATE-PREFIX-DISPATCHER, note that not only the SERVER-URI itself but also all the URLs starting with it need to be dispatched to the handler in order for "virtual .js files" mechanism to function.
[2] By default HTML-TEMPLATE escapes some characters while expanding. In the case of the prologue of HT-AJAX there's no need to do it since HT-AJAX already wraps the generated Javascript code in the proper CDATA sections (which also makes it possible to generate documents compliant with for example XHTML Strict requirements). So one of the options is to wrap the template expansion in the following binding:
(let ((*string-modifier* #'CL:IDENTITY)) ...template expansion... )
[3] The word "unsafe" means that it might not be generally safe to evaluate arbitrary Javascript code coming from an untrusted source; in our case it's ok since we control both the client and the server.
This documentation was prepared with the help of
DOCUMENTATION-TEMPLATE
by Edi Weitz (the code was hacked to run on SBCL).
The initial inspiration for the SIMPLE processor came from Richard Newman's CL-AJAX which is designed for use with Araneida.
;;; Copyright (c) 2007, Ury Marshak