Skip to content

Commit

Permalink
Merge pull request #244 from grahammendick/fluent-link
Browse files Browse the repository at this point in the history
  • Loading branch information
grahammendick authored May 20, 2019
2 parents 6437e53 + 7a86fdc commit 21bf7b1
Show file tree
Hide file tree
Showing 10 changed files with 978 additions and 33 deletions.
38 changes: 27 additions & 11 deletions Navigation/src/FluentNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ interface FluentNavigator {
}

function createFluentNavigator(states: { [index: string]: State }, stateHandler: StateHandler, stateContext = new StateContext()): FluentNavigator {
function navigateLink(url): FluentNavigator {
var { state, data } = stateHandler.parseLink(url);
var { [state.crumbTrailKey]: crumbs, ...data } = data;
function getCrumbTrail(state: State, navigationData: any, crumbs: Crumb[], nextCrumb: Crumb): Crumb[] {
if (!state.trackCrumbTrail)
return [];
crumbs = crumbs.slice();
if (nextCrumb)
crumbs.push(nextCrumb);
return state.truncateCrumbTrail(state, navigationData, crumbs);
}

function navigateLink(state: State, data: any, crumbs: Crumb[], url: string): FluentNavigator {
var fluentContext = new StateContext();
fluentContext.state = state;
fluentContext.url = url;
Expand All @@ -26,28 +33,37 @@ function createFluentNavigator(states: { [index: string]: State }, stateHandler:
return {
url: stateContext.url,
navigate: function(stateKey: string, navigationData?: any): FluentNavigator {
if (!states[stateKey])
var state = states[stateKey];
var {crumbs, nextCrumb} = stateContext;
if (!state)
throw new Error(stateKey + ' is not a valid State');
if (typeof navigationData === 'function')
navigationData = navigationData(stateContext.data);
var url = stateHandler.getLink(states[stateKey], navigationData, stateContext.crumbs, stateContext.nextCrumb);
var url = stateHandler.getLink(state, navigationData, crumbs, nextCrumb);
if (url == null)
throw new Error('Invalid route data, a mandatory route parameter has not been supplied a value');
return navigateLink(url);
var data = { ...state.defaults, ...navigationData };
var crumbs = getCrumbTrail(state, data, crumbs, nextCrumb);
return navigateLink(state, data, crumbs, url);
},
navigateBack: function(distance: number): FluentNavigator {
if (!(distance <= stateContext.crumbs.length && distance > 0))
var {crumbs} = stateContext;
if (!(distance <= crumbs.length && distance > 0))
throw new Error('The distance parameter must be greater than zero and less than or equal to the number of Crumbs (' + stateContext.crumbs.length + ')');
var url = stateContext.crumbs[stateContext.crumbs.length - distance].url;
return navigateLink(url);
var {state, data, url} = crumbs[crumbs.length - distance];
var crumbs = crumbs.slice(0, crumbs.length - distance);
return navigateLink(state, data, crumbs, url);
},
refresh: function(navigationData?: any): FluentNavigator {
var {state, crumbs, nextCrumb} = stateContext;
if (typeof navigationData === 'function')
navigationData = navigationData(stateContext.data);
var url = stateHandler.getLink(stateContext.state, navigationData, stateContext.crumbs, stateContext.nextCrumb);
var url = stateHandler.getLink(state, navigationData, crumbs, nextCrumb);
if (url == null)
throw new Error('Invalid route data, a mandatory route parameter has not been supplied a value');
return navigateLink(url);
var data = { ...state.defaults, ...navigationData };
var crumbs = getCrumbTrail(state, data, crumbs, nextCrumb);
return navigateLink(state, data, crumbs, url);
}
}
}
Expand Down
202 changes: 194 additions & 8 deletions Navigation/test/NavigationDataTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,130 @@ describe('Navigation Data', function () {
}
});

describe('Wizard Data Defaults', function() {
var stateNavigator: StateNavigator;
beforeEach(function() {
stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1', trackCrumbTrail: true, defaults: { b: true } },
{ key: 's2', route: 'r2', trackCrumbTrail: true }
]);
});
var data = {
s: 'Hello',
n: 5
};

describe('Navigate', function() {
beforeEach(function() {
stateNavigator.navigate('s0');
stateNavigator.navigate('s1', data);
stateNavigator.navigate('s2', stateNavigator.stateContext.includeCurrentData(null));
});
test();
});

describe('Navigate Link', function() {
beforeEach(function() {
var link = stateNavigator.getNavigationLink('s0');
stateNavigator.navigateLink(link);
link = stateNavigator.getNavigationLink('s1', data);
stateNavigator.navigateLink(link);
link = stateNavigator.getNavigationLink('s2', stateNavigator.stateContext.includeCurrentData(null));
stateNavigator.navigateLink(link);
});
test();
});

describe('Fluent Navigate', function() {
beforeEach(function() {
var link = stateNavigator.fluent()
.navigate('s0')
.navigate('s1', data)
.navigate('s2', currentData => currentData)
.url;
stateNavigator.navigateLink(link);
});
test();
});

function test() {
it('should populate data', function () {
assert.strictEqual(stateNavigator.stateContext.previousData['s'], 'Hello');
assert.strictEqual(stateNavigator.stateContext.previousData['n'], 5);
assert.strictEqual(stateNavigator.stateContext.previousData['b'], true);
assert.strictEqual(stateNavigator.stateContext.crumbs[1].data['s'], 'Hello');
assert.strictEqual(stateNavigator.stateContext.crumbs[1].data['n'], 5);
assert.strictEqual(stateNavigator.stateContext.crumbs[1].data['b'], true);
assert.strictEqual(stateNavigator.stateContext.data['s'], 'Hello');
assert.strictEqual(stateNavigator.stateContext.data['n'], 5);
assert.strictEqual(stateNavigator.stateContext.data['b'], true);
});
}
});

describe('Wizard Refresh Data Defaults', function() {
var stateNavigator: StateNavigator;
beforeEach(function() {
stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1', trackCrumbTrail: true, defaults: { b: true } },
{ key: 's2', route: 'r2', trackCrumbTrail: true }
]);
});
var data = {
s: 'Hello',
n: 5
};

describe('Navigate', function() {
beforeEach(function() {
stateNavigator.navigate('s1');
stateNavigator.refresh(data);
stateNavigator.navigate('s2', stateNavigator.stateContext.includeCurrentData(null));
});
test();
});

describe('Navigate Link', function() {
beforeEach(function() {
var link = stateNavigator.getNavigationLink('s1');
stateNavigator.navigateLink(link);
link = stateNavigator.getRefreshLink(data);
stateNavigator.navigateLink(link);
link = stateNavigator.getNavigationLink('s2', stateNavigator.stateContext.includeCurrentData(null));
stateNavigator.navigateLink(link);
});
test();
});

describe('Fluent Navigate', function() {
beforeEach(function() {
var link = stateNavigator.fluent()
.navigate('s1')
.refresh(data)
.navigate('s2', currentData => currentData)
.url;
stateNavigator.navigateLink(link);
});
test();
});

function test() {
it('should populate data', function () {
assert.strictEqual(stateNavigator.stateContext.previousData['s'], 'Hello');
assert.strictEqual(stateNavigator.stateContext.previousData['n'], 5);
assert.strictEqual(stateNavigator.stateContext.previousData['b'], true);
assert.strictEqual(stateNavigator.stateContext.crumbs[1].data['s'], 'Hello');
assert.strictEqual(stateNavigator.stateContext.crumbs[1].data['n'], 5);
assert.strictEqual(stateNavigator.stateContext.crumbs[1].data['b'], true);
assert.strictEqual(stateNavigator.stateContext.data['s'], 'Hello');
assert.strictEqual(stateNavigator.stateContext.data['n'], 5);
assert.strictEqual(stateNavigator.stateContext.data['b'], true);
});
}
});

describe('Transition Transition', function() {
var stateNavigator: StateNavigator;
beforeEach(function() {
Expand Down Expand Up @@ -2192,6 +2316,66 @@ describe('Navigation Data', function () {
}
});

describe('Refresh Individual Data Custom Trail', function() {
var stateNavigator: StateNavigator;
beforeEach(function() {
stateNavigator = new StateNavigator([
{ key: 's', route: 'r', trackCrumbTrail: true }
]);
var state = stateNavigator.states['s'];
state.truncateCrumbTrail = (state, data, crumbs) => {
if (data['string'] === 'Hello' && data['boolean'] === true
&& data['number'] === 0 && +data['date'] === +new Date(2010, 3, 7))
return crumbs;
return [];
};
});
var individualNavigationData = {};
individualNavigationData['string'] = 'Hello';
individualNavigationData['boolean'] = true;
individualNavigationData['number'] = 0;
individualNavigationData['date'] = new Date(2010, 3, 7);

describe('Navigate', function() {
beforeEach(function() {
stateNavigator.navigate('s');
stateNavigator.refresh();
stateNavigator.refresh(individualNavigationData);
});
test();
});

describe('Navigate Link', function() {
beforeEach(function() {
var link = stateNavigator.getNavigationLink('s');
stateNavigator.navigateLink(link);
var link = stateNavigator.getRefreshLink();
stateNavigator.navigateLink(link);
link = stateNavigator.getRefreshLink(individualNavigationData);
stateNavigator.navigateLink(link);
});
test();
});

describe('Fluent Navigate', function() {
beforeEach(function() {
var link = stateNavigator.fluent()
.navigate('s')
.refresh()
.refresh(individualNavigationData)
.url;
stateNavigator.navigateLink(link);
});
test();
});

function test() {
it('should populate crumb trail', function() {
assert.equal(stateNavigator.stateContext.crumbs.length, 1);
});
}
});

describe('Array Data Custom Trail', function() {
var stateNavigator: StateNavigator;
beforeEach(function() {
Expand Down Expand Up @@ -5575,18 +5759,18 @@ describe('Navigation Data', function () {

describe('Navigate', function() {
beforeEach(function() {
stateNavigator.navigate('s0');
stateNavigator.navigate('s1', data);
stateNavigator.navigate('s1');
stateNavigator.refresh(data);
stateNavigator.refresh(stateNavigator.stateContext.includeCurrentData(null));
});
test();
});

describe('Navigate Link', function() {
beforeEach(function() {
var link = stateNavigator.getNavigationLink('s0');
var link = stateNavigator.getNavigationLink('s1');
stateNavigator.navigateLink(link);
link = stateNavigator.getNavigationLink('s1', data);
link = stateNavigator.getRefreshLink(data);
stateNavigator.navigateLink(link);
link = stateNavigator.getRefreshLink(stateNavigator.stateContext.includeCurrentData(null));
stateNavigator.navigateLink(link);
Expand All @@ -5597,8 +5781,8 @@ describe('Navigation Data', function () {
describe('Fluent Navigate', function() {
beforeEach(function() {
var link = stateNavigator.fluent()
.navigate('s0')
.navigate('s1', data)
.navigate('s1')
.refresh(data)
.refresh((currentData) => currentData)
.url;
stateNavigator.navigateLink(link);
Expand Down Expand Up @@ -6899,7 +7083,8 @@ describe('Navigation Data', function () {

describe('Fluent Navigate', function() {
it('should throw error', function() {
assert.throws(() => stateNavigator.fluent().navigate('s', individualNavigationData), /The Url .+ is invalid/);
var link = stateNavigator.fluent().navigate('s', individualNavigationData).url;
assert.throws(() => stateNavigator.navigateLink(link), /The Url .+ is invalid/);
});
});
});
Expand Down Expand Up @@ -6939,7 +7124,8 @@ describe('Navigation Data', function () {

describe('Fluent Navigate', function() {
it('should throw error', function() {
assert.throws(() => stateNavigator.fluent().navigate('s', arrayNavigationData), /The Url .+ is invalid/);
var link = stateNavigator.fluent().navigate('s', arrayNavigationData).url;
assert.throws(() => stateNavigator.navigateLink(link), /The Url .+ is invalid/);
});
});
});
Expand Down
16 changes: 16 additions & 0 deletions NavigationReact/src/FluentLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import LinkUtility from './LinkUtility';
import withStateNavigator from './withStateNavigator';
import { FluentLinkProps } from './Props';
import * as React from 'react';

var FluentLink = (props: FluentLinkProps) => {
var htmlProps = LinkUtility.toHtmlProps(props);
var { withContext = false, navigate, stateNavigator } = props;
try {
var link = navigate(stateNavigator.fluent(withContext)).url;
} catch {}
htmlProps.href = link && stateNavigator.historyManager.getHref(link);
htmlProps.onClick = link && LinkUtility.getOnClick(stateNavigator, props, link);
return <a {...htmlProps} />;
}
export default withStateNavigator(FluentLink);
3 changes: 2 additions & 1 deletion NavigationReact/src/LinkUtility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class LinkUtility {
if (key !== 'stateNavigator' && key !== 'stateKey' && key !== 'navigationData'
&& key !== 'includeCurrentData' && key !== 'currentDataKeys'&& key !== 'activeStyle'
&& key !== 'activeCssClass' && key !== 'disableActive' && key !== 'distance'
&& key !== 'historyAction' && key !== 'navigating' && key !== 'defer')
&& key !== 'historyAction' && key !== 'navigating' && key !== 'navigate'
&& key !== 'withContext' && key !== 'defer')
htmlProps[key] = props[key];
}
return htmlProps;
Expand Down
3 changes: 2 additions & 1 deletion NavigationReact/src/NavigationReact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import NavigationHandler from './NavigationHandler';
import NavigationBackLink from './NavigationBackLink';
import NavigationLink from './NavigationLink';
import RefreshLink from './RefreshLink';
import FluentLink from './FluentLink';

export { AsyncStateNavigator, NavigationContext, NavigationHandler, NavigationBackLink, NavigationLink, RefreshLink };
export { AsyncStateNavigator, NavigationContext, NavigationHandler, NavigationBackLink, NavigationLink, RefreshLink, FluentLink };
8 changes: 7 additions & 1 deletion NavigationReact/src/Props.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AsyncStateNavigator from './AsyncStateNavigator';
import { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent } from 'react';
import { FluentNavigator } from 'navigation';

interface LinkProps extends DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> {
historyAction?: 'add' | 'replace' | 'none';
Expand All @@ -25,4 +26,9 @@ interface NavigationBackLinkProps extends LinkProps {
distance: number;
}

export { LinkProps, RefreshLinkProps, NavigationLinkProps, NavigationBackLinkProps }
interface FluentLinkProps extends LinkProps {
withContext?: boolean;
navigate: (fluentNavigator: FluentNavigator) => FluentNavigator;
}

export { LinkProps, RefreshLinkProps, NavigationLinkProps, NavigationBackLinkProps, FluentLinkProps }
Loading

0 comments on commit 21bf7b1

Please sign in to comment.