Skip to content

Commit 047c9c6

Browse files
jacob-xhioamcclain
andauthoredMar 26, 2024··
Simple Routing Panel in the "Other" tab (#703)
* Add example of simple route-based management of a single parameter (in this case, grid selection). --------- Co-authored-by: Anselm McClain <[email protected]>
1 parent 8b0f655 commit 047c9c6

File tree

3 files changed

+119
-2
lines changed

3 files changed

+119
-2
lines changed
 

‎client-app/src/desktop/AppModel.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,12 @@ export class AppModel extends BaseAppModel {
179179
{name: 'pinPad', path: '/pinPad'},
180180
{name: 'placeholder', path: '/placeholder'},
181181
{name: 'popups', path: '/popups'},
182-
{name: 'timestamp', path: '/timestamp'}
182+
{name: 'timestamp', path: '/timestamp'},
183+
{
184+
name: 'simpleRouting',
185+
path: '/simpleRouting',
186+
children: [{name: 'recordId', path: '/:recordId'}]
187+
}
183188
]
184189
},
185190
{

‎client-app/src/desktop/tabs/other/OtherTab.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {pinPadPanel} from './PinPadPanel';
1717
import {placeholderPanel} from './PlaceholderPanel';
1818
import {popupsPanel} from './PopupsPanel';
1919
import {relativeTimestampPanel} from './relativetimestamp/RelativeTimestampPanel';
20+
import {simpleRoutingPanel} from './routing/SimpleRoutingPanel';
2021

2122
export const otherTab = hoistCmp.factory(() =>
2223
tabContainer({
@@ -44,7 +45,8 @@ export const otherTab = hoistCmp.factory(() =>
4445
{id: 'pinPad', title: 'PIN Pad', content: pinPadPanel},
4546
{id: 'placeholder', title: 'Placeholder', content: placeholderPanel},
4647
{id: 'popups', content: popupsPanel},
47-
{id: 'timestamp', content: relativeTimestampPanel}
48+
{id: 'timestamp', content: relativeTimestampPanel},
49+
{id: 'simpleRouting', content: simpleRoutingPanel}
4850
]
4951
},
5052
className: 'toolbox-tab'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {grid, GridModel} from '@xh/hoist/cmp/grid';
2+
import {creates, hoistCmp, HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
3+
import {panel} from '@xh/hoist/desktop/cmp/panel';
4+
import {Icon} from '@xh/hoist/icon';
5+
import React from 'react';
6+
import {wrapper} from '../../../common';
7+
8+
export const simpleRoutingPanel = hoistCmp.factory({
9+
displayName: 'SimpleRoutingPanel',
10+
model: creates(() => new SimpleRoutingPanelModel()),
11+
12+
render({model}) {
13+
const routedUrl = `${window.location.origin}/app/other/simpleRouting/123`;
14+
return wrapper({
15+
description: [
16+
<p>
17+
Hoist provides functionality for route parameters to interact with UI
18+
components. The grid below has its selected record synced with a routable URL.
19+
</p>,
20+
<p>
21+
Given a URL such as <a href={routedUrl}>{routedUrl}</a>, where <code>123</code>{' '}
22+
is a record ID, we can auto-select the matching record in the grid. Updates to
23+
application state can be pushed back to the URL - try selecting a different
24+
record in the grid and observe the URL change.
25+
</p>,
26+
<p>
27+
Note that this routing relies on an appropriate route path being defined in the
28+
config returned by <code>AppModel.getRoutes()</code>.
29+
</p>
30+
],
31+
item: panel({
32+
title: 'Simple Routing',
33+
icon: Icon.gridPanel(),
34+
item: grid(),
35+
height: 500,
36+
width: 700
37+
})
38+
});
39+
}
40+
});
41+
42+
@managed
43+
class SimpleRoutingPanelModel extends HoistModel {
44+
private readonly BASE_ROUTE = 'default.other.simpleRouting';
45+
46+
@managed gridModel = new GridModel({
47+
columns: [{field: 'id'}, {field: 'company', flex: 1}]
48+
});
49+
50+
constructor() {
51+
super();
52+
this.addReaction(
53+
{
54+
// Track lastLoadCompleted to sync route -> grid after initial load.
55+
track: () => [XH.routerState.params, this.lastLoadCompleted],
56+
run: () => this.updateGridFromRoute()
57+
},
58+
{
59+
track: () => this.gridModel.selectedId,
60+
run: () => this.updateRouteFromGrid()
61+
}
62+
);
63+
}
64+
65+
async updateGridFromRoute() {
66+
const {gridModel, BASE_ROUTE} = this,
67+
{name: currRouteName, params} = XH.routerState,
68+
{recordId} = params;
69+
70+
// No-op if not on the current base route.
71+
if (!currRouteName.startsWith(BASE_ROUTE)) return;
72+
73+
if (recordId) {
74+
await gridModel.selectAsync(Number(recordId));
75+
76+
// Check and alert if requested record not found, and clean up route to match.
77+
if (!gridModel.selectedRecord) {
78+
XH.dangerToast(`Record ${recordId} not found.`);
79+
XH.navigate(BASE_ROUTE, {replace: true});
80+
}
81+
} else {
82+
gridModel.clearSelection();
83+
}
84+
}
85+
86+
updateRouteFromGrid() {
87+
const {gridModel, BASE_ROUTE} = this,
88+
{name: currRouteName, params} = XH.routerState,
89+
{selectedId} = gridModel,
90+
{recordId} = params;
91+
92+
// No-op if not on the current base route, or if route and selection already match.
93+
if (!currRouteName.startsWith(BASE_ROUTE) || recordId === selectedId) return;
94+
95+
if (selectedId) {
96+
XH.navigate(
97+
'default.other.simpleRouting.recordId',
98+
{recordId: selectedId},
99+
{replace: true} // avoids adding steps to browser history
100+
);
101+
} else {
102+
XH.navigate('default.other.simpleRouting', {replace: true});
103+
}
104+
}
105+
106+
override async doLoadAsync(loadSpec: LoadSpec) {
107+
const {trades} = await XH.fetchJson({url: 'trade', loadSpec});
108+
this.gridModel.loadData(trades);
109+
}
110+
}

0 commit comments

Comments
 (0)
Please sign in to comment.