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

ActiveLink that applies classes when the outlet is active #33

Merged
merged 6 commits into from
Aug 16, 2018
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
1,980 changes: 990 additions & 990 deletions package-lock.json

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions src/routing/ActiveLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import WidgetBase from '../widget-core/WidgetBase';
import { WNode } from '../widget-core/interfaces';
import { w } from '../widget-core/d';
import diffProperty from '../widget-core/decorators/diffProperty';
import { LinkProperties } from './interfaces';
import Link from './Link';
import Router from './Router';
import { Handle } from '../shim/interfaces';

export interface ActiveLinkProperties extends LinkProperties {
activeClasses: string[];
}

export class ActiveLink extends WidgetBase<ActiveLinkProperties> {
private _outletHandle: Handle | undefined;

private _renderLink(isActive = false) {
let { activeClasses, classes = [], ...props } = this.properties;
classes = Array.isArray(classes) ? classes : [classes];
if (isActive) {
classes = [...classes, ...activeClasses];
}
props = { ...props, classes };
return w(Link, props);
}

@diffProperty('to')
protected _onOutletPropertyChange(previous: ActiveLinkProperties, current: ActiveLinkProperties) {
const { to, routerKey = 'router' } = current;
const item = this.registry.getInjector<Router>(routerKey);
if (this._outletHandle) {
this._outletHandle.destroy();
this._outletHandle = undefined;
}
if (item) {
const router = item.injector();
this._outletHandle = router.on('outlet', ({ outlet }) => {
if (outlet === to) {
this.invalidate();
}
});
}
}

protected render(): WNode {
const { to, routerKey = 'router' } = this.properties;
const item = this.registry.getInjector<Router>(routerKey);
if (!item) {
return this._renderLink();
}
const router = item.injector();
return this._renderLink(!!router.getOutlet(to));
}
}

export default ActiveLink;
68 changes: 26 additions & 42 deletions src/routing/Link.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,39 @@
import { WidgetBase } from '../widget-core/WidgetBase';
import { v } from '../widget-core/d';
import { inject } from '../widget-core/decorators/inject';
import { Constructor, DNode, VNodeProperties } from '../widget-core/interfaces';
import { VNode } from '../widget-core/interfaces';
import { LinkProperties } from './interfaces';
import { Router } from './Router';

const getProperties = (router: Router, properties: LinkProperties): VNodeProperties => {
const { to, isOutlet = true, params = {}, onClick, ...props } = properties;
const href = isOutlet ? router.link(to, params) : to;

const handleOnClick = (event: MouseEvent) => {
onClick && onClick(event);

if (!event.defaultPrevented && event.button === 0 && !properties.target) {
event.preventDefault();
href !== undefined && router.setPath(href);
export class Link extends WidgetBase<LinkProperties> {
private _getProperties() {
let { routerKey = 'router', to, isOutlet = true, target, params = {}, onClick, ...props } = this.properties;
const item = this.registry.getInjector<Router>(routerKey);
let href: string | undefined = to;

if (item) {
const router = item.injector();
if (isOutlet) {
href = router.link(href, params);
}
const onclick = (event: MouseEvent) => {
onClick && onClick(event);

if (!event.defaultPrevented && event.button === 0 && !target) {
event.preventDefault();
href !== undefined && router.setPath(href);
}
};
props = { ...props, onclick, href };
} else {
props = { ...props, href };
}
};
return {
href,
onClick: handleOnClick,
...props
};
};

export class BaseLink extends WidgetBase<LinkProperties> {
private _onClick(event: MouseEvent): void {
this.properties.onClick && this.properties.onClick(event);
return props;
}

protected render(): DNode {
const props = {
...this.properties,
onclick: this._onClick,
onClick: undefined,
to: undefined,
isOutlet: undefined,
params: undefined,
routerKey: undefined,
router: undefined
};
return v('a', props, this.children);
protected render(): VNode {
return v('a', this._getProperties(), this.children);
}
}

export function createLink(routerKey: string): Constructor<BaseLink> {
@inject({ name: routerKey, getProperties })
class Link extends BaseLink {}
return Link;
}

export const Link = createLink('router');

export default Link;
15 changes: 15 additions & 0 deletions src/routing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Routing for Dojo applications.
- [Outlet Options](#outlet-options)
- [Global Error Outlet](#global-error-outlet)
- [Link](#link)
- [ActiveLink](#activelink)

<!-- end-github-only -->

Expand Down Expand Up @@ -342,6 +343,20 @@ render() {

All the standard `VNodeProperties` are available for the `Link` component as they would be creating an `a` DOM Element using `v()` with `@dojo/widget-core`.

### ActiveLink

The `ActiveLink` component is a wrapper around the `Link` component that conditionally sets classes on the `a` node if the link is currently active

```ts
import { ActiveLink } from '@dojo/framework/routing/ActiveLink';

render() {
return v('div', [
w(ActiveLink, { to: 'foo', params: { foo: 'bar' }, activeClasses: [ 'link-active' ]}, [ 'Link Text' ])
]);
}
```

<!-- doc-viewer-config
{
"api": "docs/routing/api.json"
Expand Down
19 changes: 18 additions & 1 deletion src/routing/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ export interface NavEvent extends EventObject<string> {
context: OutletContext;
}

export class Router extends QueuingEvented<{ nav: NavEvent }> implements RouterInterface {
export interface OutletEvent extends EventObject<string> {
outlet: string;
action: 'enter' | 'exit';
}

export class Router extends QueuingEvented<{ nav: NavEvent; outlet: OutletEvent }> implements RouterInterface {
private _routes: Route[] = [];
private _outletMap: { [index: string]: Route } = Object.create(null);
private _matchedOutlets: { [index: string]: OutletContext } = Object.create(null);
Expand Down Expand Up @@ -200,6 +205,7 @@ export class Router extends QueuingEvented<{ nav: NavEvent }> implements RouterI
*/
private _onChange = (requestedPath: string): void => {
this.emit({ type: 'navstart' });
const previousMatchedOutlets = this._matchedOutlets;
this._matchedOutlets = Object.create(null);
this._currentParams = {};
requestedPath = this._stripLeadingSlash(requestedPath);
Expand Down Expand Up @@ -245,6 +251,10 @@ export class Router extends QueuingEvented<{ nav: NavEvent }> implements RouterI
if (routeMatch === true) {
previousOutlet = route.outlet;
routeMatched = true;
if (!previousMatchedOutlets[route.outlet]) {
this.emit({ type: 'outlet', outlet: route.outlet, action: 'enter' });
}

this._matchedOutlets[route.outlet] = {
queryParams: this._currentQueryParams,
params: { ...this._currentParams },
Expand All @@ -265,6 +275,13 @@ export class Router extends QueuingEvented<{ nav: NavEvent }> implements RouterI
segments = [...segmentsForRoute];
}
}

const previousMatchedOutletKeys = Object.keys(previousMatchedOutlets);
for (let i = 0; i < previousMatchedOutletKeys.length; i++) {
if (!this._matchedOutlets[previousMatchedOutletKeys[i]]) {
this.emit({ type: 'outlet', outlet: previousMatchedOutletKeys[i], action: 'exit' });
}
}
if (routeMatched === false) {
this._matchedOutlets.errorOutlet = {
queryParams: this._currentQueryParams,
Expand Down
1 change: 1 addition & 0 deletions src/routing/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export type Outlet<
*/
export interface LinkProperties extends VNodeProperties {
key?: string;
routerKey?: string;
isOutlet?: boolean;
params?: Params;
onClick?: (event: MouseEvent) => void;
Expand Down
Loading