Magento 2.3 – Implement Time Range Picker in Admin system.xml

datepickermagento2.3system.xml

I found one similar solution that almost does what I want.
DateTimePicker System.xml

I have implemented its code and was able to display a DateTime Picker in my admin configuration. However, I would like to implement a Time Range Picker but I don't need a UI component way of implementing it.

Best Answer

If you wish to add time from/to elements as a slider, you can customize an element frontend model and add your own template.

How it will be looking

Here is my example (ported from the regular form to the store configuration section):

Field in the system.xml:

<field id="delivery_time" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
    <label>Delivery Time</label>
    <frontend_model>MageWorx\ExampleConfig\Model\Config\FrontendModel\DeliveryTime</frontend_model>
</field>

Note: real values will be stored with the delivery_time_from and delivery_time_to indexes.

Frontend model, where I have replaced the default output of the element:

<?php

namespace MageWorx\ExampleConfig\Model\Config\FrontendModel;


class DeliveryTime extends \Magento\Config\Block\System\Config\Form\Field
{
    /**
     * Retrieve element HTML markup
     *
     * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
     * @return string
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
    {
        $renderer = $this->getLayout()->createBlock(
            'MageWorx\ExampleConfig\Block\TimeSlider'
        );
        $renderer->setElement($element);

        return $renderer->toHtml();
    }
}

here we just using a block MageWorx\ExampleConfig\Block\TimeSlider instead of general render.

And here is the block with template (where slider was defined):

<?php

namespace MageWorx\ExampleConfig\Block;

use Magento\Framework\Data\Form\Element\AbstractElement;

/**
 * Class TimeSlider
 */
class TimeSlider extends \Magento\Framework\View\Element\Template implements
    \Magento\Framework\Data\Form\Element\Renderer\RendererInterface,
    \Magento\Widget\Block\BlockInterface
{

    const TIME_NAME_FROM = 'groups[main][fields][delivery_time_from][value]';
    const TIME_NAME_TO   = 'groups[main][fields][delivery_time_to][value]';

    /**
     * Form element which re-rendering
     *
     * @var \Magento\Framework\Data\Form\Element\Fieldset
     */
    protected $element;

    /**
     * @var string
     */
    protected $_template = 'MageWorx_ExampleConfig::form/renderer/timeslider.phtml';

    /**
     * @var string
     */
    protected $_htmlId = 'time-range';

    /**
     * Retrieve an element
     *
     * @return \Magento\Framework\Data\Form\Element\Fieldset
     */
    public function getElement()
    {
        return $this->element;
    }

    /**
     * Set an element
     *
     * @param AbstractElement $element
     * @return $this
     */
    public function setElement(\Magento\Framework\Data\Form\Element\AbstractElement $element)
    {
        $this->element = $element;

        return $this;
    }

    /**
     * Render element
     *
     * @param AbstractElement $element
     * @return string
     */
    public function render(AbstractElement $element)
    {
        $this->element = $element;

        return $this->toHtml();
    }

    /**
     * @return string
     */
    public function getHtmlId()
    {
        return $this->_htmlId;
    }

    /**
     * @return string
     */
    public function getNameFrom()
    {
        return self::TIME_NAME_FROM;
    }

    /**
     * @return string
     */
    public function getNameTo()
    {
        return self::TIME_NAME_TO;
    }

    /**
     * @param int $minutes
     * @return string
     */
    public function minutesToTime($minutes)
    {
        $hours   = floor($minutes / 60);
        $minutes = $minutes % 60;
        $part    = $hours >= 12 ? 'PM' : 'AM';

        return sprintf('%02d:%02d %s', $hours, $minutes, $part);
    }

    /**
     * @return string
     * @throws \Magento\Framework\Exception\ValidatorException
     */
    protected function _toHtml()
    {
        if (!$this->getTemplate()) {
            return '';
        }

        return $this->fetchView($this->getTemplateFile());
    }
}

Note: you should change values of the constants TIME_NAME_FROM and TIME_NAME_TO to own ones, according your definition in the system.xml.

Template:

<?php

/** @var MageWorx\ExampleConfig\Block\TimeSlider $block */
$element = $block->getElement();
$form = $element->getForm();
/** @var \Magento\Config\Block\System\Config\Form $parentForm */
$parentForm = $form->getParent();
$timeFrom = $parentForm->getConfigValue('example_config/main/delivery_time_from');;
$timeTo = $parentForm->getConfigValue('example_config/main/delivery_time_to');;
?>
<div id="time-range" class="field field-time_range">
    <label class="label" style="white-space: normal;">
        <?php echo __('Time Range: ');?>
        <span class="slider-time">
            <?php echo $block->minutesToTime($timeFrom);?>
        </span>
        <?php echo ' - '; ?>
        <span class="slider-time2">
            <?php echo $block->minutesToTime($timeTo);?>
        </span>
    </label>
    <div class="sliders_step1 control">
        <div id="slider-range"></div>
        <input type="hidden"
               name="<?php echo $block->getNameFrom();?>"
               value="<?php echo $timeFrom?>""
            />
        <input type="hidden"
               name="<?php echo $block->getNameTo();?>"
               value="<?php echo $timeTo?>""
            />
    </div>
</div>
<script type="text/javascript">
    require(['jquery', 'jquery/ui'], function($){
        $("#slider-range").slider({
            range: true,
            min: 0,
            max: 1440,
            step: 15,
            values: [<?php echo $timeFrom?>, <?php echo $timeTo?>],
            slide: function (e, ui) {
                var hours1 = Math.floor(ui.values[0] / 60);
                var minutes1 = ui.values[0] - (hours1 * 60);

                if (hours1.length == 1) hours1 = '0' + hours1;
                if (minutes1.length == 1) minutes1 = '0' + minutes1;
                if (minutes1 == 0) minutes1 = '00';
                if (hours1 >= 12) {
                    if (hours1 == 12) {
                        hours1 = hours1;
                        minutes1 = minutes1 + " PM";
                    } else {
                        hours1 = hours1 - 12;
                        minutes1 = minutes1 + " PM";
                    }
                } else {
                    hours1 = hours1;
                    minutes1 = minutes1 + " AM";
                }
                if (hours1 == 0) {
                    hours1 = 12;
                    minutes1 = minutes1;
                }


                $('.slider-time').html(hours1 + ':' + minutes1);

                var hours2 = Math.floor(ui.values[1] / 60);
                var minutes2 = ui.values[1] - (hours2 * 60);

                if (hours2.length == 1) hours2 = '0' + hours2;
                if (minutes2.length == 1) minutes2 = '0' + minutes2;
                if (minutes2 == 0) minutes2 = '00';
                if (hours2 >= 12) {
                    if (hours2 == 12) {
                        hours2 = hours2;
                        minutes2 = minutes2 + " PM";
                    } else if (hours2 == 24) {
                        hours2 = 11;
                        minutes2 = "59 PM";
                    } else {
                        hours2 = hours2 - 12;
                        minutes2 = minutes2 + " PM";
                    }
                } else {
                    hours2 = hours2;
                    minutes2 = minutes2 + " AM";
                }

                $('.slider-time2').html(hours2 + ':' + minutes2);
                $('[name="<?php echo $block->getNameFrom();?>"]').val(ui.values[0]);
                $('[name="<?php echo $block->getNameTo();?>"]').val(ui.values[1]);
            }
        });
    });
</script>

That's all. Based on this example you can modify code to add base features like a use global value etc.

Here is full code on the GitHub.

PS: Example partially taken from the Shipping Suite Ultimate Extension by MageWorx (where that kind of slider used in the form).

PPS: in the example time stored in the database in minutes, like 120 => 2:00 AM.