Skip to content

Commit

Permalink
refactor(UrlMatcherFactory): merge MatcherConfig into UrlMatcherFactory
Browse files Browse the repository at this point in the history
- Previously, matcherConfig was a global object, exported from its own es6 module.
- Now, matcherConfig is stored per-instance on the UrlMatcherFactory
- Previously, Param.from* methods were static on Param class.
- Now, urlMatcherFactory has `paramFactory` for creating Param objects
  • Loading branch information
christopherthielen committed Dec 22, 2016
1 parent fddd1e2 commit a7d5fcb
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 249 deletions.
41 changes: 13 additions & 28 deletions src/params/param.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
* @internalapi
* @module params
*/ /** for typedoc */
import {extend, filter, map, applyPairs, allTrueR} from "../common/common";
import {prop, propEq} from "../common/hof";
import {isInjectable, isDefined, isString, isArray} from "../common/predicates";
import {RawParams, ParamDeclaration} from "../params/interface";
import {services} from "../common/coreservices";
import {matcherConfig} from "../url/urlMatcherConfig";
import {ParamType} from "./type";
import {ParamTypes} from "./paramTypes";
import { extend, filter, map, applyPairs, allTrueR } from "../common/common";
import { prop, propEq } from "../common/hof";
import { isInjectable, isDefined, isString, isArray } from "../common/predicates";
import { RawParams, ParamDeclaration } from "../params/interface";
import { services } from "../common/coreservices";
import { ParamType } from "./type";
import { ParamTypes } from "./paramTypes";
import { UrlMatcherFactory } from "../url/urlMatcherFactory";

let hasOwn = Object.prototype.hasOwnProperty;
let isShorthand = (cfg: ParamDeclaration) =>
Expand Down Expand Up @@ -43,10 +43,10 @@ function getType(cfg: ParamDeclaration, urlType: ParamType, location: DefType, i
/**
* returns false, true, or the squash value to indicate the "default parameter url squash policy".
*/
function getSquashPolicy(config: ParamDeclaration, isOptional: boolean) {
function getSquashPolicy(config: ParamDeclaration, isOptional: boolean, defaultPolicy: (boolean|string)) {
let squash = config.squash;
if (!isOptional || squash === false) return false;
if (!isDefined(squash) || squash == null) return matcherConfig.defaultSquashPolicy();
if (!isDefined(squash) || squash == null) return defaultPolicy;
if (squash === true || isString(squash)) return squash;
throw new Error(`Invalid squash policy: '${squash}'. Valid policies: false, true, or arbitrary string`);
}
Expand Down Expand Up @@ -75,15 +75,15 @@ export class Param {
raw: boolean;
config: any;

constructor(id: string, type: ParamType, config: ParamDeclaration, location: DefType, paramTypes: ParamTypes) {
constructor(id: string, type: ParamType, config: ParamDeclaration, location: DefType, urlMatcherFactory: UrlMatcherFactory) {
config = unwrapShorthand(config);
type = getType(config, type, location, id, paramTypes);
type = getType(config, type, location, id, urlMatcherFactory.paramTypes);
let arrayMode = getArrayMode();
type = arrayMode ? type.$asArray(arrayMode, location === DefType.SEARCH) : type;
let isOptional = config.value !== undefined;
let dynamic = isDefined(config.dynamic) ? !!config.dynamic : !!type.dynamic;
let raw = isDefined(config.raw) ? !!config.raw : !!type.raw;
let squash = getSquashPolicy(config, isOptional);
let squash = getSquashPolicy(config, isOptional, urlMatcherFactory.defaultSquashPolicy());
let replace = getReplace(config, arrayMode, isOptional, squash);

// array config: param name (param[]) overrides default settings. explicit config overrides param name.
Expand Down Expand Up @@ -146,21 +146,6 @@ export class Param {
return `{Param:${this.id} ${this.type} squash: '${this.squash}' optional: ${this.isOptional}}`;
}

/** Creates a new [[Param]] from a CONFIG block */
static fromConfig(id: string, type: ParamType, config: any, paramTypes: ParamTypes): Param {
return new Param(id, type, config, DefType.CONFIG, paramTypes);
}

/** Creates a new [[Param]] from a url PATH */
static fromPath(id: string, type: ParamType, config: any, paramTypes: ParamTypes): Param {
return new Param(id, type, config, DefType.PATH, paramTypes);
}

/** Creates a new [[Param]] from a url SEARCH */
static fromSearch(id: string, type: ParamType, config: any, paramTypes: ParamTypes): Param {
return new Param(id, type, config, DefType.SEARCH, paramTypes);
}

static values(params: Param[], values: RawParams = {}): RawParams {
return <RawParams> params.map(param => [param.id, param.value(values[param.id])]).reduce(applyPairs, {});
}
Expand Down
12 changes: 6 additions & 6 deletions src/state/stateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {UrlMatcher} from "../url/urlMatcher";
import {Resolvable} from "../resolve/resolvable";
import {services} from "../common/coreservices";
import {ResolvePolicy} from "../resolve/interface";
import {ParamTypes} from "../params/paramTypes";
import { ParamFactory } from "../url/interface";

const parseUrl = (url: string): any => {
if (!isString(url)) return false;
Expand Down Expand Up @@ -84,9 +84,9 @@ function navigableBuilder(state: State) {
return !isRoot(state) && state.url ? state : (state.parent ? state.parent.navigable : null);
};

const getParamsBuilder = (paramTypes: ParamTypes) =>
const getParamsBuilder = (paramFactory: ParamFactory) =>
function paramsBuilder(state: State): { [key: string]: Param } {
const makeConfigParam = (config: any, id: string) => Param.fromConfig(id, null, config, paramTypes);
const makeConfigParam = (config: any, id: string) => paramFactory.fromConfig(id, null, config);
let urlParams: Param[] = (state.url && state.url.parameters({inherit: false})) || [];
let nonUrlParams: Param[] = values(mapObj(omit(state.params || {}, urlParams.map(prop('id'))), makeConfigParam));
return urlParams.concat(nonUrlParams).map(p => [p.id, p]).reduce(applyPairs, {});
Expand Down Expand Up @@ -212,7 +212,7 @@ export class StateBuilder {
/** An object that contains all the BuilderFunctions registered, key'd by the name of the State property they build */
private builders: Builders;

constructor(private matcher: StateMatcher, $urlMatcherFactoryProvider: UrlMatcherFactory) {
constructor(private matcher: StateMatcher, urlMatcherFactory: UrlMatcherFactory) {
let self = this;

const root = () => matcher.find("");
Expand All @@ -229,10 +229,10 @@ export class StateBuilder {
parent: [ parentBuilder ],
data: [ dataBuilder ],
// Build a URLMatcher if necessary, either via a relative or absolute URL
url: [ getUrlBuilder($urlMatcherFactoryProvider, root) ],
url: [ getUrlBuilder(urlMatcherFactory, root) ],
// Keep track of the closest ancestor state that has a URL (i.e. is navigable)
navigable: [ getNavigableBuilder(isRoot) ],
params: [ getParamsBuilder($urlMatcherFactoryProvider.paramTypes) ],
params: [ getParamsBuilder(urlMatcherFactory.paramFactory) ],
// Each framework-specific ui-router implementation should define its own `views` builder
// e.g., src/ng1/statebuilders/views.ts
views: [],
Expand Down
1 change: 0 additions & 1 deletion src/url/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @module url
*/ /** for typedoc */
export * from "./urlMatcher";
export * from "./urlMatcherConfig";
export * from "./urlMatcherFactory";
export * from "./urlRouter";
export * from "./urlRule";
25 changes: 25 additions & 0 deletions src/url/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { LocationServices, $InjectorLike, LocationConfig } from "../common/coreservices";
import { UrlRule } from "./urlRule";
import { UrlMatcher } from "./urlMatcher";
import { IInjectable } from "../common/common";
import { ParamType } from "../params/type";
import { Param } from "../params/param";

export interface ParamFactory {
/** Creates a new [[Param]] from a CONFIG block */
fromConfig(id: string, type: ParamType, config: any): Param;
/** Creates a new [[Param]] from a url PATH */
fromPath(id: string, type: ParamType, config: any): Param;
/** Creates a new [[Param]] from a url SEARCH */
fromSearch(id: string, type: ParamType, config: any): Param;
}

export interface UrlConfig extends LocationConfig, UrlMatcherConfig {};

export interface UrlMatcherConfig {
caseInsensitive(value?: boolean): boolean;
strictMode(value?: boolean): boolean;
defaultSquashPolicy(value?: (boolean|string)): (boolean|string);
paramType(name, type?)
}

7 changes: 4 additions & 3 deletions src/url/urlMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {DefType} from "../params/param";
import {unnestR} from "../common/common";
import {arrayTuples} from "../common/common";
import {RawParams} from "../params/interface";
import { ParamFactory } from "./interface";

/** @hidden */
function quoteRegExp(string: any, param?: any) {
Expand Down Expand Up @@ -114,7 +115,7 @@ export class UrlMatcher {
* - `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
* - `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
*/
constructor(pattern: string, paramTypes: ParamTypes, public config?: any) {
constructor(pattern: string, paramTypes: ParamTypes, paramFactory: ParamFactory, public config?: any) {
this.pattern = pattern;
this.config = defaults(this.config, {
params: {},
Expand Down Expand Up @@ -172,7 +173,7 @@ export class UrlMatcher {
if (p.segment.indexOf('?') >= 0) break; // we're into the search part

checkParamErrors(p.id);
this._params.push(Param.fromPath(p.id, p.type, this.config.paramMap(p.cfg, false), paramTypes));
this._params.push(paramFactory.fromPath(p.id, p.type, this.config.paramMap(p.cfg, false)));
this._segments.push(p.segment);
patterns.push([p.segment, tail(this._params)]);
last = placeholder.lastIndex;
Expand All @@ -192,7 +193,7 @@ export class UrlMatcher {
while ((m = searchPlaceholder.exec(search))) {
p = matchDetails(m, true);
checkParamErrors(p.id);
this._params.push(Param.fromSearch(p.id, p.type, this.config.paramMap(p.cfg, true), paramTypes));
this._params.push(paramFactory.fromSearch(p.id, p.type, this.config.paramMap(p.cfg, true)));
last = placeholder.lastIndex;
// check if ?&
}
Expand Down
25 changes: 0 additions & 25 deletions src/url/urlMatcherConfig.ts

This file was deleted.

66 changes: 41 additions & 25 deletions src/url/urlMatcherFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,15 @@
* @coreapi
* @module url
*/ /** for typedoc */
import {forEach, extend} from "../common/common";
import {isObject, isDefined, isFunction} from "../common/predicates";

import {UrlMatcher} from "./urlMatcher";
import {matcherConfig} from "./urlMatcherConfig";
import {Param} from "../params/param";
import {ParamTypes} from "../params/paramTypes";
import {ParamTypeDefinition} from "../params/interface";
import { forEach, extend } from "../common/common";
import { isObject, isDefined, isFunction, isString } from "../common/predicates";
import { UrlMatcher } from "./urlMatcher";
import { Param, DefType } from "../params/param";
import { ParamTypes } from "../params/paramTypes";
import { ParamTypeDefinition } from "../params/interface";
import { Disposable } from "../interface";

/** @hidden */
function getDefaultConfig() {
return {
strict: matcherConfig.strictMode(),
caseInsensitive: matcherConfig.caseInsensitive()
};
}
import { ParamType } from "../params/type";
import { ParamFactory } from "./interface";

/**
* Factory for [[UrlMatcher]] instances.
Expand All @@ -27,7 +19,10 @@ function getDefaultConfig() {
* `$urlMatcherFactor` or ng1 providers as `$urlMatcherFactoryProvider`.
*/
export class UrlMatcherFactory implements Disposable {
paramTypes = new ParamTypes();
/** @hidden */ paramTypes = new ParamTypes();
/** @hidden */ _isCaseInsensitive: boolean = false;
/** @hidden */ _isStrictMode: boolean = true;
/** @hidden */ _defaultSquashPolicy: (boolean|string) = false;

constructor() {
extend(this, { UrlMatcher, Param });
Expand All @@ -39,8 +34,8 @@ export class UrlMatcherFactory implements Disposable {
* @param value `false` to match URL in a case sensitive manner; otherwise `true`;
* @returns the current value of caseInsensitive
*/
caseInsensitive(value: boolean) {
return matcherConfig.caseInsensitive(value);
caseInsensitive(value?: boolean): boolean {
return this._isCaseInsensitive = isDefined(value) ? value : this._isCaseInsensitive;
}

/**
Expand All @@ -49,8 +44,8 @@ export class UrlMatcherFactory implements Disposable {
* @param value `false` to match trailing slashes in URLs, otherwise `true`.
* @returns the current value of strictMode
*/
strictMode(value: boolean) {
return matcherConfig.strictMode(value);
strictMode(value?: boolean): boolean {
return this._isStrictMode = isDefined(value) ? value : this._isStrictMode;
}

/**
Expand All @@ -64,10 +59,16 @@ export class UrlMatcherFactory implements Disposable {
* the parameter value from the URL and replace it with this string.
* @returns the current value of defaultSquashPolicy
*/
defaultSquashPolicy(value: string) {
return matcherConfig.defaultSquashPolicy(value);
defaultSquashPolicy(value?: (boolean|string)) {
if (isDefined(value) && value !== true && value !== false && !isString(value))
throw new Error(`Invalid squash policy: ${value}. Valid policies: false, true, arbitrary-string`);
return this._defaultSquashPolicy = isDefined(value) ? value : this._defaultSquashPolicy;
}

/** @hidden */
private _getConfig = (config) =>
extend({ strict: this._isStrictMode, caseInsensitive: this._isCaseInsensitive }, config);

/**
* Creates a [[UrlMatcher]] for the specified pattern.
*
Expand All @@ -76,7 +77,7 @@ export class UrlMatcherFactory implements Disposable {
* @returns The UrlMatcher.
*/
compile(pattern: string, config?: { [key: string]: any }) {
return new UrlMatcher(pattern, this.paramTypes, extend(getDefaultConfig(), config));
return new UrlMatcher(pattern, this.paramTypes, this.paramFactory, this._getConfig(config));
}

/**
Expand All @@ -86,7 +87,7 @@ export class UrlMatcherFactory implements Disposable {
* @returns `true` if the object matches the `UrlMatcher` interface, by
* implementing all the same methods.
*/
isMatcher(object: any) {
isMatcher(object: any): boolean {
// TODO: typeof?
if (!isObject(object)) return false;
let result = true;
Expand Down Expand Up @@ -128,6 +129,21 @@ export class UrlMatcherFactory implements Disposable {
return this;
};

/** @internalapi Creates a new [[Param]] for a given location (DefType) */
paramFactory: ParamFactory = {
/** Creates a new [[Param]] from a CONFIG block */
fromConfig: (id: string, type: ParamType, config: any) =>
new Param(id, type, config, DefType.CONFIG, this),

/** Creates a new [[Param]] from a url PATH */
fromPath: (id: string, type: ParamType, config: any) =>
new Param(id, type, config, DefType.PATH, this),

/** Creates a new [[Param]] from a url SEARCH */
fromSearch: (id: string, type: ParamType, config: any) =>
new Param(id, type, config, DefType.SEARCH, this),
};

/** @internalapi */
dispose() {
this.paramTypes.dispose();
Expand Down
6 changes: 3 additions & 3 deletions src/url/urlRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ export class UrlRouter implements Disposable {
*
* #### Example:
* ```js
* $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
* person: "bob"
* });
* matcher = $umf.compile("/about/:person");
* params = { person: "bob" };
* $bob = $urlRouter.href(matcher, params);
* // $bob == "/about/bob";
* ```
*
Expand Down
3 changes: 2 additions & 1 deletion src/url/urlService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { UIRouter } from "../router";
import { LocationServices, notImplemented, LocationConfig } from "../common/coreservices";
import { noop, createProxyFunctions } from "../common/common";
import { UrlConfig } from "./interface";

/** @hidden */
const makeStub = (keys: string[]): any =>
Expand Down Expand Up @@ -49,7 +50,7 @@ export class UrlService implements LocationServices {
* This information can be used to build absolute URLs, such as
* `https://example.com:443/basepath/state/substate?param1=a#hashvalue`;
*/
config: LocationConfig;
config: UrlConfig;

constructor(private router: UIRouter) {
this.config = {} as any;
Expand Down
4 changes: 2 additions & 2 deletions test/stateHelperSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('state helpers', function() {
});

it('should compile a UrlMatcher for ^ URLs', function() {
var url = new UrlMatcher('/', paramTypes);
var url = new UrlMatcher('/', paramTypes, null);
spyOn(urlMatcherFactoryProvider, 'compile').and.returnValue(url);
spyOn(urlMatcherFactoryProvider, 'isMatcher').and.returnValue(true);

Expand Down Expand Up @@ -158,7 +158,7 @@ describe('state helpers', function() {

it('should pass through custom UrlMatchers', function() {
var root = states[''] = { url: { append: function() {} } };
var url = new UrlMatcher("/", paramTypes);
var url = new UrlMatcher("/", paramTypes, null);
spyOn(urlMatcherFactoryProvider, 'isMatcher').and.returnValue(true);
spyOn(root.url, 'append').and.returnValue(url);
expect(builder.builder('url')({ url: url })).toBe(url);
Expand Down
Loading

0 comments on commit a7d5fcb

Please sign in to comment.