Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Routing Panel in the "Other" tab #703

Merged
merged 5 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion client-app/src/desktop/AppModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,12 @@ export class AppModel extends BaseAppModel {
{name: 'pinPad', path: '/pinPad'},
{name: 'placeholder', path: '/placeholder'},
{name: 'popups', path: '/popups'},
{name: 'timestamp', path: '/timestamp'}
{name: 'timestamp', path: '/timestamp'},
{
name: 'simpleRouting',
path: '/simpleRouting',
children: [{name: 'recordId', path: '/:recordId'}]
}
]
},
{
Expand Down
4 changes: 3 additions & 1 deletion client-app/src/desktop/tabs/other/OtherTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {pinPadPanel} from './PinPadPanel';
import {placeholderPanel} from './PlaceholderPanel';
import {popupsPanel} from './PopupsPanel';
import {relativeTimestampPanel} from './relativetimestamp/RelativeTimestampPanel';
import {simpleRoutingPanel} from './routing/SimpleRoutingPanel';

export const otherTab = hoistCmp.factory(() =>
tabContainer({
Expand Down Expand Up @@ -44,7 +45,8 @@ export const otherTab = hoistCmp.factory(() =>
{id: 'pinPad', title: 'PIN Pad', content: pinPadPanel},
{id: 'placeholder', title: 'Placeholder', content: placeholderPanel},
{id: 'popups', content: popupsPanel},
{id: 'timestamp', content: relativeTimestampPanel}
{id: 'timestamp', content: relativeTimestampPanel},
{id: 'simpleRouting', content: simpleRoutingPanel}
]
},
className: 'toolbox-tab'
Expand Down
110 changes: 110 additions & 0 deletions client-app/src/desktop/tabs/other/routing/SimpleRoutingPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {grid, GridModel} from '@xh/hoist/cmp/grid';
import {creates, hoistCmp, HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
import {panel} from '@xh/hoist/desktop/cmp/panel';
import {Icon} from '@xh/hoist/icon';
import React from 'react';
import {wrapper} from '../../../common';

export const simpleRoutingPanel = hoistCmp.factory({
displayName: 'SimpleRoutingPanel',
model: creates(() => new SimpleRoutingPanelModel()),

render({model}) {
const routedUrl = `${window.location.origin}/app/other/simpleRouting/123`;
return wrapper({
description: [
<p>
Hoist provides functionality for route parameters to interact with UI
components. The grid below has its selected record synced with a routable URL.
</p>,
<p>
Given a URL such as <a href={routedUrl}>{routedUrl}</a>, where <code>123</code>{' '}
is a record ID, we can auto-select the matching record in the grid. Updates to
application state can be pushed back to the URL - try selecting a different
record in the grid and observe the URL change.
</p>,
<p>
Note that this routing relies on an appropriate route path being defined in the
config returned by <code>AppModel.getRoutes()</code>.
</p>
],
item: panel({
title: 'Simple Routing',
icon: Icon.gridPanel(),
item: grid(),
height: 500,
width: 700
})
});
}
});

@managed
class SimpleRoutingPanelModel extends HoistModel {
private readonly BASE_ROUTE = 'default.other.simpleRouting';

@managed gridModel = new GridModel({
columns: [{field: 'id'}, {field: 'company', flex: 1}]
});

constructor() {
super();
this.addReaction(
{
// Track lastLoadCompleted to sync route -> grid after initial load.
track: () => [XH.routerState.params, this.lastLoadCompleted],
run: () => this.updateGridFromRoute()
},
{
track: () => this.gridModel.selectedId,
run: () => this.updateRouteFromGrid()
}
);
}

async updateGridFromRoute() {
const {gridModel, BASE_ROUTE} = this,
{name: currRouteName, params} = XH.routerState,
{recordId} = params;

// No-op if not on the current base route.
if (!currRouteName.startsWith(BASE_ROUTE)) return;

if (recordId) {
await gridModel.selectAsync(Number(recordId));

// Check and alert if requested record not found, and clean up route to match.
if (!gridModel.selectedRecord) {
XH.dangerToast(`Record ${recordId} not found.`);
XH.navigate(BASE_ROUTE, {replace: true});
}
} else {
gridModel.clearSelection();
}
}

updateRouteFromGrid() {
const {gridModel, BASE_ROUTE} = this,
{name: currRouteName, params} = XH.routerState,
{selectedId} = gridModel,
{recordId} = params;

// No-op if not on the current base route, or if route and selection already match.
if (!currRouteName.startsWith(BASE_ROUTE) || recordId === selectedId) return;

if (selectedId) {
XH.navigate(
'default.other.simpleRouting.recordId',
{recordId: selectedId},
{replace: true} // avoids adding steps to browser history
);
} else {
XH.navigate('default.other.simpleRouting', {replace: true});
}
}

override async doLoadAsync(loadSpec: LoadSpec) {
const {trades} = await XH.fetchJson({url: 'trade', loadSpec});
this.gridModel.loadData(trades);
}
}
Loading