Skip to content

Commit f7ac2bb

Browse files
INTERNAL BREAKING CHANGE: Move html5Mode and hashPrefix to LocationServices from LocationConfig
refactor(vanilla): Rewrite Location Services/Config as es6 classes
1 parent 3514cfd commit f7ac2bb

13 files changed

+259
-167
lines changed

src/common/common.ts

+6
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ export const removeFrom = curry((array: any[], obj: any) => {
140140
export const pushTo = <T> (arr: T[], val: T) =>
141141
(arr.push(val), val);
142142

143+
/** Given an array of (deregistration) functions, calls all functions and removes each one from the source array */
144+
export const deregAll = (functions: Function[]) =>
145+
functions.slice().forEach(fn => {
146+
typeof fn === 'function' && fn();
147+
removeFrom(functions, fn);
148+
});
143149
/**
144150
* Applies a set of defaults to an options object. The options object is filtered
145151
* to only those properties of the objects in the defaultsList.

src/common/coreservices.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ export interface CoreServices {
6161
export interface LocationServices {
6262
setUrl(newurl: string, replace?: boolean): void;
6363
path(): string;
64-
search(): string;
64+
search(): { [key: string]: any };
6565
hash(): string;
6666
onChange(callback: Function): Function;
67+
html5Mode(): boolean;
68+
hashPrefix(): string;
69+
hashPrefix(newprefix: string): string;
6770
}
6871

6972
export interface LocationConfig {
@@ -72,9 +75,6 @@ export interface LocationConfig {
7275
host(): string;
7376

7477
baseHref(): string;
75-
html5Mode(): boolean;
76-
hashPrefix(): string;
77-
hashPrefix(newprefix: string): string;
7878
}
7979

8080
export interface TemplateServices {

src/router.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { StateService } from "./state/stateService";
99
import { UIRouterGlobals, Globals } from "./globals";
1010
import { UIRouterPlugin, Disposable } from "./interface";
1111
import { values, removeFrom } from "./common/common";
12+
import { isFunction } from "./common/predicates";
1213

1314
/** @hidden */
1415
let _routerInstance = 0;
@@ -51,7 +52,22 @@ export class UIRouter {
5152
this._disposables.push(disposable);
5253
}
5354

54-
dispose() {
55+
/**
56+
* Disposes this router instance
57+
*
58+
* When called, clears resources retained by the router by calling `dispose(this)` on all
59+
* registered [[disposable]] objects.
60+
*
61+
* Or, if a `disposable` object is provided, calls `dispose(this)` on that object only.
62+
*
63+
* @param disposable (optional) the disposable to dispose
64+
*/
65+
dispose(disposable?: any): void {
66+
if (disposable && isFunction(disposable.dispose)) {
67+
disposable.dispose(this);
68+
return undefined;
69+
}
70+
5571
this._disposables.slice().forEach(d => {
5672
try {
5773
typeof d.dispose === 'function' && d.dispose(this);

src/transition/transitionService.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ export class TransitionService implements IHookRegistry, Disposable {
133133
}
134134

135135
/** @internalapi */
136-
dispose() {
137-
delete this._router.globals.transition;
136+
dispose(router: UIRouter) {
137+
delete router.globals.transition;
138138

139-
values(this._transitionHooks).forEach(hooksArray => hooksArray.forEach(hook => {
139+
values(this._transitionHooks).forEach((hooksArray: RegisteredHook[]) => hooksArray.forEach(hook => {
140140
hook._deregistered = true;
141141
removeFrom(hooksArray, hook);
142142
}));

src/url/urlRouter.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ export class UrlRouter implements Disposable {
380380
* @param params
381381
* @param options
382382
*/
383-
push(urlMatcher: UrlMatcher, params: StateParams, options: { replace?: (string|boolean) }) {
383+
push(urlMatcher: UrlMatcher, params: RawParams, options: { replace?: (string|boolean) }) {
384384
let replace = options && !!options.replace;
385385
services.location.setUrl(urlMatcher.format(params || {}), replace);
386386
}
@@ -411,9 +411,10 @@ export class UrlRouter implements Disposable {
411411
options = options || { absolute: false };
412412

413413
let cfg = services.locationConfig;
414-
let isHtml5 = cfg.html5Mode();
414+
let loc = services.location;
415+
let isHtml5 = loc.html5Mode();
415416
if (!isHtml5 && url !== null) {
416-
url = "#" + cfg.hashPrefix() + url;
417+
url = "#" + loc.hashPrefix() + url;
417418
}
418419
url = appendBasePath(url, isHtml5, options.absolute);
419420

src/vanilla/browserLocationConfig.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/** @internalapi @module vanilla */ /** */
2+
3+
import { isDefined } from "../common/predicates";
4+
import { LocationConfig } from "../common/coreservices";
5+
6+
/** A `LocationConfig` that delegates to the browser's `location` object */
7+
export class BrowserLocationConfig implements LocationConfig {
8+
private _baseHref = undefined;
9+
10+
port() {
11+
return parseInt(location.port);
12+
}
13+
14+
protocol () {
15+
return location.protocol;
16+
}
17+
18+
host() {
19+
return location.host;
20+
}
21+
22+
baseHref(href?: string) {
23+
return isDefined(href) ? this._baseHref = href : this._baseHref || this.applyDocumentBaseHref();
24+
}
25+
26+
applyDocumentBaseHref() {
27+
let baseTags = document.getElementsByTagName("base");
28+
return this._baseHref = baseTags.length ? baseTags[0].href.substr(location.origin.length) : "";
29+
}
30+
}

src/vanilla/hashLocation.ts

+44-39
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
11
/** @internalapi @module vanilla */ /** */
2-
import { isDefined } from '../common/module';
3-
import { LocationConfig, LocationServices } from '../common/coreservices';
4-
import { splitHash, splitQuery, trimHashVal, getParams, locationPluginFactory } from './utils';
5-
import { UIRouter } from '../router';
2+
import { isDefined } from "../common/module";
3+
import { LocationServices } from "../common/coreservices";
4+
import { splitHash, splitQuery, trimHashVal, getParams, locationPluginFactory } from "./utils";
5+
import { UIRouter } from "../router";
66
import { LocationPlugin } from "./interface";
7+
import { pushTo, deregAll } from "../common/common";
8+
import { Disposable } from "../interface";
9+
import { BrowserLocationConfig } from "./browserLocationConfig";
710

8-
let hashPrefix: string = '';
9-
let baseHref: string = '';
10-
11-
/** A `LocationConfig` that delegates to the browser's `location` object */
12-
export const hashLocationConfig: LocationConfig = {
13-
port: () =>
14-
parseInt(location.port),
15-
protocol: () =>
16-
location.protocol,
17-
host: () =>
18-
location.host,
19-
baseHref: () =>
20-
baseHref,
21-
html5Mode: () =>
22-
false,
23-
hashPrefix: (newprefix?: string): string => {
11+
/** A `LocationServices` that uses the browser hash "#" to get/set the current location */
12+
export class HashLocationService implements LocationServices, Disposable {
13+
private _listeners: Function[] = [];
14+
private _hashPrefix = "";
15+
16+
hash() {
17+
return splitHash(trimHashVal(location.hash))[1];
18+
}
19+
20+
path() {
21+
return splitHash(splitQuery(trimHashVal(location.hash))[0])[0];
22+
}
23+
24+
search() {
25+
return getParams(splitQuery(splitHash(trimHashVal(location.hash))[0])[1]);
26+
}
27+
28+
setUrl(url: string, replace: boolean = true) {
29+
if (isDefined(url)) location.hash = url;
30+
};
31+
32+
onChange(cb: EventListener) {
33+
window.addEventListener('hashchange', cb, false);
34+
return pushTo(this._listeners, () => window.removeEventListener('hashchange', cb));
35+
}
36+
37+
html5Mode() {
38+
return false;
39+
}
40+
41+
hashPrefix(newprefix?: string): string {
2442
if(isDefined(newprefix)) {
25-
hashPrefix = newprefix;
43+
this._hashPrefix = newprefix;
2644
}
27-
return hashPrefix;
45+
return this._hashPrefix;
2846
}
29-
};
3047

31-
/** A `LocationServices` that uses the browser hash "#" to get/set the current location */
32-
export const hashLocationService: LocationServices = {
33-
hash: () =>
34-
splitHash(trimHashVal(location.hash))[1],
35-
path: () =>
36-
splitHash(splitQuery(trimHashVal(location.hash))[0])[0],
37-
search: () =>
38-
getParams(splitQuery(splitHash(trimHashVal(location.hash))[0])[1]),
39-
setUrl: (url: string, replace: boolean = true) => {
40-
if (url) location.hash = url;
41-
},
42-
onChange: (cb: EventListener) => {
43-
window.addEventListener('hashchange', cb, false);
44-
return () => window.removeEventListener('hashchange', cb);
48+
dispose() {
49+
deregAll(this._listeners);
4550
}
46-
};
51+
}
4752

4853
/** A `UIRouterPlugin` uses the browser hash to get/set the current location */
4954
export const hashLocationPlugin: (router: UIRouter) => LocationPlugin =
50-
locationPluginFactory('vanilla.hashBangLocation', hashLocationService, hashLocationConfig);
55+
locationPluginFactory('vanilla.hashBangLocation', HashLocationService, BrowserLocationConfig);

src/vanilla/memoryLocation.ts

+67-48
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,98 @@
11
/** @internalapi @module vanilla */ /** */
2-
import { services, isDefined } from '../common/module';
3-
import { LocationConfig, LocationServices } from '../common/coreservices';
4-
import { splitQuery, trimHashVal, getParams, splitHash, locationPluginFactory } from './utils';
5-
import { removeFrom, unnestR } from "../common/common";
2+
import { isDefined } from "../common/module";
3+
import { LocationConfig, LocationServices } from "../common/coreservices";
4+
import { splitQuery, getParams, splitHash, locationPluginFactory } from "./utils";
5+
import { removeFrom, unnestR, deregAll } from "../common/common";
66
import { UIRouter } from "../router";
77
import { LocationPlugin } from "./interface";
88
import { isArray } from "../common/predicates";
9+
import { Disposable } from "../interface";
910

10-
var mlc;
1111
/** A `LocationConfig` mock that gets/sets all config from an in-memory object */
12-
export const memoryLocationConfig: LocationConfig = mlc = {
13-
_hashPrefix: '',
14-
_baseHref: '',
15-
_port: 80,
16-
_protocol: "http",
17-
_host: "localhost",
12+
export class MemoryLocationConfig implements LocationConfig {
13+
_baseHref = '';
14+
_port = 80;
15+
_protocol = "http";
16+
_host = "localhost";
1817

19-
port: () => mlc._port,
20-
protocol: () => mlc._protocol,
21-
host: () => mlc._host,
22-
baseHref: () => mlc._baseHref,
23-
html5Mode: () => false,
24-
hashPrefix: (newprefix?: string): string => {
25-
if (isDefined(newprefix)) {
26-
mlc._hashPrefix = newprefix;
27-
}
28-
return mlc._hashPrefix;
29-
}
30-
};
18+
port = () => this._port;
19+
protocol = () => this._protocol;
20+
host = () => this._host;
21+
baseHref = () => this._baseHref;
22+
}
3123

32-
var mls;
3324
/** A `LocationServices` that gets/sets the current location from an in-memory object */
34-
export const memoryLocationService: LocationServices = mls = {
35-
_listeners: [],
36-
_url: {
25+
export class MemoryLocationService implements LocationServices, Disposable {
26+
_listeners: Function[] = [];
27+
_hashPrefix = "";
28+
_url = {
3729
path: '',
3830
search: {},
3931
hash: ''
40-
},
41-
_changed: (newval, oldval) => {
32+
};
33+
34+
private _urlChanged(newval, oldval) {
4235
if (newval === oldval) return;
4336
let evt = new Event("locationchange");
4437
evt['url'] = newval;
45-
mls._listeners.forEach(cb => cb(evt));
46-
},
38+
this._listeners.forEach(cb => cb(evt));
39+
}
4740

48-
url: () => {
49-
let s = mls._url.search;
50-
let hash = mls._url.hash;
41+
url() {
42+
let s = this._url.search;
43+
let hash = this._url.hash;
5144
let query = Object.keys(s).map(key => (isArray(s[key]) ? s[key] : [s[key]]) .map(val => key + "=" + val))
5245
.reduce(unnestR, [])
5346
.join("&");
5447

55-
return mls._url.path +
48+
return this._url.path +
5649
(query ? "?" + query : "") +
5750
(hash ? "#" + hash : "");
58-
},
59-
hash: () => mls._url.hash,
60-
path: () => mls._url.path,
61-
search: () => mls._url.search,
62-
setUrl: (url: string, replace: boolean = false) => {
51+
}
52+
53+
hash() {
54+
return this._url.hash;
55+
}
56+
57+
path() {
58+
return this._url.path;
59+
}
60+
61+
search() {
62+
return this._url.search;
63+
}
64+
65+
html5Mode() {
66+
return false;
67+
}
68+
69+
hashPrefix(newprefix?: string): string {
70+
return isDefined(newprefix) ? this._hashPrefix = newprefix : this._hashPrefix;
71+
}
72+
73+
setUrl(url: string, replace: boolean = false) {
6374
if (isDefined(url)) {
6475
let path = splitHash(splitQuery(url)[0])[0];
6576
let hash = splitHash(url)[1];
6677
let search = getParams(splitQuery(splitHash(url)[0])[1]);
6778

68-
let oldval = mls.url();
69-
mls._url = { path, search, hash };
70-
let newval = mls.url();
71-
mls._changed(newval, oldval);
79+
let oldval = this.url();
80+
this._url = { path, search, hash };
81+
let newval = this.url();
82+
this._urlChanged(newval, oldval);
7283
}
73-
},
74-
onChange: (cb: EventListener) => (mls._listeners.push(cb), () => removeFrom(mls._listeners, cb))
75-
};
84+
}
85+
86+
onChange(cb: EventListener) {
87+
this._listeners.push(cb);
88+
return () => removeFrom(this._listeners, cb);
89+
}
90+
91+
dispose() {
92+
deregAll(this._listeners);
93+
}
94+
}
7695

7796
/** A `UIRouterPlugin` that gets/sets the current location from an in-memory object */
7897
export const memoryLocationPlugin: (router: UIRouter) => LocationPlugin =
79-
locationPluginFactory("vanilla.memoryLocation", memoryLocationService, memoryLocationConfig);
98+
locationPluginFactory("vanilla.memoryLocation", MemoryLocationService, MemoryLocationConfig);

0 commit comments

Comments
 (0)