diff --git a/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js b/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js
index 5b0917d912e..ab7cc7fd958 100644
--- a/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js
+++ b/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js
@@ -10,20 +10,54 @@ import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog
import { Component, useState } from "@odoo/owl";
-class FilterAutoComplete extends Component {
+class CalendarFilterTooltip extends Component {}
+CalendarFilterTooltip.template = "web.CalendarFilterPanel.tooltip";
+
+let nextId = 1;
+
+export class CalendarFilterPanel extends Component {
setup() {
- this.orm = useService("orm");
- this.addDialog = useOwnedDialogs();
this.state = useState({
- value: "",
+ collapsed: {},
+ fieldRev: 1,
});
- this.sources = [{ placeholder: _t("Loading..."), options: this.loadSource.bind(this) }];
+ this.addDialog = useOwnedDialogs();
+ this.orm = useService("orm");
+ this.popover = usePopover();
+ this.removePopover = null;
}
- async loadSource(request) {
- const records = await this.orm.call(this.props.resModel, "name_search", [], {
+
+ getAutoCompleteProps(section) {
+ return {
+ autoSelect: true,
+ resetOnSelect: true,
+ placeholder: `+ ${_t("Add")} ${section.label}`,
+ sources: [
+ {
+ placeholder: _t("Loading..."),
+ options: (request) => this.loadSource(section, request),
+ },
+ ],
+ onSelect: (option, params = {}) => {
+ if (option.action) {
+ option.action(params);
+ return;
+ }
+ this.props.model.createFilter(section.fieldName, option.value);
+ },
+ value: "",
+ };
+ }
+
+ async loadSource(section, request) {
+ const resModel = this.props.model.fields[section.fieldName].relation;
+ const domain = [
+ ["id", "not in", section.filters.filter((f) => f.type !== "all").map((f) => f.value)],
+ ];
+ const records = await this.orm.call(resModel, "name_search", [], {
name: request,
operator: "ilike",
- args: this.props.domain,
+ args: domain,
limit: 8,
context: {},
});
@@ -36,91 +70,48 @@ class FilterAutoComplete extends Component {
if (records.length > 7) {
options.push({
label: _t("Search More..."),
- action: this.onSearchMore.bind(this, request),
+ action: () => this.onSearchMore(section, resModel, domain, request),
+ });
+ }
+
+ if (records.length === 0) {
+ options.push({
+ label: _t("No records"),
+ classList: "o_m2o_no_result",
+ unselectable: true,
});
}
return options;
}
- async onSearchMore(request) {
- let dynamicFilters = [];
+
+ async onSearchMore(section, resModel, domain, request) {
+ const dynamicFilters = [];
if (request.length) {
- const nameGets = await this.orm.call(this.props.resModel, "name_search", [], {
+ const nameGets = await this.orm.call(resModel, "name_search", [], {
name: request,
- args: this.props.domain,
+ args: domain,
operator: "ilike",
context: {},
});
-
- dynamicFilters = [
- {
- description: sprintf(_t("Quick search: %s"), request),
- domain: [["id", "in", nameGets.map((nameGet) => nameGet[0])]],
- },
- ];
+ dynamicFilters.push({
+ description: sprintf(_t("Quick search: %s"), request),
+ domain: [["id", "in", nameGets.map((nameGet) => nameGet[0])]],
+ });
}
- const title = sprintf(this.env._t("Search: %s"), this.props.searchMoreTitle);
+ const title = sprintf(_t("Search: %s"), section.label);
this.addDialog(SelectCreateDialog, {
- title: title || _t("Select records"),
+ title,
noCreate: true,
multiSelect: false,
- resModel: this.props.resModel,
+ resModel,
context: {},
- domain: this.props.domain,
- onSelected: (...args) => {
- console.log(...args);
- },
+ domain,
+ onSelected: ([resId]) => this.props.model.createFilter(section.fieldName, resId),
dynamicFilters,
});
}
- onInput({ inputValue }) {
- this.state.value = inputValue;
- }
- onSelect(option, params = {}) {
- this.props.onSelect(option.value);
- this.state.value = "";
- }
-}
-FilterAutoComplete.template = "web.FilterAutoComplete";
-FilterAutoComplete.components = {
- AutoComplete,
-};
-
-class CalendarFilterTooltip extends Component {}
-CalendarFilterTooltip.template = "web.CalendarFilterPanel.tooltip";
-
-let nextId = 1;
-
-export class CalendarFilterPanel extends Component {
- setup() {
- this.state = useState({
- collapsed: {},
- fieldRev: 1,
- });
-
- this.popover = usePopover();
- this.removePopover = null;
- }
-
- getAutoCompleteProps(section) {
- return {
- resModel: this.props.model.fields[section.fieldName].relation,
- domain: [
- [
- "id",
- "not in",
- section.filters.filter((f) => f.type !== "all").map((f) => f.value),
- ],
- ],
- searchMoreTitle: section.label,
- placeholder: `+ ${_t("Add")} ${section.label}`,
- onSelect: (value) => {
- this.props.model.createFilter(section.fieldName, value);
- },
- };
- }
-
get nextFilterId() {
nextId += 1;
return nextId;
@@ -220,7 +211,7 @@ export class CalendarFilterPanel extends Component {
}
CalendarFilterPanel.components = {
- FilterAutoComplete,
+ AutoComplete,
Transition,
};
CalendarFilterPanel.template = "web.CalendarFilterPanel";
diff --git a/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.xml b/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.xml
index 1748f32f54b..b73f3c3c3dc 100644
--- a/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.xml
+++ b/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.xml
@@ -61,7 +61,7 @@
-
+
@@ -143,8 +143,4 @@
-
-
-
-
diff --git a/addons/web/static/tests/views/calendar/calendar_view_tests.js b/addons/web/static/tests/views/calendar/calendar_view_tests.js
index 6cba24ebad3..e8ef821bc96 100644
--- a/addons/web/static/tests/views/calendar/calendar_view_tests.js
+++ b/addons/web/static/tests/views/calendar/calendar_view_tests.js
@@ -477,6 +477,257 @@ QUnit.module("Views", ({ beforeEach }) => {
);
});
+ QUnit.test("filter panel autocomplete: updates when typing", async (assert) => {
+ await makeView({
+ type: "calendar",
+ resModel: "event",
+ serverData,
+ arch: `
+
+
+
+ `,
+ });
+ const section = findFilterPanelSection(target, "partner_ids");
+ assert.containsNone(section, ".o-autocomplete--dropdown-menu");
+ assert.containsNone(section, ".o-autocomplete--dropdown-item");
+ await click(section, ".o-autocomplete--input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsN(section, ".o-autocomplete--dropdown-item", 2);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
+ el.textContent.trim()
+ ),
+ ["partner 3", "partner 4"]
+ );
+
+ const input = section.querySelector(".o-autocomplete--input");
+ input.value = "partner 3";
+ await triggerEvent(section, ".o-autocomplete--input", "input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-item");
+ assert.strictEqual(
+ section.querySelector(".o-autocomplete--dropdown-item").textContent.trim(),
+ "partner 3"
+ );
+
+ input.value = "a string that would yield to no result as it is too very much convoluted";
+ await triggerEvent(section, ".o-autocomplete--input", "input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-item");
+ assert.strictEqual(
+ section.querySelector(".o-autocomplete--dropdown-item").textContent.trim(),
+ "No records"
+ );
+ });
+
+ QUnit.test("add a filter with the search more dialog", async (assert) => {
+ serverData.views["partner,false,search"] = ``;
+ serverData.views["partner,false,list"] = `
+
+
+
+ `;
+ serverData.models.partner.records.push(
+ { id: 5, display_name: "foo partner 5" },
+ { id: 6, display_name: "foo partner 6" },
+ { id: 7, display_name: "foo partner 7" },
+ { id: 8, display_name: "foo partner 8" },
+ { id: 9, display_name: "foo partner 9" },
+ { id: 10, display_name: "foo partner 10" },
+ { id: 11, display_name: "foo partner 11" },
+ { id: 12, display_name: "foo partner 12" },
+ { id: 13, display_name: "foo partner 13" }
+ );
+ await makeView({
+ type: "calendar",
+ resModel: "event",
+ serverData,
+ arch: `
+
+
+
+ `,
+ mockRPC(route) {
+ if (route.endsWith("/has_group")) {
+ return true;
+ }
+ },
+ });
+ const section = findFilterPanelSection(target, "partner_ids");
+ assert.containsN(section, ".o_calendar_filter_item", 3);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
+ el.textContent.trim()
+ ),
+ ["partner 2", "partner 1", "Everything"]
+ );
+
+ // Open the autocomplete dropdown
+ assert.containsNone(section, ".o-autocomplete--dropdown-menu");
+ assert.containsNone(section, ".o-autocomplete--dropdown-item");
+ await click(section, ".o-autocomplete--input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
+ el.textContent.trim()
+ ),
+ [
+ "partner 3",
+ "partner 4",
+ "foo partner 5",
+ "foo partner 6",
+ "foo partner 7",
+ "foo partner 8",
+ "foo partner 9",
+ "foo partner 10",
+ "Search More...",
+ ]
+ );
+
+ // Change the search term
+ const input = section.querySelector(".o-autocomplete--input");
+ input.value = "foo";
+ await triggerEvent(section, ".o-autocomplete--input", "input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
+ el.textContent.trim()
+ ),
+ [
+ "foo partner 5",
+ "foo partner 6",
+ "foo partner 7",
+ "foo partner 8",
+ "foo partner 9",
+ "foo partner 10",
+ "foo partner 11",
+ "foo partner 12",
+ "Search More...",
+ ]
+ );
+
+ // Open the search more dialog
+ assert.containsNone(target, ".modal");
+ await click(section, ".o-autocomplete--dropdown-item:last-child");
+ assert.containsOnce(target, ".modal");
+ assert.containsN(target, ".modal .o_data_row", 9);
+ assert.deepEqual(
+ [...target.querySelectorAll(".modal .o_data_row")].map((el) => el.textContent.trim()),
+ [
+ "foo partner 5",
+ "foo partner 6",
+ "foo partner 7",
+ "foo partner 8",
+ "foo partner 9",
+ "foo partner 10",
+ "foo partner 11",
+ "foo partner 12",
+ "foo partner 13",
+ ]
+ );
+ assert.containsOnce(target, ".modal .o_searchview_facet");
+ assert.strictEqual(
+ target.querySelector(".modal .o_searchview_facet").textContent.trim(),
+ "Quick search: foo"
+ );
+
+ // Choose a record
+ await click(target, ".modal .o_data_row:first-child > td:first-child");
+ assert.containsNone(target, ".modal");
+ assert.containsN(section, ".o_calendar_filter_item", 4);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
+ el.textContent.trim()
+ ),
+ ["partner 2", "partner 1", "foo partner 5", "Everything"]
+ );
+ assert.strictEqual(input.value, "");
+
+ // Open the autocomplete dropdown
+ assert.containsNone(section, ".o-autocomplete--dropdown-menu");
+ assert.containsNone(section, ".o-autocomplete--dropdown-item");
+ await click(section, ".o-autocomplete--input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
+ el.textContent.trim()
+ ),
+ [
+ "partner 3",
+ "partner 4",
+ "foo partner 6",
+ "foo partner 7",
+ "foo partner 8",
+ "foo partner 9",
+ "foo partner 10",
+ "foo partner 11",
+ "Search More...",
+ ]
+ );
+
+ // Change the search term
+ input.value = "foo";
+ await triggerEvent(section, ".o-autocomplete--input", "input");
+ assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
+ assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
+ el.textContent.trim()
+ ),
+ [
+ "foo partner 6",
+ "foo partner 7",
+ "foo partner 8",
+ "foo partner 9",
+ "foo partner 10",
+ "foo partner 11",
+ "foo partner 12",
+ "foo partner 13",
+ "Search More...",
+ ]
+ );
+
+ // Open the search more dialog
+ assert.containsNone(target, ".modal");
+ await click(section, ".o-autocomplete--dropdown-item:last-child");
+ assert.containsOnce(target, ".modal");
+ assert.containsN(target, ".modal .o_data_row", 8);
+ assert.deepEqual(
+ [...target.querySelectorAll(".modal .o_data_row")].map((el) => el.textContent.trim()),
+ [
+ "foo partner 6",
+ "foo partner 7",
+ "foo partner 8",
+ "foo partner 9",
+ "foo partner 10",
+ "foo partner 11",
+ "foo partner 12",
+ "foo partner 13",
+ ]
+ );
+ assert.containsOnce(target, ".modal .o_searchview_facet");
+ assert.strictEqual(
+ target.querySelector(".modal .o_searchview_facet").textContent.trim(),
+ "Quick search: foo"
+ );
+
+ // Close the search more dialog without choosing a record
+ await click(target, ".modal .o_form_button_cancel");
+ assert.containsNone(target, ".modal");
+ assert.containsN(section, ".o_calendar_filter_item", 4);
+ assert.deepEqual(
+ [...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
+ el.textContent.trim()
+ ),
+ ["partner 2", "partner 1", "foo partner 5", "Everything"]
+ );
+ assert.strictEqual(input.value, "");
+ });
+
QUnit.test(
`delete attribute on calendar doesn't show delete button in popover`,
async (assert) => {