Magento 2.1.2 Ui- Component formElement select from multiple dropdowns
-
06-10-2020 - |
Question
I have two drop downs one for Hours and one for Minutes.I have managed to display the drop down for hours
But any one could assist - how do I display a drop-down for minutes,next to hours in th Ui Component form? like the one in the image.
<field name="start_date">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">NameSpace\ModuleName\Model\Xyz\Source\Hours</item>
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Monday Opening Time</item>
<item name="visible" xsi:type="boolean">true</item>
<item name="dataType" xsi:type="string">number</item>
<item name="formElement" xsi:type="string">select</item>
<item name="source" xsi:type="string">item</item>
<item name="dataScope" xsi:type="string">start_date</item>
<item name="sortOrder" xsi:type="number">220</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
</argument>
</field>
My Model returns the hours values
public function getHours()
{
$availableOptions = [
'0' => '00',
'1' => '01',
'2' => '02',
'3' => '03',
'4' => '04',
'5' => '05',
'6' => '06',
'7' => '07',
'8' => '08',
'9' => '09',
'10' => '10',
'11' => '11',
'12' => '12',
'13' => '13',
'14' => '14',
'15' => '15',
'16' => '16',
'17' => '17',
'18' => '18',
'19' => '19',
'20' => '20',
'21' => '21',
'22' => '22',
'23' => '23',
];
return $availableOptions;
}
Solution
Here's a way to do it by making a custom ui component. It requires the backing field to be a varchar.
Define the ui component in Your_Module/view/base/web/js/form/element/time.js
:
define([
'Magento_Ui/js/form/element/abstract'
], function (AbstractElement) {
'use strict';
return AbstractElement.extend({
defaults: {
elementTmpl: 'Your_Module/form/time'
},
initialize: function () {
this._super();
this.hours = '00';
this.minutes = '00';
this.observe(['hours', 'minutes']);
var value = this.value();
this.hours(value.slice(0,2));
this.minutes(value.slice(2));
},
userChanges: function () {
this._super();
this.value(this.hours() + this.minutes());
},
hoursOpts: (function () {
var opts = [];
for (var i=0; i<24; i++) {
opts.push({
label: i.toString(),
value: ('0' + i).slice(-2)
})
}
return opts;
})(),
minutesOpts: (function () {
var opts = [];
for (var i=0; i<60; i++) {
opts.push({
label: ('0' + i).slice(-2),
value: ('0' + i).slice(-2)
})
}
return opts;
})()
});
});
and the template, in Your_Module/view/base/web/template/form/time.html
:
<select class="admin__control-select"
data-bind="
optgroup: hoursOpts,
optionsValue: 'value',
optionsText: 'label',
value: hours,
event: {change: userChanges}"/>
<select class="admin__control-select"
data-bind="
optgroup: minutesOpts,
optionsValue: 'value',
optionsText: 'label',
value: minutes,
event: {change: userChanges}"/>
Use it in your form xml like this:
<field name="start_date">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Monday Opening Time</item>
<item name="component" xsi:type="string">Your_Module/js/form/element/time</item>
<item name="visible" xsi:type="boolean">true</item>
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">input</item>
<item name="source" xsi:type="string">item</item>
<item name="dataScope" xsi:type="string">start_date</item>
<item name="sortOrder" xsi:type="number">220</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
</argument>
</field>
The value from the two drop downs is merged to produce a string like '0130'
to represent the time 1:30
, so your data type needs to be varchar otherwise the leading '0' will be dropped.
OTHER TIPS
I use custom form element to achieve it.
namespace VendorName\ModuleName\Block\Widget\Form\Element;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Framework\Data\Form\Element\Factory as ElementFactory;
use Magento\Framework\Data\Form\Element\CollectionFactory;
use Magento\Framework\Escaper;
/**
* Class Time.
*/
class Time extends AbstractElement
{
/**
* Constructor.
*
* @param ElementFactory $elementFactory
* @param CollectionFactory $collectionFactory
* @param Escaper $escaper
* @param array $data
*/
public function __construct(
ElementFactory $elementFactory,
CollectionFactory $collectionFactory,
Escaper $escaper,
array $data = []
) {
parent::__construct($elementFactory, $collectionFactory, $escaper, $data);
$this->setType('time');
}
/**
* {@inheritdoc}
*/
public function getElementHtml()
{
$this->addClass('select admin__control-select');
$defaultValues = [
'time' => '00:00:00',
'hour' => '00',
'minute' => '00',
'second' => '00',
];
$values = $this->getValue();
if (!$values) {
$values = $defaultValues;
} else {
$time = explode(':', $values);
$values = [
'time' => $values,
'hour' => $time[0],
'minute' => $time[1],
'second' => $time[2],
];
}
// value container element
$html = '<input type="hidden" id="' . $this->getHtmlId() . '" name="' . $this->getName()
. '" value="' . $values['time'] . '" '. $this->_getUiId() . '/>' . PHP_EOL
;
// hours control
$html .= '<select style="width:80px" id="' . $this->getHourControlHtmlId() . '" '
. $this->serialize($this->getControlHtmlAttributes()) . ' title="' . __('hours') . '" '
. $this->_getUiId('hour') . '>' . PHP_EOL
;
for ($i = 0; $i < 24; $i++) {
$hour = str_pad($i, 2, '0', STR_PAD_LEFT);
$html .= '<option'
. ' value="' . $hour . '"'
. ($values['hour'] == $i ? ' selected="selected"' : '') . '>'
. $hour
. '</option>' . PHP_EOL
;
}
$html .= '</select>' . PHP_EOL;
// minutes control
$html .= ': <select style="width:80px" id="' . $this->getMinuteControlHtmlId() . '" '
. $this->serialize($this->getControlHtmlAttributes()) . ' title="' . __('minutes') . '" '
. $this->_getUiId('minute') . '>' . PHP_EOL
;
for ($i = 0; $i < 60; $i++) {
$minute = str_pad($i, 2, '0', STR_PAD_LEFT);
$html .= '<option'
. ' value="' . $minute . '"'
. ($values['minute'] == $i ? ' selected="selected"' : '') . '>'
. $minute
. '</option>' . PHP_EOL
;
}
$html .= '</select>' . PHP_EOL;
// seconds control
$html .= ': <select style="width:80px" id="' . $this->getSecondControlHtmlId() . '" '
. $this->serialize($this->getControlHtmlAttributes()) . ' title="' . __('seconds') . '" '
. $this->_getUiId('second') . '>' . PHP_EOL
;
for ($i = 0; $i < 60; $i++) {
$second = str_pad($i, 2, '0', STR_PAD_LEFT);
$html .= '<option'
. ' value="' . $second . '"'
. ($values['hour'] == $i ? ' selected="selected"' : '') . '>'
. $second
. '</option>' . PHP_EOL
;
}
$html .= '</select>' . PHP_EOL;
$html .= $this->getAfterElementHtml();
$html .= $this->getAfterElementJs();
return $html;
}
/**
* Get after element JS.
*
* @return string
*/
public function getAfterElementJs()
{
$js = '
<script type="text/javascript">
require(["jquery"], function ($) {
var onTimeContainerChange = function () {
var time = $("#' . $this->getHtmlId() . '").val();
var timeArray = time.split(":");
$("#' . $this->getHourControlHtmlId() . '").val(timeArray[0]);
$("#' . $this->getMinuteControlHtmlId() . '").val(timeArray[1]);
$("#' . $this->getSecondControlHtmlId() . '").val(timeArray[2]);
};
$(document).ready(onTimeContainerChange);
$("#' . $this->getHtmlId() . '").change(onTimeContainerChange);
var onTimeControlChange = function () {
var time = $("#' . $this->getHourControlHtmlId() . '").val()
+ ":" + $("#' . $this->getMinuteControlHtmlId() . '").val()
+ ":" + $("#' . $this->getSecondControlHtmlId() . '").val()
;
$("#' . $this->getHtmlId() . '").val(time);
}
$("'
. '#' . $this->getHourControlHtmlId() . ','
. '#' . $this->getMinuteControlHtmlId() . ','
. '#' . $this->getSecondControlHtmlId()
. '").change(onTimeControlChange);
});
</script>
';
return $js;
}
/**
* Get hour control html id prefix.
*
* @return string
*/
protected function getHourControlHtmlId()
{
return $this->getHtmlId() . '_hour_control';
}
/**
* Get minute control html id prefix.
*
* @return string
*/
protected function getMinuteControlHtmlId()
{
return $this->getHtmlId() . '_minute_control';
}
/**
* Get second control html id prefix.
*
* @return string
*/
protected function getSecondControlHtmlId()
{
return $this->getHtmlId() . '_second_control';
}
/**
* Get control html attributes.
*
* @return array
*/
protected function getControlHtmlAttributes()
{
$propertiesToClear = ['title'];
$htmlAttributes = $this->getHtmlAttributes();
return array_diff($htmlAttributes, $propertiesToClear);
}
}
Customize it to fit your requirements, for example, removing the second field.
How to use:
use VendorName\ModuleName\Block\Widget\Form\Element\Time as TimeElement;
$form->addType('custom_time', TimeElement::class);
$form->addField(
'[id_of_your_field]',
'custom_time',
[
... // data
]
);