Blog

Magento 2 tips & tricks: aggiungere una checkbox al checkout

Lettura 6 minuti

A breve Magento 2 festeggerà il suo primo anniversario e la maggior parte di noi Magento developers ha iniziato a sporcarsi le mani con la nuova piattaforma.

Sono tantissimi gli argomenti da studiare e affrontare per prendere in mano Magento 2 e molti richiedono un lungo periodo di apprendimento che non sempre abbiamo a disposizione.

Per questo motivo ho pensato che possano tornare utili degli esempi veloci di codice per affrontare situazioni reali lasciando a voi il compito di approfondire la teoria che c’è dietro, soprattutto per chi, come me, ha bisogno di partire dal codice per capire meglio i concetti sottostanti.

Partiamo quindi dall’argomento del titolo: aggiungere una checkbox al checkout; sembra una cosa semplice vero? Cosa sarà mai aggiungere una checkbox durante il processo di checkout. Non fatevi ingannare dalle apparenze, Magento è cambiato molto ma regala ancora alcune gioie dei vecchi tempi. Per farvi intuire di cosa sto parlando basta andare ad esaminare il file /Magento/Checkout/view/frontend/layout/checkout_index_index.xml

Anche solo in base a dove dobbiamo posizionare la nostra checkbox cambierà non di poco la via da prendere per arrivare alla soluzione. Ho costruito l’esercizio con lo scopo di andare a toccare più argomenti possibili e questo mi ha costretto a dividere l’articolo in 2 parti.

Ipotizziamo il requisito seguente:

Vogliamo che l’utente possa indicare, durante lo step di spedizione, se vuole la fattura o meno e nel caso decida di volere la fattura, dobbiamo rendere visibile e obbligatorio il campo Società.

Prima di andare ad occuparci della parte frontend e quindi dove e come posizionare la nostra checkbox per la richiesta fattura, andiamo a vedere come aggiungere questo dato sul database all’interno della tabella degli ordini, utilizzando uno script di installazione.

Dopo aver creato la struttura di base del nostro modulo Magento 2 possiamo aggiungere la cartella Setup seguendo la documentazione; aggiungiamo lo script di installazione bitbull-team/m2-checkout-customization/Setup/InstallSchema.php mostrato di seguito:

<?php
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

/**
* Class InstallSchema
* @package Bitbull\CustomCheckout\Setup
*/
class InstallSchema implements InstallSchemaInterface
{

   /**
    * @param SchemaSetupInterface $setup
    * @param ModuleContextInterface $context
    */
   public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) 
   {
       $installer = $setup;
       $installer->startSetup();

       $installer->getConnection()->addColumn(
           $installer->getTable('sales_order'),
           'invoice_checkbox',
           [
               'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
               'nullable' => false,
               'default' => '0',
               'comment' => 'Request invoice'
           ]
       );
       $setup->endSetup();
   }
}

Tutti gli script di installazione e aggiornamento devono implementare  Magento\Framework\Setup\InstallSchemaInterface che dichiara il metodo install().

Come potete notare Magento\Framework\Setup\SchemaSetupInterface è l’interfaccia che fornisce la connessione al database tramite il quale eseguire la nostra modifica.

In questo caso Magento\Framework\Setup\ModuleContextInterface non viene utilizzato ma se ci troviamo nel caso di dover fare un aggiornamento anziché un’installazione è qui che troviamo le informazioni relative al modulo come la versione corrente del setup specificata nel file module.xml per determinare se applicare o meno il nostro aggiornamento.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="Bitbull_CustomCheckout" setup_version="0.1.0">
    </module>
</config>

Assumendo di lavorare in developer mode, basterà abilitare il modulo ed eseguire la procedura di aggiornamento come mostrato di seguito:

bin/magento module:enable Bitbull_CustomCheckout
bin/magento setup:upgrade

A questo punto andiamo a inserire la nostra checkbox all’interno dell’indirizzo di spedizione aggiungendo il file di layout checkout_index_index.xml le modifiche al layout dichiarate nel file saranno così aggiunte da Magento all’insieme delle layout rule.

Di seguito le dichiarazioni di layout che servono ad ottenere il risultato voluto:

<item name="invoice-checkbox" xsi:type="array">
   <item name="component" xsi:type="string">Magento_Ui/js/form/element/boolean</item>
       <item name="config" xsi:type="array">
        <!–customScope is used to group elements within a single form (e.g. they can be validated separately)–>
           <item name="customScope" xsi:type="string">shippingAddress.custom_attributes</item>
           <item name="template" xsi:type="string">ui/form/field</item>
           <item name="elementTmpl" xsi:type="string">Bitbull_CustomCheckout/form/element/invoice-check</item>
       </item>
       <item name="provider" xsi:type="string">checkoutProvider</item>
       <item name="dataScope" xsi:type="string">shippingAddress.custom_attributes.invoice-checkbox</item>
       <item name="description" xsi:type="string">Richiedi fattura</item>
       <item name="sortOrder" xsi:type="string">121</item>
   </item>
   <item name="company" xsi:type="array">
       <item name="component" xsi:type="string">Bitbull_CustomCheckout/js/invoice-input</item>
       <item name="sortOrder" xsi:type="string">122</item>
   </item>
</item>

Queste righe definiscono direttive importanti che andiamo ad analizzare una ad una.

<item name="component" xsi:type="string">Magento_Ui/js/form/element/boolean</item>

Con questa riga definiamo che component ui viene usato per la nostra checkbox. Se non viene specificato sarà usato quello di default Magento_Ui/js/form/element/abstract.

Senza entrare troppo nel dettaglio, per poter capire come modificare ed estendere i component ui offerti da Magento 2 per il frontend bisogna avere una conoscenza base di Knockout, il framework utilizzato per costruirli.

A tal proposito suggerisco la lettura del articolo di Alan Storm sull’argomento.

Nel caso mostrato abbiamo utilizzato un component ui predefinito, vale a dire Magento_Ui/js/form/element/boolean che contiene già tutto il necessario sia per renderizzare la nostra checkbox che per poter osservare “l’aggiornamento” del valore assunto.

Per poter cambiare la visibilità del campo società sulla base dello stato della checkbox dobbiamo sovrascrivere lo ui component di questo campo sempre modificando checkout_index_index.xml:

<item name="company" xsi:type="array">
   <item name="component" xsi:type="string">Bitbull_CustomCheckout/js/invoice-input</item>
   <item name="sortOrder" xsi:type="string">122</item>
</item>

Da notare che il campo sortOrder ci permette di modificare l’ordinamento di default dei campi input della form dell’indirizzo di spedizione; il valore utilizzato, 122, fa sì che il campo società sia posizionato subito dopo la nostra checkbox che ha sortOrder 121.

Purtroppo non vale lo stesso per la form dell’indirizzo di fatturazione; in un prossimo articolo vedremo perché e come aggirare il problema.

Per quanto riguarda lo ui component del campo società

vendor/bitbull-team/m2-checkout-customization/view/frontend/web/js/invoice-input.js

define([
   'underscore',
   'Magento_Ui/js/form/element/abstract',
   'jquery'
], function (_, Abstract, $) {
   "use strict";
   return Abstract.extend({
       defaults: {
           visible: false,
           required: false,
           listens: {
               visible: 'setPreview',
               '${ $.provider }:data.reset': 'reset',
               '${ $.provider }:${ $.customScope ? $.customScope + "." : ""}data.validate': 'validate',
               '${ $.provider }:${ $.customScope ? $.customScope + "." : ""}customAttributes.invoice-checkbox': 'setVisible'
           }
        },
	    initialize: function () {
	        this._super();
	        this.visible(false);
	    },
	    setVisible: function () {
	        var visible = this.source.shippingAddress.custom_attributes['invoice-checkbox'];
	        if (visible) {
	            this.validation['required-entry'] = true;
	        } else {
	            this.error(false);
	            this.validation = _.omit(this.validation, 'required-entry');
	        }
	        this.visible(visible);
	        this.required(visible);
	        return this;
	    }
   });
});

Con il codice sopra rendiamo visibile, obbligatorio e aggiungiamo il campo alle regole di validazione della form dell’indirizzo di spedizione a seconda del valore della nostra checkbox.

Il tutto è possibile sottoscrivendo il metodo setVisible ogni volta che cambia il valore della nostra checkbox accessibile tramite this.source.shippingAddress.custom_attributes[“‘invoice-checkbox’”].  

Infatti nel nostro checkout_index_index.xml avevamo definito come customScope:

<item name="config" xsi:type="array">
	<item name="customScope"  xsi:type="string">shippindAddress.customAttributes</item>
	<item name="template" xsi:type="string">ui/form/field</item>
	<item name="elementTmpl" xsi:type="string">Bitbull_CustomCheckout/form/element/invoice-check</item>
</item>

Il customScope ci permette di raggruppare un insieme di input per poter applicare azioni di gruppo sugli input field appartenenti a questo stesso scope.

Mentre template e elementTmpl definiscono il template Knockout utilizzato per fare il rendering del nostro input field, nel nostro caso rispettivamente ui/form/field e invoice-check.

Un tool molto utile per andare a conoscere meglio i nostri ui component e avere quindi la controprova che tutti sia settato correttamente è l’estensione per chrome Knockoutjs context debugger

A questo punto arriviamo all’ultimo pezzo che ci permette di passare il dato dal frontend al backend:

bitbull-team/m2-checkout-customization/view/frontend/requirejs-config.js

config:{
      mixins: {
          'Magento_Checkout/js/action/set-shipping-information': {
           'Bitbull_CustomCheckout/js/action/set-shipping-information-mixin': true
          }
      }
  }

I mixins hanno un comportamento simile ai Plugin con il before nel javascript, dove noi possiamo andare ad attuare le nostre modifiche prima dell’azione originale, set-shipping-information in questo caso incaricata del salvataggio dell’indirizzo di spedizione.

/*jshint browser:true jquery:true*/
/*global alert*/
define([
   'jquery',
   'mage/utils/wrapper',
   'Magento_Checkout/js/model/quote'
], function ($, wrapper, quote) {
   'use strict';
    return function (setShippingInformationAction) {
       return wrapper.wrap(setShippingInformationAction, function (originalAction) {
           var shippingAddress = quote.shippingAddress();
           if (shippingAddress['extension_attributes'] === undefined) {
               shippingAddress['extension_attributes'] = {};
           }
           shippingAddress['extension_attributes']['invoice_checkbox'] = shippingAddress.customAttributes['invoice_checkbox'];
           // pass execution to original action ('Magento_Checkout/js/action/set-shipping-information')
           return originalAction();
       });
   };
});

Qui andiamo a sfruttare l’uso degli extension_attributes inseriti da Magento nella maggior parte delle interfacce affinché possano essere aggiunti dati nuovi; nel nostro caso si tratta di salvare il valore della checkbox.

La nostra avventura non è finita e, nella speranza di avervi dato input utili e chiari, continuerà come promesso nella prossima puntata sul lato backend.

Riferimenti esterni e link utili:

https://github.com/bitbull-team/m2-checkout-customization

https://github.com/magento/magento2/issues/1864

http://magento.stackexchange.com/questions/135969/magento-2-how-to-add-a-custom-field-to-checkout-and-then-send-it

http://devdocs.magento.com/guides/v2.0/howdoi/checkout/checkout_form.html

http://devdocs.magento.com/guides/v2.1/howdoi/checkout/checkout_new_field.html

https://github.com/magento/devdocs/issues/844

Stay tuned!

Andra Lungu