summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes6
-rw-r--r--demos/quickstart/protected/pages/Tutorial/fr/AjaxChat.page757
-rw-r--r--demos/quickstart/protected/pages/Tutorial/fr/CurrencyConverter.page405
-rw-r--r--demos/quickstart/protected/pages/Tutorial/fr/chat1.pngbin0 -> 10533 bytes
-rw-r--r--demos/quickstart/protected/pages/Tutorial/fr/chat2.pngbin0 -> 13409 bytes
-rw-r--r--demos/quickstart/protected/pages/Tutorial/fr/example1.pngbin0 -> 11139 bytes
-rw-r--r--demos/quickstart/protected/pages/Tutorial/fr/example2.pngbin0 -> 13842 bytes
7 files changed, 1168 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
index a2ce04e5..833d83e4 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1217,6 +1217,12 @@ demos/quickstart/protected/pages/Tutorial/chat1.png -text
demos/quickstart/protected/pages/Tutorial/chat2.png -text
demos/quickstart/protected/pages/Tutorial/example1.png -text
demos/quickstart/protected/pages/Tutorial/example2.png -text
+demos/quickstart/protected/pages/Tutorial/fr/AjaxChat.page -text
+demos/quickstart/protected/pages/Tutorial/fr/CurrencyConverter.page -text
+demos/quickstart/protected/pages/Tutorial/fr/chat1.png -text
+demos/quickstart/protected/pages/Tutorial/fr/chat2.png -text
+demos/quickstart/protected/pages/Tutorial/fr/example1.png -text
+demos/quickstart/protected/pages/Tutorial/fr/example2.png -text
demos/quickstart/protected/pages/ViewSource.page -text
demos/quickstart/protected/pages/ViewSource.php -text
demos/quickstart/protected/pages/config.xml -text
diff --git a/demos/quickstart/protected/pages/Tutorial/fr/AjaxChat.page b/demos/quickstart/protected/pages/Tutorial/fr/AjaxChat.page
new file mode 100644
index 00000000..fc1997c8
--- /dev/null
+++ b/demos/quickstart/protected/pages/Tutorial/fr/AjaxChat.page
@@ -0,0 +1,757 @@
+<com:TContent ID="body">
+ <h1 id="18008">Building an AJAX Chat Application</h1>
+ <p id="90081" class="block-content">This tutorial introduces the Prado web application framework's
+ <a href="?page=Database.ActiveRecord">ActiveRecord</a>
+ and <a href="?page=ActiveControls.Home">Active Controls</a> to build a Chat
+ web application. It is assumed that you
+ are familiar with PHP and you have access to a web server that is able to serve PHP5 scripts.
+ This basic chat application will utilize the following ideas/components in Prado.
+ </p>
+ <ul id="u1" class="block-content">
+ <li>Building a custom User Manager class.</li>
+ <li>Authenticating and adding a new user to the database.</li>
+ <li>Using ActiveRecord to interact with the database.</li>
+ <li>Using Active Controls and callbacks to implement the user interface.</li>
+ <li>Separating application logic and application flow.</li>
+ </ul>
+
+ <p id="90082" class="block-content">In this tutorial you will build an AJAX Chat web application that allows
+ multiple users to communicate through their web browser.
+ The application consists of two pages: a login page
+ that asks the user to enter their nickname and the main application chat
+ page.
+ You can try the application <a href="../chat/index.php">locally</a> or at
+ <a href="http://www.pradosoft.com/demos/chat/">Pradosoft.com</a>.
+ The main application chat page is shown bellow.
+ <img src=<%~ chat1.png %> class="figure" />
+ </p>
+
+ <h1 id="18009">Download, Install and Create a New Application</h1>
+ <p id="90083" class="block-content">The download and installation steps are similar to those in
+ the <a href="?page=Tutorial.CurrencyConverter#download">Currency converter tutorial</a>.
+ To create the application, we run from the command line the following.
+ See the <a href="?page=GettingStarted.CommandLine">Command Line Tool</a>
+ for more details.
+<com:TTextHighlighter Language="text" CssClass="source block-content" id="code_90027">
+php prado/framework/prado-cli.php -c chat
+</com:TTextHighlighter>
+ </p>
+
+ <p id="90084" class="block-content">The above command creates the necessary directory structure and minimal
+ files (including "index.php" and "Home.page") to run a Prado web application.
+ Now you can point your browser's URL to the web server to serve up
+ the <tt>index.php</tt> script in the <tt>chat</tt> directory.
+ You should see the message "Welcome to Prado!"
+ </p>
+
+ <h1 id="18010">Authentication and Authorization</h1>
+ <p id="90085" class="block-content">The first task for this application is to ensure that each user
+ of the chat application is assigned with a unique (chosen by the user)
+ username. To achieve this, we can secure the main chat application
+ page to deny access to anonymous users. First, let us create the <tt>Login</tt>
+ page with the following code. We save the <tt>Login.php</tt> and <tt>Login.page</tt>
+ in the <tt>chat/protected/pages/</tt> directory (there should be a <tt>Home.page</tt>
+ file created by the command line tool).
+ </p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90028">
+&lt;?php
+class Login extends TPage
+{
+}
+?&gt;
+</com:TTextHighlighter>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code_90029">
+<!doctype html public "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>Prado Chat Demo Login</title>
+</head>
+<body>
+&lt;com:TForm&gt;
+ <h1 class="login">Prado Chat Demo Login</h1>
+ <fieldset class="login">
+ <legend>Please enter your name:</legend>
+ <div class="username">
+ &lt;com:TLabel ForControl="username" Text="Username:" /&gt;
+ &lt;com:TTextBox ID="username" MaxLength="20" /&gt;
+ &lt;com:TRequiredFieldValidator
+ ControlToValidate="username"
+ Display="Dynamic"
+ ErrorMessage="Please provide a username." /&gt;
+ </div>
+ <div class="login-button">
+ &lt;com:TButton Text="Login" /&gt;
+ </div>
+&lt;/com:TForm&gt;
+</body>
+</html>
+</com:TTextHighlighter>
+ <p id="90086" class="block-content">The login page contains
+ a <com:DocLink ClassPath="System.Web.UI.TForm" Text="TForm" />,
+ a <com:DocLink ClassPath="System.Web.UI.WebControls.TTextBox" Text="TTextBox" />,
+ a <com:DocLink ClassPath="System.Web.UI.WebControls.TRequiredFieldValidator" Text="TRequiredFieldValidator" />
+ and a <com:DocLink ClassPath="System.Web.UI.WebControls.TButton" Text="TButton" />. The resulting
+ page looks like the following (after applying some a style sheet).
+ <img src=<%~ chat2.png %> class="figure" />
+ If you click on the <tt>Login</tt> button without entering any
+ text in the username textbox, an error message is displayed. This is
+ due to the <com:DocLink ClassPath="System.Web.UI.WebControls.TRequiredFieldValidator" Text="TRequiredFieldValidator" />
+ requiring the user to enter some text in the textbox before proceeding.
+ </p>
+<h2 id="18019">Securing the <tt>Home</tt> page</h2>
+<p id="90087" class="block-content">Now we wish that if the user is trying to access the main application
+page, <tt>Home.page</tt>, before they have logged in, the user is presented with
+the <tt>Login.page</tt> first. We add a <tt>chat/protected/application.xml</tt> configuration
+file to import some classes that we shall use later.
+<com:TTextHighlighter Language="xml" CssClass="source block-content" id="code_90030">
+<?xml version="1.0" encoding="utf-8"?>
+<application id="Chat" Mode="Debug">
+ <paths>
+ <using namespace="System.Data.*" />
+ <using namespace="System.Data.ActiveRecord.*" />
+ <using namespace="System.Security.*" />
+ <using namespace="System.Web.UI.ActiveControls.*" />
+ </paths>
+</application>
+</com:TTextHighlighter>
+Next, we add a <tt>chat/protected/pages/config.xml</tt> configuration file to
+secure the <tt>pages</tt> directory.
+<com:TTextHighlighter Language="xml" CssClass="source block-content" id="code_90031">
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <modules>
+ <module id="users" class="TUserManager" />
+ <module id="auth" class="TAuthManager" UserManager="users" LoginPage="Login" />
+ </modules>
+ <authorization>
+ <allow pages="Login" users="?" />
+ <allow roles="normal" />
+ <deny users="*" />
+ </authorization>
+</configuration>
+</com:TTextHighlighter>
+We setup the authentication using the default classes as explained in the
+<a href="?page=Advanced.Auth">authentication/authorization quickstart</a>.
+In the authorization definition, we allow anonymous users to access the
+<tt>Login</tt> page (anonymous users is specified by the <tt>?</tt> question mark).
+We allow any users with role equal to "normal" (to be defined later)
+to access all the pages, that is, the <tt>Login</tt> and <tt>Home</tt> pages.
+Lastly, we deny all users without any roles to access any page. The authorization
+rules are executed on first match basis.
+</p>
+
+<p id="90088" class="block-content">If you now try to access the <tt>Home</tt> page by pointing your browser
+to the <tt>index.php</tt> you will be redirected to the <tt>Login</tt> page.
+</p>
+
+<h1 id="18011">Active Record for <tt>chat_users</tt> table</h1>
+<p id="90089" class="block-content">The <com:DocLink ClassPath="System.Secutity.TUserManager" Text="TUserManager" />
+class only provides a read-only list of users. We need to be able to add or
+login new users dynamically. So we need to create our own user manager class.
+First, we shall setup a database with a <tt>chat_users</tt> table and create an ActiveRecord
+that can work with the <tt>chat_users</tt> table with ease. For the demo, we
+use <tt>sqlite</tt> as our database for ease of distributing the demo. The demo
+can be extended to use other databases such as MySQL or Postgres SQL easily.
+We define the <tt>chat_users</tt> table as follows.
+<com:TTextHighlighter Language="text" CssClass="source block-content" id="code_90032">
+CREATE TABLE chat_users
+(
+ username VARCHAR(20) NOT NULL PRIMARY KEY,
+ last_activity INTEGER NOT NULL DEFAULT "0"
+);
+</com:TTextHighlighter>
+Next we define the corresponding <tt>ChatUserRecord</tt> class and save it as
+<tt>chat/protected/App_Code/ChatUserRecord.php</tt> (you need to create the
+<tt>App_Code</tt> directory as well). We also save the sqlite database file
+as <tt>App_Code/chat.db</tt>.
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90033">
+class ChatUserRecord extends TActiveRecord
+{
+ public $username;
+ public $last_activity;
+
+ public static $_tablename='chat_users';
+
+ public static function finder()
+ {
+ return parent::getRecordFinder('ChatUserRecord');
+ }
+}
+</com:TTextHighlighter>
+Before using the <tt>ChatUserRecord</tt> class we to configure a default
+database connection for ActiveRecord to function. In the <tt>chat/protected/application.xml</tt>
+we import classes from the <tt>App_Code</tt> directory and add an
+<a href="?page=Database.ActiveRecord">ActiveRecord configuration module</a>.
+<com:TTextHighlighter Language="xml" CssClass="source block-content" id="code_90034">
+<?xml version="1.0" encoding="utf-8"?>
+<application id="Chat" Mode="Debug">
+ <paths>
+ <using namespace="Application.App_Code.*" />
+ <using namespace="System.Data.*" />
+ <using namespace="System.Data.ActiveRecord.*" />
+ <using namespace="System.Security.*" />
+ <using namespace="System.Web.UI.ActiveControls.*" />
+ </paths>
+ <modules>
+ <module class="TActiveRecordConfig" EnableCache="true"
+ Database.ConnectionString="sqlite:protected/App_Code/chat.db" />
+ </modules>
+</application>
+</com:TTextHighlighter>
+</p>
+
+<h2 id="18020">Custom User Manager class</h2>
+<p id="90090" class="block-content">To implement a custom user manager module class we just need
+to extends the <tt>TModule</tt> class and implement the <tt>IUserManager</tt>
+interface. The <tt>getGuestName()</tt>, <tt>getUser()</tt> and <tt>validateUser()</tt>
+methods are required by the <tt>IUserManager</tt> interface.
+We save the custom user manager class as <tt>App_Code/ChatUserManager.php</tt>.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90035">
+class ChatUserManager extends TModule implements IUserManager
+{
+ public function getGuestName()
+ {
+ return 'Guest';
+ }
+
+ public function getUser($username=null)
+ {
+ $user=new TUser($this);
+ $user->setIsGuest(true);
+ if($username !== null && $this->usernameExists($username))
+ {
+ $user->setIsGuest(false);
+ $user->setName($username);
+ $user->setRoles(array('normal'));
+ }
+ return $user;
+ }
+
+ public function addNewUser($username)
+ {
+ $user = new ChatUserRecord();
+ $user->username = $username;
+ $user->save();
+ }
+
+ public function usernameExists($username)
+ {
+ $finder = ChatUserRecord::finder();
+ $record = $finder->findByUsername($username);
+ return $record instanceof ChatUserRecord;
+ }
+
+ public function validateUser($username,$password)
+ {
+ return $this->usernameExists($username);
+ }
+}
+</com:TTextHighlighter>
+<p id="90091" class="block-content">
+The <tt>getGuestName()</tt>
+method simply returns the name for a guest user and is not used in our application.
+The <tt>getUser()</tt> method returns a <tt>TUser</tt> object if the username
+exists in the database, the <tt>TUser</tt> object is set with role of "normal"
+that corresponds to the <tt>&lt;authorization&gt;</tt> rules defined in our
+<tt>config.xml</tt> file. </p>
+
+<p id="90092" class="block-content">The <tt>addNewUser()</tt> and <tt>usernameExists()</tt>
+method uses the ActiveRecord corresponding to the <tt>chat_users</tt> table to
+add a new user and to check if a username already exists, respectively.
+</p>
+
+<p id="90093" class="block-content">The next thing to do is change the <tt>config.xml</tt> configuration to use
+our new custom user manager class. We simply change the <tt>&lt;module&gt;</tt>
+configuration with <tt>id="users"</tt>.</p>
+<com:TTextHighlighter Language="xml" CssClass="source block-content" id="code_90036">
+<module id="users" class="ChatUserManager" />
+</com:TTextHighlighter>
+
+<h1 id="18012">Authentication</h1>
+<p id="90094" class="block-content">To perform authentication, we just want the user to enter a unique
+username. We add a
+<com:DocLink ClassPath="System.Web.UI.WebControls.TCustomValidator" Text="TCustomValidator" />
+for validate the uniqueness of the username and add an <tt>OnClick</tt> event handler
+for the login button.</p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code_90037">
+&lt;com:TCustomValidator
+ ControlToValidate="username"
+ Display="Dynamic"
+ OnServerValidate="checkUsername"
+ ErrorMessage="The username is already taken." /&gt;
+
+...
+
+&lt;com:TButton Text="Login" OnClick="createNewUser" /&gt;
+</com:TTextHighlighter>
+In the <tt>Login.php</tt> file, we add the following 2 methods.
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90038">
+function checkUsername($sender, $param)
+{
+ $manager = $this->Application->Modules['users'];
+ if($manager->usernameExists($this->username->Text))
+ $param->IsValid = false;
+}
+
+function createNewUser($sender, $param)
+{
+ if($this->Page->IsValid)
+ {
+ $manager = $this->Application->Modules['users'];
+ $manager->addNewUser($this->username->Text);
+
+ //do manual login
+ $user = $manager->getUser($this->username->Text);
+ $auth = $this->Application->Modules['auth'];
+ $auth->updateSessionUser($user);
+ $this->Application->User = $user;
+
+ $url = $this->Service->constructUrl($this->Service->DefaultPage);
+ $this->Response->redirect($url);
+ }
+}
+</com:TTextHighlighter>
+The <tt>checkUserName()</tt> method uses the <tt>ChatUserManager</tt> class
+(recall that in the <tt>config.xml</tt> configuration we set the
+ID of the custom user manager class as "users") to validate the username
+is not taken.
+</p>
+<p id="90095" class="block-content">
+In the <tt>createNewUser</tt> method, when the validation passes (that is,
+when the user name is not taken) we add a new user. Afterward we perform
+a manual login process:</p>
+<ul id="u2" class="block-content">
+ <li>First we obtain a <tt>TUser</tt> instance from
+our custom user manager class using the <tt>$manager->getUser(...)</tt> method.</li>
+ <li>Using the <tt>TAuthManager</tt> we set/update the user object in the
+ current session data.</li>
+ <li>Then we set/update the <tt>Application</tt>'s user instance with our
+ new user object.</li>
+</ul>
+</p>
+<p id="finally" class="block-content">
+Finally, we redirect the client to the default <tt>Home</tt> page.
+</p>
+
+<h2 id="18021">Default Values for ActiveRecord</h2>
+<p id="90096" class="block-content">If you try to perform a login now, you will receive an error message like
+"<i>Property '<tt>ChatUserRecord::$last_activity</tt>' must not be null as defined
+by column '<tt>last_activity</tt>' in table '<tt>chat_users</tt>'.</i>". This means that the <tt>$last_activity</tt>
+property value was null when we tried to insert a new record. We need to either
+define a default value in the corresponding column in the table and allow null values or set the default
+value in the <tt>ChatUserRecord</tt> class. We shall demonstrate the later by
+altering the <tt>ChatUserRecord</tt> with the addition of a set getter/setter
+methods for the <tt>last_activity</tt> property.
+
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90039">
+private $_last_activity;
+
+public function getLast_Activity()
+{
+ if($this->_last_activity === null)
+ $this->_last_activity = time();
+ return $this->_last_activity;
+}
+
+public function setLast_Activity($value)
+{
+ $this->_last_activity = $value;
+}
+</com:TTextHighlighter>
+Notice that we renamed <tt>$last_activity</tt> to <tt>$_last_activity</tt> (note
+the underscore after the dollar sign).
+</p>
+
+<h1 id="18013">Main Chat Application</h1>
+<p id="90097" class="block-content">Now we are ready to build the main chat application. We use a simple
+layout that consist of one panel holding the chat messages, one panel
+to hold the users list, a textarea for the user to enter the text message
+and a button to send the message.
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code_90040">
+<!doctype html public "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>Prado Chat Demo</title>
+<style>
+.messages
+{
+ width: 500px;
+ height: 300px;
+ float: left;
+ border: 1px solid ButtonFace;
+ overflow: auto;
+}
+.user-list
+{
+ margin-left: 2px;
+ float: left;
+ width: 180px;
+ height: 300px;
+ border: 1px solid ButtonFace;
+ overflow: auto;
+ font-size: 0.85em;
+}
+.message-input
+{
+ float: left;
+}
+
+.message-input textarea
+{
+ margin-top: 3px;
+ padding: 0.4em 0.2em;
+ width: 493px;
+ font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
+ font-size: 0.85em;
+ height: 40px;
+}
+.send-button
+{
+ margin: 0.5em;
+}
+</style>
+</head>
+<body>
+&lt;com:TForm&gt;
+<h1 id="18014">Prado Chat Demo</h1>
+<div id="messages" class="messages">
+ &lt;com:TPlaceHolder ID="messageList" /&gt;
+</div>
+<div id="users" class="user-list">
+ &lt;com:TPlaceHolder ID="userList" /&gt;
+</div>
+<div class="message-input">
+ &lt;com:TActiveTextBox ID="userinput"
+ Columns="40" Rows="2" TextMode="MultiLine" /&gt;
+ &lt;com:TActiveButton ID="sendButton" CssClass="send-button"
+ Text="Send" /&gt;
+</div>
+&lt;/com:TForm&gt;
+&lt;com:TJavascriptLogger /&gt;
+</body>
+</html>
+</com:TTextHighlighter>
+We added two Active Control components: a
+<com:DocLink ClassPath="System.Web.UI.ActiveControls.TActiveTextBox" Text="TActiveTextBox" />
+and a
+<com:DocLink ClassPath="System.Web.UI.ActiveControls.TActiveButton" Text="TActiveButton" />.
+We also added a
+<com:DocLink ClassPath="System.Web.UI.WebControls.TJavascriptLogger" Text="TJavascriptLogger" />
+that will be very useful for understanding how the Active Controls work.
+</p>
+
+<h2 id="18022">Exploring the Active Controls</h2>
+<p id="90098" class="block-content">We should have some fun before we proceeding with setting up the chat buffering. We want
+to see how we can update the current page when we receive a message. First, we add
+an <tt>OnClick</tt> event handler for the <tt>Send</tt> button.
+
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code_90041">
+&lt;com:TActiveButton ID="sendButton" CssClass="send-button"
+ Text="Send" OnClick="processMessage"/&gt;
+</com:TTextHighlighter>
+And the corresponding event handler method in the <tt>Home.php</tt> class (we
+need to create this new file too).
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90042">
+class Home extends TPage
+{
+ function processMessage($sender, $param)
+ {
+ echo $this->userinput->Text;
+ }
+}
+</com:TTextHighlighter>
+If you now type something in the main application textbox and click the send button
+you should see whatever you have typed echoed in the <tt>TJavascriptLogger</tt> console.
+</p>
+
+<p id="90099" class="block-content">To append or add some content to the message list panel, we need to use
+some methods in the
+<com:DocLink ClassPath="System.Web.UI.ActiveControls.TCallbackClientScript" Text="TCallbackClientScript" />
+class which is available through the <tt>CallbackClient</tt> property of the
+current <tt>TPage</tt> object. For example, we do can do
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90043">
+function processMessage($sender, $param)
+{
+ $this->CallbackClient->appendContent("messages", $this->userinput->Text);
+}
+</com:TTextHighlighter>
+This is one way to update some part of the existing page during a callback (AJAX style events)
+and will be the primary way we will use to implement the chat application.
+</p>
+
+<h1 id="18015">Active Record for <tt>chat_buffer</tt> table</h1>
+<p id="90100" class="block-content">To send a message to all the connected users we need to buffer or store
+the message for each user. We can use the database to buffer the messages. The
+<tt>chat_buffer</tt> table is defined as follows.
+<com:TTextHighlighter Language="text" CssClass="source block-content" id="code_90044">
+CREATE TABLE chat_buffer
+(
+ id INTEGER PRIMARY KEY,
+ for_user VARCHAR(20) NOT NULL,
+ from_user VARCHAR(20) NOT NULL,
+ message TEXT NOT NULL,
+ created_on INTEGER NOT NULL DEFAULT "0"
+);
+</com:TTextHighlighter>
+The corresponding <tt>ChatBufferRecord</tt> class is saved as
+<tt>App_Code/ChatBufferRecord.php</tt>.
+
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90045">
+class ChatBufferRecord extends TActiveRecord
+{
+ public $id;
+ public $for_user;
+ public $from_user;
+ public $message;
+ private $_created_on;
+
+ public static $_tablename='chat_buffer';
+
+ public function getCreated_On()
+ {
+ if($this->_created_on === null)
+ $this->_created_on = time();
+ return $this->_created_on;
+ }
+
+ public function setCreated_On($value)
+ {
+ $this->_created_on = $value;
+ }
+
+ public static function finder()
+ {
+ return parent::getRecordFinder('ChatBufferRecord');
+ }
+}
+</com:TTextHighlighter>
+</p>
+
+<h1 id="18016">Chat Application Logic</h1>
+<p id="90101" class="block-content">We finally arrive at the guts of the chat application logic. First, we
+need to save a received message into the chat buffer for <b>all</b> the
+current users. We add this logic in the <tt>ChatBufferRecord</tt> class.
+
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90046">
+public function saveMessage()
+{
+ foreach(ChatUserRecord::finder()->findAll() as $user)
+ {
+ $message = new self;
+ $message->for_user = $user->username;
+ $message->from_user = $this->from_user;
+ $message->message = $this->message;
+ $message->save();
+ if($user->username == $this->from_user)
+ {
+ $user->last_activity = time(); //update the last activity;
+ $user->save();
+ }
+ }
+}
+</com:TTextHighlighter>
+We first find all the current users using the <tt>ChatUserRecord</tt> finder
+methods. Then we duplicate the message and save it into the database. In addition,
+we update the message sender's last activity timestamp. The above piece of code
+demonstrates the simplicity and succinctness of using ActiveRecords for simple database designs.
+</p>
+
+<p id="90102" class="block-content">The next piece of the logic is to retrieve the users' messages from the buffer.
+We simply load all the messages for a particular username and format that message
+appropriately (remember to escape the output to prevent Cross-Site Scripting attacks).
+After we load the messages, we delete those loaded messages and any older
+messages that may have been left in the buffer.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90047">
+public function getUserMessages($user)
+{
+ $content = '';
+ foreach($this->findAll('for_user = ?', $user) as $message)
+ $content .= $this->formatMessage($message);
+ $this->deleteAll('for_user = ? OR created_on < ?',
+ $user, time() - 300); //5 min inactivity
+ return $content;
+}
+
+protected function formatMessage($message)
+{
+ $user = htmlspecialchars($message->from_user);
+ $content = htmlspecialchars($message->message);
+ return "<div class=\"message\"><strong>{$user}:</strong>"
+ ." <span>{$content}</span></div>";
+}
+</com:TTextHighlighter>
+
+To retrieve a list of current users (formatted), we add this logic to the
+<tt>ChatUserRecord</tt> class. We delete any users that may have been inactive
+for awhile.
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90048">
+public function getUserList()
+{
+ $this->deleteAll('last_activity < ?', time()-300); //5 min inactivity
+ $content = '<ul>';
+ foreach($this->findAll() as $user)
+ $content .= '<li>'.htmlspecialchars($user->username).'</li>';
+ $content .= '</ul>';
+ return $content;
+}
+</com:TTextHighlighter>
+
+<div class="note"><b class="tip">Note:</b>
+For simplicity
+we formatted the messages in these Active Record classes. For large applications,
+these message formatting tasks should be done using Prado components (e.g. using
+a TRepeater in the template or a custom component).
+</div>
+</p>
+
+<h1 id="18017">Putting It Together</h1>
+<p id="90103" class="block-content">Now comes to put the application flow together. In the <tt>Home.php</tt> we update
+the <tt>Send</tt> buttons <tt>OnClick</tt> event handler to use the application
+logic we just implemented.
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90049">
+function processMessage($sender, $param)
+{
+ if(strlen($this->userinput->Text) > 0)
+ {
+ $record = new ChatBufferRecord();
+ $record->message = $this->userinput->Text;
+ $record->from_user = $this->Application->User->Name;
+ $record->saveMessage();
+
+ $this->userinput->Text = '';
+ $messages = $record->getUserMessages($this->Application->User->Name);
+ $this->CallbackClient->appendContent("messages", $messages);
+ $this->CallbackClient->focus($this->userinput);
+ }
+}
+</com:TTextHighlighter>
+We simply save the message to the chat buffer and then ask for all the messages
+for the current user and update the client side message list using a callback
+response (AJAX style).
+</p>
+
+<p id="90104" class="block-content">At this point the application is actually already functional, just not very
+user friendly. If you open two different browsers, you should be able to communicate
+between the two users whenever the <tt>Send</tt> button is clicked.
+</p>
+
+<p id="90105" class="block-content">The next part is perhaps the more tricker and fiddly than the other tasks. We
+need to improve the user experience. First, we want a list of current users
+as well. So we add the following method to <tt>Home.php</tt>, we can call
+this method when ever some callback event is raised, e.g. when the <tt>Send</tt>
+button is clicked.
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90050">
+protected function refreshUserList()
+{
+ $lastUpdate = $this->getViewState('userList','');
+ $users = ChatUserRecord::finder()->getUserList();
+ if($lastUpdate != $users)
+ {
+ $this->CallbackClient->update('users', $users);
+ $this->setViewstate('userList', $users);
+ }
+}
+</com:TTextHighlighter>
+</p>
+
+<p id="90106" class="block-content">Actually, we want to periodically update the messages and user list as new
+users join in and new message may arrive from other users. So we need to refresh
+the message list as well.</p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90051">
+function processMessage($sender, $param)
+{
+ ...
+ $this->refreshUserList();
+ $this->refreshMessageList();
+ ...
+}
+
+protected function refreshMessageList()
+{
+ //refresh the message list
+ $finder = ChatBufferRecord::finder();
+ $content = $finder->getUserMessages($this->Application->User->Name);
+ if(strlen($content) > 0)
+ {
+ $anchor = (string)time();
+ $content .= "<a href=\"#\" id=\"{$anchor}\"> </a>";
+ $this->CallbackClient->appendContent("messages", $content);
+ $this->CallbackClient->focus($anchor);
+ }
+}
+</com:TTextHighlighter>
+The anchor using <tt>time()</tt> as ID for a focus point is so that when the
+message list on the client side gets very long, the focus method will
+scroll the message list to the latest message (well, it works in most browsers).
+</p>
+
+<p id="90107" class="block-content">Next, we need to redirect the user back to the login page if the user has
+been inactive for some time, say about 5 mins, we can add this check to any stage
+of the page life-cycle. Lets add it to the <tt>onLoad()</tt> stage.
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90052">
+public function onLoad($param)
+{
+ $username = $this->Application->User->Name;
+ if(!$this->Application->Modules['users']->usernameExists($username))
+ {
+ $auth = $this->Application->Modules['auth'];
+ $auth->logout();
+
+ //redirect to login page.
+ $this->Response->Redirect($this->Service->ConstructUrl($auth->LoginPage));
+ }
+}
+</com:TTextHighlighter>
+</p>
+
+<h1 id="18018">Improving User Experience</h1>
+<p id="90108" class="block-content">The last few details are to periodically check for new messages and
+refresh the user list. We can accomplish this by polling the server using a
+<com:DocLink ClassPath="System.Web.UI.ActiveControls.TTimeTriggeredCallback" Text="TTimeTriggeredCallback" />
+control. We add a <tt>TTimeTriggeredCallback</tt> to the <tt>Home.page</tt>
+and call the <tt>refresh</tt> handler method defined in <tt>Home.php</tt>.
+We set the polling interval to 2 seconds.
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code_90053">
+&lt;com:TTimeTriggeredCallback OnCallback="refresh"
+ Interval="2" StartTimerOnLoad="true" /&gt;
+</com:TTextHighlighter>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_90054">
+function refresh($sender, $param)
+{
+ $this->refreshUserList();
+ $this->refreshMessageList();
+}
+</com:TTextHighlighter>
+</p>
+
+<p id="90109" class="block-content">The final piece requires us to use some javascript. We want that when the
+user type some text in the textarea and press the <tt>Enter</tt> key, we want it
+to send the message without clicking on the <tt>Send</tt> button. We add to the
+<tt>Home.page</tt> some javascript.
+
+<com:TTextHighlighter Language="javascript" CssClass="source block-content" id="code_90055">
+&lt;com:TClientScript&gt;
+Event.observe($("&lt;%= $this->userinput->ClientID %&gt;"), "keypress", function(ev)
+{
+ if(Event.keyCode(ev) == Event.KEY_RETURN)
+ {
+ if(Event.element(ev).value.length > 0)
+ new Prado.Callback("&lt;%= $this->sendButton->UniqueID %&gt;");
+ Event.stop(ev);
+ }
+});
+&lt;/com:TClientScript&gt;
+</com:TTextHighlighter>
+Details regarding the javascript can be explored in the
+<a href="?page=Advanced.Scripts">Introduction to Javascript</a> section of the quickstart.
+</p>
+
+<p id="90110" class="block-content">This completes the tutorial on making a basic chat web application using
+the Prado framework. Hope you have enjoyed it.
+</p>
+
+<div class="last-modified">$Id: AjaxChat.page 1650 2007-01-24 06:55:32Z wei $</div></com:TContent> \ No newline at end of file
diff --git a/demos/quickstart/protected/pages/Tutorial/fr/CurrencyConverter.page b/demos/quickstart/protected/pages/Tutorial/fr/CurrencyConverter.page
new file mode 100644
index 00000000..071a13d7
--- /dev/null
+++ b/demos/quickstart/protected/pages/Tutorial/fr/CurrencyConverter.page
@@ -0,0 +1,405 @@
+<com:TContent ID="body">
+ <h1 id="16001">Building a Simple Currency Converter</h1>
+ <p id="80053" class="block-content">This tutorial introduces the Prado web application framework and teaches
+ you how to build a simple web application in a few simple steps. This
+ tutorial assumes that you are familiar with PHP and you have access
+ to a web server that is able to serve PHP5 scripts.
+ </p>
+
+ <p id="80054" class="block-content">In this tutorial you will build a simple web application that converts
+ a dollar amount to an other currency, given the rate of that currency
+ relative to the dollar. The completed application is shown bellow.
+ <img src=<%~ example2.png %> class="figure" />
+ You can try the application <a href="../currency-converter/index.php">locally</a> or at
+ <a href="http://www.pradosoft.com/demos/currency-converter/">Pradosoft.com</a>.
+ Notice that the application still functions exactly the same if javascript
+ is not available on the user's browser.
+ </p>
+
+ <h1 id="download">Downloading and Installing Prado</h1>
+ <p id="80055" class="block-content">To install Prado, simply download the latest version of Prado from
+ <a href="http://www.pradosoft.com/">http://www.pradosoft.com</a>
+ and unzip the file to a directory <b>not</b> accessible by your web server
+ (you may unzip it to a directory accessible by the web server if you wish
+ to see the demos and test). For further detailed installation, see the
+ <a href="?page=GettingStarted.Installation">Quickstart Installation</a> guide.
+ </p>
+
+ <h1 id="16002">Creating a new Prado web Application</h1>
+ <p id="80056" class="block-content">The quickest and simplest way to create a new Prado web application is
+ to use the command tool <tt>prado-cli.php</tt> found in the <tt>framework</tt>
+ directory of the Prado distribution. We create a new application by running
+ the following command in your
+ command prompt or console. The command creates a new directory named
+ <tt>currency-converter</tt> in your current working directory.
+ You may need to change to the appropriate directory
+ first.
+ See the <a href="?page=GettingStarted.CommandLine">Command Line Tool</a>
+ for more details.
+ </p>
+<com:TTextHighlighter Language="text" CssClass="source block-content" id="code111">
+php prado/framework/prado-cli.php -c currency-converter
+</com:TTextHighlighter>
+
+ <p id="80057" class="block-content">The above command creates the necessary directory structure and minimal
+ files (including "index.php" and "Home.page") to run a Prado web application.
+ Now you can point your browser's url to the web server to serve up
+ the <tt>index.php</tt> script in the <tt>currency-converter</tt> directory.
+ You should see the message "Welcome to Prado!"
+ </p>
+
+ <h1 id="16003">Creating the Currency Converter User Interface</h1>
+ <p id="80058" class="block-content">We start by editing the <tt>Home.page</tt> file found in the
+ <tt>currency-converter/protected/pages/</tt> directory. Files ending
+ with ".page" are page templates that contains HTML and Prado controls.
+ We simply add two textboxes, three labels and one button as follows.
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="form1">
+&lt;com:TForm&gt;
+ <fieldset>
+ <legend>Currency Converter</legend>
+ <div class="rate-field">
+ &lt;com:TLabel ForControl="currencyRate" Text="Exchange Rate per $1:" /&gt;
+ &lt;com:TTextBox ID="currencyRate" /&gt;
+ </div>
+ <div class="dollar-field">
+ &lt;com:TLabel ForControl="dollars" Text="Dollars to Convert:" /&gt;
+ &lt;com:TTextBox ID="dollars" /&gt;
+ </div>
+ <div class="total-field">
+ <span class="total-label">Amount in Other Currency:</span>
+ &lt;com:TLabel ID="total" CssClass="result" /&gt;
+ </div>
+ <div class="convert-button">
+ &lt;com:TButton Text="Convert" /&gt;
+ </div>
+ </fieldset>
+&lt;/com:TForm&gt;
+</com:TTextHighlighter>
+ <p id="refresh" class="block-content">
+ If you refresh the page, you should see something similar to the following figure.
+ It may not look very pretty or orderly, but we shall change that later using CSS.
+ <img src=<%~ example1.png %> class="figure" />
+ </p>
+
+ <p id="80059" class="block-content">
+ The first component we add is a
+ <com:DocLink ClassPath="System.Web.UI.TForm" Text="TForm" />
+ that basically corresponds to the HTML <tt>&lt;form&gt;</tt> element.
+ In Prado, only <b>one</b> <tt>TForm</tt> element is allowed per page.
+ </p>
+
+ <p id="80060" class="block-content">The next two pair of component we add is the
+ <com:DocLink ClassPath="System.Web.UI.WebControls.TLabel" Text="TLabel" />
+ and
+ <com:DocLink ClassPath="System.Web.UI.WebControls.TTextBox" Text="TTextBox" />
+ that basically defines a label and a textbox for the user of the application
+ to enter the currency exchange rate.
+ The <tt>ForControl</tt> property value determines which component
+ that the label is for. This allows the user of the application to click
+ on the label to focus on the field (a good thing). You could have used
+ a plain HTML <tt>&lt;label&gt;</tt> element to do the same thing, but
+ you would have to find the correct <tt>ID</tt> of the textbox (or
+ <tt>&lt;input&gt;</tt> in HTML) as Prado components may/will render the
+ <tt>ID</tt> value differently in the HTML output.
+ </p>
+
+ <p id="80061" class="block-content">The next pair of components are similar and defines the textbox
+ to hold the dollar value to be converted.
+ The <tt>TLabel</tt> with <tt>ID</tt> value "total" defines a simple label.
+ Notice that the <tt>ForControl</tt> property is absent. This means that this
+ label is simply a simple label which we are going to use to display the
+ converted total amount.
+ </p>
+
+ <p id="80062" class="block-content">The final component is a
+ <com:DocLink ClassPath="System.Web.UI.WebControls.TButton" Text="TButton" />
+ that the user will click to calculate the results. The <tt>Text</tt>
+ property sets the button label.
+ </p>
+
+ <h1 id="16004">Implementing Currency Conversion</h1>
+
+ <p id="80063" class="block-content">If you tried clicking on the "Convert" button then the page will refresh
+ and does not do anything else. For the button to do some work, we need
+ to add a "Home.php" to where "Home.page" is. The <tt>Home</tt> class
+ should extends the
+ <com:DocLink ClassPath="System.Web.UI.TPage" Text="TPage" />, the default base
+ class for all Prado pages.
+ </p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code3">
+&lt;?php
+class Home extends TPage
+{
+
+}
+?&gt;
+</com:TTextHighlighter>
+ <p id="1111" class="block-content">
+ Prado uses PHP's <tt>__autoload</tt> method to load classes. The convention
+ is to use the class name with ".php" extension as filename.
+ </p>
+
+ <p id="80064" class="block-content">So far there is nothing interesting about Prado, we just declared some
+ "web components" in some template file named Home.page and created
+ a "Home.php" file with a <tt>Home</tt> class. The more interesting
+ bits are in Prado's event-driven architecture as we shall see next.
+ </p>
+
+ <p id="80065" class="block-content">We want that when the user click on the "Convert" button, we take the
+ values in the textbox, do some calculation and present the user with
+ the converted total. To handle the user clicking of the "Convert" button
+ we simply add an <tt>OnClick</tt> property to the "Convert" button in
+ the "Home.page" template and add a corresponding event handler method
+ in the "Home.php".
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code4">
+&lt;com:TButton Text="Convert" OnClick="convert_clicked" /&gt;
+</com:TTextHighlighter>
+ <p id="222" class="block-content">
+ The value of the <tt>OnClick</tt>, "<tt>convert_clicked</tt>", will be the method
+ name in the "Home.php" that will called when the user clicks on the
+ "Convert" button.
+ </p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code5">
+class Home extends TPage
+{
+ public function convert_clicked($sender, $param)
+ {
+ $rate = floatval($this->currencyRate->Text);
+ $dollars = floatval($this->dollars->Text);
+ $this->total->Text = $rate * $dollars;
+ }
+}
+</com:TTextHighlighter>
+<div id="3332" class="block-content">
+ <p id="333">
+ If you run the application in your web browser, enter some values and click
+ the "Convert" button then you should see that calculated value displayed next
+ to the "Amount in Other Currency" label.
+ </p>
+
+ <p id="80066">In the "<tt>convert_clicked</tt>" method the first parameter, <tt>$sender</tt>,
+ corresponds to the object that raised the event, in this case,
+ the "Convert" button. The second parameter, <tt>$param</tt> contains
+ any additional data that the <tt>$sender</tt> object may wish to have added.
+ </p>
+
+ <p id="80067">We shall now examine, the three lines that implements the simply currency
+ conversion in the "<tt>convert_clicked</tt>" method.
+ </p>
+</div>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code6" >
+$rate = floatval($this->currencyRate->Text);
+</com:TTextHighlighter>
+ <p id="444" class="block-content">
+ The statement <tt>$this->currencyRate</tt> corresponds to the
+ <tt>TTextBox</tt> component with <tt>ID</tt> value "currencyRate" in the
+ "Home.page" template. The <tt>Text</tt> property of the <tt>TTextBox</tt>
+ contains the value that the user entered. So, we obtain this
+ value by <tt>$this->currencyRate->Text</tt> which we convert the
+ value to a float value.
+ </p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code7">
+$dollars = floatval($this->dollars->Text);
+</com:TTextHighlighter>
+<div id="5551" class="block-content">
+ <p id="555">
+ The next line does a similar things, it takes the user value from
+ the <tt>TTextBox</tt> with <tt>ID</tt> value "dollars and converts it to
+ a float value.
+ </p>
+
+ <p id="80068">The third line calculates the new amount and set this value in the
+ <tt>Text</tt> property of the <tt>TLabel</tt> with <tt>ID="total"</tt>.
+ Thus, we display the new amount to the user in the label.
+ </p>
+</div>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code8">
+$this->total->Text = $rate * $dollars;
+</com:TTextHighlighter>
+
+ <h1 id="16005">Adding Validation</h1>
+ <p id="80069" class="block-content">The way we convert the user entered value to float ensures that the
+ total amount is always a number. So the user is free to enter what
+ ever they like, they could even enter letters. The user's experience
+ in using the application can be improved by adding validators
+ to inform the user of the allowed values in the currency rate and the
+ amount to be calcuated.
+ </p>
+
+ <p id="80070">For the currency rate, we should ensure that</p>
+ <ol id="o111" class="block-content">
+ <li>the user enters a value,</li>
+ <li>the currency rate is a valid number,</li>
+ <li>the currency rate is positive.</li>
+ </ol>
+ <p id="666" class="block-content">
+ To ensure 1 we add one
+ <com:DocLink ClassPath="System.Web.UI.WebControls.TRequiredFieldValidator" Text="TRequiredFieldValidator" />. To ensure 2 and 3, we add one
+ <com:DocLink ClassPath="System.Web.UI.WebControls.TCompareValidator" Text="TCompareValidator" />. We may add these validators any where within
+ the "Home.page" template. Further details regarding these validator and other
+ validators can be found in the
+ <a href="?page=Controls.Validation">Validation Controls</a> page.
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code9">
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="currencyRate"
+ ErrorMessage="Please enter a currency rate." /&gt;
+&lt;com:TCompareValidator
+ ControlToValidate="currencyRate"
+ DataType="Float"
+ ValueToCompare="0"
+ Operator="GreaterThan"
+ ErrorMessage="Please enter a positive currency rate." /&gt;
+</com:TTextHighlighter>
+
+ <p id="80071" >For the amount to be calculated, we should ensure that</p>
+ <ol id="o222" class="block-content">
+ <li>the user enters a value,</li>
+ <li>the value is a valid number (not including any currency or dollar signs).</li>
+ </ol>
+ <p id="777" class="block-content">
+ To ensure 1 we just add another <tt>TRequiredFieldValidator</tt>, for 2
+ we could use a
+ <com:DocLink ClassPath="System.Web.UI.WebControls.TDataTypeValidator" Text="TDataTypeValidator" />. For simplicity we only allow the user to enter
+ a number for the amount they wish to convert.
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code9a">
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="dollars"
+ ErrorMessage="Please enter the amount you wish to calculate." /&gt;
+&lt;com:TDataTypeValidator
+ ControlToValidate="dollars"
+ DataType="Float"
+ ErrorMessage="Please enter a number." /&gt;
+</com:TTextHighlighter>
+ </p>
+
+ <p id="80072" class="block-content">Now if you try to enter some invalid data in the application or left out
+ any of the fields the validators will be activated and present the user
+ with error messages. Notice that the error messages are presented
+ without reloading the page. Prado's validators by default validates
+ using both javascript and server side. The server side validation
+ is <b>always performed</b>. For the server side, we
+ should skip the calculation if the validators are not satisfied. This can
+ done as follows.
+ </p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code10" >
+public function convert_clicked($sender, $param)
+{
+ if($this->Page->IsValid)
+ {
+ $rate = floatval($this->currencyRate->Text);
+ $dollars = floatval($this->dollars->Text);
+ $this->total->Text = $rate * $dollars;
+ }
+}
+</com:TTextHighlighter>
+
+ <h1 id="16006">Improve User Experience With Active Controls</h1>
+ <p id="80073" class="block-content">In this simple application we may further improve the user experience
+ by increasing the responsiveness of the application. One way to achieve
+ a faster response is calculate and present the results without reloading
+ the whole page.
+ </p>
+
+ <p id="80074" class="block-content">We can replace the <tt>TButton</tt> with the Active Control counter part,
+ <com:DocLink ClassPath="System.Web.UI.ActiveControls.TActiveButton" Text="TActiveButton" />,
+ that can trigger a server side click event without reloading the page.
+ In addition, we can change the "totals" <tt>TLabel</tt> with the
+ Active Control counter part,
+ <com:DocLink ClassPath="System.Web.UI.ActiveControls.TActiveLabel" Text="TActiveLabel" />, such that the server side can update the browser without
+ reloading the page.
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code11">
+<div class="total-field">
+ <span class="total-label">Amount in Other Currency:</span>
+ &lt;com:TActiveLabel ID="total" CssClass="result" /&gt;
+ </div>
+ <div class="convert-button">
+ &lt;com:TActiveButton Text="Convert" OnClick="convert_clicked" /&gt;
+</div>
+</com:TTextHighlighter>
+ <p id="1232" class="block-content">
+ The server side logic remains the same, we just need to import the
+ Active Controls name space as they are not included by default. We
+ add the following line to the begin of "Home.php".
+ </p>
+<com:TTextHighlighter Language="php" CssClass="source block-content" id="code12">
+Prado::using('System.Web.UI.ActiveControls.*');
+</com:TTextHighlighter>
+
+ <p id="80075" class="block-content">If you try the application now, you may notice that the page no longer
+ needs to reload to calculate and display the converted total amount.
+ However, since there is not page reload, there is no indication or not obvious
+ that by clicking on the "Convert" button any has happened.
+ We can further refine the user experience by change the text of "total" label
+ to "calculating..." when the user clicks on the "Convert" button. The text of
+ the "total" label will still be updated with the new calculate amount as before.
+ </p>
+
+ <p id="80076" class="block-content">To indicate that the calculation is in progress, we can change the text
+ of the "total" label as follows. We add a <tt>ClientSide.OnLoading</tt> property
+ to the "Convert" button (since this button is responsible for requesting
+ the calculation).
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code13">
+&lt;com:TActiveButton Text="Convert" OnClick="convert_clicked" &gt;
+ &lt;prop:ClientSide.OnLoading&gt;
+ $('&lt;%= $this->total->ClientID %&gt;').innerHTML = "calculating..."
+ &lt;/prop:ClientSide.OnLoading&gt;
+&lt;/com:TActiveButton&gt;
+</com:TTextHighlighter>
+
+ <p id="80077" class="block-content">The <tt>ClientSide.OnLoading</tt> and various
+ <com:DocLink ClassPath="System.Web.UI.ActiveControls.TCallbackClientSide" Text="other properties" /> accept a javascript block as their content or value.
+ The javascript code <tt>$('...')</tt> is a javascript function that is
+ equivalent to <tt>document.getElementById('...')</tt> that takes a string
+ with the ID of an HTML element. Since Prado renders its components's IDs, we need
+ to use the rendered ID of the "total" label, that is, <tt>$this->total->ClientID</tt>. We place this bit of code within a <tt>&lt;%= %&gt;</tt> to obtain the rendered HTML ID for the "total" label. The rest of the
+ javascript code <tt>innerHTML = "calculating..."</tt> simply changes
+ the content of the "total" label.
+ </p>
+
+ <h1 id="16007">Adding Final Touches</h1>
+ <p id="80078" class="block-content">So far we have built a simple currency converter web application with
+ little attention of the looks and feel. Now we can add a stylesheet
+ to improve the overall appearance of the application. We can simply
+ add the stylesheet inline with the template code or we may create
+ a "theme".
+ </p>
+
+ <p id="80079" class="block-content">To create and use a theme with Prado applications, we simply create a new
+ directory "themes/Basic" in the <tt>currency-converter</tt> directory.
+ You may need to create the <tt>themes</tt> directory first. Any
+ directory within the <tt>themes</tt> are considered as a theme with the
+ name of the theme being the directory name. See the
+ <a href="?page=Advanced.Themes">Themes and Skins</a> for further details.
+ </p>
+
+ <p id="80080" class="block-content">We simply create a CSS file named "common.css" and save it in the
+ <tt>themes/Basic</tt> directory. Then we add the following code
+ to the beginning of "Home.page" (we add a little more HTML as well).
+ </p>
+<com:TTextHighlighter Language="prado" CssClass="source block-content" id="code14">
+&lt;%@ Theme="Basic" %&gt;
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
+&lt;com:THead Title="Currency Converter" /&gt;
+<body>
+</com:TTextHighlighter>
+ <p id="4334" class="block-content">
+ The first line <tt>&lt;%@ Theme="Basic" %&gt;</tt> defines the
+ theme to be used for this page. The
+ <com:DocLink ClassPath="System.Web.UI.WebControls.THead" Text="THead" />
+ corresponds to the HTML <tt>&lt;head&gt;</tt> element. In addition
+ to display the <tt>Title</tt> property by the <tt>THead</tt>, all CSS
+ files in the <tt>themes/Basic</tt> directory are also rendered/linked
+ for the current page. Our final currency converter web application
+ looks like the following.
+ <img src=<%~ example2.png %> class="figure" />
+ This completes introduction tutorial to the Prado web application framework.
+ </p>
+<div class="last-modified">$Id: CurrencyConverter.page 1654 2007-01-25 07:24:40Z wei $</div></com:TContent> \ No newline at end of file
diff --git a/demos/quickstart/protected/pages/Tutorial/fr/chat1.png b/demos/quickstart/protected/pages/Tutorial/fr/chat1.png
new file mode 100644
index 00000000..8288b496
--- /dev/null
+++ b/demos/quickstart/protected/pages/Tutorial/fr/chat1.png
Binary files differ
diff --git a/demos/quickstart/protected/pages/Tutorial/fr/chat2.png b/demos/quickstart/protected/pages/Tutorial/fr/chat2.png
new file mode 100644
index 00000000..97cbc51d
--- /dev/null
+++ b/demos/quickstart/protected/pages/Tutorial/fr/chat2.png
Binary files differ
diff --git a/demos/quickstart/protected/pages/Tutorial/fr/example1.png b/demos/quickstart/protected/pages/Tutorial/fr/example1.png
new file mode 100644
index 00000000..0c7da7ba
--- /dev/null
+++ b/demos/quickstart/protected/pages/Tutorial/fr/example1.png
Binary files differ
diff --git a/demos/quickstart/protected/pages/Tutorial/fr/example2.png b/demos/quickstart/protected/pages/Tutorial/fr/example2.png
new file mode 100644
index 00000000..1df56cfb
--- /dev/null
+++ b/demos/quickstart/protected/pages/Tutorial/fr/example2.png
Binary files differ