Ajax == Description == Asynchronous server function calls is a means for clients to invoke services on servers. The result of such invocation is a data structure containing a status code and a characters string denoting the answer. ~~ Asynchronous server calls are ubiquitous in HOP. They are implicitly present in on-demand ++++s and ++++s. They are explicit with the ++with-hop++ client function calls. In this tutorial we show how to use the latter. ~~ In this tutorial, we are going to use asynchronous function calls for searching a database located on the server each time a character is typed-in in an input text of the client. The matches will be instantly displayed in the client. == Architecture == This tutorial will use two HOP modules, one named ++tutorial-ajax-database++ and one named ++tutorial-ajax++. HOP uses no automatic mapping of module-name to file-name. In other words, although a source file cannot contain more than one module, there is no relationship between the name of a module and the name of the file holding that module. There are two ways for instructing HOP with the association between modules and file names. The first one, consists in specifying when a module is imported. Assuming a module ++mod1++ implemented in a file named ++src1.hop++, one may write: (module my-module (import (mod1 "src1.hop"))) Specifying the mapping module->file for each import has two drawbacks: * it is fastidious and, * more important, it requires to change the source code as soon as one source file is renamed. To workaround these drawbacks, HOP supports a second mechanism, namely the ++afile++ association table, that allows the mapping module->file to be specified globally once for all. When a source file name ++src.hop++ located in a directory ++dir++, is read, HOP checks if the file name ++dir/.afile++ exists. If it exists, this file is loaded internally in the system. The structure of this file must be a list of mapping ++module-name x file-name++. With our previous example, the ++.afile++ should contain: ( (mod1 "src1.hop") ) And the module statement should look like this: (module my-module (import mod1)) One may write these ++.afile++ by hand or use the tool ++bglafile++ that can generate these files. Using ++bglafile++ is straightforward, in general a simple execution such as (in the code below, lines prefixed with a ++$++ sign are assumed to be command lines executed with a command line editor such as a Unix shell): $ bglafile -suffix hop *.hop > .afile is all you need. In the rest of this tutorial we will assume the following files organization: +- tutorial/ | +- .afile | +- tutorial-ajax-database.hop | +- tutorial-ajax.hop +- ... The file ++.afile++ contains: ( (tutorial-ajax-database "tutorial-ajax-database.hop") (tutorial-ajax "tutorial-ajax.hop") ) Hop should be ran as: $ cd tutorial && hop -v2 tutorial-ajax.hop ~~ The tutorial will defined a service named ++tutorial-ajax++. Hence, to run the tutorial, one should direct his Web browser to: http://localhost:8080/hop/tutorial-ajax == Version 1 == In this version, the server builds a HTML tree that is transmitted as a sequence of characters to the client. The client parses these characters for elaborating locally a new tree that is inserted in the graphical user interface. === The server code === The server code implements a small database containing programming language description. (Note that results window that pops up when you hit the "Run" button will only contain the return value of the last command executed, the definition of the ++tutorial-ajax/query++ service.) (module tutorial-ajax-database (library sqlite) (export tutorial-ajax/query)) ;; create an in-memory database (define *db* (instantiate::sqlite)) ;; create a table (sqlite-exec *db* "CREATE TABLE languages (name STRING, kind STRING)") ;; populate the table (for-each (lambda (kind) (for-each (lambda (lang) (sqlite-exec *db* "INSERT INTO languages VALUES (~q, ~q)" lang (car kind))) (cadr kind))) '(("functional" ("Haskell" "Hop" "ML")) ("imperative" ("Ada" "C" "Pascal")) ("object" ("Smalltalk" "Java")))) ;; define the weblet searching the database (define-service (tutorial-ajax/query v) ( :border 1 (map (lambda (x) ( (
x))) (sqlite-map *db* (lambda (x) x) "SELECT name FROM languages WHERE (name LIKE '%~a%')" v)))) === The client code === The client code consists in a simple text input selection. Each time a character is typed-in, the client invokes the ,( "service" "service.wiki") ++tutorial-ajax++ on the server. This returns a vector that is displayed in the ++show++ ++
++ element. (module tutorial-ajax (import tutorial-ajax-database)) (define (tutorial) ( () (let ((show (
""))) ( (
"Type a programming language name") ( :type "text" :onkeyup ~(with-hop ($tutorial-ajax/query this.value) (lambda (h) (innerHTML-set! $show h)))) (
"Results...") show)))) (define-service (tutorial-ajax) (tutorial)) (tutorial) == Version 2 == In this second version instead of writting an HTML tree that is parsed by the client, the server directly send to the client a list. === The server code === The only variation with respect to the first version are located in the ++tutorial-ajax++ service. (module tutorial-ajax-database-v2 (library sqlite) (export tutorial-ajax/query-v2)) ;; create an in-memory database (define *db* (instantiate::sqlite)) ;; create a table (sqlite-exec *db* "CREATE TABLE languages (name STRING, kind STRING)") ;; populate the table (for-each (lambda (kind) (for-each (lambda (lang) (sqlite-exec *db* "INSERT INTO languages VALUES (~q, ~q)" lang (car kind))) (cadr kind))) '(("functional" ("Haskell" "Hop" "ML")) ("imperative" ("Ada" "C" "Pascal")) ("object" ("Smalltalk" "Java")))) ;; define the weblet searching the database (define-service (tutorial-ajax/query-v2 v) (sqlite-map *db* (lambda (x) x) "SELECT name FROM languages WHERE (name LIKE '%~a%')" v)) === The client code === As for the previous version, the client code implements a simple text input selection. Each time a character is typed-in, the client invokes the ,( "service" "service.wiki") ++tutorial-ajax++ on the server. This returns a list of strings from which the client builds a new table that replaces the body of the ++
++ element ++show++ . In this second version, the code for building the table has been //shifted// from the server to the client. (module tutorial-ajax-v2 (import tutorial-ajax-database-v2)) (define (tutorial-v2) ( () (let ((show (
""))) ( (
"Type a programming language name") ( :type "text" :onkeyup ~(with-hop ($tutorial-ajax/query-v2 this.value) (lambda (h) (let ((table ( :border 1 ( (map (lambda (x) ( (
x))) h))))) (if (dom-has-child-nodes? $show) (dom-remove-child! $show (car (dom-child-nodes $show)))) (dom-append-child! $show table))))) (
"Results...") show)))) (define-service (tutorial-ajax-v2) (tutorial-v2))