How to design an HTML Form class

Please no :(. I’ve been in the trenches on this one far to often.

[ot]There is actually one thread where someone flat out flamed me for posting some of Ramus’ opinions on the issue. Deep wounds… deep wounds.[/ot] lol
In any case, the argument never goes anywhere. It’s like a massive circular argument … it has no beginning or end… x_x… I’m actually thinking of rewriting smarty in C++ just to end the endless debating… But I’m not that desperate yet >.>

Let’s agree to let each developer use what he finds most suitable and leave it at that. More interesting than the tired argument is your remark about rewriting Smarty in C++. What would that accomplish besides making it faster?

However a form is much more than the HTML.

My form class handles validation by setting $field->validation on the object itself. If you’re just outputting HTML there’s no clean way of doing this.

If you use a as an object you can use the same form for both “Add” and “Edit”.

My Form class looks like this, it’s extended on a per form basis.


abstract class Form {
    private $fields = array();
    abstract public function save($id);
    abstract public function load($id);
    abstract public function insert();

    public function populate($data) {
       ... populate e.g. from $_REQUEST
    }

}

$form->load(123);

which will load the correct record(s) from the database to populate the form.

My forms get auto-populated from $_GET/POST depening on form method if the relevant values are set, and I can then do $form->save($_GET[‘id’]); to save the data for the current record being edited.

This is a far cleaner way of keeping all aspects of form control together.

Tom:

I’m sure to borrow some of your ideas for my Form class. I think what ctx2002 didn’t like was the template class. I’d like to do the job without an additional Template class myself. I think I can get the Form class to do the display as well as the processing.

I’ve messed around a bit with form generation and automatic validation, in an XHTML-based file actually. It was basically the form but with validation as attributes too; these were stripped before output and used by PHP in the validation process.

I even ventured into the automated process via attribute method too, where attributes could make the form auto update, insert into or delete a record from a database table.

Might be an idea for you to try.

For example:

<form action="/someaction" method="post">
    <input type="text" name="Username" format="[A-Za-z ]+" min="3" max="20" />
    <input type="password" name="Password" min="20" />
    <verification>
         <query>SELECT id FROM Users WHERE Username=:Username AND Password = :Password</query>
         <success>SessionSet(array('loggedin', true), array('userID', :id))</success>
         <fail>redirect(HOME)</fail>
    </verification>
</form>

I got it to work, but I didn’t like using it, so scrapped it.

That’s mixing layout and process, no wonder you didn’t like using it. :wink:

Exactly :wink:

Off Topic:

I unsubscribed from this thread by mistake. How do you re-subscribe without posting a reply? :confused:

Off Topic:

Top of the thread view, Thread Tools and ‘Subscribe to this thread’.

Could skip XHTML and go with plain old XML and use XSLT to output (X)HTML using PHP. It is also easy as pie to use the DOM to get what you need as well.

<?xml version="1.0" encoding="UTF-8"?>
<form action="..." method="post">
    <input type="text">
        <name>username</name>
        <label>Username:</label>
        <validation>
            <format>/^\\w[\\w ]+$/</format>
            <!-- Other options too.. -->
        </validation>
    </input>
    <input type="password">
        <name>password</name>
        <label>Password:</label>
    </input>
</form>

It would work for display and entry, but how would you load existing values into it? When it’s posted, you still need an entirely separate PHP script to deal with the posted data.

The DOM would make manipulating the data very very easy, weather it is XML or HTML.

Just a question…why not suggest the original poster simply use DOMDocument to build his output, save the result to string and display that? Try the following and see if you can adjust it to your needs.


 
<?php
final class HTMLForm extends DOMDocument
{
public function __construct($method, $action)
{
parent::__construct("1.0", "utf-8");
$this->loadXML("<form></form>");
$this->documentElement->setAttribute('method', $method);
$this->documentElement->setAttribute('action', $action);
}
public function addButton()
{
$btn = $this->createElement('input');
$btn->setAttribute('type', 'button');
$this->documentElement->appendChild($btn);
return $btn;
}
public function render()
{
echo $this->saveXML($this->documentElement); 
}
}
$form = new HTMLForm("post", "forms.php");
$button = $form->addButton();
$button->setAttribute('value', 'Hello world!');
$form->render();
?>

@serenarules, why is your class “final”? What purpose does that serve other then making it un-extend-able?

LOL! It’s just an example. I just got into writing the the “finals” for an unrelated app today, so my muscle memory took over.

Indeed, why not! Thanks. I played around a bit with it.

<?php
final class HTMLForm extends DOMDocument
{

    public function __construct($method, $action)
    {
    parent::__construct("1.0", "utf-8");
    $this->loadXML("<form></form>");
    $this->documentElement->setAttribute('method', $method);
    $this->documentElement->setAttribute('action', $action);
    }

    public function addButton()
    {
    $btn = $this->createElement('input');
    $btn->setAttribute('type', 'button');
    $this->documentElement->appendChild($btn);
    return $btn;
    }

    public function addButton1($attributes=array())
    {
        $btn = $this->createElement('input');
        foreach($attributes AS $name => $value) {
            $btn->setAttribute($name, $value);
        }
        $this->documentElement->appendChild($btn);
        return $btn;
    }

    public function addTag($tag)
    {
        $tag = $this->createElement($tag);
        $this->documentElement->appendChild($tag);
        return $tag;
    }

    public function render()
    {
    echo $this->saveXML($this->documentElement);
    }

}
$form = new HTMLForm("post", "forms.php");
$button = $form->addButton();
$button->setAttribute('value', 'Hello world!');
$br = $form->addTag('br');
$but = array();
$but['id'] = "b01";
$but['type'] = "button";
$but['name'] = "myButton";
$but['value'] = "Click me!";
$button1 = $form->addButton1($but);
$button1->setAttribute('style', 'color:red;');
$form->render();
?>

Because this is the form to end all forms, the mother of all forms, the last form in the desert. After this form there are no more forms. :rofl:

The one catchpa is that, however you design a dom based engine, you need to call appendChild to attach the new element BEFORE you call setAttribute on it, otherwise, you tempt the wrath of the exception gods.

The DOM method has severe drawbacks with extendability.

In my form class I have elements for Date Picker, Colour Picker, Gmail style File Upload, Spell checked text boxes using HTML richedits. Each of these consists of more than on element. This is possible using objects but will start to become ugly if using the above DOM method.

I’ve been working on this Form class and I thought I’d share what I have so far. At this point I have two classes, a base Tag class and an Input class that extends the Tag class. I expect that I’ll create child classes for the various input devices such as radio buttons, checkboxes, and select. These child classes will get a lot more functionality such as error checking. At this point I don’t see a need to create classes for tags that don’t send input to the server.

BTW, func_num_args() and func_get_arg() are really useful when you have an indeterminate number of arguments. Here are my two classes:

class Tag {

    protected $tag;
    protected $attributes = array();
    protected $content = array();
    protected $xhtml;

    public function __construct($tag, $xhtml = FALSE) {
        $this->tag = $tag;
        $this->xhtml = $xhtml;
    }

    public function setAttribute($name, $value) {
        $this->attributes[] = array($name, $value);
    }

    public function setContent() {
        $i = 0;
        while ($i < func_num_args()) {
            $this->content[] = func_get_arg($i++);
        }
    }

    public function render() {
        $result = "<{$this->tag}";
        if (is_array($this->attributes)) {
            foreach ($this->attributes AS $pair) {
                $result .= " {$pair[0]}='{$pair[1]}'";
            }
        }
        $result .= ">\
";
        foreach($this->content AS $content) {
            $isContent = TRUE;
            if (is_object ($content)) {
                $result .= $content->render();
            } else {
                if ($content != '') {
                    $result .= "{$content} \
";
                } else {
                    $isContent = FALSE;
                }
            }
        }
        if ($isContent) {
            $result .= "</{$this->tag}>\
";
        }
        return $result;
    }

}

class Input extends Tag {

   public function __construct($type, $name = '', $value = '') {
       $this->tag = 'input';
       $this->attributes[] = array('type', $type);
       $this->attributes[] = array('name', $name);
       $this->attributes[] = array('value', $value);
   }

}

Using this code:

$br = new Tag('br');

$month = new Input('text', 'month', $_POST['month']);
$month->setAttribute('size', '2');

$day = new Input('text', 'day', $_POST['day']);
$day->setAttribute('size', '2');

$year = new Input('text', 'year', $_POST['year']);
$year->setAttribute('size', '4');

$submit = new Input('submit', 'submit', 'Submit');

$reset = new Input('reset', '', 'Reset');

$legend = new Tag('legend');
$legend->setContent('Action');

$fieldset = new Tag('fieldset');
$fieldset->setAttribute('style', 'width:300px;');
$fieldset->setContent($legend, $month, $day, $year, $br, $submit, $reset);

$form = new Tag('form');
$form->setAttribute('action', $_SERVER['PHP_SELF']);
$form->setAttribute('method', 'post');
$form->setContent($fieldset);

echo $form->render();

I generated the following form:

<form action='/testing/tag.php' method='post'>
<fieldset style='width:300px;'>
<legend>
Action
</legend>
<input type='text' name='month' value='' size='2'>
<input type='text' name='day' value='' size='2'>
<input type='text' name='year' value='' size='4'>
<br>
<input type='submit' name='submit' value='Submit'>
<input type='reset' name='' value='Reset'>
</fieldset>
</form>