Blog

Use of Data Persistor and ViewModel in Magento2

In this article, we are going to discuss about data persistor and view model and its example.

Data persistor:

Data Persistor in Magento 2 is a class which stores data to the current user session.
It is used to store temporary data, meaning the data that need not be stored in the database.
You can get, set and clear data from the user session using Data Persistor.

View models

A view model is a class that allows you to pass data and additional functionality from a separate class to a template.

When to use view models:-

Use this approach anytime you need to inject functionality into template files and your code does not need to be backwards compatible with Magento.

In this article, I am going to show you use of data persistor and view model in the module.

Requirement:-

Whenever server side validation error occurs your form should persist the data which user has entered.

I have explained this in 5 steps. Follow the steps as shown

    1. Step 1: Pass view model in argument
    2. Step 2: Get Data Posted by User
    3. Step 3: Define Function required by ViewModel UserDataProvider
    4. Step 4: Call ViewModel on the Form
    5. Step 5: Call the data persistor on the Controller

Lets start the process…

Step 1: Pass view model in argument

View models can be used by passing the view model class as an argument to a template’s block in the page layout configuration file. In the following example snippet, UserDataProvider is the view model class of the module passed as an argument to a block.

In your layout file, you need to add argument as shown below:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
      <title>Thecoachsmb - Jobs</title>
    </head>
     <body>
          <referenceContainer name="content">
               <block class="Thecoachsmb\Jobs\Block\Apply"
                      name="job.apply"
                      template="Thecoachsmb_Jobs::apply.phtml" >
                   <arguments>
                       <argument name="view_model" xsi:type="object">Thecoachsmb\Jobs\ViewModel\UserDataProvider</argument>
                   </arguments>
             </block>
        </referenceContainer>
    </body>
</page>

Step 2: Get Data Posted by User

Add UserDataProvider.php file in app/code/Thecoachsmb/Jobs/ViewModel folder.

This file will help us to get the form data posted by user and store it in data persistor.

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Thecoachsmb\Jobs\ViewModel;

use Thecoachsmb\Jobs\Helper\Data;
use Magento\Framework\View\Element\Block\ArgumentInterface;

/**
* Provides the user data to fill the form.
*/
class UserDataProvider implements ArgumentInterface
{

    /**
     * @var Data
     */
    private $helper;

    /**
     * UserDataProvider constructor.
     * @param Data $helper
     */
     public function __construct(
         Data $helper
     ) {
        $this->helper = $helper;
     }

     /**
      * Get user name
      *
      * @return string
      */
      public function getUserName()
      {
          return $this->helper->getPostValue('name');
      }

      /**
       * Get user email
       *
       * @return string
       */
       public function getUserEmail()
       {
          return $this->helper->getPostValue('email');
       }

      /**
       * Get user telephone
       *
       * @return string
       */
       public function getUserTelephone()
       {
           return $this->helper->getPostValue('telephone');
       }

      /**
       * Get user experience
       *
       * @return string
       */
       public function getUserExperience()
       {
         return $this->helper->getPostValue('experience');
       }

      /**
       * Get user comment
       *
       * @return string
       */
       public function getUserPost()
       {
          return $this->helper->getPostValue('post');
       }
}

Step 3: Define Function required by ViewModel UserDataProvider

Add Helper File Data.php in app/code/Thecoachsmb/Jobs/Helper folder. This file will have functions required by ViewModel UserDataProvider.

Content would be like this:-

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Thecoachsmb\Jobs\Helper;

use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\Request\DataPersistorInterface;

/**
* Contact base helper
*
* @deprecated 100.2.0
* @see \Magento\Contact\Model\ConfigInterface
*/
class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * @var DataPersistorInterface
     */
     private $dataPersistor;

    /**
     * @var array
     */
     private $postData = null;

    /**
     * @param \Magento\Framework\App\Helper\Context $context
     */
     public function __construct(
         \Magento\Framework\App\Helper\Context $context
     ) {
         parent::__construct($context);
     }

     /**
      * Get value from POST by key
      *
      * @param string $key
      * @return string
      */
      public function getPostValue($key)
     {
         if (null === $this->postData) {
             $this->postData = (array) $this->getDataPersistor()->get('apply_here');
             $this->getDataPersistor()->clear('apply_here');
         }

         if (isset($this->postData[$key])) {
            return (string) $this->postData[$key];
         }

         return '';
     }

     /**
      * Get Data Persistor
      *
      * @return DataPersistorInterface
      */
      private function getDataPersistor()
     {
        if ($this->dataPersistor === null) {
             $this->dataPersistor = ObjectManager::getInstance()
                     ->get(DataPersistorInterface::class);
       }

       return $this->dataPersistor;
    }
}

Step 4: Call ViewModel on the Form

In your phtml file, call viewModel and the respective value attribute of the form field, call the viewmodel function.

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

// phpcs:disable Magento2.Templates.ThisInTemplate
// phpcs:disable Generic.Files.LineLength.TooLong

/** @var \Magento\Contact\Block\ContactForm $block */
/** @var \Magento\Contact\ViewModel\UserDataProvider $viewModel */

$viewModel = $block->getViewModel();
?>
<form class="form contact"
action="<?= $block->escapeUrl($block->getFormAction()) ?>"
id="jobapply-form"
method="post"
enctype="multipart/form-data"
data-hasrequired="<?= $block->escapeHtmlAttr(__('* Required Fields')) ?>"
data-mage-init='{"validation":{}}'>
<fieldset class="fieldset">
<legend class="legend"><span><?= $block->escapeHtml(__('Write Us')) ?></span></legend><br />
<div class="field note no-label">
<?= $block->escapeHtml(__('Jot us a note and we’ll get back to you as quickly as possible.')) ?>
</div>
<div class="field name required">
<label class="label" for="name"><span><?= $block->escapeHtml(__('Name')) ?></span></label>
<div class="control">
<input name="name"
id="name"
title="<?= $block->escapeHtmlAttr(__('Name')) ?>"
value="<?= $block->escapeHtmlAttr($viewModel->getUserName()) ?>"
class="input-text"
type="text"
data-validate="{required:true, 'validate-alphanum-with-spaces':true, 'validate-not-number-first':true}"/>
</div>
</div>
<div class="field email required">
<label class="label" for="email"><span><?= $block->escapeHtml(__('Email')) ?></span></label>
<div class="control">
<input name="email"
id="email"
title="<?= $block->escapeHtmlAttr(__('Email')) ?>"
value="<?= $block->escapeHtmlAttr($viewModel->getUserEmail()) ?>"
class="input-text"
type="email"
data-validate="{required:true, 'validate-email':true}"
data-mage-init='{"mage/trim-input":{}}'
/>
</div>
</div>
<div class="field telephone">
<label class="label" for="telephone"><span><?= $block->escapeHtml(__('Phone Number')) ?></span></label>
<div class="control">
<input name="telephone"
id="telephone"
title="<?= $block->escapeHtmlAttr(__('Phone Number')) ?>"
value="<?= $block->escapeHtmlAttr($viewModel->getUserTelephone()) ?>"
class="input-text"
type="tel" />
</div>
</div>
<div class="field experience required">
<label class="label" for="experience">
<span><?= $block->escapeHtml(__('Experience')) ?></span>
</label>
<div class="control">
<?php $selectedExperience = $block->escapeHtmlAttr($viewModel->getUserExperience()) ?>
<select name="experience"
id="experience" data-validate="{'validate-select':true}">
<option value=""><?= $block->escapeHtml(__('-- Please Select --')) ?></option>
<option value="0" <?= ($selectedExperience == 0) ? 'selected' : '' ?>><?= $block->escapeHtml(__('Fresher')) ?></option>
<option value="1" <?= ($selectedExperience == 1) ? 'selected' : '' ?>>1</option>
<option value="2" <?= ($selectedExperience == 2) ? 'selected' : '' ?>>2</option>
<option value="3" <?= ($selectedExperience == 3) ? 'selected' : '' ?>>3</option>
<option value="4" <?= ($selectedExperience == 4) ? 'selected' : '' ?>>4</option>
</select>
</div>
</div>
<div class="field post required">
<label class="label" for="post">
<span><?= $block->escapeHtml(__('Post')) ?></span>
</label>
<div class="control">
<?php $selectedPost = $block->escapeHtmlAttr($viewModel->getUserPost()) ?>
<select name="post"
id="post" data-validate="{'validate-select':true}">
<option value=""><?= $block->escapeHtml(__('-- Please Select --')) ?></option>
<option value="0" <?= ($selectedPost == 0) ? 'selected' : '' ?>><?= $block->escapeHtml(__('Fresher')) ?></option>
<option value="1" <?= ($selectedPost == 1) ? 'selected' : '' ?>><?= $block->escapeHtml(__('Junior SE')) ?></option>
<option value="2" <?= ($selectedPost == 2) ? 'selected' : '' ?>><?= $block->escapeHtml(__('SE')) ?></option>
<option value="3" <?= ($selectedPost == 3) ? 'selected' : '' ?>><?= $block->escapeHtml(__('Senior SE')) ?></option>
<option value="4" <?= ($selectedPost == 4) ? 'selected' : '' ?>><?= $block->escapeHtml(__('Team Lead')) ?></option>
</select>
</div>
</div>
<?= $block->getChildHtml('form.additional.info') ?>
</fieldset>
<div class="actions-toolbar">
<div class="primary">
<input type="hidden" name="hideit" id="hideit" value="" />
<button type="submit" title="<?= $block->escapeHtmlAttr(__('Submit')) ?>" class="action submit primary">
<span><?= $block->escapeHtml(__('Submit')) ?></span>
</button>
</div>
</div>
</form>
<script type="text/x-magento-init">
{
"*": {
"Magento_Customer/js/block-submit-on-send": {
"formId": "jobapply-form"
}
}
}
</script>

Step 5: Call the data persistor on the Controller

In the post controller where you are posting the form, which means action url pointing to. In this controller we need to call the data persistor to set the form data if any server side validation error occurs and clear the form data when processing is done.

Content would be

<?php
namespace Thecoachsmb\Jobs\Controller\Apply;

use \Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\Request\DataPersistorInterface;

class Post extends Action
{
/**
* @var DataPersistorInterface
*/
private $dataPersistor;

/**
* @var Context
*/
private $context;

/**
* @var UploaderFactory
*/
protected $uploaderFactory;

/**
* @var AdapterFactory
*/
protected $adapterFactory;

/**
* @var Filesystem
*/
protected $filesystem;

/**
* @param Context $context
*/
public function __construct(
Context $context,
DataPersistorInterface $dataPersistor
) {
$this->dataPersistor = $dataPersistor;
$this->context = $context;
parent::__construct($context);
}

/**
* Prints the information
* @return Page
*/
public function execute()
{
if (!$this->getRequest()->isPost()) {
return $this->resultRedirectFactory->create()->setPath('*/*/');
}

try {
$data = $this->validatedParams();

$this->messageManager->addSuccessMessage(
__('Thanks for submitting your profile. We\'ll respond to you very soon.')
);
$this->dataPersistor->clear('apply_here');
} catch (LocalizedException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
$this->dataPersistor->set('apply_here', $this->getRequest()->getParams());
} catch (\Exception $e) {
$this->logger->critical($e);
$this->messageManager->addErrorMessage(
__('An error occurred while processing your form. Please try again later.')
);
$this->dataPersistor->set('apply_here', $this->getRequest()->getParams());
}
return $this->resultRedirectFactory->create()->setPath('job/apply');
}

/**
* Method to validated params.
*
* @return array
* @throws \Exception
*/
private function validatedParams()
{
$request = $this->getRequest();

if (trim($request->getParam('name', '')) === '') {
throw new LocalizedException(__('Enter the Name and try again.'));
}
if (\strpos($request->getParam('email', ''), '@') === false) {
throw new LocalizedException(__('The email address is invalid. Verify the email address and try again.'));
}
if (trim($request->getParam('experience', '')) === '') {
throw new LocalizedException(__('Select the experience and try again.'));
}
if (trim($request->getParam('post', '')) === '') {
throw new LocalizedException(__('Select the post and try again.'));
}
if (trim($request->getParam('hideit', '')) !== '') {
// phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception();
}

return $request->getParams();
}
}

After this, clear cache.

That’s it. Feel free to comment for the feedback.

2 thoughts on “Use of Data Persistor and ViewModel in Magento2

Leave a Reply

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