Skip to main content

Routing

Sometimes it is useful to share a link with a colleague or business executive that opens an app in a specific state depending on the URI and points to the important information that the one would like to share. Datagrok provides routing support for package developers to achieve this.

Routing tutorial

Routing is implemented by manipulating the path property of the View. Let's build a simple app that opens tables, based on the URL path and applies selection according to the parameters passed in the URL.

We start off by getting the path and splitting it in segments:

//name: Test
//input: string path {meta.url: true}
//input: string searchParam
//tags: app
export function test(url, searchParam) {
const pathSegments = url.split('/').filter((s) => s != '');
// etc.
}

Application gets the path in input parameter marked with meta.url: true, and all URL parameters mapped by names.

Depending on the pathSegments length, we choose to either start the application with the default behavior or handle the URI parameters. Typically, when the app starts from the Apps page, the pathSegments would contain no elements because path equals /

Let's say the default behavior would be to add a couple of tables and set default paths for them.

if (pathSegments.length == 0) {
//Adding demog table view
const demog = grok.data.testData('demog');
const demogView = grok.shell.addTableView(demog);
demogView.scatterPlot();
demogView.path = '/Demog/All';
grok.shell.v = demogView;

//Adding random walk table view
const wells = grok.data.testData('wells');
const wellsView = grok.shell.addTableView(wells);
wellsView.scatterPlot();
wellsView.basePath = '/wells/All';
grok.shell.v = wellsView;
}

Note that by setting .path we add path postfix after base path. In the case of demog table, the pathname would be /apps/TestPackage/demog/All. Link with such URI could be opened by another user, but for this, to work we also need to handle the case, when the pathSegments contains segments demog and All

const tableName = pathSegments[0];
const selectionLabel = pathSegments[1] ?? 'All';
const table = grok.data.testData(tableName as testData);
const tableView = grok.shell.addTableView(table);
tableView.path = `/${tableName}/${selectionLabel}`;
setSelection(tableView, selectionLabel);
grok.shell.v = tableView;

That's it! Now you've learned how to use routing to enhance your apps. For additional info see the full code and useful links below.

Function base path

By default, function base path looks like /apps of /browse/apps prefix with package name and then app name. If there is an only app in the package, app name can be omitted;

For example:

/apps/TestPackage
/browse/apps/TestPackage

or in case of 2 and more apps in the package:

/apps/TestPackage/Test
/browse/apps/TestPackage/Test

You can customize package segment by adding meta section in package.json file:

  "meta": {
"url": "/some/custom/route"
}

Now URL starts to look like this:

/apps/some/custom/route
/browse/apps/some/custom/route

or in case of 2 and more apps in the package:

/apps/some/custom/route/Test
/browse/apps/some/custom/route/Test

Package name based URL will also work, but will be immediately replaced by the new one, when user opens it.

Also, you can specify app URL alias with meta.url tag:

//name: Test
//input: string path {meta.url: true}
//input: string searchParam
//meta.url: /application
//tags: app
export function test(url, searchParam) {
const pathSegments = url.split('/').filter((s) => s != '');
// etc.
}

In this case, URL becomes:

/apps/some/custom/route/application
/browse/apps/some/custom/route/application

If app URL contains the only slash, application becomes default for the package:

/apps/some/custom/route
/browse/apps/some/custom/route

Tutorial code

Here's the full code used in the tutorial.

/* Do not change these import lines to match external modules in webpack configuration */
import * as DG from 'datagrok-api/dg';
import * as grok from 'datagrok-api/grok';

type testData = "wells" | "demog" | "biosensor" | "random walk" | "geo" | "molecules" | "dose-response";

//name: Test
//input: string path {meta.url: true}
//input: string filter
//tags: app
export function test(path: string, filter: string) {
const pathSegments = url.split('/').filter((s) => s != '');

if (pathSegments.length == 0) { //Fresh app start
//Adding demog table view
const demog = grok.data.testData('demog');
const demogView = grok.shell.addTableView(demog);
demogView.scatterPlot();
demogView.path = '/demog/All';
grok.shell.v = demogView;

//Adding random walk table view
const wells = grok.data.testData('wells');
const wellsView = grok.shell.addTableView(wells);
wellsView.scatterPlot();
wellsView.path = '/wells/All';
grok.shell.v = wellsView;
} else { //Handle routing
const tableName = pathSegments[0];
const selectionLabel = pathSegments[1] ?? 'All';
const table = grok.data.testData(tableName as testData);
const tableView = grok.shell.addTableView(table);
setSelection(tableView, tableName, selectionLabel, filter);
grok.shell.v = tableView;
}
}

function setSelection(tableView: DG.TableView, name: string, label: string, filter: string) {
tableView.path = `/${name}/${label}`;
if (label === 'All') {
tableView.dataFrame.selection.init(_ => true);
return;
}
const [colName, category] = filter.split('=');
if (!colName || !category)
throw new Error(`PathError: wrong filter format '${label}'. Should be 'colName=value'.`)
tableView.dataFrame.rows.match(`${colName} = ${category}`).select();
}

See also: