You are viewing a plain text version of this content. The canonical link for it is here.
Posted to docs@cocoon.apache.org by da...@cocoon.zones.apache.org on 2007/06/07 19:15:07 UTC

[DAISY] Updated: Tutorial: A Gentle Introduction to Cocoon Control Flow

A document has been updated:

http://cocoon.zones.apache.org/daisy/documentation/1241.html

Document ID: 1241
Branch: main
Language: default
Name: Tutorial: A Gentle Introduction to Cocoon Control Flow (previously Samples)
Document Type: Cocoon Document (unchanged)
Updated on: 6/7/07 5:15:04 PM
Updated by: Reinhard Pötz

A new version has been created, state: publish

Parts
=====

Content
-------
This part has been updated.
Mime type: text/xml (unchanged)
File name:  (unchanged)
Size: 10295 bytes (previous version: 37 bytes)
Content diff:
--- <html><body><p>TODO</p></body></html>
+++ <html>
+++ <body>
+++ 
+++ <p class="warn">This tutorial was converted from having Cocoon 2.1 users as
+++ target audience to Cocoon 2.2 users but hasn't been verified if everything works
+++ as expected yet.</p>
+++ 
+++ <h1>Overview</h1>
+++ 
+++ <p>In this tutorial, we will create a simple number guessing game using
+++ <a href="daisy:1372">Cocoon's Control Flow engine</a>.</p>
+++ 
+++ <p>After you have the <a href="daisy:1159">Your first Cocoon application using
+++ Maven 2</a> tutorial running, create a new subdirectory, open the blocks root
+++ sitemap <tt>block1\src\main\resources\COB-INF\sitemap.xmap)</tt></p>
+++ 
+++ <h1>Sitemap</h1>
+++ 
+++ <p>Create the following <tt>sitemap.xmap</tt> in the new subdirectory:</p>
+++ 
+++ <pre>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+++ &lt;map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0"&gt;
+++ 
+++ &lt;map:pipelines&gt;
+++ 
+++   &lt;map:pipeline id="flow-sample"&gt;
+++     &lt;!-- no filename: call main() in game.js --&gt;
+++     &lt;map:match pattern="game"&gt;
+++       &lt;map:call function="main"/&gt;
+++     &lt;/map:match&gt;
+++ 
+++     &lt;!-- use JXtemplate to generate page content --&gt;
+++     &lt;map:match pattern="*.jx" internal-only="true"&gt;
+++       &lt;map:generate type="jx" src="documents/{1}.jx"/&gt;
+++       &lt;map:serialize type="xhtml"/&gt;
+++     &lt;/map:match&gt;
+++ 
+++     &lt;!-- .kont URLs are generated by the Flow system for continuations --&gt;
+++     &lt;map:match pattern="*.kont"&gt;
+++       &lt;map:call continuation="{1}"/&gt;
+++     &lt;/map:match&gt;
+++ 
+++     &lt;!-- handle invalid continuations --&gt;
+++ 
+++     &lt;!-- this style of handling invalidContinuation is now deprecated: --&gt;
+++     &lt;!-- this URI will never be called automatically anymore. --&gt;
+++     &lt;!-- see handle-errors below --&gt;
+++     &lt;map:match pattern="invalidContinuation"&gt;
+++       &lt;map:generate src="documents/invalidContinuation.xml"/&gt;
+++       &lt;map:serialize type="xml"/&gt;
+++     &lt;/map:match&gt;
+++ 
+++     &lt;!-- the new non-hardcoded way of handling invalidContinuation --&gt;
+++     &lt;map:handle-errors&gt;
+++       &lt;map:select type="exception"&gt;
+++         &lt;map:when test="invalid-continuation"&gt;
+++           &lt;map:generate src="documents/invalidContinuation.html"/&gt;
+++           &lt;map:serialize type="xhtml"/&gt;
+++         &lt;/map:when&gt;
+++       &lt;/map:select&gt;
+++     &lt;/map:handle-errors&gt;
+++   &lt;/map:pipeline&gt;
+++ 
+++ &lt;/map:pipelines&gt;
+++ 
+++ &lt;/map:sitemap&gt;
+++ </pre>
+++ 
+++ <h1>View</h1>
+++ 
+++ <p>In <tt>block1\src\main\resources\COB-INF</tt>, create a subdirectory,
+++ <tt>documents/</tt>.</p>
+++ 
+++ <p>Inside <tt>documents/</tt>, you will store the "views" -- pages to send to
+++ the player. Create the file <tt>guess.jx</tt>, which will be the page the player
+++ will enter their guess:</p>
+++ 
+++ <pre>&lt;?xml version="1.0"?&gt;
+++ &lt;html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0"&gt;
+++ &lt;head&gt;
+++   &lt;title&gt;cocoon flow number guessing game&lt;/title&gt;
+++ &lt;/head&gt;
+++ &lt;body&gt;
+++   &lt;h1&gt;Guess the Number Between 1 and 10&lt;/h1&gt;
+++   &lt;h2&gt;${hint}&lt;/h2&gt;
+++   &lt;h3&gt;You've guessed ${guesses} times.&lt;/h3&gt;
+++   &lt;form method="post" action="${cocoon.continuation.id}.kont"&gt;
+++     &lt;input type="text" name="guess"/&gt;
+++     &lt;input type="submit"/&gt;
+++   &lt;/form&gt;
+++ &lt;/body&gt;
+++ &lt;/html&gt;
+++ </pre>
+++ 
+++ <p>You'll also need a page to display when the person chooses the correct
+++ number. Name it <tt>success.jx</tt> (Again in <tt>documents/</tt>):</p>
+++ 
+++ <pre>&lt;?xml version="1.0"?&gt;
+++ &lt;html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0"&gt;
+++ &lt;head&gt;
+++   &lt;title&gt;cocoon flow number guessing game&lt;/title&gt;
+++ &lt;/head&gt;
+++ &lt;body&gt;
+++   &lt;h1&gt;Success!&lt;/h1&gt;
+++   &lt;h2&gt;The number was: ${random}&lt;/h2&gt;
+++   &lt;h3&gt;It took you ${guesses} tries.&lt;/h3&gt;
+++   &lt;p&gt;&lt;a href="./"&gt;Play again&lt;/a&gt;&lt;/p&gt;
+++ &lt;/body&gt;
+++ &lt;/html&gt;
+++ </pre>
+++ 
+++ <p>You may notice some strange codes inside the files -- namely things like
+++ <tt>${random}</tt> and <tt>${guesses}</tt>. They look like variables and they
+++ will be replaced with values when the pages are sent to the client. This is
+++ where the <a href="daisy:511">JXTemplateGenerator</a> comes in.</p>
+++ 
+++ <h1>Controller</h1>
+++ 
+++ <p>Inside <tt>flow/</tt> you will store the code that actually controls how this
+++ application runs. In the "MVC" pattern the Flow is the "Controller" and it is
+++ very powerful.</p>
+++ 
+++ <p>Create the following file named <tt>game.js</tt>:</p>
+++ 
+++ <pre>function main() {
+++ 
+++   var random =  Math.round( Math.random() * 9 ) + 1;
+++ 
+++   var hint = "No hint for you!"
+++   var guesses = 0;
+++ 
+++   while (true) {
+++ 
+++     cocoon.sendPageAndWait("guess.jxt",
+++       { 
+++         "random" : random, 
+++         "hint" : hint,
+++         "guesses" : guesses
+++       } 
+++     );
+++ 
+++     var guess = parseInt( cocoon.request.get("guess") );
+++     guesses++;
+++ 
+++     if (guess) {
+++       if (guess &gt; random) {
+++         hint = "Nope, lower!"
+++       } else if (guess &lt; random) {
+++         hint = "Nope, higher!"
+++       } else {
+++         break;
+++       }
+++     }
+++   }
+++ 
+++   cocoon.sendPage("success.jx", 
+++     {
+++       "random" : random, 
+++       "guess" : guess,
+++       "guesses" : guesses
+++     } 
+++   );
+++ }
+++ </pre>
+++ 
+++ <p>Alright, now let's follow the execution of this Flow and pipeline: The player
+++ accesses the URL <tt>http://host/cocoon/game/</tt> and the &lt;map:match
+++ pattern=""&gt; matches, and starts the pipeline.</p>
+++ 
+++ <p>The function <tt>main()</tt> which is referenced in <tt>flow/game.js</tt> is
+++ called and a new Continuation object is created. Without getting into too much
+++ detail the state of the Javascript code is saved and can be recalled any number
+++ of times.</p>
+++ 
+++ <p>We now enter the code in <tt>game.js</tt>:</p>
+++ 
+++ <p>A random number between 1 and 10 is chosen.</p>
+++ 
+++ <p>Variables containing a hint for the player and the player's current number of
+++ guesses are initialized. The Flow now enters the <tt>while(true)</tt> loop which
+++ basically keeps the game going until the player guesses the correct number.</p>
+++ 
+++ <p>We now get to the following line, where things start to get interesting:</p>
+++ 
+++ <pre>cocoon.sendPageAndWait("guess.jxt", { "random" : random, "hint" : hint, "guesses" : guesses} );
+++ </pre>
+++ 
+++ <p>The Flow layer sends the contents of the URI "guess.jx" which is matched in
+++ the sitemap (see above). We also pass an inline Javascript object, containing
+++ three key/value pairs, one named "random" which contains the value of the
+++ variable random as initialized above, and so on for hint and guesses. The keys
+++ are substituted later down the line, when the <tt>JXTemplateGenerator</tt>
+++ comes into play.</p>
+++ 
+++ <p>We could also do the following:</p>
+++ 
+++ <pre>cocoon.sendPageAndWait("guess.jx", { "foo" : random } );
+++ </pre>
+++ 
+++ <p>In this case, the value of random would be able to be substituted in our
+++ JXTemplate, but under the name "foo" instead -- we'd just have to make sure we
+++ have the correct keyname in our template.</p>
+++ 
+++ <p>The Flow Layer also does another interesting thing: it halts the execution of
+++ the Javascript! Through the magic of continuations the Flow Layer is able to
+++ resume execution of the script at the exact line in which it left off. This
+++ creates some very powerful situations with respect to web programming, and
+++ forces the reader to think very differently about how web applications are
+++ designed.</p>
+++ 
+++ <p>Picking back up in the script execution, the client is sent through the
+++ pipeline matching "guess.jx". Referring back to the sitemap, we match *.jx, and
+++ run the file through the JXTemplateGenerator, which substitutes the keynames for
+++ the values sent from the
+++ <a href="daisy:518#sendPageAndWait">cocoon.sendPageAndWait()</a> function.</p>
+++ 
+++ <p>One thing to note is in the form which is sent back to Cocoon when the player
+++ submits the guess:</p>
+++ 
+++ <pre>&lt;form method="post" action="${cocoon.continuation.id}.kont"&gt;
+++ </pre>
+++ 
+++ <p>Here, ${cocoon.continuation.id} is resolved to a unique identifier which
+++ points to the current continuation. One can think of this somewhat of a session
+++ ID.</p>
+++ 
+++ <p>When the player submits the form, it is submitted to a unique URL which
+++ contains the continuation ID, plus ".kont", which we end up matching in the
+++ sitemap:</p>
+++ 
+++ <pre>&lt;map:match pattern="*.kont"&gt;
+++   &lt;map:call continuation="{1}"/&gt;
+++ &lt;/map:match&gt;
+++ </pre>
+++ 
+++ <p>When Cocoon sees a URL like this, it attempts to restart the continuation
+++ with the specified ID and we re-enter the Javascript code where we left off
+++ previously.</p>
+++ 
+++ <p>We are now back in the Javascript at the line after
+++ <a href="daisy:518#sendPageAndWait">sendPageAndWait()</a>. We create a new
+++ variable (an int), which we get from the POST request that was sent by the form.
+++ Notice in the form we had <tt>&lt;input type="text" name="guess"/&gt;</tt> and
+++ in the Javascript we get the request parameter by using
+++ <tt>cocoon.request.get("guess");</tt>.</p>
+++ 
+++ <p>Now we increment the player's guess count and we test to see if they guessed
+++ the correct number. If the guess was too high, we set the hint variable telling
+++ them to guess lower, we fall through the bottom of the while loop and we send
+++ the guess form back to the player.</p>
+++ 
+++ <p>If the guess was too low, we tell them to guess higher, we fall through the
+++ loop as well sending the player the form again.</p>
+++ 
+++ <p>If the guess was correct, we break out of the main loop and send the player
+++ to a different view, this time to "success.jx", and we give the template not
+++ only their number and the random number (pointless, yes, because they were the
+++ same), but also the number of guesses to tell the player how good or bad at
+++ guessing numbers they are.</p>
+++ 
+++ <p>The main point of interest in the Flow script at this point is the use of
+++ <tt>sendPage()</tt> instead of <tt>sendPageAndWait()</tt>. <tt>sendPage()</tt>
+++ works exactly the same, except, yes, you guessed it, we don't halt execution of
+++ code and keep processing.</p>
+++ 
+++ <p>At this point there's no more code left and the game is over and the Flow
+++ stops.</p>
+++ 
+++ <h1>Model</h1>
+++ 
+++ <p>TBD</p>
+++ 
+++ <h1>Error Handling</h1>
+++ 
+++ <p>Another thing to note is the &lt;map:handle-errors&gt; tag in the sitemap.
+++ Previously, when a continuation which did not exist was called, the Flow layer
+++ would automatically redirect to the URI "invalidContinuation". Now, the Flow
+++ layer throws an <tt>InvalidContinuationException</tt> and you can now handle it
+++ as described in the handle-errors tag.</p>
+++ 
+++ <p>And that's it! You have now just made your very first application using the
+++ Flow layer.</p>
+++ 
+++ </body>
+++ </html>