odoo18/addons/website_sale/static/src/js/address.js

228 lines
7.4 KiB
JavaScript

/** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
import { rpc } from "@web/core/network/rpc";
import { debounce } from "@web/core/utils/timing";
publicWidget.registry.websiteSaleAddress = publicWidget.Widget.extend({
// /shop/address
selector: '.o_wsale_address_fill',
events: {
'change select[name="country_id"]': '_onChangeCountry',
'click #save_address': '_onSaveAddress',
"change select[name='state_id']": "_onChangeState",
},
/**
* @constructor
*/
init: function () {
this._super.apply(this, arguments);
this.http = this.bindService('http');
this._changeCountry = debounce(this._changeCountry.bind(this), 500);
this.addressForm = document.querySelector('form.checkout_autoformat');
this.errorsDiv = document.getElementById('errors');
this.addressType = this.addressForm['address_type'].value;
this.countryCode = this.addressForm.dataset.companyCountryCode;
this.requiredFields = this.addressForm.required_fields.value.split(',');
},
/**
* @override
*/
start() {
const def = this._super(...arguments);
this.requiredFields.forEach((fname) => {
this._markRequired(fname, true);
})
this._changeCountry(true);
return def;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onChangeCountry(ev) {
return this._changeCountry();
},
/**
* @private
* @param {Event} ev
*/
_onChangeState(ev) {
return Promise.resolve();
},
/**
* @private
*/
async _changeCountry(init=false) {
const countryId = parseInt(this.addressForm.country_id.value);
if (!countryId) {
return;
}
const data = await rpc(
`/shop/country_info/${parseInt(countryId)}`,
{address_type: this.addressType},
);
if (data.phone_code !== 0) {
this.addressForm.phone.placeholder = '+' + data.phone_code;
} else {
this.addressForm.phone.placeholder = '';
}
// populate states and display
var selectStates = this.addressForm.state_id;
if (!init || selectStates.options.length === 1) {
// dont reload state at first loading (done in qweb)
if (data.states.length || data.state_required) {
// empty existing options, only keep the placeholder.
selectStates.options.length = 1;
// create new options and append them to the select element
data.states.forEach((state) => {
let option = new Option(state[1], state[0]);
// Used by localizations
option.setAttribute('data-code', state[2]);
selectStates.appendChild(option);
});
this._showInput('state_id');
} else {
this._hideInput('state_id');
}
}
// manage fields order / visibility
if (data.fields) {
if (data.zip_before_city) {
this._getInputDiv('zip').after(this._getInputDiv('city'));
} else {
this._getInputDiv('zip').before(this._getInputDiv('city'));
}
var all_fields = ['street', 'zip', 'city'];
all_fields.forEach((fname) => {
if (data.fields.includes(fname)) {
this._showInput(fname);
} else {
this._hideInput(fname);
}
});
}
const required_fields = this.addressForm.querySelectorAll(':required');
required_fields.forEach((element) => {
// remove requirement on previously required fields
if (
!data.required_fields.includes(element.name)
&& !this.requiredFields.includes(element.name)
) {
this._markRequired(element.name, false);
}
});
data.required_fields.forEach((fieldName) => {
this._markRequired(fieldName, true);
})
},
_getInputDiv(name) {
return this.addressForm[name].parentElement;
},
_getInputLabel(name) {
const input = this.addressForm[name];
return input?.parentElement.querySelector(`label[for='${input.id}']`);
},
_showInput(name) {
// show parent div, containing label and input
this.addressForm[name].parentElement.style.display = '';
},
_hideInput(name) {
// show parent div, containing label and input
this.addressForm[name].parentElement.style.display = 'none';
},
_markRequired(name, required) {
const input = this.addressForm[name];
if (input) {
input.required = required;
}
this._getInputLabel(name)?.classList.toggle('label-optional', !required);
},
/**
* Disable the button, submit the form and add a spinner while the submission is ongoing
*
* @private
* @param {Event} ev
*/
async _onSaveAddress(ev) {
if (!this.addressForm.reportValidity()) {
return
}
const submitButton = ev.currentTarget;
if (!ev.defaultPrevented && !submitButton.disabled) {
ev.preventDefault();
submitButton.disabled = true;
const spinner = document.createElement('span');
spinner.classList.add('fa', 'fa-cog', 'fa-spin');
submitButton.appendChild(spinner);
const result = await this.http.post(
'/shop/address/submit',
new FormData(this.addressForm),
)
if (result.successUrl) {
window.location = result.successUrl;
} else {
// Highlight missing/invalid form values
document.querySelectorAll('.is-invalid').forEach(element => {
if (!result.invalid_fields.includes(element.name)) {
element.classList.remove('is-invalid');
}
})
result.invalid_fields.forEach(
fieldName => this.addressForm[fieldName].classList.add('is-invalid')
);
// Display the error messages
// NOTE: setCustomValidity is not used as we would have to reset the error msg on
// input update, which is not worth catching for the rare cases where the
// server-side validation will catch validation issues (now that required inputs
// are also handled client-side)
const newErrors = result.messages.map(message => {
const errorHeader = document.createElement('h5');
errorHeader.classList.add('text-danger');
errorHeader.appendChild(document.createTextNode(message));
return errorHeader;
});
this.errorsDiv.replaceChildren(...newErrors);
// Re-enable button and remove spinner
submitButton.disabled = false;
spinner.remove();
}
}
},
});
export default publicWidget.registry.websiteSaleAddress;