Pradoquickstart PDF
Pradoquickstart PDF
Pradoquickstart PDF
4 Quickstart
Tutorial 1
Qiang Xue and Wei Zhuo
Contents
ii
Preface
Prado quick start doc
iii
iv
License
PRADO is free software released under the terms of the following BSD license.
Copyright 2004-2014, The PRADO Group (http://www.pradosoft.com) All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
3. Neither the name of the PRADO Group nor the names of its contributors may be used
to endorse or promote products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Chapter 1
Getting Started
1.1
This Quickstart tutorial is provided to help you quickly start building your own Web applications
based on PRADO version 3.x.
If you are an existing PRADO 3.x user and would like to learn what enhancements are available
for each new version, please check out the new features page. Otherwise, the following sections are
helpful for newbies.
You may refer to the following resources if you find this tutorial does not fulfill all your needs.
PRADO Classes
PRADO Forum
PRADO Wiki
1.2
What is PRADO?
PRADO is a component-based and event-driven programming framework for developing Web applications in PHP 5. PRADO stands for PHP Rapid Application Development Object-oriented.
A primary goal of PRADO is to enable maximum reusability in Web programming. By reusability,
we mean not only reusing ones own code, but also reusing other peoples code in an easy way. The
latter is more important as it saves the effort of reinventing the wheels and may cut off development
time dramatically. The introduction of the concept of component is for this purpose.
To achieve the above goal, PRADO stipulates a protocol of writing and using components to
construct Web applications. A component is a software unit that is self-contained and can be
reused with trivial customization. New components can be created by simple composition of
existing components.
To facilitate interacting with components, PRADO implements an event-driven programming
paradigm that allows delegation of extensible behavior to components. End-user activities, such as
clicking on a submit button, are captured as server events. Methods or functions may be attached
to these events so that when the events happen, they are invoked automatically to respond to the
events. Compared with the traditional Web programming in which developers have to deal with
the raw POST or GET variables, event-driven programming helps developers better focus on the
necessary logic and reduces significantly the low-level repetitive coding.
In summary, developing a PRADO Web application mainly involves instantiating prebuilt component types, configuring them by setting their properties, responding to their events by writing
handler functions, and composing them into pages for the application. It is very similar to RAD
toolkits, such as Borland Delphi and Microsoft Visual Basic, that are used to develop desktop GUI
applications.
1.2.1
Why PRADO?
PRADO is mostly quoted as a unique framework. In fact, it is so unique that it may turn your
boring PHP programming into a fun task. The following list is a short summary of the main
features of PRADO,
Reusability - Code following the PRADO component protocol are highly reusable. This
benefits development teams in the long run as they can reuse their previous work and integrate
1.2.2
PRADO is best suitable for creating Web applications that are highly user-interactive. It can be
used to develop systems as simple as a blog system to those as complex as a content management
system (CMS) or a complete e-commerce solution. Because PRADO promotes object-oriented
programming through its component-based methodology, it fits extremely well for team work and
enterprise development.
PRADO comes with a complete set of caching techniques which help accelerate PRADO Web
applications to accommodate high traffic requirement. Its modular architecture allows developers
to use or plug in different cache modules for different needs. The output caching enables one to
selectively choose to cache part of a rendered Web page.
1.2.3
PRADO is often quoted as a unique framework. Its uniqueness mainly lies in the component-based
and event-driven programming paradigm that it tries to promote. Although this programming
paradigm is not new in desktop application programming and not new in a few Web programming
languages, PRADO is perhaps the first PHP framework enabling it.
Most PHP frameworks mainly focuses on separating presentation and logic and promotes the MVC
(model-view-controller) design pattern. PRADO achieves the same goal naturally by requiring logic
be stored in classes and presentation in templates. PRADO does much more on aspects other than
MVC. It fills lot of blank area in PHP Web programming with its component-based programming
paradigm, its rich set of Web controls, its powerful database support, its flexible error handling
and logging feature, and many others.
1.2.4
Yes. PRADO was initially released in August 2004. Many test suites have been written and
conducted frequently to ensure its quality. It has been used by thousands of developers and many
Web applications have been developed based on it. Bugs and feature requests are managed through
GitHub and we have a great user community and development team to ensure all questions are
answered in a timely fashion.
1.2.5
History of PRADO
The very original inspiration of PRADO came from Apache Tapestry. During the design and
implementation, Qiang Xue borrowed many ideas from Borland Delphi and Microsoft ASP.NET.
The first version of PRADO came out in June 2004 and was written in PHP 4. Driven by the
Zend PHP 5 coding contest, Qiang rewrote PRADO in PHP 5, which proved to be a wise move,
thanks to the new object model provided by PHP 5. PRADO won the grand prize in the Zend
contest, earning the highest votes from both the public and the judges panel.
In August 2004, PRADO started to be hosted on SourceForge as an open source project. Soon
after, the project site xisc.com was announced to public. With the fantastic support of PRADO
developer team and PRADO users, PRADO evolved to version 2.0 in mid 2005. In this version,
Wei Zhuo contributed to PRADO with the excellent I18N and L10N support.
In May 2005, we decided to completely rewrite the PRADO framework to resolve a few fundamental
issues found in version 2.0 and to catch up with some cool features available in Microsoft ASP.NET
2.0. After nearly a years hard work with over 50,000 lines of new code, version 3.0 was finally
made available in April 2006.
In October 2008, the original development team released a new framework called Yii. Inheriting
most of the PRADO code, Yii left the pages/events concept, focusing on a pure MVC design
pattern. Since 2009 a lot of people contributed to PRADO fixing issues, developing new features
or backporting them from Yii.
To promote a model of community-driven development, the project repositories were moved first
on Google Code and then, in September 2013, on GitHub.
Starting from version 3.0, significant efforts are allocated to ensure the quality and stability of
PRADO. If we say PRADO v2.x and v1.x are proof-of-concept work, we can say PRADO 3.x has
grown up to a project that is suitable for serious business application development.
1.3
Installing PRADO
If you are viewing this page from your own Web server, you are already done with the installation.
The minimum requirement by PRADO is that the Web server support PHP 5.3.3. PRADO has
been tested with Apache Web server on Windows, MacOSX and various Linux distro. Highly
possibly it may also run on other platforms with other Web servers, as long as PHP 5.3.3 is
Standalone package
1. Go to pradosoft.com to grab the latest version of PRADO.
2. Unpack the PRADO release file to a Web-accessible directory.
Your installation of PRADO is done and you can start to play with the demo applications included
in the PRADO release via URL http://web-server-address/prado/demos/. Here we assume
PRADO is unpacked to the prado subdirectory under the DocumentRoot of the Web server.
If you encounter any problems with the demo applications, please use the PRADO requirement
checker script, accessible via http://web-server-address/prado/requirements/index.php, to
check first if your server configuration fulfills the conditions required by PRADO.
div id=install-steps class=block-content
Composer install
1. If you dont have installed composer already, install it globally:
$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install
4. In your project include the autoloader, and you will have access to the library classes:
<?php
require_once(vendor/autoload.php);
1.4
New Features
This page summarizes the main new features that are introduced in each PRADO release.
1.4.1
Version 3.2.4
1.4.2
Version 3.2.3
1.4.3
Version 3.2.2
1.4.4
Version 3.2.1
TSecurityManager has been enhanced to support all the ciphers available in php
Added a new UrlFormat for TUrlManager: HiddenPath; works like the Path format, but
hides the entryscript.php name
Updated external packages
1.4.5
Version 3.2.0
1.4.6
Version 3.1.10
bugfix-only release/a
1.4.7
Version 3.1.9
Added documentation and samples to the quickstart tutorial for all the active controls
Added TTemplateControlInheritable control
Added TActiveDataGrid control
Added TActiveDataList control
Added TActiveMultiView control
Added TActiveRepeater control
Added TActiveTableRow control
Added TActiveTableCell control
1.4.8
Version 3.1.8
1.4.9
Version 3.1.7
1.4.10
Version 3.1.6
1.4.11
Version 3.1.5
1.4.12
Version 3.1.4
1.4.13
Version 3.1.3
1.4.14
Version 3.1.2
Added a new active control TActivePager that allows to paginate a databound control with
an ajax callback.
Added TFirebugLogRoute to send logs to the Firebug console
1.4.15
Version 3.1.1
10
1.4.16
Version 3.1.0
Added seamless AJAX support. A whole array of AJAX-enabled controls, called active
controls, are introduced. The usage of these active controls is very similar to their nonAJAX counterparts, i.e., plug in and use. For more details, see the tutorial about active
controls.
Added complete database support.
Added new controls, modules and services, including TSoapService, TOutputCache, TSessionPageStatePersister, TFeedService, TJsonService, cache dependency classes, TXmlTransform.
Enhanced some data controls with renderers. Renderer enables reusing item templates that
are commonly found in TRepeater, TDataList and TDataGrid, and makes the configuration
on these controls much cleaner. For more details about renders, see the updated tutorials on
TRepeater, TDataList and TDataGrid.
Added support to allow including external application configurations. Enhanced template
syntax to facilitate subproperty configuration.
Added TDbUserManager and TDbUser to simplify authentication and authorization with
user accounts stored in a database.
1.5
11
1.5.1
Component Definition
Version 3.0 has completely discarded the need of component specification files. It relies more on
conventions for defining component properties and events. In particular, a property is defined by
the existence of a getter method and/or a setter method, while an event is defined by the existence
of an on-method. Property and event names in v3.0 are both case-insensitive. As a consequence,
developers are now required to take care of type conversions when a component property is being
set. For example, the following code is used to define the setter method for the Enabled property
of TControl, which is of boolean type,
1.5.2
Application Controller
Application controller now implements a modular architecture. Modules can be plugged in and
configured in application specifications. Each module assumes a particular functionality, and they
are coordinated together by the application lifecycle. The concept of v2.x modules is replaced in
v3.0 by page directories. As a result, the format of v3.0 application specification is also different
from earlier versions.
12
1.5.3
Pages
Pages in v3.0 are organized in directories which may be compared to the module concept in v2.x.
Pages are requested using the path to them. For example, a URL index.php?page=Controls.Samples.Sample1
would be used to request for a page named Sample1 stored under the [BasePath]/Controls/Samples
directory, where [BasePath] refers to the root page path. The file name of a page template must be
ended with .page, mainly to differentiate page templates from non-page control templates whose
file names must be ended with .tpl.
1.5.4
Control Relationship
Version 3.0 redefines the relationships between controls. In particular, the parent-child relationship now refers to the enclosure relationship between controls presentation. And a new namingcontainer relationship is introduced to help better manage control IDs. For more details, see the
controls section.
1.5.5
Template Syntax
The syntax of control templates in v3.0 remains similar to those in earlier versions, with many
enhancements. A major change is about the databinding expression. In v3.0, this is done by the
following,
Expression and statement tags are also changed similarly. For more details, see the template
definition section.
1.5.6
Theme Syntax
Themes in v3.0 are defined like control templates with a few restrictions.
13
1.6
Web Site Administration Tool (WSAT) is a development tool which allows you to perform several
tedious tasks of a PRADO project in a GUI fashion. Its inspired in both Asp.Net - Web Site
Administration Tool and Yiis Gii. WSAT will continue gaining new features along the time, at
the moment it bring you the followings:
1.6.1
t oString()methodinallARClasses.
Requirements
To use WSAT, you need to add in your project configuration file: application.xml, in the services
section the wsat service like follows:
<services>
...
<service id="wsat" class="System.Wsat.TWsatService" Password="my_secret_password" />
</services>
1.6.2
Usage
1.6.3
14
In which you can find a basic usage documentation in a light green panel and then some proper
fields in order to generate active record classes. These fields are:
Table Name:
refers to the table name in your database for which you want to generate
points in between. It default value is: Application.AppD ata.ARC lasseswhichref erencestheprotected/AppD ata
Class Suffix: refers to the suffix that you whish to specify at the end of every class name. Clear
the field to specify no suffix.
Build Relations: whether or not you want to add relationships to AR classes generation.
1.7
Note: With Prado version 3.2.3 prado-cli.php location changed from framework to bin directory.
If youre using Prado 3.2.2 or earlier, replace bin with framework in examples below.
The optional prado-cli.php PHP script file in the bin directory provides command line tools to
15
perform various tedious takes in Prado. The prado-cli.php can be used to create Prado project
skeletons, create initial test fixtures, and access to an interactive PHP shell.
16
1.7.1
Requirements
To use the command line tool, you need to use your command prompt, command console or
terminal. In addition, PHP must be able to execute PHP scripts from the command line.
1.7.2
Usage
If you type php path/to/bin/prado-cli.php, you should see the following information. Alternatively, if you are not on Windows, you may try to change the prado-cli.php into an executable
and execute it as a script
Command line tools for Prado 3.0.5.
usage: php prado-cli.php action <parameter> [optional]
example: php prado-cli.php -c mysite
actions:
-c <directory>
Creates a Prado project skeleton for the given <directory>.
-t <directory>
Create test fixtures in the given <directory>.
shell [directory]
Runs a PHP interactive interpreter. Initializes the Prado
application in the given [directory].
The parameter are required parameters and [optional] are optional parameters.
1.7.3
17
1.7.4
Interactive Shell
The interactive shell allows you to evaluate PHP statements from the command line. The prado-cli.php
script can be used to start the shell and load an existing Prado project. For example, let us load
the blog demo project. Assume that your command line is in the prado distribution directory and
you type.
$: php bin/prado-cli.php shell demos/blog
Then we will get an instance of the Prado blog application, and from that instance we want an
instance of the data module. Notice that a semicolon at the end of the line suppresses the
output.
>> $app = Prado::getApplication();
>> $db = $app->getModule(data);
Lastly, we want to use the data module to query for a post with ID=1. Notice that we leave out
the semicolon to show the results.
>> $db->queryPostByID(1)
There should not be any errors and you should see the following.
18
1.7.5
In the blog demo project, we need to create two Active Record classes, UserRecord and PostRecord,
to represent data records in the users and posts tables, respectively. Active Record classes must
extend from the base class ActiveRecord, and must define property names that matches with the
field names of the corresponding table.
To better organize our directories, we create a new directory protected/database to hold the class
files. We also modify our application configuration by inserting the following lines. It is equivalent
At the prompt, enter the following two commands to create UserRecord and PostRecord classes:
>> generate users Application.database.UserRecord
>> generate posts Application.database.PostRecord
Here we used the namespace format again to specify the classes to be created. The path Application.database.UserRe
indicates that we want the UserRecord class file to be protected/database/UserRecord.php.
19
20
Chapter 2
Tutorials
2.1
In this section, we guide you through creating your first PRADO application, the famous Hello
World application.
Hello World perhaps is the simplest iinteractive/i PRADO application that you can create.
It displays to end-users a page with a submit button whose caption is Click Me. After the user
clicks on the button, its caption is changed to Hello World.
There are many approaches that can achieve the above goal. One can submit the page to the
server, examine the POST variable, and generate a new page with the button caption updated.
Or one can simply use JavaScript to update the button caption upon its onclick client event.
PRADO promotes component-based and event-driven Web programming. The button is represented by a TButton object. It encapsulates the button caption as the Text property and associates
the user button click action with a server-side OnClick event. To respond to the user clicking on
the button, one simply needs to attach a function to the buttons OnClick event. Within the
function, the buttons Text property is modified as Hello World. The following diagram shows
the above sequence,
Our PRADO application consists of three files, index.php, Home.page and Home.php, which are
organized as follows,
21
Chapter 2. Tutorials
where each directory is explained as follows. Note, the above directory structure can be customized.
For example, one can move the protected directory out of Web directories. You will know how
to do this after you go through this tutorial.
assets - directory storing published private files. See assets section for more details. This
directory must be writable by the Web server process.
protected - application base path storing application data and private script files. This
directory should be configured as inaccessible to end-users.
runtime - application runtime storage path storing application runtime information, such
as application state, cached data, etc. This directory must be writable by the Web server
process.
pages - base path storing all PRADO pages.
22
$application=new TApplication;
$application->run();
Home.page - template for the default page returned when users do not explicitly specify
the page requested. A template specifies the presentational layout of components. In this
example, we use two components, TForm and TButton, which correspond to the form and
input HTML tags, respectively. The template contains the following content,
<html>
<body>
<com:TForm>
<com:TButton Text="Click me" OnClick="buttonClicked" />
</com:TForm>
</body>
</html>
Home.php - page class for the Home page. It mainly contains the method responding to the
OnClick event of the button.
<?php
class Home extends TPage
{
public function buttonClicked($sender,$param)
{
// $sender refers to the button component
$sender->Text="Hello World!";
}
}
23
Chapter 2. Tutorials
2.2
Having seen the simple Hello World application, we now build a more complex application called
Hangman Game. In this game, the player is asked to guess a word, a letter at a time. If he
guesses a letter right, the letter will be shown in the word. The player can continue to guess as
long as the number of his misses is within a prespecified bound. The player wins the game if he
finds out the word within the miss bound, or he loses.
To facilitate the building of this game, we show the state transition diagram of the gaming process
in the following, br /br / To be continued...
Fundamentals.Samples.Hangman.Home Demo
24
Chapter 3
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.
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.
You can try the application locally or at Pradosoft.com. Notice that the application still functions
exactly the same if javascript is not available on the users browser.
25
3.2
To install Prado, simply download the latest version of Prado from http://www.pradosoft.com and
unzip the file to a directory not 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 Quickstart Installation guide.
3.3
The quickest and simplest way to create a new Prado web application is to use the command
tool prado-cli.php found in the framework 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 currency-converter in your current working directory. You may
need to change to the appropriate directory first. See the Command Line Tool for more details.
php prado/framework/prado-cli.php -c currency-converter
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 browsers
url to the web server to serve up the index.php script in the currency-converter directory. You
should see the message Welcome to Prado!
3.4
26
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.
The first component we add is a TForm that basically corresponds to the HTML <form> element.
In Prado, only one TForm element is allowed per page.
The next two pair of component we add is the TLabel and TTextBox that basically defines a label
and a textbox for the user of the application to enter the currency exchange rate. The ForControl
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 <label> element to do the same thing, but you would have to find the correct ID of the
textbox (or <input> in HTML) as Prado components may/will render the ID value differently in
the HTML output.
The next pair of components are similar and defines the textbox to hold the dollar value to be
converted. The TLabel with ID value total defines a simple label. Notice that the ForControl
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.
27
3.5
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 Home class should extends the TPage, the default base class for all Prado pages.
<?php
class Home extends TPage
{
}
autoload method to load classes. The convention is to use the class name
The value of the OnClick, convert clicked, will be the method name in the Home.php that
will called when the user clicks on the Convert button.
28
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.
In the convert clicked method the first parameter, $sender, corresponds to the object that
raised the event, in this case, the Convert button. The second parameter, $param contains any
additional data that the $sender object may wish to have added.
We shall now examine, the three lines that implements the simply currency conversion in the
convert clicked method.
$rate = floatval($this->currencyRate->Text);
The statement $this->currencyRate corresponds to the TTextBox component with ID value currencyRate in the Home.page template. The Text property of the TTextBox contains the value
that the user entered. So, we obtain this value by $this->currencyRate->Text which we convert
the value to a float value.
$dollars = floatval($this->dollars->Text);
The next line does a similar things, it takes the user value from the TTextBox with ID value dollars
and converts it to a float value.
The third line calculates the new amount and set this value in the Text property of the TLabel
with ID="total". Thus, we display the new amount to the user in the label.
$this->total->Text = $rate * $dollars;
3.6
Adding Validation
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 users
29
30
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. Prados validators by default validates using both javascript
and server side. The server side validation is always performed. For the server side, we should
skip the calculation if the validators are not satisfied. This can done as follows.
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;
}
}
3.7
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.
We can replace the TButton with the Active Control counter part, TActiveButton, that can trigger
a server side click event without reloading the page. In addition, we can change the totals
TLabel with the Active Control counter part, TActiveLabel, such that the server side can update
the browser without reloading the page.
31
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.
Prado::using(System.Web.UI.ActiveControls.*);
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.
To indicate that the calculation is in progress, we can change the text of the total label as
follows. We add a ClientSide.OnLoading property to the Convert button (since this button is
responsible for requesting the calculation).
<com:TActiveButton Text="Convert" OnClick="convert_clicked" >
<prop:ClientSide.OnLoading>
$(<%= $this->total->ClientID %>).innerHTML = "calculating..."
</prop:ClientSide.OnLoading>
</com:TActiveButton>
The ClientSide.OnLoading and various other properties accept a javascript block as their content or value.
3.8
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
32
The first line <%@ Theme="Basic" %> defines the theme to be used for this page. The THead
corresponds to the HTML <head> element. In addition to display the Title property by the
THead, all CSS files in the themes/Basic directory are also rendered/linked for the current page.
Our final currency converter web application looks like the following.
33
34
Chapter 4
This tutorial introduces the Prado web application frameworks ActiveRecord and Active Controls
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.
Building a custom User Manager class.
Authenticating and adding a new user to the database.
Using ActiveRecord to interact with the database.
Using Active Controls and callbacks to implement the user interface.
Separating application logic and application flow.
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
locally or at Pradosoft.com. The main application chat page is shown bellow.
35
4.2
The download and installation steps are similar to those in the Currency converter tutorial. To
create the application, we run from the command line the following. See the Command Line Tool
for more details.
php prado/framework/prado-cli.php -c chat
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 browsers
URL to the web server to serve up the index.php script in the chat directory. You should see the
message Welcome to Prado!
4.3
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 Login page with the following
code. We save the Login.php and Login.page in the chat/protected/pages/ directory (there
should be a Home.page file created by the command line tool).
<?php
class Login extends TPage
36
The login page contains a TForm, a TTextBox, a TRequiredFieldValidator and a TButton. The
resulting page looks like the following (after applying some a style sheet).
If you click on the Login button without entering any text in the username textbox, an error
message is displayed. This is due to the TRequiredFieldValidator requiring the user to enter some
text in the textbox before proceeding.
37
4.3.1
Now we wish that if the user is trying to access the main application page, Home.page, before they
have logged in, the user is presented with the Login.page first. We add a chat/protected/application.xml
configuration file to import some classes that we shall use later.
<?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>
38
4.4
The 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 chat users table and create an ActiveRecord that can work with the chat users
table with ease. For the demo, we use sqlite 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 chat users table as follows.
CREATE TABLE chat_users
(
username VARCHAR(20) NOT NULL PRIMARY KEY,
last_activity INTEGER NOT NULL DEFAULT "0"
);
Next we define the corresponding ChatUserRecord class and save it as chat/protected/App Code/ChatUserRecord.php
(you need to create the App Code directory as well). We also save the sqlite database file as
App Code/chat.db.
class ChatUserRecord extends TActiveRecord
{
const TABLE=chat_users;
public $username;
public $last_activity;
public static function finder($className=__CLASS__)
{
39
Before using the ChatUserRecord class we to configure a default database connection for ActiveRecord to function. In the chat/protected/application.xml we import classes from the App Code
directory and add an ActiveRecord configuration module.
<?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>
4.4.1
To implement a custom user manager module class we just need to extends the TModule class and
implement the IUserManager interface. The getGuestName(), getUser() and validateUser()
methods are required by the IUserManager interface. We save the custom user manager class as
App Code/ChatUserManager.php.
class ChatUserManager extends TModule implements IUserManager
{
public function getGuestName()
{
return Guest;
}
public function getUser($username=null)
40
The getGuestName() method simply returns the name for a guest user and is not used in our
application. The getUser() method returns a TUser object if the username exists in the database,
the TUser object is set with role of normal that corresponds to the <authorization> rules
defined in our config.xml file.
The addNewUser() and usernameExists() method uses the ActiveRecord corresponding to the
chat users table to add a new user and to check if a username already exists, respectively.
41
4.5
Authentication
To perform authentication, we just want the user to enter a unique username. We add a TCustomValidator for validate the uniqueness of the username and add an OnClick event handler for
the login button.
<com:TCustomValidator
ControlToValidate="username"
Display="Dynamic"
OnServerValidate="checkUsername"
ErrorMessage="The username is already taken." />
...
<com:TButton Text="Login" OnClick="createNewUser" />
42
4.5. Authentication
$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);
}
}
The checkUserName() method uses the ChatUserManager class (recall that in the config.xml
configuration we set the ID of the custom user manager class as users) to validate the username
is not taken.
In the createNewUser 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:
First we obtain a TUser instance from our custom user manager class using the $manager->getUser(...)
method.
Using the TAuthManager we set/update the user object in the current session data.
Then we set/update the Applications user instance with our new user object.
Finally, we redirect the client to the default Home page.
4.5.1
If you try to perform a login now, you will receive an error message like iProperty ChatUserRecord::$last activity
must not be null as defined by column last activity in table chat users./i. This means
that the $last activity 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 ChatUserRecord class. We shall demonstrate the later by altering the
ChatUserRecord with the addition of a set getter/setter methods for the last activity property.
private $_last_activity;
public function getLast_Activity()
43
Notice that we renamed $last activity to $ last activity (note the underscore after the dollar
sign).
4.6
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.
<!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;
44
<com:TActiveTextBox ID="userinput"
Columns="40" Rows="2" TextMode="MultiLine" />
<com:TActiveButton ID="sendButton" CssClass="send-button"
Text="Send" />
45
</com:TForm>
</body>
</html>
4.6.1
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 OnClick event
handler for the Send button.
And the corresponding event handler method in the Home.php class (we need to create this new
file too).
class Home extends TPage
{
function processMessage($sender, $param)
{
echo $this->userinput->Text;
}
}
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 TJavascriptLogger console.
To append or add some content to the message list panel, we need to use some methods in the
TCallbackClientScript class which is available through the CallbackClient property of the current
TPage object. For example, we do can do
function processMessage($sender, $param)
46
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.
4.7
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 chat buffer table is defined as follows.
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"
);
47
4.8
We finally arrive at the guts of the chat application logic. First, we need to save a received message
into the chat buffer for all the current users. We add this logic in the ChatBufferRecord class.
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();
}
}
}
We first find all the current users using the ChatUserRecord finder methods. Then we duplicate
the message and save it into the database. In addition, we update the message senders last
48
To retrieve a list of current users (formatted), we add this logic to the ChatUserRecord class. We
delete any users that may have been inactive for awhile.
49
Note: 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).
4.9
Putting It Together
Now comes to put the application flow together. In the Home.php we update the Send buttons
OnClick event handler to use the application logic we just implemented.
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);
}
}
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).
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
Send button is clicked.
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 Home.php, we can call this method when ever some callback event is raised, e.g. when the Send
button is clicked.
protected function refreshUserList()
50
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.
The anchor using time() 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).
51
4.10
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 TTimeTriggeredCallback control. We add a
TTimeTriggeredCallback to the Home.page and call the refresh handler method defined in
Home.php. We set the polling interval to 2 seconds.
<com:TTimeTriggeredCallback OnCallback="refresh"
Interval="2" StartTimerOnLoad="true" />
The final piece requires us to use some javascript. We want that when the user type some text in
the textarea and press the Enter key, we want it to send the message without clicking on the Send
button. We add to the Home.page some javascript.
52
Details regarding the javascript can be explored in the Introduction to Javascript section of the
quickstart.
This completes the tutorial on making a basic chat web application using the Prado framework.
Hope you have enjoyed it.
53
54
Chapter 5
Fundamentals
5.1
Architecture
PRADO is primarily a presentational framework, although it is not limited to be so. The framework
focuses on making Web programming, which deals most of the time with user interactions, to be
component-based and event-driven so that developers can be more productive. The following class
tree depicts some of the major classes provided by PRADO:
When a PRADO application is processing a page request, its static object diagram can be shown
as follows:
Once the main Application object gets created, it load the application configuration. The minimal
configuration defines a set of basic modules to parse the Request, create a proper Response, mantain the user Session, handle any Error and publish the needed Assets (images, css, javascript,
etc). These helpers module will be available from anywhere inside the application code. br/
Additionally, any 3rd-party or custom module can be loaded, and external Parameters can be
loaded from external configurations.
Once the basic infrastructure has been set up, the Request module parses the request trying to
identify the requested route. Different routes can be handled by different services, but the default
route for http requests is the Page Service. br/ The Page Services role is to instanciate the
requested Page, run it, apply any defined Theme and grab the result in order to build the Response.
A Page can be a simple script (.php), a Template (.page), or both. PRADO uses a very powerful
55
Chapter 5. Fundamentals
template engine where Controls can be instanciated directly. br/ A Control is an self-contained
widget that fullfills a specific task; they can be a simple script (.php), a Template (.page), or both.
5.2
Components: Part I
A component is an instance of TComponent or its child class. The base class TComponent implements
the mechanism of component properties and events.
56
5.2.1
Component Properties
A component property can be viewed as a public variable describing a specific aspect of the
component, such as the background color, the font size, etc. A property is defined by the existence
of a getter and/or a setter method in the component class. For example, in TControl, we define
its ID property using the following getter and setter methods,
class TControl extends TComponent {
public function getID() {
...
}
public function setID($value) {
...
}
}
To get or set the ID property, do as follows, just like working with a variable,
$id = $component->ID;
$component->ID = $id;
A property is read-only if it has a getter method but no setter method. Since PHP method names
are case-insensitive, property names are also case-insensitive. A component class inherits all its
ancestor classes properties.
Subproperties
A subproperty is a property of some object-typed property. For example, TWebControl has a Font
property which is of TFont type. Then the Name property of Font is referred to as a subproperty
(with respect to TWebControl).
To get or set the Name subproperty, use the following method,
57
Chapter 5. Fundamentals
$name = $component->getSubProperty(Font.Name);
$component->setSubProperty(Font.Name, $name);
Js-friendly properties
A JavaScript-friendly property is a property that can accept both simple strings and raw javascript.
Prado automatically encodes all properties sent clientside inside javascript blocks to avoid security
problems (like injections or cross site scripting). If a property is known to always contain only
safe javascript code and its value needs to bypass this encoding, that property can be defined in a
special way that will make Prado mark its value as safe. Js-friendly properties are identified by
their name starting with js (case insensitive):
// getter, defines a readable property Text
function getJsText() {
// setter, defines a writable property Text, with $value being the value to be set to the property
function setJsText(TJavaScriptLiteral $value) {
Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name:
// set some simple text as property value
$component->Text = text;
// set some javascript code as property value
$component->JsText = raw javascript;
In the first case, the property value will automatically gets encoded when sent clientside inside
a javascript block. In the second case, the property will be marked as being a safe javascript
statement and will not be encoded when rendered inside a javascript block. This special handling
makes use of the TJavaScriptLiteral class.
5.2.2
Component Events
Component events are special properties that take method names as their values. Attaching
(setting) a method to an event will hook up the method to the places at which the event is raised.
58
This defines an event named OnClick, and a handler can be attached to the event using one of the
following ways,
$button->OnClick = $callback;
$button->OnClick->add( $callback );
$button->OnClick[] = $callback;
$button->attachEventHandler( OnClick , $callback );
The variable $callback contains the definition of the event handler that can be either a string
referring to a global function name, or an array whose first element refers to an object and second
element a method name/path that is reachable by the object, e.g.
buttonClicked : buttonClicked($sender,$param);
array($object,buttonClicked) : $object-buttonClicked($sender,$param);
array($object,MainContent.SubmitButton.buttonClicked) : $object-MainContent-SubmitButtonbuttonClicked($sender,$param);
5.2.3
Namespaces
A namespace refers to a logical grouping of some class names so that they can be differentiated
from other class names even if their names are the same. Since PHP does not support namespace
intrinsically, you cannot create instances of two classes who have the same name but with different
definitions. To differentiate from user defined classes, all PRADO classes are prefixed with a letter
59
Chapter 5. Fundamentals
T (meaning Type). Users are advised not to name their classes like this. Instead, they may
prefix their class names with any other letter(s).
A namespace in PRADO is considered as a directory containing one or several class files. A class
may be specified without ambiguity using such a namespace followed by the class name. Each
namespace in PRADO is specified in the following format,
<div class="source">
where PathAlias is an alias of some directory, while Dir1 and Dir2 are subdirectories under that directory. A class named MyClass defined under Dir2 may now be fully qualified as PathAlias.Dir1.Dir2.MyClass.
To use a namespace in code, do as follows,
Prado::using(PathAlias.Dir1.Dir2.*);
which appends the directory referred to by PathAlias.Dir1.Dir2 into PHP include path so that
classes defined under that directory may be instantiated without the namespace prefix. You may
also include an individual class definition by
Prado::using(PathAlias.Dir1.Dir2.MyClass);
5.2.4
Component Instantiation
Component instantiation means creating instances of component classes. There are two types of
component instantation: static instantiation and dynamic instantiation. The created components
are called static components and dynamic components, respectively.
60
where ComponentType refers to a class name or a type name in namespace format (e.g. System.Web.UI.TControl).
The second approach is introduced to compensate for the lack of namespace support in PHP.
5.3
5.3.1
Components: Part II
Global events
With the addition of behaviors, a more expansive event model is needed. There are two new event
types (global and dynamic events) as well as a more comprehensive behavior model that includes
class wide behaviors.
A global event is defined by all events whose name starts with fx. The event name is potentially a
method name and is thus case-insensitive. All fx events are valid as the whole fx event/method
space is global in nature. Any object may patch into any global event by defining that event as
a method. Global events have priorities just like on events; so as to be able to order the event
execution. Due to the nature of all events which start with fx being valid, in effect, every object
has every fx global event. It is simply an issue of tapping into the desired global event.
A global event that starts with fx can be called even if the object does not implement the method of
the global event. A call to a non-existing fx method will, at minimal, function and return null. If a
method argument list has a first parameter, it will be returned instead of null. This allows filtering
61
Chapter 5. Fundamentals
and chaining. fx methods do not automatically install and uninstall. To install and uninstall an
objects global event listeners, call the objects listen and unlisten methods, respectively. An
object may auto-install its global event during
global event queue because destruct is not automatically called. In the common d estructmethod,if anobjectislisteningtogloba
An object that contains a method that starts with fx will have those functions automatically
receive those events of the same name after listen is called on the object.
An object may listen to a global event without defining an fx method of the same name by adding
an object method to the global event list. For example
$component->fxGlobalCheck=$callback;
// or $component->OnClick->add($callback);
$component->attachEventHandler(fxGlobalCheck,array($object, someMethod));
5.3.2
An intra-object/behavior event is defined by methods that start with dy. Just as with fx global
events, every object has every dynamic event. Any call to a method that starts with dy will
be handled, regardless of whether it is implemented. These events are for communicating with
attached behaviors.
Dynamic events can be used in a variety of ways. They can be used to tell behaviors when a
non-behavior method is called. Dynamic events could be used as data filters. They could also be
used to specify when a piece of code is to be run, eg. should the loop process be performed on
a particular piece of data. In this way, some control is handed to the behaviors over the process
and/or data.
If there are no handlers for an fx or dy event, it will return the first parameter of the argument
list. If there are no arguments, these events will return null. If there are handlers an fx method
will be called directly within the object. Global fx events are triggered by calling raiseEvent.
For dynamic events where there are behaviors that respond to the dynamic events, a TCallChain
is developed. A call chain allows the behavior dynamic event implementations to call further
implementing behaviors within a chain.
If an object implements IDynamicMethods, all global and object dynamic events will be sent to
62
5.3.3
Behaviors
There are two types of behaviors. There are individual object behaviors and there are class wide
behaviors. Class behaviors depend upon object behaviors.
When a new class implements IBehavior or IClassBehavior or extends TBehavior or TClassBehavior,
it may be added to an object by calling the objects attachBehavior. The behaviors associated
name can then be used to enableBehavior or disableBehavior the specific behavior.
All behaviors may be turned on and off via enableBehaviors and disableBehaviors, respectively.
To check if behaviors are on or off a call to getBehaviorsEnabled will provide the variable.
Attaching and detaching whole sets of behaviors is done using attachBehaviors and detachBehaviors.
clearBehaviors removes all of an objects behaviors.
asa returns a behavior of a specific name. isa is the behavior inclusive function that acts as the
PHP operator instanceof. A behavior could provide the functionality of a specific class thus
causing the host object to act similarly to a completely different class. A behavior would then
implement IInstanceCheck to provide the identity of the different class.
Class behaviors are similar to object behaviors except that the class behavior is the implementation
for all instances of the class. A class behavior will have the object upon which is being called
be prepended to the parameter list. This way the object is known across the class behavior
implementation.
Class behaviors are attached using attachClassBehavior and detached using detachClassBehavior.
Class behaviors are important in that they will be applied to all new instances of a particular class.
In this way class behaviors become default behaviors to a new instances of a class in
construct.
Detaching a class behavior will remove the behavior from the default set of behaviors created for
an object when the object is instanced.
Class behaviors are also added to all existing instances via the global fx event mechanism. When a
new class behavior is added, the event fxAttachClassBehavior is raised and all existing instances
that are listening to this global event (primarily after listen is called) will have this new behavior
attached. A similar process is used when detaching class behaviors. Any objects listening to the
global fx event fxDetachClassBehavior will have a class behavior removed.
63
Chapter 5. Fundamentals
5.3.4
Dynamic events start with dy. This mechanism is used to allow objects to communicate with
their behaviors directly. The entire dy event space is valid. All attached, enabled behaviors that
implement a dynamic event are called when the host object calls the dynamic event. If there is no
implementation or behaviors, this returns null when no parameters are supplied and will return
the first parameter when there is at least one parameter in the dynamic event.
null == $this->dyBehaviorEvent();
5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
Dynamic events can be chained together within behaviors to allow for data filtering. Dynamic
events are implemented within behaviors by defining the event as a method.
$param1 += 13;
return $callchain->dyBehaviorEvent($param1);
}
}
This implementation of a behavior and dynamic event will flow through to the next behavior implementing the dynamic event. The first parameter is always return when it is supplied. Otherwise
a dynamic event returns null.
In the case of a class behavior, the object is also prepended to the dynamic event.
$param1 += $hostobject->getNumber();
return $callchain->dyBehaviorEvent($param1);
}
}
When calling a dynamic event, only the parameters are passed. The host object and the call chain
are built into the framework.
64
5.4. Controls
5.3.5
Given that all global fx events and dynamic dy events are valid and operational, there is a
mechanism for catching events called that are not implemented (similar to the built-in PHP method
call). When a dynamic or global event is called but a behavior does not implement it, yet
desires to know when an undefined dynamic event is run, the behavior implements the interface
IDynamicMethods and method dycall.
In the case of dynamic events,
When a global event is raised, via raiseEvent, the method is the event name and the parameters
are supplied.
When implemented, this catch-all mechanism is called for event global event event when implemented outside of a behavior. Within a behavior, it will also be called when the object to which the
behavior is attached calls any unimplemented dynamic event. This is the fall-back mechanism for
informing a class and/or behavior of when an global and/or undefined dynamic event is executed.
5.4
Controls
5.4.1
Control Tree
Controls are related to each other via parent-child relationship. Each parent control can have one or
several child controls. A parent control is in charge of the state transition of its child controls. The
rendering result of the child controls are usually used to compose the parent controls presentation.
The parent-child relationship brings together controls into a control tree. A page is at the root of
the tree, whose presentation is returned to the end-users.
The parent-child relationship is usually established by the framework via templates. In code, you
may explicitly specify a control as a child of another using one of the following methods,
$parent->Controls->add($child);
$parent->Controls[]=$child;
65
Chapter 5. Fundamentals
where the property Controls refers to the child control collection of the parent.
5.4.2
Control Identification
Each control has an ID property that can be uniquely identify itself among its sibling controls.
In addition, each control has a UniqueID and a ClientID which can be used to globally identify
the control in the tree that the control resides in. UniqueID and ClientID are very similar. The
former is used by the framework to determine the location of the corresponding control in the tree,
while the latter is mainly used on the client side as HTML tag IDs. In general, you should not
rely on the explicit format of UniqueID or ClientID.
5.4.3
Naming Containers
Each control has a naming container which is a control creating a unique namespace for differentiating between controls with the same ID. For example, a TRepeater control creates multiple
items each having child controls with the same IDs. To differentiate these child controls, each
item serves as a naming container. Therefore, a child control may be uniquely identified using its
naming containers ID together with its own ID. As you may already have understood, UniqueID
and ClientID rely on the naming containers.
A control can serve as a naming container if it implements the INamingContainer interface.
5.4.4
HTTP is a stateless protocol, meaning it does not provide functionality to support continuing
interaction between a user and a server. Each request is considered as discrete and independent
of each other. A Web application, however, often needs to know what a user has done in previous
requests. People thus introduce sessions to help remember such state information.
PRADO borrows the viewstate and controlstate concept from Microsoft ASP.NET to provides
additional stateful programming mechanism. A value storing in viewstate or controlstate may be
available to the next requests if the new requests are form submissions (called postback) to the
same page by the same user. The difference between viewstate and controlstate is that the former
can be disabled while the latter cannot.
Viewstate and controlstate are implemented in TControl. They are commonly used to define
66
5.5. Pages
various properties of controls. To save and retrieve values from viewstate or controlstate, use
following methods,
$this->getViewState(Name,$defaultValue);
$this->setViewState(Name,$value,$defaultValue);
$this->getControlState(Name,$defaultValue);
$this->setControlState(Name,$value,$defaultValue);
where $this refers to the control instance, Name refers to a key identifying the persistent value,
$defaultValue is optional. When retrieving values from viewstate or controlstate, if the corresponding key does not exist, the default value will be returned.
5.5
Pages
Pages are top-most controls that have no parent. The presentation of pages are directly displayed
to end-users. Users access pages by sending page service requests.
Each page can have a template file. The file name suffix must be .page. The file name (without
suffix) is the page name. PRADO will try to locate a page class file under the directory containing
the page template file. Such a page class file must have the same file name (suffixed with .php) as
the template file. If the class file is not found, the page will take class TPage.
5.5.1
PostBack
A form submission is called ipostback/i if the submission is made to the page containing the
form. Postback can be considered an event happened on the client side, raised by the user. PRADO
will try to identify which control on the server side is responsible for a postback event. If one is
determined, for example, a TButton, we call it the postback event sender which will translate
the postback event into some specific server-side event (e.g. OnClick and OnCommand events for
TButton). br/ TPage has a IsPostBack property exposing whether the current request being
handled is the first request for this page or the consequence of a postback.
67
Chapter 5. Fundamentals
5.5.2
CallBack
A icallback/i is a special form submission that, instead of requiring a full page reload on the
browser, gets executed in the background through an ajax call. So, a callback is considered a
postback too, but not vice versa. br/ A callback is handled as a normal postback but, instead of
re-rendering the entire page, only the specific changes occured on the page gets sent back to the
client and merged with the current browser page. A typical callback response consists of:
1. one or more pieces of html code that will replace existing content on the page;
2. the javascript instructions needed to update the page;
3. some specific fields used by prado to mantain the pagestate and add the needed external
resources (stylesheets, javascript files, etc..).
TPage has a IsCallBack property exposing whether the current request being handled is the
consequence of a callback. Since a callback is also a postback, both IsPostBack and IsCallBack
are true during callback requests.
5.5.3
Page Lifecycles
Understanding the page lifecycles is crucial to grasp PRADO programming. Page lifecycles refer
to the state transitions of a page when serving this page to end-users. They can be depicted in the
following statechart,
5.6
Modules
68
5.6. Modules
There are three core modules that are loaded by default whenever an application runs. They are
request module, response module, and error handler module. In addition, session module is loaded
when it is used in the application. PRADO provides default implementation for all these modules.
Custom modules may be configured or developed to override or supplement these core modules.
5.6.1
Request Module
Request module represents provides storage and access scheme for user request sent via HTTP. User
request data comes from several sources, including URL, post data, session data, cookie data, etc.
These data can all be accessed via the request module. By default, PRADO uses THttpRequest
as request module. The request module can be accessed via the Request property of application
and controls.
5.6.2
Response Module
Response module implements the mechanism for sending output to client users. Response module
may be configured to control how output are cached on the client side. It may also be used to send
cookies back to the client side. By default, PRADO uses THttpResponse as response module. The
response module can be accessed via the Response property of application and controls.
5.6.3
Session Module
Session module encapsulates the functionalities related with user session handling. Session module
is automatically loaded when an application uses session. By default, PRADO uses THttpSession
as session module, which is a simple wrapper of the session functions provided by PHP. The session
module can be accessed via the Session property of application and controls.
5.6.4
Error handler module is used to capture and process all error conditions in an application. PRADO
uses TErrorHandler as error handler module. It captures all PHP warnings, notices and exceptions,
and displays in an appropriate form to end-users. The error handler module can be accessed via
the ErrorHandler property of the application instance.
69
Chapter 5. Fundamentals
5.6.5
Custom Modules
PRADO is released with a few more modules besides the core ones. They include caching modules
(TSqliteCache and TMemCache), user management module (TUserManager), authentication and
authorization module (TAuthManager), etc.
When TPageService is requested, it also loads modules specific for page service, including asset
manager (TAssetManager), template manager (TTemplateManager), theme/skin manager (TThemeManager).
Custom modules and core modules are all configurable via configurations.
5.7
Services
A service is an instance of a class implementing the IService interface. Each kind of service
processes a specific type of user requests. For example, the page service responds to users requests
for PRADO pages.
A service is uniquely identified by its ID property. By default when THttpRequest is used as the
request module, GET variable names are used to identify which service a user is requesting. If a
GET variable name is equal to some service ID, the request is considered for that service, and the
value of the GET variable is passed as the service parameter. For page service, the name of the GET
variable must be page. For example, the following URL requests for the Fundamentals.Services
page,
http://hostname/index.php?page=Fundamentals.Services
Developers may implement additional services for their applications. To make a service available,
configure it in application configurations.
5.7.1
Page Service
PRADO implements TPageService to process users page requests. Pages are stored under a
directory specified by the BasePath property of the page service. The property defaults to Pages
directory under the application base path. You may change this default by configuring the service
in the application configuration.
Pages may be organized into subdirectories under the BasePath. In each directory, there may be a
70
5.8. Applications
page configuration file named config.xml, which contains configurations effective only when a page
under that directory or a sub-directory is requested. For more details, see the page configuration
section.
Service parameter for the page service refers to the page being requested. A parameter like
Fundamentals.Services refers to the Services page under the <BasePath>/Fundamentals directory. If such a parameter is absent in a request, a default page named Home is assumed. Using
THttpRequest as the request module (default), the following URLs will request for Home, About
and Register pages, respectively,
http://hostname/index.php
http://hostname/index.php?page=About
http://hostname/index.php?page=Users.Register
where the first example takes advantage of the fact that the page service is the default service and
Home is the default page.
More advanced url routes, like masking real page names and permitting the use of dynamic parameters can be created using the Url mapping module.
5.8
Applications
71
Chapter 5. Fundamentals
where the method run() starts the application to handle user requests.
5.8.1
Directory Organization
A minimal PRADO application contains two files: an entry file and a page template file. They
must be organized as follows,
A product PRADO application usually needs more files. It may include an application configuration file named application.xml under the application base path protected. The pages may be
organized in directories, some of which may contain page configuration files named config.xml.
Fore more details, please see configurations section.
5.8.2
Application Deployment
Deploying a PRADO application mainly involves copying directories. For example, to deploy the
above minimal application to another server, follow the following steps,
1. Copy the content under wwwroot to a Web-accessible directory on the new server.
72
5.8. Applications
2. Modify the entry script file index.php so that it includes correctly the prado.php file.
3. Remove all content under assets and runtime directories and make sure both directories
are writable by the Web server process.
5.8.3
Application Lifecycles
Like page lifecycles, an application also has lifecycles. Application modules can register for the
lifecycle events. When the application reaches a particular lifecycle and raises the corresponding
event, the registered module methods are invoked automatically. Modules included in the PRADO
release, such as TAuthManager, are using this way to accomplish their goals.
The application lifecycles can be depicted as follows,
73
Chapter 5. Fundamentals
74
5.8. Applications
75
Chapter 5. Fundamentals
76
Chapter 6
Configurations
6.1
Configuration Overview
PRADO uses configurations to glue together components into pages and applications. There are
application configurations, page configurations, and templates.
Application and page configurations are optional if default values are used. Templates are mainly
used by pages and template controls. They are optional, too.
6.2
Templates: Part I
Templates are used to specify the presentational layout of controls. A template can contain static
text, components, or controls that contribute to the ultimate presentation of the associated control. By default, an instance of TTemplateControl or its subclass may automatically load and
instantiate a template from a file whose name is the same as the control class name. For page
templates, the file name suffix must be .page; for other regular template controls, the suffix is
.tpl.
The template format is like HTML, with a few PRADO-specifc tags, including component tags,
template control tags, comment tags, dynamic content tags, and dynamic property tags.
77
Chapter 6. Configurations
6.2.1
Component Tags
A component tag specifies a component as part of the body content of the template control. If the
component is a control, it usually will become a child or grand child of the template control, and
its rendering result will be inserted at the place where it is appearing in the template.
The format of a component tag is as follows,
<com:ComponentType PropertyName="PropertyValue" ... EventName="EventHandler" ...>
body content
</com:ComponentType>
ComponentType can be either the class name or the dotted type name (e.g. System.Web.UI.TControl)
of the component. PropertyName and EventName are both case-insensitive. PropertyName can be
a property or subproperty name (e.g. Font.Name). Note, PropertyValue will be HTML-decoded
when assigned to the corresponding property. Content enclosed between the opening and closing
component tag are normally treated the body of the component.
It is required that component tags nest properly with each other and an opening component tag
be paired with a closing tag, similar to that in XML.
The following template shows a component tag specifying the Text property and OnClick event
of a button control,
<com:TButton Text="Register" OnClick="registerUser" />
Note, property names and event names are all case-insensitive, while component type names are
case-sensitive. Event names always begin with On.
Also note, initial values for properties whose name ends with Template are specially processed. In
particular, the initial values are parsed as TTemplate objects. The ItemTemplate property of the
TRepeater control is such an example.
To facilitate initializing properties with big trunk of data, the following property initialization
tag is introduced. It is equivalent to ...PropertyName="PropertyValue"... in every aspect.
Property initialization tags must be directly enclosed between the corresponding opening and
closing component tag.
<prop:PropertyName>
78
Since version 3.1.0, the property initialization tag can also be used to initialize a set of subproperties who share the same parent property.
Component IDs
When specified in templates, component ID property has special meaning in addition to its normal
property definition. A component tag specified with an ID value in template will register the
corresponding component to the template owner control. The component can thus be directly
accessed from the template control with its ID value. For example, in Home pages template, the
following component tag
<com:TTextBox ID="TextBox" Text="First Name" />
6.2.2
A template control tag is used to configure the initial property values of the control owning the
template. Its format is as follows,
<%@ PropertyName="PropertyValue" ... %>
79
Chapter 6. Configurations
6.2.3
Comment Tags
Comment tags are used to put in a template developer comments that will not display to end-users.
Contents enclosed within a comment tag will be treated as raw text strings and PRADO will not
attempt to parse them. Comment tags cannot be used within property values. The format of
comment tags is as follows,
6.2.4
Include Tags
Since version 3.0.5, PRADO starts to support external template inclusion. This is accomplished
via include tags, where external template files are specified in namespace format and their file
name must be terminated as .tpl.
<%include path.to.templateFile %>
External templates will be inserted at the places where the include tags occur in the base template.
Note, nested template inclusion is not supported, i.e., you cannot have include tags in an external
template.
6.3
6.3.1
Templates: Part II
Dynamic Content Tags
Dynamic content tags are introduced as shortcuts to some commonly used component tags. These
tags are mainly used to render contents resulted from evaluating some PHP expressions or state-
80
Expression Tags
An expression tag represents a PHP expression that is evaluated when the template control is in
PreRender stage. The expression evaluation result is inserted at the place where the tag resides in
the template. The context (namely $this) of the expression is the control owning the template.
The format of an expression tag is as follows,
For example, the following expression tag will display the current page title at the place,
Statement Tags
Statement tags are similar to expression tags, except that statement tags contain PHP statements
rather than expressions. The output of the PHP statements (using for example echo or print in
PHP) are displayed at the place where the statement tag resides in the template. The context
(namely $this) of the statements is the control owning the template. The format of statement
tags is as follows,
<%%
PHP Statements
%>
The following example displays the current time in Dutch at the place,
<%%
setlocale(LC_ALL, nl_NL);
echo strftime("%A %e %B %Y",time());
%>
81
Chapter 6. Configurations
Databind Tags
Databind tags are similar to expression tags, except that the expressions are evaluated only when
a dataBind() call is invoked on the controls representing the databind tags. The context (namely
$this) of a databind expression is the control owning the template. The format of databind tags
is as follows,
<%# PhpExpression %>
Parameter Tags
Parameter tags are used to insert application parameters at the place where they appear in the
template. The format of parameter tags is as follows,
<%$ ParameterName %>
Note, application parameters are usually defined in application configurations or page directory
configurations. The parameters are evaluated when the template is instantiated.
Asset Tags
Asset tags are used to publish private files and display the corresponding the URLs. For example,
if you have an image file that is not Web-accessible and you want to make it visible to end-users,
you can use asset tags to publish this file and show the URL to end-users so that they can fetch
the published image.
The format of asset tags is as follows,
<%~ LocalFileName %>
where LocalFileName refers to a file path that is relative to the directory containing the current
template file. The file path can be a single file or a directory. If the latter, the content in the whole
directory will be made accessible by end-users.
BE VERY CAUTIOUS when you are using asset tags as it may expose to end-users files that you
probably do not want them to see.
82
<%[string]%>
where string will be translated to different languages according to the end-users language preference. Localization tags are in fact shortcuts to the function call Prado::localize(string).
URL Tags
URL tags are used to insert the relative web url path to the Prado application in the template.
You can use it in the following format:
If your Prado application is deployed on http://localhost/pradoapp/, the tag above will produce
/pradoapp/image.jpg. This tag will help you to use the correct file path even with UrlFormat
set to Path, or if you are using url mappings.
6.4
6.4.1
Dynamic property tags are very similar to dynamic content tags, except that they are applied to
component properties. The purpose of dynamic property tags is to allow more versatile component
property configuration. Note, you are not required to use dynamic property tags because what
can be done using dynamic property tags can also be done in PHP code. However, using dynamic
property tags bring you much more convenience at accomplishing the same tasks. The basic usage
of dynamic property tags is as follows,
83
Chapter 6. Configurations
where you may enclose DynamicPropertyTag within single or double quotes for better readability.
Like dynamic content tags, we have expression tags, databind tags, parameter tags, asset tags and
localization tags. (Note, there is no statement tag here.)
Expression Tags
An expression tag represents a PHP expression that is evaluated when the control is in PreRender
stage. The expression evaluation result is assigned to the corresponding component property. The
format of expression tags is as follows,
<%= PhpExpression %>
In the expression, $this refers to the control owning the template. The following example specifies
a TLabel control whose Text property is initialized as the current page title when the TLabel
control is being constructed,
<com:TLabel Text=<%= $this->Page->Title %> />
Databind Tags
Databind tags are similar to expression tags, except that they can only be used with control
properties and the expressions are evaluated only when a dataBind() call is invoked on the controls
represented by the component tags. In the expression, $this refers to the control owning the
template. Databind tags do not apply to all components. They can only be used for controls.
The format of databind tags is as follows,
<%# PhpExpression %>
Since v3.0.2, expression tags and databind tags can be embedded within static strings. For example,
you can write the following in a template,
<com:TLabel>
<prop:Text>
Today is <%= date(F d, Y,time()) %>.
84
Previously, you would have to use a single expression with string concatenations to achieve the
same effect.
Parameter Tags
Parameter tags are used to assign application parameter values to the corresponding component
properties. The format of parameter tags is as follows,
Note, application parameters are usually defined in application configurations or page directory
configurations. The parameters are evaluated when the template is instantiated.
Asset Tags
Asset tags are used to publish private files and assign the corresponding the URLs to the component
properties. For example, if you have an image file that is not Web-accessible and you want to make
it visible to end-users, you can use asset tags to publish this file and show the URL to end-users
so that they can fetch the published image. The asset tags are evaluated when the template is
instantiated.
The format of asset tags is as follows,
where LocalFileName refers to a file path that is relative to the directory containing the current
template file. The file path can be a single file or a directory. If the latter, the content in the whole
directory will be made accessible by end-users.
BE VERY CAUTIOUS when you are using asset tags as it may expose to end-users files that you
probably do not want them to see.
85
Chapter 6. Configurations
Localization Tags
Localization tags represent localized texts. They are in the following format,
<%[string]%>
where string will be translated to different languages according to the end-users language preference. The localization tags are evaluated when the template is instantiated. Localization tags
are in fact shortcuts to the function call Prado::localize(string).
6.5
Application Configurations
Application configurations are used to specify the global behavior of an application. They include
specification of path aliases, namespace usages, module and service configurations, and parameters.
Configuration for an application is stored in an XML file named application.xml, which should
be located under the application base path. Its format is shown in the following. Complete
specification of application configurations can be found in the DTD and XSD files.
</modules>
<parameters>
<parameter id="ParameterID" class="ParameterClass" PropertyName="PropertyValue" ... />
</parameters>
<include file="path.to.extconfig" when="PHP expression" />
<services>
<service id="ServiceID" class="ServiceClass" PropertyName="PropertyValue" ... />
</services>
</application>
86
Note, if the value attribute is not specified, the whole parameter XML node (of type
TXmlElement) will be returned as the parameter value. In addition, the System.Util.TParameterModule
module provides a way to load parameters from an external XML file. See more details in
its API documentation.
The <include> element allows one to include external configuration files. It has been introduced since v3.1.0. The file attribute specifies the external configuration file in namespace
format. The extension name of the file must be .xml. The when attribute contains a PHP
expression and is optional (defaults to true). Only when the expression evaluates true, will
the external configuration file be included. The context of the expression is the application,
i.e., $this in the expression would refer to the application object.
The <services> element is similar to the <modules> element. It mainly specifies the services
provided by the application. Within a <service> element, one can have any of the above
elements. They will be effective only when the corresponding service is being requested.
87
Chapter 6. Configurations
An external configuration file has the same format as described above. Although the name of
the root element does not matter, it is recommended to be <configuration>. External configurations will append to the main configuration. For example, if a path alias is specified in an
external configuration, it will become available in addition to those aliases specified in the main
configuration.
By default without explicit configuration, a PRADO application will load a few core modules,
such as THttpRequest, THttpResponse, etc. It will also provide the TPageService as a default
service. Configuration and usage of these modules and services are covered in individual sections
of this tutorial. Note, if your application takes default settings for these modules and service, you
do not need to provide an application configuration. However, if these modules or services are not
sufficient, or you want to change their behavior by configuring their property values, you will need
an application configuration.
By default PRADO instanciates all modules defined in the application configuration at the beginning of the application lifecycle. This can hit the application performance if you have a lot of
modules defined but not used at every request. Since version 3.2.2 you can set the lazy property
on modules defined in the application configuration to enable the lazy loading of that module.
<modules>
<module id="ModuleID" class="ModuleClass" lazy="true" PropertyName="PropertyValue" ... />
</modules>
A module with the lazy property set wont be instanciated until the first time it gets actually
used by the application:
Since version 3.2 the application configuration can be stored in PHP array format in a file named
application.php. The format of the configuration file is exactly the same of its XML counterpart,
but following the PHP syntax.
<?php
return array(
application => array(
PropertyName => PropertyValue
88
The use of a PHP application configuration must be defined in the TApplication constructor,
tipically located in the index.php entry script:
$application=new TApplication(protected,false,TApplication::CONFIG_TYPE_PHP);
$application->run();
6.6
Page Configurations
Page configurations are mainly used by TPageService to modify or append the application configuration. As the name indicates, a page configuration is associated with a directory storing some
page files. It is stored as an XML file named config.xml.
When a user requests a page stored under <BasePath>/dir1/dir2, the TPageService will try to
parse and load config.xml files under <BasePath>, <BasePath>/dir1 and <BasePath>/dir1/dir2.
Paths, modules, and parameters specified in these configuration files will be appended or merged
into the existing application configuration. Here <BasePath> is as defined in page service.
The format of a page configuration file is as follows,
89
Chapter 6. Configurations
<configuration>
<paths>
<alias id="AliasID" path="AliasPath" />
<using namespace="Namespace" />
</paths>
<modules>
<module id="ModuleID" class="ModuleClass"
</modules>
<parameters>
<parameter id="ParameterID" class="ParameterClass" PropertyName="PropertyValue" ... />
</parameters>
<include file="path.to.extconfig" when="PHP expression" />
<authorization>
<allow pages="PageID1,PageID2" users="User1,User2" roles="Role1,Role2" verb="get" />
<deny pages="PageID1,PageID2" users="User1,User2" roles="Role1,Role2" verb="post" />
</authorization>
<pages PropertyName="PropertyValue" ...>
<page id="PageID" PropertyName="PropertyValue" ... />
</pages>
</configuration>
The <paths>, <modules>, <parameters> and <include> are similar to those in an application
configuration. The <authorization> element specifies the authorization rules that apply to the
current page directory and all its subdirectories. For more details, see authentication and authorization section. The <pages> element specifies the initial values for the properties of pages.
Each <page> element specifies the initial property values for a particular page identified by the id
attribute. Initial property values given in the <pages> element apply to all pages in the current
directory and all its subdirectories.
Complete specification of page configurations can be found in the DTD and XSD files.
Since version 3.1.1, the id attribute in the page element can be a relative page path pointing
to a page in the subdirectory of the directory containing the page configuration. For example,
id="admin.Home" refers to the Home page under the admin directory. The id attribute can also
contain wildcard * to match all pages under the specified directory. For example, id="admin.*"
refers to all pages under the admin directory and its subdirectories. This enhancement allows
developers to centralize their page configurations (e.g. put all page initializations in the aplication
configuration or the root page configuration.)
90
6.7
The above example is part of the application configuration of the blog demo in the PRADO release.
It enables recognition of the following URL formats:
/index.php/post/123 is recognized as /index.php?page=Posts.ViewPost&id=123
/index.php/archive/200605 is recognized as /index.php?page=Posts.ListPost&time=200605
/index.php/category/2 is recognized as /index.php?page=Posts.ListPost&cat=2
The ServiceParameter and ServiceID (the default ID is page) set the service parameter and
service ID, respectively, of the Request module. The service parameter for the TPageService
service is the Page class name, e.g., for an URL index.php?page=Home, page is the service
ID and the service parameter is Home. Other services may use the service parameter and ID
differently. See Services for further details.
91
Chapter 6. Configurations
Info: The TUrlMapping must be configured before the request module resolves the request.
This means delcaring the TUrlMapping outside of the <services> element in the application
configuration. Specifying the mappings in the per directory config.xml is not supported.
6.7.1
TUrlMapping enables recognition of customized URL formats based on a list prespecified of URL
patterns. Each pattern is specified in a <url> tag.
The Pattern and Parameters attribute values are regular expression patterns that determine the
mapping criteria. The Pattern property takes a regular expression with parameter names enclosed
between a left brace { and a right brace }. The patterns for each parameter can be set using
Parameters attribute collection. For example,
<url ServiceParameter="ArticleView" pattern="articles/{year}/{month}/{day}"
parameters.year="\d{4}" parameters.month="\d{2}" parameters.day="\d+" />
The example is equivalent to the following regular expression (it uses the named group feature
in regular expressions available in PHP):
<url ServiceParameter="ArticleView"><![CDATA[
#articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)#u
]]></url>
In the above example, the pattern contains 3 parameters named year, month and day. The
pattern for these parameters are, respectively, {
. (1 or more
.4} (4 digits), {
.2} (2 digits) and +
digits). Essentially, the Parameters attribute name and values are used as substrings in replacing
the placeholders in the Pattern string to form a complete regular expression string.
For simple (not parameterized) regular expressions you can use the RegularExpression property:
<url ServiceParameter="ArticleView"
RegularExpression="/articles\/best-of/u" />
92
class, the pattern is matched against the PATHI N F Opartof theU RLonly.F orexample, onlythe/articles/2006/07/2
The mapped request URL is equivalent to index.php?page=ArticleView&year=2006&month=07&day=21.
The request parameter values are available through the standard Request object. For
example, $this->Request[year].
The URL mapping are evaluated in order they are placed and only the first pattern
that matches the URL will be used. Cascaded mapping can be achieved by placing the
URL mappings in particular order. For example, placing the most specific mappings
first.
Since version 3.1.4, Prado also provides wildcard patterns to use friendly URLs for
a bunch of pages in a directory with a single rule. Therefore you can use the {*}
wildcard in your pattern to let Prado know, where to find the ServiceID in your
request URL. You can also specify parameters with these patterns if a lot of pages
share common parameters.
93
Chapter 6. Configurations
To make configuration of friendly URLs for multiple pages even easier, you can also
use UrlFormat="Path" in combination with wildcard patterns. In fact, this feature only
is available in combination with wildcard rules:/P
Parameters will get appended to the specified patterns as name/value pairs, separated
by a /. (You can change the separator character with UrlParamSeparator.)
/index.php/list/cat/15/month/12 is recognized as /index.php?page=list&cat=15&month=12
/index.php/edit/id/12 is recognized as /index.php?page=list&id=12
/index.php/show/name/foo is recognized as /index.php?page=show&name=foo
/index.php/admin/edit/id/12 is recognized as /index.php?page=user.admin.edit&id=12
6.7.2
Since version 3.1.1, TUrlMapping starts to support constructing customized URLs based
on the provided patterns. To enable this feature, set TUrlMapping.EnableCustomUrl to
true.
work will be delegated to a matching TUrlMappingPattern instance. It replaces the parameters in the pattern with the corresponding GET variables passed to constructUrl().
A matching pattern is one whose ServiceID and ServiceParameter properties are the
same as those passed to constructUrl() and whose named parameters are found in
the GET variables. For example, constructUrl(Posts.ListPost,array(cat=>2)) will
use the third pattern in the above example.
By default, TUrlMapping will construct URLs prefixed with the currently requesting
PHP script path, such as /path/to/index.php/article/3. Users may change this behavior by explicitly specifying the URL prefix through its UrlPrefix property. For
example, if the Web server configuration treats index.php as the default script, we can
set UrlPrefix as /path/to and the constructed URL will look like /path/to/article/3.
94
Note: If you use constructUrl() with string parameters that contain slashes
(/) they will get encoded to %2F. By default most Apache installations give
a 404 Not found if a URL contains a %2F. You can add AllowEncodedSlashes
On to your VirtualHost configuration to resolve this. (Available since Apache
2.0.46).
95
Chapter 6. Configurations
96
Chapter 7
Standard Controls
Standard controls are the most basic controls in PRADO. They can represent a simple
html tag like an input or even more comple controls like a date picker or a WYSIWYG text area. Standard controls are in the System.Web.UI.WebControls namespace.
TAccordion displays an accordion consisting of multiple views. At any time, only
one view is visible.
TButton represents a click button on a Web page. It is mainly used to trigger
page postback.
TCaptcha displays a CAPTCHA to keep spammers from signing up for certain
accounts online.
TCheckBox represents a checkbox on a Web page. It can be used to collect
two-state user input.
TClientScript adds javascript code to the page.
TColorPicker represents an input field taking color values via a color dialog.
97
98
99
7.2
TAccordion
ActiveViewIndex - the zero-based integer index of the view in the view collection.
ActiveViewID - the text ID of the visible view.
ActiveView - the visible view instance.
If both ActiveViewIndex and ActiveViewID are set, the latter takes precedence.
TAccordion uses CSS to specify the appearance of the accordion headers and panels.
By default, an embedded CSS file will be published which contains the default CSS
for TAccordion. You may also use your own CSS file by specifying the CssUrl property.
The following properties specify the CSS classes used for elements in a TAccordion:
CssClass - the CSS class name for the outer-most div element (defaults to accordion);
HeaderCssClass - the CSS class name for nonactive accordion div elements (defaults to accordion-header);
ActiveHeaderCssClass - the CSS class name for the active accordion div element
(defaults to accordion-header-active);
ViewCssClass - the CSS class for the div element enclosing view content (defaults
to accordion-view);
100
7.3. TButton
<com:TAccordion>
<com:TAccordionView Caption="View 1">
content for view 1
</com:TAccordionView>
<com:TAccordionView Caption="View 2">
content for view 2
</com:TAccordionView>
<com:TAccordionView Caption="View 3">
content for view 3
</com:TAccordionView>
</com:TAccordion>
Controls.Samples.TAccordion.Home Demo
7.3
TButton
by Text property. A button is used to submit data to a page. TButton raises two
server-side events, OnClick and OnCommand, when it is clicked on the client-side. The
difference between OnClick and OnCommand events is that the latter event is bubbled
up to the buttons ancestor controls. An OnCommand event handler can use CommandName
and CommandParameter associated with the event to perform specific actions.
Clicking on button can trigger form validation, if CausesValidation is true. And the
validation may be restricted within a certain group of validator controls according to
ValidationGroup.
Controls.Samples.TButton.Home Demo
TODO: custom attributes
7.4
TCaptcha
101
102
7.5. TCheckBox
<com:TCaptcha ID="Captcha" />
<com:TTextBox ID="Input" />
<com:TCaptchaValidator CaptchaControl="Captcha"
ControlToValidate="Input"
ErrorMessage="You are challenged!" />
Controls.Samples.TCaptcha.Home Demo
7.5
TCheckBox
7.6
TClientScript
7.6.1
103
7.6.2
Custom Javascript files can be register using the ScriptUrl property. The following
example includes the Javascript file test.js to the page. In this case, the file test.js
is relative the current template you are using. Since the property value is dynamic
asset tag, the file test.js will be published automatically, that is, the file will be
copied to the assets directory if necessary.
104
7.7. TColorPicker
You can include Javascript files from other servers by specifying the full URL string
in the ScriptUrl property.
7.6.3
Any content within the TClientScript control tag will be considered as Javascript code
and will be rendered where it is declared.
7.7
TColorPicker
105
7.8
TConditional
Controls.Samples.TConditional.Home Demo
7.9
TDatePicker
106
7.9. TDatePicker
automatically entered into the text box. The format of the date string displayed
in the text box is determined by the DateFormat property. Valid formats are the
combination of the following tokens:
Character
--------------------------------------------------------------------d
day digit
dd
month digit
MM
MMM
MMMM
yy
2 digit year
yyyy
4 digit year
---------------------------------------------------------------------
The date of the date picker can be set using the Date or Timestamp properties. The Date
property value must be in the same format as the pattern specified in the DateFormat
property. The Timestamp property only accepts integers such as the Unix timestamp.
TDatePicker has three Mode to show the date picker popup.
Basic - Only shows a text input, focusing on the input shows the date picker.
Button - Shows a button next to the text input, clicking on the button shows the
date, button text can be by the ButtonText property.
ImageButton - Shows an image next to the text input, clicking on the image shows
the date picker, image source can be change through the ImageUrl property.
The CssClass property can be used to override the CSS class name for the date picker
panel. The CalendarStyle property changes the overall calendar style. The following
CalendarStyle values are available:
default - The default calendar style.
The InputMode property can be set to TextBox or DropDownList with default as
TextBox. In DropDownList mode, in addition to the popup date picker, three drop
107
The
starting day of the week can be changed by the FirstDayOfWeek property, with 0 as
Sunday, 1 as Monday, etc.
Note 1: If the InputMode is TextBox, the DateFormat should only NOT contain MMM
or MMMM patterns. The server side date parser will not be able to determine the correct
date if MMM or MMMM are used. When InputMode equals DropDownList, all patterns
can be used.
Note 2: When the TDatePicker is used together with a validator, the DateFormat property of the validator must be equal to the DateFormat of the TDatePicker AND must set
DataType=Date on the validator to ensure correct validation. See TCompareValidator, TDataTypeValidator and TRangeValidator for details.
Controls.Samples.TDatePicker.Home Demo
7.10
TExpression
108
7.11. TFileUpload
<com:TExpression Expression="$this->Page->Title" />
Be aware, since TExpression allows execution of arbitrary PHP code, in general you
should not use it to evaluate expressions submitted by your application users.
Controls.Samples.TExpression.Home Demo
7.11
TFileUpload
entered into the field will be treated as the (local) name of the file that is uploaded
to the server.
TFileUpload raises an OnFileUpload event when it is post back. The property HasFile
indicates whether the file upload is successful or not. If successful, the uploaded file
may be saved on the server by calling saveAs() method.
The following properties give the information about the uploaded file:
If the file upload is unsuccessful, the property ErrorCode gives the error code describing the cause of failure. See PHP documentation for a complete explanation of the
possible error codes.
Controls.Samples.TFileUpload.Home Demo
109
7.12
TFlushOutput
You can specify whether you want to keep buffering of the output (if it was enabled)
till the next occourence of a ttcom: TFlushOutput //tt or the end of the page
rendering, or stop buffering, by using the ContinueBuffering property.
7.13
THead
110
7.14. THiddenField
<com:THead>
<com:TMetaTag HttpEquiv="Pragma" Content="no-cache" />
<com:TMetaTag Name="keywords" Content="Prado" />
</com:THead>
Note, TPage has a property Head that refers to the THead control currently on the page.
A page can have at most one THead control. Although not required, it is recommended
to place a THead on your page. Without a THead on the page, stylesheets and javascripts
in the current page theme will not be rendered.
7.14
THiddenField
7.15
THtmlArea
111
The client-side visual editing capability is supported by Internet Explorer 5.0+ for
Windows and Gecko-based browser. If the browser does not support the visual editing,
a traditional textarea will be displayed.
<pre>
Controls.Samples.THtmlArea.Home Demo
7.16
THtmlArea4
112
7.17. THyperLink
</com:THtmlArea>
Controls.Samples.THtmlArea4.Home Demo
7.17
THyperLink
7.18
TImage
7.19
TImageButton
113
7.20
TImageMap
Controls.Samples.TImageMap.Home Demo
7.21
TInlineFrame
114
7.22. TJavascriptLogger
The appearance of a TInlineFrame may be customized with the following properties,
in addition to those inherited from TWebControl.
Align - the alignment of the frame.
DescriptionUrl - the URI of a long description of the frames contents.
MarginWidth and MarginHeight - the number of pixels to use as the left/right
margins and top/bottom margins, respectively.
ScrollBars - whether scrollbars are provided for the inline frame. By default, it
is Auto, meaning the scroll bars appear as needed. Setting it as None or Both to
explicitly hide or show the scroll bars.
The following samples show TInlineFrame with different property settings. The Google
homepage is used as the frame content.
Controls.Samples.TInlineFrame.Home Demo
7.22
TJavascriptLogger
Then, the client-side Javascript may contain the following statements. When they are
executed, they will appear in the logger window.
Logger.info(something happend);
Logger.warn(A warning);
Logger.error(This is an error);
Logger.debug(debug information);
115
7.23
TKeyboard
7.24
TLabel
116
7.25. TLinkButton
Controls.Samples.TLabel.Home Demo
7.25
TLinkButton
7.26
TLiteral
7.27
TMarkdown
117
Controls.Samples.TMarkdown.Home Demo
118
7.28. TMultiView
7.28
TMultiView
<com:TMultiView>
<com:TView>
view 1 content
</com:TView>
<com:TView>
view 2 content
</com:TView>
</com:TMultiView>
TMultiView responds to the following command events to manage the visibility of its
views.
NextView : switch to the next view (with respect to the currently active view).
PreviousView : switch to the previous view (with respect to the currently active
view).
SwitchViewID : switch to a view by its ID path. The ID path is fetched from the
command parameter.
SwitchViewIndex : switch to a view by its zero-based index in the Views collection.
The index is fetched from the command parameter.
Upon postback, if the active view index is changed, TMultiView will raise an OnActiveViewChanged
event.
119
7.29
TOutputCache
where content to be cached can be static text and/or template tags. If the latter, the
rendering results of the template tags will be cached. You can place one or several
TOutputCache on a single template and they can be nested.
Note: TOutputCache stores cached content via PRADO cache modules (e.g.
TSqliteCache) and thus requires at least one cache module loaded when the
application runs.
The validity of the cached content is determined based on two factors: the Duration
and the cache dependency. The former specifies the number of seconds that the data
can remain valid in cache (defaults to 60s), while the latter specifies conditions that
the cached data depends on. If a dependency changes (e.g. relevant data in DB are
updated), the cached data will be invalidated and discarded.
120
7.30. TPager
There are two ways to specify cache dependency. One may write event handlers
to respond to the OnCheckDependency event and set the event parameters IsValid
property to indicate whether the cached data remains valid or not. One can also
extend TOutputCache and override its getCacheDependency() method.
The content fetched from cache may be variated with respect to some parameters.
TOutputCache supports variation with respect to request parameters, which is specified
by VaryByParam property.
version of cached content is used. This is extremely useful if a pages content may be
variated according to some GET parameters. The content being cached may also be
variated with user sessions if VaryBySession is set true. To variate the cached content
by other factors, override calculateCacheKey() method.
Output caches can be nested. An outer cache takes precedence over an inner cache
in determining the validity of cached contents. This means, if the content cached by
the inner cache expires or is invalidated, while that by the outer cache not, the outer
cached content will be used.
By default, TOutputCache is effective only for non-postback page requests and when a
cache module is enabled. Do not attempt to address child controls of TOutputCache
when the cached content is currently being used.
7.30
TPager
ControlToPaginate property, which must be the ID path of the target control reaching
from the pagers naming container.
Note, the target data-bound control must have its AllowPaging set to true. Otherwise
the pager will be invisible. Also, in case when there is only one page of data available,
the pager will also be invisible.
TPager can display one of the following three types of user interface, specified via its
121
7.31
TPanel
122
7.32. TPlaceHolder
TPanel acts as a presentational container for other control. It displays a div element
on a page. The property Wrap specifies whether the panels body content should wrap
or not, while HorizontalAlign governs how the content is aligned horizontally and
Direction indicates the content direction (left to right or right to left). You can set
BackImageUrl to give a background image to the panel, and you can set GroupingText
so that the panel is displayed as a field set with a legend text. Finally, you can specify
a default button to be fired when users press return key within the panel by setting
the DefaultButton property.
Controls.Samples.TPanel.Home Demo
7.32
TPlaceHolder
7.33
TRadioButton
7.34
TSafeHtml
123
To use TSafeHtml, simply enclose the content to be secured within the TSafeHtml component tag in a template. The content may consist of both static text and PRADO
controls. If the latter, the rendering result of the controls will be secured.
If the content is encoded in UTF-7, youll need to enable the RepackUTF7 property to
ensure the contents gets parsed correctly.
Controls.Samples.TSafeHtml.Home Demo
7.35
TSlider
124
7.36. TStatements
The range boundaries are defined by MinValue and MaxValue properties. The default
range is from 0 to 100. The StepSize property can be used to define the emstep/em
between 2 values inside the range. Notice that this step will be recomputed if there is
more than 200 values between the range boundaries. You can also provide the allowed
values by setting the Values array.
The handle sub-properties can be accessed by Handle property. You can also provide
your own control for the handle, using HandleClass property. Note that this class
must be a subclass of TSliderHandle
The TSlider control can be easily customized using CssClasses. You can provide your
own css file, using the CssUrl property. The css class for TSlider can be set by the
CssClass property. Defaults values are hslider for an Horizontal slider, or vslider
for a Vertical one. The css class for the Handle can be set by the Handle.CssClass
subproperty. Defaults is handle, which just draw a red block as a cursor. handleimage css class is also provided for your convenience, which display an image as the
handle.
If AutoPostBack property is true, postback is performed as soon as the value changed.
TSlider raises the onValueChanged event when the value of the slider has changed
during postback.
You can also attach ClientSide javascript events handler to the slider :
ClientSide.onSlide is called when the handle is slided on the track. You can
get the current value in the value javascript variable. You can use this event to
update on client side a label with the current value
ClientSide.onChange is called when the slider value has changed (at the end of a
move).
Controls.Samples.TSlider.Home Demo
7.36
TStatements
125
<com:TStatements>
<prop:Statements>
setlocale(LC_ALL, nl_NL);
echo strftime("%A %e %B %Y",time());
</prop:Statements>
</com:TStatements>
Note, TStatements evaluates the PHP statements during the rendering control lifecycle. Unlike TExpression, TStatements only displays the content echoed within the
statements.
The context of the statements in a TStatements control is the control itself. That is,
$this represents the control object if it is present in the statements. For example, the
following statement tag will display the title of the page containing the TStatements
control.
<com:TStatements>
<prop:Statements>
$page=$this->Page;
echo $page->Title;
</prop:Statements>
</com:TStatements>
Be aware, since TStatements allows execution of arbitrary PHP code, in general you
should not use it to evaluate PHP code submitted by your application users.
Controls.Samples.TStatements.Home Demo
7.37
TTable
126
7.38. TTabPanel
TTable displays an HTML table on a page. It is used together with TTableRow and
TTableCell to allow programmatically manipulating HTML tables. The rows of the
table is stored in Rows property. You may set the table cellspacing and cellpadding
via the CellSpacing and CellPadding properties, respectively. The table caption can
be specified via Caption whose alignment is specified by CaptionAlign. The GridLines
property indicates how the table should display its borders, and the BackImageUrl
allows the table to have a background image.
Controls.Samples.TTable.Home Demo
7.38
TTabPanel
ActiveViewIndex - the zero-based integer index of the view in the view collection.
ActiveViewID - the text ID of the visible view.
ActiveView - the visible view instance.
If both ActiveViewIndex and ActiveViewID are set, the latter takes precedence.
TTabPanel uses CSS to specify the appearance of the tab bar and panel. By default,
an embedded CSS file will be published which contains the default CSS for TTabPanel.
You may also use your own CSS file by specifying the CssUrl property. The following
properties specify the CSS classes used for elements in a TTabPanel:
CssClass - the CSS class name for the outer-most div element (defaults to tabpanel);
127
<com:TTabPanel>
<com:TTabView Caption="View 1">
content for view 1
</com:TTabView>
<com:TTabView Caption="View 2">
content for view 2
</com:TTabView>
<com:TTabView Caption="View 3">
content for view 3
</com:TTabView>
</com:TTabPanel>
Controls.Samples.TTabPanel.Home Demo
7.39
TTextBox
128
7.40. TTextHighlighter
7.40
TTextHighlighter
<com:TTextHighlighter ShowLineNumbers="true">
<?php
$str = one|two|three|four;
print_r(explode(|, $str, 2)); // will output an array
</com:TTextHighlighter>
Controls.Samples.TTextHighlighter.Home Demo
7.41
TWizard
7.41.1
Overview
129
By default, TWizard embeds the above components in an HTML table so that the side
bar is displayed on the left while the rest on the right. If UseDefaultLayout is set to
false, no HTML table will be used, and developers should use pure CSS techniques
to position the wizard components. Note, each component is displayed as a div and
the wizard itself is also a div that encloses its components div.
Wizard steps are represented by TWizardStep and are maintained in TWizard through
its WizardSteps property. At any time, only one step is visible, which is determined by
the ActiveStep property. The ActiveStepIndex property gives the index of the active
step in the step collection. Clicking on navigation buttons can activate different wizard
steps.
Wizard steps are typically added to a wizard through template as follows,
<com:TWizard>
<com:TWizardStep Title="step 1" StepType="Start">
content in step 1, may contain other controls
</com:TWizardStep>
<com:TWizardStep Title="step 2" StepType="Step">
content in step 2, may contain other controls
</com:TWizardStep>
<com:TWizardStep Title="finish step" StepType="Finish">
content in finish step, may contain other controls
</com:TWizardStep>
</com:TWizard>
In the above, StepType refers to the type of a wizard step, which can affect how the
navigation appearance and behavior of the step. A wizard step can be of one of the
130
7.41. TWizard
following types:
Start - the first step in the wizard.
Step - the internal steps in the wizard.
Finish - the last step that allows user interaction.
Complete - the step that shows a summary to user. In this step, both side bar
and navigation panel are invisible. Thus, this step usually does not allow user
interaction.
Auto - the step type is determined by wizard automatically.
7.41.2
Using TWizard
131
132
7.41. TWizard
Using Templated Wizard Steps
Wizard steps can also be templated. By using TTemplatedWizardStep, one can customize step content and navigation through its ContentTemplate and NavigationTemplate
properties, respectively. This is useful for control developers to build specialized wizards, such as user registration, shopping carts, etc.
Controls.Samples.TWizard.Sample5 Demo
133
134
Chapter 8
List Controls
List controls covered in this section all inherit directly or indirectly from TListControl.
Therefore, they share the same set of commonly used properties, including,
Items - list of items in the control. The items are of type TListItem. The item
list can be populated via databinding or specified in templates like the following:
<com:TListBox>
<com:TListItem Text="text 1" Value="value 1" />
<com:TListItem Text="text 2" Value="value 2" Selected="true" />
<com:TListItem Text="text 3" Value="value 3" />
</com:TListBox>
SelectedIndex - the zero-based index of the first selected item in the item list.
SelectedIndices - the indices of all selected items.
SelectedItem - the first selected item in the item list.
SelectedValue - the value of the first selected item in the item list.
AutoPostBack - whether changing the selection of the control should trigger postback.
135
tabular (two-dimensional) data : each row of data populates a single list item.
The list item value is specified by the data member indexed with DataValueField,
and the list item text by DataTextField. For example,
$listbox->DataTextField=name;
$listbox->DataValueField=id;
$listbox->DataSource=array(
array(id=>001,name=>John,age=>31),
array(id=>002,name=>Mary,age=>30),
array(id=>003,name=>Cary,age=>20));
$listbox->dataBind();
8.1.1
TListBox
136
8.1.2
TDropDownList
8.1.3
TCheckBoxList
8.1.4
TRadioButtonList
137
8.1.5
TBulletedList
Controls.Samples.TBulletedList.Home Demo
8.1.6
TRatingList
138
Chapter 9
Validation Controls
139
9.2
9.2.1
140
9.2.2
TRegularExpressionValidator
More regular expression patterns can be found on the Internet, e.g. http://regexlib.com/.
Note, TRegularExpressionValidator only checks for nonempty user input. Use a TRequiredFieldValidator to ensure the user input is not empty.
Controls.Samples.TRegularExpressionValidator.Home Demo
141
9.2.3
TEmailAddressValidator
9.2.4
TCompareValidator
142
9.2.5
TDataTypeValidator
9.2.6
TRangeValidator
143
9.2.7
TCustomValidator
<script type="text/javascript">
function ValidationFunctionName(sender, parameter)
{
// if(parameter == ...)
//
return true;
// else
//
return false;
}
</script>
Controls.Samples.TCustomValidator.Home Demo
9.2.8
TValidationSummary
144
9.3
9.3.1
Validators can be reset on the client-side using javascript by calling the Prado.Validation.reset(groupID)
where groupID is the validator grouping name. If groupID is null, then validators without grouping are used.
<script type="text/javascript">
function reset_validator()
{
Prado.Validation.reset("group1");
}
</script>
Controls.Samples.ResetValidation.Home Demo
9.3.2
All validators contains the following events. The corresponding events for the client
side is available as sub-properties of the ClientSide property of the validator.
The OnValidate event is raise before the validator validation functions are called.
The OnValidationSuccess event is raised after the validator has successfully validate the control.
The OnValidationError event is raised after the validator fails validation.
145
Note: For Prado versions earlier than 3.1 the property names were OnError
and OnSuccess. For Prado version 3.1 or later they are OnValidationError and
OnValidationSuccess, respectively.
The following example pop-up a message saying hello when the validator fails on
the client-side.
<com:TRequiredFieldValidator ... >
<prop:ClientSide.OnValidationError>
alert("hello");
</prop:ClientSide.OnValidationError>
</com:TRequiredFieldValidator>
Where sender is the current client-side validator and parameter is the control that
invoked the validator.
146
Chapter 10
Data Controls
Data controls are used to display a repeated content like a list or a table. The content
is generated from a collection of items containing the data, called the DataSource, and
a template describing the appearance, called Renderer. br/ The process of assigning
a DataSource to a Data control and render the template for each item is called data
binding.
TDataList is used to display or modify a list of data items.
TDataGrid displays data in a tabular format with rows and columns.
TRepeater displays its content defined in templates repeatedly based on the
given data.
10.2
TDataList
147
When dataBind() is called, TDataList undergoes the following lifecycles for each row
of data:
148
10.2. TDataList
1. create item based on templates or renderers
2. set the row of data to the item
3. raise an OnItemCreated event
4. add the item as a child control
5. call dataBind() of the item
6. raise an OnItemDataBound event
TDataList raises an OnItemCommand whenever a button control within some datalist
item raises an OnCommand event.
The number of
columns used to display the data items is specified via RepeatColumns property, while
the RepeatDirection governs the order of the items being rendered. The layout of the
data items in the list is specified via RepeatLayout, which takes one of the following
values:
Table (default) - items are organized using HTML table and cells. When using
this layout, one can set CellPadding and CellSpacing to adjust the cellpadding
and cellspacing of the table, and Caption and CaptionAlign to add a table caption
with the specified alignment.
149
150
10.3. TDataGrid
size will become big. Some complex data may also have serializing problem if
saved in viewstate.
The following example shows how to use TDataList to display tabular data, with
different layout and styles.
Controls.Samples.TDataList.Sample1 Demo
A common use of TDataList is for maintaining tabular data, including browsing,
editing, deleting data items. This is enabled by the command events and various item
templates of TDataList.
The following example displays a computer product information. Users can add new
products, modify or delete existing ones. In order to locate the data item for updating
or deleting, DataKeys property is used.
Be aware, for simplicity, this application does not do any input validation. In real
applications, make sure user inputs are valid before saving them into databases.
Controls.Samples.TDataList.Sample2 Demo
10.3
TDataGrid
151
10.3.1
Columns
Columns of a data grid determine how the associated cells are displayed. For example,
cells associated with a TBoundColumn are displayed differently according to their modes.
A cell is displayed as a static text if the cell is in browsing mode, a text box if it is in
editing mode, and so on.
PRADO provides eight types of columns:
TBoundColumn associates cells with a specific field of data and displays the cells
according to their modes.
TLiteralColumn associates cells with a specific field of data and displays the cells
with static texts.
TCheckBoxColumn associates cells with a specific field of data and displays in each
cell a checkbox whose check state is determined by the data field value.
TDropDownListColumn associates cells with a specific field of data and displays the
cells according to their modes. If in edit mode, a cell will be displayed with a
TDropDownList.
THyperLinkColumn displays in the cells a hyperlink whose caption and URL can
be either statically specified or bound to some fields of data.
TEditCommandColumn displays in the cells edit/update/cancel command buttons
according to the state of the item that a cell resides in.
TButtonColumn displays in the cells a command button.
TTemplateColumn displays the cells according to different templates defined for it.
10.3.2
Item Styles
TDataGrid defines different styles applied to its items. For example, AlternatingItemStyle
is applied to alternating items (item 2, 4, 6, etc.) Through these properties, one can
set CSS style fields or CSS classes for the items.
152
10.3. TDataGrid
Item styles are applied in a hierarchical way. Styles in higher hierarchy will inherit
from styles in lower hierarchy. Starting from the lowest hierarchy, the item styles
include items own style, ItemStyle, AlternatingItemStyle, SelectedItemStyle, and
EditItemStyle. Therefore, if background color is set as red in ItemStyle, EditItemStyle
will also have red background color, unless it is explicitly set to a different value.
10.3.3
Events
10.3.4
Using TDataGrid
153
Controls.Samples.TDataGrid.Sample1 Demo
Note, if AutoGenerateColumns is true and there are manually specified columns, the
automatically generated columns will be appended to the manually specified columns.
Also note, the datagrids Columns property contains only manually specified columns
and no automatically generated ones.
154
10.3. TDataGrid
The following example uses manually specified columns to show a list of book information,
Book title - displayed as a hyperlink pointing to the corresponding amazon.com
book page. THyperLinkColumn is used.
Publisher - displayed as a piece of text using TBoundColumn.
Price - displayed as a piece of text using TBoundColumn with output formatting
string and customized styles.
In-stock or not - displayed as a checkbox using TCheckBoxColumn.
Rating - displayed as an image using TTemplateColumn which allows maximum
freedom in specifying cell contents.
Pay attention to how item (row) styles and column styles cooperate together to affect
the appearance of the cells in the datagrid. Controls.Samples.TDataGrid.Sample2
Demo
10.3.5
155
10.3.6
Sorting
TDataGrid supports sorting its items according to specific columns. To enable sorting,
set AllowSorting to true. This will turn column headers into clickable buttons if their
SortExpression property is not empty. When users click on the header buttons, an
OnSortCommand event will be raised. Developers can write handlers to respond to the
sort command and sort the data according to SortExpression which is specified in the
corresponding column.
The following example turns the datagrid in Example 2 into a sortable one. Users
can click on the link button displayed in the header of any column, and the data will
be sorted in ascending order along that column.
Controls.Samples.TDataGrid.Sample4 Demo
10.3.7
Paging
When dealing with large datasets, paging is helpful in reducing the page size and complexity. TDataGrid has an embedded pager that allows users to specify which page
of data they want to see. The pager can be customized via PagerStyle. For example,
PagerStyle.Visible determines whether the pager is visible or not; PagerStyle.Position
indicates where the pager is displayed; and PagerStyle.Mode specifies what type of
pager is displayed, a numeric one or a next-prev one.
To enable paging, set AllowPaging to true. The number of rows of data displayed in a
page is specified by PageSize, while the index (zero-based) of the page currently showing to users is by CurrentPageIndex. When users click on a pager button, TDataGrid
raises OnPageIndexChanged event. Typically, the event handler is written as follows,
The following example enables the paging functionality of the datagrid shown in Example 1. In this example, you can set various pager styles interactively to see how
156
10.3. TDataGrid
they affect the pager display.
Controls.Samples.TDataGrid.Sample5 Demo
Custom Paging
The paging functionality shown above requires loading all data into memory, even
though only a portion of them is displayed in a page. For large datasets, this is
inefficient and may not always be feasible. TDataGrid provides custom paging to solve
this problem. Custom paging only requires the portion of the data to be displayed to
end users.
To enable custom paging, set both AllowPaging and AllowCustomPaging to true. Notify
TDataGrid the total number of data items (rows) available by setting VirtualItemCount.
And respond to the OnPageIndexChanged event. In the event handler, use the NewPageIndex
property of the event parameter to fetch the new page of data from data source. For
MySQL database, this can be done by using LIMIT clause in an SQL select statement.
Controls.Samples.TDataGrid.Sample6 Demo
10.3.8
Extending TDataGrid
157
10.4
TRepeater
158
10.4. TRepeater
IDataRenderer - the Data property will be set as the row of the data bound to the
repeater item. Many PRADO controls implement this interface, such as TLabel,
TTextBox, etc.
IItemDataRenderer - the ItemIndex property will be set as the zero-based index of the item in the repeater item collection, and the ItemType property
as the items type (such as TListItemType::Item). As a convenient base class,
TRepeaterItemRenderer implements IDataItemRenderer and can have an associated
template because it extends from TTemplateControl.
The following properties are used to specify different types of template and renderer
for a repeater. If an item type is defined with both a template and a renderer, the
latter takes precedence.
ItemTemplate, ItemRenderer - for each repeated row of data.
AlternatingItemTemplate, AlternatingItemRenderer: for each alternating row of
data. If not set, ItemTemplate or ItemRenderer will be used instead, respectively.
HeaderTemplate, HeaderRenderer - for the repeater header.
FooterTemplate, FooterRenderer - for the repeater footer.
SeparatorTemplate, SeparatorRenderer - for content to be displayed between items.
EmptyTemplate, EmptyRenderer - used when data bound to the repeater is empty.
To populate data into the repeater items, set DataSource to a valid data object, such
as array, TList, TMap, or a database table, and then call dataBind() for the repeater.
That is,
class MyPage extends TPage {
public function onLoad($param) {
parent::onLoad($param);
if(!$this->IsPostBack) {
$this->Repeater->DataSource=$data;
$this->Repeater->dataBind();
}
}
}
159
Normally, you only need to bind the data to repeater when the page containing
the repeater is initially requested. When the page is post back, the repeater will
restore automatically all its contents, including items, header, footer and separators.
However, the data row associated with each item will not be recovered and thus
become null.
To access the repeater item data in postbacks, use one of the following ways:
Use DataKeys to obtain the data key associated with the specified repeater item
and use the key to fetch the corresponding data from some persistent storage
such as DB.
Save the whole dataset in viewstate, which will restore the dataset automatically
upon postback. Be aware though, if the size of your dataset is big, your page
size will become big. Some complex data may also have serializing problem if
saved in viewstate.
TRepeater raises an OnItemCommand event whenever a button control within some repeater item raises a OnCommand event. Therefore, you can handle all sorts of OnCommand
event in a central place by writing an event handler for OnItemCommand.
The following example shows how to use TRepeater to display tabular data.
Controls.Samples.TRepeater.Sample1 Demo
160
10.4. TRepeater
TRepeater can be used in more complex situations. As an example, we show in the
following how to use nested repeaters, i.e., repeater in repeater. This is commonly
seen in presenting master-detail data. To use a repeater within another repeater,
for an item for the outer repeater is created, we need to set the detail data source
for the inner repeater. This can be achieved by responding to the OnItemDataBound
event of the outer repeater. An OnItemDataBound event is raised each time an outer
repeater item completes databinding. In the following example, we exploit another
event of repeater called OnItemCreated, which is raised each time a repeater item (and
its content) is newly created. We respond to this event by setting different background
colors for repeater items to achieve alternating item background display. This saves
us from writing an AlternatingItemTemplate for the repeaters.
Controls.Samples.TRepeater.Sample2 Demo
Besides displaying data, TRepeater can also be used to collect data from users. Validation controls can be placed in TRepeater templates to verify that user inputs are
valid.
The PRADO component composer demo is a good example of such usage. It uses
a repeater to collect the component property and event definitions. Users can also
delete or adjust the order of the properties and events, which is implemented by
responding to the OnItemCommand event of repeater.
See in the following yet another example showing how to use repeater to collect user
inputs.
Controls.Samples.TRepeater.Sample3 Demo
This sample shows how to use drop-in item renderers, available since v3.1.0. These
renderers come in the PRADO release. They are essentially controls implementing
the IDataRenderer interface.
implement this interface. When such controls are used item renderers, their Data
property is assigned with the row of the data being bound to the repeater item.
Controls.Samples.TRepeater.Sample4 Demo
More often, one needs to customize the layout of repeater items. The sample above
relies on OnItemCreated to adjust the appearance of the renderer. Templated item
renderers are perferred in this situation, as they allow us to put in more complex
layout and content in a repeater item. The following sample reimplements the nested
161
162
Chapter 11
TActiveButton
163
11.2
TActiveCheckBox
11.3
TActiveCheckBoxList
11.4
TActiveCustomValidator
164
11.5. TActiveDataList
The client-side uses callbacks to raise onServerValidate event. The ClientValidationFunction
property is disabled and will throw an exception if trying to set this property.
Beware that the onServerValidate may be raised when the control to validate on the
client side changes value, that is, the server validation may be called many times.
After the callback or postback, the @link onServerValidate onServerValidate is raised
once more. The IsCallback property of the TPage class will be true when validation is
made during a callback request.
ActiveControls.Samples.TActiveCustomValidator.Home Demo
11.5
TActiveDataList
11.6
TActiveDataGrid
165
11.6.1
Columns
166
11.6. TActiveDataGrid
Manually Specified Columns
The following example uses manually specified columns to show a list of book information,
Book title - displayed as a hyperlink pointing to the corresponding amazon.com
book page. TActiveHyperLinkColumn is used.
Publisher - displayed as a piece of text using TvBoundColumn.
Price - displayed as a piece of text using TActiveBoundColumn with output formatting string and customized styles.
In-stock or not - displayed as a checkbox using TActiveCheckBoxColumn.
Rating - displayed as an image using TActiveTemplateColumn which allows maximum freedom in specifying cell contents.
Each column can be shown or hidden in a callback.
ActiveControls.Samples.TActiveDataGrid.Sample2 Demo
11.6.2
The following example shows how to make the previous book information table an
interactive one. It allows users to edit and delete book items from the table. Two
additional columns are used in the example to allow users interact with the datagrid:
TActiveEditCommandColumn and TActiveButtonColumn. In addition, TActiveDropDownListColumn
replaces the previous TActiveTemplateColumn to allow users to select a rating from a
dropdown list. Note, it is also possible to use TActiveTemplateColumn to achieve the
same task. All the iteration is done using AJAX callbacks.
ActiveControls.Samples.TActiveDataGrid.Sample3 Demo
11.6.3
Sorting
The following example turns the datagrid in Example 2 into a sortable one. Users can
click on the link button displayed in the header of any column, and the data will be
167
11.6.4
Paging
The following example enables the paging functionality of the datagrid shown in Example 1. In this example, you move between the datagrid pages clicking on the pager
links. The grid reacts to paging rendering itself as the result of a callback request.
Note that you cant change the pager style upon callback.
ActiveControls.Samples.TActiveDataGrid.Sample5 Demo
11.7
TActiveDatePicker
11.8
TActiveDropDownList
168
11.9. TActiveFileUpload
List items can be changed dynamically during a callback request.
Please refer to the original documentation of TDropDownList for usage.
ActiveControls.Samples.TActiveDropDownList.Home Demo
11.9
TActiveFileUpload
status icon is displayed; either a green checkmark if the upload is successful, or a red
x if there was an error.
ActiveControls.Samples.TActiveFileUpload.Home Demo
11.10
TActiveHiddenField
169
11.11
TActiveHyperLink
11.12
TActiveImage
11.13
TActiveImageButton
170
11.14. TActiveLabel
is specified by Text. In addition, it is possible to obtain the coordinate of the point
where the image is clicked. The coordinate information is contained in the event
parameter of the OnClick event (not OnCallback).
ActiveControls.Samples.TActiveImageButton.Home Demo
11.14
TActiveLabel
11.15
TActiveLinkButton
property during callback request will update the link text upon callback response
completion.
ActiveControls.Samples.TActiveLinkButton.Home Demo
11.16
TActiveListBox
171
11.17
TActiveMultiView
11.18
TActivePager
172
11.19. TActivePanel
the pager will also be invisible.
TActivePager can display one of the following three types of user interface, specified
via its Mode property:
NextPrev - a next page and a previous page button are rendered on each page.
Numeric - a list of page index buttons are rendered.
DropDownList - a dropdown list of page indices is rendered.
These user interfaces may be further customized by configuring the following properties
NextPageText and PrevPageText - the label of the next/previous page button.
These properties are used when the pager Mode is NextPrev or Numeric.
FirstPageText and LastPageText - the label of the first/last page button. If empty,
the corresponding button will not be displayed. These properties are used when
the pager Mode is NextPrev or Numeric.
PageButtonCount - the maximum number of page index buttons on a page. This
property is used when the pager Mode is Numeric.
ButtonType - type of page buttons, either PushButton meaning normal form submission buttons, or LinkButton meaning hyperlink buttons.
TActivePager raises an OnPageIndexChanged event when an end-user interacts with it
and specifies a new page (e.g. by clicking on a next page button that would lead to
the next page.) Developers may write handlers to respond to this event and obtain
the desired new page index from the event parameters property NewPageIndex. Using
this new page index, one can feed a new page of data to the associated data-bound
control. Additionnaly, TActivePager raises OnCallback after the OnPageIndexChanged.
ActiveControls.Samples.TActivePager.Home Demo
11.19
TActivePanel
173
ActiveControls.Samples.TActivePanel.Home Demo
11.20
TRadioButton
11.21
TActiveRadioButtonList
174
11.22. TActiveRepeater
With ActiveControl.EnableUpdate set to true (default is true), changes to the selection
will be updated on the client side.
List items can not be changed dynamically during a callback request.
Please refer to the original documentation of TRadioButtonList for usage.
ActiveControls.Samples.TActiveRadioButtonList.Home Demo
11.22
TActiveRepeater
11.23
TActiveTextBox
175
11.24
TAutoComplete
11.25
TCallback
176
11.26. TCallbackClientScript
{
var request = <%= $this->callback1->ActiveControl->Javascript %>;
request.dispatch();
}
</script>
<div onclick="do_callback1()">Click Me!</div>
ActiveControls.Samples.TCallback.Home Demo
11.26
TCallbackClientScript
$this->getPage()->getCallbackClient()->hide($myTextBox);
ActiveControls.Samples.TCallbackClientScript.Home Demo
11.27
TCallbackClientSide
177
Events
The following client side events are executing in order if the callback request and
response are send and received successfuly.
178
11.28. TCallbackEventParameter
* Note that theses 2 events are not fired correctly by Opera. To make them work in
this browser, Prado will fire them just after onPreDispatch.
In a general way, onUninitialized, onLoading, onLoaded and onInteractive events are
not implemented consistently in all browsers. When cross browser compatibility is
needed, it is best to avoid use them.
are raised when the response is returned. A successful request/response will raise
OnSuccess event otherwise OnFailure will be raised.
Properties
The following properties can be used to change the way the callback request is performed.
PostState: true to collect the form inputs and post them during callback (default:
true);
RequestTimeOut The request timeout in milliseconds (default: 30000, i.e. 30 seconds);
HasPriority true to ensure that the callback request will be sent immediately
and will abort existing prioritized requests. It does not affect callbacks that are
not prioritized (default: true);
EnablePageStateUpdate enable the callback response to enable the viewstate update. This will automatically set HasPriority to true when enabled. (default:
true).
ActiveControls.Samples.TCallbackClientSide.Home Demo
11.28
TCallbackEventParameter
179
11.29
TCallbackOptions
<com:TCallbackOptions
ID="MyOptions"
ClientSide.OnLoading="... kindly inform the user that he should wait ..."
ClientSide.OnComplete="... callback completed, ready to serve the user again ..."
/>
Then, share this set of options to one or more active controls; each control will follow
them:
180
11.30. TDropContainer
<com:TActiveButton
Text="simple button"
OnCallback="..."
ActiveControl.CallbackOptions="MyOptions"
...
/>
<com:TActiveCheckBox
Text="simple checkbox"
OnCallback="..."
ActiveControl.CallbackOptions="MyOptions"
...
/>
ActiveControls.Samples.TCallbackOptions.Home Demo
11.30
TDropContainer
11.31
TDraggable
181
11.32
TEventTriggeredCallback
11.33
TInPlaceTextBox
182
11.34. TTimeTriggeredCallback
11.34
TTimeTriggeredCallback
11.35
TValueTriggeredCallback
183
184
Chapter 12
Active controls extends standard PRADO controls adding the ability to automatically update themselves on callbacks without the need of ad-hoc javascript calls. See
the Introduction for a quick overview of the concept behind active controls (AJAX
enabled controls). Most active controls have a property of ActiveControl and a subproperty ClientSide that provides many properties to customize the controls. The
CallbackClient property of the TPage class provides many methods to update and alter the client-side content during a callback request. Active controls is reliant on a
collection of javascript classes.
For a quick demo of active controls, try the TActiveButton control. See also the later
part of the Currency Converter tutorial for a more in depth example.
12.1.1
185
186
12.1.2
TActiveCheckBoxList displays a list of checkboxes on a Web page and each checkbox can trigger a callback request.
TActiveDropDownList displays a dropdown list box that allows users to select a
single option from a few prespecified ones. It can be used to perform a callback
request.
TActiveListBox displays a list box that allows single or multiple selection. It can
be used to perform a callback request.
TActiveRadioButtonList is similar to TActiveCheckBoxList in every aspect except that each TActiveRadioButtonList displays a group of radiobuttons. Each
radio button can perform a callback request.
12.1.3
12.1.4
187
12.1.5
The following table shows the Active Controls that can trigger a callback event and
whether the control will raise a PostBack event if Javascript was disabled on the
clients browser.
12.2
AJAX: Introduction
A classic webpage can only transfer data back to the server using an http postback
request that requires a full page reload. This is a problem for web applications, since
a synchronous page reload breaks the user interaction: the user must wait for the
response to arrive and the page will lose its current status (the scrolling position, the
currently focused control, etc..).
A common solution to this problem is the use of AJAX (Asynchronous JavaScript
and XML) callbacks. After the first full page load, the web application can make
subsequents requests using javascript. The callback requests are asynchronous, so
the user can continue to interact with the page while the response is loading. The
response contains a list of changes that will be applied to the page on the fly, like
replacing existing elements with new content or add some css style to an existing
element.
12.2.1
PRADO has builtin support for AJAX callbacks in the form of iActive Controls/i.
These controls can trigger a callback request and have their properties (value, css style,
attributes, ..) updated during a callback. Before digging inside the list of Active
Controls, its good to have a look to how a page can be aware if the current request
188
Triggers Callback
TActiveButton
Yes
Yes
TActiveCheckBox
Yes
Yes
TActiveCustomValidator
Yes
Yes
TActiveHyperLink
No
Yes
TActiveImage
No
Yes
TActiveImageButton
Yes
Yes
TActiveLabel
No
Yes
TActiveLinkButton
Yes
No
TActivePanel
No
Yes
TActiveRadioButton
Yes
Yes
TActiveTextBox
Yes
Yes
TCallbackOptions
No
N/A
TActiveCheckBoxList
Yes
Yes
TActiveDropDownList
Yes
Yes
TActiveListBox
Yes
Yes
TActiveRadioButtonList
Yes
Yes
TAutoComplete
Yes
No
TCallback
Yes
No
TEventTriggeredCallback
Yes
No
TInPlaceTextBox
Yes
No
TTimeTriggeredCallback
Yes
No
TValueTriggeredCallback
Yes
No
TDropContainer
Yes
No
TDraggable
No
189
No
is a callback and how to interact with the page rendered on the client browser. br/
The IsCallBack property of the TPage class exposes whether the current request being
handled is the consequence of a callback, and the CallbackClient property provides
many methods to update and alter the client-side content during a callback request.
190
12.3
Active controls extends standard PRADO controls adding the ability to automatically
update themselves on callbacks without the need of ad-hoc javascript calls. Active
controls are reliant on a collection of javascript classes that gets added to the page
automatically when needed.
Most active controls have a ActiveControl.EnableUpdate property that determines
whether the active control is allowed to update the contents of the client-side when
the callback response returns. Depending on the control different properties can be
updated.
Some active controls can trigger a callback as a consequence of a clientside event (a
button click, a checkbox being checked, a DOM event). The callback will first raise
the normal serverside event associated to the control (eg: OnClick for a TButton or
OnSelectedIndexChanged for a TRadioButtonList) and then the OnCallBack event. The
AutoPostBack property typically defaults to true for these controls.
Active controls have a ClientSide property that provides many subproperties to customize the controls and to hook some javascript code to the callback lifecycle, like
showing a Loading logo at the start of a callback and hide it at the end.
12.3.1
The class diagram for TActiveButton is illustrated in the figure below. Most active
control that can perform callback request have a similar structure.
TActiveButton is an extension of TButton and implements two additional interfaces
ICallbackEventHandler and IActiveControl. The TActiveButton contains an instance of
TBaseActiveCallbackControl available through the ActiveControl property of TActiveButton.
The following example set the callback parameter of the TActiveButton when a callback
request is dispatched.
<com:TActiveButton
Text="Click Me"
OnCallback="button_callback"
ActiveControl.CallbackParameter="value" />
191
12.3.2
192
The example loads the effects javascript library using the TClientScript component.
The ClientSide.OnLoading property value contains javascript statement that uses the
effects library to show the Loading... span tag. Similarly, ClientSide.OnComplete
property value contains the javascript statement that hides the Loading... span tag.
See TCallbackClientSide for further details on client-side property details.
12.3.3
The following classes provide the basic infrastructure classes required to realize the
active controls. They can be useful to develop new active controls, but Prado users
tipically dont need to use them.
TActiveControlAdapter
System.Web.UI.ActiveControls.TActiveControlAdapter API Reference
TActiveControlAdapter customizes the parent TControl class for active control
classes.
193
12.3.4
The following classes provide advanced properties and events needed to realize the
active controls. A Prado user can use them to customize active controls behaviour
and interact directly with the client side during a callback.
TCallbackClientScript methods to manipulate the client-side HTML elements,
also includes methods to invoke javascript Effects on HTML elements.
TCallbackClientSide is used to specify client-side callback request options and
client-side event handlers.
TCallbackEventParameter provides the parameter passed during the callback
request.
TCallbackOptions allows a common set of callback client-side options to be attached to one or more active controls.
194
Chapter 13
Writing new controls is often desired by advanced programmers, because they want
to reuse the code that they write for dealing with complex presentation and user
interactions.
In general, there are two ways of writing new controls: composition of existing controls
and extending existing controls. They all require that the new control inherit from
TControl or its child classes.
13.1.1
Composition is the easiest way of creating new controls. It mainly involves instantiating existing controls, configuring them and making them the constituent components.
The properties of the constituent components are exposed through subproperties.
One can compose a new control in two ways. One is to extend TCompositeControl
and override the TControl::createChildControls() method. The other is to extend
TTemplateControl (or its child classes) and write a control template. The latter is
easier to use and can organize the layout constituent components more intuitively,
while the former is more efficient because it does not require parsing of the template.
195
The above template specifies a TLabel control named Label and a TTextBox control
named TextBox. We would to expose these two controls. This can be done by defining
a property for each control in the LabeledTextBox class file. For example, we can define
a Label property as follows,
class LabeledTextBox extends TTemplateControl {
public function getLabel() {
$this->ensureChildControls();
return $this->getRegisteredObject(Label);
}
}
In the above, the method call to ensureChildControls() ensures that both the label
and the textbox controls are created (from template) when the Label property is
accessed. The TextBox property can be implemented similarly.
Controls.Samples.LabeledTextBox1.Home Demo
196
Controls.Samples.LabeledTextBox2.Home Demo
Using LabeledTextBox
To use LabeledTextBox control, first we need to include the corresponding class file.
Then in a page template, we can write lines like the following,
<com:LabeledTextBox ID="Input" Label.Text="Username" />
197
13.1.2
Extending existing controls is the same as conventional class inheritance. It allows developers to customize existing control classes by overriding their properties, methods,
events, or creating new ones.
The difficulty of the task depends on how much an existing class needs to be customized. For example, a simple task could be to customize TLabel control, so that
it displays a red label by default. This would merely involves setting the ForeColor
property to "red" in the constructor. A difficult task would be to create controls that
provide completely innovative functionalities. Usually, this requires the new controls
extend from low level control classes, such as TControl or TWebControl.
In this section, we mainly introduce the base control classes TControl and TWebControl,
showing how they can be customized. We also introduce how to write controls with
specific functionalities, such as loading post data, raising post data and databinding
with data source.
Extending TControl
TControl is the base class of all control classes. Two methods are of the most importance for derived control classes:
addParsedObject() - this method is invoked for each component or text string enclosed within the component tag specifying the control in a template. By default,
the enclosed components and text strings are added into the Controls collection
of the control. Derived controls may override this method to do special processing about the enclosed content. For example, TListControl only accepts TListItem
components to be enclosed within its component tag, and these components are
added into the Items collection of TListControl.
render() - this method renders the control. By default, it renders items in the
Controls collection.
tomized presentation.
198
199
200
Chapter 14
Service References
14.1
SOAP Service
SOAP forms the foundation layer of the Web services stack. It provides a neat way
for PHP applications to communicate with each other or with applications written
in other languages. PRADO provides TSoapService that makes developing a SOAP
server application an extremely easy task.
To use TSoapService, configure it in the application specification like following:
<services>
<service id="soap" class="System.Web.Services.TSoapService">
<soap id="stockquote" provider="path.to.StockQuote" />
</service>
</services>
The example specifies a SOAP service provider named stockquote which implements
the getPrice SOAP method in the provider class StockQuote,
class StockQuote
{
/**
201
Note: TSoapService is based on PHP SOAP extension and thus requires the
extension to be installed.
With the above simple code, we already finish a simple SOAP service that allows
other applications to query the price of a specific stock. For example, a typical SOAP
client may be written as follows to query the stock price of IBM,
$client=new SoapClient(http://path/to/index.php?soap=stockquote.wsdl);
echo $client->getPrice(IBM);
Notice the URL used to construct SoapClient (a class provided by PHP SOAP extension). This is the URL for the WSDL that describes the communication protocol for
the SOAP service we just implemented. WSDL is often too complex to be manually
written. Fortunately, TSoapService can generate this for us using a WSDL generator. In general, the URL for the automatically generated WSDL in PRADO has the
following format:
http://path/to/index.php?SoapServiceID=SoapProviderID.wsdl
In order for the WSDL generator to generate WSDL for a SOAP service, the provider
class needs to follow certain syntax. In particular, for methods to be exposed as SOAP
methods, a keyword @soapmethod must appear in the phpdoc comment of the method
with the following lines specifying method parameters and return value:
parameter: @param parameter-type $parameter-name description
return value: @return value-type description
202
203
For a complex soap object, the properties of the object are specified with @soapproperty
keyword in the property phpdocs. Furthermore, the propertys type name must be
specified as @var type $name where type is any valid type in mentioned earlier and
$name will defined a property name (notice that if your class is a TComponent, you can
provide property setter/getter methods).
Optionally, extra attributes (nillable, minOccurs, maxOccurs) can be defined for each
property by enclosing definitions into curly brackets and separated by comma like so:
{[attribute1 = value1][, attribute2 = value2], ...}
204
similar
tool
is
available
for
Mac
OS
Tiger
from
http://www.ditchnet.org/soapclient/
TSoapService may be configured and customized in several ways. In the example above,
the <soap> element actually specifies a SOAP service using the default TSoapServer
implementation. Attributes in <soap> are passed to TSoapServer as its initial property values. For example, the provider attribute initializes the Provider property of
TSoapServer. By setting SessionPersistent to be true in <soap> element, the provider
instance will persist within the user session. You may develop your own SOAP server
class and use it by specifying the class attribute of <soap>.
By default, PHPs soap server will create objects of the type StdClass when objects
are received from the client. The soap server can be configured to automatically create
objects of certain type objects are received as method parameters. For example, if
we have a Soap method that accepts a Contact object as parameter.
/**
* @param Contact $contact
* @return boolean true if saved, false otherwise
* @soapmethod
*/
function save(Contact $contact)
{
return true
}
The do this, we need to set the ClassMaps property of the TSoapServer in the <soap>
205
14.2
RPC Service
RPC Service stands for Remote Procedure Call Service and is a common name
used to identify a service that exposes an interface that can be called by remote
programs in order to execute a procedure. An RPC Service tipically exposes one or
more APIs (Application programming interface), permitting remote clients to make
requests to the available methods and receive a proper response. The interface itself
is not bound to a specific programming language, but uses a standard data exchange
protocol (tipically xml or json). PRADO provides TRpcService that makes developing
a RPC server application an extremely easy task.
To use TRpcService, configure it in the application specification like following:
<services>
<service id="rpc" class="System.Web.Services.TRpcService">
<rpcapi id="stockquote" class="path.to.StockQuote" />
</service>
</services>
The example specifies a RPC service provider named stockquote which implements
the getPrice RPC method in the provider class StockQuote,
class StockQuote
{
/**
* @param string $symbol the symbol of the stock
* @return float the stock price
* @soapmethod
*/
206
With the above simple code, we already finish a simple RPC service that allows other
applications to query the price of a specific stock. A client needs to know the exact
url of the service, the name of the method and the list of parameters needed by the
method.
207
208
Chapter 15
Data Access Objects (DAO) separates a data resources client interface from its data
access mechanisms. It adapts a specific data resources access API to a generic client
interface. As a result, data access mechanisms can be changed independently of the
code that uses the data.
Since version 3.1, PRADO starts to provide a DAO that is a thin wrap around PHP
Data Objects (PDO). Although PDO has a nice feature set and good APIs, we choose
to implement the PRADO DAO on top of PDO because the PRADO DAO classes
are component classes and are thus configurable in a PRADO application. Users can
use these DAO classes in a more PRADO-preferred way.
Note: Since the PRADO DAO is based on PDO, the PDO PHP extension
needs to be installed. In addition, you need to install the corresponding PDO
driver for the database to be used in your application. See more details in the
PHP Manual.
The PRADO DAO mainly consists of the following four classes (in contrast to PDO
which uses only two classes, PDO and PDOStatement):
TDbConnection - represents a connection to a database.
209
15.1.1
$connection=new TDbConnection($dsn,$username,$password);
// call setAttribute() to pass in additional connection parameters
// $connection->Persistent=true;
$connection->Active=true;
// connection is established
....
$connection->Active=false;
// connection is closed
MySQL - mysql:host=localhost;dbname=test
SQLite - sqlite:/path/to/dbfile
ODBC - odbc:SAMPLE
MSSql - sqlsrv:server=hostname;database=test"
In case any error occurs when establishing the connection (such as bad DSN or username/password), a TDbException will be raised.
210
15.1.2
An SQL statement is executed via TDbCommand in one of the following two ways:
execute() - performs a non-query SQL statement, such as INSERT, UPDATE and
DELETE. If successful, it returns the number of rows that are affected by the
execution.
query() - performs an SQL statement that returns rows of data, such as SELECT.
If successful, it returns a TDbDataReader instance from which one can fetch the
resulting rows of data.
$affectedRowCount=$command->execute();
$dataReader=$command->query();
$row=$command->queryRow();
$value=$command->queryScalar();
In case an error occurs during the execution of SQL statements, a TDbException will
be raised.
15.1.3
After TDbCommand.query() generates the TDbDataReader instance, one can retrieve rows
of resulting data by calling TDbDataReader.read() repeatedly. One can also use TDbDataReader
in PHPs foreach language construct to retrieve row by row.
// calling read() repeatedly until it returns false
while(($row=$dataReader->read())!==false) { ... }
211
15.1.4
Using Transactions
When an application executes a few queries, each reading and/or writing information
in the database, it is important to be sure that the database is not left with only some
of the queries carried out. A transaction, represented as a TDbTransaction instance in
PRADO, may be initiated in this case:
Begin the transaction.
Execute queries one by one. Any updates to the database are not visible to the
outside world.
Commit the transaction. Updates become visible if the transaction is successful.
If one of the queries fails, the entire transaction is rolled back.
$transaction=$connection->beginTransaction();
try
{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}
catch(Exception $e) // an exception is raised if a query fails will be raised
{
$transaction->rollBack();
}
15.1.5
Binding Parameters
212
Call TDbCommand.bindParameter() or
The methods bindParameter() and bindValue() are very similar. The only difference
is that the former binds a parameter with a PHP variable reference while the latter
with a value. For parameters that represent large block of data memory, the former
is preferred for performance consideration.
For more details about binding parameters, see the relevant PHP documentation.
15.1.6
Binding Columns
When fetching query results, one can also bind columns with PHP variables so that
they are automatically populated with the latest data each time a row is fetched.
$sql="SELECT username, email FROM users";
$dataReader=$connection->createCommand($sql)->query();
// bind the 1st column (username) with the $username variable
$dataReader->bindColumn(1,$username);
213
15.2
Active Record
Active Records are objects that wrap a row in a database table or view, encapsulate
the database access and add domain logic on that data. The basics of an Active
Record are business classes, e.g., a Products class, that match very closely the record
structure of an underlying database table. Each Active Record will be responsible for
saving and loading data to and from the database.
Info: The data structure of an Active Record should match that of a table in
the database. Each column of a table should have a corresponding member
variable or property in the Active Record class the represents the table.
15.2.1
When to Use It
Active Record is a good choice for domain logic that isnt too complex, such as creates,
reads, updates, and deletes. Derivations and validations based on a single record work
well in this structure. Active Record has the primary advantage of simplicity. Its
easy to build Active Records, and they are easy to understand.
However, as your business logic grows in complexity, youll soon want to use your
objects direct relationships, collections, inheritance, and so forth. These dont map
easily onto Active Record, and adding them piecemeal gets very messy. Another
argument against Active Record is the fact that it couples the object design to the
database design. This makes it more difficult to refactor as a project goes forward.
The alternative is to use a Data Mapper that separates the roles of the business object
and how these objects are stored. Prado provides a complimentary choice between
Active Record and SqlMap Data Mapper. A SqlMap Data Mapper can be used to load
Active Record objects, in turn; these Active Record objects can be used to update
214
The Active Record class has functionality to perform the following tasks.
Finder methods to wrap commonly used SQL queries and return Active Record
objects.
Fetch relationships (related foreign objects) such as has many, has one,
belongs to and many to many via association table.
215
15.2.2
Design Implications
Prados implementation of Active Record does not maintain referential identity. Each
object obtained using Active Record is a copy of the data in the database.
For
example, If you ask for a particular customer and get back a Customer object, the next
time you ask for that customer you get back another instance of a Customer object.
This implies that a strict comparison (i.e., using ===) will return false, while loose
comparison (i.e., using ==) will return true if the object values are equal by loose
comparison.
This is design implication related to the following question. iDo you think of the
customer as an object, of which theres only one, or do you think of the objects you
operate on as copies of the database?/i Other O/R mappings will imply that there
is only one Customer object with custID 100, and it literally is that customer. If you
get the customer and change a field on it, then you have now changed that customer.
iThat constrasts with: you have changed this copy of the customer, but not that
copy. And if two people update the customer on two copies of the object, whoever
updates first, or maybe last, wins./i [A. Hejlsberg 2003]
15.2.3
Database Supported
The Active Record implementation utilizes the Prado DAO classes for data access.
The current Active Record implementation supports the following database.
Support for other databases can be provided when there are sufficient demands.
216
15.3
Let us consider the following users table that contains two columns named username
and email, where username is also the primary key.
CREATE TABLE users
(
username VARCHAR( 20 ) NOT NULL ,
email VARCHAR( 200 ) ,
PRIMARY KEY ( username )
);
Next we define our Active Record class that corresponds to the users table.
class UserRecord extends TActiveRecord
{
const TABLE=users; //table name
public $username; //the column named "username" in the "users" table
public $email;
/**
* @return TActiveRecord active record finder instance
*/
public static function finder($className=__CLASS__)
{
return parent::finder($className);
}
}
Each column of the users table must have corresponding property of the same name
as the column name in the UserRecord class. Of course, you also define additional
member variables or properties that does not exist in the table structure. The class
constant TABLE is optional when the class name is the same as the table name in
the database, otherwise TABLE must specify the table name that corresponds to your
Active Record class.
Tip:
database1.table1".
217
E.g.
Note: Since version 3.1.3 you can also use a method table() to define the table
name. This allows you to dynamically specify which table should be used by
the ActiveRecord.
class TeamRecord extends TActiveRecord
{
public function table() {
return Teams;
}
}
Since TActiveRecord extends TComponent, setter and getter methods can be defined to
allow control over how variables are set and returned. For example, adding a $level
property to the UserRecord class:
class UserRecord extends TActiveRecord {
... //existing definitions as above
private $_level;
public function setLevel($value) {
$this->_level=TPropertyValue::ensureInteger($value,0);
}
public function getLevel($value){
return $this->_level;
}
}
The static method finder() returns an UserRecord instance that can be used to load
records from the database. The loading of records using the finder methods is discussed a little later. The TActiveRecord::finder() static method takes the name of an
Active Record class as parameter.
218
15.3.1
A default database connection for Active Record can be set as follows. See Establishing Database Connection for further details regarding creation of database connection
in general.
//create a connection and give it to the Active Record manager.
$dsn = pgsql:host=localhost;dbname=test; //Postgres SQL
$conn = new TDbConnection($dsn, dbuser,dbpass);
TActiveRecordManager::getInstance()->setDbConnection($conn);
Alternatively, you can create a base class and override the getDbConnection() method
to return a database connection. This is a simple way to permit multiple connections and multiple databases. The following code demonstrates defining the database
connection in a base class (not need to set the DB connection anywhere else).
class MyDb1Record extends TActiveRecord
{
public function getDbConnection()
{
static $conn;
if($conn===null)
$conn = new TDbConnection(xxx,yyy,zzz);
return $conn;
}
}
class MyDb2Record extends TActiveRecord
{
public function getDbConnection()
{
static $conn;
if($conn===null)
$conn = new TDbConnection(aaa,bbb,ccc);
return $conn;
}
}
219
Tip: The EnableCache attribute when set to true will cache the table meta
data, that is, the table columns names, indexes and constraints are saved in
the cache and reused. You must clear or disable the cache if you wish to see
changes made to your table definitions. A cache module must also be defined
for the cache to function.
/>
<module class="System.Data.SqlMap.TSqlMapConfig"
ConnectionID="db1"
... />
</modules>
15.3.2
The TActiveRecord class provides many convenient methods to find records from
the database.
220
Info: All finder methods that may return 1 record only will return null if no
matching data is found. All finder methods that return an array of records
will return an empty array if no matching data is found.
findByPk()
Finds one record using only a primary key or a composite key.
$finder = UserRecord::finder();
$user = $finder->findByPk($primaryKey);
//when the table uses a composite key
$record = $finder->findByPk($key1, $key2, ...);
$record = $finder->findByPk(array($key1, $key2,...));
findAllByPks()
Finds multiple records using a list of primary keys or composite keys. The following
are equivalent for primary keys (primary key consisting of only one column/field).
$finder = UserRecord::finder();
$users = $finder->findAllByPks($key1, $key2, ...);
$users = $finder->findAllByPks(array($key1, $key2, ...));
$record = $finder->findAllByPks($keys);
221
222
Note: For MSSQL and when Limit and Offset are positive integer values. The
actual query to be executed is modified by the TMssqlCommandBuilder class
according to http://troels.arvin.dk/db/rdbms/ to emulate the Limit and Offset
conditions.
findAll()
Same as find() but returns an array of objects.
$finder->findByUsernameAndPassword($name,$pass);
$finder->findBy_Username_And_Password($name,$pass);
$finder->find(Username = ? AND Password = ?, $name, $pass);
$finder->findAllByAge($age);
$finder->findAll(Age = ?, $age);
Tip: You may also use a combination of AND and OR as a condition in the
dynamic methods.
223
count()
Find the number of matchings records, accepts same parameters as the findAll()
method.
15.3.3
Add a new record using TActiveRecord is very simple, just create a new Active Record
object and call the save() method. E.g.
$user1 = new UserRecord();
$user1->username = "admin";
$user1->email = "[email protected]";
$user1->save(); //insert a new record
$data = array(username=>admin, email=>[email protected]);
$user2 = new UserRecord($data); //create by passing some existing data
$user2->save(); //insert a new record
Tip: If you insert a new record into a MySQL table that has columns defined
with autoincrement, the Active Record objects will be updated with the
new incremented value.
To update a record in the database, just change one or more properties of the Active Record object that has been loaded from the database and then call the save()
method.
224
Active Record objects have a simple life-cycle illustrated in the following diagram.
We see that new TActiveRecord objects are created by either using one of the find*()
methods or using creating a new instance by using PHPs new keyword. Objects
created by a find*() method starts with clean state. New instance of TActiveRecord
created other than by a find*() method starts with new state. Whenever you call
the save() method on the TActiveRecord object, the object enters the clean state.
Objects in the clean becomes dirty whenever one of more of its internal states are
changed. Calling the delete() method on the object ends the object life-cycle, no
further actions can be performed on the object.
15.3.4
To delete an existing record that is already loaded, just call the delete() method. You
can also delete records in the database by primary keys without loading any records
using the deleteByPk() method (and equivalently the deleteAllByPks() method). For
example, to delete one or several records with tables using one or more primary keys.
225
15.3.5
Transactions
All Active Record objects contain the property DbConnection that can be used to
obtain a transaction object.
$finder = UserRecord::finder();
$finder->DbConnection->Active=true; //open if necessary
$transaction = $finder->DbConnection->beginTransaction();
try
{
$user = $finder->findByPk(admin);
$user->email = [email protected]; //alter the $user object
$user->save();
226
15.3.6
Events
Logging Example
Using the OnExecuteCommand we can attach an event handler to log the entire SQL
query executed for a given TActiveRecord class or instance. For example, we define
a base class and override either the getDbConnection() or the constructor.
227
15.4
The Prado Active Record implementation supports the foreign key mappings for
database that supports foreign key constraints. For Active Record relationships to
function the underlying database must support foreign key constraints (e.g. MySQL
using InnoDB).
In the following sections we will consider the following table relationships between
Teams, Players, Skills and Profiles.
The goal is to obtain object models that represent to some degree the entity relationships in the above figure.
There is a mismatch between relationships with objects and table relationships. First
228
Tip: For SQLite database, you may create tables that defines the foreign key
constraints such as the example below. However, these constraints are NOT
enforced by the SQLite database itself.
CREATE TABLE foo
(
id INTEGER NOT NULL PRIMARY KEY,
id2 CHAR(2)
);
CREATE TABLE bar
(
id INTEGER NOT NULL PRIMARY KEY,
foo_id INTEGER
CONSTRAINT fk_foo_id REFERENCES foo(id) ON DELETE CASCADE
);
229
15.4.1
The entity relationship between the Teams and Players table is what is known as an
1-M relationship. That is, one Team may contain 0 or more Players. In terms of
object relationships, we say that a TeamRecord object has many PlayerRecord objects.
(Notice the reversal of the direction of relationships between tables and objects.)
//define the $player member having has many relationship with PlayerRecord
public static $RELATIONS=array
(
players => array(self::HAS_MANY, PlayerRecord, team_name),
);
public static function finder($className=__CLASS__)
{
return parent::finder($className);
}
}
The static $RELATIONS property of TeamRecord defines that the property $players has
many PlayerRecords. Multiple relationships is permitted by defining each relationship with an entry in the $RELATIONS array where array key for the entry corresponds to the property name. In array(self::HAS MANY, PlayerRecord), the first element defines the relationship type, the valid types are self::HAS MANY, self::HAS ONE,
self::BELONGS TO and self::MANY TO MANY. The second element is a string PlayerRecord
230
Note: As described in the code comment above, since version 3.1.2, related
properties no longer need to be explicitly declared.
A major
The foreign key constraint of the Players table is used to determine the corresponding
Teams tables corresponding key names. This is done automatically handled in Active
Record by inspecting the Players and Teams table definitions.
231
Info: Since version 3.1.2, Active Record supports multiple foreign key references of the same table. Ambiguity between multiple foreign key references
to the same table is resolved by providing the foreign key column name as the
3rd parameter in the relationship array. For example, both of the following
foreign keys owner id and reporter id references to the same table defined in
UserRecord.
class TicketRecord extends TActiveRecord
{
public $owner_id;
public $reporter_id;
public $owner;
public $reporter;
The has many relationship is not fetched automatically when you use any of the
Active Record finder methods. You will need to explicitly fetch the related objects
as follows. In the code below, both lines are equivalent and the method names are
case insensitive.
$team = TeamRecord::finder()->withPlayers()->findAll();
$team = TeamRecord::finder()->with_players()->findAll(); //equivalent
The method with xxx() (where xxx is the relationship property name, in this case,
players) fetches the corresponding PlayerRecords using a second query (not by using a join). The with xxx() accepts the same arguments as other finder methods of
TActiveRecord, e.g. with players(age = ?, 35).
232
Note: It is essential to understand that the related objects are fetched using
additional queries. The first query fetches the source object, e.g. the TeamRecord
in the above example code. A second query is used to fetch the corresponding
related PlayerRecord objects. The usage of the two query is similar to a single
query using Left-Outer join with the exception that null results on the right
table are not returned. The consequence of using two or more queries is that
the aggregates and other join conditions are not feasible using Active Records.
For queries outside the scope of Active Record the SqlMap Data Mapper may
be considered.
Info: The above with approach also works with implicitly declared related
properties (introduced in version 3.1.2). So what is the difference between the
with approach and the lazy loading approach? Lazy loading means we issue
an SQL query if a related object is initially accessed and not ready, while the
with approach queries for the related objects once for all, no matter the related
objects are accessed or not. The lazy loading approach is very convenient since
we do not need to explictly load the related objects, while the with approach is
more efficient if multiple records are returned, each with some related objects.
Belongs To Relationship
The has many relationship in the above section defines a collection of foreign objects. In particular, we have that a TeamRecord has many (zero or more) PlayerRecord
objects. We can also add a back pointer by adding a property in the PlayerRecord class
that links back to the TeamRecord object, effectively making the association bidirectional. We say that the $team property in PlayerRecord class belongs to a TeamRecord
object. The following code defines the complete PlayerRecord class with 3 relationships.
233
public $skills=array();
public $profile;
The static $RELATIONS property of PlayerRecord defines that the property $team belongs to a TeamRecord. The $RELATIONS array also defines two other relationships that
we shall examine in later sections below. In array(self::BELONGS TO, TeamRecord,
team name), the first element defines the relationship type, in this case strongself::BELONGS TO/strong
the second element is a string TeamRecord that corresponds to the class name of the
TeamRecord class; and the third element teamn ame0 ref erstothef oreignkeyof P layersref erencingT eams.Aplaye
$players = PlayerRecord::finder()->with_team()->findAll();
The method with xxx() (where xxx is the relationship property name, in this case,
team) fetches the corresponding TeamRecords using a second query (not by using a join).
The with xxx() accepts the same arguments as other finder methods of TActiveRecord,
e.g. with team(location = ?, Madrid).
234
Tip: Additional relationships may be fetched by chaining the with xxx() together as the following example demonstrates.
$players = PlayerRecord::finder()->with_team()->with_skills()->findAll();
Each with xxx() method will execute an additional SQL query. Every with xxx()
accepts arguments similar to those in the findAll() method and is only applied
to that particular relationship query.
In essence, there exists a belongs to relationship for objects corresponding to entities that has column which are foreign keys. In particular, we see that the Profiles
table has a foreign key constraint on the column player id that relates to the Players
tables player id column. Thus, the ProfileRecord object has a property ($player)
that belongs to a PlayerRecord object. Similarly, the Players table has a foreign key
constraint on the column team name that relates to the Teams tables name column. Thus,
the PlayerRecord object has a property ($team) that belongs to a TeamRecord object.
235
public $child_categories=array();
lation type, related AR class name, and the foreign key(s). For example, we use
array(self::HAS MANY, PlayerRecord, team name) to specify the players in a team.
There are two more optional elements that can be specified in this array: query condition (the fourth element) and parameters (the fifth element). They are used to control
how to query for the related objects. For example, if we want to obtain the players ordered by their age, we can specify array(self::HAS MANY, PlayerRecord, team name,
ORDER BY age). If we want to obtain players whose age is smaller than 30, we could
use ttarray(self::HASM AN Y, P layerRecord0 , teamn ame0 , age <: age0 , array( : age0 =>
30)) < /tt > .Ingeneral, thesetwoadditionalelementsaresimilarastheparameterspassedtothefind()methodinAR.
236
15.4.2
Objects can handle multivalued fields quite easily by using collections as field values.
Relational databases dont have this feature and are constrained to single-valued fields
only. When youre mapping a one-to-many association you can handle this using has
many relationships, essentially using a foreign key for the single-valued end of the
association. But a many-to-many association cant do this because there is no singlevalued end to hold the foreign key.
The answer is the classic resolution thats been used by relational data people for
decades: create an extra table (an association table) to record the relationship. The
basic idea is using an association table to store the association. This table has only
the foreign key IDs for the two tables that are linked together, it has one row for each
pair of associated objects.
The association table has no corresponding in-memory object and its primary key is
the compound of the two primary keys of the tables that are associated. In simple
terms, to load data from the association table you perform two queries (in general,
it may also be achieved using one query consisting of joins). Consider loading the
SkillRecord collection for a list PlayerRecord objects. In this case, you do queries in
two stages. The first stage queries the Players table to find all the rows of the players
you want. The second stage finds the SkillRecord object for the related player ID for
each row in the Player Skills association table using an inner join.
The Prado Active Record design implements the two stage approach. For the PlayersSkills M-N (many-to-many) entity relationship, we define a many-to-many relationship in the PlayerRecord class and in addition we may define a many-to-many relationship in the SkillRecord class as well. The following sample code defines the
complete SkillRecord class with a many-to-many relationship with the PlayerRecord
class. (See the PlayerRecord class definition above to the corresponding many-to-many
relationship with the SkillRecord class.)
237
The static $RELATIONS property of SkillRecord defines that the property $players has
many PlayerRecords via an association table Player Skills. In array(self::MANY TO MANY,
PlayerRecord, Player Skills), the first element defines the relationship type, in
this case strongself::MANY TO MANY/strong, the second element is a string PlayerRecord
that corresponds to the class name of the PlayerRecord class, and the third element is
the name of the association table name.
Note: Prior to version 3.1.2 (versions up to 3.1.1), the many-to-many relationship was defined using self::HAS MANY. For version 3.1.2 onwards, this must be
changed to self::MANY TO MANY. This can be done by searching for the HAS MANY
in your source code and carfully changing the appropriate definitions.
A list of player objects with the corresponding collection of skill objects may be
fetched as follows.
$players = PlayerRecord::finder()->withSkills()->findAll();
The method with xxx() (where xxx is the relationship property name, in this case,
Skill) fetches the corresponding SkillRecords using a second query (not by using
a join).
The with xxx() accepts the same arguments as other finder methods of
TActiveRecord.
238
The association table name in third element of the relationship array may contain the
foreign table column names. The columns defined in the association table must also
be defined in the record class (e.g. the $related item id property corresponds to the
related item id column in the related items table).
class Item extends TActiveRecord
{
const TABLE="items";
239
Tip: Compound keys in the foreign table can be specified as comma separated
values between brackets. E.g. related items.(id1,id2).
15.4.3
Using the with xxx() methods will load the relationship record on demand. Retrieving
the related record using lazy loading (that is, only when those related objects are
accessed) can be achieved by using a feature of the TComponent that provides accessor
methods. In particular, we define a pair of getter and setter methods where the getter
method will retrieve the relationship conditionally. The following example illustrates
that the PlayerRecord can retrieve its $skills foreign objects conditionally.
class PlayerRecord extends BaseFkRecord
{
//... other properties and methods as before
private $_skills; //change to private and default as null
240
The setSkills() ensures that the skills property will always be a TList. Using a
TList allows us to set the elements of the skills property as if they were arrays.
E.g. $player->skills[] = new SkillRecord(). If array was used, a PHP error will be
thrown.
241
15.4.4
Column Mapping
Since v3.1.1, Active Record starts to support column mapping. Column mapping
allows developers to address columns in Active Record using a more consistent naming
convention. In particular, using column mapping, one can access a column using
whatever name he likes, rather than limited by the name defined in the database
schema.
To use column mapping, declare a static array named COLUMN MAPPING in the Active
Record class. The keys of the array are column names (called iphysical column
names/i) as defined in the database schema, while the values are corresponding
property names (called ilogical column names/i) defined in the Active Record
class.
The property names can be either public class member variable names or
With the above column mapping, we can address first name using $userRecord->firstName
instead of $userRecord->first name. This helps separation of logic and model.
242
15.4.5
References
15.5
Active Record classes can be used together with TScaffoldListView and TScaffoldEditView ( TScaffoldView links both TScaffoldListView and TScaffoldEditView) to create
isimple/i Create/Read/Update/Delete (CRUD) web applications.
The scaffold views are intended to assist in prototyping web application, they are not
designed to be as customiziable as more complex components such as TDataGrid.
The scaffold views provide the following builtin functionality:
Scaffold views are dependent on Active Records and currently supports the following databases: Mysql, Sqlite and Postgres SQL. Support for other databases can be
considered when there are sufficient demand.
243
15.5.1
To use the scaffold view, we first define an Active Record class that represents a table
or view in the database. Consider the following Active Record class that corresponds
to the users table as defined in the Active Record quickstart page.
The above code will list the current records in the users table. Each record can be
edited by clicking on the edit button and deleted by clicking on the delete button.
A new record can be added by clicking on the Add new record button, enter some
data (notice the automatic validation of required fields and data types), and click
the save button. Specifying search terms in the search textbox to find particular
records. Finally, the record list can be sorted for each column by changing the sorting
column and order.
The TScaffoldView is a template control composed of other scaffold controls. The
following properties gives access to these composite controls.
bListView/b the TScaffoldListView displaying the record list.
bEditView/b the TScaffoldEditView that renders the inputs for editing and
adding records.
244
15.5.2
TScaffoldListView
A list of Active Records can be displayed using the TScaffoldListView with the following useful properties.
bHeader/b a TRepeater displaying the Active Record property/field names.
bSort/b a TDropDownList displaying the combination of properties and its
possible ordering.
bPager/b a TPager control displaying the links and/or buttons that navigate
to different pages in the Active Record data.
bList/b a TRepeater that renders a row of Active Record data.
Custom rendering of the each Active Record can be achieved by specifying the
ItemTemplate and/or AlternatingItemTemplate property of the List repeater.
The
TScaffoldListView will listen for two command events named delete and edit.
A delete command will delete a the record for the row where the delete command originates. An edit command will push the record data to be edited by a
TScaffoldEditView with ID specified by the EditViewID property. The following example lists the usernames only with bold formatting.
<com:TScaffoldListView RecordClass="UserRecord" >
<prop:List.ItemTemplate>
<strong><%# $this->Data->username %></strong>
</prop:List.ItemTemplate>
</com:TScaffoldListView>
Info: For the TScaffoldView the list view can be accessed throught the ListView
property of a TScaffoldView. Thus, the subproperty ListView.List.ItemTemplate
on TScaffoldView is equivalent to the List.ItemTemplate subproperty of
TScaffoldListView in the above example.
245
15.5.3
TScaffoldEditView
15.5.4
15.5.5
15.6
Data Mapper
Data Mappers moves data between objects and a database while keeping them independent of each other and the mapper itself. If you started with Active Records, you
may eventually faced with more complex business objects as your project progresses.
When you build an object model with a lot of business logic its valuable to use these
mechanisms to better organize the data and the behavior that goes with it. Doing so
leads to variant schemas; that is, the object schema and the relational schema dont
match up.
The Data Mapper separates the in-memory objects from the database. Its responsi-
246
15.6.1
When to Use It
The primary occasion for using Data Mapper is when you want the database schema
and the object model to evolve independently. Data Mappers primary benefit is that
when working on the business (or domain) objects you can ignore the database, both
in design and in the build and testing process. The domain objects have no idea what
the database structure is, because all the correspondence is done by the mappers.
This helps you in the code because you can understand and work with the domain
objects without having to understand how theyre stored in the database. You can
modify the business models or the database without having to alter either. With
complicated mappings, particularly those involving existing databases, this is very
valuable.
The price, of course, is the extra layer that you dont get with Active Record, so the
test for using these patterns is the complexity of the business logic. If you have fairly
simple business logic, an Active Record will probably work. For more complicated
logic a Data Mapper may be more suitable.
15.6.2
The SqlMap DataMapper framework makes it easier to use a database with a PHP
application. SqlMap DataMapper couples objects with stored procedures or SQL
statements using a XML descriptor. Simplicity is the biggest advantage of the SqlMap
DataMapper over object relational mapping tools. To use SqlMap DataMapper you
rely on your own objects, XML, and SQL. There is little to learn that you dont
already know. With SqlMap DataMapper you have the full power of both SQL and
stored procedures at your fingertip
Heres a high level description of the work flow illustrated in the figure above. Provide
a parameter, either as an object or a primitive type. The parameter can be used to
247
set runtime values in your SQL statement or stored procedure. If a runtime value is
not needed, the parameter can be omitted.
Execute the mapping by passing the parameter and the name you gave the statement
or procedure in your XML descriptor. This step is where the magic happens. The
framework will prepare the SQL statement or stored procedure, set any runtime values
using your parameter, execute the procedure or statement, and return the result.
In the case of an update, the number of rows affected is returned. In the case of a
query, a single object, or a collection of objects is returned. Like the parameter, the
result object, or collection of objects, can be a plain-old object or a primitive PHP
type.
15.6.3
A database connection for SqlMap can be set as follows. See Establishing Database
Connection for futher details regarding creation of database connection in general.
248
The TSqlMapManager is responsible for setting up the database connection and configuring the SqlMap with given XML file(s). The configureXml() method accepts a
string that points to a SqlMap XML configuration file. Once configured, call the
getSqlMapGateway() method to obtain an instance of the SqlMap gateway interface
(use this object to insert/delete/find records).
SqlMap database connection can also be configured using a <module> tag in the application.xml or config.xml as follows.
<modules>
<module id="my-sqlmap" class="System.Data.SqlMap.TSqlMapConfig"
EnableCache="true" ConfigFile="my-sqlmap.xml" >
<database ConnectionString="pgsql:host=localhost;dbname=test"
Username="dbuser" Password="dbpass" />
</module>
</modules>
The ConfigFile attribute should point to a SqlMap configuration file (to be detailed
later) either using absolute path, relative path or the Prados namespace dot notation
path (must omit the .xml extension).
Tip: The EnableCache attribute when set to true will cache the parsed configuration. You must clear or disable the cache if you make changes to your
configuration file. A cache module must also be defined for the cache to function.
To obtain the SqlMap gateway interface from the module configuration, simply do,
for example,
class MyPage extends TPage
{
249
15.6.4
A quick example
Let us consider the following users table that contains two columns named username and email, where username is also the primary key.
Next we define our plain User class as follows. Notice that the User is very simple.
class User
{
public $username;
public $email;
}
Next, we need to define a SqlMap XMl configuration file, lets name the file as
my-sqlmap.xml
<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig>
<select id="SelectUsers" resultClass="User">
SELECT username, email FROM users
</select>
</sqlMapConfig>
250
The above example shows demonstrates only a fraction of the capabilities of the
SqlMap Data Mapper. Further details can be found in the SqlMap Manual.
15.6.5
The above example may seem trival and it also seems that there is alot work just
to retrieve some data. However, notice that the User class is totally unware of been
stored in the database, and the database is unware of the User class.
One of advantages of SqlMap is the ability to map complex object relationship, collections from an existing database. On the other hand, Active Record provide a very
simple way to interact with the underlying database but unable to do more complicated relationship or collections. A good compromise is to use SqlMap to retrieve
complicated relationships and collections as Active Record objects and then using
these Active Records to do the updates, inserts and deletes.
Continuing with the previous example, we change the definition of the User class to
become an Active Record.
class UserRecord extends TActiveRecord
{
const TABLE=users; //table name
public $username; //the column named "username" in the "users" table
public $email;
/**
* @return TActiveRecord active record finder instance
251
We also need to change the definition of the SqlMap XML configuration. We just
need to change the value of resultClass attribute to UserRecord.
<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig>
<select id="SelectUsers" resultClass="UserRecord">
SELECT username, email FROM users
</select>
</sqlMapConfig>
The PHP code for retrieving the users remains the same, but SqlMap returns Active
Records instead, and we can take advantage of the Active Record methods.
15.6.6
References
252
Chapter 16
Advanced Topics
16.1
Collections
16.1.1
Using TList
A TList object represents a cardinal-indexed array, i.e., an array (object) with the
index 0, 1, 2, ...
TList may be used like a PHP array. For example,
253
To obtain the number of items in the list, use the Count property. Note, do not use
count($list), as it always returns 1.
In addition, TList implements a few commonly used convenient methods for manipulating the data in a list. These include
clear(): removes all items in the list.
contains(): tests if the list contains the specified item.
indexOf(): obtains the zero-based index of the specified item in the list.
toArray(): returns an array representation of the items in the list.
copyFrom(): populates the list with data from an array or traversable object
(including TList). Existing items will be removed first.
mergeWith(): appends the list with data from an array or traversable object
(including TList).
254
16.1. Collections
foreach($control->Controls as $childControl) ...
Another example is the Items property, available in list controls, TRepeater, TDataList
and TDataGrid. In these controls, the ancestor class of Items is TList.
Extending TList
Often, we want to extend TList to perform additional operations for each addition
or removal of an item. The only methods that the child class needs to override are
insertAt() and removeAt(). For example, to ensure the list only contains items that
are of TControl type, we can override insertAt() as follows,
public function insertAt($index,$item)
{
if($item instanceof TControl)
parent::insertAt($index,$item)
else
throw new Exception(TControl required.);
}
16.1.2
Using TMap
The Count property gives the number of items in the map while the Keys property
returns a list of keys used in the map.
The following methods are provided by TMap for convenience,
255
Using of TAttributeCollection
TAttributeCollection is a special class extending from TMap. It is mainly used by the
Attributes property of TControl.
Besides the normal functionalities provided by TMap, TAttributeCollection allows you
to get and set collection items like getting and setting properties. For example,
256
16.2
16.2.1
As de-
257
16.2.2
To enable PRADO auth framework, add the TAuthManager module and TUserManager
module to application configuration,
<service id="page" class="TPageService">
<modules>
<module id="auth" class="System.Security.TAuthManager"
UserManager="users" LoginPage="UserLogin" />
<module id="users" class="System.Security.TUserManager"
PasswordMode="Clear">
<user name="demo" password="demo" />
<user name="admin" password="admin" />
</module>
</modules>
</service>
In the above, the UserManager property of TAuthManager is set to the users module
which is TUserManager. Developers may replace it with a different user management
module that is derived from TUserManager.
Authorization rules for pages are specified in page configurations as follows,
<authorization>
<allow pages="PageID1,PageID2"
users="User1,User2"
roles="Role1" />
<deny pages="PageID1,PageID2"
users="?"
verb="post" />
</authorization>
An authorization rule can be either an allow rule or a deny rule. Each rule consists
of four optional properties:
pages - list of comma-separated page names that this rule applies to. If empty,
not set or wildcard *, this rule will apply to all pages under the current directory
and all its subdirectories recursively.
258
259
16.2.3
Using TUserManager
follows,
where the roles attribute in user element is optional. User roles can be specified in
either the user element or in a separate role element.
16.2.4
Using TDbUserManager
<module id="db"
class="System.Data.TDataSourceConfig" ..../>
<module id="users"
class="System.Security.TDbUserManager"
UserClass="Path.To.MyUserClass"
ConnectionID="db" />
<module id="auth"
260
In the above, UserClass specifies what class will be used to create user instance. The
class must extend from TDbUser. ConnectionID refers to the ID of a TDataSourceConfig
module which specifies how to establish database connection to retrieve user information.
The user class has to implement the two abstract methods in TDbUser: validateUser()
and createUser(). Since user account information is stored in a database, the user
class may make use of its DbConnection property to reach the database.
Since 3.1.1, TAuthManager provides support to allow remembering login by setting
AllowAutoLogin to true.
261
16.3
Security
16.3.1
Viewstate Protection
Viewstate lies at the heart of PRADO. Viewstate represents data that can be used
to restore pages to the state that is last seen by end users before making the current
request. By default, PRADO uses hidden fields to store viewstate information.
It is extremely important to ensure that viewstate is not tampered by end users.
Without protection, malicious users may inject harmful code into viewstate and unwanted instructions may be performed when page state is being restored on server
side.
To prevent viewstate from being tampered, PRADO enforces viewstate HMAC (KeyedHashing for Message Authentication) check before restoring viewstate. Such a check
can detect if the viewstate has been tampered or not by end users.
Should the
viewstate is modified, PRADO will stop restoring the viewstate and return an error
message.
HMAC check requires a private key that should be secret to end users. Developers can
either manually specify a key or let PRADO automatically generate a key. Manually
262
16.3. Security
specified key is useful when the application runs on a server farm. To do so, configure
TSecurityManager in application configuration,
<modules>
<module id="security"
class="TSecurityManager"
ValidationKey="my private key" />
</modules>
HMAC check does not prevent end users from reading the viewstate content. An
added security measure is to encrypt the viewstate information so that end users
cannot decipher it. To enable viewstate encryption, set the EnableStateEncryption
of pages to true. This can be done in page configurations or in page code. Note,
encrypting viewstate may degrade the application performance. A better strategy is
to store viewstate on the server side, rather than the default hidden field.
16.3.2
Cross site scripting (also known as XSS) occurs when a web application gathers malicious data from a user. Often attackers will inject JavaScript, VBScript, ActiveX,
HTML, or Flash into a vulnerable application to fool other application users and
gather data from them. For example, a poorly design forum system may display user
input in forum posts without any checking. An attacker can then inject a piece of
malicious JavaScript code into a post so that when other users read this post, the
JavaScript runs unexpectedly on their computers.
One of the most important measures to prevent XSS attacks is to check user input before displaying them. One can do HTML-encoding with the user input to achieve this
goal. However, in some situations, HTML-encoding may not be preferable because it
disables all HTML tags.
PRADO incorporates the work of SafeHTML and provides developers with a useful
component called TSafeHtml. By enclosing content within a TSafeHtml component tag,
the enclosed content are ensured to be safe to end users. In addition, the commonly
used TTextBox has a SafeText property which contains user input that are ensured to
be safe if displayed directly to end users.
263
16.3.3
Protecting cookies from being attacked is of extreme important, as session IDs are
commonly stored in cookies. If one gets hold of a session ID, he essentially owns all
relevant session information.
There are several countermeasures to prevent cookies from being attacked.
An application can use SSL to create a secure communication channel and only
pass the authentication cookie over an HTTPS connection. Attackers are thus
unable to decipher the contents in the transferred cookies.
Expire sessions appropriately, including all cookies and session tokens, to reduce
the likelihood of being attacked.
Prevent cross-site scripting (XSS) which causes arbitrary code to run in a users
browser and expose his cookies.
Validate cookie data and detect if they are altered.
PRADO implements a cookie validation scheme that prevents cookies from being
modified. In particular, it does HMAC check for the cookie values if cookie validation
is enable.
264
16.4. Assets
Cookie validation is disabled by default. To enable it, configure the THttpRequest
module as follows,
<modules>
<module id="request" class="THttpRequest" EnableCookieValidation="true" />
</modules>
To make use of cookie validation scheme provided by PRADO, you also need to
retrieve cookies through the Cookies collection of THttpRequest by using the following
PHP statements,
foreach($this->Request->Cookies as $cookie)
// $cookie is of type THttpCookie
To send cookie data encoded with validation information, create new THttpCookie
objects and add them to the Cookies collection of THttpResponse,
$cookie=new THttpCookie($name,$value);
$this->Response->Cookies[]=$cookie;
16.4
Assets
Assets are resource files (such as images, sounds, videos, CSS stylesheets, javascripts,
etc.) that belong to specific component classes. Assets are meant to be provided
to Web users. For better reusability and easier deployment of the corresponding
component classes, assets should reside together with the component class files . For
example, a toggle button may use two images, stored in file down.gif and up.gif, to
show different toggle states. If we require the image files be stored under images
directory under the Web server document root, it would be inconvenient for the
users of the toggle button component, because each time they develop or deploy a
new application, they would have to manually copy the image files to that specific
directory. To eliminate this requirement, a directory relative to the component class
file should be used for storing the image files. A common strategy is to use the
directory containing the component class file to store the asset files.
265
16.4.1
Asset Publishing
PRADO provides several methods for publishing assets or directories containing assets:
In a template file, you can use asset tags to publish assets and obtain their URLs.
Note, the assets must be relative to the directory containing the template file.
In PHP code, you can call $object->publishAsset($assetPath) to publish an asset
and obtain its URL. Here, $object refers to an instance of TApplicationComponent
or derived class, and $assetPath is a file or directory relative to the directory
containing the class file.
If you want to publish an arbitrary asset, you need to call TAssetManager::publishFilePath($path).
BE AWARE: Be very careful with assets publishing, because it gives Web users access
to files that were previously inaccessible to them. Make sure that you do not publish
files that do not want Web users to see.
16.4.2
Customization
266
16.4. Assets
BasePath="Web.images"
BaseUrl="images" />
</modules>
16.4.3
Performance
PRADO uses caching techniques to ensure the efficiency of asset publishing. Publishing an asset essentially requires file copy operation, which is expensive. To save
unnecessary file copy operations, System.Web.TAssetManager only publishes an asset
when it has a newer file modification time than the published file. When an application runs under the Performance mode, such timestamp checking is also omitted.
ADVISORY: Do not overuse asset publishing. The asset concept is mainly used to
help better reuse and redistribute component classes. Normally, you should not use
asset publishing for resources that are not bound to any component in an application.
For example, you should not use asset publishing for images that are mainly used
as design elements (e.g. logos, background images, etc.) Let Web server to directly
serve these images will help improve the performance of your application.
16.4.4
We now use the toggle button example to explain the usage of assets. The control uses
two image files up.gif and down.gif, which are stored under the directory containing
the control class file. When the button is in Up state, we would like to show the up.gif
image. This can be done as follows,
267
In the above, the call $this->getAsset(up.gif) will publish the up.gif image file and
return a URL for the published image file. The URL is then rendered as the src
attribute of the HTML image tag.
To redistribute ToggleButton, simply pack together the class file and the image files.
Users of ToggleButton merely need to unpack the file, and they can use it right away,
without worrying about where to copy the image files to.
16.5
Pages in a Web application often share common portions. For example, all pages of
this tutorial application share the same header and footer portions. If we repeatedly
put header and footer in every page source file, it will be a maintenance headache if in
future we want to something in the header or footer. To solve this problem, PRADO
introduces the concept of master and content. It is essentially a decorator pattern,
with content being decorated by master.
Master and content only apply to template controls (controls extending TTemplateControl
or its child classes). A template control can have at most one master control and one
or several contents (each represented by a TContent control). Contents will be inserted into the master control at places reserved by TContentPlaceHolder controls.
And the presentation of the template control is that of the master control with
TContentPlaceHolder replaced by TContent.
For example, assume a template control has the following template:
268
which uses MasterControl as its master control. The master control has the following
template,
other stuff
<com:TContentPlaceHolder ID="A" />
other stuff
<com:TContentPlaceHolder ID="B" />
other stuff
<com:TContentPlaceHolder ID="C" />
other stuff
Then, the contents are inserted into the master control according to the following diagram, while the resulting parent-child relationship can be shown in the next diagram.
Note, the template control discards everything in the template other than the contents, while the master control keeps everything and replaces the content placeholders
with the contents according to ID matching.
16.5.1
Master is very similar to external templates which are introduced since version 3.0.5.
A special include tag is used to include an external template file into a base template.
269
Both master and external template can be used to share common contents among
pages. A master is a template control whose template contains the common content and whose class file contains the logic associated with the master. An external
template, on the other hand, is a pure template file without any class files.
Therefore, use master control if the common content has to be associated with some
logic, such as a page header with search box or login box. A master control allows
you to specify how the common content should interact with end users. If you use
external templates, you will have to put the needed logic in the page or control class
who owns the base template.
Performancewise, external template is lighter than master as the latter is a selfcontained control participating the page lifecycles, while the former is used only when
the template is being parsed.
16.6
16.6.1
Introduction
Themes in PRADO provide a way for developers to provide a consistent look-andfeel across an entire web application. A theme contains a list of initial values for
properties of various control types. When applying a theme to a page, all controls on
that page will receive the corresponding initial property values from the theme. This
allows themes to interact with the rich property sets of the various PRADO controls,
meaning that themes can be used to specify a large range of presentational properties
that other theming methods (e.g. CSS) cannot. For example, themes could be used
270
16.6.2
Understanding Themes
A theme is a directory that consists of skin files, javascript files and CSS files. Any
javascript or CSS files contained in a theme will be registered with the page that the
theme is applied to. A skin is a set of initial property values for a particular control
type. A control type may have one or several skins, each identified by a unique SkinID.
When applying a theme to a page, a skin is applied to a control if the control type
and the SkinID value both match to those of the skin. Note, if a skin has an empty
SkinID value, it will apply to all controls of the particular type whose SkinID is not set
or empty. A skin file consists of one or several skins, for one or several control types.
A theme is the union of skins defined in all skin files.
16.6.3
Using Themes
To use a theme, you need to set the Theme property of the page with the theme name,
which is the theme directory name. You may set it in either page configurations or
in the constructor or onPreInit() method of the page. You cannot set the property
after onPreInit() because by that time, child controls of the page are already created
(skins must be applied to controls right after they are created.)
To use a particular skin in the theme for a control, set SkinID property of the control
in template like following,
<com:TButton SkinID="Blue" ... />
This will apply the Blue skin to the button. Note, the initial property values specified
by the Blue skin will overwrite any existing property values of the button. Use
stylesheet theme if you do not want them to be overwritten. To use stylesheet theme,
set the StyleSheetTheme property of the page instead of Theme (you can have both
StyleSheetTheme and Theme).
To use the Javascript files and CSS files contained in a theme, a THead control must
be placed on the page template. This is because the theme will register those files
271
16.6.4
Theme Storage
16.6.5
Creating Themes
Creating a theme involves creating the theme directory and writing skin files (and
possibly Javascript and CSS files). The name of skin files must be terminated with
.skin. The format of skin files are the same as that of control template files. Since skin
files do not define parent-child presentational relationship among controls, you cannot
place a component tag within another. And any static texts between component tags
are discarded. To define the aforementioned Blue skin for TButton, write the following
in a skin file,
272
16.6.6
<com:THeader3>
<prop:Decorator.PreTagText>
<!-- Surround the control with a div and apply a css class to it -->
<div class="imported-theme-h3-container">
</prop:Decorator.PreTagText>
<prop:Decorator.PostTagText>
<!-- Properly close the tag -->
</div>
</prop:Decorator.PostTagText>
My Nice Title
</com:THeader3>
In this example, a container div will be rendered around the THeader3 control, allowing
the user to specify a css class for the container. TWebControlDecorator can also interpret
prados template code and output the resulting html:
<com:TPanel>
<prop:Decorator.PreTagTemplate>
<!-- Insert an header before the panel -->
<com:THeader3>
Panels Injected Title
</com:THeader3>
</prop:Decorator.PreTagTemplate>
<prop:Decorator.PostTagTemplate>
<!-- Insert a date picker after the panel -->
Pick a date: <com:TDatePicker />
273
Note that you can use the Decorator property also inside skin files, getting it applied
automatically to all the controls subjected to the specific skin.
16.7
Persistent State
Web applications often need to remember what an end user has done in previous page
requests so that the new page request can be served accordingly. State persistence is to
address this problem. Traditionally, if a page needs to keep track of user interactions,
it will resort to session, cookie, or hidden fields. PRADO provides a new line of state
persistence schemes, including view state, control state, and application state.
16.7.1
View State
View state lies at the heart of PRADO. With view state, Web pages become stateful
and are capable of restoring pages to the state that end users interacted with before
the current page request. Web programming thus resembles to Windows GUI programming, and developers can think continuously without worrying about the round
trips between end users and the Web server. For example, with view state, a textbox
control is able to detect if the user input changes the content in the textbox.
View state is only available to controls. View state of a control can be disabled by
setting its EnableViewState property to false. To store a variable in view state, call
the following,
$this->setViewState(Caption,$caption);
where $this refers to the control object, Caption is a unique key identifying the
$caption variable stored in viewstate. To retrieve the variable back from view state,
call the following,
$caption = $this->getViewState(Caption);
274
16.8. Logging
16.7.2
Control State
Control state is like view state in every aspect except that control state cannot be
disabled. Control state is intended to be used for storing crucial state information
without which a page or control may not work properly.
To store and retrieve a variable in control state, use the following commands,
$this->setControlState(Caption,$caption);
$caption = $this->getControlState(Caption);
16.7.3
Application State
Application state refers to data that is persistent across user sessions and page requests. A typical example of application state is the user visit counter. The counter
value is persistent even if the current user session terminates. Note, view state and
control state are lost if the user requests for a different page, while session state is
lost if the user session terminates.
To store and retrieve a variable in application state, use the following commands,
$application->setGlobalState(Caption,$caption);
$caption = $application->getGlobalState(Caption);
16.7.4
Session State
16.8
Logging
275
further routed to different destinations, such as files, emails, browser windows, etc.
The following diagram shows the basic architecture of PRADO logging mechanism,
16.8.1
The following two methods are provided for logging messages in PRADO,
The difference between Prado::log() and Prado::trace() is that the latter automatically selects the log level according to the application mode. If the application is in
Debug mode, stack trace information is appended to the messages. Prado::trace() is
widely used in the core code of the PRADO framework.
16.8.2
Message Routing
Messages logged using the above two functions are kept in memory. To make use of the
messages, developers need to route them to specific destinations, such as files, emails,
or browser windows.
module. When plugged into an application, it can route the messages to different
destination in parallel. Currently, PRADO provides four types of routes:
276
16.8. Logging
TFileLogRoute - filtered messages are stored in a specified log file. By default,
this file is named prado.log under the runtime directory of the application. File
rotation is provided.
TEmailLogRoute - filtered messages are sent to pre-specified email addresses.
TBrowserLogRoute - filtered messages are appended to the end of the current page
output.
TFirebugLogRoute - filtered messages are sent to the Firebug console
To enable message routing, plug in and configure the TLogRouter module in application
configuration,
<module id="log" class="System.Util.TLogRouter">
<route class="TBrowserLogRoute"
Levels="Info"
Categories="System.Web.UI.TPage, System.Web.UI.WebControls" />
<route class="TFileLogRoute"
Levels="Warning, Error"
Categories="System.Web" />
</module>
In the above, the Levels and Categories specify the log and category filters to selectively retrieve the messages to the corresponding destinations.
16.8.3
Message Filtering
Messages can be filtered according to their log levels and categories. Each log message
is associated with a log level and a category. With levels and categories, developers
can selectively retrieve messages that they are interested on.
Log levels defined in System.Util.TLogger include : DEBUG, INFO, NOTICE, WARNING, ERROR,
ALERT, FATAL. Messages can be filtered according log level criteria. For example, if a
filter specifies WARNING and ERROR levels, then only those messages that are of WARNING
and ERROR will be returned.
Message categories are hierarchical. A category whose name is the prefix of another is
said to be the ancestor category of the other category. For example, System.Web cate-
277
16.9
Many web application built with PHP will not have internationalization in mind when
it was first written. It may be that it was not intended for use in languages and cultures. Internationalization is an important aspect due to the increase adoption of
the Internet in many non-English speaking countries. The process of internationalization and localization will contain difficulties. Below are some general guidelines to
internationalize an existing application.
16.9.1
Identify and separate data that varies with culture. The most obvious are text/string/message.
Other type of data should also be considered. The following list categorize some examples of culture sensitive data
Strings, Messages, Text, in relatively small units (e.g. phrases, sentences, paragraphs, but not the full text of a book).
Labels on buttons.
Help files, large units of text, static text.
Sounds.
Colors.
Graphics,Icons.
278
16.9.2
Configuration
To enable the localization features in PRADO, you need to add a few configuration
options in your application configuration. First you need to include the System.I18N.*
namespace to your paths.
<paths>
<using namespace="System.I18N.*" />
</paths>
Then, if you wish to translate some text in your application, you need to add one
translation message data source.
<module id="globalization" class="TGlobalization">
<translation type="XLIFF"
source="MyApp.messages"
marker="@@"
autosave="true" cache="true" />
</module>
Where source in translation is the dot path to a directory where you are going to
store your translate message catalogue. The autosave attribute if enabled, saves untranslated messages back into the message catalogue. With cache enabled, translated
279
16.9.3
The translation message catalogue file, if using type="XLIFF", is a standardized translation message interchange XML format. You can edit the XML file using any UTF-8
aware editor. The format of the XML is something like the following.
<?xml version="1.0"?>
<xliff version="1.0">
<file original="I18N Example IndexPage"
source-language="EN"
datatype="plaintext"
date="2005-01-24T11:07:53Z">
<body>
<trans-unit id="1">
<source>Hello world.</source>
<target>Hi World!!!</target>
</trans-unit>
</body>
</file>
</xliff>
Each translation message is wrapped within a trans-unit tag, where source is the
original message, and target is the translated message. Editors such as Heartsome
XLIFF Translation Editor can help in editing these XML files.
280
16.9.4
Since version 3.1.3 the messages can also be stored in a database using the connection
id from an existing TDataSourceConfig. You have to create two tables in your database:
catalogue and trans unit. The catalogue table needs an entry for each catalogue you
want to use. Example schemas for different databases can be found in the frameworks
I18N/schema directory. To configure translation with a database use:/
The translation messages will be stored in the trans unit table. Add your translation
in the target field of that table. You should make sure that you are working on the
right catalogue by comparing the messages cat id with that from the catalogue table.
16.9.5
Once globalization is enabled, you can access the globalization settings, such as,
Culture, Charset, etc, using
$globalization = $this->getApplication()->getGlobalization();
echo $globalization->Culture;
$globalization->Charset= "GB-2312"; //change the charset
You also change the way the culture is determined by changing the class attribute
in the module configuration. For example, to set the culture that depends on the
browser settings, you can use the TGlobalizationAutoDetect class.
281
You may also provide your own globalization class to change how the application
culture is set. Lastly, you can change the globalization settings on page by page basis
using template control tags. For example, changing the Culture to zh.
<%@ Application.Globalization.Culture="zh" %>
16.9.6
There are two areas in your application that may need message or string localization, in PHP code and in the templates. To localize strings within PHP, use the
localize function detailed below. To localize text in the template, use the TTranslate
component.
16.9.7
The localize function searches for a translated string that matches original from your
translation source. First, you need to locate all the hard coded text in PHP that are
displayed or sent to the end user. The following example localizes the text of the
$sender (assuming, say, the sender is a button). The original code before localization
is as follows.
function clickMe($sender,$param)
{
$sender->Text="Hello, world!";
}
The hard coded message Hello, world! is to be localized using the localize function.
function clickMe($sender,$param)
{
$sender->Text=Prado::localize("Hello, world!");
}
282
16.9.8
Compound Messages
Compound messages can contain variable data. For example, in the message There
are 12 users online., the integer 12 may change depending on some data in your
application. This is difficult to translate because the position of the variable data
may be difference for different languages. In addition, different languages have their
own rules for plurals (if any) and/or quantifiers. The following example can not be
easily translated, because the sentence structure is fixed by hard coding the variable
data within message.
$num_users = 12;
$message = "There are " . $num_users . " users online.";
This problem can be solved using the localize function with string substitution. For
example, the $message string above can be constructed as follows.
$num_users = 12;
$message = Prado::localize("There are {num_users} users online.", array(num_users=>$num_users));
Where the second parameter in localize takes an associative array with the key as
the substitution to find in the text and replaced it with the associated value. The
localize function does not solve the problem of localizing languages that have plural
forms, the solution is to use TChoiceFormat.
The following sample demonstrates the basics of localization in PRADO. Advanced.Samples.I18N.Home
Demo
16.10
I18N Components
16.10.1
TTranslate
<com:TTranslate>Hello World</com:TTranslate>
<com:TTranslate Text="Goodbye" />
283
16.10.2
TDateFormat
The Pattern property accepts 4 predefined localized date patterns and 4 predefined
localized time patterns.
fulldate
longdate
mediumdate
shortdate
fulltime
longtime
284
pattern, the first pattern must be the date, followed by a space, and lastly the time
pattern. For example, full date pattern with short time pattern. The actual ordering
of the date-time and the actual pattern will be determine automatically from locale
data specified by the Culture property.
<com:TDateFormat Pattern="fulldate shorttime" />
You can also specify a custom pattern using the following sub-patterns. The date/time
format is specified by means of a string time pattern. In this pattern, all ASCII letters
are reserved as pattern letters, which are defined as the following:
Symbol
Meaning
Presentation
Example
------
-------
------------
-------
era designator
(Text)
AD
year
(Number)
1996
month in year
July & 07
day in month
(Number)
10
(Number)
12
(Number)
minute in hour
(Number)
30
second in minute
(Number)
55
day of week
(Text)
Tuesday
day in year
(Number)
189
(Number)
week in year
(Number)
27
week in month
(Number)
am/pm marker
(Text)
PM
(Number)
24
(Number)
time zone
(Time)
(Delimiter)
Date=
single quote
(Literal)
oclock
285
Format Pattern
Result
--------------
-------
"yyyy.MM.dd G at HH:mm:ss"
->>
1996.07.10 AD at 15:08:56
->>
"h:mm a"
->>
12:08 PM
->>
"K:mm a"
->>
0:00 PM
->>
1996.July.10 AD 12:08 PM
If the Value property is not specified, the current date and time is used.
16.10.3
TNumberFormat
286
Culture and Currency properties may be specified to format locale specific numbers.
If someone from US want to see sales figures from a store in Germany (say using the
EURO currency), formatted using the german currency, you would need to use the
attribute Culture="de DE" to get the currency right, e.g. 100,00$. The decimal and
grouping separator is then also from the de DE locale. This may lead to some confusion
because people from US uses the , (comma) as thousand separator. Therefore a
Currency attribute is available, so that the output from the following example results
in $100.00
<com:TNumberFormat Type="currency"
Culture="en_US" Currency="EUR" Value="100" />
The Pattern property determines the number of digits, thousand grouping positions,
the number of decimal points and the decimal position. The actual characters that are
used to represent the decimal points and thousand points are culture specific and will
change automatically according to the Culture property. The valid Pattern characters
are:
# (hash) - represents the optional digits
0 (zero) - represents the mandatory digits, zero left filled
.
(full stop) - the position of the decimal point (only 1 decimal point is al-
lowed)
, (comma) - thousand point separation (up to 2 commas are allowed)
For example, consider the Value="1234567.12345" and with Culture="en US" (which uses
, for thousand point separator and . for decimal separators).
287
Output
-------
------
##,###.00
->>
1,234,567.12
##,###.##
->>
1,234,567.12345
##,##.0000
->>
1,23,45,67.1235
##,###,##.0
->>
12,345,67.1
000,000,000.0
->>
001,234,567.1
16.10.4
TTranslateParameter
<com:TTranslate>
{greeting} {name}!
<com:TTranslateParameter Key="name">World</com:TTranslateParameter>
<com:TTranslateParameter Key="greeting">Hello</com:TTranslateParameter>
</com:TTranslate>
16.10.5
TChoiceFormat
Using the localize function or TTranslate component to translate messages does not
inform the translator the cardinality of the data required to determine the correct
plural structure to use. It only informs them that there is a variable data, the data
could be anything. Thus, the translator will be unable to determine with respect to
the substitution data the correct plural, language structure or phrase to use . E.g.
in English, to translate the sentence, There are number of apples., the resulting
translation should be different depending on the number of apples.
The TChoiceFormat component performs message/string choice translation. The following example demonstrated a simple 2 choice message translation.
288
289
16.11
PRADO provides a complete error handling and reporting framework based on the
PHP 5 exception mechanism.
16.11.1
Exception Classes
Errors occur in a PRADO application may be classified into three categories: those
caused by PHP script parsing, those caused by wrong code (such as calling an undefined function, setting an unknown property), and those caused by improper use of
the Web application by client users (such as attempting to access restricted pages).
PRADO is unable to deal with the first category of errors because they cannot be
caught in PHP code. PRADO provides an exception hierarchy to deal with the second
and third categories.
290
Errors due to improper usage of the Web application by client users inherit from
TApplicationException.
16.11.2
Raising Exceptions
Raising exceptions in PRADO has no difference than raising a normal PHP exception.
The only thing matters is to raise the right exception. In general, exceptions meant
to be shown to application users should use THttpException, while exceptions shown
to developers should use other exception classes.
291
16.11.3
Exceptions raised during the runtime of PRADO applications are captured by System.Exceptions.TErrorH
module.
THttpException is assumed to contain error messages that are meant for application
end users and thus uses a specific group of templates. For all other exceptions, a
common template shown as follows is used for presenting the exceptions.
16.11.4
Developers can customize the presentation of exception messages. By default, all error
output templates are stored under framework/Exceptions/templates. The location can
be changed by configuring TErrorHandler in application configuration,
<module id="error"
class="TErrorHandler"
ErrorTemplatePath="Application.ErrorTemplates" />
292
exception-<language code>.html
Again, if the preferred language is not found, PRADO will try to use exception.html,
instead.
CAUTION: When saving a template file, please make sure the file is saved
using UTF-8 encoding. On Windows, you may use Notepad++ to accomplish
such saving.
16.12
Performance Tuning
16.12.1
Caching
PRADO provides a generic caching technique used by in several core parts of the
framework. For example, when caching is enabled, TTemplateManager will save parsed
templates in cache and reuse them in the following requests, which saves time for
parsing templates. The TThemeManager adopts the similar strategy to deal with theme
parsing.
Enabling caching is very easy. Simply add the cache module in the application configuration, and PRADO takes care of the rest.
293
Developers can also take advantage of the caching technique in their applications.
The Cache property of TApplication returns the plugged-in cache module when it is
available. To save and retrieve a data item in cache, use the following commands,
if($application->Cache) {
// saves data item in cache
$application->Cache->set($keyName,$dataItem);
// retrieves data item from cache
$dataItem=$application->Cache->get($keyName);
}
where $keyName should be a string that uniquely identifies the data item stored in
cache.
Since v3.1.0, a new control called TOutputCache has been introduced. This control
allows users to selectively cache parts of a pages output. When used appropriately,
this technique can significant improve pages performance because the underlying
controls are not created at all if the cached versions are hit.
16.12.2
Using pradolite.php
Including many PHP script files may impact application performance significantly.
PRADO classes are stored in different files and when processing a page request, it
may require including tens of class files.To alleviate this problem, in each PRADO
release, a file named pradolite.php is also included. The file is a merge of all core
PRADO class files with comments being stripped off and message logging removed.
To use pradolite.php, in your application entry script, replace the inclusion of prado.php
with pradolite.php.
294
16.12.3
16.12.4
By default, PRADO stores page state in hidden fields of the HTML output. The page
state could be very large in size if complex controls, such as TDataGrid, is used. To
reduce the size of the network transmitted page size, two strategies can be used.
First, you may disable viewstate by setting EnableViewState to false for the page or
some controls on the page if they do not need user interactions. Viewstate is mainly
used to keep track of page state when a user interacts with that page/control.
Second, you may use a different page state storage. For example, page state may be
stored in session, which essentially stores page state on the server side and thus saves
the network transmission time. The StatePersisterClass property of the page determines which state persistence class to use. By default, it uses System.Web.UI.TPageStatePersister
to store persistent state in hidden fields. You may modify this property to a persister
class of your own, as long as the new persister class implements the IPageStatePersister
295
Note, in the above the SpecialPage will use MyPersister2 as its persister class, while
the rest pages will use MyPersister1. Therefore, you can have different state persister
strategies for different pages.
16.12.5
Other Techniques
Server caching techniques are proven to be very effective in improving the performance of PRADO applications. For example, we have observed that by using Zend
Optimizer, the RPS (request per second) of a PRADO application can be increased
by more than ten times. Of course, this is at the cost of stale output, while PRADOs
caching techniques always ensure the correctness of the output.
296
Chapter 17
Client-side Scripting
17.1
Introduction to Javascript
This guide is based on the Quick guide to somewhat advanced JavaScript tour of some
OO features by Sergio Pereira.
17.1.1
If you are a web developer and come from the same place I do, you have probably
used quite a bit of Javascript in your web pages, mostly as UI glue.
Until recently, I knew that Javascript had more OO capabilities than I was employing,
but I did not feel like I needed to use it. As the browsers started to support a more
standardized featureset of Javascript and the DOM, it became viable to write more
complex and functional code to run on the client. That helped giving birth to the
AJAX phenomena.
As we all start to learn what it takes to write our cool, AJAX applications, we begin
to notice that the Javascript we used to know was really just the tip of the iceberg.
We now see Javascript being used beyond simple UI chores like input validation and
frivolous tasks. The client code now is far more advanced and layered, much like a
real desktop application or a client-server thick client. We see class libraries, object
297
17.1.2
JavaScript Object Notation (JSON,) is one of the new buzzwords popping up around
the AJAX theme. JSON, simply put, is a way of declaring an object in Javascript.
Lets see an example right away and note how simple it is.
Lets just add little bit of formatting so it looks more like how we usually find out
there:
var myPet =
{
color: black,
legCount: 4,
communicate: function(repeatCount)
{
for(i=0;i<repeatCount;i++)
alert(Woof!);
298
Here we created a reference to an object with two properties (color and legCount) and
a method (communicate.) Its not hard to figure out that the objects properties and
methods are defined as a comma delimited list. Each of the members is introduced
by name, followed by a colon and then the definition. In the case of the properties
it is easy, just the value of the property. The methods are created by assigning an
anonymous function, which we will explain better down the line. After the object is
created and assigned to the variable myPet, we can use it like this:
alert(my pet is + myPet.color);
alert(my pet has + myPet.legCount + legs);
//if you are a dog, bark three times:
myPet.communicate(3);
Youll see JSON used pretty much everywhere in JS these days, as arguments to
functions, as return values, as server responses (in strings,) etc.
17.1.3
This might be unusual to developers that never thought about that, but in JS a
function is also an object. You can pass a function around as an argument to another
function just like you can pass a string, for example. This is extensively used and
very handy.
Take a look at this example. We will pass functions to another function that will use
them.
var myDog =
{
bark: function()
{
alert(Woof!);
}
};
299
Note that we pass myDog.bark and myCat.meow without appending parenthesis "()"
to them. If we did that we would not be passing the function, rather we would be
calling the method and passing the return value, undefined in both cases here.
If you want to make my lazy cat start barking, you can easily do this:
myCat.meow = myDog.bark;
myCat.meow(); //alerts Woof!
17.1.4
As Im sure you already know, you can access individual items in an array by using
the square brackets:
300
But you are not limited to numeric indices. You can access any member of a JS object
by using its name, in a string. The following example creates an empty object, and
adds some members by name.
var obj = {}; //new, empty object
obj[member_1] = this is the member value;
obj[flag_2] = false;
obj[some_function] = function(){ /* do something */};
In many ways, the idea of objects and associative arrays (hashes) in JS are not distiguishable. The following two lines do the same thing too.
obj.some_function();
obj[some_function]();
17.1.5
The great power of object oriented programming languages derive from the use of
classes. I dont think I would have guessed how classes are defined in JS using only
my previous experience with other languages. Judge for yourself.
//defining a new class called Pet
301
Lets see how we add a method to our Pet class. We will be using the prototype
property that all classes have. The prototype property is an object that contains
all the members that any object of the class will have. Even the default JS classes,
like String, Number, and Date have a prototype object that we can add methods and
properties to and make any object of that class automatically gain this new member.
Pet.prototype.communicate = function()
{
alert(I do not know what I should say, but my name is + this.name);
};
Thats when a library like prototype.js comes in handy. If we are using prototype.js,
we can make our code look cleaner (at least in my opinion.)
var Pet = Class.create();
Pet.prototype =
{
//our constructor
initialize: function(petName, age)
{
this.name = petName;
this.age = age;
},
communicate: function()
{
alert(I do not know what I should say, but my name is + this.name);
}
};
302
17.1.6
If you have never worked with languages that support closures you may find the
following idiom too funky.
Whoa! Lets explain what is going on here before you decide Ive gone too far and
navigate to a better article than this one.
First of all, in the above example we are using the prototype.js library, which adds
the each function to the Array class. The each function accepts one argument that
is a function object. This function, in turn, will be called once for each item in the
array, passing two arguments when called, the item and the index for the current
item. Lets call this function our iterator function. We could have also written the
code like this.
But then we would not be doing like all the cool kids in school, right? More seriously,
though, this last format is simpler to understand but causes us to jump around in the
code looking for the myIterator function. Its nice to have the logic of the iterator
function right there in the same place its called. Also, in this case, we will not
need the iterator function anywhere else in our code, so we can transform it into an
anonymous function without penalty.
303
17.1.7
One of the most common troubles we have with JS when we start writing our code it
the use of the this keyword. It could be a real tripwire.
As we mentioned before, a function is also an object in JS, and sometimes we do not
notice that we are passing a function around.
Take this code snippet as an example.
function buttonClicked()
{
alert(button + this.id + was clicked);
}
var myButton = document.getElementById(someButtonID);
var myButton2 = document.getElementById(someOtherButtonID);
myButton.onclick = buttonClicked;
myButton2.onclick = buttonClicked;
Because the buttonClicked function is defined outside any object we may tend to think
the this keyword will contain a reference to the window or document object (assuming
this code is in the middle of an HTML page viewed in a browser.)
But when we run this code we see that it works as intended and displays the id
of the clicked button. What happened here is that we made the onclick method of
each button contain the buttonClicked object reference, replacing whatever was there
before. Now whenever the button is clicked, the browser will execute something
similar to the following line.
myButton.onclick();
That isnt so confusing afterall, is it? But see what happens you start having other
objects to deal with and you want to act on these object upon events like the buttons
click.
var myHelper =
{
304
So you think, nice, now I can click the Clear button on my page and those three
text boxes will be emptied. Then you try clicking the button only to get a runtime
error. The error will be related to (guess what?) the this keyword. The problem is
that this.formFields is not defined if this contains a referece to the button, which is
precisely whats happening. One quick solution would be to rewrite our last line of
code.
clearButton.onclick = function()
{
myHelper.emptyAllFields();
};
That way we create a brand new function that calls our helper method within the
helper objects context.
305
17.2
This guide is based on the Developer Notes for prototype.js by Sergio Pereira.
17.2.1
What is that?
In case you havent already used it, prototype.js is a JavaScript library written by
Sam Stephenson. This amazingly well thought and well written piece of standardscompliant code takes a lot of the burden associated with creating rich, highly interactive web pages that characterize the Web 2.0 off your back.
If you tried to use this library recently, you probably noticed that documentation is
not one of its strongest points. As many other developers before me, I got my head
around prototype.js by reading the source code and experimenting with it. I thought
it would be nice to take notes while I learned and share with everybody else.
As you read the examples and the reference, developers familiar with the Ruby
programming language will notice an intentional similarity between Rubys built-in
classes and many of the extensions implemented by this library.
17.2.2
306
Another nice thing about this function is that you can pass either the id string or
the element object itself, which makes this function very useful when creating other
functions that can also take either form of argument.
17.2.3
The $F() function is a another welcome shortcut. It returns the value of any field
input control, like text boxes or drop-down lists. The function can take as argument
either the element id or the element object itself.
<input type="text" id="userName" value="Joe Doe" />
<input type="button" value=Test3 onclick="test3();" />
307
17.3
17.3.1
The syntax for working with events looks like the code below.
Event.observe(element, name, observer, [useCapture]);
Assuming for a moment that we want to observe when a link was clicked, we could
do the following:
// <a id="clicker" href="http://foo.com">Click me</a>
Event.observe(clicker, click, function(event)
{
alert(clicked!);
});
If we wanted to get the element that fired the event, wed do this:
Event.observe(clicker, click, function(event)
{
alert(Event.element(event));
});
17.3.2
Observing keystrokes
If we wanted to observe keystrokes for the entire document, we could do the following:
308
And lets say we wanted to keep track of what has been typed :
Prototype defines properties inside the event object for some of the more common
keys, so feel free to dig around in Prototype to see which ones those are.
A final note on keypress events; If youd like to detect a left click you can use
Event.isLeftClick(event).
17.3.3
Drag and drop, dynamic element resizing, games, and much more all require the
ability to track the X and Y location of the mouse. Prototype makes this fairly
simple. The code below tracks the X and Y position of the mouse and spits out those
values into an input box named mouse.
If we wanted to observe the mouse location when it was hovering over a certain
element, wed just change the document argument to the id or element that was
relevant.
309
17.3.4
Stopping Propagation
17.3.5
Everything has been fairly straight forward so far, but things start getting a little
trickier when you need to work with events in and object-oriented environment. You
have to deal with binding and funky looking syntax that might take a moment to get
your head around.
Lets look at some code so you can get a better understanding of what Im talking
about.
EventDispenser = Class.create();
EventDispenser.prototype =
{
initialize: function(list)
{
this.list = list;
// Observe clicks on our list items
$$(this.list + " li").each(function(item)
{
Event.observe(item, click, this.showTagName.bindEvent(this));
}.bind(this));
// Observe when a key on the keyboard is pressed.
// In the observer, we check for
// the tab key and alert a message if it is pressed.
Event.observe(document, keypress, this.onKeyPress.bindEvent(this));
// Observe our fake live search box.
310
Whoa! Whats going on here? Well, weve defined our a custom class EventDispenser.
Were going to be using this class to setup events for our document. Most of this
code is a rewrite of the code we looked at earlier except this time, we are working
from inside an object.
Looking at the initialize method, we can really see how things are different now.
Take a look at the code below:
311
Now looking at the code above, youll notice the bindEvent function. This takes the
method before it showTagName and treats it as the method that will be triggered when,
in this case, someone clicks one of our list items.
Youll also notice we pass this as an argument to the bindEvent function. This simply allows us to reference the object in context EventDispenser inside our function
showTagName(event). If the showTagName function requires additional parameters, you
can attach them to the later parameters of bindEvent. For example
this.showTagName.bindEvent(this, param1, param2);
//where the showTagName function is defined as
showTime : function (event, param1, param2) { ... }
Moving on, youll see bind(this) attached to our iterator function. This really has
nothing to do with events, it is only here to allow me to use this inside the iterator.
If we did not use bind(this), I could not reference the method showTagName inside the
iterator.
Ok, so well move on to looking at our methods that actually get called when an event
occurs. Since weve been dealing with showTagName, lets look at it.
showTagName: function(event)
{
alert(Event.element(event).tagName);
}
As you can see, this function accepts one argumentthe event. In order for us to get
the element which fired the event we need to pass that argument to Event.element.
Now we can manipulate it at will.
312
17.3.6
This one threw me for a loop the first time I tried to use it. I tried something similar
to what I did in the Event.observe call with the exception of using stopObserving, but
nothing seemed to change. In other words, the code below does NOT work.
$$(this.list + " li").each(function(item)
{
Event.stopObserving(item, click, this.showTagName);
}.bind(this));
Whats the deal here? The reason this does not work is because there is no pointer to
the observer. This means that when we passed this.showTagName in the Event.observe
method before hand, we passed it as an anonymous function. We cant reference an
anonymous function because it simply does not have a pointer.
So how do we get the job done? All we need to do is give the observing function
a pointer, or the jargon free version: Set a variable that points to this.showTagName.
Ok, lets change our code a bit.
this.showTagObserver = this.showTagName.bindEvent(this);
// Observe clicks on our list items
$$(this.list + " li").each(function(item)
{
Event.observe(item, click, this.showTagObserver);
}.bind(this));
Now we can remove the event listeners from our list like this:
$$(this.list + " li").each(function(item)
{
Event.stopObserving(item, click, this.showTagObserver);
}.bind(this));
313
17.4
17.4.1
The javascript libraries distributed with Prado can be found in the framework/Web/Javascripts/source
directory. The packages.php file in that directory defines a list of available package
names available to be loaded. They can be loaded as follows.
Adding libraries in the template
<com:TClientScript PradoScripts="effects" />
314
315