| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
| <html> |
| <head> |
| <META http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
| <title>Advanced Control Flow</title> |
| <link href="http://purl.org/DC/elements/1.0/" rel="schema.DC"> |
| <meta content="Tony Collen" name="DC.Creator"> |
| </head> |
| <body> |
| |
| |
| <h1>Tutorial: A Gentle Introduction to Cocoon Control Flow</h1> |
| |
| <p>In this tutorial, we will create a simple number guessing game using |
| Cocoon's Control Flow engine.</p> |
| |
| <p>After you have Cocoon 2.1 deployed and running, go to where you have |
| Cocoon deployed and create a new subdirectory named <span class="codefrag">game</span>. |
| Cocoon's default main sitemap will automatically mount the sitemap in |
| the subdirectory.</p> |
| |
| <p>Create the following <span class="codefrag">sitemap.xmap</span> in the new subdirectory:</p> |
| |
| <pre class="code"> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0"> |
| |
| <map:components> |
| <map:generators default="file"> |
| <!-- in this example we use JXTemplateGenerator to insert |
| Flow variables in page content --> |
| <map:generator label="content,data" logger="sitemap.generator.jx" |
| name="jx" src="org.apache.cocoon.generation.JXTemplateGenerator"/> |
| </map:generators> |
| <map:transformers default="xslt"/> |
| <map:serializers default="html"/> |
| <map:matchers default="wildcard"/> |
| <map:selectors default="browser"> |
| <map:selector name="exception" src="org.apache.cocoon.selection.XPathExceptionSelector"> |
| <exception name="invalid-continuation" |
| class="org.apache.cocoon.components.flow.InvalidContinuationException"/> |
| <exception class="java.lang.Throwable" unroll="true"/> |
| </map:selector> |
| </map:selectors> |
| <map:actions/> |
| <map:pipes default="caching"/> |
| </map:components> |
| |
| <map:views/> |
| <map:resources/> |
| <map:action-sets/> |
| |
| <map:flow language="javascript"> |
| <!-- Flow will use the javascript functions defined in game.js --> |
| <map:script src="flow/game.js"/> |
| </map:flow> |
| |
| <map:pipelines> |
| <map:component-configurations> |
| <global-variables/> |
| </map:component-configurations> |
| |
| <map:pipeline> |
| <!-- no filename: call main() in game.js --> |
| <map:match pattern=""> |
| <map:call function="main"/> |
| </map:match> |
| |
| <!-- use JXtemplate to generate page content --> |
| <map:match pattern="*.jx"> |
| <map:generate type="jx" src="documents/{1}.jx"/> |
| <map:serialize type="xhtml"/> |
| </map:match> |
| |
| <!-- .kont URLs are generated by the Flow system for continuations --> |
| <map:match pattern="*.kont"> |
| <map:call continuation="{1}"/> |
| </map:match> |
| |
| <!-- handle invalid continuations --> |
| |
| <!-- this style of handling invalidContinuation is now deprecated: --> |
| <!-- this URI will never be called automatically anymore. --> |
| <!-- see handle-errors below --> |
| <map:match pattern="invalidContinuation"> |
| <map:generate src="documents/invalidContinuation.xml"/> |
| <map:serialize type="xml"/> |
| </map:match> |
| |
| <!-- the new non-hardcoded way of handling invalidContinuation --> |
| <map:handle-errors> |
| <map:select type="exception"> |
| <map:when test="invalid-continuation"> |
| <map:generate src="documents/invalidContinuation.html"/> |
| <map:serialize type="xhtml"/> |
| </map:when> |
| </map:select> |
| </map:handle-errors> |
| </map:pipeline> |
| |
| </map:pipelines> |
| |
| </map:sitemap> |
| </pre> |
| |
| <p>Inside the new subdirectory, create two more directories, |
| <span class="codefrag">documents/</span> and <span class="codefrag">flow/</span>.</p> |
| |
| <p>Inside <span class="codefrag">documents/</span>, you will store the "views" -- pages to |
| send to the player. Create the file <span class="codefrag">guess.jx</span>, which will |
| be the page the player will enter their guess:</p> |
| |
| <pre class="code"> |
| <?xml version="1.0"?> |
| <html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0"> |
| <head> |
| <title>cocoon flow number guessing game</title> |
| </head> |
| <body> |
| <h1>Guess the Number Between 1 and 10</h1> |
| <h2>${hint}</h2> |
| <h3>You've guessed ${guesses} times.</h3> |
| <form method="post" action="${cocoon.continuation.id}.kont"> |
| <input type="text" name="guess"/> |
| <input type="submit"/> |
| </form> |
| </body> |
| </html> |
| </pre> |
| |
| <p> |
| You'll also need a page to display when the person chooses the correct |
| number. Name it <span class="codefrag">success.jx</span> (Again in <span class="codefrag">documents/</span>): |
| </p> |
| |
| <pre class="code"> |
| <?xml version="1.0"?> |
| <html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0"> |
| <head> |
| <title>cocoon flow number guessing game</title> |
| </head> |
| <body> |
| <h1>Success!</h1> |
| <h2>The number was: ${random}</h2> |
| <h3>It took you ${guesses} tries.</h3> |
| <p><a href="./">Play again</a></p> |
| </body> |
| </html> |
| </pre> |
| |
| <p> |
| You may notice some strange codes inside the files -- namely things like |
| <span class="codefrag">${random}</span> and <span class="codefrag">${guesses}</span>. 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="jxtemplate.html">JXTemplateGenerator</a> comes in. |
| </p> |
| |
| <p> |
| Inside <span class="codefrag">flow/</span> 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 <span class="codefrag">game.js</span>: |
| </p> |
| |
| <pre class="code"> |
| 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 > random) { |
| hint = "Nope, lower!" |
| } else if (guess < 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 <span class="codefrag">http://host/cocoon/game/</span> and the |
| <map:match pattern=""> matches, and starts the pipeline. |
| </p> |
| |
| <p> |
| The function <span class="codefrag">main()</span> which is referenced in |
| <span class="codefrag">flow/game.js</span> 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 <span class="codefrag">game.js</span>:</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 |
| <span class="codefrag">while(true)</span> 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 class="code"> |
| 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 |
| <span class="codefrag">JXTemplateGenerator</span> comes into play. |
| </p> |
| |
| <p>We could also do the following:</p> |
| |
| <pre class="code"> |
| 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="api.html#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 class="code"> |
| <form method="post" action="${cocoon.continuation.id}.kont"> |
| </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 class="code"> |
| <map:match pattern="*.kont"> |
| <map:call continuation="{1}"/> |
| </map:match> |
| </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="api.html#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 |
| <span class="codefrag"><input type="text" name="guess"/></span> and in the Javascript |
| we get the request parameter by using <span class="codefrag">cocoon.request.get("guess");</span>. |
| </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 |
| <span class="codefrag">sendPage()</span> instead of <span class="codefrag">sendPageAndWait()</span>. |
| <span class="codefrag">sendPage()</span> 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> |
| |
| <p> |
| Another thing to note is the <map:handle-errors> 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 <span class="codefrag">InvalidContinuationException</span> 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> |