Startox Pilot is a JavaScript router designed for ease of use and flexibility. It employs regular expressions to offer dynamic routing, allowing for both straightforward and complex navigation paths. As a universal library, it works across different platforms without needing any external dependencies. This independence makes Startox Pilot a practical option for developers in search of a dependable routing tool that combines advanced features and modular design in a compact package.
- Installation
- A basic example
- Configuration Options
- Defining routes
- Dispatcher overview
- Navigation
- Form submission
- Have any questions
npm install @stratox/pilot
Below is a simple yet comprehensive example. Each component will be explored in further detail later in this guide.
import { Router, Dispatcher } from '@stratox/pilot';
const router = new Router();
const dispatcher = new Dispatcher();
// GET: example.se/
router.get('/', function() {
console.log("Start page");
});
// GET: example.se/#about
// REGULAR URI paths (example.se/about) are of course also supported!
router.get('/about', function(vars, request, path) {
const page = vars[0].pop();
console.log(`The current page is: ${page}`);
});
// GET: example.se/#articles/824/hello-world
router.get('/articles/{id:[0-9]+}/{slug:[^/]+}', function(vars, request, path) {
const id = vars.id.pop();
const slug = vars.slug.pop();
console.log(`Article post ID is: ${id} and post slug is: ${slug}.`);
});
// POST: example.se/#post/contact
router.post('/post/contact', function(vars, request, path) {
console.log(`Contact form catched with post:`, request.post);
});
// Will catch 404 and 405 HTTP Status Errors codes
// Not required you can also handle it directly in the dispatcher
router.get('[STATUS_ERROR]', function(vars, request, path, statusCode) {
if(statusCode === 404) {
console.log("404 Page not found", statusCode);
} else {
console.log("405 Method not allowed", statusCode);
}
});
dispatcher.dispatcher(router, dispatcher.serverParams("fragment"), function(response, statusCode) {
// response.controller is equal to what the routers second argument is being fed with.
// You can add Ajax here if you wish to trigger a ajax call.
response.controller(response.vars, response.request, response.path, statusCode);
});
// URI HASH: dispatcher.serverParams("fragment") // Fragment is HASH without "#" character.
// URI PATH: dispatcher.serverParams("path") // Regular URI path
// SCRIPT PATH: dispatcher.request("path") // Will work without browser window.history support
The dispatcher offers several configuration options to tailor its behavior to your application's needs.
const dispatcher = new Dispatcher({
catchForms: false, // Toggle form submission catching
root: "", // Set a root directory
fragmentPrefix: "" // Define a prefix for hash/fragment navigation
});
-
catchForms (bool): When set to
true
, enables the dispatcher to automatically intercept and route form submissions. This feature facilitates seamless integration of form-based navigation within your application. - root (string): This parameter allows you to specify a root directory using an absolute path. This setting is crucial for defining where simulated or "pretty" URI paths begin. The necessity of this configuration depends on your specific deployment environment.
- fragmentPrefix (string): This option lets you prepend a prefix to fragment or hash navigation calls. For instance, adding the "!" character means the URL's hash will be expected to appear as "#!your-hash", modifying the default behavior to accommodate specific routing schemes or to enhance compatibility with certain browsers or frameworks.
In Stratox Pilot, there are two primary router types: get
and post
. Both types follow the same structural format, as illustrated below, with the key difference being that they will expect different request (see navigation for more information)
router.get(string pattern, mixed call);
router.post(string pattern, mixed call);
- pattern (string): This parameter expects a URI path in the form of a string, which may include regular expressions for more complex matching criteria.
- call (mixed): This parameter can accept any data type, such as a callable, anonymous function, string, number, or boolean. However, it is most common to use a function. For the purposes of this guide, I use a regular callable function in my examples.
// Possible path: #about
router.get('/about', function(vars, request, path) {
});
And you can of course add multiple paths.
// Possible path: #about/contact
router.get('/about/contact', function(vars, request, path) {
});
To incorporate regular expressions in routing patterns, enclose the expression within curly brackets: {PATTERN}
. This syntax allows for flexible and powerful URL matching based on specified patterns.
// Possible path: #about/location/stockholm
router.get('/about/location/{[a-z]+}', function(vars, request, path) {
});
It is strongly advised to associate each URI path you wish to access with a specific key. This approach enhances the clarity and manageability of your route definitions.
// Possible path: #about/location/stockholm
router.get('/{page:about}/location/{city:[^/]+}', function(vars, request, path) {
//vars.page[0] is expected to be "about"
//vars.city[0] is expected to be any string value (stockholm, denmark, new-york) from passed URI.
});
You can also map an entire path to a key, allowing for more concise and organized route management.
// Possible path: #about/contact
router.get('/{page:about/location}', function(vars, request, path) {
//vars.page[0] is expected to be "about"
//vars.page[1] is expected to be "location"
});
Combining patterns with keywords e.g. (post-[0-9]+) enables you to create more expressive and versatile route definitions.
// Possible path: #articles/post-824/hello-world
router.get('/articles/{id:post-[0-9]+}/{slug:[^/]+}', function(vars, request, path) {
//vars.id[0] is expected to be "post-824"
//vars.slug[0] is expected to be "hello-world"
});
To accommodate an unlimited number of nested paths within your routing configuration, you can utilize the pattern ".+"
. However, it's strongly advised to precede such a router pattern with a specific prefix to maintain clarity and structure, as demonstrated in the example below with the prefix /shop
.
// Example of accessing a single category: #shop/furniture
// Example of accessing multiple nested categories: #shop/furniture/sofas/chesterfield
router.get('/shop/{category:.+}', function(vars, request, path) {
// Retrieves the last category segment from the path
const category = vars.category.pop();
console.log(`The current category is: ${category}`);
});
This approach allows for the dynamic handling of deeply nested routes under a common parent path, offering flexibility in how URLs are structured and processed.
To define one or more optional URI paths, enclose the path segment (excluding the slash) in brackets followed by a question mark, for example: /(PATH_NAME)?. This syntax allows for flexibility in route matching by making certain path segments non-mandatory.
// Possible path: #articles
// Possible path: #articles/post-824/hello-world
router.get('/articles/({id:post-[0-9]+})?/({slug:[^/]+})?', function(vars, request, path) {
});
It's important to note that you should not enclose the leading slash in brackets. The leading slash is automatically excluded from the pattern, ensuring the correct interpretation of the route.
There is an optional and special router pattern that let's you catch HTTP Status Errors with in a router.
router.get('[STATUS_ERROR]', function(vars, request, path, statusCode) {
if(statusCode === 404) {
console.log("404 Page not found", statusCode);
} else {
console.log("405 Method not allowed", statusCode);
}
});
The dispatcher is essential for identifying and providing the appropriate route from the state handler. Designed for flexibility, it enables the incorporation of custom logic, such as AJAX, to tailor functionality to specific needs.
dispatcher.dispatcher(Router routerCollection, serverParams, callable dispatch);
This expects a Router instance, allowing for customization. You can create your router collection extending the Router class, potentially adding more HTTP methods, structure, or functionality.
Server params indicate the URL segment the dispatcher should utilize. These params dynamically target the specified URI segment. Several built-in options include:
Represents the URL hash or anchor minus the "#" character.
dispatcher.serverParams("fragment");
The regular URI path segment.
dispatcher.serverParams("path");
Ideal for non-browser environments, supporting backend applications, APIs, or shell command routes.
dispatcher.request("path");
The "dispatch" argument expects a callable function to process the match result, handling both successful (status code 200) and error outcomes (status code 404 for "page not found" and 405 for "Method not allowed"). The function receives two parameters: response (object) and statusCode (int).
- response (object): Provides an object with vital response data.
- statusCode (int): Indicates the result, either successful (200) or error (404 or 405).
Below is an excerpt from the example at the start of the guide:
dispatcher.dispatcher(router, dispatcher.serverParams("fragment"), function(response, statusCode) {
response.controller(response.vars, response.request, response.path, statusCode);
});
The response structure, as illustrated with the router pattern "/{page:product}/{id:[0-9]+}/{slug:[^/]+}"
, and URI path /product/72/chesterfield includes:
{
"verb": "GET",
"status": 200,
"path": ["product", "72", "chesterfield"],
"vars": {
"page": "product",
"id": "72",
"slug": "chesterfield"
},
"form": {},
"request": {
"get": "URLSearchParams",
"post": {}
}
}
- verb: The HTTP method (GET or POST).
- status: The HTTP status code (200, 404, or 405).
- path: The URI path as an array.
- vars: An object mapping path segments to keys.
- form: Captures submitted DOM form elements.
- request.get: An instance of URLSearchParams for GET requests.
- request.post: An object with expected postdata
The library provides intuitive navigation options to seamlessly transition between pages and initiate GET or POST requests.
Initiating a GET request or navigating to a new page is straightforward. Such actions will correspond to a get
router, with the request parameter converting into an instance of URLSearchParams for the request.
- path (string): Specifies the URI, which can be a regular path or a hash.
- request (object): Sends a GET request or query string to the dispatcher. This will be transformed into an instance of URLSearchParams. When executed in a browser, the query string will also be appended to the URL in the address field.
// URI hash (fragment with hashtag) navigation
dispatcher.navigateTo("#articles/824/hello-world", { test: "A get request" });
// URI path navigation
// dispatcher.navigateTo("/articles/824/hello-world", { test: "A get request" });
The above navigation will trigger the result for the matching router:
// GET: example.se/?test=A+get+request#articles/824/hello-world
router.get('/articles/{id:[0-9]+}/{slug:[^/]+}', function(vars, request, path) {
const id = vars.id.pop();
const slug = vars.slug.pop();
const test = request.get.get("test"); // Get the query string/get request "test"
console.log(`Article ID: ${id}, Slug: ${slug} and GET Request ${test}.`);
});
Creating a POST request is similarly efficient, targeting a post
router. The request parameter will be a object.
- path (string): Defines the URI, which can be a regular path or a hash.
- request (object): Submits a POST request to the dispatcher. This will be an regular object.
dispatcher.postTo("#post/contact", { firstname: "John", lastname: "Doe" });
The above post will trigger the result for the matching router:
// POST: example.se/#post/contact
router.post('/post/contact', function(vars, request, path) {
const firstname = request.post.firstname;
const lastname = request.post.lastname;
console.log(`The post request, first name: ${firstname}, last name: ${lastname}`);
});
Stratox Pilot supports automatic form submission handling through routers, a feature that must be explicitly enabled in the Dispatcher's configuration.
To allow automatic catching and routing of form submissions, enable the catchForms
option in the Dispatcher configuration:
const dispatcher = new Dispatcher({
catchForms: true
});
Next, define the routes that will handle form submissions. For example, to handle a POST request:
// POST: example.se/#post/contact
router.post('/post/contact', function(vars, request, path) {
console.log('Contact form posted with form request:', request.post);
});
Forms can use both GET and POST methods. Below is an example of a form designed to submit via POST:
<form action="#post/contact" method="post">
<div>
<label>First name</label>
<input type="text" name="firstname" value="">
</div>
<div>
<label>Last name</label>
<input type="text" name="lastname" value="">
</div>
<div>
<label>E-mail</label>
<input type="email" name="email" value="">
</div>
<input type="submit" name="submit" value="Send">
</form>
With these settings, the dispatcher will automatically capture and route submissions to the corresponding handler if a matching route is found.
If there's anything unclear or you have further questions, feel free to reach out via email at daniel.ronkainen@wazabii.se.