I've recently bought angular theme admin.io (https://themeforest.net/item/adminio-responsive-material-design-admin/10761963) which I would like to use with PHP Nette framework. Everything goes well with text inputs and buttons by manual rendering. But I got stuck with select element. It should look like this:
<md-select ng-model="someVal" name="priority" placeholder="Zvolte prioritu">
<md-option value="1">Urgent</md-option>
<md-option value="2">Vysoká</md-option>
<md-option value="3">Střední</md-option>
<md-option value="4">Nízká</md-option>
</md-select>
but Nette renders it as basic <select>
. I could render select with <md-select n:name="">
, but there is problem with rendering options. Is there any way how to manually render <option>
as I did with select and whole form, or can I use my own template for that form, or please, someone knows, how to make this work? Thank you!
This will be little difficult. Nette SelectBox isn't built for this. You need to construct your own Control. You can use Nette\Forms\Controls\SelectBox
as template and make few adjustments:
<?php
namespace App\Forms;
use Nette;
/**
* Select box control that allows single item selection.
*/
class MdSelectBox extends Nette\Forms\Controls\ChoiceControl
{
/** validation rule */
const VALID = ':selectBoxValid';
/** @var array of option / optgroup */
private $options = [];
/** @var mixed */
private $prompt = false;
/** @var array */
private $optionAttributes = [];
public function __construct($label = null, array $items = null)
{
parent::__construct($label, $items);
$this->setOption('type', 'md-select');
$this->addCondition(Nette\Forms\Form::BLANK)
->addRule([$this, 'isOk'], Nette\Forms\Validator::$messages[self::VALID]);
}
/**
* Sets first prompt item in select box.
* @param string|object
* @return static
*/
public function setPrompt($prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Returns first prompt item?
* @return mixed
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Sets options and option groups from which to choose.
* @return static
*/
public function setItems(array $items, $useKeys = true)
{
if (!$useKeys) {
$res = [];
foreach ($items as $key => $value) {
unset($items[$key]);
if (is_array($value)) {
foreach ($value as $val) {
$res[$key][(string) $val] = $val;
}
} else {
$res[(string) $value] = $value;
}
}
$items = $res;
}
$this->options = $items;
return parent::setItems(Nette\Utils\Arrays::flatten($items, true));
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
$items = $this->prompt === false ? [] : ['' => $this->translate($this->prompt)];
foreach ($this->options as $key => $value) {
$items[is_array($value) ? $this->translate($key) : $key] = $this->translate($value);
}
return Helpers::createSelectBox(
$items,
[
'disabled:' => is_array($this->disabled) ? $this->disabled : null,
] + $this->optionAttributes,
$this->value
)->addAttributes(parent::getControl()->attrs);
}
/**
* @return static
*/
public function addOptionAttributes(array $attributes)
{
$this->optionAttributes = $attributes + $this->optionAttributes;
return $this;
}
/**
* @return bool
*/
public function isOk()
{
return $this->isDisabled()
|| $this->prompt !== false
|| $this->getValue() !== null
|| !$this->options
|| $this->control->size > 1;
}
}
You also need to rewrite Nette\Forms\Helpers
class, which is main reason you can't achieve this with Nette\Forms\Controls\SelectBox
:
<?php
namespace App\Forms;
use Nette;
use Nette\Utils\Html;
/**
* Forms helpers.
*/
class Helpers
{
use Nette\StaticClass;
/**
* @return Html
*/
public static function createSelectBox(array $items, array $optionAttrs = null, $selected = null)
{
if ($selected !== null) {
$optionAttrs['selected?'] = $selected;
}
list($optionAttrs, $optionTag) = self::prepareAttrs($optionAttrs, 'md-option');
$option = Html::el();
$res = $tmp = '';
foreach ($items as $group => $subitems) {
if (is_array($subitems)) {
$res .= Html::el('md-optgroup')->label($group)->startTag();
$tmp = '</md-optgroup>';
} else {
$subitems = [$group => $subitems];
}
foreach ($subitems as $value => $caption) {
$option->value = $value;
foreach ($optionAttrs as $k => $v) {
$option->attrs[$k] = isset($v[$value]) ? $v[$value] : null;
}
if ($caption instanceof Html) {
$caption = clone $caption;
$res .= $caption->setName('md-option')->addAttributes($option->attrs);
} else {
$res .= $optionTag . $option->attributes() . '>'
. htmlspecialchars((string) $caption, ENT_NOQUOTES, 'UTF-8')
. '</md-option>';
}
if ($selected === $value) {
unset($optionAttrs['selected'], $option->attrs['selected']);
}
}
$res .= $tmp;
$tmp = '';
}
return Html::el('md-select')->setHtml($res);
}
private static function prepareAttrs($attrs, $name)
{
$dynamic = [];
foreach ((array) $attrs as $k => $v) {
$p = str_split($k, strlen($k) - 1);
if ($p[1] === '?' || $p[1] === ':') {
unset($attrs[$k], $attrs[$p[0]]);
if ($p[1] === '?') {
$dynamic[$p[0]] = array_fill_keys((array) $v, true);
} elseif (is_array($v) && $v) {
$dynamic[$p[0]] = $v;
} else {
$attrs[$p[0]] = $v;
}
}
}
return [$dynamic, '<' . $name . Html::el(null, $attrs)->attributes()];
}
}
Then you can use you newly built MdSelectBox
in your form like this:
$options = ['1' => 'Urgent', '2' => 'Medium', '3' => 'Low'];
$form->addComponent(new \App\Forms\MdSelectBox('Priority', $options), 'priority');
Or you can register your new control somewhere to use it directly on Form as addMdSelectBox()
:
ObjectMixin::setExtensionMethod(Container::class, 'addMdSelectbox', function (Container $container, $name, $label = null, array $items = null) {
return $container[$name] = new \App\Forms\MdSelectBox($label, $items);
});