Skip to content

Commit f4b4474

Browse files
committed
[IMP] web_view_leaflet_map: add standard Odoo search bar and remove sidebar search
This commit integrates the leaflet_map view with Odoo's standard search infrastructure (SearchBar, CogMenu, filters, groupBy, favorites). Changes: - Model now extends core Model class with setup() and load() methods - Controller uses useModelWithSampleData, useSetupAction, useSearchBarToggler - View definition returns modelParams and includes groupBy in searchMenuTypes - Template adds slots for SearchBar, CogMenu and toggler components - ArchParser accepts fields parameter for field definitions - Renderer extracts config from model.metaData.archInfo - Removed redundant search from PinList and DraggablePinList sidebars The search is now handled by Odoo's standard search bar in the control panel.
1 parent 9ffad6d commit f4b4474

9 files changed

Lines changed: 313 additions & 317 deletions

File tree

web_view_leaflet_map/static/src/components/pin-list/draggable_pin_list.xml

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,6 @@ This is a generic component for any model that supports resequencing.
3333

3434
<!-- Content (hidden when collapsed) -->
3535
<t t-if="!state.collapsed">
36-
<!-- Search -->
37-
<div class="o_pin_list_search p-2">
38-
<div class="input-group input-group-sm">
39-
<span class="input-group-text">
40-
<i class="fa fa-search" />
41-
</span>
42-
<input
43-
type="text"
44-
class="form-control"
45-
placeholder="Search..."
46-
t-att-value="state.searchQuery"
47-
t-on-input="onSearchInput"
48-
/>
49-
<t t-if="state.searchQuery">
50-
<button
51-
class="btn btn-outline-secondary"
52-
type="button"
53-
t-on-click="clearSearch"
54-
>
55-
<i class="fa fa-times" />
56-
</button>
57-
</t>
58-
</div>
59-
</div>
60-
6136
<!-- Stats -->
6237
<div class="o_pin_list_stats px-2 pb-2">
6338
<small class="text-muted">

web_view_leaflet_map/static/src/components/pin-list/pin_list.esm.js

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,6 @@ export class PinList extends Component {
3939
this.state = useState({
4040
collapsed: false,
4141
collapsedGroups: {},
42-
searchQuery: "",
43-
});
44-
}
45-
46-
/**
47-
* Get filtered records based on search query
48-
*/
49-
get filteredRecords() {
50-
if (!this.state.searchQuery) {
51-
return this.props.records;
52-
}
53-
const query = this.state.searchQuery.toLowerCase();
54-
return this.props.records.filter((record) => {
55-
const title = this.getRecordTitle(record).toLowerCase();
56-
const address = this.getRecordAddress(record).toLowerCase();
57-
return title.includes(query) || address.includes(query);
5842
});
5943
}
6044

@@ -63,7 +47,7 @@ export class PinList extends Component {
6347
* Records without a groupBy value are placed in the unassigned group.
6448
*/
6549
get groupedRecords() {
66-
const records = this.filteredRecords;
50+
const records = this.props.records;
6751
const UNASSIGNED_GROUP_NAME = this.props.unassignedGroupName;
6852
// Orange color for unassigned group
6953
const UNASSIGNED_COLOR = "#fd7e14";
@@ -111,7 +95,7 @@ export class PinList extends Component {
11195
* Get total count of located records
11296
*/
11397
get locatedCount() {
114-
return this.filteredRecords.filter(
98+
return this.props.records.filter(
11599
(r) =>
116100
r[this.props.fieldLatitude] &&
117101
r[this.props.fieldLongitude] &&
@@ -126,7 +110,7 @@ export class PinList extends Component {
126110
* Get count of records without valid coordinates
127111
*/
128112
get unlocatedCount() {
129-
return this.filteredRecords.length - this.locatedCount;
113+
return this.props.records.length - this.locatedCount;
130114
}
131115

132116
/**
@@ -240,20 +224,6 @@ export class PinList extends Component {
240224
}
241225
}
242226

243-
/**
244-
* Update search query
245-
*/
246-
onSearchInput(ev) {
247-
this.state.searchQuery = ev.target.value;
248-
}
249-
250-
/**
251-
* Clear search
252-
*/
253-
clearSearch() {
254-
this.state.searchQuery = "";
255-
}
256-
257227
/**
258228
* Generate Google Maps navigation URL
259229
*/

web_view_leaflet_map/static/src/components/pin-list/pin_list.xml

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,6 @@
2525

2626
<!-- Content (hidden when collapsed) -->
2727
<t t-if="!state.collapsed">
28-
<!-- Search -->
29-
<div class="o_pin_list_search p-2">
30-
<div class="input-group input-group-sm">
31-
<span class="input-group-text">
32-
<i class="fa fa-search" />
33-
</span>
34-
<input
35-
type="text"
36-
class="form-control"
37-
placeholder="Search..."
38-
t-att-value="state.searchQuery"
39-
t-on-input="onSearchInput"
40-
/>
41-
<t t-if="state.searchQuery">
42-
<button
43-
class="btn btn-outline-secondary"
44-
type="button"
45-
t-on-click="clearSearch"
46-
>
47-
<i class="fa fa-times" />
48-
</button>
49-
</t>
50-
</div>
51-
</div>
52-
5328
<!-- Stats -->
5429
<div class="o_pin_list_stats px-2 pb-2">
5530
<small class="text-muted">

web_view_leaflet_map/static/src/leaflet_map_view/leaflet_map_arch_parser.esm.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,22 @@ import {visitXML} from "@web/core/utils/xml";
1414
export class LeafletMapArchParser {
1515
/**
1616
* Parse the arch XML and extract view configuration.
17+
* Follows the standard Odoo pattern with (arch, fields) signature.
1718
*
1819
* @param {Element} arch - The XML arch element
20+
* @param {Object} fields - Field definitions from the model (optional)
1921
* @returns {Object} Parsed arch information
2022
*/
21-
parse(arch) {
23+
parse(arch, fields = {}) {
2224
const archInfo = {
2325
// Required fields that are always loaded
2426
fieldNames: ["id", "display_name"],
2527
// Fields displayed in marker popup
2628
fieldNamesMarkerPopup: [],
2729
// Field metadata from arch
2830
fieldNodes: {},
31+
// Store fields reference for validation
32+
fields,
2933
};
3034

3135
visitXML(arch, (node) => {
@@ -34,7 +38,7 @@ export class LeafletMapArchParser {
3438
}
3539

3640
if (node.tagName === "field") {
37-
this._parseFieldNode(node, archInfo);
41+
this._parseFieldNode(node, archInfo, fields);
3842
}
3943
});
4044

@@ -105,7 +109,7 @@ export class LeafletMapArchParser {
105109
archInfo.fieldNames.push(archInfo.groupBy);
106110
}
107111

108-
// Drag-and-drop configuration (NEW)
112+
// Drag-and-drop configuration
109113
archInfo.draggable = getAttr("draggable") === "1";
110114
archInfo.groupField = getAttr("group_field");
111115
archInfo.defaultOrder = getAttr("default_order");
@@ -131,22 +135,29 @@ export class LeafletMapArchParser {
131135
*
132136
* @param {Element} node - The field XML element
133137
* @param {Object} archInfo - The arch info object to populate
138+
* @param {Object} fields - Field definitions from the model
134139
*/
135-
_parseFieldNode(node, archInfo) {
140+
_parseFieldNode(node, archInfo, fields) {
136141
const fieldName = node.getAttribute("name");
137142
if (!fieldName) {
138143
return;
139144
}
140145

141146
archInfo.fieldNames.push(fieldName);
147+
148+
// Get field info from model definition if available
149+
const fieldDef = fields[fieldName] || {};
150+
142151
archInfo.fieldNodes[fieldName] = {
143152
name: fieldName,
144-
string: node.getAttribute("string"),
153+
string: node.getAttribute("string") || fieldDef.string || fieldName,
145154
invisible: node.getAttribute("invisible") === "1",
155+
type: fieldDef.type,
146156
};
157+
147158
archInfo.fieldNamesMarkerPopup.push({
148159
fieldName,
149-
string: node.getAttribute("string"),
160+
string: node.getAttribute("string") || fieldDef.string || fieldName,
150161
});
151162
}
152163
}

web_view_leaflet_map/static/src/leaflet_map_view/leaflet_map_controller.esm.js

Lines changed: 23 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -5,118 +5,48 @@
55
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
66
*/
77

8-
import {Component, onWillStart, useState, useSubEnv} from "@odoo/owl";
8+
import {Component, useRef} from "@odoo/owl";
99
import {useService} from "@web/core/utils/hooks";
10+
import {useModelWithSampleData} from "@web/model/model";
11+
import {standardViewProps} from "@web/views/standard_view_props";
12+
import {useSetupAction} from "@web/search/action_hook";
1013
import {Layout} from "@web/search/layout";
11-
12-
import {LeafletMapModel} from "./leaflet_map_model.esm";
13-
import {LeafletMapRenderer} from "./leaflet_map_renderer.esm";
14+
import {SearchBar} from "@web/search/search_bar/search_bar";
15+
import {useSearchBarToggler} from "@web/search/search_bar/search_bar_toggler";
16+
import {CogMenu} from "@web/search/cog_menu/cog_menu";
1417

1518
/**
1619
* LeafletMapController is the main controller for the leaflet map view.
1720
* It manages the model lifecycle and coordinates between the search panel
18-
* and the renderer.
21+
* and the renderer. Follows the standard Odoo view controller pattern.
1922
*/
2023
export class LeafletMapController extends Component {
2124
static template = "web_view_leaflet_map.LeafletMapController";
22-
static components = {Layout, LeafletMapRenderer};
25+
static components = {Layout, SearchBar, CogMenu};
2326

2427
static props = {
25-
resModel: {type: String},
26-
arch: {type: Object, optional: true},
27-
archInfo: {type: Object},
28-
domain: {type: Array, optional: true},
29-
context: {type: Object, optional: true},
30-
fields: {type: Object, optional: true},
31-
limit: {type: Number, optional: true},
32-
display: {type: Object, optional: true},
33-
// Model and Renderer classes to use (allows overriding)
34-
Model: {type: Function, optional: true},
35-
Renderer: {type: Function, optional: true},
36-
// Standard view controller props passed by Odoo framework (WithSearch)
37-
// Using wildcard to accept all standard props without explicit declaration
38-
"*": true,
39-
};
40-
41-
static defaultProps = {
42-
domain: [],
43-
context: {},
44-
fields: {},
28+
...standardViewProps,
29+
Model: Function,
30+
modelParams: Object,
31+
Renderer: Function,
32+
buttonTemplate: {type: String, optional: true},
4533
};
4634

4735
setup() {
48-
this.orm = useService("orm");
4936
this.action = useService("action");
5037
this.notification = useService("notification");
5138

52-
// State for reactive updates
53-
// dataVersion is incremented after each data reload to force re-render
54-
this.state = useState({
55-
loading: true,
56-
dataVersion: 0,
57-
});
58-
59-
// Set up sub-environment for child components
60-
useSubEnv({
61-
config: {
62-
...this.env.config,
63-
},
64-
});
65-
66-
// Create model instance
67-
const ModelClass = this.props.Model || LeafletMapModel;
68-
this.model = new ModelClass(
69-
this.env,
70-
{
71-
resModel: this.props.resModel,
72-
archInfo: this.props.archInfo,
73-
fields: this.props.fields,
74-
context: this.props.context,
75-
},
76-
{orm: this.orm}
77-
);
39+
// Use the standard model hook that integrates with WithSearch
40+
this.model = useModelWithSampleData(this.props.Model, this.props.modelParams);
7841

79-
// Initial data load
80-
onWillStart(async () => {
81-
await this.loadData();
42+
// Setup action hook for state management
43+
useSetupAction({
44+
rootRef: useRef("root"),
45+
getLocalState: () => ({metaData: this.model.metaData}),
8246
});
83-
}
84-
85-
/**
86-
* Get the Renderer component class to use.
87-
*/
88-
get RendererComponent() {
89-
return this.props.Renderer || LeafletMapRenderer;
90-
}
9147

92-
/**
93-
* Load data from the model.
94-
*/
95-
async loadData() {
96-
this.state.loading = true;
97-
try {
98-
await this.model.load({
99-
domain: this.props.domain,
100-
limit: this.props.limit,
101-
context: this.props.context,
102-
});
103-
} finally {
104-
this.state.loading = false;
105-
}
106-
}
107-
108-
/**
109-
* Reload data (called after resequencing or domain changes).
110-
*/
111-
async reloadData() {
112-
this.state.loading = true;
113-
try {
114-
await this.model.reload();
115-
} finally {
116-
this.state.loading = false;
117-
// Increment dataVersion to force re-render of child components
118-
this.state.dataVersion++;
119-
}
48+
// Setup search bar toggler for mobile responsiveness
49+
this.searchBarToggler = useSearchBarToggler();
12050
}
12151

12252
/**
@@ -134,9 +64,7 @@ export class LeafletMapController extends Component {
13464
previousRecordId
13565
);
13666

137-
if (result.success) {
138-
await this.reloadData();
139-
} else {
67+
if (!result.success) {
14068
this.notification.add(result.error || "Failed to reorder item", {
14169
type: "danger",
14270
});
@@ -155,14 +83,8 @@ export class LeafletMapController extends Component {
15583
*/
15684
get rendererProps() {
15785
return {
158-
resModel: this.props.resModel,
159-
archInfo: this.props.archInfo,
160-
fields: this.props.fields,
161-
context: this.props.context,
16286
model: this.model,
16387
onResequence: this.onResequence.bind(this),
164-
// DataVersion triggers re-render when data changes
165-
dataVersion: this.state.dataVersion,
16688
};
16789
}
16890
}

0 commit comments

Comments
 (0)