Added more details to tutorial of cont-responder

cvs
Chris Double 2004-11-24 00:58:19 +00:00
parent 24e8d2fc2a
commit 7c8e1f62b6
1 changed files with 288 additions and 20 deletions

View File

@ -45,8 +45,8 @@ browser and display a page is 'show'. Think of it as 'show a page to
the client'. 'show' takes a single item on the stack and that is a
'page generation' quotation.
A 'page generation' quotation is a quotation with stack effect (
string -- ). For now we'll ignore the string it receives on the
A 'page generation' quotation is a quotation with stack effect
( string -- ). For now we'll ignore the string it receives on the
stack. Its purpose will be explained later.
Hello World 1
@ -70,11 +70,12 @@ The responder is installed using:
"helloworld1" [ hello-world1 ] install-cont-responder
The 'install-cont-responder' word has stack effect ( name quot --
). It installs a responder with the given name. When the URL for that
responder is accessed the 'quot' quotation is run. In this case it is
'hello-world1' which displays the single HTML page described
previously.
The 'install-cont-responder' word has stack effect
( name quot -- ). It installs a responder with the given name.
When the URL for that responder is accessed the 'quot' quotation is
run. In this case it is 'hello-world1' which displays the single HTML
page described previously.
Accessing the above responder from a web browser is via an URL like:
@ -89,12 +90,12 @@ chore. Especially when the content is dynamic requiring concatenation
of many pieces of data.
The 'cont-responder' system uses 'html', a library that allows writing
HTML looking output directly in factor. This system developed for
'cont-responder' has recently been made part of the standard 'html'
HTML looking output directly in factor. This system, developed for
'cont-responder', has recently been made part of the standard 'html'
library of Factor.
'html' basically allows you to write HTML-like output in a factor word
and it will be automatically output. It can be tested at the console
and it will be output as correct HTML. It can be tested at the console
very easily:
USE: html
@ -121,8 +122,8 @@ You can use any factor code at any point:
</p>
=> <p style='text-align: red'>Using style text-align: red</p>
Tags that are not normally closed are written using an XML style
(ie. with a trailing slash):
Tags that are not normally closed are written using XML style closed
tag (ie. with a trailing slash):
"One" write <br/> "Two" write <br/> <input type= "text" input/>
=> One<br>Two<br><input type='text'>
@ -151,8 +152,10 @@ Dynamic Data
============
Adding dynamic data to the page is relatively easy. This example pulls
a value from the 'room' word which displays the amount of available
and free memory in the system.
a information from the 'room' word which displays memory details about
the running Factor system. It also uses 'room.' which outputs these
details to standard output and this is wrapped in a <pre> tag so it is
formatted correctly.
: memory-stats1 ( -- )
[
@ -227,13 +230,13 @@ the third.
When a 'show' call is executed the page generated by the quotation is
sent to the client. The computation of the responder is then
'suspended'. When the client accesses a special URL computiation is
'suspended'. When the client accesses a special URL, computation is
resumed at the point of the end of the 'show' call. In this way
procedural flow is maintained.
This brings us to the 'URL' stack item that is available to the 'page
generation' quotation passed to 'show'. This URL is a string that
contains a URL that can be embedded in the page. When the user access
contains an URL that can be embedded in the page. When the user access
that URL computation is resumed from the point of the end of the
'show' call as described above:
@ -277,9 +280,9 @@ When you display this example in the browser you'll be able to click
the URL to navigate. You can use the back button to retry the URL's,
you can clone the browser window and navigate them independantly, etc.
The similarity of the functions above could so with some
refactoring. The pages are almost exactly the same so we seperate this
into a seperate word:
The similarity of the functions above shows that some refactoring
would be useful. The pages are almost exactly the same so we seperate
this into a seperate word:
: show-flow-page ( n bool -- )
#! Show a page in the flow, using 'n' as the page number
@ -414,3 +417,268 @@ Either way works. Notice that in the 'post-example2' we had to do a
additional 'name' that is on the stack. This wasn't needed in
'post-example1' because the 'name' was not on the stack at the time of
the 'show' call.
Associating URL's with words
============================
A web page can contain URL's that when clicked perform some
action. This may be to display other pages, or to affect some server
state.
The 'cont-responder' system enables an URL to be associated with any
Factor quotation. This quotation will be run when the URL is
clicked. When that quotation exits control is returned to the page
that contained the call.
The word that enables this is 'quot-href'. It takes two items on the
stack. One is the text to display for the link. The other is the
quotation to run when the link is clicked. This quotation should have
stack effect ( -- ).
This example displays a number which can be incremented or
decremented.
0 "counter" set
: counter-example1 ( - )
#! Display a global counter which can be incremented or decremented
#! using anchors.
#!
#! We don't need the 'url' argument
[
drop
<html>
<head>
<title> "Counter: " write "counter" get unparse dup write </title>
</head>
<body>
<h2> "Counter: " write write </h2>
<p> "++" [ "counter" get succ "counter" set ] quot-href
"--" [ "counter" get pred "counter" set ] quot-href
</p>
</body>
</html>
] show
drop ;
"counter-example1" [ counter-example1 ] install-cont-responder
Accessing this example from the web browser will display a count of
zero. Clicking '++' or '--' will increment or decrement the count
respectively. This is done by calling a quotation that either
increments or decrements the count when the URL's are clicked.
Because the count is 'global' in this example, if you clone the
browser window with the count set to a specific value and increment
it, and then refresh the original browser window you will see the most
recent incremented state. This gives you 'shopping cart' like state
whereby using the back button or cloning windows shows a view of a
single global value that can be modified by all browser
instances. ie. The state is not backtracked when the back button is
used.
You'll notice that when you visit the root URL for the responder that
the count is reset back to zero. This is because when the responder
was installed the value of zero was in the namespace stack. This stack
is copied when the responder is installed resulting in initial
accesses to the URL having the starting value. This gives you 'server
side session data' for free.
Local State
===========
You can also have a counter value with 'local' state. That is, cloning
the browser window will give you a new independant state value that
can be incremented. Going to the original browser window and
refreshing will show the original value which can be incremented or
decremented seperately from that value in the cloned window. With this
type of state, using the back button results in 'backtracking' the
state value.
A way to get 'local' state is to store values on the stack itself
rather than a namespace:
: counter-example2 ( count - )
[ ( count URL -- )
drop
<html>
<head>
<title> "Counter: " write dup unparse write </title>
</head>
<body>
<h2> "Counter: " write dup unparse write </h2>
<p> "++" over [ succ counter-example2 ] cons quot-href
"--" swap [ pred counter-example2 ] cons quot-href
</p>
</body>
</html>
] show
drop ;
"counter-example2" [ 0 counter-example2 ] install-cont-responder
This example works by taking the value of the counter and consing it
to a code quotation that will increment or decrement it then call the
responder again. So if the counter value is '5' the two 'quot-href'
calls become the equivalent of:
"++" [ 5 succ counter-example2 ] cons quot-href
"--" [ 5 pred counter-example2 ] cons quot-href
Because it calls itself with the new count value the state is
remembered for that page only. This means that each page has an
independant count value. You can clone or use the back button and all
browser windows have an independant value.
Calling 'Subroutines'
=====================
Being able to call other page display functions from 'quot-href' gives
you subroutine like functionality in your web pages. A simple menu
that displays a sequence of pages and returns back to the main page is
very easy:
: show-page ( n -- )
#! Show a page in the flow, using 'n' as the page number
#! to display.
[ ( n url -- )
<html>
<head> <title> "Page " write over unparse write </title> </head>
<body>
<p> "Page " write swap unparse write </p>
<p> <a href= a> "Press to continue" write </a> </p>
</body>
</html>
] show 2drop ;
: show-some-pages ( n -- )
#! Display the given number of pages in a row.
[ succ show-page ] times* ;
: subroutine-example1 ( -- )
[
<html>
<head> <title> "Subroutine Example 1" write </title> </head>
<body>
<p> "Please select:" write
<ol>
<li> "Flow1" [ 1 show-some-pages ] quot-href </li>
<li> "Flow2" [ 2 show-some-pages ] quot-href </li>
<li> "Flow3" [ 3 show-some-pages ] quot-href </li>
</ol>
</p>
</body>
</html>
] show drop ;
"subroutine-example1" [ subroutine-example1 ] install-cont-responder
Each item in the ordered list is an anchor. When pressed they will
call a quotation that displays a certain number of pages in a
row. When that quotation finishes via dropping off the end the main
menu page is displayed again.
Simple Testing
==============
Sometimes it is useful to test the responder words from the console
instead of accessing it via a web browser. This enables you to step
through or quickly check to see if a word is generating HTML
correctly.
Because the responders require some state associated with them to keep
track of continuation id's and other things you can't usually just run
them and expect them to work. The 'show' call for example will fail as
it expects some continuations to in the continuation table for that
responder.
The 'cont-testing.factor' file contains some simple words that
maintains this state for you in such a way that you can test the words
from the console:
"cont-testing.factor" run-file
For this example we'll call the 'subroutine-example1' responder from
above. First we need to put a 'testing state' object on the stack. All
the testing functions expect this on the stack and return it after
they have been called. We then put a quotation on the stack which
calls the code we want to test and call the 'test-cont-function' word:
<cont-test-state> [ subroutine-example1 ] test-cont-function
=>
HTTP/1.1 302 Document Moved
Location: 8209741119458310
Content-Length: 0
Content-Type: text/plain
The first request is often a 'Document Moved' as above. This is
because by default the 'cont-responder' system does the
'Post-Refresh-Get' pattern which results in a redirect after each
request. This can be disabled but we'll work through the example with
it enabled.
We can see the continuation id where we are 'moved' to in the
'Location' header. To access this we use the 'test-cont-click'
function. Think of this as manually clicking the
URL. 'test-cont-click' has stack effect
( state url post -- state). 'post' is a hashtable of post data to pass
along with the request. We use 'f' here because we have no post
data. Remember that our previous 'test-cont-function' call left the
state on the stack:
"8209741119458310" f test-cont-click
=>
HTTP/1.0 200 Document follows
Content-Type: text/html
<html><head><title>Subroutine Example 1</title></head>
<body><p>Please select:
<ol><li><a href='7687398605200513'>Flow1</a></li>
<li><a href='7856272029924613'>Flow2</a></li>
<li><a href='4909116160485714'>Flow3</a></li>
</ol>
</p>
</body>
</html>
We can continue to drill down using 'test-cont-click' using the URL's
above to see the HTML for each 'click'.
Here's an example using post data. We'll test the 'post-example1' word
written previously:
<cont-test-state> [ post-example1 ] test-cont-function
=>
HTTP/1.1 302 Document Moved
Location: 5829759941409535
Content-Length: 0
Content-Type: text/plain
Again we skip past the forward:
"5829759941409535" f test-cont-click
=>
HTTP/1.0 200 Document follows
Content-Type: text/html
<html><head><title>Please enter your name</title></head>
<body>
<form action='5456539333180428' method='post'>
<p>Please enter your name:
<input type='text'size='20'name='username'>
<input type='submit'value='Ok'>
</p>
</form>
</body>
</html>
Now we submit the post data along to the 'action' url:
"5456539333180428" [ [ "username" | "Chris" ] ] alist>hash test-cont-click
=>
HTTP/1.0 200 Document follows
Content-Type: text/html
<html>
<head><title>Hello!</title></head>
<body>
<p>Chris, Good to see you!</p>
</body>
</html>
As you can see the post data was sent correctly.