home *** CD-ROM | disk | FTP | other *** search
- <?xml version="1.0" encoding="UTF-8"?>
- <!--
- Copyright 1999-2004 The Apache Software Foundation
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
- <!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.0//EN" "document-v10.dtd">
-
- <document>
- <header>
- <title>Advanced Control Flow</title>
- <authors>
- <person name="Tony Collen" email="tony@apache.org"/>
- </authors>
- </header>
- <body>
-
- <s1 title="Tutorial: A Gentle Introduction to Cocoon Control Flow">
- <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 <code>game</code>.
- Cocoon's default main sitemap will automatically mount the sitemap in
- the subdirectory.</p>
- <p>Create the following <code>sitemap.xmap</code> in the new subdirectory:</p>
- <source><![CDATA[
- <?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>
- ]]></source>
- <p>Inside the new subdirectory, create two more directories,
- <code>documents/</code> and <code>flow/</code>.</p>
- <p>Inside <code>documents/</code>, you will store the "views" -- pages to
- send to the player. Create the file <code>guess.jx</code>, which will
- be the page the player will enter their guess:</p>
- <source><![CDATA[
- <?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>
- ]]></source>
- <p>
- You'll also need a page to display when the person chooses the correct
- number. Name it <code>success.jx</code> (Again in <code>documents/</code>):
- </p>
- <source><![CDATA[
- <?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>
- ]]></source>
- <p>
- You may notice some strange codes inside the files -- namely things like
- <code>${random}</code> and <code>${guesses}</code>. They look like
- variables and they will be replaced with values when the pages are
- sent to the client. This is where the
- <link href="jxtemplate.html">JXTemplateGenerator</link> comes in.
- </p>
- <p>
- Inside <code>flow/</code> 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 <code>game.js</code>:
- </p>
- <source><![CDATA[
- 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} );
- }
- ]]></source>
- <p>
- Alright, now let's follow the execution of this Flow and pipeline: The
- player accesses the URL <code>http://host/cocoon/game/</code> and the
- <map:match pattern=""> matches, and starts the pipeline.
- </p>
- <p>
- The function <code>main()</code> which is referenced in
- <code>flow/game.js</code> 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 <code>game.js</code>:</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
- <code>while(true)</code> 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>
- <source>
- cocoon.sendPageAndWait("guess.jxt", { "random" : random, "hint" : hint, "guesses" : guesses} );
- </source>
- <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
- <code>JXTemplateGenerator</code> comes into play.
- </p>
- <p>We could also do the following:</p>
- <source>
- cocoon.sendPageAndWait("guess.jx", { "foo" : random } );
- </source>
- <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
- <link href="api.html#sendPageAndWait">cocoon.sendPageAndWait()</link>
- function.
- </p>
- <p>
- One thing to note is in the form which is sent back to Cocoon when the
- player submits the guess:
- </p>
- <source><![CDATA[
- <form method="post" action="${cocoon.continuation.id}.kont">
- ]]></source>
- <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>
- <source><![CDATA[
- <map:match pattern="*.kont">
- <map:call continuation="{1}"/>
- </map:match>
- ]]></source>
- <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
- <link href="api.html#sendPageAndWait">sendPageAndWait()</link>. 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
- <code><input type="text" name="guess"/></code> and in the Javascript
- we get the request parameter by using <code>cocoon.request.get("guess");</code>.
- </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
- <code>sendPage()</code> instead of <code>sendPageAndWait()</code>.
- <code>sendPage()</code> 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 <code>InvalidContinuationException</code> 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>
- </s1>
- </body>
- </document>
-