diff options
Diffstat (limited to 'demos/quickstart/protected/pages')
| -rw-r--r-- | demos/quickstart/protected/pages/Advanced/Scripts1.page | 465 | ||||
| -rw-r--r-- | demos/quickstart/protected/pages/Tutorial/AjaxChat.page | 753 | ||||
| -rw-r--r-- | demos/quickstart/protected/pages/Tutorial/CurrencyConverter.page | 4 | ||||
| -rw-r--r-- | demos/quickstart/protected/pages/Tutorial/chat1.png | bin | 0 -> 10533 bytes | |||
| -rw-r--r-- | demos/quickstart/protected/pages/Tutorial/chat2.png | bin | 0 -> 13409 bytes | 
5 files changed, 755 insertions, 467 deletions
diff --git a/demos/quickstart/protected/pages/Advanced/Scripts1.page b/demos/quickstart/protected/pages/Advanced/Scripts1.page index ef0b6317..7079582a 100644 --- a/demos/quickstart/protected/pages/Advanced/Scripts1.page +++ b/demos/quickstart/protected/pages/Advanced/Scripts1.page @@ -93,469 +93,4 @@ function test3()  </script>
  </com:TTextHighlighter>
 -<h2 id="6606">Using the <tt>$A()</tt> function</h2>
 -
 -<p>
 -    The <tt>$A()</tt> function converts the single argument it receives
 -    into an <tt>Array</tt> object.
 -</p>
 -<p>
 -    This function, combined with the extensions for the Array class,
 -    makes it easier to convert or copy any enumerable list into an
 -    <tt>Array</tt> object. One suggested use is to convert DOM
 -    <tt>NodeLists</tt> into regular arrays, which can be traversed
 -    more efficiently. See example below.
 -</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -<select id="lstEmployees" size="10" >
 -    <option value="5">Buchanan, Steven</option>
 -    <option value="8">Callahan, Laura</option>
 -    <option value="1">Davolio, Nancy</option>
 -</select>
 -
 -<input type="button" value="Show the options" onclick="showOptions();" />
 -
 -<script type="text/javascript">
 -/*<![CDATA[*/
 -function showOptions()
 -{
 -    var someNodeList = $('lstEmployees').options;
 -    var nodes = $A(someNodeList);
 -
 -    nodes.each(function(node)
 -    {
 -        alert(node.nodeName + ': ' + node.innerHTML);
 -    });
 -}
 -/*]]>*/
 -</script>
 -</com:TTextHighlighter>
 -
 -<h2 id="6607">Using the <tt>$H()</tt> function</h2>
 -<p>
 -    The <tt>$H()</tt> function converts
 -    objects into enumerable Hash objects that
 -    resemble associative arrays.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -function testHash()
 -{
 -    //let's create the object
 -    var a =
 -    {
 -        first: 10,
 -        second: 20,
 -        third: 30
 -    };
 -
 -    //now transform it into a hash
 -    var h = $H(a);
 -    alert(h.toQueryString());
 -
 -    //displays: first=10&second=20&third=30
 -}
 -</com:TTextHighlighter>
 -
 -<h2 id="6608">Enumerating... Wow! Damn! Wahoo!</h2>
 -<p>
 -    We are all familiar with for-loops. You know, create yourself an array, populate it with
 -    elements of the same kind, create a loop control structure (for, foreach, while, repeat, etc,)
 -    access each element sequentially, by its numeric index, and do something with the element.
 -</p>
 -<p>
 -    When you come to think about it, almost every time you have an array in your code it
 -    means that you'll be using that array in a loop sooner or later. Wouldn't it be nice
 -    if the array objects had more functionality to deal with these iterations? Yes, it would,
 -    and many programming languages provide such functionality in their arrays or equivalent
 -    structures (like collections and lists.)
 -</p>
 -
 -<p>
 -    Well, it turns out that prototype.js gives us the Enumerable
 -    object, which implements a plethora of tricks for us to use when dealing with iterable data.
 -    The prototype.js library goes one step further and extends the
 -    <tt>Array</tt> class  with all the methods of <tt>Enumerable</tt>.
 -</p>
 -
 -<a name="Loops"></a>
 -<h3 id="6617">Loops and iterator</h3>
 -<p>
 -    In standard javascript, if you wanted to sequentially display the elements of an array,
 -    you could very well write something like this.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -<script type="text/javascript">
 -/*<![CDATA[*/
 -function showList()
 -{
 -    var simpsons = ['Homer', 'Marge', 'Lisa', 'Bart', 'Meg'];
 -    for(i=0; i < simpsons.length; i++)
 -    {
 -        alert(simpsons[i]);
 -    }
 -}
 -/*]]>*/
 -</script>
 -
 -<input type="button" value="Show List" onclick="showList();" />
 -</com:TTextHighlighter>
 -<p>
 -    With our new best friend, prototype.js, we can rewrite this loop like this.
 -</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -function showList()
 -{
 -    var simpsons = ['Homer', 'Marge', 'Lisa', 'Bart', 'Meg'];
 -    simpsons.each( function(familyMember)
 -    {
 -        alert(familyMember);
 -    });
 -}
 -</com:TTextHighlighter>
 -<p>
 -    You are probably thinking "big freaking deal...just a weird syntax for the same old thing."
 -    Well, in the above example, yes, there's nothing too earth shattering going on. After all,
 -    there's not much to be changed in such a drop-dead-simple example. But
 -    keep reading, nonetheless.
 -</p>
 -<p>
 -    Before we move on. Do you see this function that is being passed as an argument
 -    to the <tt>each</tt> method? Let's start referring to it as an
 -    <b>iterator</b> function.
 -</p>
 -
 -<h3 id="6618">Your arrays on steroids</h3>
 -
 -<p>
 -    Like we mentioned above, it's very common for all the elements in your array to be of
 -    the same kind, with the same properties and methods. Let's see how we can take advantage
 -    of iterator functions with our new souped-up arrays.
 -</p>
 -<p>
 -    Finding an element according to a criteria.
 -<p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -<script type="text/javascript">
 -/*<![CDATA[*/
 -function findEmployeeById(emp_id)
 -{
 -    var listBox = $('lstEmployees')
 -    var options = $A(listBox.options);
 -    var opt = options.find( function(employee)
 -    {
 -        return (employee.value == emp_id);
 -    });
 -
 -    alert(opt.innerHTML); //displays the employee name
 -}
 -/*]]>*/
 -</script>
 -
 -<select id="lstEmployees" size="10" >
 -    <option value="5">Buchanan, Steven</option>
 -    <option value="8">Callahan, Laura</option>
 -    <option value="1">Davolio, Nancy</option>
 -</select>
 -
 -<input type="button" value="Find Laura" onclick="findEmployeeById(8);" />
 -</com:TTextHighlighter>
 -
 -<p>
 -    Now let's kick it up another notch. See how we can filter out
 -    items in arrays, then retrieve just a desired member from each
 -    element.
 -<p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -<script type="text/javascript">
 -/*<![CDATA[*/
 -function showLocalLinks(paragraph)
 -{
 -    paragraph = $(paragraph);
 -    var links = $A(paragraph.getElementsByTagName('a'));
 -
 -    //find links that do not start with 'http'
 -    var localLinks = links.findAll( function(link)
 -    {
 -        var start = link.href.substring(0,4);
 -        return start !='http';
 -    });
 -
 -    //now the link texts
 -    var texts = localLinks.pluck('innerHTML');
 -
 -    //get them in a single string
 -    var result = texts.inspect();
 -    alert(result);
 -}
 -/*]]>*/
 -</script>
 -
 -<p id="someText">
 -    This <a href="http://othersite.com/page.html">text</a> has
 -    a <a href="#localAnchor">lot</a> of
 -    <a href="#otherAnchor">links</a>. Some are
 -    <a href="http://wherever.com/page.html">external</a>
 -    and some are <a href="#someAnchor">local</a>
 -</p>
 -<input type=button value="Find Local Links"
 -        onclick="showLocalLinks('someText')" />
 -</com:TTextHighlighter>
 -<p>
 -    It takes just a little bit of practice to get completely addicted to this syntax.
 -    Next we will go through the available functions with the following example.
 -</p>
 -<h1 id="6602">Enumerable Functions</h1>
 -The sample data for the following examples.
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -var Fixtures =
 -{
 -    Products:
 -    [
 -        {name: 'Basecamp', company: '37signals',  type: 'Project Management'},
 -        {name: 'Shopify',  company: 'JadedPixel', type: 'E-Commerce'},
 -        {name: 'Mint',     company: 'Shaun Inman',type: 'Statistics'}
 -    ],
 -
 -    Artist:
 -    [
 -        'As I Lay Dying',
 -        '36 Crazyfist',
 -        'Shadows Fall',
 -        'Trivium',
 -        'In Flames'
 -    ],
 -
 -    Numbers:  [0, 1, 4, 5, 98, 32, 12, 9]
 -};
 -
 -var F = Fixtures;
 -</com:TTextHighlighter>
 -
 -<h2 id="6609"><tt>Enumerable.each</tt> function</h2>
 -<p>I used to find myself writing a lot of for loops. Although,
 -Prototype does not by any means eliminate the need to do for-loops,
 -it does give you access to what I consider to be a cleaner, easier to read method in each.
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -for(var i = 0; i < F.Numbers.length; i++)
 -{
 -    Logger.info(F.Numbers[i]);
 -}
 -</com:TTextHighlighter>
 -<p>
 -The <tt>each</tt> function allows us to iterate over these objects Ruby style.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -F.Numbers.each(function(num)
 -{
 -    Logger.info(num);
 -});
 -
 -//Output
 -0
 -1
 -4
 -5
 -98
 -32
 -12
 -9
 -</com:TTextHighlighter>
 -
 -<p>The <tt>each</tt> function takes one argument, an <b>iterator</b> function.
 -This iterator is invoked once for every item in the array, and that item
 -along with the optional index is passed to the iterator. So if
 -we also needed the index we could do something like the code below.
 -</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -F.Numbers.each(function(num, index)
 -{
 -    Logger.info(index + ": " + num);
 -});
 -
 -//Output
 -0: 0
 -1: 1
 -2: 4
 -3: 5
 -4: 98
 -5: 32
 -6: 12
 -7: 9
 -</com:TTextHighlighter>
 -
 -<h2 id="6610">Hash key/value pairs</h2>
 -<p>Hashes can be created by wrapping an Object (associative array) in
 -<tt>$H()</tt> and can have their key/value pairs exposed.</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -$H(F.Products[0]).each(function(product)
 -{
 -    Logger.info(product.key + ": " + product.value);
 -});
 -
 -//Outputs
 -name: Basecamp
 -company: 37signals
 -type: Project Management
 -</com:TTextHighlighter>
 -<p>
 -We can also directly access the keys and values of a Hash without iterating over it.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -$H(F.Products[1]).keys();
 -//Outputs name,company,type
 -
 -$H(F.Products[1]).values();
 -//Outputs Shopify,JadedPixel,E-Commerce
 -</com:TTextHighlighter>
 -
 -<h2 id="6611"><tt>Enumerable.collect</tt> function</h2>
 -
 -<p>The <tt>collect</tt> function allows you to iterate over an <tt>Array</tt> and return the
 -results as a new array. Each item returned as a result of the iteration will be
 -pushed onto the end of the new array.</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -var companies = F.Products.collect(function(product)
 -{
 -    return product.company;
 -});
 -
 -Logger.info(companies.join(', '));
 -
 -// Outputs
 -// 37signals, JadedPixel, Shaun Inman
 -</com:TTextHighlighter>
 -
 -<p>You can even join on the end of the block.</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -return F.Products.collect(function(product)
 -{
 -    return product.company;
 -}).join(', ');
 -</com:TTextHighlighter>
 -
 -<h2 id="6612"><tt>Enumerable.include</tt> function</h2>
 -
 -<p>The <tt>include</tt> function allows you to check if a value is included in an array
 -and returns true or false depending on if a match was made. Assuming I put
 -up a form asking the user to name some artist in my iTunes playlist,
 -we could do something like the code below. Prime candidate for some conditional madness.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -return F.Artists.include('Britney Spears'); // returns false
 -</com:TTextHighlighter>
 -
 -<h2 id="6613"><tt>Enumerable.inject</tt> function</h2>
 -
 -<p>The <tt>inject</tt> function is good for getting a collective sum from an array of
 -values. For instance, to add up all the numbers.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -var score = F.Numbers.inject(0, function(sum, value)
 -{
 -    return sum + value;
 -});
 -
 -Logger.info(score);
 -//Output 161
 -</com:TTextHighlighter>
 -
 -<p>The first argument to <tt>inject</tt> is just an initial value that
 -would be added to the sum, so if we added 1 instead of 0, the output would be 162.</p>
 -
 -<h2 id="6614"><tt>Enumerable.findAll</tt> function</h2>
 -<p>
 -When given an Array, the <tt>findAll</tt> function will return an array of
 -items for which the iterator evaluated to true. Basically, it allows you to
 -build a new array of values based on some search criteria.
 -If we wanted to find all products whose type was “E-Commerce”
 -we could do something like the code below.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -var ecom = F.Products.findAll(function(product)
 -{
 -    return product.type == 'E-Commerce';
 -});
 -
 -Logger.info(ecom[0].company + " produces " + ecom[0].name);
 -
 -//Outputs
 -JadedPixel produces Shopify
 -</com:TTextHighlighter>
 -
 -<p>Note that even if only one match is made, just as in this case,
 -the result is still returned as an array. In that case,
 -<tt>ecom.company</tt> would return <tt>undefined</tt>.</p>
 -
 -<h2 id="6615"><tt>Enumerable.detect</tt> function</h2>
 -<p>Unlike the <tt>findAll</tt> function, the <tt>detect</tt> function will only
 -return the first item for which the expression inside
 -the iterator is true. So, if we wanted to find the first number that
 -was greater than 5 we’d do something like the code below.
 -</p>
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -var low = F.Numbers.detect(function(num)
 -{
 -    return num > 5
 -});
 -
 -Logger.info(low);
 -//Outputs 98
 -</com:TTextHighlighter>
 -
 -<p>Even though, there are other numbers above 5 in our array, detect
 -only gives us the first match back.</p>
 -
 -<h2 id="6616"><tt>Enumerable.invoke</tt> function</h2>
 -
 -<p>The <tt>invoke</tt> function allows us to pass a method as a string and
 -have that method invoked. For instance, if we wanted to sort
 -our array of artists we’d do something like this:</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -[F.Artists].invoke('sort')
 -//Outputs 36 Crazyfist,As I Lay Dying,In Flames,Shadows Fall,Trivium
 -</com:TTextHighlighter>
 -
 -<p>Why not just use <tt>F.Artists.sort</tt>? Well, for the example above
 -we could do just that, but here is where <tt>invoke</tt> shines.</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -[F.Artists, F.Letters].invoke('sort');
 -//Outputs 36 Crazyfist,As I Lay Dying,In Flames,...
 -</com:TTextHighlighter>
 -<p>So we invoked sort for each sub-array. Note that the code below will not work.</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -F.Artists.invoke('sort');
 -</com:TTextHighlighter>
 -
 -<p>The reason this will not work is because it is taking each item
 -in that array and trying to apply sort to it, thus if we wrote it outright,
 -it would look something like this:</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -"36 Crazy Fists".sort();
 -</com:TTextHighlighter>
 -<p>We could however do something like this:</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -F.Artists.invoke('toLowerCase');
 -//Outputs 36 crazyfist,as i lay dying,in flames,shadows ...
 -</com:TTextHighlighter>
 -
 -<p>
 -Now, what about passing arguments to the <tt>invoke</tt> function?
 -The first argument passed to <tt>invoke</tt> is the method to be invoked,
 -and any other arguments beyond that will be passed as arguments to the invoked method.</p>
 -
 -<com:TTextHighlighter Language="javascript" CssClass="source">
 -F.Artists.invoke('concat', " is awesome ")
 -//Outputs
 -36 Crazyfist is awesome ,As I Lay Dying is awesome ,...
 -</com:TTextHighlighter>
 -
  </com:TContent>
\ No newline at end of file diff --git a/demos/quickstart/protected/pages/Tutorial/AjaxChat.page b/demos/quickstart/protected/pages/Tutorial/AjaxChat.page new file mode 100644 index 00000000..3a17b5d4 --- /dev/null +++ b/demos/quickstart/protected/pages/Tutorial/AjaxChat.page @@ -0,0 +1,753 @@ +<com:TContent ID="body"> +	<h1>Building an AJAX Chat Application</h1> +	<p>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 utilizing the following ideas/components in Prado. +	<ul> +		<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>  + +	<p>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>Download, Install and Create a New Application</h1> +	<p>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"> +php prado/framework/prado-cli.php -c chat +</com:TTextHighlighter>  +	</p> + +	<p>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>Authentication and Authorization</h1> +	<p>The first task for this application is to ensure that each user +	of the chat application is assigned with a unique (choosen by the user)  +	username. To achieve this, we can secure the main chat application +	page to deny access to anonymouse 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 there create by the command line tool). +	</p> +<com:TTextHighlighter Language="php" CssClass="source"> +<?php +class Login extends TPage +{ +} +?> +</com:TTextHighlighter> +<com:TTextHighlighter Language="prado" CssClass="source"> +<!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> +<com:TForm> +    <h1 class="login">Prado Chat Demo Login</h1> +    <fieldset class="login"> +        <legend>Please enter your name:</legend> +        <div class="username"> +            <com:TLabel ForControl="username" Text="Username:" /> +            <com:TTextBox ID="username" MaxLength="20" /> +            <com:TRequiredFieldValidator +                ControlToValidate="username" +                Display="Dynamic" +                ErrorMessage="Please provide a username." /> +        </div> +        <div class="login-button"> +            <com:TButton Text="Login" /> +        </div> +</com:TForm> +</body> +</html> +</com:TTextHighlighter> +	<p>The login page contains  +	a <com:DocLink ClassPath="System.Web.UI.WebControls.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>Securing the <tt>Home</tt> page</h2> +<p>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"> +<?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"> +<?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 anonymouse users to access the  +<tt>Login</tt> page (anonymouse 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>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>Active Record for <tt>chat_users</tt> table</h1> +<p>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"> +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"> +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"> +<?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>Custom User Manager class</h2> +<p>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"> +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> +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><authorization></tt> rules defined in our +<tt>config.xml</tt> file. </p> + +<p>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>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><module></tt> +configuration with <tt>id="users"</tt>.</p> +<com:TTextHighlighter Language="xml" CssClass="source"> +<module id="users" class="ChatUserManager" /> +</com:TTextHighlighter> + +<h1>Authentication</h1> +<p>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 a <tt>OnClick</tt> event handler +for the login button.</p> +<com:TTextHighlighter Language="prado" CssClass="source"> +<com:TCustomValidator +    ControlToValidate="username" +    Display="Dynamic" +    OnServerValidate="checkUsername" +    ErrorMessage="The username is already taken." /> + +... + +<com:TButton Text="Login" OnClick="createNewUser" /> +</com:TTextHighlighter> +In the <tt>Login.php</tt> file, we add the following 2 methods. +<com:TTextHighlighter Language="php" CssClass="source"> +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> +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:  +<ul> +	<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> +Finally, we redirect the client to the default <tt>Home</tt> page. +</p> + +<h2>Default Values for ActiveRecord</h2> +<p>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 'last_activity' in table 'chat_users'.</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"> +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 under score after the dollar sign). +</p> + +<h1>Main Chat Application</h1> +<p>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 textare for the user to enter the text message +and a button to send the message. +<com:TTextHighlighter Language="prado" CssClass="source"> +<!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> +<com:TForm> +<h1>Prado Chat Demo</h1> +<div id="messages" class="messages"> +    <com:TPlaceHolder ID="messageList" /> +</div> +<div id="users" class="user-list"> +    <com:TPlaceHolder ID="userList" /> +</div> +<div class="message-input"> +    <com:TActiveTextBox ID="userinput"  +        Columns="40" Rows="2" TextMode="MultiLine" /> +    <com:TActiveButton ID="sendButton" CssClass="send-button"  +        Text="Send" /> +</div> +</com:TForm> +<com:TJavascriptLogger /> +</body> +</html> +</com:TTextHighlighter> +We have add 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>Exploring the Active Controls</h2> +<p>Lets have some fun before we proceed with setuping 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"> +<com:TActiveButton ID="sendButton" CssClass="send-button"  +	Text="Send" OnClick="processMessage"/> +</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"> +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 what ever you have typed echoed in the <tt>TJavascriptLogger</tt> console. +</p> + +<p>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"> +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>Active Record for <tt>chat_buffer</tt> table</h1> +<p>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"> +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"> +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>Chat Application Logic</h1> +<p>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"> +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 simplicty and succintness of using ActiveRecords for simple database designs. +</p> + +<p>The next piece of the logic is to retreive 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"> +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"> +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>Putting It Together</h1> +<p>Nows 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"> +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>At this point the application is actually already functional, just not very +user friendly. If you open two different browser, you should be able to communicate +between the two users when ever the <tt>Send</tt> button is clicked. +</p> + +<p>The next part is perphaps 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"> +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>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"> +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>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"> +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>Improving User Experience</h1> +<p>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 to defined in <tt>Home.php</tt>. +We set the polling interval to 2 seconds. +<com:TTextHighlighter Language="prado" CssClass="source"> +<com:TTimeTriggeredCallback OnCallback="refresh"  +	Interval="2" StartTimerOnLoad="true" /> +</com:TTextHighlighter> +<com:TTextHighlighter Language="php" CssClass="source"> +function refresh($sender, $param) +{ +    $this->refreshUserList(); +    $this->refreshMessageList(); +} +</com:TTextHighlighter> +</p> + +<p>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"> +<com:TClientScript> +Event.observe($("<%= $this->userinput->ClientID %>"), "keypress", function(ev) +{ +    if(Event.keyCode(ev) == Event.KEY_RETURN) +    { +        if(Event.element(ev).value.length > 0) +            new Prado.Callback("<%= $this->sendButton->UniqueID %>"); +        Event.stop(ev); +    } +}); +</com:TClientScript> +</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>This completes the tutorial on making a basic chat web application using +the Prado framework. Hope you have enjoyed it. +</p> + +</com:TContent>
\ No newline at end of file diff --git a/demos/quickstart/protected/pages/Tutorial/CurrencyConverter.page b/demos/quickstart/protected/pages/Tutorial/CurrencyConverter.page index 0e54fbc2..fdce0b47 100644 --- a/demos/quickstart/protected/pages/Tutorial/CurrencyConverter.page +++ b/demos/quickstart/protected/pages/Tutorial/CurrencyConverter.page @@ -11,12 +11,12 @@  		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/demo/currency-converter/">Pradosoft.com</a>. +		<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>Downloading and Installing Prado</h1> +	<h1 id="download">Downloading and Installing Prado</h1>  	<p>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  diff --git a/demos/quickstart/protected/pages/Tutorial/chat1.png b/demos/quickstart/protected/pages/Tutorial/chat1.png Binary files differnew file mode 100644 index 00000000..8288b496 --- /dev/null +++ b/demos/quickstart/protected/pages/Tutorial/chat1.png diff --git a/demos/quickstart/protected/pages/Tutorial/chat2.png b/demos/quickstart/protected/pages/Tutorial/chat2.png Binary files differnew file mode 100644 index 00000000..97cbc51d --- /dev/null +++ b/demos/quickstart/protected/pages/Tutorial/chat2.png  | 
