I’ve been using PRADO for a long time. It’s not a very popular PHP framework, but since I discovered it I have liked its features:
- Extensive documentation (you can check the list of demos).
- Component-oriented and event-driven architecture (with a lot of components).
- Its separation of markup and behavior.
- The extensive use of javascript and ajax to get amazing interactive pages.
Of course, it has its drawbacks:
- A steep learning curve.
- It may be a little heavy for the server.
But I think it’s worth the effort.
I think PRADO development team is doing a great job. Last version 3.3.0 (February 15, 2016) has lots of improvements. The most important one is a full rewritten of its javascript components, now most of them depend only on jQuery (previously, they depended on Prototype.js).
I’d like to show a little demo. I’ve chosen to build a multi-step form. I think it’s a good opportunity to show some of PRADO’s components and to get an overview of PRADO’s organization of pages. I hope you will like it.
The final product
You can see the final product here. You can see it’s a registration form. I have divided the form into three steps and the user can navigate through them. To simplify the code, this version doesn’t validate the fields nor save them into any database (I will save these steps for next posts).
The full code
The code of a PRADO page is split into two files: the .page
and the .php
files (both of them have the same name). Each one of them has its own responsibilities. Let’s see them:
Home.page file
First, the Home.page
file. You can see it has a lot of markup. It looks like HTML … but it’s slightly different (don’t worry, I’ll explain it later).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
<!DOCTYPE html> <html> <com:THead> <meta charset="utf-8" /> <title>Registration form Template | PrepBootstrap</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="<%= $this->assetUrl . '/bootstrap/css/bootstrap.min.css'%>" /> <link rel="stylesheet" type="text/css" href="<%= $this->assetUrl . '/font-awesome/css/font-awesome.min.css'%>" /> <style type="text/css"> span.progress-step { position:absolute; width: 100%; text-align: center; background-color: transparent; } div.step-progress { margin-bottom:20px; } </style> </com:THead> <body> <com:TForm> <div class="container"> <div class="page-header"> <h1><a href="/">jmjg.es</a> - PRADO Multi Step Registration form</h1> </div> <div class="container"> <div class="row step-progress"> <div class="col-lg-12"> <com:TJuiProgressbar Id="StepsProgress" Options.Max="3" Options.Value="1"> <com:TActiveLabel Id="ProgressTitle" CssClass="progress-step" Text=""/> </com:TJuiProgressbar> </div> </div> <div class="row"> <div class="col-lg-6 col-lg-offset-3"> <h3><com:TActiveLabel Id="Title" Text=""/></h3> <com:TActiveMultiView Id="MultiView" ActiveViewIndex="0" OnActiveViewChanged="viewChanged"> <com:TView> <div class="form-group"> <com:TLabel ForControl="InputName" Text="Enter Name"/> <div class="input-group"> <com:TActiveTextBox CssClass="form-control" Id="InputName" Attributes.Placeholder="Enter Name" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <com:TLabel ForControl="InputEmailFirst" Text="Enter Email"/> <div class="input-group"> <com:TActiveTextBox CssClass="form-control" Id="InputEmailFirst" Attributes.Placeholder="Enter Email" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <com:TLabel ForControl="InputEmailSecond" Text="Confirm Email"/> <div class="input-group"> <com:TActiveTextBox CssClass="form-control" Id="InputEmailSecond" Attributes.Placeholder="Confirm Email" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <com:TLabel ForControl="InputMessage" Text="Enter Message"/> <div class="input-group"> <com:TActiveTextBox Id="InputMessage" CssClass="form-control" TextMode="MultiLine" Rows="5" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> </com:TView> <com:TView> <div class="form-group"> <com:TLabel ForControl="NewsletterFrecuency" Text="Which frecuency do you want to receive our Newsletter?"/> <div class="input-group"> <com:TActiveDropDownList Id="NewsletterFrecuency" CssClass="form-control"> <com:TListItem Value="" Text="Select a frecuency" Selected="true"/> <com:TListItem Value="1" Text="Daily"/> <com:TListItem Value="2" Text="Weekly"/> <com:TListItem Value="3" Text="Monthly"/> </com:TActiveDropDownList> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <label>Which are your areas of interest?</label> <div class="checkbox"> <label><com:TActiveCheckBox Id="PhpChecked"/> PHP</label> </diV> <div class="checkbox"> <label><com:TActiveCheckBox Id="javaChecked"/> Java</label> </div> <div class="checkbox"> <label><com:TActiveCheckBox Id="javascriptChecked"/> Javascript</label> </div> </com:TView> <com:TView> <div class="checkbox"> <label><com:TActiveCheckBox Id="NewsletterChecked"/> I agree to receive the Newsletter <span class="glyphicon glyphicon-asterisk"></label> </div> <div class="checkbox"> <label><com:TActiveCheckBox Id="CommercialChecked"/> I agree to receive interesting commercial information</label> </diV> </com:TView> </com:TActiveMultiView> <div class="well well-sm"><strong><span class="glyphicon glyphicon-asterisk"></span>Required Field</strong></div> <com:TActiveButton Id="Previous" Text="Previous" CssClass="btn btn-info pull-left" OnClick="previousClicked"/> <com:TActiveButton Id="Next" Text="Next" CssClass="btn btn-primary pull-right" OnClick="nextClicked"/> <com:TActiveButton Id="Submit" Text="Submit" CssClass="btn btn-success pull-right" OnClick="submitClicked"/> </div> </div> </div> </div> </com:TForm> </body> </html> |
I have used this template to build my form. Later, we will compare this markup with the original html template, to check the differences.
Home.php file
Now, let’s see the Home.page
file. You can see the code is very short. How is it possible? Because PRADO’s component are very smart. And they save you a lot of code (I will also explain this code later):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<?php class Home extends TPage { public $assetUrl; private $steps; public function __construct() { parent::__construct(); $this->assetUrl = Prado::getApplication()->getAssetManager()->getBaseUrl(); $this->steps = array( 'Introduce your personal details', 'Introduce your preferences', 'Accept the conditions', ); } public function onLoad($param) { parent::onLoad($param); $this->getClientScript()->registerPradoScript('jquery'); $this->getClientScript()->registerScriptFile('bootstrap', $this->assetUrl . '/bootstrap/js/bootstrap.min.js'); } public function previousClicked($sender, $param) { $activeView = $this->MultiView->getActiveViewIndex(); $this->MultiView->setActiveViewIndex($activeView - 1); } public function nextClicked($sender, $param) { $activeView = $this->MultiView->getActiveViewIndex(); $this->MultiView->setActiveViewIndex($activeView + 1); } public function viewChanged($sender, $param) { if ($this->MultiView->ActiveViewIndex == 0) { $this->Previous->setVisible(false); $this->Next->setVisible(true); $this->Submit->setVisible(false); } elseif ($this->MultiView->ActiveViewIndex == 2) { $this->Previous->setVisible(true); $this->Next->setVisible(false); $this->Submit->setVisible(true); } else { $this->Previous->setVisible(true); $this->Next->setVisible(true); $this->Submit->setVisible(false); } $stepNumber = $this->MultiView->ActiveViewIndex; $stepTitle = 'Step ' . ($stepNumber + 1) . ': ' . $this->steps[$stepNumber]; $this->ProgressTitle->setText($stepTitle); $this->StepsProgress->Options->Value = $stepNumber + 1; $this->Title->setText($stepTitle); } public function submitClicked($sender, $param) { // very soon } } |
Home.page in detail
When the user requested an URL, the PRADO framework will calculate the requested page and will load the associated .page
. This .page
file has all the necessary HTML markup (and some intelligence).
Let’s read together the full Home.page
file in detail:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html> <com:THead> <meta charset="utf-8" /> <title>Registration form Template | PrepBootstrap</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="<%= $this->assetUrl . '/bootstrap/css/bootstrap.min.css'%>" /> <link rel="stylesheet" type="text/css" href="<%= $this->assetUrl . '/font-awesome/css/font-awesome.min.css'%>" /> <style type="text/css"> span.progress-step { position:absolute; width: 100%; text-align: center; background-color: transparent; } div.step-progress { margin-bottom:20px; } </style> </com:THead> |
As you can see, it begins like any HTML5 standard document. But I have replaced the <head> tag for a PRADO THead
component (we know it’s a PRADO component because it has the namespace com:
). This PRADO component will render the <head> tag.
Inside this component, there are some standard html tags (meta
, title
, link
). But, wait! we can read href="<%= $this->assetUrl . '/bootstrap/css/bootstrap.min.css'%>"
. What does it mean? The <%= ... %>
tag is a way to ask PRADO «please, evaluate the expression we are passing inside this tag and render it into the final page». In this case, we need to evaluate and render the expression $this->assetUrl . '/bootstrap/css/bootstrap.min.css'
. The object $this
, in this context, refers to the page object itself. We will see this property page->assetUrl
later when we read the Home.php
file. This expression will return where our public resources are hosted. I have chosen this way to configure where the additional public resources we need for our page (bootstrap and font-awesome files) are hosted.
1 2 3 4 5 6 7 |
<body> <com:TForm> <div class="container"> <div class="page-header"> <h1><a href="/">jmjg.es</a> - PRADO Multi Step Registration form</h1> </div> <div class="container"> |
It continues with a standard body
tag, followed by a PRADO TForm
component. It’s mandatory to have this component (and only one instance) inside every page. It will render a standard form
tag. I also encourage you to avoid to write any other form
tag.
1 2 3 4 5 6 7 |
<div class="row step-progress"> <div class="col-lg-12"> <com:TJuiProgressbar Id="StepsProgress" Options.Max="3" Options.Value="1"> <com:TActiveLabel Id="ProgressTitle" CssClass="progress-step" Text=""/> </com:TJuiProgressbar> </div> </div> |
Now, PRADO’s magic begins: we write a new TJuiProgressbar
component. It will render a fully working jQuery-UI Progressbar. In this example, we assign its properties Id
, Options.Max
and Options.Value
(you can read the component’s documentation here).
The Id property
Along this example, we will create a lot of PRADO components. We will assign the Id
property to several of them. This property is associated to the component and it will give us a way to point to each specific component from our php code (so we can manipulate the components from the server side). Please, don’t confuse this property to the html id
attribute (PRADO will generate an id
attribute for every rendered html element but its value may not be the same to this Id
property).
Some PRADO components may include inside them some other components. In this case, our TJuiProgressbar
component includes a TActiveLabel
component inside. This component will render a label
element (o a span
element, depending on how you configure it). Its initial Text
property is empty (but we will change it from our php code). We have also assigned its CssClass
property (to assign a css class to our rendered component). You can read about the TActiveLabel
component here.
1 2 3 4 5 6 7 8 9 10 |
<div class="col-lg-6 col-lg-offset-3"> <h3><com:TActiveLabel Id="Title" Text=""/></h3> <com:TActiveMultiView Id="MultiView" ActiveViewIndex="0" OnActiveViewChanged="viewChanged"> <com:TView> </com:TView> <com:TView> </com:TView> <com:TView> </com:TView> </com:TActiveMultiView> |
Next, we have a new TactiveLabel
(with Id
«Title», remind this) and a new type of component: a TActiveMultiView
component. This component has three TView
components inside. The TActiveMultiView
component is responsible to render only one of its TView
children at a time (you can check its documentation here). We have configured to show first our first TView
(ActiveViewIndex
is «0»). We have also configured its OnActiveViewChanged
property to «viewChanged» (we’ll come to this property later, when we study the Home.php file).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<com:TView> <div class="form-group"> <com:TLabel ForControl="InputName" Text="Enter Name"/> <div class="input-group"> <com:TActiveTextBox CssClass="form-control" Id="InputName" Attributes.Placeholder="Enter Name" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <com:TLabel ForControl="InputEmailFirst" Text="Enter Email"/> <div class="input-group"> <com:TActiveTextBox CssClass="form-control" Id="InputEmailFirst" Attributes.Placeholder="Enter Email" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <com:TLabel ForControl="InputEmailSecond" Text="Confirm Email"/> <div class="input-group"> <com:TActiveTextBox CssClass="form-control" Id="InputEmailSecond" Attributes.Placeholder="Confirm Email" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <com:TLabel ForControl="InputMessage" Text="Enter Message"/> <div class="input-group"> <com:TActiveTextBox Id="InputMessage" CssClass="form-control" TextMode="MultiLine" Rows="5" Attributes.Required=""/> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> </com:TView> |
The content of the three TView
components is very similar: they have inside the necessary PRADO components to render the html fields for each one of our three steps. This first TView
, for example, contains the fields «Name», «Email», «Confirm Email» and «Messsage». Let’s compare this markup with the original html markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<div class="form-group"> <label for="InputName">Enter Name</label> <div class="input-group"> <input type="text" class="form-control" name="InputName" id="InputName" placeholder="Enter Name" required> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <label for="InputEmail">Enter Email</label> <div class="input-group"> <input type="email" class="form-control" id="InputEmailFirst" name="InputEmail" placeholder="Enter Email" required> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <label for="InputEmail">Confirm Email</label> <div class="input-group"> <input type="email" class="form-control" id="InputEmailSecond" name="InputEmail" placeholder="Confirm Email" required> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> <div class="form-group"> <label for="InputMessage">Enter Message</label> <div class="input-group"> <textarea name="InputMessage" id="InputMessage" class="form-control" rows="5" required></textarea> <span class="input-group-addon"><span class="glyphicon glyphicon-asterisk"></span></span> </div> </div> |
You can see the markup is very similar, except we have replaced the html label
, input
and textarea
elements for its equivalent PRADO components TLabel
and TActiveTextBox
:
TActiveLabel vs TLabel
On the first part of our page, we used a TActiveLabel
component. Now, we are using a TLabel
. What’s the difference? To understand its difference I have to explain a little about PRADO’s evolution.
To implement its communication between the components (TLabel, TTextbox, TButton, …) rendered to the browser and the server side, first versions of PRADO used standard POST requests. This mechanism has some limitations. Thankfully, as browsers and javascript evolved, PRADO added a new communication protocol based on AJAX calls. To keep backward compatibilty, PRADO developers added a new generation of AJAX enabled components (TActiveLabel, TActiveTextbox, TActiveButton, …).
You can still use the «classic» components as long you don’t need AJAX interactions with them (that is, they are rendered when the page is first loaded and you don’t need to modify them during your AJAX interactions).
We will use some PRADO components:
- The
TActiveTextbox
component will render aninput
html element or atextarea
element (when itsTextMode
property is set to «Multiline»). - The
TActiveDropdownList
component will render aselect
html element. - The
TActiveCheckbox
component will render aninput
html element of typecheck
.
1 2 3 4 5 6 7 8 9 10 11 12 |
</com:TActiveMultiView> <div class="well well-sm"><strong><span class="glyphicon glyphicon-asterisk"></span>Required Field</strong></div> <com:TActiveButton Id="Previous" Text="Previous" CssClass="btn btn-info pull-left" OnClick="previousClicked"/> <com:TActiveButton Id="Next" Text="Next" CssClass="btn btn-primary pull-right" OnClick="nextClicked"/> <com:TActiveButton Id="Submit" Text="Submit" CssClass="btn btn-success pull-right" OnClick="submitClicked"/> </div> </div> </div> </div> </com:TForm> </body> </html> |
After the TActiveMultiView
component we have three TActiveButton
components. Each one will render an input
html element (of type submit
). You can see each component has defined an OnClick
property. We will see their purpose when we read our Home.php file.
Home.php in detail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php class Home extends TPage { public $assetUrl; private $steps; public function __construct() { parent::__construct(); $this->assetUrl = Prado::getApplication()->getAssetManager()->getBaseUrl(); $this->steps = array( 'Introduce your personal details', 'Introduce your preferences', 'Accept the conditions', ); } |
We define a new Home
class which extends the PRADO’s TPage
class. This way, our class will have all the properties and methods we need to manage our page.
We also define two properties, one of them is public (assetUrl
) and the other is private (steps
). In our construct method, we configure both properties:
steps
is very straightforward: it’s an array of our three steps.assetUrl
is a bit complex: we set it to the public url where PRADO publishes its assets (to do so, we ask it to theAssetManager
component which is inside theApplication
component).
Components in PRADO
PRADO has lots of components. And each component has its own responsibilities. The only problem is that the programmer must be familiar with the catalog of components and which are their responsibilities.
In this example, the AssetManager
component is responsible to publish resources needed for our application (you can read its documentation here). You can get this component through the Application
component. It’s a singleton, with a lot of responsibilies (you can read its documentation here).
Do you remember our Home.page file, we use the expression $this->assetUrl . '/bootstrap/css/bootstrap.min.css'
? Well, I hope that you can understand it now: we are accessing our page
‘s assetUrl
property from our Home.page file (because we know it’s the url where we have published our public resources). That’s the reason why I have declared the assetUrl
property as public (so, the Home.page file can access it).
1 2 3 4 5 6 7 |
public function onLoad($param) { parent::onLoad($param); $this->getClientScript()->registerPradoScript('jquery'); $this->getClientScript()->registerScriptFile('bootstrap', $this->assetUrl . '/bootstrap/js/bootstrap.min.js'); } |
Next we define an onLoad
method. This method is inherited from our TPage
parent class. PRADO pages are loaded and rendered following predefined stages. onLoad
is a hook to the page’s load
stage. In sum, we’re asking PRADO: «when this page enters its Load stage, please execute this additional code». What additional code do we need to execute this time? We need to register the javascript libraries that we need for our application (so, they will be properly rendered when the page’s Render stage comes). To do so:
- We load the standard (it’s included with PRADO) jQuery library (we do so by the
clientScript
component, using itsregisterPradoScript
method). - As bootstrap is not included in PRADO, we have copied its files inside the public assets folder and we register it as an additional library (using again the
clientScript
component, but this time we’re using itsregisterScriptFile
method).
1 2 3 4 5 6 7 8 9 10 11 |
public function previousClicked($sender, $param) { $activeView = $this->MultiView->getActiveViewIndex(); $this->MultiView->setActiveViewIndex($activeView - 1); } public function nextClicked($sender, $param) { $activeView = $this->MultiView->getActiveViewIndex(); $this->MultiView->setActiveViewIndex($activeView + 1); } |
In our Home.page file, when we defined the buttons with Id «Previous» and «Next», we configure their onClick
properties. It’s now time to explain them. Whenever the user clicks on these buttons, the defined method will be called on the server side. On these methods, we will update the current view (changing the TActiveMultiView
‘s ActiveViewIndex
property).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public function viewChanged($sender, $param) { if ($this->MultiView->ActiveViewIndex == 0) { $this->Previous->setVisible(false); $this->Next->setVisible(true); $this->Submit->setVisible(false); } elseif ($this->MultiView->ActiveViewIndex == 2) { $this->Previous->setVisible(true); $this->Next->setVisible(false); $this->Submit->setVisible(true); } else { $this->Previous->setVisible(true); $this->Next->setVisible(true); $this->Submit->setVisible(false); } $stepNumber = $this->MultiView->ActiveViewIndex; $stepTitle = 'Step ' . ($stepNumber + 1) . ': ' . $this->steps[$stepNumber]; $this->ProgressTitle->setText($stepTitle); $this->StepsProgress->Options->Value = $stepNumber + 1; $this->Title->setText($stepTitle); } |
Do you remember our Home.page file, when we defined the TActiveMultiView
component?. We declared a OnActiveViewChanged
property with value «viewChanged». We were asking PRADO: «whenever our TActiveMultiView component changes the active view, please call the method viewChanged defined in our page». What do we do in this method?:
- Depending on the current view active (we can find what it is using the
ActiveViewIndex
property) we enable / disable the appropiate buttons. Each button, is accesible from our Home.php file because we have declared theirId
property. - We update our
TJuiProgressBar
‘sOptions.Value
property to show the current progress on our progress bar. - We also update the
TActiveLabel
components withId
ProgressTitle and Title.
These two last pieces of code are a great example of PRADO’s magic: we can react to changes on the fronted side from the server side, changing the desired elements of the frontend. Whenever the user clicks a navigation button:
- On the server side, the associated method will change the current
ActiveViewIndex
. - This change will trigger the
viewChanged
method. - This method will update the presentation: hide / show the appropriate
TActiveButton
components, update theTJuiProgressbar
component and update theTActiveLabel
components. - All these updates, will be rendered and sent back to the browser.
Conclusions?
I hope you have liked this small demo of PRADO’s features. Of course, there’s room for improvement. But my goal was to have a glance of PRADO using a real page as clear as possible and focusing on the main topics.
I have always liked the way PRADO organizes your code:
- html markup in the .page files, php code in the .php files.
- Excellent integration between frontend and backend.
- You can always encapsulate any piece of code into a component (if you want to reuse it or you think it’s too complex to program it directly inside your .page or .php files).
- Lots of components you can reuse.
The main problem may be that you need to study the documentation to tame all available components and their behaviors (it may take a lot of effort).
Next posts, I would like to finish this example:
- Including field validations.
- Saving the form to a database.
See you soon!!!
Excelente articulo sobre PRADO y animarte a seguir usándolo.
Concuerdo con usted sobre la versatilidad que tiene este Framework, como dices no es el mas popular pero si es uno de los mas flexibles y poderosos.