I didn't find @mingos's answer complete and had some issues with implementing setting the value. His answer helped a lot with what needed to be extended and changed however. Once I had that start the rest was pretty easy. I just extended ZF1's Select and overrode where I needed:
/**
* Select, now with abbility to specify attributes on <Option>, addMultiOption has new syntax
* @author Seth Miller
*/
class MyNamespace_Form_Element_SelectAttribs extends Zend_Form_Element_Select {
public $options = array();
public $helper = 'selectAttribs';
/**
* Add an option
*
* @param string $option
* @param string $value
* @return Zend_Form_Element_Multi
*/
public function addMultiOption($value, $label = '', $attribs = array()) {
$value = (string) $value;
if (!empty($label)) {
$label = (string) $label;
}
else {
$label = $value;
}
$this->_getMultiOptions();
if (!$this->_translateOption($value, $label)) {
$this->options[$value] = array(
'value' => $value,
'label' => $label
) + $attribs;
}
return $this;
}
/**
* Add many options at once
*
* @param array $options
* @return Zend_Form_Element_Multi
*/
public function addMultiOptions(array $options) {
foreach ($options as $optionKey => $optionProperties) {
if (is_array($optionProperties)
&& array_key_exists('key', $optionProperties)
&& array_key_exists('value', $optionProperties)
) {
if(array_key_exists('key', $optionProperties)) $optionKey = $optionProperties['key'];
$this->addMultiOption($optionKey, $optionProperties['value'], $optionProperties['attribs']);
}
else {
$this->addMultiOption($optionKey, $optionProperties);
}
}
return $this;
}
public function isValid($value, $context = null)
{
if ($this->registerInArrayValidator()) {
if (!$this->getValidator('InArray')) {
$multiOptions = $this->getMultiOptions();
$options = array();
foreach ($multiOptions as $optionKey => $optionData) {
// optgroup instead of option label
if (is_array($optionData['options'])) {
$options = array_merge($options, array_keys($optionData['options']));
}
else {
$options[] = $optionKey;
}
}
$this->addValidator(
'InArray',
true,
array($options)
);
}
}
return parent::isValid($value, $context);
}
}
And the view helper:
class MyNamespace_View_Helper_SelectAttribs extends Zend_View_Helper_FormElement {
public function selectAttribs($name, $value = null, $attribs = null, $options = null, $listsep = "<br />\n") {
$info = $this->_getInfo($name, $value, $attribs, $options, $listsep);
extract($info); // name, id, value, attribs, options, listsep, disable
// force $value to array so we can compare multiple values to multiple
// options; also ensure it's a string for comparison purposes.
$value = array_map('strval', (array) $value);
// check if element may have multiple values
$multiple = '';
if (substr($name, -2) == '[]') {
// multiple implied by the name
$multiple = ' multiple="multiple"';
}
if (isset($attribs['multiple'])) {
// Attribute set
if ($attribs['multiple']) {
// True attribute; set multiple attribute
$multiple = ' multiple="multiple"';
// Make sure name indicates multiple values are allowed
if (!empty($multiple) && (substr($name, -2) != '[]')) {
$name .= '[]';
}
}
else {
// False attribute; ensure attribute not set
$multiple = '';
}
unset($attribs['multiple']);
}
// now start building the XHTML.
$disabled = '';
if (true === $disable) {
$disabled = ' disabled="disabled"';
}
// Build the surrounding select element first.
$xhtml = '<select'
.' name="'.$this->view->escape($name).'"'
.' id="'.$this->view->escape($id).'"'
.$multiple
.$disabled
.$this->_htmlAttribs($attribs)
.">\n ";
// build the list of options
$list = array();
$translator = $this->getTranslator();
foreach ((array) $options as $optionKey => $optionData) {
if (isset($optionData['options'])) {
$optDisable = '';
if (is_array($disable) && in_array($optionData['value'], $disable)) {
$optDisable = ' disabled="disabled"';
}
if (null !== $translator) {
$optValue = $translator->translate($optionData['value']);
}
$optId = ' id="'.$this->view->escape($id).'-optgroup-'
.$this->view->escape($optionData['value']).'"';
$list[] = '<optgroup'
.$optDisable
.$optId
.' label="'.$this->view->escape($optionData['value']).'">';
foreach ($optionData['options'] as $optionKey2 => $optionData2) {
$list[] = $this->_build($optionKey2, $optionData2, $value, $disable);
}
$list[] = '</optgroup>';
}
else {
$list[] = $this->_build($optionKey, $optionData, $value, $disable);
}
}
// add the options to the xhtml and close the select
$xhtml .= implode("\n ", $list)."\n</select>";
return $xhtml;
}
/**
* Builds the actual <option> tag
*
* @param string $value Options Value
* @param string $label Options Label
* @param array $selected The option value(s) to mark as 'selected'
* @param array|bool $disable Whether the select is disabled, or individual options are
* @return string Option Tag XHTML
*/
protected function _build($optionKey, $optionData, $selected, $disable)
{
if (is_bool($disable)) {
$disable = array();
}
$opt = '<option';
foreach ($optionData as $attrib => $attribValue) {
$opt .= ' '.$this->view->escape($attrib).'="'.$this->view->escape($attribValue).'"';
}
// selected?
if (in_array((string) $optionData['value'], $selected)) {
$opt .= ' selected="selected"';
}
// disabled?
if (in_array($optionData['value'], $disable)) {
$opt .= ' disabled="disabled"';
}
$opt .= '>' . $this->view->escape($optionData['label']) . "</option>";
return $opt;
}
}
And implementation in a form would be something like:
$selectElement = new MyNamespace_Form_Element_SelectAttribs('selectElementName');
$selectElement->addMultiOption($value, $label, array('data-custom' => 'custom data embedded in option tag.');
I hope that helps someone. Thanks.
data-type
which is perfectly valid in HTML5. The actual attribute name is arbitrary in relation to this question – Superstitious