4

Excuse me for the bad wording in the title.. It's a little hard to explain what I'm trying to accomplish in a few words.

I'm trying to add a custom field to the shipping address and billing address forms in the Magento 2.1.2 checkout with a custom uiComponent that applies its own bindings / listeners to that field. Below I'll try to explain what I already did (simplified):

I have added the field myCustomField to both the shipping and billing forms, using the following XML in checkout_index_index.xml for the shipping form:

<item name="myCustomField" xsi:type="array">
    <item name="component" xsi:type="string">Vendor_MyModule/js/view/myCustomField</item>
    <item name="config" xsi:type="array">
        <item name="customScope" xsi:type="string">shippingAddress</item>
        <item name="template" xsi:type="string">Vendor_MyModule/form/myCustomField</item>
        <item name="id" xsi:type="string">myCustomField</item>
    </item>
    <item name="dataScope" xsi:type="string">shippingAddress.myCustomField</item>
    <item name="provider" xsi:type="string">checkoutProvider</item>
    <item name="visible" xsi:type="boolean">true</item>
    <item name="sortOrder" xsi:type="string">45</item>
</item>

For the billing form I have added the a similar configuration through a plugin/interceptor (Vendor/MyModule/Block/Plugin/Checkout/LayoutProcessor/Plugin.php):

public function afterProcess(\Magento\Checkout\Block\Checkout\LayoutProcessor $subject, $result){

    //Loop over all payment methods
    foreach($result['components']['checkout']['children']['steps']['children']['billing-step']['children']
            ['payment']['children']['payments-list']['children'] as $key => $paymentMethodForm) {

        //Get the payment method name (without -form)
        $paymentMethod = explode("-", $key)[0];


        if(array_key_exists("form-fields", $paymentMethodForm['children'])){

            if(!array_key_exists('myCustomField', $paymentMethodForm['children']['form-fields']['children'])){

                $result['components']['checkout']['children']['steps']['children']['billing-step']['children']
                ['payment']['children']['payments-list']['children'][$key]['children']['form-fields']['children']['myCustomField'] = [
                    "component" => "Vendor_MyModule/js/view/myCustomField",
                    "config" => [
                        "customScope" => "billingAddress" . $paymentMethod,
                        "template" => "Vendor_MyModule/form/myCustomField",
                        "id" => "myCustomField_" . $key
                    ],
                    "dataScope" => "billingAddress" . $paymentMethod . ".myCustomField",
                    "provider" => "checkoutProvider",
                    "visible" => "true",
                    "sortOrder" => "45"
                ];
            }
        }

    }



    return $result;
}

This is what the Vendor_MyModule/js/view/myCustomField component / viewmodel looks like:

define(
    [
        'ko',
        'jquery',
        'Magento_Ui/js/form/element/abstract'
    ],
    function(ko, $, Abstract) {
        'use strict';

        return Abstract.extend({

            myValue: ko.observable(),

            defaults: {
                listens: {
                    myValue: 'changed',
                }
            },

            initialize: function(){
                this._super();

                return this;
            },

            changed: function(value){
                console.log("Myvalue was changed!", value);
            }
        });
    }
);

And this is what the template looks like:

<div class="field" data-bind="visible: visible, attr: {'name': element.dataScope}, css: additionalClasses">
    <div class="fieldset">
        <div class="field">
            <label for="myCustomField" data-bind="i18n: 'My custom field'"></label>
            <div class="control">
                <input class="input-text myCustomField" id="myCustomField" type="text" name="myCustomField" data-bind="textInput: myValue" />
            </div>
        </div>

    </div>
</div>

All this successfully renders an additional field in the shipping address form and (all of) the billing address forms. As you can see I have set up an observable variable myValue: ko.observable() and I'm using data-bind="textInput: myValue" on the input to bind myValue to the input field. The listens: {} object in my viewmodel makes the changed() function execute whenever the myValue observable is changed.

However, when I change the value of the input field in any of the forms, it also gets updated in ALL of the other forms. With the above code in place, I get 47 console.log entries saying 'Myvalue was changed!' any time I enter a character in the input field. I'm guessint this happens because the page contains multiple input fields that all bind to the myValue observable.

How do I make sure that the observable, listeners etc. are only called for the current input field I'm editing, and not for all occurrences of the input field anywhere on the page? In other words: how do I 'isolate' my viewmodel so it knows it should only apply its functionality to the input field being changed?

Thanks!

1 Answer 1

2

you can directly listen for the specific field using the datascope you've defined in the XML shippingAddress.myCustomField

defaults: {
   listens: {
      '$.checkoutProvider:shippingAddress.myCustomField.myValue': 'changed',
   }
},
2
  • Thanks for answering, but I'm afraid this isn't exactly what I'm looking for. Yes, when I listen to the specific field throug the dataScope, the changed function is called only for that specific field, but still the value I enter into the field is automatically represented in all the occurrences of the myCustomField input field because the myValue observable is sort of 'global'. How can I make sure that the myValue observable is unique for every occurence of the input field? Nov 2, 2016 at 12:52
  • OK, I think I just found out what the problem was: in stead of defining the observable 'myValue' through myValue: ko.observable() I had to define it with this.observe('myValue'); in a initObservable function. Will update my question or add an answer for this later. Nov 2, 2016 at 13:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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