sparouter v3.3.0
Spa Router v3
A router developed with TypeScript with :
- 2 modes : html5 history,hash
- page animation/transition with css or js
- active elements
- route guards
- child routes
- actions
Installation
npm i sparouter -S
Workflow
With TypeScript / es6
We could use a starter kit.
With es5
Its possible. Reference the lib in the main page.
<body>
<script src="node_modules/sparouter/dist/sparouter.js"></script>
<script src="src/app.js"></script>
</body>
new SpaRouter.Router().map([
{ path: "/", action: function () { return SpaRouter.render({ selector: "#main", template: "<h1>Home</h1>" }); } },
{ path: "**", redirectTo: "/" }
]).run();
Router & route configs
Router config | Description |
---|---|
mode | hash (by default) and html5 history. |
scroll | handle navigation to fragment (true by default) |
Route config | Description |
---|---|
path | the path pattern ("/posts" or "posts/:id" or "/posts/:id(a-z+)" for example) |
name | route name |
action | an action |
actions | an array of actions |
data | extra data to pass |
canActivate | route guards |
canDeactivate | route guards |
redirectTo | redirect to route url |
children | nested routes |
import { Router } from "sparouter";
const routes = [
{ path: "/", action: () => document.querySelector("#main").innerHTML = "<h1>Home</h1>" },
{ path: "/posts", action: () => render({ selector: "#main", templateUrl: "views/posts.html" }) },
{ path: "/posts/:id", canActivate: [MyGuard], action: ({route, router}) => console.log("Activate post details") },
{ path: "**", redirectTo: "/" },
];
new Router({
mode: "html5"
}).map(routes).run((route) => {
// on route change success
}, (err) => {
// route change error ("aborted" with a guard or "notfound" if no matched route found)
});
With html5 history mode (uris without '#'), the server have to redirect to index page.
The base tag with html5 history mode allow to set the base path. Examples:
<base href="/"/>
or
<base href="http://mysite.com/blog/"/>
Param regex (number by default)
Example:
const routes =[
{ path: "/posts/:id([a-z]+)", /* etc. */ }
];
Named routes:
const routes = [
{ name: "home", path: "/", /* etc. */ },
{ name: "posts", path: "/posts", /* etc. */ }
];
Route with actions (array of functions)
const routes = [
{
path: "/",
actions: [
() => document.querySelector("#main").innerHTML = "<h1>Home</h1>",
({ route, router }) => console.log("Activate home", route, router),
/* other actions */
]
}
];
Its possible to pass an "action result" to the next action
const routes = [
{
path: "/", actions: [
() => { return ["a", "b", "c"]; },
({ result, router }) => { console.log(result); }
]
}
];
... Or with a promise
const routes = [
{
path: "/", actions: [
() => {
return new Promise((resolve) => {
resolve(["a", "b", "c"]);
});
},
({ result, router }) => { console.log(result); }
]
}
];
children
const routes = [
{ path: "/", templateUrl: "src/views/home.html" },
{
path: "posts",
children: [
{ path: "", action: () => { /* do something */ } },
{ path: ":id", actions: [ /* do things */ ] }
]
}
];
Links
With hash mode
<a href="#/">Home</a>
<a href="#/posts">Posts</a>
<a href="#/posts/10">With parameter</a>
<a href="#/posts/10?q=news#section1">Query and fragment</a>
With to attribute: the best way to switch easilly between "hash" and "html5 history"
<a to="/">Home</a>
<a to="/posts">Posts</a>
<a to="/posts/10">With parameter</a>
<a to="/posts/10?q=news#section1">Query and fragment</a>
Active attributes
- active-class the css class to add if active
<a href="/posts" active-class="active">Posts</a>
.active {
color:red
}
- active-path allow to set a regex pattern or to add on any element ("li" for example)
<li active-path="/posts" active-class="active"></li>
- active-exact the css class is only added if path + query + fragment equal to link href or active-path
<a href="/posts/10?q=abc#section1" active-class="active" active-exact="true">Details</a>
<!-- with active-path -->
<a href="/posts/10?q=abc#section1" active-path="/c/([a-z]+)\\?q=10#section1" active-class="active" active-exact="true">Details</a>
Navigate programmatically
Navigate by route name
router.navigateTo("home");
// with parameter
router.navigateTo("post-detail",{ id: 10});
// with query and fragment
router.navigateTo("post-detail",{ id: 10},{ q: "news" },"section1");
Navigate by url
router.navigateToUrl("/");
// with parameter
router.navigateToUrl("/posts/10");
// with query and fragment
router.navigateToUrl("/posts/10?q=news#section1");
Go back
router.goBack();
Go forward
router.goForward();
render function
Allow to render content in an HTMLElement, and create an instance of a vm and pass args.
import { render } from "sparouter";
class PostDetail {
onActivate(route, router,scope) {
// route with params, query, fragment and data
}
}
const routes = [
{ path: "/posts", action: () => render({ selector: "#main", templateUrl: "views/posts.html" }) },
{ path: "/posts/:id", action: ({ route, router }) => render({ selector: "#main", templateUrl: "views/post-detail.html", vm: PostDetail, args: [route, router] }) }
];
Async await or promises
Allow to wait the end of the action before reach the next
Example with async await
function doSomething() {
return new Promise((resolve) => {
setTimeout(function () {
console.log("Completed");
resolve();
}, 5000);
});
}
const routes = [{ path: "/", action: async() => {
await doSomething();
}}];
Example with promise
const routes = [{
path: "/", action: () => {
return new Promise((resolve) => {
setTimeout(function () {
console.log("Completed");
resolve();
}, 5000);
});
}
}];
Page transition
with navigate function
Animation "leave" and "enter" (could be played simultaneously)
Example simple , a slide in / slide out
const routes = [
{ path: "/", action: () => navigate({ selector: "#main", template: "<h1>Home</h1>", enter: "slideInRight", leave: "slideOutLeft" }) }
];
Other example "Shuffle" on the container and
const routes = [
{ path: "/", action: () => navigate({ selector: "#main", template: "<h1>Home</h1>", enter: "navInPrev", leave: "navOutPrev", simultaneous: true }) }
];
Before each and after each
Usefull for page animations with javaScript (SVG for example)
var router = new SpaRouter.Router().map(routes).beforeEach((next) => {
next();
}).afterEach(() => {
}).run();
Route guards
class PostDetail {
checkDeactivate() {
return confirm("Leave this page?");
}
}
class MyGuard implements CanActivate, CanDeactivate {
canActivate(route, next) {
let result = confirm("Navigate?");
next(result);
}
canDeactivate(activeVms, route, next) {
let vm = activeVms["PostDetail"];
let result = vm && vm.checkDeactivate ? vm.checkDeactivate() : true;
next(result);
}
}
Example route with guard:
const routes = [
{ path:"/posts/:id", canActivate: [MyGuard], canDeactivate: [MyGuard], /* etc. */ }
]);
Or register with injector
injector.registerSecure("MyGuard",MyGuard);
const routes = [
{ path:"/posts/:id", canActivate: ["MyGuard"], /* etc. */ }
]);
injector
Allow to inject services
Example
Create and register a service
function MyService() {
this.getAll = function () {
// return some data
}
}
injector.register("MyService", MyService);
Inject the service
function MyVM(myService) { }
injector.register("MyVM", MyVM, ["MyService"]);
Register a secure service (service is not returned with getInstance/ getNewInstance and cannot be removed)
injector.registerSecure("MySecureService", MySecureService);
Chaining registrations
injector
.register("MyService1", MyService1)
.register("MyService2", MyService2);
Get an instance (create or get a cached instance)
let instance = injector.getInstance("MyService");
Get a new instance
let instance = injector.getNewInstance("MyService");
Invoke a function with Injector
injector.invoke(myFunc);
View usefull functions
Allow to select and animate HTML elements.
import { qs, qsa } from "sparouter";
qs(".box").changeContent("<h1>New content</h1>");
qs(".box").animate("fadeIn",() => {
// completed
});
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago