angularjsmaterial-designnette

Use Nette form with material design


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!


Solution

  • 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);
    });