diff --git a/src/params/interface.ts b/src/params/interface.ts
index d4c79df5..1e43ab75 100644
--- a/src/params/interface.ts
+++ b/src/params/interface.ts
@@ -24,42 +24,49 @@ export interface RawParams {
export type ParamsOrArray = (RawParams|RawParams[]);
/**
- * Inside a [[StateDeclaration.params]]:
+ * Configuration for a single Parameter
*
- * A ParamDeclaration object defines how a single State Parameter should work.
- *
- * @example
- * ```
+ * In a [[StateDeclaration.params]], each `ParamDeclaration`
+ * defines how a single State Parameter should work.
*
+ * #### Example:
+ * ```js
* var mystate = {
* template: '
',
* controller: function() {}
- * url: '/mystate/:param1',
+ * url: '/mystate/:start?{count:int}',
* params: {
- * param1: "index", // <-- Default value for 'param1'
- * // (shorthand ParamDeclaration)
+ * start: { // <-- ParamDeclaration for `start`
+ * type: 'date',
+ * value: new Date(), // <-- Default value
+ * squash: true,
+ * },
*
* nonUrlParam: { // <-- ParamDeclaration for 'nonUrlParam'
* type: "int",
* array: true,
* value: []
- * }
+ * },
+ *
+ * count: 0, // <-- Default value for 'param1'
+ * // (shorthand ParamDeclaration.value)
* }
* }
* ```
*/
export interface ParamDeclaration {
/**
- * A property of [[ParamDeclaration]]:
+ * The default value for this parameter.
*
- * Specifies the default value for this parameter. This implicitly sets this parameter as optional.
+ * Specifies the default value for this parameter.
+ * This implicitly sets this parameter as optional.
*
* When UI-Router routes to a state and no value is specified for this parameter in the URL or transition,
- * the default value will be used instead. If value is a function, it will be injected and invoked, and the
- * return value used.
+ * the default value will be used instead.
+ * If value is a function, it will be injected and invoked, and the return value used.
*
- * Note: `value: undefined` is treated as though no default value was specified, while `value: null` is treated
- * as "the default value is null".
+ * Note: `value: undefined` is treated as though **no default value was specified**, while `value: null` is treated
+ * as **"the default value is null"**.
*
* ```
* // define default values for param1 and param2
@@ -74,43 +81,48 @@ export interface ParamDeclaration {
* ```
*
* ### Shorthand Declaration
+ *
* If you only want to set the default value of the parameter, you may use a shorthand syntax.
* In the params map, instead mapping the param name to a full parameter configuration object, simply set map it
* to the default parameter value, e.g.:
* ```
- * // define a parameter's default value
+ * // Normal (non-shorthand) default value syntax
* params: {
* param1: {
* value: "defaultValue"
* },
* param2: {
- * value: "param2Default;
+ * value: "param2Default"
* }
* }
*
- * // shorthand default values
+ * // Shorthand default value syntax
* params: {
* param1: "defaultValue",
* param2: "param2Default"
* }
* ```
*
- * This defines a default value for the parameter. If the parameter value is `undefined`, this value will be used instead
+ * This defines a default value for the parameter.
+ * If a parameter value is `undefined`, this default value will be used instead
*/
value?: any;
/**
- * A property of [[ParamDeclaration]]:
+ * The parameter's type
*
* Specifies the [[ParamType]] of the parameter.
+ * Parameter types can be used to customize the encoding/decoding of parameter values.
+ *
+ * Set this property to the name of parameter's type.
+ * The type may be either one of the built in types, or a custom type that has been registered with the [[UrlMatcherFactory]].
*
- * Set this property to the name of parameter's type. The type may be either one of the
- * built in types, or a custom type that has been registered with the [[$urlMatcherFactory]]
+ * See [[ParamTypes]] for the list of built in types.
*/
type: (string|ParamType);
/**
- * A property of [[ParamDeclaration]]:
+ * The parameter's `array` mode
*
* Explicitly specifies the array mode of a URL parameter
*
@@ -124,9 +136,8 @@ export interface ParamDeclaration {
* If you specified a [[type]] for the parameter, the value will be treated as an array
* of the specified [[ParamType]].
*
- * @example
- * ```
- *
+ * #### Example:
+ * ```js
* {
* name: 'foo',
* url: '/foo/{arrayParam:int}`,
@@ -144,8 +155,9 @@ export interface ParamDeclaration {
* @default `true` if the parameter name ends in `[]`, such as `url: '/foo/{implicitArrayParam:int[]}'`
*/
array: boolean;
+
/**
- * A property of [[ParamDeclaration]]:
+ * Squash mode: omit default parameter values in URL
*
* Configures how a default parameter value is represented in the URL when the current parameter value
* is the same as the default value.
@@ -153,15 +165,14 @@ export interface ParamDeclaration {
* There are three squash settings:
*
* - `false`: The parameter's default value is not squashed. It is encoded and included in the URL
- * - `true`: The parameter's default value is omitted from the URL. If the parameter is preceeded
- * and followed by slashes in the state's url declaration, then one of those slashes are omitted.
+ * - `true`: The parameter's default value is omitted from the URL.
+ * If the parameter is preceeded and followed by slashes in the state's url declaration, then one of those slashes are omitted.
* This can allow for cleaner looking URLs.
* - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary
* placeholder of your choice.
*
- * @example
- * ```
- *
+ * #### Example:
+ * ```js
* {
* name: 'mystate',
* url: '/mystate/:myparam',
@@ -178,9 +189,8 @@ export interface ParamDeclaration {
* $state.go('mystate', { myparam: 'someOtherValue' });
* ```
*
- * @example
- * ```
- *
+ * #### Example:
+ * ```js
* {
* name: 'mystate2',
* url: '/mystate2/:myparam2',
@@ -200,6 +210,7 @@ export interface ParamDeclaration {
* If squash is not set, it uses the configured default squash policy. (See [[defaultSquashPolicy]]())
*/
squash: (boolean|string);
+
/**
* @internalapi
*
@@ -218,6 +229,7 @@ export interface ParamDeclaration {
* ```
*/
replace: Replace[];
+
/**
* @hidden
* @internalapi
@@ -225,14 +237,50 @@ export interface ParamDeclaration {
* This is not part of the declaration; it is a calculated value depending on if a default value was specified or not.
*/
isOptional: boolean;
+
/**
* Dynamic flag
*
* When `dynamic` is `true`, changes to the parameter value will not cause the state to be entered/exited.
+ * The resolves will not be re-fetched, nor will views be reloaded.
*
- * The resolves will not be re-fetched, nor will views be recreated.
+ * Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited).
+ * When a parameter is `dynamic`, a transition still occurs, but it does not cause the state to exit/enter.
+ *
+ * This can be useful to build UI where the component updates itself when the param values change.
+ * A common scenario where this is useful is searching/paging/sorting.
*/
dynamic: boolean;
+
+ /**
+ * Disables url-encoding of parameter values
+ *
+ * When `true`, parameter values are not url-encoded.
+ * This is commonly used to allow "slug" urls, with a parameter value including non-semantic slashes.
+ *
+ * #### Example:
+ * ```js
+ * url: '/product/:slug',
+ * params: {
+ * slug: { type: 'string', raw: true }
+ * }
+ * ```
+ *
+ * This allows a URL parameter of `{ slug: 'camping/tents/awesome_tent' }`
+ * to serialize to `/product/camping/tents/awesome_tent`
+ * instead of `/product/camping%2Ftents%2Fawesome_tent`.
+ *
+ * ### Decoding warning
+ *
+ * The decoding behavior of raw parameters is not defined.
+ * For example, given a url template such as `/:raw1/:raw2`
+ * the url `/foo/bar/baz/qux/`, there is no way to determine which slashes belong to which params.
+ *
+ * It's generally safe to use a raw parameter at the end of a path, like '/product/:slug'.
+ * However, beware of the characters you allow in your raw parameter values.
+ * Avoid unencoded characters that could disrupt normal URL parsing, such as `?` and `#`.
+ */
+ raw: boolean;
}
export interface Replace {
@@ -240,9 +288,10 @@ export interface Replace {
to: string;
}
-
/**
- * Definition for a custom [[ParamType]]
+ * Describes a custom [[ParamType]]
+ *
+ * See: [[UrlMatcherFactory.type]]
*
* A developer can create a custom parameter type definition to customize the encoding and decoding of parameter values.
* The definition should implement all the methods of this interface.
@@ -254,11 +303,10 @@ export interface Replace {
* - date
* - array of
* - custom object
- * - some custom string representation
+ * - some internal string representation
*
* Typed parameter definitions control how parameter values are encoded (to the URL) and decoded (from the URL).
- * UI-Router always provides the decoded parameter values to the user from methods such as [[Transition.params]].
- *
+ * UI-Router always provides the decoded parameter values to the user (from methods such as [[Transition.params]])).
*
* For example, if a state has a url of `/foo/{fooId:int}` (the `fooId` parameter is of the `int` ParamType)
* and if the browser is at `/foo/123`, then the 123 is parsed as an integer:
@@ -358,13 +406,17 @@ export interface ParamTypeDefinition {
* If your custom type encodes the parameter to a specific type, check for that type here.
* For example, if your custom type decodes the URL parameter value as an array of ints, return true if the
* input is an array of ints:
- * `(val) => Array.isArray(val) && array.reduce((acc, x) => acc && parseInt(val, 10) === val, true)`.
+ *
+ * ```
+ * is: (val) => Array.isArray(val) && array.reduce((acc, x) => acc && parseInt(val, 10) === val, true)
+ * ```
+ *
* If your type decodes the URL parameter value to a custom string, check that the string matches
* the pattern (don't use an arrow fn if you need `this`): `function (val) { return !!this.pattern.exec(val) }`
*
* Note: This method is _not used to check if the URL matches_.
- * It's used to check if a _decoded value is this type_.
- * Use [[pattern]] to check the URL.
+ * It's used to check if a _decoded value *is* this type_.
+ * Use [[pattern]] to check the encoded value in the URL.
*
* @param val The value to check.
* @param key If the type check is happening in the context of a specific [[UrlMatcher]] object,
@@ -377,11 +429,14 @@ export interface ParamTypeDefinition {
/**
* Encodes a custom/native type value to a string that can be embedded in a URL.
*
- * Note that the return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
- * only needs to be a representation of `val` that has been encoded as a string.
+ * Note that the return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`).
+ * It only needs to be a representation of `val` that has been encoded as a string.
*
- * For example, if your type decodes to an array of ints, then encode the array of ints as a string here:
- * `(intarray) => intarray.join("-")`
+ * For example, if your custom type decodes to an array of ints, then encode the array of ints to a string here:
+ *
+ * ```js
+ * encode: (intarray) => intarray.join("-")
+ * ```
*
* Note: in general, [[encode]] and [[decode]] should be symmetrical. That is, `encode(decode(str)) === str`
*
@@ -395,7 +450,9 @@ export interface ParamTypeDefinition {
* Decodes a parameter value string (from URL string or transition param) to a custom/native value.
*
* For example, if your type decodes to an array of ints, then decode the string as an array of ints here:
- * `(str) => str.split("-").map(str => parseInt(str, 10))`
+ * ```js
+ * decode: (str) => str.split("-").map(str => parseInt(str, 10))
+ * ```
*
* Note: in general, [[encode]] and [[decode]] should be symmetrical. That is, `encode(decode(str)) === str`
*
@@ -409,7 +466,9 @@ export interface ParamTypeDefinition {
* Determines whether two decoded values are equivalent.
*
* For example, if your type decodes to an array of ints, then check if the arrays are equal:
- * `(a, b) => a.length === b.length && a.reduce((acc, x, idx) => acc && x === b[idx], true)`
+ * ```js
+ * equals: (a, b) => a.length === b.length && a.reduce((acc, x, idx) => acc && x === b[idx], true)
+ * ```
*
* @param a A value to compare against.
* @param b A value to compare against.
@@ -420,7 +479,7 @@ export interface ParamTypeDefinition {
/**
* A regular expression that matches the encoded parameter type
*
- * This regular expression is used to match the parameter type in the URL.
+ * This regular expression is used to match an encoded parameter value **in the URL**.
*
* For example, if your type encodes as a dash-separated numbers, match that here:
* `new RegExp("[0-9]+(?:-[0-9]+)*")`.
diff --git a/src/params/param.ts b/src/params/param.ts
index f820e887..a48cb140 100644
--- a/src/params/param.ts
+++ b/src/params/param.ts
@@ -28,7 +28,12 @@ function getType(cfg: ParamDeclaration, urlType: ParamType, location: DefType, i
if (cfg.type && urlType && urlType.name !== 'string') throw new Error(`Param '${id}' has two type configurations.`);
if (cfg.type && urlType && urlType.name === 'string' && paramTypes.type(cfg.type as string)) return paramTypes.type(cfg.type as string);
if (urlType) return urlType;
- if (!cfg.type) return (location === DefType.CONFIG ? paramTypes.type("any") : paramTypes.type("string"));
+ if (!cfg.type) {
+ let type = location === DefType.CONFIG ? "any" :
+ location === DefType.PATH ? "path" :
+ location === DefType.SEARCH ? "query" : "string";
+ return paramTypes.type(type);
+ }
return cfg.type instanceof ParamType ? cfg.type : paramTypes.type(cfg.type as string);
}
diff --git a/src/params/paramTypes.ts b/src/params/paramTypes.ts
index d13537eb..d9794124 100644
--- a/src/params/paramTypes.ts
+++ b/src/params/paramTypes.ts
@@ -1,58 +1,298 @@
-/** @module params */ /** for typedoc */
-import { fromJson, toJson, identity, equals, inherit, map, extend } from "../common/common";
+/**
+ * @coreapi
+ * @module params
+ */ /** for typedoc */
+import { fromJson, toJson, identity, equals, inherit, map, extend, pick } from "../common/common";
import { isDefined, isNullOrUndefined } from "../common/predicates";
import { is } from "../common/hof";
import { services } from "../common/coreservices";
import { ParamType } from "./type";
import { ParamTypeDefinition } from "./interface";
-// Use tildes to pre-encode slashes.
-// If the slashes are simply URLEncoded, the browser can choose to pre-decode them,
-// and bidirectional encoding/decoding fails.
-// Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character
-function valToString(val: any) { return val != null ? val.toString().replace(/(~|\/)/g, m => ({'~':'~~', '/':'~2F'}[m])) : val; }
-function valFromString(val: string) { return val != null ? val.toString().replace(/(~~|~2F)/g, m => ({'~~':'~', '~2F':'/'}[m])) : val; }
-
+/**
+ * A registry for parameter types.
+ *
+ * This registry manages the built-in (and custom) parameter types.
+ *
+ * The built-in parameter types are:
+ *
+ * - [[string]]
+ * - [[path]]
+ * - [[query]]
+ * - [[hash]]
+ * - [[int]]
+ * - [[bool]]
+ * - [[date]]
+ * - [[json]]
+ * - [[any]]
+ */
export class ParamTypes {
+ /** @hidden */
types: any;
+ /** @hidden */
enqueue: boolean = true;
+ /** @hidden */
typeQueue: any[] = [];
- private defaultTypes: any = {
- "hash": {
- encode: valToString,
- decode: valFromString,
- is: is(String),
- pattern: /.*/,
- equals: (a: any, b: any) => a == b // allow coersion for null/undefined/""
- },
- "string": {
- encode: valToString,
- decode: valFromString,
- is: is(String),
+ /**
+ * Built-in parameter type: `string`
+ *
+ * This parameter type coerces values to strings.
+ * It matches anything (`new RegExp(".*")`) in the URL
+ */
+ static string: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `path`
+ *
+ * This parameter type is the default type for path parameters.
+ * A path parameter is any parameter declared in the path portion of a url
+ *
+ * - `/foo/:param1/:param2`: two path parameters
+ *
+ * This parameter type behaves exactly like the [[string]] type with one exception.
+ * When matching parameter values in the URL, the `path` type does not match forward slashes `/`.
+ *
+ * #### Angular 1 note:
+ * In ng1, this type is overridden with one that pre-encodes slashes as `~2F` instead of `%2F`.
+ * For more details about this angular 1 behavior, see: https://github.com/angular-ui/ui-router/issues/2598
+ */
+ static path: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `query`
+ *
+ * This parameter type is the default type for query/search parameters.
+ * It behaves the same as the [[string]] parameter type.
+ *
+ * A query parameter is any parameter declared in the query/search portion of a url
+ *
+ * - `/bar?param2`: a query parameter
+ */
+ static query: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `hash`
+ *
+ * This parameter type is used for the `#` parameter (the hash)
+ * It behaves the same as the [[string]] parameter type.
+ * @coreapi
+ */
+ static hash: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `int`
+ *
+ * This parameter type serializes javascript integers (`number`s which represent an integer) to the URL.
+ *
+ * #### Example:
+ * ```js
+ * .state({
+ * name: 'user',
+ * url: '/user/{id:int}'
+ * });
+ * ```
+ * ```js
+ * $state.go('user', { id: 1298547 });
+ * ```
+ *
+ * The URL will serialize to: `/user/1298547`.
+ *
+ * When the parameter value is read, it will be the `number` `1298547`, not the string `"1298547"`.
+ */
+ static int: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `bool`
+ *
+ * This parameter type serializes `true`/`false` as `1`/`0`
+ *
+ * #### Example:
+ * ```js
+ * .state({
+ * name: 'inbox',
+ * url: '/inbox?{unread:bool}'
+ * });
+ * ```
+ * ```js
+ * $state.go('inbox', { unread: true });
+ * ```
+ *
+ * The URL will serialize to: `/inbox?unread=1`.
+ *
+ * Conversely, if the url is `/inbox?unread=0`, the value of the `unread` parameter will be a `false`.
+ */
+ static bool: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `date`
+ *
+ * This parameter type can be used to serialize Javascript dates as parameter values.
+ *
+ * #### Example:
+ * ```js
+ * .state({
+ * name: 'search',
+ * url: '/search?{start:date}'
+ * });
+ * ```
+ * ```js
+ * $state.go('search', { start: new Date(2000, 0, 1) });
+ * ```
+ *
+ * The URL will serialize to: `/search?start=2000-01-01`.
+ *
+ * Conversely, if the url is `/search?start=2016-12-25`, the value of the `start` parameter will be a `Date` object where:
+ *
+ * - `date.getFullYear() === 2016`
+ * - `date.getMonth() === 11` (month is 0-based)
+ * - `date.getDate() === 25`
+ */
+ static date: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `json`
+ *
+ * This parameter type can be used to serialize javascript objects into the URL using JSON serialization.
+ *
+ * #### Example:
+ * This example serializes an plain javascript object to the URL
+ * ```js
+ * .state({
+ * name: 'map',
+ * url: '/map/{coords:json}'
+ * });
+ * ```
+ * ```js
+ * $state.go('map', { coords: { x: 10399.2, y: 49071 });
+ * ```
+ *
+ * The URL will serialize to: `/map/%7B%22x%22%3A10399.2%2C%22y%22%3A49071%7D`
+ */
+ static json: ParamTypeDefinition;
+
+ /**
+ * Built-in parameter type: `any`
+ *
+ * This parameter type is used by default for url-less parameters (parameters that do not appear in the URL).
+ * This type does not encode or decode.
+ * It is compared using a deep `equals` comparison.
+ *
+ * #### Example:
+ * This example defines a non-url parameter on a [[StateDeclaration]].
+ * ```js
+ * .state({
+ * name: 'new',
+ * url: '/new',
+ * params: {
+ * inrepyto: null
+ * }
+ * });
+ * ```
+ * ```js
+ * $state.go('new', { inreplyto: currentMessage });
+ * ```
+ */
+ static any: ParamTypeDefinition;
+
+
+ /** @internalapi */
+ private defaultTypes: any = pick(ParamTypes.prototype, "hash", "string", "query", "path", "int", "bool", "date", "json", "any");
+
+ /** @internalapi */
+ constructor() {
+ // Register default types. Store them in the prototype of this.types.
+ const makeType = (definition: ParamTypeDefinition, name: string) =>
+ new ParamType(extend({ name }, definition));
+ this.types = inherit(map(this.defaultTypes, makeType), {});
+ }
+
+ /** @internalapi */
+ dispose() {
+ this.types = {};
+ }
+
+ /**
+ * Registers a parameter type
+ *
+ * End users should call [[UrlMatcherFactory.type]], which delegates to this method.
+ */
+ type(name: string, definition?: ParamTypeDefinition, definitionFn?: () => ParamTypeDefinition) {
+ if (!isDefined(definition)) return this.types[name];
+ if (this.types.hasOwnProperty(name)) throw new Error(`A type named '${name}' has already been defined.`);
+
+ this.types[name] = new ParamType(extend({ name }, definition));
+
+ if (definitionFn) {
+ this.typeQueue.push({ name, def: definitionFn });
+ if (!this.enqueue) this._flushTypeQueue();
+ }
+
+ return this;
+ }
+
+ /** @internalapi */
+ _flushTypeQueue() {
+ while (this.typeQueue.length) {
+ let type = this.typeQueue.shift();
+ if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
+ extend(this.types[type.name], services.$injector.invoke(type.def));
+ }
+ }
+}
+
+/** @hidden */
+function initDefaultTypes() {
+ const valToString = (val: any) =>
+ val != null ? val.toString() : val;
+
+ const defaultTypeBase = {
+ encode: valToString,
+ decode: valToString,
+ is: is(String),
+ pattern: /.*/,
+ equals: (a: any, b: any) => a == b, // allow coersion for null/undefined/""
+ };
+
+ const makeDefaultType = (def) =>
+ extend({}, defaultTypeBase, def) as ParamTypeDefinition;
+
+ // Default Parameter Type Definitions
+ extend(ParamTypes.prototype, {
+ hash: makeDefaultType({}),
+
+ query: makeDefaultType({}),
+
+ string: makeDefaultType({}),
+
+ path: makeDefaultType({
pattern: /[^/]*/
- },
- "int": {
- encode: valToString,
- decode(val: string) { return parseInt(val, 10); },
- is(val: any) { return !isNullOrUndefined(val) && this.decode(val.toString()) === val; },
- pattern: /-?\d+/
- },
- "bool": {
+ }),
+
+ int: makeDefaultType({
+ decode: (val: string) => parseInt(val, 10),
+ is: function(val: any) {
+ return !isNullOrUndefined(val) && this.decode(val.toString()) === val;
+ },
+ pattern: /-?\d+/,
+ }),
+
+ bool: makeDefaultType({
encode: (val: any) => val && 1 || 0,
decode: (val: string) => parseInt(val, 10) !== 0,
is: is(Boolean),
pattern: /0|1/
- },
- "date": {
- encode(val: any) {
+ }),
+
+ date: makeDefaultType({
+ encode: function(val: any) {
return !this.is(val) ? undefined : [
val.getFullYear(),
('0' + (val.getMonth() + 1)).slice(-2),
('0' + val.getDate()).slice(-2)
].join("-");
},
- decode(val: string) {
+ decode: function(val: string) {
if (this.is(val)) return val as Date;
let match = this.capture.exec(val);
return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
@@ -64,51 +304,26 @@ export class ParamTypes {
},
pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
- },
- "json": {
+ }),
+
+ json: makeDefaultType({
encode: toJson,
decode: fromJson,
is: is(Object),
equals: equals,
pattern: /[^/]*/
- },
- "any": { // does not encode/decode
+ }),
+
+ // does not encode/decode
+ any: makeDefaultType({
encode: identity,
decode: identity,
+ is: () => true,
equals: equals,
- pattern: /.*/
- }
- };
-
- constructor() {
- // Register default types. Store them in the prototype of this.types.
- const makeType = (definition: ParamTypeDefinition, name: string) => new ParamType(extend({ name }, definition));
- this.types = inherit(map(this.defaultTypes, makeType), {});
- }
-
- /** @internalapi */
- dispose() {
- this.types = {};
- }
+ }),
+ })
- type(name: string, definition?: ParamTypeDefinition, definitionFn?: () => ParamTypeDefinition) {
- if (!isDefined(definition)) return this.types[name];
- if (this.types.hasOwnProperty(name)) throw new Error(`A type named '${name}' has already been defined.`);
+}
- this.types[name] = new ParamType(extend({ name }, definition));
+initDefaultTypes();
- if (definitionFn) {
- this.typeQueue.push({ name, def: definitionFn });
- if (!this.enqueue) this._flushTypeQueue();
- }
- return this;
- }
-
- _flushTypeQueue() {
- while (this.typeQueue.length) {
- let type = this.typeQueue.shift();
- if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
- extend(this.types[type.name], services.$injector.invoke(type.def));
- }
- }
-}
diff --git a/src/params/type.ts b/src/params/type.ts
index 461bf6bf..f93c5bab 100644
--- a/src/params/type.ts
+++ b/src/params/type.ts
@@ -5,6 +5,7 @@ import {ParamTypeDefinition} from "./interface";
/**
* Wraps up a `ParamType` object to handle array values.
+ * @internapi
*/
function ArrayType(type: ParamType, mode: (boolean|"auto")) {
// Wrap non-array value as array
@@ -59,25 +60,28 @@ function ArrayType(type: ParamType, mode: (boolean|"auto")) {
}
/**
- * A class that implements Custom Parameter Type functionality.
+ * An internal class which implements [[ParamTypeDefinition]].
*
- * This class has naive implementations for all the [[ParamTypeDefinition]] methods.
+ * A [[ParamTypeDefinition]] is a plain javascript object used to register custom parameter types.
+ * When a param type definition is registered, an instance of this class is created internally.
*
- * An instance of this class is created when a custom [[ParamTypeDefinition]] object is registered with the [[UrlMatcherFactory.type]].
+ * This class has naive implementations for all the [[ParamTypeDefinition]] methods.
*
* Used by [[UrlMatcher]] when matching or formatting URLs, or comparing and validating parameter values.
*
- * @example
- * ```
- *
- * {
+ * #### Example:
+ * ```js
+ * var paramTypeDef = {
* decode: function(val) { return parseInt(val, 10); },
* encode: function(val) { return val && val.toString(); },
* equals: function(a, b) { return this.is(a) && a === b; },
* is: function(val) { return angular.isNumber(val) && isFinite(val) && val % 1 === 0; },
* pattern: /\d+/
* }
+ *
+ * var paramType = new ParamType(paramTypeDef);
* ```
+ * @internalapi
*/
export class ParamType implements ParamTypeDefinition {
pattern: RegExp = /.*/;
diff --git a/src/transition/transitionEventType.ts b/src/transition/transitionEventType.ts
index e2fe54e4..6da7a7ba 100644
--- a/src/transition/transitionEventType.ts
+++ b/src/transition/transitionEventType.ts
@@ -1,3 +1,4 @@
+/** @module transition */ /** */
import { TransitionHookPhase, PathType } from "./interface";
import { GetErrorHandler, GetResultHandler, TransitionHook } from "./transitionHook";
/**
@@ -5,7 +6,6 @@ import { GetErrorHandler, GetResultHandler, TransitionHook } from "./transitionH
* Plugins can define custom hook types, such as sticky states does for `onInactive`.
*
* @interalapi
- * @module transition
*/
export class TransitionEventType {
diff --git a/src/url/urlMatcher.ts b/src/url/urlMatcher.ts
index 237da79a..1c25cd4b 100644
--- a/src/url/urlMatcher.ts
+++ b/src/url/urlMatcher.ts
@@ -56,15 +56,14 @@ const memoizeTo = (obj: Obj, prop: string, fn: Function) =>
* (`/somePath/{param:[a-zA-Z0-9]+}`) in a curly brace placeholder.
* The regexp must match for the url to be matched.
* Should the regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
- * Note that a RegExp parameter will encode its value with `string` ParamType encoding: "/" as "~2F", and "~" as "~~".
- * When matching these characters, use the encoded versions in the regexp.
- * See issue [#2540](https://github.com/angular-ui/ui-router/issues/2540) for more information.
*
- * - *Custom parameter types* may also be specified after a colon (`/somePath/{param:int}`)
- * in curly brace parameters. See [[UrlMatcherFactory.type]] for more information.
+ * Note: a RegExp parameter will encode its value using either [[ParamTypes.path]] or [[ParamTypes.query]].
*
- * - *Catch-all parameters* are defined using an asterisk placeholder (`/somepath/*catchallparam`). A catch-all
- * parameter value will contain the remainder of the URL.
+ * - *Custom parameter types* may also be specified after a colon (`/somePath/{param:int}`) in curly brace parameters.
+ * See [[UrlMatcherFactory.type]] for more information.
+ *
+ * - *Catch-all parameters* are defined using an asterisk placeholder (`/somepath/*catchallparam`).
+ * A catch-all * parameter value will contain the remainder of the URL.
*
* ---
*
@@ -158,18 +157,21 @@ export class UrlMatcher {
// The number of segments is always 1 more than the number of parameters.
const matchDetails = (m: RegExpExecArray, isSearch: boolean) => {
// IE[78] returns '' for unmatched groups instead of null
- let id = m[2] || m[3], regexp = isSearch ? m[4] : m[4] || (m[1] === '*' ? '.*' : null);
+ let id = m[2] || m[3];
+ let regexp = isSearch ? m[4] : m[4] || (m[1] === '*' ? '.*' : null);
+
+ const makeRegexpType = (regexp) => inherit(paramTypes.type(isSearch ? "query" : "path"), {
+ pattern: new RegExp(regexp, this.config.caseInsensitive ? 'i' : undefined)
+ });
return {
id,
regexp,
cfg: this.config.params[id],
segment: pattern.substring(last, m.index),
- type: !regexp ? null : paramTypes.type(regexp || "string") || inherit(paramTypes.type("string"), {
- pattern: new RegExp(regexp, this.config.caseInsensitive ? 'i' : undefined)
- })
+ type: !regexp ? null : paramTypes.type(regexp) || makeRegexpType(regexp)
};
- }
+ };
let p: any, segment: string;
diff --git a/test/urlMatcherFactorySpec.ts b/test/urlMatcherFactorySpec.ts
index 2850aa1d..40bd5bb8 100644
--- a/test/urlMatcherFactorySpec.ts
+++ b/test/urlMatcherFactorySpec.ts
@@ -71,44 +71,6 @@ describe("UrlMatcher", function () {
expect(matcher.format(array)).toBe('/?foo=bar&foo=baz');
});
- it("should encode and decode slashes in parameter values as ~2F", function () {
- var matcher1 = makeMatcher('/:foo');
-
- expect(matcher1.format({ foo: "/" })).toBe('/~2F');
- expect(matcher1.format({ foo: "//" })).toBe('/~2F~2F');
-
- expect(matcher1.exec('/')).toBeTruthy();
- expect(matcher1.exec('//')).not.toBeTruthy();
-
- expect(matcher1.exec('/')['foo']).toBe("");
- expect(matcher1.exec('/123')['foo']).toBe("123");
- expect(matcher1.exec('/~2F')['foo']).toBe("/");
- expect(matcher1.exec('/123~2F')['foo']).toBe("123/");
-
- // param :foo should match between two slashes
- var matcher2 = makeMatcher('/:foo/');
-
- expect(matcher2.exec('/')).not.toBeTruthy();
- expect(matcher2.exec('//')).toBeTruthy();
-
- expect(matcher2.exec('//')['foo']).toBe("");
- expect(matcher2.exec('/123/')['foo']).toBe("123");
- expect(matcher2.exec('/~2F/')['foo']).toBe("/");
- expect(matcher2.exec('/123~2F/')['foo']).toBe("123/");
- });
-
- it("should encode and decode tildes in parameter values as ~~", function () {
- var matcher1 = makeMatcher('/:foo');
-
- expect(matcher1.format({ foo: "abc" })).toBe('/abc');
- expect(matcher1.format({ foo: "~abc" })).toBe('/~~abc');
- expect(matcher1.format({ foo: "~2F" })).toBe('/~~2F');
-
- expect(matcher1.exec('/abc')['foo']).toBe("abc");
- expect(matcher1.exec('/~~abc')['foo']).toBe("~abc");
- expect(matcher1.exec('/~~2F')['foo']).toBe("~2F");
- });
-
describe("snake-case parameters", function() {
it("should match if properly formatted", function() {
var matcher = makeMatcher('/users/?from&to&snake-case&snake-case-triple');