Home / Section index | www.icosaedro.it | ![]() |
Last updated: 2018-04-16
This tutorial presents the "sticky form" class that allows to build simple, self-contained, event-driven, safe and secure web forms. This class helps implementing the usual "one PHP file per web page" style of developing web sites by taking care to handle secure data storage in the generated page, user's events dispatching to handler methods, and in general giving some defined structure to the code and to the usual input, validation and feedback interaction cycle.
A sticky form web page is a single PHP file containing the definition of a class that extends the it\icosaedro\web\Form class. It is mandatory to define the render method that actually displays the page, create and instance of the class, and invoke the request handler method:
<?php require_once __DIR__ . "/../phplint/stdlib/all.php"; use it\icosaedro\web\Form; class MyForm extends Form { function render() { header("Content-Type: text/html; charset=UTF-8"); echo "<html><body>"; $this->open(); // opens the form element echo "Hello, world!"; $this->close(); // closes the form element echo "</body></html>"; } } (new MyForm(FALSE))->processRequest();
It is really important to note that:
Once invoked from the remote client, our current page does not do very much: it simply displays its greeting message and that is it. To do something a bit more interesting we need to introduce events and controls.
To actually perform a post-back, our form needs at least a button or an anchor to submit the form. Adding a button or an anchor is simple as invoking a method:
$this->button("Do Something", "doSomethingEvent", 1, 2, 3); $this->anchor("Do Something", "doSomethingEvent", 1, 2, 3);
The button() and anchor() methods are inherited from the Form class. What they do is very simple: they display respectively a button and an anchor with caption "Do Something"; if that control is clicked by the user, the specified handler method doSomethingEvent(1,2,3) is invoked with the specified arguments. That handler method can then do something with the form (for example, validating and saving the data on a data base) and then it may re-display the form again by invoking the render method, or it may re-direct the user to some other web page in the usual way. To make things just a little more interesting, the following example also adds two buttons; a feedback about the button that was actually pressed is given on the page:
<?php require_once __DIR__ . "/../phplint/stdlib/all.php"; use it\icosaedro\web\Form; class MyForm extends Form { private $feedback = ""; function render() { header("Content-Type: text/html; charset=UTF-8"); echo "<html><body>"; $this->open(); echo $this->feedback; $this->button("Do This", "buttonEvent", "Do this button!"); $this->button("Do That", "buttonEvent", "Do that button!"); $this->close(); echo "</body></html>"; } /** * @param string $msg */ function buttonEvent($msg) { $this->feedback = $msg; $this->render(); } } (new MyForm(FALSE))->processRequest();
Invokation flow between methods and window.
When the user invokes this page for the first time, here is what happens:
Note that you may add as many buttons and anchors as you want into the page, and each of these controls may have a distinct handler method, or the same handler method can be used with different parameters as we do in the example above. Generally, handler methods perform some change in the internal state of the form and then invoke the render method again to display the effect. Handler methods may also read data entered by the user through the $_POST[] superglobal variable, as usual. But the sticky form class comes with a complete set of control classes that make things much easier.
Under the it\icosaedro\web\controls are available several classes implementing the usual web controls: text input, check boxes, radio buttons, and so on. Sticky form is aware of their existence and will take care to save, resume and retrieve their current state to and from the form automatically for you. Lets take the Line control, for example. The Line control implements the single line text input box, that is the "<input type=text>" element. In the next example we will add a Line control to our form; feedback about the pressed control is appended to the string currently entered by the user. Not a very exciting example, but it demonstrates that data entered by the user are preserved between pages invocations and that our handler method works as expected:
<?php require_once __DIR__ . "/../phplint/stdlib/all.php"; use it\icosaedro\web\Form; use it\icosaedro\web\controls\Line; class MyForm extends Form { /** @var Line */ private $feedback; function __construct() { parent::__construct(FALSE); $this->feedback = new Line($this, "feedback"); } function render() { header("Content-Type: text/html; charset=UTF-8"); echo "<html><body>"; $this->open(); $this->feedback->render(); $this->button("Do This", "buttonEvent", "Do this button!"); $this->button("Do That", "buttonEvent", "Do that button!"); $this->close(); echo "</body></html>"; } /** * @param string $msg */ function buttonEvent($msg) { $s = $this->feedback->getValue(); $this->feedback->setValue($msg.$s); $this->render(); } } (new MyForm())->processRequest();
The final result will be this masterpiece of web page:
By inspecting the source code, we note that:
What happens at runtime is very similar to what happened in the previous example. The only difference is that the request handler invokes the retrieve() method of the Form which, in turn, invokes the retrieve() method on any control contained in it, recursively. In our example there is only one control: it's our $feedback text box. In turn, the retrieve() method of the text box control parses the request and retrieves the value entered by the user.
Two methods, setData() and getData(), give access to the internal storage area of the form. These data are saved along with the state of the form in the page itself, so no session nor cookies are required. Note that the state of the private variables of our object are not saved automatically: you must explicitly save them before leaving the page, and restore them after each post-back. The Form handler provides two event handlers specific for this task: the save() method and the resume() method. The first is invoked by the close() method. As said, there is no need to explicitly save the controls, as each Control already implements its own save/resume methods as needed. It's your own class that might want to save specific custom data. Say for example we have a form to modify a record; we need a way to preserve the record ID between post-backs, so that we may update the proper record ID later when the user finally saves the form contents. To do that we add a private property $record_id and the implementations needed to save and restore its value:
<?php require_once __DIR__ . "/../phplint/stdlib/all.php"; use it\icosaedro\web\Form; class RecordForm extends Form { /** @var int */ private $record_id = -1; function save() { parent::save(); $this->setData("record_id", $this->record_id); } function resume() { parent::resume(); $this->record_id = (int) $this->getData("record_id"); } //... } (new RecordForm())->processRequest();
It is very important to note that our implementations MUST invoke the respective base methods parent::... in order to propagate the event to the nested controls. Most controls actually ignore these events as they do not need them, but complex controls and user's panels may fail to work properly and loose their internal state.
The sticky form provides a very basic but effective tool to give structure to self-contained forms, and it helps building the simplest pages, but joining together several pages in more complex web applications, guiding users through these pages, and passing parameters between them in a safe and secure way requires something else, something that goes beyond the "one PHP file per web page" paradigm and gives a larger view to the application. And it's here that bee tee underscore comes.
→ Continue reading the bt_ tutorial.
Umberto Salsi | Comments | Contact | Site map | Home / Section index |
Still no comments to this page. Use the Comments link above to add your contribute.