giorgio bignozzi - how to develop a sticker plug-in for magento 2: best practice
TRANSCRIPT
Giorgio BignozziTechnical Manager | Marketing Informatico
https://www.marketinginformatico.it
@GiorgioBignozzi
Michele FantettiMagento Developer at Maggioli Editore
http://www.fantetti.net
@WaP_oNe
Who we are
Abstract: Our aim was to create an extension that adds the classical label to the product image in the different layouts concerning products (product page, category page, related products, upsell products, cross-sell products, widget, search results).
Goals:• To create two labels (“Discount” and “New”) with different shapes and positions
• To have a separate graphic management of these labels (custom label or image). If youchoose a custom label, you can set two labels with a configurable text, text colour and background colour
• To display the label on sale products or products associated to certain categories(“Discount”)
• To display the label on products associated to certain categories (“New”)
• To enable/disable automatically (with programmable date and time) one or both the stickers.
Our project: Stickers
• ISSUE #1: how to add the label (HTML code) without modifying layout files in multiple pages and making the plugin independent
• SOLUTION #1: when rendering product attributes, upload HTML code that willbe completed lately via script JS.
• ISSUE #2: how and where to insert this HTML code
• SOLUTION #2: thanks to Magento 2 PLUGIN, beforeGetProductPrice() and afterGetProductPrice().
Development: problems and solutions (1)
• ISSUE #3: how to insert extra code to this HTML code
• SOLUTION #3: requireJS
• ISSUE #4: enable/disable some configurable fields depending on which fieldsyou have valued before
• SOLUTION #4: upload a JS in the layout of the configuration page of the plugin
Development: problems and solutions (2)
• registration.php
• etc/module.xml
Development: M1 vs M2 structure for plug-in registration
In M2 we need to modify onlyone folder (app/code)
app/code/Mainf/Stickers/Model/Stickers.php
It manages every feature of the sticker (isInCategory(), isDiscounted(), getDiscountAmount() etc.) in addition to inserting HTML code:protected function _getHTML($stickerLayoutType, $class, $firstLabel = '', $secondLabel = '') {
switch ($stickerLayoutType) {case self::STICKER_IMAGE:
$html = "<input type='hidden' class='".$class."' value='1' />";break;
case self::STICKER_AREA:$html = "<input type='hidden' class='".$class."' value='" . $this->getConfigValue($firstLabel) . "<br
/>" . $this->getConfigValue($secondLabel) . "' />";break;
case self::STICKER_CALCULATED:$html = "<input type='hidden' class='".$class."' value='" . $this->getDiscountAmount() . "' />";break;
default:$html = "<input type='hidden' class='".$class."' value='1' />";break;
}
return $html;}
Development : model class Stickers
app/code/Mainf/Stickers/Plugin/ProductStickersPlugin.php
namespace Mainf\Stickers\Plugin;
class ProductStickersPlugin extends \Mainf\Stickers\Model\Stickers{
const DISCOUNT_ACTIVATION = "stickers/stickers_discount_page/discount_activation";const LATEST_ACTIVATION = "stickers/stickers_latest_page/latest_activation";
public function beforeGetProductPrice(\Magento\Catalog\Block\Product\AbstractProduct $abstractProduct,\Magento\Catalog\Model\Product $product
) {$this->_product = $product;
}
public function afterGetProductPrice(\Magento\Catalog\Block\Product\AbstractProduct $product, $result){
if($this->isStickerActive(self::DISCOUNT_ACTIVATION)) {$result .= $this->_setDiscountStickerHTML();
}if($this->isStickerActive(self::LATEST_ACTIVATION)) {
$result .= $this->_setLatestStickerHTML();}
return $result;}
}
Development : PLUGIN (Dependency Injection)
app/code/Mainf/Stickers/view/frontend/layout/default.xml<?xml version="1.0"?><page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head><css src="Mainf_Stickers::css/mainf-stickers.css"/><link src="https://fonts.googleapis.com/css?family=Oswald:400,700,300" rel="stylesheet" type="text/css"
src_type="url" /></head><body>
<referenceContainer name="content"><block name="category.discount.sticker" class="Mainf\Stickers\Block\Discount"
template="Mainf_Stickers::categoryDiscount.phtml" cacheable="false" after="-" /><block name="category.latest.sticker" class="Mainf\Stickers\Block\Latest"
template="Mainf_Stickers::categoryLatest.phtml" cacheable="false" after="-" /></referenceContainer>
</body></page>
Other layout files: catalog_product_view.xml e checkout_cart_index.xml
Development: layout
app/code/Mainf/Stickers/view/frontend/templates/categoryDiscount.phtml
if($block->isDiscountActive()):$discountImage = "<div class='mainf-sticker-wrapper top-right'>";$discountImage .= "<img class='categoryDiscountImage' alt='".__('Discount')."' src='".$this-
>getUrl('pub/media')."mainf/stickers/images/".$block->getStickerImage()."' />";$discountImage .= "</div>";$discountArea = "<div class='mainf-sticker-wrapper top-right'>";$discountArea .= "<div class='mainf-sticker discount-product' style='background-color: #".$block->getStickerBackground()."; color: #".$block-
>getStickerText().";'></div>";$discountArea .= "</div>";
?>
Development: template
<script type="text/x-magento-init">{
"*":{"categoryPageDiscount":{
"imageTag": {"discountImage": "<?php /* @escapeNotVerified */ echo $discountImage; ?>","discountArea": "<?php /* @escapeNotVerified */ echo $discountArea; ?>"
}}}}</script>
<?phpendif;
app/code/Mainf/Stickers/view/frontend/requirejs-config.js
var config = {map: {
'*': {categoryPageDiscount: 'Mainf_Stickers/js/categoryPageDiscount',categoryPageLatest: 'Mainf_Stickers/js/categoryPageLatest',viewPageDiscount: 'Mainf_Stickers/js/viewPageDiscount',viewPageLatest: 'Mainf_Stickers/js/viewPageLatest'
}}
};
Development: RequireJS (1)
app/code/Mainf/Stickers/view/frontend/web/js/categoryPageDiscount.js
define(['jquery'],function ($) {
$.widget('mainf.categoryPageStickers',{
_create: function () {var self = this;$(".categoryPageDiscount").each(function () {
if ($(this).val() == 1) {$($(this).parent().parent().find("a").find("span").find("span"))
.prepend(self.options.imageTag.discountImage);} else {
var discountAmount = $(this).val();$($(this).parent().parent().find("a").find("span").find("span"))
.prepend(self.options.imageTag.discountArea);$(".discount-product").html(discountAmount);
}});
}}
);return $.mainf.categoryPageStickers;
});
Development: RequireJS (2)
app/code/Mainf/Stickers/etc/adminhtml/system.xml<system>
<tab>..</tab><section><group><field>..</field></ group></section>
</system>
<source_model>Mainf\Stickers\Model\Config\Backend\StickerType</source_model>
app/code/Mainf/Stickers/Model/Config/Backend/StickerType.php<?phpnamespace Mainf\Stickers\Model\Config\Backend;
use Magento\Framework\Option\ArrayInterface;
class StickerType implements ArrayInterface{
public function toOptionArray(){
$stickerTypes = array();
$stickerTypes[] = ['value' => 'image','label' => __('Image')];$stickerTypes[] = ['value' => 'custom','label' => __('Custom Label')];return $stickerTypes;
}}
Development: module configuration & source model
Issue: to create a hierarchy when enabling/disabling fields
Solution: to upload a template in the layout of the configuration page
(adminhtml_system_config_edit.xml):<?xml version="1.0"?><page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body><referenceContainer name="footer">
<block class="Mainf\Stickers\Block\Config" template="Mainf_Stickers::js.phtml"/></referenceContainer>
</body></page>
In js.phtml template insert the JS code:<script type="text/javascript">
require(['jquery'],function($) {
$(function() {..
});}
);</script>
Development: module configuration & customization
• crontab.xml file creation (new!):<?xml version="1.0"?><config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
<group id="default"><job name="mainf_stickers_activation" instance="Mainf\Stickers\Model\Observer”
method="checkStickersActivation"><schedule>*/5 * * * *</schedule>
</job></group>
</config>
• Observer.php file creation:control of the status of the sticker (enabled/disabled)
comparison of date
enabling/disabling the sticker
flush of date field
Development: Observer
Basic CSS:.product.media .mainf-sticker-wrapper.top-right {
top: 13px;right: 13px;
}.product.media .mainf-sticker-wrapper.top-left {
bottom: 200px;}.mainf-sticker.discount-product {
width: 66px;border: #FFF 2px solid;border-radius: 50%;box-shadow: 2px 2px 2px #CCC;text-align: center;height: 60px;padding: 10px 5px;
}.mainf-sticker.latest-product {
min-width: 66px;max-width: 130px;min-height: 76px;padding: 5px;border: #FFF 2px solid;box-shadow: 2px 2px 2px #CCC;text-align:left;
}
Graphic changes
Basic CSS 1° option:
.product.media .mainf-sticker-wrapper.top-right {top: 150px;right: 150px;
}
Graphic changes
Basic CSS 2° option:
.product.media .mainf-sticker-wrapper.top-right {top: 400px;right: 150px;
}.product.media .mainf-sticker-wrapper.top-left {
bottom: 600px;}.mainf-sticker.latest-product {
min-width: 66px;max-width: 130px; width: 150px;min-height: 76px;padding: 5px;border: #FFF 2px solid;box-shadow: 2px 2px 2px #CCC;text-align:left;
}
Graphic changes
• Structural improvements for the development of extensions:all files for each extension in the same folder;
substitution of controllers/ folder with Controller/ ;
use of XML Style Definition (XSD) within xml file;
split of config.xml in multiple xml files, each with a precise task (module.xml, routes.xml, crontab.xml etc.) ;
one file for each action;
bin/magento
• Lots of bugs still open (1695 in GitHub, our is #8370), community not ready as it is for M1 and a lot of doubts.
Conclusions
You can download the extension “Stickers” here:
https://github.com/WaPoNe/module-stickers
Or you can buy the extension “Stickers Pro” here:
http://www.magentostoremanager.it/module-stickers-pro.html
Discount coupon for you: MMIT17
Reference
Write us at:
Marketing InformaticoMilano | Bologna | Rimini | Bari
Any question?