Skip to content

Commit

Permalink
feat(LocationServices): Add a parts() method which returns the URL …
Browse files Browse the repository at this point in the history
…parts as an object

refactor(UrlSync): Consolidate UrlSync UrlListen UrlDeferIntercept
feat(UrlServices): Add `match()`: given a URL, return the best matching Url Rule
  • Loading branch information
christopherthielen committed Jan 4, 2017
1 parent ee340d4 commit 32e64f0
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 38 deletions.
10 changes: 7 additions & 3 deletions src/common/coreservices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/** for typedoc */
import {IInjectable, Obj} from "./common";
import { Disposable } from "../interface";
import { UrlParts } from "../url/interface";

export let notImplemented = (fnname: string) => () => {
throw new Error(`${fnname}(): No coreservices implementation for UI-Router is loaded.`);
Expand Down Expand Up @@ -77,29 +78,32 @@ export interface LocationServices extends Disposable {
url(newurl: string, replace?: boolean, state?: any): string;

/**
* Gets the path portion of the current url
* Gets the path part of the current url
*
* If the current URL is `/some/path?query=value#anchor`, this returns `/some/path`
*
* @return the path portion of the url
*/
path(): string;

/**
* Gets the search portion of the current url as an object
* Gets the search part of the current url as an object
*
* If the current URL is `/some/path?query=value#anchor`, this returns `{ query: 'value' }`
*
* @return the search (querystring) portion of the url, as an object
*/
search(): { [key: string]: any };

/**
* Gets the hash portion of the current url
* Gets the hash part of the current url
*
* If the current URL is `/some/path?query=value#anchor`, this returns `anchor`
*
* @return the hash (anchor) portion of the url
*/
hash(): string;

/**
* Registers a url change handler
*
Expand Down
30 changes: 18 additions & 12 deletions src/url/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export interface UrlMatcherConfig {
*/
defaultSquashPolicy(value?: (boolean|string)): (boolean|string);


/**
* Creates and registers a custom [[ParamTypeDefinition]] object
*
Expand Down Expand Up @@ -120,7 +119,7 @@ export interface UrlMatcherConfig {
}

/** @internalapi */
export interface UrlSync {
export interface UrlSyncApi {
/**
* Checks the URL for a matching [[UrlRule]]
*
Expand All @@ -142,10 +141,7 @@ export interface UrlSync {
* ```
*/
sync(evt?): void;
}

/** @internalapi */
export interface UrlListen {
/**
* Starts or stops listening for URL changes
*
Expand All @@ -166,11 +162,8 @@ export interface UrlListen {
* });
* ```
*/
listen(enabled?: boolean): Function;
}
listen(enabled?: boolean): Function

/** @internalapi */
export interface UrlDeferIntercept {
/**
* Disables monitoring of the URL.
*
Expand All @@ -195,7 +188,7 @@ export interface UrlDeferIntercept {
* @param defer Indicates whether to defer location change interception.
* Passing no parameter is equivalent to `true`.
*/
deferIntercept(defer?: boolean);
deferIntercept(defer?: boolean)
}

/**
Expand Down Expand Up @@ -376,10 +369,23 @@ export interface UrlRules {
*/
export interface UrlParts {
path: string;
search: { [key: string]: any };
hash: string;
search?: { [key: string]: any };
hash?: string;
}

/**
* A UrlRule match result
*
* The result of UrlRouter.match()
*/
export interface MatchResult {
/** The matched value from a [[UrlRule]] */
match: any;
/** The rule that matched */
rule: UrlRule;
/** The match result weight */
weight: number;
}
/**
* A function that matches the URL for a [[UrlRule]]
*
Expand Down
49 changes: 31 additions & 18 deletions src/url/urlRouter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/**
* @internalapi
* @module url
*/ /** for typedoc */
import { removeFrom, createProxyFunctions, inArray, composeSort, sortBy } from "../common/common";
*/
/** for typedoc */
import { removeFrom, createProxyFunctions, inArray, composeSort, sortBy, extend } from "../common/common";
import { isFunction, isString, isDefined } from "../common/predicates";
import { UrlMatcher } from "./urlMatcher";
import { RawParams } from "../params/interface";
Expand All @@ -11,7 +12,7 @@ import { UIRouter } from "../router";
import { val, is, pattern, prop, pipe } from "../common/hof";
import { UrlRuleFactory } from "./urlRule";
import { TargetState } from "../state/targetState";
import { UrlRule, UrlRuleHandlerFn, UrlParts, UrlRules, UrlSync, UrlListen, UrlDeferIntercept } from "./interface";
import { UrlRule, UrlRuleHandlerFn, UrlParts, UrlRules, UrlSyncApi, MatchResult } from "./interface";
import { TargetStateDef } from "../state/interface";

/** @hidden */
Expand Down Expand Up @@ -53,7 +54,7 @@ defaultRuleSortFn = composeSort(
* This class updates the URL when the state changes.
* It also responds to changes in the URL.
*/
export class UrlRouter implements UrlRules, UrlSync, UrlListen, UrlDeferIntercept, Disposable {
export class UrlRouter implements UrlRules, UrlSyncApi, Disposable {
/** used to create [[UrlRule]] objects for common cases */
public urlRuleFactory: UrlRuleFactory;

Expand Down Expand Up @@ -85,25 +86,20 @@ export class UrlRouter implements UrlRules, UrlSync, UrlListen, UrlDeferIntercep
this._rules.sort(this._sortFn = compareFn || this._sortFn);
}

/** @inheritdoc */
sync(evt?) {
if (evt && evt.defaultPrevented) return;

let router = this._router,
$url = router.urlService,
$state = router.stateService;

/**
* Given a URL, check all rules and return the best [[MatchResult]]
* @param url
* @returns {MatchResult}
*/
match(url: UrlParts): MatchResult {
url = extend({path: '', search: {}, hash: '' }, url);
let rules = this.rules();
if (this._otherwiseFn) rules.push(this._otherwiseFn);

let url: UrlParts = {
path: $url.path(), search: $url.search(), hash: $url.hash()
};

// Checks a single rule. Returns { rule: rule, match: match, weight: weight } if it matched, or undefined
interface MatchResult { match: any, rule: UrlRule, weight: number }

let checkRule = (rule: UrlRule): MatchResult => {
let match = rule.match(url, router);
let match = rule.match(url, this._router);
return match && { match, rule, weight: rule.matchPriority(match) };
};

Expand All @@ -121,6 +117,23 @@ export class UrlRouter implements UrlRules, UrlSync, UrlListen, UrlDeferIntercep
best = (!best || current && current.weight > best.weight) ? current : best;
}

return best;
}

/** @inheritdoc */
sync(evt?) {
if (evt && evt.defaultPrevented) return;

let router = this._router,
$url = router.urlService,
$state = router.stateService;

let url: UrlParts = {
path: $url.path(), search: $url.search(), hash: $url.hash()
};

let best = this.match(url);

let applyResult = pattern([
[isString, (newurl: string) => $url.url(newurl)],
[TargetState.isDef, (def: TargetStateDef) => $state.go(def.state, def.params, def.options)],
Expand Down
20 changes: 17 additions & 3 deletions src/url/urlService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { UIRouter } from "../router";
import { LocationServices, notImplemented, LocationConfig } from "../common/coreservices";
import { noop, createProxyFunctions } from "../common/common";
import { UrlConfig, UrlSync, UrlListen, UrlRules, UrlDeferIntercept } from "./interface";
import { UrlConfig, UrlSyncApi, UrlRules, UrlParts, MatchResult } from "./interface";

/** @hidden */
const makeStub = (keys: string[]): any =>
Expand All @@ -16,12 +16,12 @@ const makeStub = (keys: string[]): any =>
/** @hidden */ const locationConfigFns = ["port", "protocol", "host", "baseHref", "html5Mode", "hashPrefix"];
/** @hidden */ const umfFns = ["type", "caseInsensitive", "strictMode", "defaultSquashPolicy"];
/** @hidden */ const rulesFns = ["sort", "when", "otherwise", "rules", "rule", "removeRule"];
/** @hidden */ const syncFns = ["deferIntercept", "listen", "sync"];
/** @hidden */ const syncFns = ["deferIntercept", "listen", "sync", "match"];

/**
* API for URL management
*/
export class UrlService implements LocationServices, UrlSync, UrlListen, UrlDeferIntercept {
export class UrlService implements LocationServices, UrlSyncApi {
/** @hidden */
static locationServiceStub: LocationServices = makeStub(locationServicesFns);
/** @hidden */
Expand All @@ -41,6 +41,18 @@ export class UrlService implements LocationServices, UrlSync, UrlListen, UrlDefe
/** @inheritdoc */
onChange(callback: Function): Function { return };


/**
* Returns the current URL parts
*
* This method returns the current URL components as a [[UrlParts]] object.
*
* @returns the current url parts
*/
parts(): UrlParts {
return { path: this.path(), search: this.search(), hash: this.hash() }
}

dispose() { }

/** @inheritdoc */
Expand All @@ -49,6 +61,8 @@ export class UrlService implements LocationServices, UrlSync, UrlListen, UrlDefe
listen(enabled?: boolean): Function { return };
/** @inheritdoc */
deferIntercept(defer?: boolean) { return }
/** @inheritdoc */
match(urlParts: UrlParts): MatchResult { return }

/**
* A nested API for managing URL rules and rewrites
Expand Down
2 changes: 1 addition & 1 deletion src/vanilla/hashLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class HashLocationService implements LocationServices, Disposable {
url(url?: string, replace: boolean = true): string {
if (isDefined(url)) location.hash = url;
return buildUrl(this);
};
}

onChange(cb: EventListener) {
window.addEventListener('hashchange', cb, false);
Expand Down
30 changes: 29 additions & 1 deletion test/urlRouterSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { LocationServices } from "../src/common/coreservices";
import { UrlService } from "../src/url/urlService";
import { StateRegistry } from "../src/state/stateRegistry";
import { noop } from "../src/common/common";
import { UrlRule } from "../src/url/interface";
import { UrlRule, MatchResult } from "../src/url/interface";

declare var jasmine;
var _anything = jasmine.anything();
Expand Down Expand Up @@ -272,6 +272,34 @@ describe("UrlRouter", function () {
})
});
});

describe('match', () => {
let A, B, CCC;
beforeEach(() => {
A = stateRegistry.register({ name: 'A', url: '/:pA' });
B = stateRegistry.register({ name: 'B', url: '/BBB' });
CCC = urlService.rules.when('/CCC', '/DDD');
});

it("should return the best match for a URL 1", () => {
let match: MatchResult = urlRouter.match({ path: '/BBB' });
expect(match.rule.type).toBe("STATE");
expect(match.rule['state']).toBe(B)
});

it("should return the best match for a URL 2", () => {
let match: MatchResult = urlRouter.match({ path: '/EEE' });
expect(match.rule.type).toBe("STATE");
expect(match.rule['state']).toBe(A);
expect(match.match).toEqual({ pA: 'EEE' });
});

it("should return the best match for a URL 3", () => {
let match: MatchResult = urlRouter.match({ path: '/CCC' });
expect(match.rule.type).toBe("URLMATCHER");
expect(match.rule).toBe(CCC);
});
});
});

describe('UrlRouter.deferIntercept', () => {
Expand Down

0 comments on commit 32e64f0

Please sign in to comment.