Table of Contents for
Magento 2 Development Quick Start Guide

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Magento 2 Development Quick Start Guide by Branko Ajzele Published by Packt Publishing, 2018
  1. Magento 2 Development Quick Start Guide
  2. Title Page
  3. Copyright and Credits
  4. Magento 2 Development Quick Start Guide
  5. Packt Upsell
  6. Why subscribe?
  7. Packt.com
  8. Contributors
  9. About the author
  10. About the reviewer
  11. Packt is searching for authors like you
  12. Table of Contents
  13. Preface
  14. Who this book is for
  15. What this book covers
  16. To get the most out of this book
  17. Download the example code files
  18. Code in Action
  19. Conventions used
  20. Get in touch
  21. Reviews
  22. Understanding the Magento Architecture
  23. Technical requirements
  24. Installing Magento
  25. Modes
  26. Areas
  27. Request flow processing
  28. Modules
  29. Creating the minimal module
  30. Cache
  31. Dependency injection
  32. Argument injection
  33. Virtual types
  34. Proxies
  35. Factories
  36. Plugins
  37. The before plugin
  38. The around plugin
  39. The after plugin
  40. Events and observers
  41. Console commands
  42. Cron jobs
  43. Summary
  44. Working with Entities
  45. Technical requirements
  46. Understanding types of models
  47. Creating a simple model
  48. Methods worth memorizing
  49. Working with setup scripts
  50. The InstallSchema script
  51. The UpgradeSchema script
  52. The Recurring script
  53. The InstallData script
  54. The UpgradeData script
  55. The RecurringData script
  56. Extending entities
  57. Creating extension attributes
  58. Summary
  59. Understanding Web APIs
  60. Technical requirements
  61. Types of users
  62. Types of authentication
  63. Types of APIs
  64. Using existing web APIs
  65. Creating custom web APIs
  66. Understanding search criteria
  67. Summary
  68. Building and Distributing Extensions
  69. Technical requirements
  70. Building a shipping extension
  71. Distributing via GitHub
  72. Distributing via Packagist
  73. Summary
  74. Developing for Admin
  75. Technical requirements
  76. Using the listing component
  77. Using the form component
  78. Summary
  79. Developing for Storefront
  80. Technical requirements
  81. Setting up the playground
  82. Calling and initializing JS components
  83. Meet RequireJS
  84. Replacing jQuery widget components
  85. Extending jQuery widget components
  86. Creating jQuery widgets components
  87. Creating UI/KnockoutJS components
  88. Extending UI/KnockoutJS components
  89. Summary
  90. Customizing Catalog Behavior
  91. Technical requirements
  92. Creating the size guide
  93. Creating the same day delivery
  94. Flagging new products
  95. Summary
  96. Customizing Checkout Experiences
  97. Technical requirements
  98. Passing data to the checkout
  99. Adding order notes to the checkout
  100. Summary
  101. Customizing Customer Interactions
  102. Technical requirements
  103. Understanding the section mechanism
  104. Adding contact preferences to customer accounts
  105. Adding contact preferences to the checkout
  106. Summary
  107. Other Books You May Enjoy
  108. Leave a review - let other readers know what you think

Creating the same day delivery

We have been asked to add a functionality that shows an active countdown with a You have %h %min %sec to catch our same day delivery offer message on a product view page, whereas the countdown is based on an optionally assigned daily cutoffAt time, set for every product individually, for every day of a week independently.

Let's take a moment to think about our approach here:

  • Every product and every day of a week imply Monday to Sunday _[Cutoff_At] product attributes
  • Product attributes imply setup script
  • Active countdown implies JS components

We start by bumping up the setup_version value of our <MODULE_DIR>/etc/module.xml file from 1.0.0 to 1.0.1. This allows us to introduce the <MODULE_DIR>/Setup/UpgradeData.php file with an upgrade, as follows:

protected function upgradeToVersionOneZeroOne(
\Magento\Framework\Setup\ModuleDataSetupInterface $setup
) {
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

$days = [
'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday'
];

$sortOrder = 100;

foreach ($days as $day) {
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
$day . '_cutoff_at',
[
'type' => 'varchar',
'label' => ucfirst($day) . ' Cutoff At',
'input' => 'text',
'required' => false,
'sort_order' => $sortOrder++,
'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
'group' => 'Cutoff',
]
);
}
}

The addAttribute method here is run for each day of the week, thus creating monday_cutoff_at to sunday_cutoff_at product attributes. If, at this point, we were to run the Magento's setup:upgrade command, our UpgradeData script would get executed and schema_version and data_version numbers from within the setup_module table would get bumped to the 1.0.1 version. Likewise, going into the Magento admin area and editing or creating a new product, would show the following screen. This is where we enable the user to enter the time of the day in an <hour>:<minute> format, such as 15:30. This time, if entered, will later be used by the JS component to render the countdown functionality on the storefront product view page:

We then create <MODULE_DIR>/Block/Product/View/Cutoff.php, as follows:

namespace Magelicious\Catalog\Block\Product\View;

class Cutoff extends \Magento\Framework\View\Element\Template implements \Magento\Framework\DataObject\IdentityInterface
{
private $product;
protected $coreRegistry;
protected $localeDate;

public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
\Magento\Framework\Registry $coreRegistry,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
array $data = []
) {
$this->coreRegistry = $coreRegistry;
$this->localeDate = $localeDate;
parent::__construct($context, $data);
}

public function getProduct() { /* ... */ }
public function getCutoffAt() { /* ... */ }
public function getIdentities() { /* ... */ }
}

We will use this class when we reach our layout update.

The getProduct method is further implemented, as follows:

public function getProduct()
{
if (!$this->product) {
$this->product = $this->coreRegistry->registry('product');
}
return $this->product;
}

As mentioned previously, the registry's product key is already set by the parent class up the layout tree, so we exploit that fact to fetch the current product.

The getCutoffAt method is further implemented, as follows:

public function getCutoffAt()
{
$timezone = new \DateTimeZone($this->localeDate->getConfigTimezone());
$now = new \DateTime('now', $timezone);
$day = strtolower($now->format('l'));
$cutoffAt = $this->getProduct()->getData($day . '_cutoff_at');
if ($cutoffAt) {
$timeForDay = \DateTime::createFromFormat(
'Y-m-d H:i',
$now->format('Y-m-d') . ' ' . $cutoffAt,
$timezone
);

if ($timeForDay instanceof \DateTime) {
return $timeForDay->format(DATE_ISO8601);
}
}
return 0;
}

This is the gist of our same day delivery functionality from the PHP side of things. We ensure we properly return the full date and time based on the product's $day . '_cutoff_at' attribute value; this will later be passed onto the  JS component.

Finally, the getIdentities method is further implemented, as follows:

public function getIdentities()
{
$identities = $this->getProduct()->getIdentities();
$timezone = new \DateTimeZone($this->localeDate->getConfigTimezone());
$now = new \DateTime('now', $timezone);
$day = strtolower($now->format('l'));
return array_push($identities, $day);
}

The getIdentities method has been implemented in a way to ensure caching of this block is considered in a relation to product identity as well as the day of the week.

We then create the <MODULE_DIR>/view/frontend/requirejs-config.js file, as follows:

var config = {
map: {
'*': {
cutoffAt: 'Magelicious_Catalog/js/cutoff'
}
}
};

This registers the cutoffAt component with Magento, which points to our module's cutoff.js file.

We then create the <MODULE_DIR>/view/frontend/web/js/cutoff.js file, as follows:

define([
'jquery',
'uiComponent',
'ko',
'moment'
], function ($, Component, ko, moment) {
'use strict';
return Component.extend({
defaults: {
template: 'Magelicious_Catalog/cutoff',
expiresAt: null,
timerHide: false,
timerHours: null,
timerMinutes: null,
timerSeconds: null,
},
initialize: function () {
this._super();
this.countdown(this);
return this;
},
initObservable: function () {
this._super()
.observe('timerHide timerHours timerMinutes timerSeconds');
return this;
},
countdown: function (self) { /* ... */ }
});
}
);

Our JS component template value points to <MODULE_DIR>/view/frontend/web/template/cutoff.html, which we will soon address. expiresAt is the only real option that is expected to be passed on when the component is initialized. The observable timer* options will be used internally to control the functionality of our component.

The countdown function is further implemented, as follows:

countdown: function (self) {
var today = moment(new Date());
setInterval(function () {
self.expiresAt = moment(self.expiresAt).subtract(1, 'seconds');
var milliseconds = moment(self.expiresAt, 'DD/MM/YYYY HH:mm:ss').diff(moment(today, 'DD/MM/YYYY HH:mm:ss'));
var duration = moment.duration(milliseconds);
self.timerHours(duration.hours() >= 0 ? duration.hours() : 0);
self.timerMinutes(duration.minutes() >= 0 ? duration.minutes() : 0);
self.timerSeconds(duration.seconds() >= 0 ? duration.seconds() : 0);
if (self.timerHours() == 0
&& self.timerMinutes() == 0
&& self.timerSeconds() == 0
) {
self.timerHide(true);
}
}, 1000);
}

This here is the gist of our same day delivery functionality. Using the core JS setInterval method, we set up a simple per-second counter. With the few lines of code wrapped within setInterval, we control our observable timer* options bound to our cutoff.html template. This, in turn, results in the visual countdown effect.

We then create the <MODULE_DIR>/view/frontend/web/template/cutoff.html file, as follows:

<span class="cutoff-component" data-bind="ifnot: timerHide">
<span translate="'You have'"></span>
<span class="timer">
<span class="timer-part timer-part-hours">
<span class="numeric" data-bind="text: timerHours"></span>
<span class="label" data-bind="i18n: 'hours'"></span>
</span>
<span class="timer-part timer-part-minutes">
<span class="numeric" data-bind="text: timerMinutes"></span>
<span class="label" data-bind="i18n: 'minutes'"></span>
</span>
<span class="timer-part timer-part-seconds">
<span class="numeric" data-bind="text: timerSeconds"></span>
<span class="label" data-bind="i18n: 'seconds'"></span>
</span>
</span>
<span translate="'to catch our same day delivery offer.'"></span>
</span>

This is the template file behind our JS component. We see all those timer* options being bounded to proper span elements. Wrapping every timer* option in its own span allows for potential flexibility around styling later on.

We then create the <MODULE_DIR>/view/frontend/templates/product/view/cutoff.phtml file, as follows:

<?php /* @var \Magelicious\Catalog\Block\Product\View\Cutoff $block */ ?>
<?php $jsonHelper = $this->helper('Magento\Framework\Json\Helper\Data'); ?>

<div class="cutoff" data-bind="scope:'cutoff-scope'">
<!-- ko template: getTemplate() --><!-- /ko -->
</div>

<script type="text/x-magento-init">
{
".cutoff": {
"Magento_Ui/js/core/app": {
"components": {
"cutoff-scope": {
"component": "cutoffAt",
"expiresAt": <?= /* @escapeNotVerified */ $jsonHelper->jsonEncode($block->getCutoffAt()) ?>
}
}
}
}
}
</script>

This is the template file that initializes our JS component. With this file in place, we can finally glue things together by amending the body element of the  <MODULE_DIR>/view/frontend/layout/catalog_product_view.xml file, as follows:

<referenceBlock name="product.info.extrahint">
<block name="cutoff"
class="Magelicious\Catalog\Block\Product\View\Cutoff"
template="Magelicious_Catalog::product/view/cutoff.phtml">
</block>
</referenceBlock>

The final product view page result should look like this:

Once the timer reaches 0 hours 0 minutes 0 seconds, it should disappear.