[REF] web(site): migrate jQueryUI urlautocomplete to owl component
*: web, website, website_form_project Remove the jQuery UI Widget `urlautocomplete` by using made some refactor to allow usage of the owl component. task-3439226 Part-of: odoo/odoo#139209
This commit is contained in:
@@ -64,7 +64,7 @@ export class AutoComplete extends Component {
|
||||
|
||||
// position and size
|
||||
if (this.props.dropdown) {
|
||||
usePosition("sourcesList", () => this.inputRef.el, {
|
||||
usePosition("sourcesList", () => this.targetDropdown, {
|
||||
position: "bottom-start",
|
||||
});
|
||||
} else {
|
||||
@@ -72,6 +72,10 @@ export class AutoComplete extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
get targetDropdown() {
|
||||
return this.inputRef.el;
|
||||
}
|
||||
|
||||
get isOpened() {
|
||||
return this.state.open;
|
||||
}
|
||||
@@ -263,6 +267,7 @@ export class AutoComplete extends Component {
|
||||
this.inputRef.el.setSelectionRange(0, this.inputRef.el.value.length);
|
||||
this.props.onFocus(ev);
|
||||
}
|
||||
|
||||
get autoCompleteRootClass() {
|
||||
let classList = "";
|
||||
if (this.props.class) {
|
||||
@@ -274,6 +279,16 @@ export class AutoComplete extends Component {
|
||||
return classList;
|
||||
}
|
||||
|
||||
get ulDropdownClass() {
|
||||
let classList = "";
|
||||
if (this.props.dropdown) {
|
||||
classList += " dropdown-menu ui-autocomplete";
|
||||
} else {
|
||||
classList += " list-group";
|
||||
}
|
||||
return classList;
|
||||
}
|
||||
|
||||
async onInputKeydown(ev) {
|
||||
const hotkey = getActiveHotkey(ev);
|
||||
const isSelectKey = hotkey === "enter" || hotkey === "tab";
|
||||
|
||||
@@ -20,10 +20,7 @@
|
||||
/>
|
||||
<t t-if="displayOptions">
|
||||
<ul class="o-autocomplete--dropdown-menu ui-widget show"
|
||||
t-att-class="{
|
||||
'dropdown-menu ui-autocomplete': props.dropdown,
|
||||
'list-group': !props.dropdown
|
||||
}"
|
||||
t-att-class="ulDropdownClass"
|
||||
t-on-mousedown.prevent="" t-ref="sourcesList">
|
||||
<t t-foreach="sources" t-as="source" t-key="source.id">
|
||||
<t t-if="source.isLoading">
|
||||
|
||||
@@ -131,6 +131,8 @@
|
||||
'website/static/src/scss/website.ui.scss',
|
||||
'website/static/src/libs/zoomodoo/zoomodoo.js',
|
||||
'website/static/src/js/utils.js',
|
||||
'web/static/src/core/autocomplete/*',
|
||||
'website/static/src/components/autocomplete_with_pages/*',
|
||||
'website/static/src/js/tours/tour_utils.js',
|
||||
'website/static/src/js/content/website_root.js',
|
||||
'website/static/src/js/widgets/dialog.js',
|
||||
@@ -193,6 +195,8 @@
|
||||
'website/static/src/components/views/*',
|
||||
'website/static/src/services/website_service.js',
|
||||
'website/static/src/js/utils.js',
|
||||
'web/static/src/core/autocomplete/*',
|
||||
'website/static/src/components/autocomplete_with_pages/*',
|
||||
'website/static/src/xml/website.xml',
|
||||
|
||||
# Don't include dark mode files in light mode
|
||||
|
||||
@@ -366,10 +366,11 @@ class Website(Home):
|
||||
for name, url, mod in current_website.get_suggested_controllers():
|
||||
if needle.lower() in name.lower() or needle.lower() in url.lower():
|
||||
module_sudo = mod and request.env.ref('base.module_%s' % mod, False).sudo()
|
||||
icon = mod and "<img src='%s' width='24px' height='24px' class='mr-2 rounded' /> " % (module_sudo and module_sudo.icon or mod) or ''
|
||||
icon = mod and '%s' % (module_sudo and module_sudo.icon or mod) or ''
|
||||
suggested_controllers.append({
|
||||
'value': url,
|
||||
'label': '%s%s (%s)' % (icon, url, name),
|
||||
'icon': icon,
|
||||
'label': '%s (%s)' % (url, name),
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
|
||||
import { useEffect } from "@odoo/owl";
|
||||
|
||||
export class AutoCompleteWithPages extends AutoComplete {
|
||||
static template = "website.AutoCompleteWithPages";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useEffect(
|
||||
(input, inputRef) => {
|
||||
if (inputRef) {
|
||||
inputRef.value = input.value;
|
||||
}
|
||||
const targetBlur = this.onInputBlur.bind(this);
|
||||
const targetClick = this._syncInputClick.bind(this);
|
||||
const targetChange = this.onInputChange.bind(this);
|
||||
const targetInput = this._syncInputValue.bind(this);
|
||||
const targetKeydown = this.onInputKeydown.bind(this);
|
||||
const targetFocus = this.onInputFocus.bind(this);
|
||||
input.addEventListener("blur", targetBlur);
|
||||
input.addEventListener("click", targetClick);
|
||||
input.addEventListener("change", targetChange);
|
||||
input.addEventListener("input", targetInput);
|
||||
input.addEventListener("keydown", targetKeydown);
|
||||
input.addEventListener("focus", targetFocus);
|
||||
return () => {
|
||||
input.removeEventListener("blur", targetBlur);
|
||||
input.removeEventListener("click", targetClick);
|
||||
input.removeEventListener("change", targetChange);
|
||||
input.removeEventListener("input", targetInput);
|
||||
input.removeEventListener("keydown", targetKeydown);
|
||||
input.removeEventListener("focus", targetFocus);
|
||||
};
|
||||
},
|
||||
() => [this.targetDropdown, this.inputRef.el]
|
||||
);
|
||||
}
|
||||
|
||||
get dropdownOptions() {
|
||||
if (this.props.dropdownOptions) {
|
||||
return {
|
||||
...super.dropdownOptions,
|
||||
...this.props.dropdownOptions,
|
||||
};
|
||||
}
|
||||
return super.dropdownOptions;
|
||||
}
|
||||
|
||||
get ulDropdownClass() {
|
||||
let classList = super.ulDropdownClass;
|
||||
if (this.props.dropdownClass) {
|
||||
classList += ` ${this.props.dropdownClass}`;
|
||||
}
|
||||
return classList;
|
||||
}
|
||||
|
||||
get targetDropdown() {
|
||||
return this.props.targetDropdown;
|
||||
}
|
||||
|
||||
_syncInputClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this.onInputClick(ev);
|
||||
}
|
||||
|
||||
async _syncInputValue() {
|
||||
if (this.inputRef.el) {
|
||||
this.inputRef.el.value = this.targetDropdown.value;
|
||||
this.onInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param indices
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
_isCategory(indices) {
|
||||
const [sourceIndex, optionIndex] = indices;
|
||||
return !!this.sources[sourceIndex]?.options[optionIndex]?.separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onOptionMouseEnter(indices) {
|
||||
if (!this._isCategory(indices)) {
|
||||
return super.onOptionMouseEnter(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onOptionMouseLeave(indices) {
|
||||
if (!this._isCategory(indices)) {
|
||||
return super.onOptionMouseLeave(...arguments);
|
||||
}
|
||||
}
|
||||
isActiveSourceOption(indices) {
|
||||
if (!this._isCategory(indices)) {
|
||||
return super.isActiveSourceOption(...arguments);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
selectOption(indices) {
|
||||
if (!this._isCategory(indices)) {
|
||||
const [sourceIndex, optionIndex] = indices;
|
||||
const { value } = Object.getPrototypeOf(this.sources[sourceIndex].options[optionIndex]);
|
||||
this.targetDropdown.value = value;
|
||||
return super.selectOption(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
navigate(direction) {
|
||||
super.navigate(direction);
|
||||
if (direction !== 0 && this.state.activeSourceOption) {
|
||||
let [sourceIndex, optionIndex] = this.state.activeSourceOption;
|
||||
const option = this.sources[sourceIndex]?.options[optionIndex];
|
||||
if (option) {
|
||||
if (!!option.separator) {
|
||||
this.navigate(direction);
|
||||
}
|
||||
const suggestion = Object.getPrototypeOf(option);
|
||||
if (suggestion && suggestion.value) {
|
||||
this.inputRef.el.value = suggestion.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
close() {
|
||||
this.props.onInput({
|
||||
inputValue: this.inputRef.el.value,
|
||||
});
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
AutoCompleteWithPages.props = {
|
||||
...AutoComplete.props,
|
||||
targetDropdown: { type: HTMLElement },
|
||||
dropdownClass: { type: String, optional: true },
|
||||
dropdownOptions: { type: Object, optional: true },
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="website.AutoCompleteWithPages" t-inherit="web.AutoComplete">
|
||||
<xpath expr="//input[@t-ref='input']" position="attributes">
|
||||
<attribute name="class" add="d-none" separator=" " />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,95 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { AutoCompleteWithPages } from "@website/components/autocomplete_with_pages/autocomplete_with_pages";
|
||||
|
||||
export class UrlAutoComplete extends Component {
|
||||
static template = "website.UrlAutoComplete";
|
||||
static components = { AutoCompleteWithPages };
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.rpc = useService("rpc");
|
||||
}
|
||||
|
||||
_mapItemToSuggestion(item) {
|
||||
return {
|
||||
...item,
|
||||
classList: item.separator ? "ui-autocomplete-category" : "ui-autocomplete-item",
|
||||
};
|
||||
}
|
||||
|
||||
get dropdownClass() {
|
||||
const classList = [];
|
||||
for (const key in this.props.options?.classes) {
|
||||
classList.push(key, this.props.options.classes[key]);
|
||||
}
|
||||
return classList.join(" ")
|
||||
}
|
||||
|
||||
get dropdownOptions() {
|
||||
const options = {};
|
||||
if (this.props.options?.position) {
|
||||
options.position = this.props.options?.position;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
get sources() {
|
||||
return [
|
||||
{
|
||||
optionTemplate: "website.AutoCompleteWithPagesItem",
|
||||
options: async (term) => {
|
||||
if (term[0] === "#") {
|
||||
const anchors = await this.props.loadAnchors(
|
||||
term,
|
||||
this.props.options && this.props.options.body
|
||||
);
|
||||
return anchors.map((anchor) =>
|
||||
this._mapItemToSuggestion({ label: anchor, value: anchor })
|
||||
);
|
||||
} else if (term.startsWith("http") || term.length === 0) {
|
||||
// avoid useless call to /website/get_suggested_links
|
||||
return [];
|
||||
}
|
||||
if (this.props.options.isDestroyed?.()) {
|
||||
return [];
|
||||
}
|
||||
const res = await this.rpc("/website/get_suggested_links", {
|
||||
needle: term,
|
||||
limit: 15,
|
||||
});
|
||||
let choices = res.matching_pages;
|
||||
res.others.forEach((other) => {
|
||||
if (other.values.length) {
|
||||
choices = choices.concat(
|
||||
[{ separator: other.title, label: other.title }],
|
||||
other.values
|
||||
);
|
||||
}
|
||||
});
|
||||
return choices.map(this._mapItemToSuggestion);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
onSelect(selectedSubjection, { input }) {
|
||||
const { value } = Object.getPrototypeOf(selectedSubjection);
|
||||
input.value = value;
|
||||
this.props.targetDropdown.value = value;
|
||||
this.props.options.urlChosen?.();
|
||||
}
|
||||
|
||||
onInput({ inputValue }) {
|
||||
this.props.targetDropdown.value = inputValue;
|
||||
this.props.options.urlChosen?.();
|
||||
}
|
||||
}
|
||||
|
||||
UrlAutoComplete.props = {
|
||||
options: { type: Object },
|
||||
loadAnchors: { type: Function },
|
||||
targetDropdown: { type: HTMLElement },
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.o-autocomplete {
|
||||
.ui-autocomplete-category > .dropdown-item:hover {
|
||||
background-color: initial;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="website.UrlAutoComplete">
|
||||
<AutoCompleteWithPages
|
||||
onInput.bind="onInput"
|
||||
onSelect.bind="onSelect"
|
||||
dropdownClass="dropdownClass"
|
||||
dropdownOptions="dropdownOptions"
|
||||
sources="sources"
|
||||
targetDropdown="props.targetDropdown"/>
|
||||
</t>
|
||||
|
||||
<t t-name="website.AutoCompleteWithPagesItem">
|
||||
<div t-att-class="{
|
||||
'fw-bold text-capitalize p-2': option.separator,
|
||||
}">
|
||||
<t t-if="option.icon and option.icon.length">
|
||||
<img t-att-src="option.icon" width="24px" height="24px" class="me-2 rounded"/>
|
||||
</t>
|
||||
<t t-out="option.label"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -33,7 +33,6 @@ const useControlledInput = (initialValue, validate) => {
|
||||
|
||||
export class MenuDialog extends Component {
|
||||
setup() {
|
||||
this.rpc = useService('rpc');
|
||||
this.website = useService('website');
|
||||
this.title = _t("Add a menu item");
|
||||
useAutofocus();
|
||||
@@ -42,20 +41,23 @@ export class MenuDialog extends Component {
|
||||
this.url = useControlledInput(this.props.url, value => !!value);
|
||||
this.urlInputRef = useRef('url-input');
|
||||
|
||||
useEffect(() => {
|
||||
const $input = $(this.urlInputRef.el);
|
||||
useEffect((input) => {
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
body: this.website.pageDocument.body,
|
||||
position: "bottom-fit",
|
||||
classes: {
|
||||
'ui-autocomplete': 'o_edit_menu_autocomplete'
|
||||
},
|
||||
urlChosen: () => {
|
||||
this.url.input.value = this.urlInputRef.el.value;
|
||||
this.url.input.value = input.value;
|
||||
},
|
||||
};
|
||||
wUtils.autocompleteWithPages(this.rpc.bind(this), $input, options);
|
||||
return () => $input.urlcomplete('destroy');
|
||||
}, () => []);
|
||||
const unmountAutocompleteWithPages = wUtils.autocompleteWithPages(input, options);
|
||||
return () => unmountAutocompleteWithPages();
|
||||
}, () => [this.urlInputRef.el]);
|
||||
}
|
||||
|
||||
onClickOk() {
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
border-bottom: $o-we-sidebar-content-field-border-width solid lighten($o-we-sidebar-content-field-dropdown-border-color, 15%);
|
||||
border-radius: $o-we-sidebar-content-field-border-radius;
|
||||
background-color: $o-we-sidebar-content-field-clickable-bg;
|
||||
color: $o-we-sidebar-content-field-clickable-color;
|
||||
> .dropdown-item,
|
||||
> .dropdown-item:focus,
|
||||
> .dropdown-item.ui-state-active {
|
||||
color: $o-we-sidebar-content-field-clickable-color;
|
||||
}
|
||||
&.ui-menu-item {
|
||||
> div {
|
||||
white-space: nowrap;
|
||||
@@ -29,6 +33,9 @@
|
||||
}
|
||||
&.ui-autocomplete-category {
|
||||
background-color: $o-we-bg-lighter;
|
||||
a:hover > {
|
||||
color: $o-we-sidebar-content-field-clickable-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,3 +165,12 @@
|
||||
transition: transform ease 400ms;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-widget.ui-autocomplete.o_website_ui_autocomplete .ui-menu-item {
|
||||
> a:hover {
|
||||
color: $o-we-sidebar-content-field-clickable-color;
|
||||
}
|
||||
&.ui-autocomplete-category > a:hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,28 @@
|
||||
|
||||
import { LinkDialog } from "@web_editor/js/wysiwyg/widgets/link_dialog";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import wUtils from "@website/js/utils";
|
||||
import { useEffect } from '@odoo/owl';
|
||||
|
||||
patch(LinkDialog.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.rpc = useService("rpc");
|
||||
},
|
||||
/**
|
||||
* Allows the URL input to propose existing website pages.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async start() {
|
||||
const options = {
|
||||
body: this.$link && this.$link[0].ownerDocument.body,
|
||||
urlChosen: () => this.__onURLInput(),
|
||||
};
|
||||
const result = await super.start(...arguments);
|
||||
wUtils.autocompleteWithPages(this.rpc.bind(this), this.$el.find('input[name="url"]'), options);
|
||||
return result;
|
||||
},
|
||||
setup() {
|
||||
super.setup();
|
||||
useEffect(($link, container) => {
|
||||
const input = container?.querySelector(`input[name="url"]`);
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
body: $link && $link[0].ownerDocument.body,
|
||||
urlChosen: () => this.__onURLInput(),
|
||||
};
|
||||
const unmountAutocompleteWithPages = wUtils.autocompleteWithPages(input, options);
|
||||
return () => unmountAutocompleteWithPages();
|
||||
}, () => [this.$link, this.linkComponentWrapperRef.el]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -104,16 +104,23 @@ const UrlPickerUserValueWidget = InputUserValueWidget.extend({
|
||||
this.el.classList.add('o_we_large');
|
||||
this.inputEl.classList.add('text-start');
|
||||
const options = {
|
||||
position: {
|
||||
collision: 'flip flipfit',
|
||||
},
|
||||
classes: {
|
||||
"ui-autocomplete": 'o_website_ui_autocomplete'
|
||||
},
|
||||
body: this.getParent().$target[0].ownerDocument.body,
|
||||
urlChosen: this._onWebsiteURLChosen.bind(this),
|
||||
};
|
||||
wUtils.autocompleteWithPages(this.rpc.bind(this), $(this.inputEl), options);
|
||||
this.unmountAutocompleteWithPages = wUtils.autocompleteWithPages(this.inputEl, options);
|
||||
},
|
||||
|
||||
open() {
|
||||
this._super(...arguments);
|
||||
document.querySelector(".o_website_ui_autocomplete")?.classList?.remove("d-none");
|
||||
},
|
||||
|
||||
close() {
|
||||
this._super(...arguments);
|
||||
document.querySelector(".o_website_ui_autocomplete")?.classList?.add("d-none");
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -140,6 +147,11 @@ const UrlPickerUserValueWidget = InputUserValueWidget.extend({
|
||||
window.open(this._value, '_blank');
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
this.unmountAutocompleteWithPages?.();
|
||||
this.unmountAutocompleteWithPages = null;
|
||||
this._super(...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
const FontFamilyPickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
|
||||
@@ -2,19 +2,14 @@
|
||||
|
||||
import { LinkTools } from '@web_editor/js/wysiwyg/widgets/link_tools';
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import {status} from '@odoo/owl';
|
||||
import { status, useEffect } from '@odoo/owl';
|
||||
import wUtils from "@website/js/utils";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
|
||||
const LINK_DEBOUNCE = 1000;
|
||||
|
||||
patch(LinkTools.prototype, {
|
||||
setup() {
|
||||
this.rpc = useService('rpc');
|
||||
return super.setup(...arguments);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
@@ -30,22 +25,29 @@ patch(LinkTools.prototype, {
|
||||
*/
|
||||
async start() {
|
||||
var def = await super.start(...arguments);
|
||||
const options = {
|
||||
position: {
|
||||
collision: 'flip flipfit',
|
||||
},
|
||||
classes: {
|
||||
"ui-autocomplete": 'o_website_ui_autocomplete'
|
||||
},
|
||||
body: this.$editable[0].ownerDocument.body,
|
||||
urlChosen: this._onAutocompleteClose.bind(this),
|
||||
isDestroyed: () => status(this) === 'destroyed',
|
||||
};
|
||||
wUtils.autocompleteWithPages(this.rpc.bind(this), this.$el.find('input[name="url"]'), options);
|
||||
this._adaptPageAnchor();
|
||||
return def;
|
||||
},
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useEffect((container) => {
|
||||
const input = container?.querySelector(`input[name="url"]`);
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
classes: {
|
||||
"ui-autocomplete": 'o_website_ui_autocomplete'
|
||||
},
|
||||
body: this.$editable[0].ownerDocument.body,
|
||||
urlChosen: this._onAutocompleteClose.bind(this),
|
||||
isDestroyed: () => status(this) === 'destroyed',
|
||||
};
|
||||
const unmountAutocompleteWithPages = wUtils.autocompleteWithPages(input, options);
|
||||
return () => unmountAutocompleteWithPages();
|
||||
}, () => [this.linkComponentWrapperRef.el]);
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import { intersection } from "@web/core/utils/arrays";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
|
||||
import { App, Component } from "@odoo/owl";
|
||||
import { templates } from "@web/core/assets";
|
||||
import { UrlAutoComplete } from "@website/components/autocomplete_with_pages/url_autocomplete";
|
||||
|
||||
/**
|
||||
* Allows to load anchors from a page.
|
||||
@@ -45,77 +47,30 @@ function loadAnchors(url, body) {
|
||||
/**
|
||||
* Allows the given input to propose existing website URLs.
|
||||
*
|
||||
* @param {Function} rpc
|
||||
* @param {jQuery} $input
|
||||
* @param {HTMLInputElement} input
|
||||
*/
|
||||
function autocompleteWithPages(rpc, $input, options) {
|
||||
$.widget("website.urlcomplete", $.ui.autocomplete, {
|
||||
options: options || {},
|
||||
_create: function () {
|
||||
this._super();
|
||||
this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)");
|
||||
},
|
||||
_renderMenu: function (ul, items) {
|
||||
const self = this;
|
||||
items.forEach(item => {
|
||||
if (item.separator) {
|
||||
self._renderSeparator(ul, item);
|
||||
}
|
||||
else {
|
||||
self._renderItem(ul, item);
|
||||
}
|
||||
});
|
||||
},
|
||||
_renderSeparator: function (ul, item) {
|
||||
return $("<li class='ui-autocomplete-category fw-bold text-capitalize p-2'>")
|
||||
.append(`<div>${item.separator}</div>`)
|
||||
.appendTo(ul);
|
||||
},
|
||||
_renderItem: function (ul, item) {
|
||||
return $("<li>")
|
||||
.data('ui-autocomplete-item', item)
|
||||
.append(`<div>${item.label}</div>`)
|
||||
.appendTo(ul);
|
||||
},
|
||||
});
|
||||
$input.urlcomplete({
|
||||
source: function (request, response) {
|
||||
if (request.term[0] === '#') {
|
||||
loadAnchors(request.term, options && options.body).then(function (anchors) {
|
||||
response(anchors);
|
||||
});
|
||||
} else if (request.term.startsWith('http') || request.term.length === 0) {
|
||||
// avoid useless call to /website/get_suggested_links
|
||||
response();
|
||||
} else {
|
||||
if (options.isDestroyed?.()) {
|
||||
return;
|
||||
}
|
||||
return rpc('/website/get_suggested_links', {
|
||||
needle: request.term,
|
||||
limit: 15,
|
||||
}).then(function (res) {
|
||||
let choices = res.matching_pages;
|
||||
res.others.forEach(other => {
|
||||
if (other.values.length) {
|
||||
choices = choices.concat(
|
||||
[{separator: other.title}],
|
||||
other.values,
|
||||
);
|
||||
}
|
||||
});
|
||||
response(choices);
|
||||
});
|
||||
}
|
||||
},
|
||||
select: function (ev, ui) {
|
||||
// choose url in dropdown with arrow change ev.target.value without trigger_up
|
||||
// so cannot check here if value has been updated
|
||||
ev.target.value = ui.item.value;
|
||||
options.urlChosen?.();
|
||||
ev.preventDefault();
|
||||
function autocompleteWithPages(input, options= {}) {
|
||||
const owlApp = new App(UrlAutoComplete, {
|
||||
env: Component.env,
|
||||
dev: Component.env.debug,
|
||||
templates,
|
||||
props: {
|
||||
options,
|
||||
loadAnchors,
|
||||
targetDropdown: input,
|
||||
},
|
||||
translatableAttributes: ["data-tooltip"],
|
||||
translateFn: _t,
|
||||
});
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("ui-widget","ui-autocomplete", "ui-widget-content");
|
||||
document.body.appendChild(container);
|
||||
owlApp.mount(container)
|
||||
return () => {
|
||||
owlApp.destroy();
|
||||
container.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,8 @@ Generate tasks in Project app from a form published on your website. This module
|
||||
# utils.js in its assets, otherwise, there will be an unmet
|
||||
# dependency.
|
||||
'website/static/src/js/utils.js',
|
||||
'web/static/src/core/autocomplete/*',
|
||||
'website/static/src/components/autocomplete_with_pages/*',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
Reference in New Issue
Block a user