← Login Page | Lazy Objects →
HTML (and the corresponding XHTML) provides elements for configuring a POST request. The <form> element describes the target of the POST request (and allows using a GET instead) using its attributes, and contains <input>, <select> and <textarea> elements which describe the data to be sent (either as hidden elements, or as user-modifiable elements).
The usual approach is to start with the HTML layout:
<html>
<head>
<title>Login Page</title>
</head>
<body>
<!-- Login form -->
<form action="/do-login">
<table id="login-form">
<tr><td colspan="2"><h2>Login</h2></td></tr>
<tr>
<td><label for="login-mail">E-mail:</label></td>
<td><input id="login-mail" name="mail"/></td>
</tr>
<tr>
<td><label for="login-pass">Password:</label></td>
<td><input id="login-pass name="pass" type="password"/></td>
</tr>
<tr>
<td></td>
<td><button type="submit">Login</button></td>
</tr>
<tr>
<td></td>
<td><a href="/reset-password">Forgot your password?</a></td>
</tr>
</table>
</form>
<!-- Sign up form -->
<form action="/do-signup">
<table id="signup-form">
<tr><td colspan="2"><h2>Sign Up</h2></tr></tr>
<tr>
<td><label for="signup-mail">E-mail:</label></td>
<td><input id="signup-mail" name="mail"/></td>
</tr>
<tr>
<td><label for="signup-name">Display name:</label></td>
<td><input id="signup-name" name="name"/></td>
</tr>
<tr>
<td><label for="signup-pass">Password:</label></td>
<td><input id="signup-pass" name="pass" type="password"/></td>
</tr>
<tr>
<td><label for="signup-pass2">(twice)</label></td>
<td><input id="signup-pass2" name="pass2" type="password"/></td>
</tr>
<tr>
<td></td>
<td><button type="submit">Login</button></td>
</tr>
</table>
</form>
</body>
</html>
Then, this layout has to be sliced into elements that are generic (used on every page on the system) and elements that are specific to this page. Every distinct part will be moved to its own view, and specific views may be able to use more generic views to perform part of their work.
In this example, the “table-like form” concept is used twice, so it could be turned into a reusable element. Similarly, the outer layers of the page and the header will probably be used on a lot of other pages,so we can also turn them into a reusable template.
Let’s start with the two-column form tables. Such a form needs an action and a list of fields. The fields can be various input elements, submit buttons, or arbitrary one-cell or two-cell HTML. This is how I expect to be able to defin a two-column form table:
<?php TwoColFormView::Render("/do-login","login", array(TwoColFormView::OneCell ("<h2>Login</h2>"),TwoColFormView::Input ("mail", "E-mail", ""),TwoColFormView::Password ("pass", "Password:"),TwoColFormView::Submit ("Login"),TwoColFormView::TwoCell ("", "<a href='/reset-password'>Forgot your password?</a>") )); ?>
I could have gone with a method chaining approach here, in the same vein as what the Zend Framework does for a lot of things: you create an object, and add member functions which “add” some data to the object and also return the object (which lets you chain the method calls one after another until you reach a final execution method). Here, it would have looked like this:
<?php $view = new TwoColFormView("/do-login","login"); $view -> OneCell ("<h2>Login</h2>") -> Input ("mail", "E-mail", "") -> Password ("pass", "Password:", "") -> Submit ("Login") -> TwoCell ("", "<a href='/reset-password'>Forgot your password?</a>") -> Render(); ?>
That would have required the same amount of implementation code, and it also has some elegant “aha, nice one” factor that makes it attractive to developers, but it would not have been as flexible for the developer as the array version I decided to use: the array-based render function assumes that the form is represented as a two-column table, and every row of that column is specified by an element in the array (the elements being, appropriately, HTML snippets). So, the developer gets the choice of using helper functions from the view to construct the rows, or build the row HTML himself.
Sure, you could have added an “add HTML yourself” function to your method chaining system, but then you would lose the elegance (and the consistency) every time you would use that method. And, of course, the fact that the rows are presented as an array means that all the array functions can be applied, including concatenation, filtering, sorting by keys, and so on.
On to the actual implementation of the TwoColFormView class:
<?php// views/twocolform.phpclass TwoColFormView { public static function Render($action, $id, $fields) { $id = htmlspecialchars($id); $action =htmlspecialchars(DomainUtils::Url($action)); ?><form id="<?="$id-form"?>" action="<?=$action?>"><table><?php foreach ($fields as $field) {if ($field) {?><tr><?=$field?></tr><?php}} ?></table></form><?php } public static function OneCell($html) { return sprintf('<td colspan="2">%s</td>', $html); } public static function TwoCell($lefthtml, $righthtml) { return sprintf('<td>%s</td><td>%s</td>', $lefthtml, $righthtml); }public static function Error($html) {if (!$html) returnnull;return sprintf('<td colspan="2" class="error">%s</td>', $html); }public static function Input($name, $label, $value) { $id = uniqid(); return sprintf('<td><label for="%s">%s</label></td>' . '<td><input id="%s" name="%s" value="%s"/></td>', $id, htmlspecialchars($label), $id, htmlspecialchars($name), htmlspecialchars($value)); } public static function Password($name, $label) { $id = uniqid(); return sprintf('<td><label for="%s">%s</label></td>' . '<td><input id="%s" name="%s" type="password"/></td>', $id, htmlspecialchars($label), $id, htmlspecialchars($name)); } public static function Submit($label) { return sprintf('<button type="submit">%s</button>', htmlspecialchars($label)); } }
Any element of the array may be null or an empty string. These are ignored by the renderer, so that no <tr></tr> will be generated for them. This makes conditional construction of array elements possible, as you can just cond ? value : null an element.
Also, I don’t want to force the user to specify their own identifiers for the fields: as long as they provide the identifier of the form, I can always use jQuery selectors to select the fields by name:
$('#login-form input[name="input"]').value();
A possible extension, if you really want to, would be to add an optional “id” argument to every field-generating function.
This view class sets up the “tradition” for what our views will look like: classes with a lot of helper functions that return strings or output text, and a single Render function that performs the actual rendering by outputting the final text. So, the next step is the page object. A first try would look like this:
<?php // views/plainpage.php class PlainPageView { public static function Render($title, $contents) { $title = htmlspecialchars($title); ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><?php ?><html><head><title><?=$title?></title></head><?php ?><body><?=$contents?></body></html><?php } }
Then, whenever the high-level page layout (or the included stylesheets or javascript) has to be changed, only this file will be affected. After a while, it might even get interesting to add other configuration elements, such as meta keywords.
← Login Page | Lazy Objects →
Hi. I'm Victor Nicollet,
0 Responses to “6. Forms and Views”