Magento 2 – Jquery-Ui Autocomplete with KnockoutJs

autocompletecheckoutjqueryknockoutjsmagento2

The Context:

I'm implementing an autocomplete functionality in checkout page and i've added two inputs (ccity and ccity_id). I need ccity_id to be hidden while storing the value of the selected label in ccity.

The Problem:

The Component (.js) is loaded, as well as the templates but the console.log() message is not getting printed and the autocomplete is not getting called either. There are no errors at all (i mean, complainings about bindings or undefined elements, etc). So, the custom binding is not getting initialize.

What I've Done:

I have created two custom inputs (with custom component and template). For ccity and ccity_id the Component is the same but the HTML Templates are not.

In Vendor/Module/view/frontend/layout/checkout_index_index is the definition of the inputs.

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
                <arguments>
                    <argument name="jsLayout" xsi:type="array">
                        <item name="components" xsi:type="array">
                            <item name="checkout" xsi:type="array">
                                <item name="children" xsi:type="array">
                                    <item name="steps" xsi:type="array">
                                        <item name="children" xsi:type="array">
                                            <item name="shipping-step" xsi:type="array">
                                                <item name="children" xsi:type="array">
                                                    <item name="shippingAddress" xsi:type="array">
                                                        <item name="children" xsi:type="array">
                                                            <item name="shipping-address-fieldset" xsi:type="array">
                                                                <item name="children" xsi:type="array">
                                                                    <item name="ccity" xsi:type="array">
                                                                        <item name="component" xsi:type="string">Vendor_Module/js/autocomplete</item>
                                                                        <item name="template" xsi:type="string">ui/form/field</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</item>
                                                                        </item>
                                                                        <item name="provider" xsi:type="string">checkoutProvider</item>
                                                                        <item name="dataScope" xsi:type="string">shippingAddress.ccity</item>
                                                                        <item name="label" xsi:type="string">City</item>
                                                                        <item name="sortOrder" xsi:type="string">100</item>
                                                                    </item>
                                                                    <item name="ccity_id" xsi:type="array">
                                                                        <item name="component" xsi:type="string">Vendor_Module/js/autocomplete</item>
                                                                        <item name="template" xsi:type="string">ui/form/field</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</item>
                                                                            <item name="elementTmpl" xsi:type="string">Vendor_Module/autocomplete/customhidden</item>
                                                                        </item>
                                                                        <item name="provider" xsi:type="string">checkoutProvider</item>
                                                                        <item name="dataScope" xsi:type="string">shippingAddress.ccity_id</item>
                                                                        <item name="label" xsi:type="string">City_Id</item>
                                                                        <item name="sortOrder" xsi:type="string">101</item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>                                            
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </argument>
                </arguments>
        </referenceBlock>
    </body>
</page>

In Vendor/Module/web/js/autocomplete.js i've defined the component.

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

    ko.bindingHandlers.autoComplete = {

        init: function (element, valueAccessor) {

            console.log("Init - Custom Binding");
            console.log(element);
            console.log(valueAccessor);

            // valueAccessor = { selected: mySelectedOptionObservable, options: myArrayOfLabelValuePairs }
            var settings = valueAccessor();

            var selectedOption = settings.selected;
            var options = settings.options;

            var updateElementValueWithLabel = function (event, ui) {
                // Stop the default behavior
                event.preventDefault();

                // Update the value of the html element with the label
                // of the activated option in the list (ui.item)
                $(element).val(ui.item.label);

                // Update our SelectedOption observable
                if(typeof ui.item !== "undefined") {
                    // ui.item - id|label|...
                    selectedOption(ui.item);
                }
            };

            $(element).autocomplete({
                source: options,
                select: function (event, ui) {
                    updateElementValueWithLabel(event, ui);
                },
                focus: function (event, ui) {
                    updateElementValueWithLabel(event, ui);
                },
                change: function (event, ui) {
                    updateElementValueWithLabel(event, ui);
                }
            });

        }
    };

    return Abstract.extend({
        defaults: {
            elementTmpl: 'Vendor_Module/autocomplete/custominput'
        },
        selectedOption: ko.observable(''),
        getElements: function( request, response ) {
            var departmentValue = $('anotherInput').val();
            $.ajax({
                url: url.build('list/check/cities/'),
                data: {
                    q: request.term,
                    filter: departmentValue
                },
                contentType: "application/json",
                type: "POST",
                dataType: 'json',
                error : function () {
                    alert("An error have occurred.");
                },
                success : function (data) {
                    // Data is of the form: [{"city_id": "some_id", "city": "some_city"}, ...]
                    response( data );
                }
            });
        },
        selectedValue: ko.observable('')
    });
});

In the above i've defined my custom binding and i've extended the Abstract component with my custom template for ccity which is defined in Vendor/Module/view/frontend/web/template/autocomplete/custominput.html as follows:

<div class="autocomplete">
    <input id="custom_input" type="text" data-bind="autocomplete: { selected: selectedOption, options: options } " />
</div>

In Vendor/Module/view/frontend/web/template/autocomplete/customhidden.html i've defined the template for ccity_id as follows:

<input class="admin__control-text" type="hidden"
       data-bind="
        value: selectedValue,
        hasFocus: focused,
        attr: {
            name: inputName,
            placeholder: placeholder,
            'aria-describedby': noticeId,
            id: uid
    }"/>

What could i be missing?

PS:
As a basis i'm using the post in http://cameron-verhelst.be/blog/2014/04/20/knockoutjs-autocomplete/

Thanks,

Best Answer

Take a look at this post. This should give you an idea what you're doing wrong. You can read about Magento 2 jQuery-ui styles in the Magento 2 doc.