The Problem:
I've implemented jQuery-ui Autocomplete with Knockout Js in Checkout Page but some input fields that are binded to my Component throw a validation error ("Required Field") when i update the input's value programmatically.
The Context:
I've asked already a question for the way of implementing the Autocomplete functionality but taking into account that the new problems are more specific i've posted this question. This is the link for the other one: Jquery-Ui Autocomplete with KnockoutJs Magento 2
What I've Done:
In Vendor/Module/Plugin/Checkout/Block/Checkout/LayoutProcessorPlugin.php
public function afterProcess(
\Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
array $jsLayout
) {
$departmentField = [
'component' => 'Vendor_Module/js/shippingAutocomplete',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress',
'template' => 'ui/form/field',
'elementTmpl' => 'Vendor_Module/autocomplete/shippingAddress/regionInput',
'id' => 'region',
'tooltip' => [
'description' => 'Select a Department',
],
],
'dataScope' => 'shippingAddress.region',
'label' => 'Department',
'provider' => 'checkoutProvider',
'sortOrder' => 51,
'validation' => [
'required-entry' => true
],
'options' => [],
'visible' => true,
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children']['region'] = $departmentField;
$departmentIdField = [
'component' => 'Vendor_Module/js/shippingAutocomplete',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress',
'template' => 'ui/form/field',
'elementTmpl' => 'Vendor_Module/autocomplete/regionIdInput',
'id' => 'region_id',
],
'dataScope' => 'shippingAddress.region_id',
'provider' => 'checkoutProvider',
'sortOrder' => 50,
'validation' => [
'required-entry' => true
],
'options' => [],
'visible' => true,
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children']['region_id'] = $departmentIdField;
$cityField = [
'component' => 'Vendor_Module/js/shippingAutocomplete',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress',
'template' => 'ui/form/field',
'elementTmpl' => 'Vendor_Module/autocomplete/shippingAddress/cityInput',
'id' => 'city',
'tooltip' => [
'description' => 'Select a City.',
],
],
'dataScope' => 'shippingAddress.city',
'label' => 'City',
'provider' => 'checkoutProvider',
'sortOrder' => 53,
'validation' => [
'required-entry' => true
],
'options' => [],
'visible' => true,
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children']['city'] = $cityField;
$cityIdField = [
'component' => 'Vendor_Module/js/shippingAutocomplete',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress',
'template' => 'ui/form/field',
'elementTmpl' => 'Vendor_Module/autocomplete/cityIdInput',
'id' => 'city_id',
],
'dataScope' => 'shippingAddress.city_id',
'provider' => 'checkoutProvider',
'sortOrder' => 52,
'validation' => [
'required-entry' => true
],
'options' => [],
'visible' => true,
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children']['city_id'] = $cityIdField;
$postcodeField = [
'component' => 'Vendor_Module/js/shippingAutocomplete',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress',
'template' => 'ui/form/field',
'elementTmpl' => 'Vendor_Module/autocomplete/postcodeInput',
'id' => 'postcode',
],
'dataScope' => 'shippingAddress.postcode',
'label' => 'Postal Code',
'provider' => 'checkoutProvider',
'sortOrder' => 54,
'validation' => [
'required-entry' => true
],
'options' => [],
'visible' => true,
];
return $jsLayout;
}
My Vendor/Module/view/frontend/web/js/shippingAutocomplete.js looks like this:
define([
'Magento_Ui/js/form/element/abstract',
'mage/url',
'ko',
'jquery',
'jquery/ui'
], function (Abstract, url, ko, $) {
'use strict';
ko.bindingHandlers.shippingAutoComplete = {
init: function (element, valueAccessor) {
console.log("Init - Custom Binding Shipping");
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);
if (ui.item.type == 'city') {
var cityId = $('[name="city_id"]');
cityId.val(ui.item.value);
var postcode = $('[name="region_id"]').val()+cityId.val();
$('[name="postcode"]').val(postcode);
} else if (ui.item.type == 'department') {
$('[name="region_id"]').val(ui.item.value);
}
// Update our SelectedOption observable
if(typeof ui.item !== "undefined") {
// ui.item - id|label|...
selectedOption(ui.item);
//selectedValue(ui.item.value);
}
};
$(element).autocomplete({
source: options,
select: function (event, ui) {
updateElementValueWithLabel(event, ui);
}
});
}
};
return Abstract.extend({
selectedDepartment: ko.observable(''),
selectedCity: ko.observable(''),
postCode: ko.observable(''),
getDepartments: function( request, response ) {
$.ajax({
url: url.build('list/check/departments/'),
data: JSON.stringify({
q: request.term
}),
contentType: "application/json",
type: "POST",
dataType: 'json',
error : function () {
alert("An error have occurred.");
},
success : function (data) {
//Data is a string of the form: '[{"label": label, "value": value}]'
var items = JSON.parse(data);
response( items );
}
});
},
getCities: function( request, response ) {
var departmentValue = $('[name="region"]').val();
$.ajax({
url: url.build('list/check/cities/'),
data: JSON.stringify({
q: request.term,
filter: departmentValue
}),
contentType: "application/json",
type: "POST",
dataType: 'json',
error : function () {
alert("An error have occurred.");
},
success : function (data) {
//Data is a string of the form: '[{"label": label, "value": value}]'
var items = JSON.parse(data);
response( items );
}
});
}
});
});
The inputs are as follows:
In Vendor/Module/view/frontend/web/template/autocomplete/shippingAddress/regionInput.html
<input class="admin__control-text" type="text"
data-bind="
shippingAutoComplete: {
selected: selectedDepartment,
options: getDepartments
},
event: {change: userChanges},
value: value,
hasFocus: focused,
valueUpdate: valueUpdate,
attr: {
name: inputName,
placeholder: placeholder,
'aria-describedby': noticeId,
id: uid,
disabled: disabled
}"/>
In Vendor/Module/view/frontend/web/template/autocomplete/shippingAddress/cityInput.html
<input class="admin__control-text" type="text"
data-bind="
shippingAutoComplete: {
selected: selectedCity,
options: getCities
},
event: {change: userChanges},
value: value,
hasFocus: focused,
valueUpdate: valueUpdate,
attr: {
name: inputName,
placeholder: placeholder,
'aria-describedby': noticeId,
id: uid,
disabled: disabled
}"/>
In Vendor/Module/view/frontend/web/template/autocomplete/regionIdInput.html
<input class="admin__control-text"
data-bind="
value: value,
hasFocus: focused,
attr: {
name: inputName,
placeholder: placeholder,
'aria-describedby': noticeId,
id: uid
}"/>
In Vendor/Module/view/frontend/web/template/autocomplete/cityIdInput.html
<input class="admin__control-text"
data-bind="
value: value,
hasFocus: focused,
attr: {
name: inputName,
placeholder: placeholder,
'aria-describedby': noticeId,
id: uid
}"/>
In Vendor/Module/view/frontend/web/template/autocomplete/postcodeInput.html
<input class="admin__control-text" type="text"
data-bind="
event: {change: userChanges},
value: value,
hasFocus: focused,
valueUpdate: valueUpdate,
attr: {
name: inputName,
placeholder: placeholder,
'aria-describedby': noticeId,
id: uid,
disabled: disabled
}"/>