How to design an HTML Form class

Something that may not seems obvious is the need to distinguish required, invalid and feedback for invalid fields. Also, not just output every form element but build a unordered list. A form class that automates everything, but doesn’t offer those functions is worth as much as a paper weight in my opinion. Normally I just hard code forms because I find using objects to represent each and every tag is tedious and It rarely has a advantage.

However, if your set on doing this I would recommend not cramming everything into one class. Have separate classes for separate elements.


class Tag {

}

class InputTag extends Tag {

}

class SelectTag extends Tag {

}

class OptionTag extends Tag {

}

Also, perhaps leave the logic to build the form outside of the tag classes. Instead create a separate class or set of classes to handle the responsibility of managing multiple tags and building the physical representation of the form.

I’ve attempted to build something like this multiple times and each and every time its really more work then what its worth. There are just so many different exceptions and circumstances that it would be very difficult to automate them all. I don’t think create classes to represent each tag is the answer though because its much easier just to write the mark-up. At least that is general what I find.

In the end, HTML is easy to write - it was made for that reason. Making a program to automate writing it isn’t going to be useful because you’ll end up writing more code in the PHP side than you would possibly need on the HTML side.

For example, look at all that code you’ve got for creating the form, and the little code that’s actually output from it. Not really worth it…

all that code you’ve got for creating the form, and the little code that’s actually output from it. Not really worth it…
Good point. But laziness is mother of efficiency :slight_smile:

This something I’ve been exploring recently. Leave the mark-up in the view where it belongs, but create a object that is responsible for the data and configuration of the form based on the field name. $f would be instance of a class that implements the IFormConfig interface.


<?php
interface IFormConfig {

	public function current($name);

	public function required(); // :boolean
	public function valid(); // :boolean
	public function invalid(); // :boolean
	
	public function feedback($echo=true); // :string
	public function value($echo=true); // :string
	public function type($echo=true); // :string
	public function max($echo=true); // :string
	

}
?>

<?php $f->current('pwd'); ?>
<label for="user-pwd"><?php echo $f->required()?'*':''; ?>Password:</label>
<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" id="user-pwd" class="<?php $f->invalid()?'invalid':''; ?>">

<?php $f->current('email'); ?>
<label for="user-email"><?php echo $f->required()?'*':''; ?>Email:</label>
<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" id="user-email" class="<?php $f->invalid()?'invalid':''; ?>">

Something along these lines. Allow your view to do its job and controller to do its but allow them the ability to communicate between each other indirectly through another object.


<?php
interface IForm {

	public function current($name);
	
	public function addRequired($name);
	public function addInvalid($name);
	public function addValid($name);
	
	public function addFeedback($name,$feedback);
	public function addValue($name,$value);
	public function addMax($name,$max);
	public function addType($name,$type);
	public function addName($name,$formName);

	public function required(); // :boolean
	public function valid(); // :boolean
	public function invalid(); // :boolean
	
	public function feedback($echo=true); // :string
	public function value($echo=true); // :string
	public function type($echo=true); // :string
	public function max($echo=true); // :string
	public function name($echo=true); // :string
	

}

class Form implements IForm {

	protected $valid;
	protected $invalid;
	protected $required;
	protected $feedback;
	protected $type;
	protected $value;
	protected $name;
	
	public function __construct() {
	
		$this->_init();
		$this->field = '';
	
	}
	
	protected function _init() {
	
		$this->valid = array();
		$this->invalid = array();
		$this->required = array();
		$this->feedback = array();
		$this->type = array();
		$this->value = array();
		$this->name = array();
	
	}
	
	public function current($field) {
	
		$this->field = $field;
	
	}

	public function addValid($name) {
	
		$this->valid[] = $name;	
	
	}
	
	public function addInvalid($name) {
	
		$this->invalid[] = $name;	
	
	}
	
	public function addRequired($name) {
	
		$this->required[] = $required;
	
	}
	
	public function addFeedback($name,$feedback) {
	
		$this->feedback[$name] = $feedback;
	
	}
	
	public function addType($name,$type) {
	
		$this->type[$name] = $type;
	
	}
	
	public function addValue($name,$value) {
	
		$this->value[$name] = $value;
	
	}
	
	public function addMax($name,$max) {
	
		$this->max[$name] = $max; 
	
	}
	
	public function addName($name,$formName) {
	
		$this->name[$name] = $formName;
	
	}
	
	public function required() {
	
		return in_array($this->field,$this->required);
	
	}
	
	public function invalid() {
	
		return in_array($this->field,$this->invalid);
	
	}
	
	public function valid() {
	
		return in_array($this->field,$this->valid);
	
	}
	
	public function feedback($echo=true) {
	
		$feedback = array_key_exists($this->field,$this->feedback)?$this->feedback[$this->field]:'';
		
		if($echo===true) {
			echo $feedback;
		}
		
		return $feedback;
	
	}
	
	public function value($echo=true) {
	
		$value = array_key_exists($this->field,$this->value)?$this->value[$this->field]:'';
		
		if($echo===true) {
			echo $value;
		}
		
		return $value;	
	
	
	}
	

	public function type($echo=true) {
	
		$type = array_key_exists($this->field,$this->type)?$this->type[$this->field]:'';
		
		if($echo===true) {
			echo $type;
		}
		
		return $type;	
	
	
	}	
	
	
	public function max($echo=true) {
	
		$max = array_key_exists($this->field,$this->max)?$this->max[$this->field]:'';
		
		if($echo===true) {
			echo $max;
		}
		
		return $max;	
	
	
	}
	
	public function name($echo=true) {
	
		$name = array_key_exists($this->field,$this->name)?$this->name[$this->field]:$this->field;
		
		if($echo===true) {
			echo $name;
		}
		
		return $name;	
	
	
	}
	

}

$f = new Form();

$f->addName('user-name','user[user_name]');
$f->addName('pwd','user[pwd]');
$f->addName('email','user[email]');
$f->addName('first-name','user[first_name]');
$f->addName('last-name','user[last_name]');
$f->addName('submit','submit');
$f->addName('reset','reset');

$f->addType('user-name','text');
$f->addType('pwd','password');
$f->addType('email','text');
$f->addType('first-name','text');
$f->addType('last-name','text');
$f->addType('submit','submit');
$f->addType('reset','reset');

$f->addMax('user-name','15');
$f->addMax('pwd','10');
$f->addMax('email','100');
$f->addMax('first-name','20');
$f->addMax('last-name','20');

$f->addValue('submit','Submit Form');
$f->addValue('reset','Reset Form');

$f->addValue('action',$_SERVER['PHP_SELF']);
?>

<?php $f->current('action'); ?>
<form method="post" action="<?php $f->value(); ?>">

	<fieldset>
	
		<ol>
		
			<li>
				
				<?php $f->current('user-name'); ?>
				<label for="user-name"><?php echo $f->required()?'*':''; ?>Name:</label>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" class="<?php echo $f->invalid()?'invalid':''; ?>" id="user-name">
				<?php echo $f->feedback(false)?'<p>'.$f->feedback(false).'</p>':''; ?>
			
			</li>
			
			<li>
				
				<?php $f->current('pwd'); ?>
				<label for="pwd"><?php echo $f->required()?'*':''; ?>Password:</label>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" class="<?php echo $f->invalid()?'invalid':''; ?>" id="pwd">
				<?php echo $f->feedback(false)?'<p>'.$f->feedback(false).'</p>':''; ?>
			
			</li>
			
			<li>
				
				<?php $f->current('email'); ?>
				<label for="email"><?php echo $f->required()?'*':''; ?>Email:</label>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" class="<?php echo $f->invalid()?'invalid':''; ?>" id="email">
				<?php echo $f->feedback(false)?'<p>'.$f->feedback(false).'</p>':''; ?>
			
			</li>
			
			<li>
				
				<?php $f->current('first-name'); ?>
				<label for="first-name"><?php echo $f->required()?'*':''; ?>First Name:</label>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" class="<?php echo $f->invalid()?'invalid':''; ?>" id="first-name">
				<?php echo $f->feedback(false)?'<p>'.$f->feedback(false).'</p>':''; ?>
			
			</li>
			
			<li>
				
				<?php $f->current('last-name'); ?>
				<label for="last-name"><?php echo $f->required()?'*':''; ?>Last name:</label>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>" class="<?php echo $f->invalid()?'invalid':''; ?>" id="last-name">
				<?php echo $f->feedback(false)?'<p>'.$f->feedback(false).'</p>':''; ?>
			
			</li>
		
		</ol>
	
	</fieldset>
	<fieldset>
		<ol>
			<li>
				<?php $f->current('reset'); ?>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>">
			</li>
			<li>
				<?php $f->current('submit'); ?>
				<input type="<?php $f->type(); ?>" name="<?php $f->name(); ?>" value="<?php $f->value(); ?>">
			</li>
		</ol>
	</fieldset>

</form>


<form method="post" action="/tags.php">

	<fieldset>
	
		<ol>
		
			<li>
				
								<label for="user-name">Name:</label>
				<input type="text" name="user[user_name]" value="" class="" id="user-name">
							
			</li>
			
			<li>

				
								<label for="pwd">Password:</label>
				<input type="password" name="user[pwd]" value="" class="" id="pwd">
							
			</li>
			
			<li>
				
								<label for="email">Email:</label>
				<input type="text" name="user[email]" value="" class="" id="email">
							
			</li>
			
			<li>

				
								<label for="first-name">First Name:</label>
				<input type="text" name="user[first_name]" value="" class="" id="first-name">
							
			</li>
			
			<li>
				
								<label for="last-name">Last name:</label>
				<input type="text" name="user[last_name]" value="" class="" id="last-name">
							
			</li>
		
		</ol>

	
	</fieldset>
	<fieldset>
		<ol>
			<li>
								<input type="reset" name="reset" value="Reset Form">
			</li>
			<li>
								<input type="submit" name="submit" value="Submit Form">
			</li>

		</ol>
	</fieldset>

</form>

Nobody loves my code… :weee:

Guys, thanks for the feedback. I’m afraid I have not yet made my case. The reason we are :argue: is because you guys are looking at the problem top down (what does this form do?) while I’m looking at it from the bottom up (what does this tag do?). The way I see it there are tags that do things and should be objects and there are tags that just lie there and should be strings (literals or variables). In the code I’ve written you can freely mix objects and strings. Suppose you wanted to write an unordered list as HTML, it would look like this:

$ul = <<<LISTLIST
<ul id="latestlinks">

	<li><a href="search.php?do=getnew">Latest Forum Posts</a></li>
	<li><a href="search.php?do=noreplies">Unanswered Threads</a></li>
	<li><a href="/marketplace/">Latest Marketplace Listings</a></li>
	<li><a href="/contests/">Latest Design Contests</a></li>
</ul>
LISTLIST;

Now you can use $ul as the content of a tag, say a div:

$div = new Tag('div');
$div->setContent($ul);
echo $div->render();

You can generate $ul or anything else any which way you want, just feed it to my Tag class as the final step to rendering the page. You could have included the div with the list into your body tag:

$body = new Tag('body');
$div->setContent($menuBar, $logo, '<br>', $br, $div, $anotherDiv, $somethingElse, '<hr>', $footer);
echo $body->render();

All of a sudden all you need to build web pages is the Tag->render() function. You build the components in any way you want, writing it as straight html or generating it with some other code. At the end of the day, an html page is just a bunch of nested tags. :wink:

Forms are not the only active tags. Say you have rows in a table with alternating backgrounds, a live object. You can extend the Tag class with an AltRow class that does that functionality for you. Or say you have the need to show random images, you could extend the Tag class with a RandomImg class.

Anyway, I’m excited! :slight_smile:

Some people misunderstood me. I didn’t mean to create the HTML using the DOM, it should already be made thanks to the XML file. What I meant by using the DOM is pulling out a select few values, name and value, and validation type data. The Form object would not need to be concerned with build the form it just needs the values to have something to process when the form is returned. XSLT would take the XML and output the HTML.

It seems just that though that is mixing presentation with logic and there is little benefit. Especially, if you work alongside a designers who need to be able to understand and manipulate the html. Very few designers would be able to manipulate that type of logic.

Nope, it is not presentation or logic, merely data. This is all the XML contains, data. You use the DOM to extract configuration data for the logic, use XSLT to transform the data into presentation (aka HTML).

In the context of the back-end I would consider the HTML presentational. The view is responsible for the visual aspects of the application. The HTML is generally always considered to be part of the view thus it is the presentational when programming server side. If HTML was data then it would go in model. However, we all that the model is responsible for gathering the data and the view is responsible for displaying that data.

Errrm…I said XML not HTML. And yes XML is data and would go in the model, if you feel so inclined.

I was referring to captainccs implementation not yours to clear things up. What he is doing it presentational what you were doing not so much.

You should affix names or quotes to whom you are replying too, oddz. :stuck_out_tongue:

logic_earth:

You’re off the hook but I’m on the hook. :frowning:

Not really on the hook if it works well for you then by all means use it. I’m just saying from my experience it rarely seems worth while to glorify HTML like that.

When you’re inside a form there is a lot of presentation and a lot of data, input tags are both, so it gets difficult to keep them strictly separate. In practice, working with a designer, I would expect him to show me a model of what he wants the form to look like and I would implement both, the look and feel and the functionality, the data.

In practice I would expect the designer to tell me: “Put that form in this here square space.” The form would be in a separate file, an include file, so the designer never have to mess with it. Until now I have managed to keep the functionality reduced to a php include or two. I don’t see that changing.

If you are making good use of CSS, the designer can make changes to the look and feel of the form without ever looking at the code, just by adjusting the various class attributes.

This is something along your lines that I attempted a while back. However, there isn’t really a benefit to it all. Its just not very practical. Its much easier to just write it straight then attempt to represent each tag/element as a object.


$blogTitleLabel = new Element('label');
$blogTitleLabel
->addAttribute('name','blog-title');

$blogTitleInput = new Element('input');
$blogTitleInput
->addAttribute('id','blog-title')
->addAttribute('value','')
->addAttribute('type','text')
->addAttribute('maxlength','40');

$blogTitleListItem = new Element('li');
$blogTitleListItem
->addAttribute('class','blog-title');

$ol1 = new Element('ol');
$ol1
->addAttribute('class','fields');

$ol2 = new Element('ol');
$ol2
->addAttribute('class','controls');

$fieldset1 = new Element('fieldset');
$fieldset1
->addAttribute('class','fields');

$fieldset2 = new Element('fieldset');
$fieldset2
->addAttribute('class','controls');

$form = new Element('form');
$form
->addAttribute('method','post')
->addAttribute('action','/index.php');

$blogTitleListItem
->addChild($blogTitleLabel)
->addChild($blogTitleInput);

$ol1
->addChild($blogTitleListItem);

$fieldset1->addChild($ol1);
$fieldset2->addChild($ol2);

$form
->addChild($fieldset1)
->addChild($fieldset2);

echo $form->render();

Just to show you some output although its invalid because I abandoned this idea and never finished it.


	<form method="post" action="/index.php">
		<fieldset class="fields">
			<ol class="fields">
				<li class="blog-title">
					<label name="blog-title">
					</label>
					<input id="blog-title" value="" type="text" maxlength="40">
					</input>

				</li>
			</ol>
		</fieldset>
		<fieldset class="controls">
			<ol class="controls">
			</ol>
		</fieldset>

	</form>

These are the two primary classes. I would have added classes for SimpleText and ClosedElements, but like I said I abandoned this idea a while back because it didn’t seem to be all that practical.


<?php
class Attribute {

	protected $name;
	protected $value;
	
	protected $attribute;
	
	protected $active = true;
	
	public function __construct($name='',$value='') {
	
		$this->setName($name);
		$this->setValue($value);
	
	}
	
	public function setName($name) {
	
		$this->name = $name;
	
	}
	
	public function setValue($value) {
	
		$this->value = $value;
	
	}
	
	public function getName() {
	
		return $this->name;
	
	}
	
	public function getValue() {
	
		return $this->value;
	
	}
	
	public function addAttribute($name,$value) {
	
		if($this->attribute) {
		
			$this->attribute->addAttribute($name,$value);
		
		} else {
		
			$this->attribute = new Attribute($name,$value);
		
		}
		
		return $this;
	
	}
	
	public function render($all=true) {
	
		$str = $this->active?$this->getName().'="'.$this->getValue().'"':'';
		
		if($all && $this->attribute) {
			
			$str.= ' '.$this->attribute->render();
			
		}
		
		return $str;
	
	}
	
	public function getAttribute($index=1) {
	
		return ($index!=1 && $this->attribute)?$this->attribute->getAttribute(--$index):$this->attribute;
		
	}

}

class Element {

	protected $name;	
	protected $attribute;
	
	protected $sibling;
	protected $child;

	public function __construct($name) {
	
		$this->setName($name);
	
	}
	
	public function addAttribute($name,$value) {
	
		if($this->attribute) {
		
			$this->attribute->addAttribute($name,$value);
		
		} else {
		
			$this->attribute = new Attribute($name,$value);
		
		}
		
		return $this;
	
	}
	
	public function addSibling(Element $element) {
	
		if($this->sibling) {
		
			$this->sibling->addSibling($element);
		
		} else {
		
			$this->sibling = $element;
		
		}
		
		return $this;
	
	}
	
	public function addChild(Element $element) {
	
		if($this->child) {
		
			$this->child->addSibling($element);
		
		} else {
		
			$this->child = $element;
		
		}
		
		return $this;
	
	}
	
	public function setName($name) {
	
		$this->name = $name;
	
	}
	
	public function getName() {
	
		return $this->name;
	
	}
	
	public function render($runner=1) {
	
		$str = implode('',array_fill(0,$runner,"\	")).'<'.$this->getName();	
		$str.=  $this->attribute?' '.$this->attribute->render().'>'."\
":'>'."\
";
		
		if($this->child) {
		
			$str.= $this->child->render(($runner+1));
		
		}
		
		$str.= implode('',array_fill(0,$runner,"\	")).'</'.$this->name.'>'."\
";
		
		if($this->sibling) {
		
			$str.= $this->sibling->render($runner)."\
";
		
		}
		
		return $str;
	
	}
	
	public function getSibling($index=1) {
	
		return ($index!=1 && $this->sibling)?$this->sibling->getSibling(--$index):$this->sibling;
	
	}

	public function getChild($index=1) {
	
		return ($index!=1 && $this->child)?$this->child->getSibling(--$index):$this->child;
	
	}

}

Thanks for that! There certainly are a lot of similarities and I see where you are coming from. So far, I’ve gotten a lot of good milage out of there very specific classes I have been using: select, radiobuttons and checkboxes. These are really tedious to write by hand. If I share your experience I might stop there but I’m trying to redo my procedural framework in OOP and I’m not entirely sure just where I’ll wind up. :wink:

I’ve always found this interesting just never practical. However, what you could do is something similar to the below. Define classes for closed elements and text nodes. Text nodes can’t have things added as children and closed elements can’t either.


class SimpleText extends Element {

	protected $value;

	public function __construct($value) {
	
		$this->value = $value;
	
	}

	final public function addChild(Element $element) {
	
		return $this;
	
	}
	
	final public function getChild($index=1) {
	
		return null;
	
	}
	
	public final function render($runner=1) {
	
		$str = $this->value; 
		
		if($this->sibling) {
		
			$str.= $this->sibling->render($runner)."\
";
		
		}
		
		return $str;	
	
	}

}

class ClosedElement extends Element {

	final public function addChild(Element $element) {
	
		return $this;
	
	}
	
	final public function getChild($index=1) {
	
		return null;
	
	}
	
	final public function render($runner=1) {

		$str = implode('',array_fill(0,$runner,"\	")).'<'.$this->getName();	
		$str.=  $this->attribute?' '.$this->attribute->render().'>'."\
":'>'."\
";	
		
		if($this->sibling) {
		
			$str.= $this->sibling->render($runner)."\
";
		
		}
		
		return $str;		
	
	}

}

Then you could just begin building tag classes and maybe even include a list of valid attributes or something.


class Paragraph extends Element {

	public function __construct() {
	
		parent::__construct('p');
	
	}
	
	public function addChild(SimpleText $text) {
	
		return parent::addChild($text);
	
	}

}

class FormSelect extends Element {

	public function __construct() {
	
		parent::__construct('select');
	
	}
	
	public function addChild(FormOption $option) {
	
		return parent::addChild($option);
	
	}

}

class FormOption extends Element {

	public function __construct() {
	
		parent::__construct('option');
	
	}

	public function addChild(SimpleText $text) {
	
		return parent::addChild($text);
	
	}

}

class FormInput extends ClosedElement {

	public function __construct() {
	
		parent::__construct('input');
	
	}

}

For example, a select element can only have options added as children so the parent addChild is overwritten to only allow option elements be added as children.

You got me interested in this anyway.


$page = new DTStrict4();

$html = new Element('html');

$page->addSibling($html);

$head = new Element('head');
$body = new Element('body');

$html->addChild($head)->addChild($body);

$head->addChild(new HTitle('Project-Pad Project Form'))->addChild(new CSS('/include/css/base.css'));

$form = new HForm($_SERVER['PHP_SELF'],'POST','project-form');
$body->addChild($form);

$projectIdInput = new FormInput();
$projectIdInput->addAttribute('name','project[id]')->addAttribute('type','hidden');

$form->addChild($projectIdInput);

$fieldsFieldset = new Fieldset(null,'fields');
$controlsFieldset = new Fieldset(null,'controls');

$form->addChild($fieldsFieldset)->addChild($controlsFieldset);

$ol1 = new OL('project-data');
$ol2 = new OL('form-controls');

$fieldsFieldset->addChild(new Legend('Project Form'));

$fieldsFieldset->addChild($ol1);
$controlsFieldset->addChild($ol2);

$titleLi = new LI();
$categoryLi = new LI();
$rangeLi = new LI();
$messageLi = new LI();

$ol1->addChild($titleLi)->addChild($categoryLi)->addChild($rangeLi)->addChild($messageLi);

$titleInput = new FormInput();
$titleInput->addAttribute('name','project[title]')->addAttribute('type','text')->addAttribute('maxlength','40')->addAttribute('id','project-title');

$titleLi->addChild(new Label('Project Title:','project-title'))->addChild($titleInput);

$categorySelect = new FormSelect();
$categorySelect->addAttribute('id','project-category')->addAttribute('name','project[category]');
$categoryLi->addChild(new Label('Project Category:','project-category'))->addChild($categorySelect);

$rangeSelect = new FormSelect();
$rangeSelect->addAttribute('id','project-range')->addAttribute('name','project[range]');
$rangeLi->addChild(new Label('Project Range:','project-range'))->addChild($rangeSelect);

$messageTextarea = new Textarea();
$messageTextarea->addAttribute('id','project-message')->addAttribute('name','project[message]')->addAttribute('cols','60')->addAttribute('rows','15');
$messageLi->addChild(new Label('Project Description:','project-message'))->addChild($messageTextarea);

$resetButton = new FormInput();
$resetButton->addAttribute('type','reset')->addAttribute('name','reset')->addAttribute('value','Reset Form');

$submitButton = new FormInput();
$submitButton->addAttribute('type','submit')->addAttribute('name','submit')->addAttribute('value','Submit Form');

$resetLi = new LI();
$submitLi = new LI();

$ol2->addChild($resetLi->addChild($resetButton));
$ol2->addChild($submitLi->addChild($submitButton));

// fill tempate with data
foreach(Category::find('all',array('sort'=>array('name'=>'ASC'))) as $category) {
	$option = new FormOption(ucwords($category->name),$category->id);
	if(isset($_POST['project']['category']) && $_POST['project']['category']==$category->id) { $option->addAttribute('selected','selected'); }
	$categorySelect->addChild($option);
}

foreach(Range::find('all',array('sort'=>array('min_value'=>'ASC'))) as $range) {
	$option = new FormOption(ucwords('$'.$range->min_value.'&nbsp;&ndash;&nbsp;$'.$range->max_value),$range->id);
	if(isset($_POST['project']['range']) && $_POST['project']['range']==$range->id) { $option->addAttribute('selected','selected'); }
	$rangeSelect->addChild($option);
}

if(isset($_POST['project']['message']) && !empty($_POST['project']['message'])) $messageTextarea->addChild(new SimpleText($_POST['project']['message']));
if(isset($_POST['project']['title']) && !empty($_POST['project']['title'])) $titleInput->addAttribute('value',$_POST['project']['title']);

echo $page->render();


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
	<head>
		<title>Project-Pad Project Form</title>
		<link rel="stylesheet" type="text/css" href="/include/css/base.css">

	</head>
	<body>
		<form action="/project_form_dynamic.php" method="POST" id="project-form">
			<input name="project[id]" type="hidden">
			<fieldset class="fields">
				<legend>Project Form</legend>
				<ol id="project-data">
					<li>

						<label name="project-title">Project Title:</label>
						<input name="project[title]" type="text" maxlength="40" id="project-title">
					</li>
					<li>
						<label name="project-category">Project Category:</label>
						<select id="project-category" name="project[category]">
							<option value="5">Advertising</option>

							<option value="4">Copywriting</option>
							<option value="3">Graphic Design</option>
							<option value="6">Marketing</option>
							<option value="2">Mysql</option>
							<option value="1">Web Design</option>
						</select>

					</li>
					<li>
						<label name="project-range">Project Range:</label>
						<select id="project-range" name="project[range]">
							<option value="1">$10&nbsp;&ndash;&nbsp;$50</option>
							<option value="2">$50&nbsp;&ndash;&nbsp;$100</option>

							<option value="3">$100&nbsp;&ndash;&nbsp;$200</option>
							<option value="4">$200&nbsp;&ndash;&nbsp;$500</option>
							<option value="5">$500&nbsp;&ndash;&nbsp;$1000</option>
							<option value="6">$1000&nbsp;&ndash;&nbsp;$2000</option>
							<option value="7">$2000&nbsp;&ndash;&nbsp;$5000</option>

							<option value="8">$5000&nbsp;&ndash;&nbsp;$10000</option>
							<option value="9">$10000&nbsp;&ndash;&nbsp;$50000</option>
							<option value="10">$50000&nbsp;&ndash;&nbsp;$200000</option>
						</select>
					</li>
					<li>

						<label name="project-message">Project Description:</label>
						<textarea id="project-message" name="project[message]" cols="60" rows="15"></textarea>
					</li>
				</ol>
			</fieldset>
			<fieldset class="controls">
				<ol id="form-controls">
					<li>

						<input type="reset" name="reset" value="Reset Form">
					</li>
					<li>
						<input type="submit" name="submit" value="Submit Form">
					</li>
				</ol>
			</fieldset>
		</form>
	</body>

</html>

Basically, it will not only build all the HTML, but do in a easy to read manor also. I can’t stand auto generators that just result in one long string.

Example of some of the subclasses I’ll leave the rest up to you if you feel so inclined to create something similar:


class Paragraph extends Element {

	public function __construct($text=null) {
	
		parent::__construct('p');
		
		if($text) {
			$this->addChild(new SimpleText($text));
		}
	
	}
	
	public function addChild(SimpleText $text) {
	
		return parent::addChild($text);
	
	}

}

class FormSelect extends Element {

	public function __construct() {
	
		parent::__construct('select');
	
	}
	
	public function addChild(FormOption $option) {
	
		return parent::addChild($option);
	
	}

}

class FormOption extends Element {

	public function __construct($text=null,$value=null) {
	
		parent::__construct('option');
		
		if($text) {
			$this->addChild(new SimpleText($text));
		}
		
		if($value) {
			$this->addAttribute('value',$value);
		}
	
	}

	public function addChild(SimpleText $text) {
	
		return parent::addChild($text);
	
	}

}

class FormInput extends ClosedElement {

	public function __construct() {
	
		parent::__construct('input');
	
	}

}

class Div extends Element {

	public function __construct($id=null,$class=null) {
	
		parent::__construct('div');	
		
		if($id) {
			$this->addAttribute('id',$id);
		}
		
		if($class) {
			$this->addAttribute('class',$class);
		}
	
	}

}

class HForm extends Element {

	public function __construct($action=null,$method=null,$id=null,$class=null) {
	
		parent::__construct('form');
		
		if($action) $this->addAttribute('action',$action); 
		if($method) $this->addAttribute('method',$method); 
		if($id) $this->addAttribute('id',$id); 
		if($class) $this->addAttribute('class',$class); 
	
	}

}

class Fieldset extends Element {

	public function __construct($id=null,$class=null) {
	
		parent::__construct('fieldset');
		
		if($id) $this->addAttribute('id',$id);
		if($class) $this->addAttribute('class',$class);
	
	}

}

class OL extends Element {

	public function __construct($id=null,$class=null) {
	
		parent::__construct('ol');
		
		if($id) $this->addAttribute('id',$id);
		if($class) $this->addAttribute('class',$class);
	
	}
	
	public function addChild(LI $element) {
	
		return parent::addChild($element);
	
	}

}

class UL extends Element {

	public function __construct($id=null,$class=null) {
	
		parent::__construct('ul');
		
		if($id) $this->addAttribute('id',$id);
		if($class) $this->addAttribute('class',$class);
	
	}
	
	public function addChild(LI $element) {
	
		return parent::addChild($element);
	
	}

}

class LI extends Element {

	public function __construct($id=null,$class=null) {
	
		parent::__construct('li');
		
		if($id) $this->addAttribute('id',$id);
		if($class) $this->addAttribute('class',$class);
	
	}

}

Here is an example of using it to create a table component:


// projects table
		
		$filter = array();
		$filter['status<>'] = 0;
		$time = time();
		$projects=
			Project::find(
				'all'
				,array(
					'include'=>array('range','category','user','bids')
					,'condition'=>array('won'=>'Project.id NOT IN (SELECT DISTINCT project_id FROM won_projects)')
					,'conditionMap'=>'{won}'
					,'filter'=>$filter
					,'group'=>'id'
					,'dynamic'=>array(
						'bid_count'=>'COUNT(Bid.project_id)'
						,'days'=>array('TIMESTAMPDIFF(DAY,Project.created,FROM_UNIXTIME(?))',$time)
					)
					,'limit'=>100
					,'offset'=>0
					,'sort'=>array('created'=>'DESC')
				)
				,array()
				,array()
				,array('select'=>'name')
				,array(
					'select'=>'created'
					,'include'=>'user'
					,'require'=>false
					,'condition'=>array('stat'=>'(Bid.status IS NULL OR Bid.status <> 0)')
					,'conditionMap'=>'{stat}'
				)
				,array('select'=>'name','require'=>false)
			);

$projectsTable = new Element('table');
$projectsTable->addAttribute('class','projects');

$projectsTableHead = new Element('thead');
$projectsTableBody = new Element('tbody');

$projectsTable->addChild($projectsTableHead)->addChild($projectsTableBody);

$projectsHeadTitle = new Element('th');
$projectsHeadBids = new Element('th');
$projectsHeadBudget = new Element('th');
$projectsHeadCategory = new Element('th');
$projectsHeadPosted = new Element('th');

$projectsHeadTitle
->addChild(new SimpleText('Title'))
->addAttribute('class','project-title');

$projectsHeadBids
->addChild(new SimpleText('Bids'))
->addAttribute('class','project-bids');

$projectsHeadBudget
->addChild(new SimpleText('Budget'))
->addAttribute('class','project-range');

$projectsHeadCategory
->addChild(new SimpleText('Category'))
->addAttribute('class','project-category');

$projectsHeadPosted
->addChild(new SimpleText('Posted'))
->addAttribute('class','project-created');

$projectsTableHead
->addChild($projectsHeadTitle)
->addChild($projectsHeadBids)
->addChild($projectsHeadBudget)
->addChild($projectsHeadCategory)
->addChild($projectsHeadPosted);

foreach($projects as $project) {

	$row = new Element('tr');
	
	$projectsTableBody
	->addChild($row);
	
	$projectTitle = new Element('td');
	$projectBids = new Element('td');
	$projectBudget = new Element('td');
	$projectCategory = new Element('td');
	$projectPosted = new Element('td');
	
	$row
	->addAttribute('class','project')
	->addChild($projectTitle)
	->addChild($projectBids)
	->addChild($projectBudget)
	->addChild($projectCategory)
	->addChild($projectPosted);
	
	$projectTitleAnchor = new Anchor('/index.php/project/'.$project->id);
	$projectTitleAnchor
	->addChild(new SimpleText($project->title));
	
	$projectTitle
	->addChild($projectTitleAnchor)
	->addAttribute('class','project-title');
	
	$projectBids
	->addChild(new SimpleText($project->bid_count))
	->addAttribute('class','project-bids');
		
	$projectBudget
	->addChild(new SimpleText('$'.$project->range->min_value.'&ndash;$'.$project->range->max_value))
	->addAttribute('class','project-range');
		
	$projectCategory
	->addChild(new SimpleText(ucwords($project->category->name)))
	->addAttribute('class','project-category');
	
	$day = $project->days==0 || $project->days == 1?$project->days==0?'today':'yesterday':$project->days.' days ago';
	$projectPosted
	->addChild(new SimpleText($day))
	->addAttribute('class','project-created');

}

echo "\
".$projectsTable->render();


<table class="projects">
	<thead>
		<th class="project-title">Title</th>

		<th class="project-bids">Bids</th>
		<th class="project-range">Budget</th>
		<th class="project-category">Category</th>
		<th class="project-created">Posted</th>
	</thead>
	<tbody>
		<tr class="project">

			<td class="project-title">
				<a href="/index.php/project/13">My new project *****es!!!</a>
			</td>
			<td class="project-bids">0</td>
			<td class="project-range">$5000&ndash;$10000</td>
			<td class="project-category">Graphic Design</td>

			<td class="project-created">2 days ago</td>
		</tr>
		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/12">new title</a>
			</td>
			<td class="project-bids">0</td>

			<td class="project-range">$50&ndash;$100</td>
			<td class="project-category">Marketing</td>
			<td class="project-created">3 days ago</td>
		</tr>
		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/6">My new project</a>

			</td>
			<td class="project-bids">2</td>
			<td class="project-range">$100&ndash;$200</td>
			<td class="project-category">Copywriting</td>
			<td class="project-created">7 days ago</td>
		</tr>

		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/11">My new project</a>
			</td>
			<td class="project-bids">2</td>
			<td class="project-range">$100&ndash;$200</td>
			<td class="project-category">Copywriting</td>

			<td class="project-created">12 days ago</td>
		</tr>
		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/5">wqdwq d qw d qw d q wd  qw d</a>
			</td>
			<td class="project-bids">0</td>

			<td class="project-range">$200&ndash;$500</td>
			<td class="project-category">Mysql</td>
			<td class="project-created">16 days ago</td>
		</tr>
		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/4">wqdwq d qw d qw d q wd  qw d</a>

			</td>
			<td class="project-bids">0</td>
			<td class="project-range">$200&ndash;$500</td>
			<td class="project-category">Mysql</td>
			<td class="project-created">16 days ago</td>
		</tr>

		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/3">Looking for web designer</a>
			</td>
			<td class="project-bids">1</td>
			<td class="project-range">$1000&ndash;$2000</td>
			<td class="project-category">Graphic Design</td>

			<td class="project-created">17 days ago</td>
		</tr>
		<tr class="project">
			<td class="project-title">
				<a href="/index.php/project/2">Small On going assignment</a>
			</td>
			<td class="project-bids">0</td>

			<td class="project-range">$100&ndash;$200</td>
			<td class="project-category">Copywriting</td>
			<td class="project-created">17 days ago</td>
		</tr>
	</tbody>
</table>