0
# URL Routing
1
2
URL-based navigation system with pattern matching, command mapping, and programmatic route handling for single-page application behavior.
3
4
## Capabilities
5
6
### Router Class
7
8
Main router implementation providing URL routing with pattern matching and command execution for navigation within the application.
9
10
```typescript { .api }
11
/**
12
* URL routing implementation for applications
13
*/
14
class Router implements IRouter {
15
constructor(options: Router.IOptions);
16
17
/** The base URL for the router */
18
readonly base: string;
19
20
/** The command registry used by the router */
21
readonly commands: CommandRegistry;
22
23
/** The parsed current URL of the application */
24
readonly current: IRouter.ILocation;
25
26
/** A signal emitted when the router routes a route */
27
readonly routed: ISignal<this, IRouter.ILocation>;
28
29
/** Stop token for halting route matching chain */
30
readonly stop: Token<void>;
31
32
/**
33
* Navigate to a new path within the application
34
* @param path - The new path or empty string if redirecting to root
35
* @param options - The navigation options
36
*/
37
navigate(path: string, options?: IRouter.INavOptions): void;
38
39
/**
40
* Register a rule that maps a path pattern to a command
41
* @param options - The route registration options
42
* @returns A disposable that removes the registered rule from the router
43
*/
44
register(options: IRouter.IRegisterOptions): IDisposable;
45
46
/** Cause a hard reload of the document */
47
reload(): void;
48
49
/**
50
* Route the current URL
51
* @returns Promise that resolves when routing is complete
52
*/
53
route(): Promise<void>;
54
}
55
```
56
57
**Usage Examples:**
58
59
```typescript
60
import { Router, IRouter } from "@jupyterlab/application";
61
import { CommandRegistry } from "@lumino/commands";
62
63
// Create router
64
const commands = new CommandRegistry();
65
const router = new Router({
66
base: '/lab',
67
commands: commands
68
});
69
70
// Register commands first
71
commands.addCommand('app:open-file', {
72
execute: (args) => {
73
console.log('Opening file:', args.path);
74
}
75
});
76
77
commands.addCommand('app:show-help', {
78
execute: () => {
79
console.log('Showing help');
80
}
81
});
82
83
// Register route patterns
84
router.register({
85
command: 'app:open-file',
86
pattern: /^\/file\/(.+)$/,
87
rank: 10
88
});
89
90
router.register({
91
command: 'app:show-help',
92
pattern: /^\/help$/,
93
rank: 20
94
});
95
96
// Navigate programmatically
97
router.navigate('/file/notebook.ipynb');
98
router.navigate('/help');
99
100
// Listen to route changes
101
router.routed.connect((sender, location) => {
102
console.log('Routed to:', location.path);
103
});
104
105
// Current location info
106
console.log('Current path:', router.current.path);
107
console.log('Current hash:', router.current.hash);
108
```
109
110
### IRouter Interface
111
112
Service interface for URL routing functionality with dependency injection support.
113
114
```typescript { .api }
115
/**
116
* URL routing service interface
117
*/
118
interface IRouter {
119
/** The base URL for the router */
120
readonly base: string;
121
122
/** The command registry used by the router */
123
readonly commands: CommandRegistry;
124
125
/** The parsed current URL of the application */
126
readonly current: IRouter.ILocation;
127
128
/** A signal emitted when the router routes a route */
129
readonly routed: ISignal<IRouter, IRouter.ILocation>;
130
131
/** Stop token for halting route matching if returned from command */
132
readonly stop: Token<void>;
133
134
/**
135
* Navigate to a new path within the application
136
* @param path - The new path or empty string if redirecting to root
137
* @param options - The navigation options
138
*/
139
navigate(path: string, options?: IRouter.INavOptions): void;
140
141
/**
142
* Register a rule that maps a path pattern to a command
143
* @param options - The route registration options
144
* @returns A disposable that removes the registered rule from the router
145
*/
146
register(options: IRouter.IRegisterOptions): IDisposable;
147
148
/** Cause a hard reload of the document */
149
reload(): void;
150
151
/**
152
* Route the current URL
153
* @returns Promise that resolves when routing is complete
154
*
155
* #### Notes
156
* If a pattern is matched, its command will be invoked with arguments that
157
* match the `IRouter.ILocation` interface.
158
*/
159
route(): Promise<void>;
160
}
161
162
/**
163
* Service token for URL router
164
*/
165
const IRouter: Token<IRouter>;
166
```
167
168
**Usage in Plugins:**
169
170
```typescript
171
import { IRouter } from "@jupyterlab/application";
172
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
173
174
const routingPlugin: JupyterFrontEndPlugin<void> = {
175
id: 'my-routing-plugin',
176
autoStart: true,
177
requires: [IRouter],
178
activate: (app, router: IRouter) => {
179
// Register routes for this plugin
180
const disposable = router.register({
181
command: 'myapp:handle-route',
182
pattern: /^\/myapp\/(.+)$/,
183
rank: 100
184
});
185
186
// Listen to route changes
187
router.routed.connect((sender, location) => {
188
if (location.path.startsWith('/myapp/')) {
189
console.log('My app route activated');
190
}
191
});
192
193
// Clean up on plugin disposal
194
return disposable;
195
}
196
};
197
```
198
199
### Location Interface
200
201
Information about the current URL location including parsed components.
202
203
```typescript { .api }
204
/**
205
* The parsed location currently being routed
206
*/
207
interface IRouter.ILocation extends ReadonlyPartialJSONObject {
208
/** The location hash */
209
hash: string;
210
211
/** The path that matched a routing pattern */
212
path: string;
213
214
/**
215
* The request being routed with the router `base` omitted
216
*
217
* #### Notes
218
* This field includes the query string and hash, if they exist.
219
*/
220
request: string;
221
222
/**
223
* The search element, including leading question mark (`'?'`), if any,
224
* of the path.
225
*/
226
search?: string;
227
}
228
```
229
230
**Usage Examples:**
231
232
```typescript
233
// Accessing location information
234
const currentLocation = router.current;
235
236
console.log('Full request:', currentLocation.request);
237
console.log('Path only:', currentLocation.path);
238
console.log('Hash:', currentLocation.hash);
239
console.log('Query string:', currentLocation.search);
240
241
// Example values for URL: /lab/tree/notebooks?filter=python#section1
242
// base: '/lab'
243
// request: '/tree/notebooks?filter=python#section1'
244
// path: '/tree/notebooks'
245
// search: '?filter=python'
246
// hash: '#section1'
247
```
248
249
### Navigation Options
250
251
Options for controlling navigation behavior and routing.
252
253
```typescript { .api }
254
/**
255
* The options passed into a navigation request
256
*/
257
interface IRouter.INavOptions {
258
/**
259
* Whether the navigation should be hard URL change instead of an HTML
260
* history API change.
261
*/
262
hard?: boolean;
263
264
/**
265
* Should the routing stage be skipped when navigating? This will simply rewrite the URL
266
* and push the new state to the history API, no routing commands will be triggered.
267
*/
268
skipRouting?: boolean;
269
}
270
```
271
272
**Usage Examples:**
273
274
```typescript
275
// Soft navigation (default) - uses HTML5 history API
276
router.navigate('/new-path');
277
278
// Hard navigation - causes full page reload
279
router.navigate('/new-path', { hard: true });
280
281
// Skip routing - just update URL without triggering commands
282
router.navigate('/new-path', { skipRouting: true });
283
284
// Combined options
285
router.navigate('/external-link', {
286
hard: true,
287
skipRouting: false
288
});
289
```
290
291
### Route Registration
292
293
System for mapping URL patterns to commands with configurable priority.
294
295
```typescript { .api }
296
/**
297
* The specification for registering a route with the router
298
*/
299
interface IRouter.IRegisterOptions {
300
/** The command string that will be invoked upon matching */
301
command: string;
302
303
/** The regular expression that will be matched against URLs */
304
pattern: RegExp;
305
306
/**
307
* The rank order of the registered rule. A lower rank denotes a higher
308
* priority. The default rank is `100`.
309
*/
310
rank?: number;
311
}
312
```
313
314
**Usage Examples:**
315
316
```typescript
317
// High priority route (lower rank = higher priority)
318
router.register({
319
command: 'app:admin-panel',
320
pattern: /^\/admin\/(.*)$/,
321
rank: 1
322
});
323
324
// Medium priority route
325
router.register({
326
command: 'app:open-notebook',
327
pattern: /^\/notebooks\/(.+\.ipynb)$/,
328
rank: 50
329
});
330
331
// Default priority route (rank defaults to 100)
332
router.register({
333
command: 'app:catch-all',
334
pattern: /^\/(.*)$/
335
});
336
337
// Route with capture groups
338
router.register({
339
command: 'app:user-profile',
340
pattern: /^\/users\/([^\/]+)(?:\/(.+))?$/,
341
rank: 25
342
});
343
344
// Command handler receives location as arguments
345
commands.addCommand('app:user-profile', {
346
execute: (args: IRouter.ILocation) => {
347
// Access path parts via regex match
348
const match = args.path.match(/^\/users\/([^\/]+)(?:\/(.+))?$/);
349
if (match) {
350
const [, username, subpath] = match;
351
console.log('User:', username, 'Subpath:', subpath);
352
}
353
}
354
});
355
```
356
357
### Constructor Options
358
359
Configuration for creating Router instances.
360
361
```typescript { .api }
362
namespace Router {
363
/**
364
* Constructor options for Router
365
*/
366
interface IOptions {
367
/** Base URL path for the router */
368
base: string;
369
370
/** Command registry to use for route commands */
371
commands: CommandRegistry;
372
}
373
}
374
```
375
376
**Usage Examples:**
377
378
```typescript
379
import { Router } from "@jupyterlab/application";
380
import { CommandRegistry } from "@lumino/commands";
381
382
// Basic router setup
383
const commands = new CommandRegistry();
384
const router = new Router({
385
base: window.location.pathname,
386
commands: commands
387
});
388
389
// Router with custom base
390
const labRouter = new Router({
391
base: '/lab',
392
commands: commands
393
});
394
395
// Router for development
396
const devRouter = new Router({
397
base: process.env.NODE_ENV === 'development' ? '/dev' : '/app',
398
commands: commands
399
});
400
```
401
402
### Advanced Routing Patterns
403
404
Complex routing scenarios and patterns for real-world applications.
405
406
```typescript { .api }
407
// Advanced routing patterns
408
interface AdvancedRoutingPatterns {
409
/** File path routing with extension matching */
410
fileRouting: RegExp; // /^\/files\/(.+\.(py|js|ts|md))$/
411
412
/** Nested resource routing */
413
nestedRouting: RegExp; // /^\/workspaces\/([^\/]+)\/files\/(.+)$/
414
415
/** Optional parameters */
416
optionalParams: RegExp; // /^\/search(?:\/(.+))?$/
417
418
/** Query parameter handling */
419
queryParams: RegExp; // /^\/results/
420
}
421
```
422
423
**Complete Routing Example:**
424
425
```typescript
426
import { Router, IRouter } from "@jupyterlab/application";
427
import { CommandRegistry } from "@lumino/commands";
428
429
// Complete routing setup
430
function setupRouting(commands: CommandRegistry): Router {
431
const router = new Router({
432
base: '/app',
433
commands: commands
434
});
435
436
// Define commands
437
commands.addCommand('app:home', {
438
execute: () => console.log('Home page')
439
});
440
441
commands.addCommand('app:open-file', {
442
execute: (args: IRouter.ILocation) => {
443
const match = args.path.match(/^\/files\/(.+)$/);
444
if (match) {
445
const filePath = decodeURIComponent(match[1]);
446
console.log('Opening file:', filePath);
447
}
448
}
449
});
450
451
commands.addCommand('app:search', {
452
execute: (args: IRouter.ILocation) => {
453
const searchParams = new URLSearchParams(args.search || '');
454
const query = searchParams.get('q') || '';
455
console.log('Searching for:', query);
456
}
457
});
458
459
// Register routes with priorities
460
const disposables = [
461
// High priority exact matches
462
router.register({
463
command: 'app:home',
464
pattern: /^\/$/,
465
rank: 1
466
}),
467
468
// Medium priority with parameters
469
router.register({
470
command: 'app:open-file',
471
pattern: /^\/files\/(.+)$/,
472
rank: 50
473
}),
474
475
router.register({
476
command: 'app:search',
477
pattern: /^\/search$/,
478
rank: 50
479
})
480
];
481
482
// Navigation helpers
483
const navigateToFile = (path: string) => {
484
router.navigate(`/files/${encodeURIComponent(path)}`);
485
};
486
487
const navigateToSearch = (query: string) => {
488
router.navigate(`/search?q=${encodeURIComponent(query)}`);
489
};
490
491
// Route change logging
492
router.routed.connect((sender, location) => {
493
console.log(`Routed to: ${location.path}`, location);
494
});
495
496
return router;
497
}
498
```
499
500
## Error Handling
501
502
Handling routing errors and edge cases.
503
504
```typescript
505
// Error handling in route commands
506
commands.addCommand('app:error-prone-route', {
507
execute: async (args: IRouter.ILocation) => {
508
try {
509
// Route handling that might fail
510
await handleRoute(args);
511
} catch (error) {
512
console.error('Route handling failed:', error);
513
// Navigate to error page or show notification
514
router.navigate('/error');
515
}
516
}
517
});
518
519
// Graceful fallback routing
520
router.register({
521
command: 'app:not-found',
522
pattern: /.*/, // Matches everything
523
rank: 1000 // Lowest priority (highest rank)
524
});
525
```