Skip to content

Commit

Permalink
fix(history): rewrite for Cycle Unified
Browse files Browse the repository at this point in the history
BREAKING CHANGES:
New API. You no longer need createHistory() calls that
create history objects to be passed to makeHistoryDriver(). You simply
call makeHistoryDriver() and those history objects are created under the
hood.
  • Loading branch information
staltz committed Jan 28, 2017
1 parent 16d394d commit d41bdae
Show file tree
Hide file tree
Showing 16 changed files with 873 additions and 581 deletions.
24 changes: 8 additions & 16 deletions history/package.json
Expand Up @@ -17,35 +17,27 @@
"history": "^4.3.0"
},
"devDependencies": {
"@cycle/base": "^4.1.1",
"@cycle/dom": "^13.0.0",
"@cycle/most-adapter": "^4.0.1",
"@cycle/most-run": "^4.1.3",
"@cycle/rxjs-adapter": "^3.0.3",
"@cycle/rxjs-run": "^3.0.3",
"@cycle/xstream-adapter": "^3.0.4",
"@cycle/xstream-run": "^3.1.0",
"@cycle/dom": "15.0.0-rc.1",
"@cycle/rxjs-run": "4.0.0-rc.3",
"@cycle/run": "1.0.0-rc.7",
"@types/history": "^4.5.0",
"@types/mocha": "^2.2.32",
"@types/node": "^6.0.46",
"most": "^1.0.4",
"rxjs": "5.0.1",
"saucie": "^1.4.1",
"ts-node": "^1.6.1",
"xstream": "9.x.x"
"xstream": "10.x.x"
},
"scripts": {
"lint": "../node_modules/.bin/tslint -c ../tslint.json src/**/*.ts",
"test-node": "../node_modules/.bin/mocha -r ts-node/register test/node/*.ts",
"build-browser-tests": "../node_modules/.bin/browserify -p tsify test/browser/index.ts -o test/browser/bundle.js",
"test-browser": "../node_modules/.bin/testem ci -l Chrome,Firefox",
"prelib": "rm -rf lib/ && mkdir -p lib",
"lib": "../node_modules/.bin/tsc",
"test-node": "../node_modules/.bin/mocha -r ts-node/register test/node/index.ts",
"test-browser": "../node_modules/.bin/testem -l Chrome",
"test-browser-ci": "../node_modules/.bin/testem ci",
"test": "npm run lint && npm run lib && npm run test-node && npm run test-browser",
"test-ci": "npm run lint && npm run lib && npm run test-node && npm run test-browser-ci",
"browserify": "../node_modules/.bin/browserify lib/index.js --standalone CycleHistory --outfile dist/cycle-history.js",
"uglify": "../node_modules/.bin/uglifyjs dist/cycle-history.js -o dist/cycle-history.min.js",
"prelib": "rm -rf lib/ && mkdir -p lib",
"lib": "../node_modules/.bin/tsc",
"predist": "rm -rf dist/ && mkdir -p dist/",
"dist": "npm run lib && npm run browserify && npm run uglify",
"readme": "node ../.scripts/make-api-docs.js ${PWD##*/} && cat ./.scripts/template-readme.md ./generated-api.md > README.md && rm ./generated-api.md",
Expand Down
43 changes: 25 additions & 18 deletions history/src/captureClicks.ts
@@ -1,7 +1,17 @@
import { StreamAdapter } from '@cycle/base';

const clickEvent = 'undefined' !== typeof document && document.ontouchstart ?
'touchstart' : 'click';
import xs, {Stream, MemoryStream} from 'xstream';
import {
HistoryInput,
HistoryDriver,
GoBackHistoryInput,
GoForwardHistoryInput,
GoHistoryInput,
PushHistoryInput,
ReplaceHistoryInput,
} from './types';

const CLICK_EVENT = typeof document !== 'undefined' && document.ontouchstart ?
'touchstart' :
'click';

function which(ev: any) {
if (typeof window === 'undefined') {
Expand All @@ -19,8 +29,8 @@ function sameOrigin(href: string) {
return href && href.indexOf(window.location.origin) === 0;
}

function makeClickListener(push: Function) {
return function clickListener(event: any) {
function makeClickListener(push: (p: string) => void) {
return function clickListener(event: MouseEvent) {
if (which(event) !== 1) {
return;
}
Expand All @@ -33,7 +43,7 @@ function makeClickListener(push: Function) {
return;
}

let element = event.target;
let element: any = event.target;
while (element && element.nodeName !== 'A') {
element = element.parentNode;
}
Expand Down Expand Up @@ -61,23 +71,20 @@ function makeClickListener(push: Function) {
};
}

function captureAnchorClicks(push: Function) {
function captureAnchorClicks(push: (p: string) => void) {
const listener = makeClickListener(push);
if (typeof window !== 'undefined') {
document.addEventListener(clickEvent, listener, false);
document.addEventListener(CLICK_EVENT, listener, false);
}
}

export function captureClicks(historyDriver: (sink$: any, runStreamAdapter: StreamAdapter) => any) {
return function historyDriverWithClickCapture(sink$: any, runStreamAdapter: StreamAdapter): any {
const { observer, stream } = runStreamAdapter.makeSubject();

export function captureClicks(historyDriver: HistoryDriver): HistoryDriver {
return function historyDriverWithClickCapture(sink$: Stream<HistoryInput | string>) {
const internalSink$ = xs.create<HistoryInput | string>();
captureAnchorClicks((pathname: string) => {
observer.next({ type: 'push', pathname });
internalSink$._n({type: 'push', pathname});
});

runStreamAdapter.streamSubscribe(sink$, observer);

return historyDriver(stream, runStreamAdapter);
sink$._add(internalSink$);
return historyDriver(internalSink$);
};
}
48 changes: 20 additions & 28 deletions history/src/createHistory$.ts
@@ -1,27 +1,19 @@
import { StreamAdapter } from '@cycle/base';
import { Location, History, UnregisterCallback } from 'history';
import { HistoryInput } from './types';

export function createHistory$ (history: History, sink$: any,
runStreamAdapter: StreamAdapter): any {
const push = makePushState(history);

const { observer, stream } = runStreamAdapter.makeSubject<Location>();

const history$ = runStreamAdapter.remember<Location>(stream);

const unlisten = history.listen((loc: Location) => {
observer.next(loc);
});

(history$ as any).dispose =
runStreamAdapter.streamSubscribe(sink$, createObserver(push, unlisten));

import xs, {Stream, MemoryStream, Listener} from 'xstream';
import {Location, History, UnregisterCallback} from 'history';
import {HistoryInput} from './types';

export function createHistory$(history: History,
sink$: Stream<HistoryInput | string>): MemoryStream<Location> {
const history$ = xs.createWithMemory<Location>();
const call = makeCallOnHistory(history);
const unlisten = history.listen((loc: Location) => { history$._n(loc); });
const sub = sink$.subscribe(createObserver(call, unlisten));
(history$ as any).dispose = () => { sub.unsubscribe(); unlisten(); };
return history$;
};

function makePushState (history: History) {
return function pushState (input: HistoryInput): void {
function makeCallOnHistory(history: History) {
return function call(input: HistoryInput): void {
if (input.type === 'push') {
history.push(input.pathname, input.state);
}
Expand All @@ -44,17 +36,17 @@ function makePushState (history: History) {
};
}

function createObserver (push: (input: HistoryInput) => any,
unlisten: UnregisterCallback) {
function createObserver(call: (input: HistoryInput) => void,
unlisten: UnregisterCallback): Listener<HistoryInput | string> {
return {
next (input: HistoryInput | String) {
next (input: HistoryInput | string) {
if (typeof input === 'string') {
push({ type: 'push', pathname: input });
call({type: 'push', pathname: input});
} else {
push(input as HistoryInput);
call(input);
}
},
error: unlisten,
complete: unlisten,
error: (err) => { unlisten(); },
complete: () => { setTimeout(unlisten); },
};
}
51 changes: 51 additions & 0 deletions history/src/drivers.ts
@@ -0,0 +1,51 @@
import {Stream, MemoryStream} from 'xstream';
import {
createBrowserHistory,
createMemoryHistory,
createHashHistory,
BrowserHistoryBuildOptions,
MemoryHistoryBuildOptions,
HashHistoryBuildOptions,
Location,
} from 'history';
import {createHistory$} from './createHistory$';
import {
HistoryInput,
HistoryDriver,
GoBackHistoryInput,
GoForwardHistoryInput,
GoHistoryInput,
PushHistoryInput,
ReplaceHistoryInput,
} from './types';

/**
* Create a History Driver to be used in the browser.
*/
export function makeHistoryDriver(options?: BrowserHistoryBuildOptions): HistoryDriver {
const history = createBrowserHistory(options);
return function historyDriver(sink$: Stream<HistoryInput | string>) {
return createHistory$(history, sink$);
};
}

/**
* Create a History Driver to be used in non-browser enviroments
* such as server-side node.js.
*/
export function makeServerHistoryDriver(options?: MemoryHistoryBuildOptions): HistoryDriver {
const history = createMemoryHistory(options);
return function serverHistoryDriver(sink$: Stream<HistoryInput | string>) {
return createHistory$(history, sink$);
};
}

/**
* Create a History Driver for older browsers using hash routing
*/
export function makeHashHistoryDriver(options?: HashHistoryBuildOptions): HistoryDriver {
const history = createHashHistory(options);
return function hashHistoryDriver(sink$: Stream<HistoryInput | string>) {
return createHistory$(history, sink$);
};
}
41 changes: 0 additions & 41 deletions history/src/historyDriver.ts

This file was deleted.

4 changes: 2 additions & 2 deletions history/src/index.ts
@@ -1,4 +1,4 @@
export { Location } from 'history';
export {Location} from 'history';
export * from './types';
export * from './historyDriver';
export * from './drivers';
export * from './captureClicks';
14 changes: 9 additions & 5 deletions history/src/types.ts
@@ -1,29 +1,33 @@
import {Stream, MemoryStream} from 'xstream';
import {Location} from 'history';
export type Pathname = string;

export type HistoryDriver = (sink$: Stream<HistoryInput | string>) => MemoryStream<Location>;

export interface PushHistoryInput {
type: 'push';
pathname: Pathname;
state?: any;
};
}

export interface ReplaceHistoryInput {
type: 'replace';
pathname: Pathname;
state?: any;
};
}

export interface GoHistoryInput {
type: 'go';
amount: number;
};
}

export interface GoBackHistoryInput {
type: 'goBack';
};
}

export interface GoForwardHistoryInput {
type: 'goForward';
};
}

export type HistoryInput =
PushHistoryInput
Expand Down
34 changes: 34 additions & 0 deletions history/test/browser/captureClicks.ts
@@ -0,0 +1,34 @@
/// <reference path="../../node_modules/@types/mocha/index.d.ts" />
/// <reference path="../../node_modules/@types/node/index.d.ts" />
import * as assert from 'assert';
import xs from 'xstream';
import {makeHashHistoryDriver, captureClicks, Location} from '../../src';

describe('captureClicks', () => {
beforeEach(() => {
window.location.hash = '';
});

it('should allow listening to link clicks and change route', function (done) {
const historyDriver = makeHashHistoryDriver();
const history$ = captureClicks(historyDriver)(xs.never());

const sub = history$.subscribe({
next: (location: Location) => {
assert.strictEqual(location.pathname, '/test');
sub.unsubscribe();
done();
},
error: (err) => {},
complete: () => {},
});

const a = document.createElement('a');
a.href = '/test';
document.body.appendChild(a);

setTimeout(() => {
a.click();
});
});
});
24 changes: 24 additions & 0 deletions history/test/browser/common.ts
@@ -0,0 +1,24 @@
/// <reference path="../../node_modules/@types/mocha/index.d.ts" />
/// <reference path="../../node_modules/@types/node/index.d.ts" />
import * as assert from 'assert';
import {makeHashHistoryDriver, makeHistoryDriver} from '../../src';

describe('makeHistoryDriver', () => {
it('should be a function', () => {
assert.strictEqual(typeof makeHistoryDriver, 'function');
});

it('should return a function' , () => {
assert.strictEqual(typeof makeHistoryDriver(), 'function');
});
});

describe('makeHashHistoryDriver', () => {
it('should be a function', () => {
assert.strictEqual(typeof makeHashHistoryDriver, 'function');
});

it('should return a function' , () => {
assert.strictEqual(typeof makeHashHistoryDriver(), 'function');
});
});

0 comments on commit d41bdae

Please sign in to comment.