diff --git a/addons/partner_autocomplete/__manifest__.py b/addons/partner_autocomplete/__manifest__.py
index 72cdffb709b..66185ad989e 100644
--- a/addons/partner_autocomplete/__manifest__.py
+++ b/addons/partner_autocomplete/__manifest__.py
@@ -23,12 +23,10 @@
'auto_install': True,
'assets': {
'web.assets_backend': [
- 'partner_autocomplete/static/src/scss/partner_autocomplete.scss',
- 'partner_autocomplete/static/src/js/partner_autocomplete_core.js',
- 'partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js',
- 'partner_autocomplete/static/src/js/partner_autocomplete_many2one.js',
- 'partner_autocomplete/static/src/js/web_company_autocomplete.js',
- 'partner_autocomplete/static/src/xml/**/*',
+ 'partner_autocomplete/static/lib/jsvat.js',
+ 'partner_autocomplete/static/src/scss/*',
+ 'partner_autocomplete/static/src/js/*',
+ 'partner_autocomplete/static/src/xml/*',
],
'web.tests_assets': [
'partner_autocomplete/static/lib/**/*',
diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js
index 8831b9312b4..c0808e3893e 100644
--- a/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js
+++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js
@@ -1,56 +1,52 @@
-/* global checkVATNumber */
-odoo.define('partner.autocomplete.Mixin', function (require) {
-'use strict';
+/** @odoo-module **/
-var concurrency = require('web.concurrency');
-
-var core = require('web.core');
-var Qweb = core.qweb;
-var utils = require('web.utils');
-var _t = core._t;
+import { _t } from "@web/core/l10n/translation";
+import { KeepLast } from "@web/core/utils/concurrency";
+import { useService } from "@web/core/utils/hooks";
+import { renderToMarkup } from "@web/core/utils/render";
+import { getDataURLFromFile } from "@web/core/utils/urls";
/**
- * This mixin only works with classes having EventDispatcherMixin in 'web.mixins'
+ * Get list of companies via Autocomplete API
+ *
+ * @param {string} value
+ * @returns {Promise}
+ * @private
*/
-var PartnerAutocompleteMixin = {
- _dropPreviousOdoo: new concurrency.DropPrevious(),
- _dropPreviousClearbit: new concurrency.DropPrevious(),
- _timeout : 1000, // Timeout for Clearbit autocomplete in ms
+export function usePartnerAutocomplete() {
+ const keepLastOdoo = new KeepLast();
+ const keepLastClearbit = new KeepLast();
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
+ const http = useService("http");
+ const notification = useService("notification");
+ const orm = useService("orm");
- /**
- * Get list of companies via Autocomplete API
- *
- * @param {string} value
- * @returns {Promise}
- * @private
- */
- _autocomplete: function (value) {
- var self = this;
+ function autocomplete(value, isVAT = false) {
value = value.trim();
- var isVAT = this._isVAT(value);
- var odooSuggestions = [];
- var clearbitSuggestions = [];
- return new Promise(function (resolve, reject) {
- var odooPromise = self._getOdooSuggestions(value, isVAT).then(function (suggestions){
+ let odooSuggestions = [];
+ let clearbitSuggestions = [];
+ return new Promise((resolve, reject) => {
+ const odooPromise = getOdooSuggestions(value, isVAT).then((suggestions) => {
odooSuggestions = suggestions;
});
// Only get Clearbit suggestions if not a VAT number
- var clearbitPromise = isVAT ? false : self._getClearbitSuggestions(value).then(function (suggestions){
+ const clearbitPromise = isVAT ? false : getClearbitSuggestions(value).then((suggestions) => {
+ suggestions.forEach((suggestion) => {
+ suggestion.label = suggestion.name;
+ suggestion.website = suggestion.domain;
+ suggestion.description = suggestion.website;
+ });
clearbitSuggestions = suggestions;
});
- var concatResults = function () {
+ const concatResults = () => {
// Add Clearbit result with Odoo result (with unique domain)
if (clearbitSuggestions && clearbitSuggestions.length) {
- var websites = odooSuggestions.map(function (suggestion) {
+ const websites = odooSuggestions.map((suggestion) => {
return suggestion.website;
});
- clearbitSuggestions.forEach(function (suggestion) {
+ clearbitSuggestions.forEach((suggestion) => {
if (websites.indexOf(suggestion.domain) < 0) {
websites.push(suggestion.domain);
odooSuggestions.push(suggestion);
@@ -58,19 +54,18 @@ var PartnerAutocompleteMixin = {
});
}
- odooSuggestions = _.filter(odooSuggestions, function (suggestion) {
+ odooSuggestions = odooSuggestions.filter((suggestion) => {
return !suggestion.ignored;
});
- _.each(odooSuggestions, function(suggestion){
- delete suggestion.ignored;
+ odooSuggestions.forEach((suggestion) => {
+ delete suggestion.ignored;
});
return resolve(odooSuggestions);
};
- self._whenAll([odooPromise, clearbitPromise]).then(concatResults, concatResults);
+ whenAll([odooPromise, clearbitPromise]).then(concatResults, concatResults);
});
-
- },
+ }
/**
* Get enrichment data
@@ -82,13 +77,13 @@ var PartnerAutocompleteMixin = {
* @returns {Promise}
* @private
*/
- _enrichCompany: function (company) {
- return this._rpc({
- model: 'res.partner',
- method: 'enrich_company',
- args: [company.website, company.partner_gid, company.vat],
- });
- },
+ function enrichCompany(company) {
+ return orm.call(
+ 'res.partner',
+ 'enrich_company',
+ [company.website, company.partner_gid, company.vat]
+ );
+ }
/**
* Get the company logo as Base 64 image from url
@@ -97,14 +92,16 @@ var PartnerAutocompleteMixin = {
* @returns {Promise}
* @private
*/
- _getCompanyLogo: function (url) {
- return this._getBase64Image(url).then(function (base64Image) {
+ async function getCompanyLogo(url) {
+ try {
+ const base64Image = await getBase64Image(url)
// base64Image equals "data:" if image not available on given url
return base64Image ? base64Image.replace(/^data:image[^;]*;base64,?/, '') : false;
- }).catch(function () {
+ }
+ catch {
return false;
- });
- },
+ }
+ }
/**
* Get enriched data + logo before populating partner form
@@ -112,54 +109,54 @@ var PartnerAutocompleteMixin = {
* @param {Object} company
* @returns {Promise}
*/
- _getCreateData: function (company) {
- var self = this;
-
- var removeUselessFields = function (company) {
- var fields = 'label,description,domain,logo,legal_name,ignored,email'.split(',');
- fields.forEach(function (field) {
+ function getCreateData(company) {
+ const removeUselessFields = (company) => {
+ // Delete attribute to avoid "Field_changed" errors
+ const fields = ['label', 'description', 'domain', 'logo', 'legal_name', 'ignored', 'email', 'bank_ids', 'classList'];
+ fields.forEach((field) => {
delete company[field];
});
- var notEmptyFields = "country_id,state_id".split(',');
- notEmptyFields.forEach(function (field) {
- if (!company[field]) delete company[field];
+ // Remove if empty and format it otherwise
+ const many2oneFields = ['country_id', 'state_id'];
+ many2oneFields.forEach((field) => {
+ if (!company[field]) {
+ delete company[field];
+ }
});
};
- return new Promise(function (resolve) {
+ return new Promise((resolve) => {
// Fetch additional company info via Autocomplete Enrichment API
- var enrichPromise = self._enrichCompany(company);
+ const enrichPromise = enrichCompany(company);
// Get logo
- var logoPromise = company.logo ? self._getCompanyLogo(company.logo) : false;
- self._whenAll([enrichPromise, logoPromise]).then(function (result) {
- var company_data = result[0];
- var logo_data = result[1];
-
+ const logoPromise = company.logo ? getCompanyLogo(company.logo) : false;
+ whenAll([enrichPromise, logoPromise]).then(([company_data, logo_data]) => {
// The vat should be returned for free. This is the reason why
// we add it into the data of 'company' even if an error such as
- // an insufficient credit error is raised.
+ // an insufficient credit error is raised.
if (company_data.error && company_data.vat) {
company.vat = company_data.vat;
}
if (company_data.error) {
if (company_data.error_message === 'Insufficient Credit') {
- self._notifyNoCredits();
- } else if (company_data.error_message === 'No Account Token') {
- self._notifyAccountToken();
- } else {
- self.displayNotification({ message: company_data.error_message });
+ notifyNoCredits();
+ }
+ else if (company_data.error_message === 'No Account Token') {
+ notifyAccountToken();
+ }
+ else {
+ notification.add(company_data.error_message);
}
company_data = company;
}
- if (_.isEmpty(company_data)) {
+ if (!Object.keys(company_data).length) {
company_data = company;
}
- // Delete attribute to avoid "Field_changed" errors
removeUselessFields(company_data);
// Assign VAT coming from parent VIES VAT query
@@ -172,33 +169,7 @@ var PartnerAutocompleteMixin = {
});
});
});
- },
-
- /**
- * Check connectivity
- *
- * @returns {boolean}
- */
- _isOnline: function () {
- return navigator && navigator.onLine;
- },
-
- /**
- * Validate: Not empty and length > 1
- *
- * @param {string} search_val
- * @param {string} onlyVAT : Only valid VAT Number search
- * @returns {boolean}
- * @private
- */
- _validateSearchTerm: function (search_val, onlyVAT) {
- if (onlyVAT) return this._isVAT(search_val);
- else return search_val && search_val.length > 2;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
+ }
/**
* Returns a promise which will be resolved with the base64 data of the
@@ -208,18 +179,18 @@ var PartnerAutocompleteMixin = {
* @param {string} url : the url where to find the image to fetch
* @returns {Promise}
*/
- _getBase64Image: function (url) {
- return new Promise(function (resolve, reject) {
- var xhr = new XMLHttpRequest();
- xhr.onload = function () {
- utils.getDataURLFromFile(xhr.response).then(resolve);
+ function getBase64Image(url) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.onload = () => {
+ getDataURLFromFile(xhr.response).then(resolve);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onerror = reject;
xhr.send();
});
- },
+ }
/**
* Use Clearbit Autocomplete API to return suggestions
@@ -228,25 +199,11 @@ var PartnerAutocompleteMixin = {
* @returns {Promise}
* @private
*/
- _getClearbitSuggestions: function (value) {
- var url = 'https://autocomplete.clearbit.com/v1/companies/suggest?query=' + value;
- var def = $.ajax({
- url: url,
- dataType: 'json',
- timeout: this._timeout,
- success: function (suggestions) {
- suggestions.map(function (suggestion) {
- suggestion.label = suggestion.name;
- suggestion.website = suggestion.domain;
- suggestion.description = suggestion.website;
- return suggestion;
- });
- return suggestions;
- },
- });
-
- return this._dropPreviousClearbit.add(def);
- },
+ async function getClearbitSuggestions(value) {
+ const url = `https://autocomplete.clearbit.com/v1/companies/suggest?query=${value}`;
+ const prom = http.get(url);
+ return keepLastClearbit.add(prom);
+ }
/**
* Use Odoo Autocomplete API to return suggestions
@@ -256,56 +213,31 @@ var PartnerAutocompleteMixin = {
* @returns {Promise}
* @private
*/
- _getOdooSuggestions: function (value, isVAT) {
- var method = isVAT ? 'read_by_vat' : 'autocomplete';
+ async function getOdooSuggestions(value, isVAT) {
+ const method = isVAT ? 'read_by_vat' : 'autocomplete';
- var def = this._rpc({
- model: 'res.partner',
- method: method,
- args: [value],
- }, {
- shadow: true,
- }).then(function (suggestions) {
- suggestions.map(function (suggestion) {
- suggestion.logo = suggestion.logo || '';
- suggestion.label = suggestion.legal_name || suggestion.name;
- if (suggestion.vat) suggestion.description = suggestion.vat;
- else if (suggestion.website) suggestion.description = suggestion.website;
+ const prom = orm.silent.call(
+ 'res.partner',
+ method,
+ [value],
+ );
- if (suggestion.country_id && suggestion.country_id.display_name) {
- if (suggestion.description) suggestion.description += _.str.sprintf(' (%s)', suggestion.country_id.display_name);
- else suggestion.description += suggestion.country_id.display_name;
- }
+ const suggestions = await keepLastOdoo.add(prom);
+ suggestions.map((suggestion) => {
+ suggestion.logo = suggestion.logo || '';
+ suggestion.label = suggestion.legal_name || suggestion.name;
+ if (suggestion.vat) suggestion.description = suggestion.vat;
+ else if (suggestion.website) suggestion.description = suggestion.website;
- return suggestion;
- });
- return suggestions;
+ if (suggestion.country_id && suggestion.country_id.display_name) {
+ if (suggestion.description) suggestion.description += ` (${suggestion.country_id.display_name})`;
+ else suggestion.description += suggestion.country_id.display_name;
+ }
+
+ return suggestion;
});
-
- return this._dropPreviousOdoo.add(def);
- },
- /**
- * Check if searched value is possibly a VAT : 2 first chars = alpha + min 5 numbers
- *
- * @param {string} search_val
- * @returns {boolean}
- * @private
- */
- _isVAT: function (search_val) {
- var str = this._sanitizeVAT(search_val);
- return checkVATNumber(str);
- },
-
- /**
- * Sanitize search value by removing all not alphanumeric
- *
- * @param {string} search_value
- * @returns {string}
- * @private
- */
- _sanitizeVAT: function (search_value) {
- return search_value ? search_value.replace(/[^A-Za-z0-9]/g, '') : '';
- },
+ return suggestions;
+ }
/**
* Utility to wait for multiple promises
@@ -316,60 +248,49 @@ var PartnerAutocompleteMixin = {
* @returns {Promise}
* @private
*/
- _whenAll: function (promises) {
- return Promise.all(promises.map(function (p) {
+ function whenAll(promises) {
+ return Promise.all(promises.map((p) => {
return Promise.resolve(p);
}));
- },
+ }
/**
* @private
* @returns {Promise}
*/
- _notifyNoCredits: function () {
- var self = this;
- return this._rpc({
- model: 'iap.account',
- method: 'get_credits_url',
- args: ['partner_autocomplete'],
- }).then(function (url) {
- var title = _t('Not enough credits for Partner Autocomplete');
- var content = Qweb.render('partner_autocomplete.insufficient_credit_notification', {
- credits_url: url
+ async function notifyNoCredits() {
+ const url = await orm.call(
+ 'iap.account',
+ 'get_credits_url',
+ ['partner_autocomplete'],
+ );
+ const title = _t('Not enough credits for Partner Autocomplete');
+ const content = renderToMarkup('partner_autocomplete.InsufficientCreditNotification', {
+ credits_url: url
+ });
+ notification.add(content, {
+ title,
+ });
+ }
+
+ async function notifyAccountToken() {
+ const url = await orm.call(
+ 'iap.account',
+ 'get_config_account_url',
+ []
+ );
+ const title = _t('IAP Account Token missing');
+ if (url) {
+ const content = renderToMarkup('partner_autocomplete.AccountTokenMissingNotification', {
+ account_url: url
});
- self.displayNotification({
+ notification.add(content, {
title,
- message: utils.Markup(content),
- className: 'o_partner_autocomplete_no_credits_notify',
});
- });
- },
-
- _notifyAccountToken: function () {
- var self = this;
- return this._rpc({
- model: 'iap.account',
- method: 'get_config_account_url',
- args: []
- }).then(function (url) {
- var title = _t('IAP Account Token missing');
- if (url){
- var content = Qweb.render('partner_autocomplete.account_token', {
- account_url: url
- });
- self.displayNotification({
- title,
- message: utils.Markup(content),
- className: 'o_partner_autocomplete_no_credits_notify',
- });
- }
- else {
- self.displayNotification({ title });
- }
- });
- },
-};
-
-return PartnerAutocompleteMixin;
-
-});
+ }
+ else {
+ notification.add(title);
+ }
+ }
+ return { autocomplete, getCreateData };
+}
diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js
index a9342c5d7fc..03be2716579 100644
--- a/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js
+++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js
@@ -1,341 +1,96 @@
-odoo.define('partner.autocomplete.fieldchar', function (require) {
-'use strict';
+/** @odoo-module **/
+/* global checkVATNumber */
-var basic_fields = require('web.basic_fields');
-var core = require('web.core');
-var field_registry = require('web.field_registry');
-var AutocompleteMixin = require('partner.autocomplete.Mixin');
+import { AutoComplete } from "@web/core/autocomplete/autocomplete";
+import { useChildRef } from "@web/core/utils/hooks";
+import { registry } from "@web/core/registry";
+import { _t } from "@web/core/l10n/translation";
+import { CharField } from "@web/views/fields/char/char_field";
+import { useInputField } from "@web/views/fields/input_field_hook";
-var QWeb = core.qweb;
+import { usePartnerAutocomplete } from "@partner_autocomplete/js/partner_autocomplete_core"
-var FieldChar = basic_fields.FieldChar;
+export class PartnerAutoCompleteCharField extends CharField {
+ setup() {
+ super.setup();
-/**
- * FieldChar extension to suggest existing companies when changing the company
- * name on a res.partner view (indeed, it is designed to change the "name",
- * "website" and "image" fields of records of this model).
- */
-var FieldAutocomplete = FieldChar.extend(AutocompleteMixin, {
- className: 'o_field_partner_autocomplete',
- debounceSuggestions: 400,
- resetOnAnyFieldChange: true,
+ this.partner_autocomplete = usePartnerAutocomplete();
- jsLibs: [
- '/partner_autocomplete/static/lib/jsvat.js'
- ],
+ this.inputRef = useChildRef();
+ useInputField({ getValue: () => this.props.value || "", parse: (v) => this.parse(v), ref: this.inputRef});
+ }
- events: _.extend({}, FieldChar.prototype.events, {
- 'keyup': '_onKeyup',
- 'mousedown .o_partner_autocomplete_suggestion': '_onMousedown',
- 'focusout': '_onFocusout',
- 'mouseenter .o_partner_autocomplete_suggestion': '_onHoverDropdown',
- 'click .o_partner_autocomplete_suggestion': '_onSuggestionClicked',
- }),
+ sanitizeVAT(request) {
+ return request ? request.replace(/[^A-Za-z0-9]/g, '') : '';
+ }
- /**
- * @constructor
- * Prepares the basic rendering of edit mode by setting the root to be a
- * div.dropdown.open.
- * @see FieldChar.init
- */
- init: function () {
- this._super.apply(this, arguments);
+ isVAT(request) {
+ // checkVATNumber is defined in library jsvat.
+ // It validates that the input has a valid VAT number format
+ return checkVATNumber(this.sanitizeVAT(request));
+ }
- // If the autocomplete is applied to vat field, only search valid vat number
- this.onlyVAT = this.name === 'vat';
-
- if (this.mode === 'edit') {
- this.tagName = 'div';
- this.className += ' dropdown open w-100';
+ validateSearchTerm(request) {
+ if (this.props.name == 'vat') {
+ return this.isVAT(request);
}
-
- if (this.debounceSuggestions > 0) {
- this._suggestCompanies = _.debounce(this._suggestCompanies.bind(this), this.debounceSuggestions);
+ else {
+ return request && request.length > 2;
}
- },
+ }
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Check if the autocomplete should be active
- * Active :
- * - only when creating new record
- * - on model res.partner and is_company=true
- * - on model res.company
- *
- * @returns {boolean}
- * @private
- */
- _isActive: function () {
- return this.model === 'res.company' ||
- (
- this.model === 'res.partner'
- && this.record.data.is_company
- && !(this.record.data && this.record.data.id)
- );
- },
-
- /**
- *
- * @private
- */
- _removeDropdown: function () {
- if (this.$dropdown) {
- this.$dropdown.remove();
- this.$dropdown = undefined;
- }
- },
-
- /**
- * Adds the element and prepares it. Note: the dropdown rendering
- * is handled outside of the rendering routine (but instead by reacting to
- * user input).
- *
- * @override
- * @private
- */
- _renderEdit: function () {
- this.$el.empty();
- // Prepare and add the input
- this._prepareInput().appendTo(this.$el);
- },
-
- /**
- * Selects the given company suggestions by notifying changes to the view
- * for the "name", "website" and "image" fields. This is of course intended
- * to work only with the "res.partner" form view.
- *
- * @private
- * @param {Object} company
- */
- _selectCompany: function (company) {
- var self = this;
- this._getCreateData(company).then(function (data) {
- if (data.logo) {
- var logoField = self.model === 'res.partner' ? 'image_1920' : 'logo';
- data.company[logoField] = data.logo;
- }
-
- // Some fields are unnecessary in res.company
- if (self.model === 'res.company') {
- var fields = 'comment,child_ids,bank_ids,additional_info'.split(',');
- fields.forEach(function (field) {
- delete data.company[field];
- });
- }
-
- self._setOne2ManyField('bank_ids', data.company.bank_ids);
- delete data.company.bank_ids;
-
- self.trigger_up('field_changed', {
- dataPointID: self.dataPointID,
- changes: data.company,
- onSuccess: function () {
- // update the input's value directly
- if (self.onlyVAT)
- self.$input.val(self._formatValue(company.vat));
- else
- self.$input.val(self._formatValue(company.name));
+ get sources() {
+ return [
+ {
+ options: async (request) => {
+ if (this.validateSearchTerm(request)) {
+ const suggestions = await this.partner_autocomplete.autocomplete(request, this.isVAT(request));
+ suggestions.forEach((suggestion) => {
+ suggestion.classList = "partner_autocomplete_dropdown_char";
+ });
+ return suggestions;
+ }
+ else {
+ return [];
+ }
},
+ optionTemplate: "partner_autocomplete.CharFieldDropdownOption",
+ placeholder: _t('Searching Autocomplete...'),
+ },
+ ];
+ }
+
+ async onSelect(option) {
+ const data = await this.partner_autocomplete.getCreateData(Object.getPrototypeOf(option));
+
+ if (data.logo) {
+ const logoField = this.props.record.resModel === 'res.partner' ? 'image_1920' : 'logo';
+ data.company[logoField] = data.logo;
+ }
+
+ // Some fields are unnecessary in res.company
+ if (this.props.record.resModel === 'res.company') {
+ const fields = ['comment', 'child_ids', 'additional_info'];
+ fields.forEach((field) => {
+ delete data.company[field];
});
+ }
+
+ // Format the many2one fields
+ const many2oneFields = ['country_id', 'state_id'];
+ many2oneFields.forEach((field) => {
+ if (data.company[field]) {
+ data.company[field] = [data.company[field].id, data.company[field].display_name];
+ }
});
- this._removeDropdown();
- },
+ this.props.record.update(data.company);
+ }
+}
- _setOne2ManyField: function (field, list) {
- var self = this;
- var viewType = this.record.viewType;
- if (list && this.record.fieldsInfo[viewType] && this.record.fieldsInfo[viewType][field]) {
- list.forEach(function (item) {
- var changes = {};
- changes[field] = {
- operation: 'CREATE',
- data: item,
- };
+PartnerAutoCompleteCharField.template = "partner_autocomplete.PartnerAutoCompleteCharField";
+PartnerAutoCompleteCharField.components = {
+ ...CharField.components,
+ AutoComplete,
+};
- self.trigger_up('field_changed', {
- dataPointID: self.dataPointID,
- changes: changes,
- });
- });
- }
- },
-
- /**
- * Shows the dropdown with the suggestions. If one is
- * already opened, it removes the old one before rerendering the dropdown.
- *
- * @private
- */
- _showDropdown: function () {
- this._removeDropdown();
- if (this.suggestions.length > 0) {
- this.$dropdown = $(QWeb.render('partner_autocomplete.dropdown', {
- suggestions: this.suggestions,
- }));
- this.$dropdown.appendTo(this.$el);
- // We need to make sure that the element containing the dropdown menu doesn't have the
- // style attribute overflow: hidden, otherwise the dropdown menu will be hidden by it
- this.$el.removeClass('o_text_overflow');
- this.$input.addClass('o_text_overflow');
- }
- },
-
- /**
- * Shows suggestions according to the given value.
- * Note: this method is debounced (@see init).
- *
- * @private
- * @param {string} value - searched term
- */
- _suggestCompanies: function (value) {
- var self = this;
- if (this._validateSearchTerm(value, this.onlyVAT) && this._isOnline()) {
- return this._autocomplete(value).then(function (suggestions) {
- if (suggestions && suggestions.length) {
- self.suggestions = suggestions;
- self._showDropdown();
- } else {
- self._removeDropdown();
- }
- });
- } else {
- this._removeDropdown();
- }
- },
-
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Called on focusout -> removes the suggestions dropdown.
- *
- * @private
- */
- _onFocusout: function () {
- this._removeDropdown();
- },
-
- /**
- * Called when hovering a suggestion in the dropdown -> sets it as active.
- *
- * @private
- * @param {Event} e
- */
- _onHoverDropdown: function (e) {
- this.$dropdown.find('.active').removeClass('active');
- $(e.currentTarget).parent().addClass('active');
- },
-
- /**
- * @override of FieldChar (called when the user is typing text)
- * Checks the value and shows suggestions according to
- * this value.
- *
- * @private
- */
- _onInput: function () {
- this._super.apply(this, arguments);
- if (this._isActive()) {
- this._suggestCompanies(this.$input.val());
- }
- },
-
- /**
- * @override of FieldChar
- * Changes the "up" and "down" key behavior when the dropdown is opened (to
- * navigate through dropdown suggestions).
- * Triggered by keydown to execute the navigation multiple times when the
- * user keeps the "down" or "up" pressed.
- *
- * @private
- * @param {Event} e
- */
- _onKeydown: function (e) {
- switch (e.which) {
- case $.ui.keyCode.UP:
- case $.ui.keyCode.DOWN:
- if (!this.$dropdown) {
- break;
- }
- e.preventDefault();
- var $suggestions = this.$dropdown.children();
- var $active = $suggestions.filter('.active');
- var $to;
- if ($active.length) {
- $to = e.which === $.ui.keyCode.DOWN ?
- $active.next() :
- $active.prev();
- } else {
- $to = $suggestions.first();
- }
- if ($to.length) {
- $active.removeClass('active');
- $to.addClass('active');
- }
- return;
- }
- this._super.apply(this, arguments);
- },
-
- /**
- * Called on keyup events to:
- * -> remove the suggestions dropdown when hitting the "escape" key
- * -> select the highlighted suggestion when hitting the "enter" key
- *
- * @private
- * @param {Event} e
- */
- _onKeyup: function (e) {
- switch (e.which) {
- case $.ui.keyCode.ESCAPE:
- e.preventDefault();
- this._removeDropdown();
- break;
- case $.ui.keyCode.ENTER:
- if (!this.$dropdown) {
- break;
- }
- e.preventDefault();
- var $active = this.$dropdown.find('.o_partner_autocomplete_suggestion.active');
- if (!$active.length) {
- return;
- }
- this._selectCompany(this.suggestions[$active.data('index')]);
- break;
- }
- },
-
- /**
- * Called on mousedown event on a suggestion -> prevent default
- * action so that the element does not lose the focus.
- *
- * @private
- * @param {Event} e
- */
- _onMousedown: function (e) {
- e.preventDefault(); // prevent losing focus on suggestion click
- },
-
- /**
- * Called when a dropdown suggestion is clicked -> trigger_up changes for
- * some fields in the view (not only this one) with the associated
- * data (@see _selectCompany).
- *
- * @private
- * @param {Event} e
- */
- _onSuggestionClicked: function (e) {
- e.preventDefault();
- this._selectCompany(this.suggestions[$(e.currentTarget).data('index')]);
- },
-});
-
-field_registry.add('field_partner_autocomplete', FieldAutocomplete);
-
-return FieldAutocomplete;
-});
+registry.category("fields").add("field_partner_autocomplete", PartnerAutoCompleteCharField);
diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js
index 2fbeefced4e..1f503860f86 100644
--- a/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js
+++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js
@@ -1,141 +1,72 @@
-odoo.define('partner.autocomplete.many2one', function (require) {
-'use strict';
+/** @odoo-module **/
-var FieldMany2One = require('web.relational_fields').FieldMany2One;
-var core = require('web.core');
-var AutocompleteMixin = require('partner.autocomplete.Mixin');
-var field_registry = require('web.field_registry');
+import { Many2XAutocomplete } from '@web/views/fields/relational_utils';
+import { Many2OneField } from '@web/views/fields/many2one/many2one_field';
+import { _t } from "@web/core/l10n/translation";
+import { registry } from "@web/core/registry";
-var _t = core._t;
+import { usePartnerAutocomplete } from "@partner_autocomplete/js/partner_autocomplete_core"
-var PartnerField = FieldMany2One.extend(AutocompleteMixin, {
- jsLibs: [
- '/partner_autocomplete/static/lib/jsvat.js'
- ],
+export class PartnerMany2XAutocomplete extends Many2XAutocomplete {
+ setup() {
+ super.setup();
- /**
- * @override
- */
- init: function () {
- this._super.apply(this, arguments);
- this._addAutocompleteSource(this._searchSuggestions, {
- placeholder: _t('Searching Autocomplete...'),
- order: 20,
- validation: this._validateSearchTerm,
- });
+ this.partner_autocomplete = usePartnerAutocomplete();
+ }
- this.additionalContext['show_vat'] = true;
- },
+ validateSearchTerm(request) {
+ return request && request.length > 2;
+ }
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
+ get sources() {
+ return super.sources.concat(
+ {
+ options: async (request) => {
+ if (this.validateSearchTerm(request)) {
+ const suggestions = await this.partner_autocomplete.autocomplete(request);
+ suggestions.forEach((suggestion) => {
+ suggestion.classList = "partner_autocomplete_dropdown_many2one";
+ });
+ return suggestions;
+ }
+ else {
+ return [];
+ }
+ },
+ optionTemplate: "partner_autocomplete.Many2oneDropdownOption",
+ placeholder: _t('Searching Autocomplete...'),
+ },
+ );
+ }
- /**
- * Action : create popup form with pre-filled values from Autocomplete
- *
- * @param {Object} company
- * @returns {Promise}
- * @private
- */
- _createPartner: function (company) {
- var self = this;
- self.$('input').val('');
-
- return self._getCreateData(company).then(function (data){
- var context = {
+ async onSelect(option) {
+ if (option.partner_gid) { // Checks that it is a partner autocomplete option
+ const data = await this.partner_autocomplete.getCreateData(Object.getPrototypeOf(option));
+ let context = {
'default_is_company': true
};
- _.each(data.company, function (val, key) {
+
+ for (const [key, val] of Object.entries(data.company)) {
context['default_' + key] = val && val.id ? val.id : val;
- });
+ }
- // if(data.company.street_name && !data.company.street_number) context.default_street_number = '';
- if (data.logo) context.default_image_1920 = data.logo;
-
- return self._searchCreatePopup("form", false, context);
- });
- },
-
- /**
- * Modify autocomplete results rendering
- * Add logo in the autocomplete results if logo is provided
- *
- * @private
- */
- _modifyAutompleteRendering: function (){
- var api = this.$input.data('ui-autocomplete');
- // FIXME: bugfix to prevent traceback in mobile apps due to override
- // of Many2one widget with native implementation.
- if (!api) {
- return;
+ if (data.logo) {
+ context.default_image_1920 = data.logo;
+ }
+ return this.openMany2X({ context });
}
- api._renderItem = function(ul, item){
- ul.addClass('o_partner_autocomplete_dropdown');
- var $a = $('')["html"](item.label);
- if (item.logo){
- var $img = $('').attr('src', item.logo);
- $a.append($img);
- }
+ else {
+ return super.onSelect(option);
+ }
+ }
- return $("