PHP drop-down list - Part 6 (Adding Optgroups)

February 17, 2017 by PHP  

In this post we're going to extend our select element to support optgroups and thereby complete our support for all select child elements.

Interestingly enough, the standard ASP.NET web control doesn't support optgroups for some reason, read more about it over here and how to fix it over here.

Firstly, know thy element Winking smile

Attribute Description
disabled Specifies that an option-group should be disabled
label Specifies a label for an option-group
Nothing fancy over here, the PHP implementation looks something like this:

class HtmlOptionGroupElement extends HtmlElement
{
	private $Children;
	public $Disabled;
	public $Label;

	public function __construct($label,
		array $children = [], 
		$disabled = false) 
	{
		$this->Label = $label;
		$this->Disabled = $disabled;
		$this->Children = $children;
	}
	
	public function GetChildren() {
		return $this->Children;
	}
}

The serialization classes for our various strategies will look like this:

class HtmlOptionGroupSerializer 
implements IHtmlElement, IHtmlInnerHtml
{
	protected $element;
	
	public function __construct(HtmlOptionGroupElement $element) {
		$this->element = $element;
	}
	
	public function GetAttributes() {
		return [
			'disabled' => ($this->element->Disabled) ? '' : null,
			'label' => $this->element->Label
		];
	}

	public function GetTagName() {
		return 'optgroup';
	}	
	
	public function GetInnerHtml() {
		return $this->element->GetChildren();
	}
}
class XHtmlOptionGroupSerializer extends HtmlOptionGroupSerializer
{
	public function __construct(HtmlOptionGroupElement $element) {
		$this->element = $element;
	}
	
	public function GetAttributes() {
		return [
			'disabled' => ($this->element->Disabled) ? 'disabled' : null,
			'label' => $this->element->Label
		];
	}
}

Like with the other elements, we add our mappings to our metadata in the HtmlSerializer and XHtmlSerializer like seen below.

class HtmlSerializer
implements IHtmlSerializer
{	
...
	protected function getSerializer($element) {
...
		}  else if ($element instanceof HtmlOptionGroupElement) {
			return new HtmlOptionGroupSerializer($element);
		}
...
	}
...
}
class XHtmlSerializer extends HtmlSerializer
{	
...	
	protected function getSerializer($element) {
...
		} else if ($element instanceof HtmlOptionGroupElement) {
			return new XHtmlOptionGroupSerializer($element);
		}
...
	}
...
}

The biggest changes to the codebase can be seen below, now instead of having a nice flat structure, our optgroups added a little bit of complexity to our uniqueness check.

I was forced to add an OptionValues property, containing a flat version of option values, feels a bit ugly though, perhaps I will revisit these methods in future.

class HtmlSelectElement extends HtmlFormControlElement
{
...
	private $OptionValues = [];
...
	private function setChild($child, $value) {
		$optionValue = (string)$child;
		$this->OptionValues[] = $optionValue;
		$child->Selected = ($optionValue == $value);
		if ($child->Selected) {
			$this->Value = $optionValue;
		}
	}
...
	public function SetValue($value) {
		$this->OptionValues = [];
		foreach($this->Children as $child) {
			if ($child instanceof HtmlOptionElement) {
				$this->setChild($child, $value);
			} else if ($child instanceof HtmlOptionGroupElement) {
				$groupChildren = $child->GetChildren();
				foreach($groupChildren as $groupChild) {
					$this->setChild($groupChild, $value);
				}
			} else {
				throw new \Exception("Type of HtmlOptionElement expected in drop-down list $this->Name");
			}
		}
	}
...
	public function SetChildren(array $children, $value = null)
	{
		$this->Children = $children;
		$this->SetValue($value);
		
		if (count($this->OptionValues) != count(array_unique($this->OptionValues))) {
			throw new \Exception("Non unique values assigned to drop-down list $this->Name");
		}		
	}
}

The SetValue method also now caters for HtmlOptionElements and HtmlOptionGroupElements.

Which also us to do the following:

$select = new HtmlSelectElement('friends', [
	new HtmlOptionElement('Not Selected', 0),
	new HtmlOptionElement('Gerhardt Stander', 1),
	new HtmlOptionElement('Bronwen Murdoch', 2),
	new HtmlOptionGroupElement('Family', [
		new HtmlOptionElement('Jurgens Truter', 4),
		new HtmlOptionElement('Marisa Truter')
	]),
	new HtmlOptionElement('Maree Kleu')
],  'Marisa Truter');

This is the basic flow you would follow if you wanted to add your own custom elements, not much to it Winking smile

In the final part of this series (Part 7), we're going to add a little bit of JavaScript to the equation, e.g. on change do a form submit aka AutoPostback etc.


Leave a Comment