2004-01-16 20:35:16 -05:00
|
|
|
%%documentclass{article}
|
|
|
|
%%
|
|
|
|
%%usepackage[latin1]{inputenc}
|
|
|
|
%%usepackage{fontenc}
|
|
|
|
%%usepackage{alltt}
|
|
|
|
%%usepackage{url,xspace}
|
|
|
|
%%usepackage{tabularx}
|
|
|
|
%%usepackage{theorem,ulem,float,afterpage}
|
|
|
|
%%normalem %usually, don't use ulem
|
|
|
|
%%
|
|
|
|
%%
|
|
|
|
%%input{decls}
|
|
|
|
%%
|
|
|
|
%%title{Howto write \surflets}
|
|
|
|
%%author{Andreas Bernauer}
|
|
|
|
%%
|
|
|
|
%%begin{document}
|
|
|
|
%%maketitle
|
|
|
|
%%
|
|
|
|
%%tableofcontents
|
|
|
|
%%sloppy
|
2004-01-14 22:46:27 -05:00
|
|
|
|
2003-11-21 14:09:36 -05:00
|
|
|
\section{Introduction}
|
2004-01-16 20:35:16 -05:00
|
|
|
This howto gives a short introduction in how to write a \surflet. It
|
2003-11-21 14:09:36 -05:00
|
|
|
is concentrated on the practical side rather on describing the
|
|
|
|
\surflet API in detail to give you instant succes in running your own
|
|
|
|
surflets. The \surflet API will be described in the SUnet
|
|
|
|
documentation eventually.
|
|
|
|
|
2004-01-14 22:46:27 -05:00
|
|
|
%\marginpar{\surflets are pieces of code for web site scripting.}
|
2003-11-21 14:09:36 -05:00
|
|
|
For those who don't know it already, \surflets are pieces of code that
|
|
|
|
can be executed interactively trough a website. There is a \surflet
|
|
|
|
handler who administrates their execution and suspension and as part
|
|
|
|
of the SUnet webserver. \surflets ease the implementation of web
|
|
|
|
applications in two ways, compared to other server-side scripting
|
|
|
|
tools like Java\texttrademark Servlets or Microsoft\textregistered
|
|
|
|
Active Server Pages or PHP:
|
|
|
|
|
|
|
|
\begin{enumerate}
|
|
|
|
\item \surflets have an automatic program flow control like any
|
|
|
|
other usual program, \ie the web designer doesn't have to care about
|
|
|
|
session management at all. The sequence of the web pages result from
|
|
|
|
their appearance in the program like the print statements in any other
|
|
|
|
usual program.
|
|
|
|
|
|
|
|
\item \surflets come along with a library for robust user
|
|
|
|
interaction. \surflets represent interaction elements of the web page
|
|
|
|
like text input fields or dropdown lists in the \surflet program by
|
|
|
|
specific objects. A web designer can plug in these objects into a
|
|
|
|
website and use them to read out the user input.
|
|
|
|
\end{enumerate}
|
|
|
|
|
|
|
|
The following sections probably assume that you have basic knowledge
|
|
|
|
of the SUnet webserver and scsh. The environment variable
|
2004-01-16 20:35:16 -05:00
|
|
|
\typew{\$sunet} refers to the top level directory of your sunet
|
2003-11-21 14:09:36 -05:00
|
|
|
installation. On my system this is \name{/home/andreas/sw/sunet}.
|
|
|
|
|
|
|
|
\section{How to run the SUnet webserver that handles \surflets}
|
|
|
|
|
|
|
|
The following sections will show pieces of \surflet code you might
|
|
|
|
want to try out. Therefore you need the SUnet webserver running with
|
|
|
|
the ability to serve \surflets. This section tells you how to do it.
|
|
|
|
|
|
|
|
\begin{enumerate}
|
|
|
|
\item You need Oleg's SSAX package (for scsh), to be able to use
|
2004-01-16 20:35:16 -05:00
|
|
|
surflets:
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
\begin{itemize}
|
|
|
|
\item Download Oleg's SSAX package from
|
2004-01-14 22:46:27 -05:00
|
|
|
\url{http://prdownloads.sf.net/ssax/ssax-sr5rs-plt200-4.9.tar.gz?download}.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
\item Uncompress and untar it to any directory. This will create a
|
|
|
|
directory called \name{SSAX}, to which I will refer to as
|
2004-01-16 20:35:16 -05:00
|
|
|
\typew{\$SSAX}.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
2004-01-17 22:32:08 -05:00
|
|
|
\item Unfortunately, this SSAX distribution (plt200-4.9) has a typo in
|
2003-11-21 14:09:36 -05:00
|
|
|
the package definition for scsh. Apply the patch that comes with
|
|
|
|
the \surflets distribution to get rid of this typo:
|
|
|
|
\begin{itemize}
|
2004-01-16 20:35:16 -05:00
|
|
|
\item \typew{cd \$SSAX}
|
|
|
|
\item \typew{patch -p1 < \$sunet/httpd/surflets/SSAX-goodhtml-patch}
|
2003-11-21 14:09:36 -05:00
|
|
|
\end{itemize}
|
|
|
|
\end{itemize}
|
|
|
|
|
|
|
|
\item You can start the SUnet webserver along with the
|
|
|
|
\surflet-handler now. The \surflets distribution comes with a script that
|
|
|
|
does this for you:
|
|
|
|
|
|
|
|
\begin{itemize}
|
2004-01-16 20:35:16 -05:00
|
|
|
\item \typew{cd \$sunet/httpd/surflets}
|
|
|
|
\item \typew{SSAX=\$SSAX ./start-surflet-server}
|
2003-11-21 14:09:36 -05:00
|
|
|
\end{itemize}
|
|
|
|
|
|
|
|
|
|
|
|
Please be patient, scsh has to load a lot of libraries. If the
|
|
|
|
loading succeeds you will see something like this:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
[andreas@hgt surflets]\$ ssax=/home/andreas/sw/SSAX ./start-surflet-server
|
|
|
|
Loading...
|
|
|
|
reading options: ()
|
|
|
|
Going to run SUrflet server with:
|
|
|
|
htdocs-dir: /home/andreas/sw/sunet/httpd/surflets/web-server/root/htdocs
|
|
|
|
surflet-dir: /home/andreas/sw/sunet/httpd/surflets/web-server/root/surflets
|
|
|
|
images-dir: /home/andreas/sw/sunet/httpd/surflets/web-server/root/img
|
|
|
|
port: 8008
|
|
|
|
log-file-name: /home/andreas/sw/sunet/httpd/surflets/web-server/httpd.log
|
|
|
|
a maximum of 5 simultaneous requests, syslogging activated,
|
|
|
|
and home-dir-handler (public_html) activated.
|
|
|
|
|
|
|
|
NOTE: This is the SUrflet server. It does not support cgi.
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
This means the server is up and running. Try to connect to
|
|
|
|
\url{http://localhost:8008} with your browser and you will see the
|
|
|
|
welcome page of the \surflets. You can also already try out some of
|
|
|
|
\surflets that come with the distribution.
|
|
|
|
|
|
|
|
You will probably notice a long response time the first time you load
|
|
|
|
the first \surflet. This is because the server has to load the
|
|
|
|
\surflet libraries. The server handles further requests to \surflets
|
|
|
|
faster.
|
|
|
|
|
|
|
|
If the port the \surflet server tries to use is occupied use, you will
|
|
|
|
see an error message similar to this one:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
Error: 98
|
|
|
|
"Address already in use"
|
|
|
|
#{Procedure 11701 (\%bind in scsh-level-0)}
|
|
|
|
4
|
|
|
|
2
|
|
|
|
(0 . 8008)
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
In this case, pass another port number to the script, \eg 8000:
|
|
|
|
\codex{ssax=\$SSAX ./start-surflet-server -p 8000}
|
|
|
|
|
2004-01-16 20:35:16 -05:00
|
|
|
The \typew{--help} option will show you more parameters that you can
|
2003-11-21 14:09:36 -05:00
|
|
|
adjust, but you won't need them for this howto.
|
|
|
|
\end{enumerate}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\section{How to send web pages}
|
|
|
|
|
|
|
|
This section will discuss some of the various ways in which you can
|
|
|
|
send a web page to a browser that contacted your \surflet.
|
|
|
|
|
|
|
|
\subsection{My first \surflet}
|
|
|
|
\label{sec:first-surflet}
|
|
|
|
|
|
|
|
Traditionally, your first program in any programming language prints
|
|
|
|
something like ``Hello, World!''. We follow this tradition:
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
(define (main req)
|
|
|
|
(send-html/finish
|
|
|
|
'(html (body (h1 "Hello, world!")))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
You can either save a file with that content in the \surflets
|
|
|
|
directory the server mentioned at startup or you can use the file
|
|
|
|
\name{howto/hello.scm} that comes along with the \surflets
|
|
|
|
distribution and which is located in the server's standard \surflets
|
|
|
|
directory. Let's go through the small script step by step:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
This defines a module named \name{surflet} which implements the
|
|
|
|
interface \name{surflet-interface}. \name{surflet-interface} just
|
|
|
|
states that the module exports a function named \name{main} to which
|
|
|
|
we will come shortly. For those of you who know about the scsh module
|
|
|
|
system: Yes, \surflets are basically scsh modules that are loaded
|
|
|
|
dynamically during run time.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
The \name{open} form lists all the modules the \surflet needs. You
|
|
|
|
will probably always need the two modules that are stated here
|
|
|
|
(namely \name{surflets} and \name{scheme-with-scsh}). If you need
|
|
|
|
other modules, like \name{srfi-13} for string manipulation, this is
|
|
|
|
the place where you want to state it.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(begin
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
This just opens the body of the \surflet. All your \surflet code goes
|
|
|
|
here.\footnote{If you know about scsh modules, you probably also know
|
|
|
|
that there is a \name{file} clause that you could use to place the
|
|
|
|
code in a file instead or along with the \name{begin} clause.}
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define (main req)
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Here is the \name{main} function that the interface declared this
|
|
|
|
\surflet will implement. The \name{main} function is the entry point
|
|
|
|
to your \surflet: The server calls this function every time a user
|
|
|
|
browses to your \surflet the first time. The server calls \name{main}
|
|
|
|
with one argument: a representation of the inital request of the
|
|
|
|
browser. We don't have to worry about that at this point.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(send-html/finish
|
|
|
|
'(html (body (h1 "Hello, world!")))))
|
|
|
|
))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{send-html/finish} is one of three function you will regularly
|
|
|
|
use to send web pages to the browser. The other two functions are
|
|
|
|
\name{send-html} and \name{send-html/suspend}.
|
|
|
|
\name{send-html/finish} -- as the name already suggests -- sends a
|
|
|
|
HTML page to the browser and finishes the \surflet. \name{send-html}
|
|
|
|
just sends the HTML page and does not return and
|
|
|
|
\name{send-html/suspend} sends the HTML page and suspends the
|
|
|
|
\surflet, \ie it waits until the user continues with the \surflet,
|
|
|
|
\eg by submitting a webform. We will discuss \name{send-html} and
|
|
|
|
\name{send-html/suspend} in detail later. I will refer to these three
|
|
|
|
functions as the \emph{sending functions}.
|
|
|
|
|
|
|
|
In a \surflet, HTML pages are represented as lists, or, to be more
|
2004-01-17 22:32:08 -05:00
|
|
|
precise, as SXML (S-expression based XML).\label{sec:SXML} The first
|
|
|
|
element of a SXML list is a symbol stating the HTML tag. The other
|
|
|
|
elements of a SXML list are the contents that are enclosed by this
|
|
|
|
HTML tag. The contents can be other SXML list, too. Here are some
|
|
|
|
examples of SXML lists and how they translate to HTML:
|
2003-11-21 14:09:36 -05:00
|
|
|
|
2004-01-16 20:35:16 -05:00
|
|
|
\newcommand{\htmltag}[1]{$\mathtt{<}$#1$\mathtt{>}$}
|
2003-11-21 14:09:36 -05:00
|
|
|
\begin{tabbing}
|
|
|
|
HTML: \medskip\=\kill
|
|
|
|
SXML: \> \texttt{'(p "A paragraph.")} \\
|
2004-01-16 20:35:16 -05:00
|
|
|
HTML: \> \texttt{\htmltag{p}A paragraph.\htmltag{/p}}\\
|
2003-11-21 14:09:36 -05:00
|
|
|
\\
|
|
|
|
SXML: \> \texttt{'(p "A paragraph." (br) "With break line.")} \\
|
2004-01-16 20:35:16 -05:00
|
|
|
HTML: \> \texttt{\htmltag{p}A paragraph.\htmltag{br}With break line.\htmltag{/p}}\\
|
2003-11-21 14:09:36 -05:00
|
|
|
\\
|
|
|
|
SXML: \> \texttt{'(p "Nested" (p "paragraphs"))}\\
|
2004-01-16 20:35:16 -05:00
|
|
|
HTML: \> \texttt{\htmltag{p}Nested\htmltag{p}paragraphs\htmltag{/p}\htmltag{/p}}\\
|
2003-11-21 14:09:36 -05:00
|
|
|
\end{tabbing}
|
|
|
|
|
|
|
|
Attributes are stated by a special list whose first element is the
|
|
|
|
at-symbol. The attribute list must be the second element in the list:
|
|
|
|
|
|
|
|
\begin{tabbing}
|
|
|
|
HTML: \medskip\=\kill
|
|
|
|
SXML: \> \texttt{'(a (@ (href "attr.html")) "Attributed HTML tags.")} \\
|
2004-01-16 20:35:16 -05:00
|
|
|
HTML: \> \texttt{\htmltag{a href="attr.html"}Attributed HTML tags.\htmltag{/a}}\\
|
2003-11-21 14:09:36 -05:00
|
|
|
\\
|
|
|
|
SXML: \> \texttt{'(a (@ (href "attr2.html") (target "\_blank")) "2
|
|
|
|
attributes.")} \\
|
2004-01-16 20:35:16 -05:00
|
|
|
HTML: \> \texttt{\htmltag{a href="attr2.html" target="\_blank"}2
|
|
|
|
attributes.\htmltag{/a}}
|
2003-11-21 14:09:36 -05:00
|
|
|
\end{tabbing}
|
|
|
|
|
|
|
|
As you see from the \surflet example, \name{send-html/finish} expects
|
|
|
|
as an argument SXML. In the example, the SXML translates to the
|
|
|
|
following HTML code:
|
|
|
|
\begin{alltt}
|
|
|
|
<html><body><h1>Hello, world!</h1>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Please note, that there is no check for valid HTML or even XHTML
|
|
|
|
here. The only thing the translation process takes care of are
|
2004-01-16 20:35:16 -05:00
|
|
|
special characters in strings like the ampersand (\typew{\&}). The
|
2003-11-21 14:09:36 -05:00
|
|
|
translation process replaces them by their HTML representation (\eg,
|
2004-01-16 20:35:16 -05:00
|
|
|
\typew{\&}) so you don't have to worry about that when you use
|
2003-11-21 14:09:36 -05:00
|
|
|
strings. Everything else like using valid HTML tags or valid
|
|
|
|
attributes is your responsibility.
|
|
|
|
|
|
|
|
|
|
|
|
\subsection{Dynamic content}
|
|
|
|
|
|
|
|
Let's extend our first \surflet example by some dynamic content, \eg
|
|
|
|
by displaying the current time using scsh's \name{format-date}
|
|
|
|
function. As the HTML page is basically represented as a list, this
|
|
|
|
can be done like this:
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
(define (main req)
|
|
|
|
(send-html/finish
|
|
|
|
`(html (body (h1 "Hello, world!")
|
|
|
|
(p "The current date and time is "
|
|
|
|
,(format-date "~H:~M:~S ~p ~m/~d/~Y"
|
|
|
|
(date)))))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
This \surflet can be found in \name{howto/hello-date.scm}. The
|
|
|
|
beginning of this \surflet is the same as in the previous example.
|
|
|
|
The difference lies in the argument to \name{send-html/finish}. Note
|
2004-01-16 20:35:16 -05:00
|
|
|
that the argument starts with a backquote (\typew{`}) rather than with
|
|
|
|
a regular quote (\typew{'}) as in the previous example.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
Instead of passing a ``static'' list, \ie a list whose contents are
|
|
|
|
given before execution, this \surflet uses the quasiquote and unquote
|
|
|
|
feature of Scheme to create a ``dynamic'' list, \ie list whose
|
|
|
|
contents are given only during execution. A ``dynamic'' list is
|
2004-01-16 20:35:16 -05:00
|
|
|
introduced by a backquote (\typew{`}) and its dynamic contents are
|
|
|
|
noted by commata (\typew{,}). Thus, if the \surflet is executed while
|
2003-11-21 14:09:36 -05:00
|
|
|
I am writing this howto, the argument to \name{send-html/finish} above
|
|
|
|
is translated to
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
'(html (body (h1 "Hello, world!")
|
|
|
|
(p "The current date and time is "
|
|
|
|
"13:09:03 PM 11/18/2003")))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\noindent\emph{before} it is passed to \name{send-html/finish}. Thus, using
|
|
|
|
dynamic content can be easily done with Scheme's quasiquote and
|
|
|
|
unquote feature. Of course, you can build your list in any way you
|
|
|
|
want; the quasiquote notation is just a convenient way to do it.
|
|
|
|
|
|
|
|
|
|
|
|
\subsection{Several web pages in a row}
|
|
|
|
|
|
|
|
The previous example \surflets only showed one page and finished
|
|
|
|
afterwards. Here, we want to present to web pages in a row. We use
|
|
|
|
the previously mentioned function \name{send-html/suspend}, which
|
|
|
|
suspends after it has send the page and continues when the user
|
|
|
|
clicked for the next page. In contrast to \name{send-html/finish},
|
|
|
|
that expected SXML, \name{send-html/suspend} expects a function that
|
|
|
|
takes an argument and returns SXML. The parameter the function gets
|
|
|
|
(here: \name{k-url} is the URL that points to the next
|
|
|
|
page:\footnote{In the API this URL is called the \emph{continuation
|
|
|
|
URL}.}
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
|
|
|
|
(define (main req)
|
|
|
|
(send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html (body (h1 "Hello, world!")
|
|
|
|
(p (a (@ (href ,k-url)) "Next page -->"))))))
|
|
|
|
(send-html/finish
|
|
|
|
'(html (body (h1 "Hello, again!")))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
This \surflet can be found in \name{howto/hello-twice.scm}. This
|
|
|
|
example first displays a web page with the message ``Hello, world!''
|
|
|
|
and a link to the next page labeled with ``Next page --$>$''. When the
|
|
|
|
user clicks on the provided link, \name{send-html/suspend} returns and
|
|
|
|
the the next statement after the call to \name{send-html/suspend} is
|
|
|
|
executed. Here it is \name{send-html/finish} which shows a web page
|
|
|
|
with the message ``Hello, again!''.
|
|
|
|
|
|
|
|
When \name{send-html/suspend} returns, (almost) the complete context
|
|
|
|
of the running \surflet is restored. Thus, every variable in the
|
|
|
|
\surflet will retain its value during suspension. The consequence is
|
|
|
|
that you don't have to worry about sessions, sesssion variables and
|
|
|
|
alike. The user can freely use the back button of her browser or
|
|
|
|
clone a window while the \surflet will keep on responding in the
|
|
|
|
expected way. This is all automatically managed by the
|
|
|
|
\surflet-handler.
|
|
|
|
|
|
|
|
The only exception are variables whose values are changed by side
|
|
|
|
effects, \eg if you change a variable via \name{set!}. These
|
|
|
|
variables keep their modified values, allowing communication between
|
|
|
|
sessions of the same \surflet.\footnote{If you want to change a
|
|
|
|
variable via side effects but you don't want to interfere with other
|
|
|
|
session, you can use \name{set-session-data!} and
|
|
|
|
\name{get-session-data}. See the API documentation for further
|
|
|
|
information.}
|
|
|
|
|
|
|
|
|
|
|
|
\subsection{Begin and end of sessions}
|
|
|
|
|
|
|
|
So far I don't have mentioned too much details about sessions. The
|
|
|
|
reason is, as mentioned before, that the \surflet handler takes of the
|
|
|
|
session automatically as described in the previous paragraph.
|
|
|
|
%, \ie it starts the session automatically when an
|
|
|
|
%instance of your \surflet starts and takes care of the saving and
|
|
|
|
%restoring of all variable values during suspensions of your \surflet
|
2004-01-16 20:35:16 -05:00
|
|
|
%instance, except for \typew{set!}ed values.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
The only thing you have to worry about is when your session
|
|
|
|
\emph{ends}. As long as your session hasn't been finished by
|
|
|
|
\name{send-html/finish}, the user can move freely between the web
|
|
|
|
pages your \surflet provides. Once you've finished the session via
|
|
|
|
\name{send-html/finish}, this freedom ends. As the session is over,
|
|
|
|
the user will get an error message when he tries to recall some web
|
|
|
|
page from the server. The server will tell the user about the
|
|
|
|
possible reasons for the error (namely that most likely the session
|
|
|
|
was finished) and provides a link to the beginning of a new session.
|
|
|
|
|
|
|
|
Thus, \name{send-html/suspend} suspends the current execution of a
|
|
|
|
\surflet, returning with the request for the next web page of your
|
|
|
|
\surflet and \name{send/finish} finishes the session. The third
|
|
|
|
sending function is \name{send-html} which just sends a web page.
|
|
|
|
\name{send-html} does not return and does not touch the session of
|
|
|
|
your \surflet instance.
|
|
|
|
|
|
|
|
\subsection{Abbreviations in SXML}
|
2004-01-14 22:46:27 -05:00
|
|
|
\label{sxml-abbrvs}
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
The example in subsection ``Several web pages in a row'' wrote down
|
|
|
|
the link to the next web page explicitly via the ``a''-tag. As
|
|
|
|
websites contain a lot of links, the sending functions (like
|
|
|
|
\name{send-html/finish}) allow an abbreviation. The following SXML
|
|
|
|
snippets are equivalent:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(a (@ (href ,k-url)) "Next page -->")
|
|
|
|
(url ,k-url "Next page -->")
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{url} expects the target address as the next element and includes
|
|
|
|
every text afterwards as part of the link.
|
|
|
|
|
2004-01-16 20:35:16 -05:00
|
|
|
There are also some other abbreviations. \typew{(nbsp)} inserts
|
|
|
|
`\typew{\ }' into the HTML, \typew{(*COMMENT* \dots)} inserts a
|
|
|
|
comment, and with \typew{(plain-html \dots)} you can insert arbitrary
|
2003-11-21 14:09:36 -05:00
|
|
|
HTML code (\ie strings) directly , without any string conversions.
|
|
|
|
The last abbreviation, \name{surflet-form}, is discussed in the next
|
|
|
|
section.
|
|
|
|
|
|
|
|
|
|
|
|
\section{How to write web forms}
|
|
|
|
|
|
|
|
The \surflets come along with a libary for easy user interaction. The
|
2004-01-14 22:46:27 -05:00
|
|
|
following subsections will show how to write web forms and how to get
|
|
|
|
the data the user has entered.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
|
|
|
|
\subsection{Simple web forms}
|
|
|
|
|
|
|
|
Let's write a \surflet that reads user input and prints it out on the
|
|
|
|
next page:
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
(define (main req)
|
|
|
|
(let* ((text-input (make-text-field))
|
|
|
|
(submit-button (make-submit-button))
|
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(body
|
|
|
|
(h1 "Echo")
|
|
|
|
(surflet-form ,k-url
|
|
|
|
(p "Please enter something:"
|
|
|
|
,text-input
|
|
|
|
,submit-button)))))))
|
|
|
|
(bindings (get-bindings req))
|
|
|
|
(user-input (input-field-value text-input bindings)))
|
|
|
|
(send-html/finish
|
|
|
|
`(html (body
|
|
|
|
(h1 "Echo result")
|
|
|
|
(p "You've entered: '" ,user-input "'."))))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
Here are the details to the code in \name{main}:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define (main req)
|
|
|
|
(let* ((text-input (make-text-field))
|
|
|
|
(submit-button (make-submit-button))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{make-text-field} and \name{make-submit-button} define two user
|
|
|
|
interaction elements: a text input field and a submit button.
|
|
|
|
\surflets represent user interaction elements by \name{Input-field}
|
|
|
|
objects. Thus, user interaction elements are first class values in
|
|
|
|
\surflet, unlike in many other web scripting languages, \eg Java
|
2004-01-14 22:46:27 -05:00
|
|
|
surflets, PHP or Microsoft Active Server Pages, \ie you have a
|
|
|
|
representation of a user interaction element in your program that you
|
|
|
|
can pass to functions, receive them as return values, etc. You'll see
|
|
|
|
soon the advantages of this approach.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
\begin{alltt}
|
2004-01-14 22:46:27 -05:00
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(body
|
|
|
|
(h1 "Echo")
|
|
|
|
(surflet-form ,k-url
|
|
|
|
(p "Please enter something:"
|
|
|
|
,text-input
|
|
|
|
,submit-button)))))))
|
2003-11-21 14:09:36 -05:00
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Instead of discarding the return value of \name{send-html/suspend} as
|
|
|
|
in the examples of the previous section, this time we'll save the
|
|
|
|
return value, as it will contain the data the user has entered in our
|
|
|
|
text input field.
|
|
|
|
|
|
|
|
The definition of the website is as described in the previous section
|
|
|
|
except for the new abbreviation \name{surflet-form}.
|
|
|
|
\name{surflet-form} creates the HTML code for a web form and expects
|
|
|
|
as its next value the URL to the next webpage as provided by
|
|
|
|
\name{send-html/suspend}, here named
|
|
|
|
\name{k-url}. The remaining arguments constitute the content of the
|
|
|
|
web form. Thus, the code above is equal to the following SXML:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(form (@ (action ,k-url) (method "GET"))
|
|
|
|
(p "Please enter something:"
|
|
|
|
,text-input
|
|
|
|
,submit-button))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
If you want to use the POST method instead of the default GET method,
|
|
|
|
add the symbol \name{'POST} after the URL:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(surflet-form ,k-url
|
|
|
|
POST
|
|
|
|
(p "Please enter something:"
|
|
|
|
,text-input
|
|
|
|
,submit-button))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
The web page \name{send-html/suspend} sends to the browser looks like
|
2004-01-16 20:35:16 -05:00
|
|
|
in figure [missing]
|
|
|
|
%\ref{fig:user1-1}.
|
|
|
|
After the user has entered his data into
|
2003-11-21 14:09:36 -05:00
|
|
|
the web form, \name{send-html/suspend} returns with the request object
|
|
|
|
of the browser for the next page. This request object contains the
|
|
|
|
data the user has entered.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(bindings (get-bindings req))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
With the function \name{get-bindings} we pull out the user data of the
|
|
|
|
request object. Here we save the user data into the variable
|
|
|
|
\name{bindings}. \name{get-bindings} works for both request methods
|
2004-01-16 20:35:16 -05:00
|
|
|
\typew{GET} and \typew{POST}.
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
\begin{alltt}
|
2004-01-14 22:46:27 -05:00
|
|
|
(user-input (input-field-value text-input bindings)))
|
2003-11-21 14:09:36 -05:00
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
With the function \name{input-field-value} and the extracted user data
|
|
|
|
we can read the value for an \name{input-field}. Here, we want to
|
|
|
|
know what the user has entered into the \name{text-input-field}.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(send-html/finish
|
|
|
|
`(html (body
|
|
|
|
(h1 "Echo result")
|
|
|
|
(p "You've entered: '" ,user-input "'."))))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
After we have extracted what the user has entered into the text field,
|
|
|
|
we can show the final page of our \surflet and echo her input.
|
|
|
|
|
|
|
|
The scheme for user interaction is thus about the following:
|
|
|
|
|
|
|
|
\begin{itemize}
|
|
|
|
\item Create the user interaction elements, \name{input-field}s, you
|
|
|
|
want to use in your web page.
|
|
|
|
\item Send the web page with \name{send-html/suspend} to the browser.
|
|
|
|
Plug in the \name{input-field}s in the web page as if they were usual
|
|
|
|
values. Save the return value of \name{send-html/suspend}.
|
|
|
|
\item Extract the user data from the return value of
|
|
|
|
\name{send-html/suspend}.
|
|
|
|
\item Read the values of each \name{input-field} out of the extracted
|
|
|
|
user data with \name{input-field-value}.
|
|
|
|
\end{itemize}
|
|
|
|
|
|
|
|
The complete list of functions that create \name{input-fields} can be
|
|
|
|
found in the API.
|
|
|
|
|
|
|
|
\subsection{Return types other than strings}
|
2004-01-14 22:46:27 -05:00
|
|
|
\label{subsec:input-return}
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
As the user interaction elements are first class values in a \surflet,
|
|
|
|
they can return other types than strings. For example the \surflets
|
|
|
|
come with a number input field, \ie a input field that accepts only
|
|
|
|
text that can be interpreted as a number. If the user enters
|
|
|
|
something that is not a number, \name{input-field-value} will return
|
|
|
|
\sharpf as the value of the number input field. If you'd rather want
|
2004-01-14 22:46:27 -05:00
|
|
|
an error to be raised, you can use \name{raw-input-field-value}
|
2003-11-21 14:09:36 -05:00
|
|
|
instead.
|
|
|
|
|
|
|
|
\subsubsection{Annotated input fields}
|
|
|
|
|
|
|
|
The return value of an input field need not even be a primitive
|
|
|
|
value. The \surflets library allows you to ``annotate'' your input
|
|
|
|
fields with values which should be returned indicated by the user's
|
2004-01-14 22:46:27 -05:00
|
|
|
input. \Eg, consider this \surflet:
|
2003-11-21 14:09:36 -05:00
|
|
|
|
2004-01-14 22:46:27 -05:00
|
|
|
\begin{listing}
|
2003-11-21 14:09:36 -05:00
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
2004-01-14 22:46:27 -05:00
|
|
|
handle-fatal-error
|
2003-11-21 14:09:36 -05:00
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
(define (main req)
|
|
|
|
(let* ((select-input-field
|
|
|
|
(make-select
|
|
|
|
(map make-annotated-select-option
|
|
|
|
'("Icecream" "Chocolate" "Candy")
|
|
|
|
'(1.5 2.0 0.5))))
|
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(head (title "Sweet Store"))
|
|
|
|
(body
|
|
|
|
(h1 "Your choice")
|
|
|
|
(surflet-form
|
|
|
|
,k-url
|
|
|
|
(p "Select the sweet you want:"
|
|
|
|
,select-input-field)
|
|
|
|
,(make-submit-button)))))))
|
|
|
|
(bindings (get-bindings req))
|
2004-01-14 22:46:27 -05:00
|
|
|
(price (input-field-value select-input-field
|
|
|
|
bindings)))
|
2003-11-21 14:09:36 -05:00
|
|
|
(send-html/finish
|
|
|
|
`(html (head (title "Receipt"))
|
|
|
|
(body
|
|
|
|
(h2 "Your receipt:")
|
|
|
|
(p "This costs you \$" ,price "."))))))
|
|
|
|
))
|
2004-01-14 22:46:27 -05:00
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
Let's go through the important part of this \surflet:
|
2003-11-21 14:09:36 -05:00
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(let* ((select-input-field
|
|
|
|
(make-select
|
|
|
|
(map make-annotated-select-option
|
|
|
|
'("Icecream" "Chocolate" "Candy")
|
|
|
|
'(1.5 2.0 0.5))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Here we define a select input field (a dropdown list). Instead of
|
|
|
|
only providing a list of values that shall show up in the dropdown
|
|
|
|
list and later examining which one was select and looking up the price
|
2004-01-14 22:46:27 -05:00
|
|
|
for the sweet, we bind the values in the list with the price while we
|
2003-11-21 14:09:36 -05:00
|
|
|
create the select input field. When the select input field is shown
|
|
|
|
in the browser, it will show the names of the sweets. When we lookup
|
|
|
|
the user's input, we will get the associated price for the sweet.
|
|
|
|
Again, this works not only with numbers, but with any arbitrary Scheme
|
|
|
|
value (\eg functions or records).
|
|
|
|
|
2004-01-14 22:46:27 -05:00
|
|
|
\subsection{Sending error messages}
|
|
|
|
|
|
|
|
If a user tries to forge a \surflet-URL (\eg by extracting the
|
|
|
|
continuation URL from the HTML source and editing it), your \surflet
|
|
|
|
has to deal with unexpected values. Usually, a forged \surflet-URL
|
|
|
|
will result in an error that is raised in one of the \surflet library
|
|
|
|
functions. If you don't catch this error, the \surflet handler will
|
|
|
|
catch it for you, send an error message to the user
|
|
|
|
\emph{and terminating the current session} as your \surflet obviously
|
|
|
|
encountered an unexpected error and might be in an invalid state. If
|
|
|
|
you don't want this behavior, you can catch this error (like any other
|
|
|
|
error that is raised by \scsh) and send your own error message with
|
|
|
|
\name{send-error} which is located in the \name{surflets/error}
|
|
|
|
package. The \name{handle-fatal-error} package can be useful in this
|
|
|
|
context. Here's an example, that modifies the example from the
|
|
|
|
previous subsection (modifications emphasized):
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
\codemph{ handle-fatal-error
|
|
|
|
surflets/error}
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
(define (main req)
|
|
|
|
(let* ((select-input-field
|
|
|
|
(make-select
|
|
|
|
(map make-annotated-select-option
|
|
|
|
'("Icecream" "Chocolate" "Candy")
|
|
|
|
'(1.5 2.0 0.5))))
|
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(head (title "Sweet Store"))
|
|
|
|
(body
|
|
|
|
(h1 "Your choice")
|
|
|
|
(surflet-form
|
|
|
|
,k-url
|
|
|
|
(p "Select the sweet you want:"
|
|
|
|
,select-input-field)
|
|
|
|
,(make-submit-button)))))))
|
|
|
|
(bindings (get-bindings req))
|
|
|
|
\codemph{ (cost (with-fatal-error-handler
|
|
|
|
(lambda (condition decline)
|
|
|
|
(send-error (status-code bad-request)
|
|
|
|
req
|
|
|
|
"No such option or internal
|
|
|
|
error. Please try again."))
|
|
|
|
(raw-input-field-value select-input-field
|
|
|
|
bindings))))}
|
|
|
|
(send-html/finish
|
|
|
|
`(html (head (title "Receipt"))
|
|
|
|
(body
|
|
|
|
(h2 "Your receipt:")
|
|
|
|
(p "This costs you \$" ,cost "."))))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
Let's examine the important part of this example:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(cost (with-fatal-error-handler
|
|
|
|
(lambda (condition decline)
|
|
|
|
(send-error (status-code bad-request)
|
|
|
|
req
|
|
|
|
"No such option or internal
|
|
|
|
error. Please try again."))
|
|
|
|
(raw-input-field-value select-input-field
|
|
|
|
bindings))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
As mentioned in \ref{subsec:input-return}, this \surflet uses
|
|
|
|
\name{raw-input-field-value} instead of \name{input-field-value}
|
|
|
|
because the former raises an error while the latter returns \sharpf in
|
|
|
|
case of an error.
|
|
|
|
|
|
|
|
If a user forges a continuation URL, \name{raw-input-field-value}
|
|
|
|
might not be able to find a valid value for the
|
|
|
|
\name{select-input-field} and raises an error. This error is catched
|
|
|
|
by the error handler which was installed by
|
|
|
|
\name{with-fatal-error-handler}. The error handler uses
|
|
|
|
\name{send-error} to send an error message to the browser. Its first
|
|
|
|
argument is the status code of the error message. See the
|
|
|
|
documentation of the \sunet webserver for different status codes. The
|
|
|
|
second argument is the request which was processed while the error
|
|
|
|
occured. The last argument is a free message text to explain the
|
|
|
|
cause of the error to the user.
|
|
|
|
|
|
|
|
While in the original \surflet the user will still see the resulting
|
|
|
|
receipt web page with an empty dollar amount and has her session
|
|
|
|
finished, this modified version will show an error message and won't
|
|
|
|
finish the session.
|
|
|
|
|
|
|
|
It is your choice, which version you choose, \ie if you let the
|
|
|
|
\surflet handler handle the occuring error automatically or if you
|
|
|
|
install your own error handlers and use \name{raw-input-field-value}.
|
|
|
|
However, be careful if you use \name{raw-input-field-value} along with
|
|
|
|
check boxes. The HTML standard dictates that an unchecked check box
|
|
|
|
does not appear in the data the browser sends to the server. Thus,
|
|
|
|
\name{raw-input-field-value} won't find the check box in the data and
|
|
|
|
raise an error which is not a ``real'' error as you migh expect it.
|
|
|
|
|
|
|
|
|
|
|
|
\subsection{Your own input fields}
|
|
|
|
|
|
|
|
The \surflet library contains constructors for all input fields that
|
|
|
|
are described in the HTML~2.0 standard. See the \surflet API for a
|
|
|
|
complete list. The \surflet library also allows you to create your
|
|
|
|
own input fields, \eg an input field that only accepts valid dates as
|
|
|
|
its input. This subsection gives you a short overview how to do
|
|
|
|
this. You will find the details in the \surflet API.
|
|
|
|
|
|
|
|
Let's have a look at an \surflet that uses its own input field. The
|
|
|
|
``input field'', called nibble input field, consists of eight check
|
|
|
|
boxes which represent bits of a nibble (half a byte). The value of
|
|
|
|
the input field is the number that the check boxes represent. \Eg, if
|
|
|
|
the user checks the last two checkboxes, the value of the nibble input
|
|
|
|
field is 3.
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
surflets/my-input-fields
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
|
|
|
|
(define (make-nibble-input-fields)
|
|
|
|
(let ((checkboxes (list (make-annotated-checkbox 8)
|
|
|
|
(make-annotated-checkbox 4)
|
|
|
|
(make-annotated-checkbox 2)
|
|
|
|
(make-annotated-checkbox 1))))
|
|
|
|
(make-multi-input-field
|
|
|
|
#f "nibble-input"
|
|
|
|
(lambda (input-field bindings)
|
|
|
|
(let loop ((sum 0)
|
|
|
|
(checkboxes checkboxes))
|
|
|
|
(if (null? checkboxes)
|
|
|
|
sum
|
|
|
|
(loop (+ sum (or (input-field-value (car checkboxes)
|
|
|
|
bindings)
|
|
|
|
0))
|
|
|
|
(cdr checkboxes)))))
|
|
|
|
'()
|
|
|
|
(lambda (ignore)
|
|
|
|
checkboxes))))
|
|
|
|
|
|
|
|
(define nibble-input-field (make-nibble-input-fields))
|
|
|
|
|
|
|
|
(define (main req)
|
|
|
|
(let* ((req (send-html/suspend
|
|
|
|
(lambda (new-url)
|
|
|
|
`(html (title "Nibble Input Widget")
|
|
|
|
(body
|
|
|
|
(h1 "Nibble Input Widget")
|
|
|
|
(p "Enter your nibble (msb left):")
|
|
|
|
(surflet-form ,new-url
|
|
|
|
,nibble-input-field
|
|
|
|
,(make-submit-button)))))))
|
|
|
|
(bindings (get-bindings req))
|
|
|
|
(number (input-field-value nibble-input-field bindings)))
|
|
|
|
(send-html
|
|
|
|
`(html (title "Result")
|
|
|
|
(body
|
|
|
|
(h2 "Result")
|
|
|
|
(p "You've entered " ,number "."))))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
Let's go through this \surflet step by step.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
surflets/my-input-fields
|
|
|
|
scheme-with-scsh)
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
If you want to create your own input fields, you have to open the
|
|
|
|
\name{surflets/my-input-fields} package.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(begin
|
|
|
|
(define (make-nibble-input-fields)
|
|
|
|
(let ((checkboxes (list (make-annotated-checkbox 8)
|
|
|
|
(make-annotated-checkbox 4)
|
|
|
|
(make-annotated-checkbox 2)
|
|
|
|
(make-annotated-checkbox 1))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{make-nibble-input-fields} is the constructor for our new type of
|
|
|
|
input field. As mentioned before, we use check boxes to let the user
|
|
|
|
enter the nibble. We use annotated checkboxes for this purpose whose
|
|
|
|
value is the value in the nibble.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(make-multi-input-field
|
|
|
|
#f "nibble-input"
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
The value of our new input field will depend on the value of several
|
|
|
|
real input fields. Thus we create a multi input field. If the value
|
|
|
|
depended only on the browser data that is associated to the name of
|
|
|
|
our input field, we would use \name{make-input-field} instead, which
|
|
|
|
creates a usual input field. \Eg, if we wanted to create a date input
|
|
|
|
field that accepts only valid dates as input and used a text input
|
|
|
|
field for this purpose, we would use \name{make-input-field}.
|
|
|
|
|
|
|
|
The first two parameters is the name of the input field and its type.
|
|
|
|
As we use checkboxes to represent our input field, we don't need the
|
|
|
|
name field. The type field is meant for debugging purposes, so you
|
|
|
|
can identify the type of the input field during a debugging session.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(lambda (input-field bindings)
|
|
|
|
(let loop ((sum 0)
|
|
|
|
(checkboxes checkboxes))
|
|
|
|
(if (null? checkboxes)
|
|
|
|
sum
|
|
|
|
(loop (+ sum (or (input-field-value (car checkboxes)
|
|
|
|
bindings)
|
|
|
|
0))
|
|
|
|
(cdr checkboxes)))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
The next parameter is the so called transformer function.
|
|
|
|
\name{raw-input-field-value} calls the transformer function to
|
|
|
|
determine the value of the input field depending on the given
|
|
|
|
bindings. The transformer function of a multi input field (which our
|
|
|
|
nibble input field is) gets the input field and the bindings as
|
|
|
|
parameters. A usual input field would only get the data that is
|
|
|
|
associated to its name.
|
|
|
|
|
|
|
|
The transformer function of our nibble input field goes over each
|
|
|
|
check box, looks it up in the bindings and adds its value to a sum, if
|
|
|
|
\name{input-field-value} can find it. If it can't find it, a zero is
|
|
|
|
added instead. The value of our nibble input field is the resulting
|
|
|
|
sum.
|
|
|
|
|
|
|
|
The rest of the \surflet is straight forward and not repeated here
|
|
|
|
again. We create, use and evaluate the nibble input field as we do
|
|
|
|
with every other input field.
|
|
|
|
|
|
|
|
|
|
|
|
\section{Program flow control}
|
|
|
|
|
|
|
|
With the techniques shown so far it is rather difficult to create a
|
|
|
|
web page that has several different successor webpages rather than
|
|
|
|
only one web page. This section will show you how to do this with the
|
|
|
|
\surflets. Basically, there are two different methods how to perform
|
|
|
|
this task. One method is to mark each link in some way and evaluate
|
|
|
|
the mark after \name{send-html/suspend} has returned. The other
|
|
|
|
method is to bind a callback function to each link that is called when
|
|
|
|
the user selects the link. This section shows both methods.
|
|
|
|
|
|
|
|
\subsection{Dispatching to more than one successor web page}
|
|
|
|
|
|
|
|
The basic idea of dispatching is to add a mark to a link and evaluate
|
|
|
|
it after the user has clicked on a link and \name{send-html/suspend}
|
|
|
|
returned. Let's have a look at an example. It shows an entry page at
|
|
|
|
which the user states the language in which she wants to be greeted:
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
|
|
|
|
(define (main req)
|
|
|
|
(let* ((english (make-address))
|
|
|
|
(german (make-address))
|
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(head (title "Multi-lingual"))
|
|
|
|
(body
|
|
|
|
(h2 "Select your language:")
|
|
|
|
(ul
|
|
|
|
(li (url ,(english k-url) "English")
|
|
|
|
(li (url ,(german k-url) "Deutsch")))))))))
|
|
|
|
(bindings (get-bindings req)))
|
|
|
|
(case-returned-via bindings
|
|
|
|
((english) (result-page "Hello, how are you?"))
|
|
|
|
((german) (result-page "Hallo, wie geht es Ihnen?")))))
|
|
|
|
|
|
|
|
(define (result-page text)
|
|
|
|
(send-html/finish
|
|
|
|
`(html
|
|
|
|
(head (title "Greeting"))
|
|
|
|
(body
|
|
|
|
(h2 ,text)))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
Let's see how \name{main} presents the different options:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define (main req)
|
|
|
|
(let* ((english (make-address))
|
|
|
|
(german (make-address))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Of course you don't have to worry about adding the mark to the links.
|
|
|
|
Instead, we create the links with \name{make-address}.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(head (title "Multi-lingual"))
|
|
|
|
(body
|
|
|
|
(h2 "Select your language:")
|
|
|
|
(ul
|
|
|
|
(li (url ,(english k-url) "English")
|
|
|
|
(li (url ,(german k-url) "Deutsch")))))))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{make-address} returns a function you can call to create the
|
|
|
|
link as we did here with
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(li (url ,(english k-url) "English")
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
This creates a list item which contains a hyperlink labeled
|
|
|
|
``English''. The hyperlink is created by the SXML abbreviation
|
|
|
|
\name{url} as shown in \ref{sxml-abbrvs}. Instead of just passing
|
|
|
|
the continuation URL \name{k-url} to \name{url}, we create the marked
|
|
|
|
link by calling the function \name{make-adddress} gave us.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(bindings (get-bindings req)))
|
|
|
|
(case-returned-via bindings
|
|
|
|
((english) (result-page "Hello, how are you?"))
|
|
|
|
((german) (result-page "Hallo, wie geht es Ihnen?")))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
After \name{send-html/suspend} has returned, we can evaluate which
|
|
|
|
link the user has clicked by using \name{case-returned-via}.
|
|
|
|
\name{case-returned-via} works similar to the regular \name{case} of
|
|
|
|
Scheme. It evaluates the body of the form whose initial list contains
|
|
|
|
the address that the user used to leave the website. \Eg, if the user
|
|
|
|
has selected ``German'' as her preferred language and thus clicked on
|
|
|
|
the link we have named \name{german} in our \surflet,
|
|
|
|
\name{case-returned-via} will evaluate its second form and the
|
|
|
|
\surflet will display the greeting in German.
|
|
|
|
|
|
|
|
\name{case-returned-via} is syntactic sugar like the regular
|
|
|
|
\name{case}. However, instead of \name{equal?} it uses
|
|
|
|
\name{returned-via}. \name{returned-via} takes the bindings and
|
|
|
|
and an address and returns \sharpt, if the user left the web page via
|
|
|
|
this address (\ie, via the link that is represented by this address)
|
|
|
|
and \sharpf otherwise. \name{returned-via} does not end with a
|
|
|
|
question mark as it might return other values as well as we will see
|
|
|
|
shortly. Of course, it is your choice if you want to use
|
|
|
|
\name{case-returned-via} or explicitly \name{returned-via}.
|
|
|
|
|
|
|
|
|
|
|
|
\subsection{Annotated dispatching}
|
|
|
|
|
|
|
|
The approach shown in the previous subsection has one major drawback:
|
|
|
|
the meaning of an address becomes clear only when you look at the
|
|
|
|
dispatching section of \name{case-returned-via}. This subsection
|
|
|
|
shows you how to link the meaning and the representation of an address
|
|
|
|
closer together.
|
|
|
|
|
|
|
|
We modify the previous code example slightly to this \surflet
|
|
|
|
(differences emphasized):
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
|
|
|
|
(define (main req)
|
|
|
|
(let* (\codemph{(language (make-annotated-address))}
|
|
|
|
(req (send-html/suspend
|
|
|
|
(lambda (k-url)
|
|
|
|
`(html
|
|
|
|
(head (title "Multi-lingual"))
|
|
|
|
(body
|
|
|
|
(h2 "Select your language:")
|
|
|
|
(ul
|
|
|
|
(li (url ,\codemph{(language k-url
|
|
|
|
"Hello, how are you?")}
|
|
|
|
"English")
|
|
|
|
(li (url ,\codemph{(language k-url
|
|
|
|
"Hallo, wie geht es Ihnen?")}
|
|
|
|
"Deutsch")))))))))
|
|
|
|
(bindings (get-bindings req)))
|
|
|
|
(case-returned-via bindings
|
|
|
|
\codemph{ ((language) => result-page))))}
|
|
|
|
|
|
|
|
(define (result-page text)
|
|
|
|
(send-html/finish
|
|
|
|
`(html
|
|
|
|
(head (title "Greeting"))
|
|
|
|
(body
|
|
|
|
(h2 ,text)))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
Let's look at the differing parts:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(let* ((language (make-annotated-address))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
To link the meaning with the address itself, we use an annotated
|
|
|
|
address. As we can annotate the address now, we don't need two
|
|
|
|
distinct addresses anymore.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(li (url ,(language k-url
|
|
|
|
"Hello, how are you?")
|
|
|
|
"English")
|
|
|
|
(li (url ,(language k-url
|
|
|
|
"Hallo, wie geht es Ihnen?")
|
|
|
|
"Deutsch")))))))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
In addition to the continuation URL \name{k-url} we also annotate the
|
|
|
|
address with a value. Here we use the different greetings as the
|
|
|
|
annotation. The address can be annotated with any arbitrary Scheme
|
|
|
|
value, \eg functions or records.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(case-returned-via bindings
|
|
|
|
((language) => result-page))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{case-returned-via} has an extended syntax similar to \name{cond}
|
2004-01-17 22:32:08 -05:00
|
|
|
that it useful with annotated address. The arrow `\name{=>}' calls
|
|
|
|
the following function with the annotation of the address via which
|
|
|
|
the user has left the web page. You can extract the annotation
|
|
|
|
yourself with \name{returned-via} like this:
|
2004-01-14 22:46:27 -05:00
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(result-page (returned-via language bindings))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
This will call \name{result-page} with the annotation of the address
|
|
|
|
via which the user has left the web page. \name{returned-via} returns
|
|
|
|
\sharpf, if the user didn't leave the web page via one of the links
|
|
|
|
created with this address (which is not really possible in this
|
|
|
|
example).
|
|
|
|
|
|
|
|
\subsection{Callbacks}
|
|
|
|
|
|
|
|
The other method to lead to different successor web pages is using
|
|
|
|
callbacks. A callback is a function that is called if the user leaves
|
|
|
|
the web page via an associated link. This is different from the
|
|
|
|
dispatch method where \name{send-html/suspend} returns. You can
|
|
|
|
create a web page that only uses callbacks to lead to successor web
|
|
|
|
page and thus you don't have to use \name{send-html/suspend}.
|
|
|
|
Instead, you can use \name{send-html}.
|
|
|
|
|
|
|
|
Although it is possible to use several different callbacks in a single
|
|
|
|
web page, this is not recommended. The reason lies in the
|
|
|
|
implementation of a callback, which saves the current continuation.
|
|
|
|
Several different callbacks would result in the storage of the several
|
|
|
|
slightly different continuations. This is unnecessary, as you can
|
|
|
|
annotate the callbacks with the arguments for the callback function.
|
|
|
|
Let's see an example which is a variation of the previous examples
|
2004-01-17 22:32:08 -05:00
|
|
|
(important parts / differences emphasized):
|
2004-01-14 22:46:27 -05:00
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
\codemph{surflets/callbacks}
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
|
|
|
|
(define (main req)
|
|
|
|
(let ((language \codemph{(make-annotated-callback result-page)}))
|
|
|
|
(\codemph{send-html}
|
|
|
|
`(html
|
|
|
|
(head (title "Multi-lingual"))
|
|
|
|
(body
|
|
|
|
(h2 "Select your language:")
|
|
|
|
(ul
|
|
|
|
(li (url ,\codemph{(language "Hello, how are you?")}
|
|
|
|
"English")
|
|
|
|
(li (url ,\codemph{(language "Hallo, wie geht es Ihnen?")}
|
|
|
|
"Deutsch")))))))))
|
|
|
|
|
|
|
|
(define \codemph{(result-page req text)}
|
|
|
|
(send-html/finish
|
|
|
|
`(html
|
|
|
|
(head (title "Greeting"))
|
|
|
|
(body
|
|
|
|
(h2 ,text)))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
2003-11-21 14:09:36 -05:00
|
|
|
|
2004-01-14 22:46:27 -05:00
|
|
|
The differences to the dispatch method are the following: you have to
|
|
|
|
open the \name{surflets/callbacks} package to use callbacks, you don't
|
|
|
|
use the continuation URL to create the callback link, and the
|
|
|
|
callbacked function must accept the request from the browser as the
|
|
|
|
first argument. Furthermore, you don't have to use
|
|
|
|
\name{send-html/suspend}, if a user can only leave your web page via
|
|
|
|
callbacks. However, it can be sensible to combine the dispatch and
|
|
|
|
the callback method, so you have to use \name{send-html/suspend}.
|
|
|
|
|
2004-01-17 22:32:08 -05:00
|
|
|
Note that is nonsensical to create a callback on top level, \ie the
|
|
|
|
call to \name{make-annotated-callback} must occur every time
|
|
|
|
\name{main} is called and not only once when the \surflet is read into
|
|
|
|
memory. For the same reason it is nonsensical in most cases to reuse
|
|
|
|
a callback.
|
|
|
|
|
|
|
|
The \surflet library provides also a wrapper function with which you
|
|
|
|
can instruct the callback to call different functions instead of a
|
|
|
|
single one. If you create your callback like
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(let ((callback (make-annotated-callback callback-function)))
|
|
|
|
\dots)
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
you can instruct the callback to call different functions like this:
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(callback function1 arg1 arg2)
|
|
|
|
\dots
|
|
|
|
(callback function2 arg3 arg4 arg5)
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Again, it is your choice which option you want to use. Note that
|
|
|
|
calling a function with several arguments and of different amount each
|
|
|
|
time is also possible if you only use a single function for the
|
|
|
|
callback.
|
|
|
|
|
|
|
|
\section{Data management}
|
|
|
|
|
|
|
|
When you write web programs, there are usually two kinds of data that
|
|
|
|
you use: data that is local to each instance of a \surflet, \eg the
|
|
|
|
users login, and data that is global to each instance of a \surflet,
|
|
|
|
\eg a port to a logfile. Changes to local data is only visible to
|
|
|
|
each instance of a \surflet, while changes to global data is visible
|
|
|
|
to every instance of a \surflet.
|
|
|
|
|
|
|
|
The \surflet library does not really distinguish between these two
|
|
|
|
types of data, but provides ways to realize both of them in a
|
|
|
|
convenient way that is not (really) different from the way you handle
|
|
|
|
this data types in a regular Scheme program.
|
|
|
|
|
|
|
|
If a data item is globally used in your \surflet, define it global
|
|
|
|
(on top level) and change its values with \name{set!}. If a data
|
|
|
|
item is locally used, define it locally (in your function) and do not
|
|
|
|
change its value with \name{set!}.
|
|
|
|
|
|
|
|
If the following sounds too technical to you, you can safely skip this
|
|
|
|
paragraph. The reason why the distinction between global and local
|
|
|
|
data is done via whether you mutate the data's value with \name{set!}
|
|
|
|
is that the \surflets are implemented with continuations.
|
|
|
|
Continuations cannot reflect changes that are done via \name{set!} (or
|
|
|
|
side effects in general) and thus such changes are globally visible.
|
|
|
|
On the other hand continuations represent states of a program and a
|
|
|
|
reified continuations reifies also the values of all (local) data.
|
|
|
|
|
|
|
|
But what to do if you happen to want to change your \emph{local}
|
|
|
|
data's value with \name{set!}? The \surflet library provides a place
|
|
|
|
where you store such mutable local data and two functions to access
|
|
|
|
it: \name{set-session-data!} sets the mutable local data and
|
|
|
|
\name{get-session-data} reads the mutable local data. Here is an
|
|
|
|
example. It uses the callback technique that was presented in the
|
|
|
|
previous section. If you haven't read that section, you only need to
|
|
|
|
know that \name{show-page} is called again and again as long as the
|
|
|
|
user keeps on clicking on ``Click''.
|
|
|
|
|
|
|
|
\begin{listing}
|
|
|
|
(define-structure surflet surflet-interface
|
|
|
|
(open surflets
|
|
|
|
surflets/callbacks
|
|
|
|
scheme-with-scsh)
|
|
|
|
(begin
|
|
|
|
(define (main req)
|
|
|
|
(set-session-data! -1)
|
|
|
|
(let ((start (make-annotated-callback show-page)))
|
|
|
|
(show-page req 'click start)))
|
|
|
|
|
|
|
|
(define (show-page req what callback)
|
|
|
|
(if (eq? what 'click)
|
|
|
|
(click callback)
|
|
|
|
(cancel)))
|
|
|
|
|
|
|
|
(define (click callback)
|
|
|
|
(set-session-data! (+ 1 (get-session-data)))
|
|
|
|
(send-html
|
|
|
|
`(html
|
|
|
|
(head (title "Click counter"))
|
|
|
|
(body
|
|
|
|
(h2 "Click or cancel")
|
|
|
|
(p "You've already clicked "
|
|
|
|
,(get-session-data)
|
|
|
|
" times.")
|
|
|
|
(p (url ,(callback 'click callback) "Click")
|
|
|
|
(url ,(callback 'cancel callback) "Cancel"))))))
|
|
|
|
|
|
|
|
(define (cancel)
|
|
|
|
(send-html/finish
|
|
|
|
`(html
|
|
|
|
(head (title "Click counter finished"))
|
|
|
|
(body
|
|
|
|
(h2 "Finished")
|
|
|
|
(p "after " ,(get-session-data) " clicks.")))))
|
|
|
|
))
|
|
|
|
\end{listing}
|
|
|
|
|
|
|
|
At the beginning of \name{main} we initialize the mutable local data
|
|
|
|
with \name{set-session-data!}.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define (main req)
|
|
|
|
(set-session-data! -1)
|
|
|
|
(let ((start (make-annotated-callback show-page)))
|
|
|
|
(show-page req 'click start)))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
Afterwards, we create and save a callback that will be called again
|
|
|
|
and again. We call \name{show-page} with the callback to show the
|
|
|
|
first web page.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define (show-page req what callback)
|
|
|
|
(if (eq? what 'click)
|
|
|
|
(click callback)
|
|
|
|
(cancel)))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
\name{show-page} evaluates its second argument \name{what} to
|
|
|
|
determine what to do next: continue or cancel.
|
|
|
|
|
|
|
|
\begin{alltt}
|
|
|
|
(define (click callback)
|
|
|
|
(set-session-data! (+ 1 (get-session-data)))
|
|
|
|
(send-html
|
|
|
|
`(html
|
|
|
|
(head (title "Click counter"))
|
|
|
|
(body
|
|
|
|
(h2 "Click or cancel")
|
|
|
|
(p "You've already clicked "
|
|
|
|
,(get-session-data)
|
|
|
|
" times.")
|
|
|
|
(p (url ,(callback 'click callback) "Click")
|
|
|
|
(url ,(callback 'cancel callback) "Cancel"))))))
|
|
|
|
\end{alltt}
|
|
|
|
|
|
|
|
If the user continues, \name{click} increases the mutable local
|
|
|
|
counter and shows the next page.
|
|
|
|
|
|
|
|
Note that we don't use \name{send-html/suspend} here because we use
|
|
|
|
the callback to lead to the next web page. If the user clicks on the
|
|
|
|
link labeled with ``Click'' or ``Cancel'', \name{show-page} will be
|
|
|
|
called with \typew{'click} or \typew{'cancel}, respectively, and the
|
|
|
|
callback as parameters. This creates an endless loop without saving
|
|
|
|
endless states of the \surflet.
|
|
|
|
|
|
|
|
\name{cancel} shows the final page with the amount of clicks
|
|
|
|
performed.
|
|
|
|
|
|
|
|
\section{My own SXML}
|
|
|
|
|
|
|
|
Section \ref{sec:SXML} introduced SXML, the way how \surflets
|
|
|
|
represent HTML. This section will show you, how you can create your
|
|
|
|
own rules to translate from SXML to HTML.
|
|
|
|
|
|
|
|
\subsection{Terms and theoretical background}
|
|
|
|
|
|
|
|
This subsection will introduce the main concepts of the translation
|
|
|
|
process and some necessary terms we will use in the following.
|
|
|
|
|
|
|
|
The translation process from SXML to HTML takes two steps. In the
|
|
|
|
first step, SXML is translated to an intermediate form. This is done
|
|
|
|
by the \textit{translator}. In the second step, the intermediate form
|
|
|
|
is translated into an HTML string. This is done by the
|
|
|
|
\textit{printer}. The intermediate form looks very much like SXML,
|
|
|
|
but contains only atoms or, recursively, list of \textit{atoms}.
|
|
|
|
Atoms are numbers, characters, strings, \sharpf, and the empty list.
|
|
|
|
We call the intermediate form an \textit{atom tree} and the list from
|
|
|
|
which we've started an \textit{SXML tree}.
|
|
|
|
|
|
|
|
The basic unit in the translation process is a \textit{conversion
|
|
|
|
rule}. A conversion rule consists of a trigger and a conversion
|
|
|
|
function. The translator calls the conversion function when it sees
|
|
|
|
the trigger at the beginning of a list in the SXML tree, \ie at a
|
|
|
|
node. It calls the conversion function with the all list elements as
|
|
|
|
parameters and replaces the whole list by the result of the conversion
|
|
|
|
function. The result of the conversion function is supposed to be an
|
|
|
|
atom tree.
|
|
|
|
|
|
|
|
The translator gets the SXML tree and a list of conversion rules as
|
|
|
|
arguments. It then traverses the SXML tree depth first and calls the
|
|
|
|
conversion functions according to the triggers it encounters,
|
|
|
|
replacing the nodes in the SXML tree with the result of the conversion
|
|
|
|
functions it called for each node. The result of this translation
|
|
|
|
step will be an atom tree, which the printer will print to a port.
|
|
|
|
|
|
|
|
There are exceptions to this basic rules. First, the translator might
|
|
|
|
not traverse the whole SXML tree. If the translator traverses the
|
|
|
|
whole tree, every argument to a conversion function is first
|
|
|
|
translated before it is passed to the conversion function. This is
|
|
|
|
the regular case and we say the conversion function gets its arguments
|
|
|
|
\textit{preprocessed}. However, the conversion rule can instruct the
|
|
|
|
translator not to preprocess the conversion function's arguments and
|
|
|
|
pass the arguments as they are in the SXML tree, \ie
|
|
|
|
\textit{unprocessed}. In that case, the translator will stop
|
|
|
|
traversing the SXML tree at that node and replacing the whole node by
|
|
|
|
the result of the conversion function called for this node.
|
|
|
|
|
|
|
|
Second, there are two default triggers which you can't use in your
|
|
|
|
translation rules: \typew{*default*} and \typew{*text*}. The
|
|
|
|
conversion rule that uses \typew{*default*} as its trigger is the
|
|
|
|
default conversion rule which the translator uses if no other
|
|
|
|
conversion rule triggers for a node in the SXML tree. The conversion
|
|
|
|
rule that uses \typew{*text*} as its trigger is the text conversion
|
|
|
|
rule and triggers, if the node in the SXML tree is a string. In the
|
|
|
|
standard conversion rule set the text conversion rule performs HTML
|
|
|
|
escaping, \eg for the ampersand (\&).
|
|
|
|
|
|
|
|
|
2004-01-14 22:46:27 -05:00
|
|
|
\section{Outlook}
|
|
|
|
|
2004-01-17 22:32:08 -05:00
|
|
|
More to come soon about\surflets containing of different parts and
|
|
|
|
individual SXML.
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-01-14 22:46:27 -05:00
|
|
|
%\end{document}
|
|
|
|
|
|
|
|
%session data
|
|
|
|
%surflets containing of different parts
|
|
|
|
%my sxml
|