54

All tutorials are covering only adding a fields, but saving value of this fileds is skipped #mindblown. I don't know why, it's most important part of adding any field or form.

I tried to follow Magento docs, but... it sucks.

For testing purposes I try to add another fields to shipping address, just to ignore custom scopes, custom data sets, custom data providers and other undocumented stuff, which looks too weird for me.

I have no idea what mean that form is "static" or "dynamic". For me all checkout forms are builded dynamically on top of KnockoutJS templates, but... when I try "static" way, I'm able to add inputs here (so it's a static form or not?).

First I try to debug why Knockout observables just ignore my fields during parsing and sending data. I found that my fields have empty name parameter, but I can't manage a way to fix this issue. IMO it should be passed to UI component renderer via inputName parameter, same as any other options like disabled, placeholder etc. (other parameters works fine, I checked configuration generated from my XML to checkout module initialization and looks good for me)

Second I tried to use "dynamic" way with creating plugin with LayoutProcessor and passing exactly same data... and now I have fields with names, but sending still doesn't work at all.

After digging into JS I found that preparing this request is maintained in module-checkout/view/frontend/web/js/model/shipping-save-processor/default.js file, which depends on module-checkout/view/frontend/web/js/model/quote.js where Knockout observables are defined/created.

Somehow module-checkout/view/frontend/web/js/model/address-converter.js update this observables and depends on module-checkout/view/frontend/web/js/model/new-customer-address.js, where I finally found some interesting configuration options - list of all address fields.

When I add my fields here, scripts start parsing and sending them, OFC I get 500, b/c backend doesn't recognize them... (don't ask, I'm not a backend dev)

So here comes my questions:

  • It's a right way to handle this type of customization? (b/c looks weird for me)
  • How to send values of fields not related to new addresses? I didn't see similar configuration anywhere. In my case I'd like to send order comment (textarea) and invoice request (checkbox). Both shouldn't be saved as a address, b/c some users might want to save this address for future use.
  • Is there any documentation about "static" and "dynamic" forms or some examples / comparison? It's worth thinking about it this way?

Additional existential question:

  • Why this is so inconsistent? Why I have to define tons of parameters in XML/PHP files, while Magento at all can only render a input and then I have to handle everything on my own?
3
  • 3
    Magento Devdocs has recently added some new docs about using UI Components. We are actually in the process of writing an entire new guide... but still have a ways to go. Please take a look at devdocs.magento.com/guides/v2.1/ui_comp_guide/bk-ui_comps.html. Unfortunately, we have not yet completed the topics on data sources, which is what it sounds like you mostly need. There is a topic on the configuration flow using UI Components, which might help. I'll work on finding more answers.
    – tanberry
    Commented Sep 13, 2016 at 20:20
  • Why it has to be a nightmare adding a simple field on the checkout? I just can't understand. And almost everything is that hard
    – Marc Pont
    Commented Jun 16, 2017 at 16:15
  • @slayerbleast World is changing, server-side rendering in dying. So it's different, not harder without any reason.
    – igloczek
    Commented Jun 17, 2017 at 5:26

2 Answers 2

58
+50

I will try to answer your question(s).

  1. No. This is not a correct way to add custom attributes to the shipping address form. You do not need to edit new-customer-address.js. Indeed, this JS file lists all predefined address attributes and matches corresponding backend interface \Magento\Quote\Api\Data\AddressInterface but Magento provides ability to pass any custom attributes to backend without modification of the backend/frontend components.

    Mentioned JS component has customAttributes property. Your custom attributes will be automatically handled if their $dataScopePrefix is 'shippindAddress.custom_attributes'.

  2. If I understood your question correctly, you have data that is not a part of customer address but you need to send it to backend as well. The answer to this question is:

    It depends. For instance, you can choose the following approach: add custom form to checkout page that includes all your extra fields (like comment, invoice request etc), add JS logic that will handle this form based on some events, and provide a custom service that will receive the data from frontend and store it somewhere for future use.

  3. All official documentation related to checkout is located at http://devdocs.magento.com/guides/v2.1/howdoi/checkout/checkout_overview.html. The term static refers to the forms where all the fields are already known/predefined (for instance: form will always have 2 text fields with predefined labels) and cannot change based on some settings in the backend.

    Such forms can be declared using layout XML configuration. On the other hand, term dynamic refers to forms whose field set can change (for instance: checkout form can have more/less fields based on configuration settings).

    In this case the only way to declare such form is to use LayoutProcessor plugin.

  4. :) Magento tries to cover as much possible use cases that can be significant to the merchants during Magento use/customization as possible. Sometimes this leads to situation when some simple use cases become more complex.

Hope, this helps.

=========================================================================

OK... Lets write some code ;)

  1. PHP code that inserts additional field in LayoutProcessor

========

/**
 * @author aakimov
 */
$customAttributeCode = 'custom_field';
$customField = [
    'component' => 'Magento_Ui/js/form/element/abstract',
    'config' => [
        // customScope is used to group elements within a single form (e.g. they can be validated separately)
        'customScope' => 'shippingAddress.custom_attributes',
        'customEntry' => null,
        'template' => 'ui/form/field',
        'elementTmpl' => 'ui/form/element/input',
        'tooltip' => [
            'description' => 'Yes, this works. I tested it. Sacrificed my lunch break but verified this approach.',
        ],
    ],
    'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode,
    'label' => 'Custom Attribute',
    'provider' => 'checkoutProvider',
    'sortOrder' => 0,
    'validation' => [
       'required-entry' => true
    ],
    'options' => [],
    'filterBy' => null,
    'customEntry' => null,
    'visible' => true,
];

$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$customAttributeCode] = $customField;

return $jsLayout;

As I already mentioned, this will add your field to customAttributes property of the JS address object. This property was designed to contain custom EAV address attributes and is related to \Magento\Quote\Model\Quote\Address\CustomAttributeListInterface::getAttributes method.

The code above will automatically handle local storage persistence on frontend. You can get your field value from local storage using checkoutData.getShippingAddressFromData() (where checkoutData is Magento_Checkout/js/checkout-data).

  1. Add mixin to change the behavior of 'Magento_Checkout/js/action/set-shipping-information' (this component is responsible for data submission between shipping and billing checkout steps)

========

2.1. Create your_module_name/view/frontend/requirejs-config.js


/**
 * @author aakimov
 */
var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/action/set-shipping-information': {
                '<your_module_name>/js/action/set-shipping-information-mixin': true
            }
        }
    }
};

2.2. Create your_module_name/view/frontend/web/js/action/set-shipping-information-mixin.js


/**
 * @author aakimov
 */
/*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'] = {};
            }

            // you can extract value of extension attribute from any place (in this example I use customAttributes approach)
            shippingAddress['extension_attributes']['custom_field'] = shippingAddress.customAttributes['custom_field'];
            // pass execution to original action ('Magento_Checkout/js/action/set-shipping-information')
            return originalAction();
        });
    };
});
  1. Create your_module_name/etc/extension_attributes.xml

========

<?xml version="1.0"?>
<!--
/**
 * @author aakimov
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
        <attribute code="custom_field" type="string" />
    </extension_attributes>
</config>

This will add an extension attribute to address model on backend side. Extension attributes are one of the extension points that Magento provides. To access your data on backend you can use:

// Magento will generate interface that includes your custom attribute
$value = $address->getExtensionAttributes()->getCustomField();

Hope, this helps and will be added to official documentation.

11
0

In my experience, m2 use xml to define components such as fields etc.. It is help for debugging, interactive with data more easier. On field of front-enders you should try to override checkout templates and add your own custom fields in there. With K.O helps you ability to work and bind data from frontend and backend

3
  • 4
    It's wrong advice, b/c checkout templates doesn't contain and should never contain manually added input fields. We have to use KO to render templates in selected regions (everything is builded using UI components)
    – igloczek
    Commented Sep 13, 2016 at 8:50
  • Could you share an example of add custom field ui-components in frontend?
    – mrtuvn
    Commented Sep 13, 2016 at 11:20
  • As @igloczek says It's wrong advice, because checkout templates should never contain manually added input fields.
    – diazwatson
    Commented Jun 1, 2017 at 14:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.