Magento 2: System Configuration multi date selection

Today we are going to learn how to add dates in System Configuration of Magento 2. These dates can be used for exclusion or inclusion. The data will be saved as a serialised data in Magento 2 and can be retrieved as an array by converting serialised data back to an array.

It will looks like the following image where you can add multiple dates using system configuration section of Magento 2.

Magento 2 : Exclude Date Selection System Configuration

Please see our step by step implementation to achieve this -:

Step 1 – Add \etc\adminhtml\system.xml with the following content

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="Scommerce" translate="label comment" sortOrder="100">
            <label>Scommerce Configuration</label>
        </tab>
        
        <section id="order_section" translate="label comment" sortOrder="100" showInDefault="1" showInStore="1" showInWebsite="1">
            <label>Order Settings</label>
            <tab>Scommerce</tab>
            <resource>Scommerce::config</resource>
            <group id="order_settings" translate="label comment" sortOrder="10" showInDefault="1" showInStore="1" showInWebsite="1">
                 <field id="excludedates" translate="label" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Exclude Dates from Shipping Date Calculation</label>
                    <comment>These dates will be excluded from shipping date calculation</comment>
                    <backend_model>Scommerce\Custom\Model\Config\Backend\DatePickerList</backend_model>
                    <frontend_model>Scommerce\Custom\Block\Adminhtml\Form\Field\DatePickerList</frontend_model>
                </field>
            </group>
        </section>

    </system>
</config>

Please note, there are two custom classes needs to be created to achieve this functionality. First class is a frontend model class to show the grid in the system configuration in admin. And the other class is a backend model class to save data in database using the correct date format. Let’s now create frontend and backend model classes in the next two steps.

Step 2 – Create Frontend Class Scommerce\Custom\Block\Adminhtml\Form\Field\DatePickerList

<?php
/**
 * Date Picker List Block
 *
 * @category   Scommerce
 * @package    Scommerce_Custom
 * @author     Sagar Nayyar
 *
 */

namespace Scommerce\Custom\Block\Adminhtml\Form\Field;

class DatePickerList extends \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
{
    /**
     * Initialise form fields
     *
     * @return void
     */
    protected function _prepareToRender()
    {
        $this->addColumn('date', ['label' => __('Date'), 'class' => 'js-date-excluded-datepicker']);
        $this->addColumn('content', ['label' => __('Content')]);
        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add Date');
        parent::_prepareToRender();
    }

    /**
     * Prepare existing row data object
     * Convert backend date format "2018-01-12" to front format "12/01/2018"
     *
     * @param \Magento\Framework\DataObject $row
     * @return void
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    protected function _prepareArrayRow(\Magento\Framework\DataObject $row)
    {
        $key = 'date';
        if (!isset($row[$key])) return;
        $rowId = $row['_id'];
        try {
            $sourceDate = \DateTime::createFromFormat('Y-m-d', $row[$key]);
            $renderedDate = $sourceDate->format('d/m/Y');
            $row[$key] = $renderedDate;
            $columnValues = $row['column_values'];
            $columnValues[$this->_getCellInputElementId($rowId, $key)] = $renderedDate;
            $row['column_values'] = $columnValues;
        } catch (\Exception $e) {
            // Just skipping error values
        }
    }

    /**
     * Get the grid and scripts contents
     *
     * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
     * @return string
     */
    protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
    {
        $html = parent::_getElementHtml($element);

        $script = <<<JS
            <script type="text/javascript">
                // Bind click to "Add" buttons and bind datepicker to added date fields
                require(["jquery", "jquery/ui"], function (jq) {
                    jq(function(){
                        function bindDatePicker() {
                            setTimeout(function() { 
                                jq(".js-date-excluded-datepicker").datepicker( { dateFormat: "dd/mm/yy" } );
                            }, 50);
                        }
                        bindDatePicker();
                        jq("button.action-add").on("click", function(e) {
                            bindDatePicker();
                        });
                    });
                });
            </script>
JS;
        $html .= $script;
        return $html;
    }

}

In the above code, we have created three functions _prepareToRender, _prepareArrayRow and _getElementHtml.

_prepareToRender function is the main function which allows you to add multiple fields in the grid using addColumn function and also allow you to add custom label button using _addButtonLabel button.

_prepareArrayRow function allows you to manipulate data in our case we are converting backend date format “YYYY-MM-DD” to front format “DD/MM/YYYY”

_getElementHtml function allows you to add jquery datepicker to bind it with our date field using bindDatePicker function.

Step 3 – Create Backend Class Scommerce\Custom\Model\Config\Backend\DatePickerList

<?php
/**
 * Date Picker List Model
 *
 * @category   Scommerce
 * @package    Scommerce_Custom
 * @author     Sagar Nayyar
 *
 */
namespace Scommerce\Custom\Model\Config\Backend;

class DatePickerList extends \Magento\Config\Model\Config\Backend\Serialized\ArraySerialized
{
    /**
     * On save convert front value format like "12/01/2018" to backend format "2018-01-12"
     *
     * @return $this
     */
    public function beforeSave()
    {
        $value = [];
        $values = $this->getValue();
        foreach ((array)$values as $key => $data) {
            if ($key == '__empty') continue;
            if (!isset($data['date'])) continue;
            try {
                $date = \DateTime::createFromFormat('d/m/Y', $data['date']);
                $value[$key] = [
                    'date' => $date->format('Y-m-d'),
                    'content' => $data['content'],
                ];
            } catch (\Exception $e) {
                // Just skipping error values
            }
        }
        $this->setValue($value);
        return parent::beforeSave();
    }
}

In the above code, we are changing the date format from frontend (DD/MM/YYYY) to backend (YYYY-MM-DD). Reversing exactly what we did in our frontend class model using _prepareArrayRow function.

Step 4 – Now we have saved our dates data in config_data table, let’s retrieve these dates to be used in our business logic.
Let’s create Scommerce\Custom\Helper\Data class


<?php

/**
 * Admin Configuration settings can be retrieved from this class
 *
 * @category   Scommerce
 * @package    Scommerce_Custom
 * @author     Sagar Nayyar
 *
 */

namespace Scommerce\Custom\Helper;

use \Magento\Framework\App\Helper\AbstractHelper;

class Data extends AbstractHelper {
    
    /**
     * Admin configuration paths
     *
     */
    
    const XML_PATH_EXCLUDE_DATES            = 'order_section/order_settings/excludedates';

    /**
     * @var \Magento\Framework\Serialize\Serializer\Json
     */
    protected $_serializer;

    /**
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Framework\Serialize\Serializer\Json $serializer
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Framework\Serialize\Serializer\Json $serializer
    ) {
        $this->_serializer = $serializer;
        parent::__construct($context);
    }
    
    /**
     * Returns excluded dates (dates which will be disabled on the date grid on checkout)
     * Returns raw value as string (serialized array)
     *
     * @return string
     */
    private function getRawExcludeDates($storeId=null)
    {
        $excludeDates = $this->scopeConfig
            ->getValue(
                self::XML_PATH_EXCLUDE_DATES,
                \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
                $storeId
            );
        return $excludeDates;
    }

    /**
     * Returns excluded dates
     * @return array
     */
    public function getExcludeDates()
    {
        $raw = $this->getRawExcludeDates();
        if (empty($raw)) return [];
        if (! $values = $this->_serializer->unserialize($raw)) return [];
        $dates = array();
        foreach ($values as $value) {
            if (!isset($value['date'])) continue;
            array_push($dates,$value['date']);
        }
        return $dates;
    }
}

In the above code, you will get your dates as an array when you call getExcludeDates function. We have implemented this in our Magento 2 Shipping Date and instruction extension which allows customers to select shipping date / time and also allow them to add delivery instructions. We use the above function to exclude certain dates like xmas or boxing dates so that those dates are excluding from customer selection during checkout when they select delivery date.

That’s it, Hope this article helped you in some way. Please leave us your comment and let us know what do you think? Thanks.

Scommerce

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.