Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0e10658
refactor(Node): rename ownParams to ownParamValues to be consistent
christopherthielen Sep 20, 2015
d4d9e80
fix(Transition): reject promise in .run() if there is an error()
christopherthielen Sep 23, 2015
52aeacf
refactor(StateHandler): rename stateHandler to stateHooks, move $stat…
christopherthielen Sep 23, 2015
6181a08
refactor(StateHooks): StateHooks now has a Transition as part of its …
christopherthielen Sep 23, 2015
bc4c335
refactor(StateHandler): rename stateHandler to stateHooks, move $stat…
christopherthielen Sep 23, 2015
96df71e
refactor(StateHooks): WIP: Split out $state hook management further
christopherthielen Sep 25, 2015
d98b32e
refactor(TransitionManager): Improve clarity of TransitionManager code
christopherthielen Sep 27, 2015
46f65bb
feat(Resolve): When redirecting from a parent to a child, re-use any …
christopherthielen Sep 30, 2015
ef46b46
refactor(HookRegistry): slightly simplify the deregistration fn
christopherthielen Oct 2, 2015
5726cd2
refactor(Transition): broke out transition run method into Transition…
christopherthielen Oct 2, 2015
f065353
refactor(Type): extract ArrayType pseudo-class
nateabele Sep 21, 2015
7175a5c
refactor(*): simplify function definitions
nateabele Sep 24, 2015
c3bada1
style(*): whitespace cleanup
nateabele Sep 25, 2015
c87a0c8
refactor(*): TS/ES6ification of State & UrlRouter
nateabele Oct 3, 2015
33e38b1
refactor(*): Convert UrlMatchers to tree
nateabele Oct 3, 2015
cc76df2
refactor(*): consolidate path handling
nateabele Oct 16, 2015
7a4a116
chore(Params): fixing tests: remove private state._params and add pub…
christopherthielen Oct 24, 2015
83bd52a
test(Transition): Switch to Typescript and fix parts of the transitio…
christopherthielen Oct 24, 2015
7013035
test(Resolve): fix resolve test harness
christopherthielen Oct 24, 2015
eaa2436
fix(Transition): Transition.params() should reduce the values from ea…
christopherthielen Oct 25, 2015
6935551
fix(TargetState): default 'null' params to empty obj {}
christopherthielen Oct 25, 2015
5487fa4
fix(Node): use Param.value to apply default values (not Param.type.$n…
christopherthielen Oct 25, 2015
1cd871a
fix(UrlMatcher): Use entire UrlMatcher[] path to match URLs, but map …
christopherthielen Oct 25, 2015
d5ff3a8
fix(UrlMatcher): Fixed handling of url fragment (hash)
christopherthielen Oct 25, 2015
165aaed
fix(StateBuilder): include url and non-url params in state.params
christopherthielen Oct 25, 2015
735571d
test(transition): fix makeTransition to bind resolveContext to the fr…
christopherthielen Oct 25, 2015
470e7e6
fix(UrlMatcher): allow validates() to take empty params obj
christopherthielen Oct 25, 2015
eba85e2
fix(Node): Node.clone() retains the original node's resolvable state
christopherthielen Oct 25, 2015
d2e7ffb
test(StateBuilder): match tests to new interfaces
nateabele Oct 26, 2015
e5485ad
fix(Transition): reimplement Transition.ignored() using path-based pa…
christopherthielen Oct 26, 2015
9fec82d
fix(State): do not return duplicate Param objs for url params in Stat…
christopherthielen Oct 26, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 52 additions & 27 deletions src/common/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ export function pipe(...funcs: Function[]): (obj: any) => any {
*/
export const prop = (name: string) => (obj: any) => obj && obj[name];

/**
* Given a property name and a value, returns a function that returns a boolean based on whether
* the passed object has a property that matches the value
* let obj = { foo: 1, name: "blarg" };
* let getName = propEq("name", "blarg");
* getName(obj) === true
*/
export const propEq = curry((name: string, val: any, obj: any) => obj && obj[name] === val);

/**
* Given a dotted property name, returns a function that returns a nested property from an object, or undefined
* let obj = { id: 1, nestedObj: { foo: 1, name: "blarg" }, };
Expand All @@ -72,26 +81,22 @@ export const parse = (name: string) => pipe.apply(null, name.split(".").map(prop
* Given a function that returns a truthy or falsey value, returns a
* function that returns the opposite (falsey or truthy) value given the same inputs
*/
export const not = (fn) => (function() { return !fn.apply(null, [].slice.call(arguments)); });
export const not = (fn) => (...args) => !fn.apply(null, args);

/**
* Given two functions that return truthy or falsey values, returns a function that returns truthy
* if both functions return truthy for the given arguments
*/
export function and(fn1, fn2): Function {
return function() {
return fn1.apply(null, [].slice.call(arguments)) && fn2.apply(null, [].slice.call(arguments));
};
return (...args) => fn1.apply(null, args) && fn2.apply(null, args);
}

/**
* Given two functions that return truthy or falsey values, returns a function that returns truthy
* if at least one of the functions returns truthy for the given arguments
*/
export function or(fn1, fn2): Function {
return function() {
return fn1.apply(null, [].slice.call(arguments)) || fn2.apply(null, [].slice.call(arguments));
};
return (...args) => fn1.apply(null, args) || fn2.apply(null, args);
}

/** Given a class, returns a Predicate function that returns true if the object is of that class */
Expand Down Expand Up @@ -182,6 +187,9 @@ export function merge(dst, ...objs: Object[]) {
return dst;
}

/** Reduce function that merges each element of the list into a single object, using angular.extend */
export const mergeR = (memo, item) => extend(memo, item);

/**
* Finds the common ancestor path between two states.
*
Expand Down Expand Up @@ -282,12 +290,20 @@ export function map(collection: any, callback: any): any {
return result;
}

/** Push an object to an array, return the array */
export const push = (arr: any[], obj) => { arr.push(obj); return arr; };
/** Given an object, return its enumerable property values */
export const values: (<T> (obj: TypedMap<T>) => T[]) = (obj) => Object.keys(obj).map(key => obj[key]);

/** Reduce function that returns true if all of the values are truthy. */
export const allTrueR = (memo: boolean, elem) => memo && elem;
/** Reduce function that returns true if any of the values are truthy. */
export const anyTrueR = (memo: boolean, elem) => memo || elem;

/** Reduce function that pushes an object to an array, then returns the array */
export const pushR = (arr: any[], obj) => { arr.push(obj); return arr; };
/** Reduce function which un-nests a single level of arrays */
export const unnestR = (memo: any[], elem) => memo.concat(elem);
/** Reduce function which recursively un-nests all arrays */
export const flattenR = (memo: any[], elem) => isArray(elem) ? memo.concat(elem.reduce(flattenR, [])) : push(memo, elem);
export const flattenR = (memo: any[], elem) => isArray(elem) ? memo.concat(elem.reduce(flattenR, [])) : pushR(memo, elem);
/** Return a new array with a single level of arrays unnested. */
export const unnest = (arr: any[]) => arr.reduce(unnestR, []);
/** Return a completely flattened version of an array. */
Expand All @@ -307,21 +323,27 @@ export function assertPredicate<T>(fn: Predicate<T>, errMsg: string = "assert fa
export const pairs = (object) => Object.keys(object).map(key => [ key, object[key]] );

/**
* Sets a key/val pair on an object, then returns the object.
* Given two or more parallel arrays, returns an array of tuples where
* each tuple is composed of [ a[i], b[i], ... z[i] ]
*
* Use as a reduce function for an array of key/val pairs
* let foo = [ 0, 2, 4, 6 ];
* let bar = [ 1, 3, 5, 7 ];
* let baz = [ 10, 30, 50, 70 ];
* tuples(foo, bar); // [ [0, 1], [2, 3], [4, 5], [6, 7] ]
* tuples(foo, bar, baz); // [ [0, 1, 10], [2, 3, 30], [4, 5, 50], [6, 7, 70] ]
*
* Given:
* var keys = [ "fookey", "barkey" ]
* var pairsToObj = keys.reduce((memo, key) => applyPairs(memo, key, true), {})
* Then:
* true === angular.equals(pairsToObj, { fookey: true, barkey: true })
*/
export function applyPairs(obj: TypedMap<any>, arrayOrKey: string, val: any);
export function arrayTuples(...arrayArgs: any[]): any[] {
if (arrayArgs.length === 0) return [];
let length = arrayArgs.reduce((min, arr) => Math.min(arr.length, min), 9007199254740991); // aka 2^53 − 1 aka Number.MAX_SAFE_INTEGER
return Array.apply(null, Array(length)).map((ignored, idx) => arrayArgs.map(arr => arr[idx]).reduce(pushR, []));
}

/**
* Sets a key/val pair on an object, then returns the object.
* Reduce function which builds an object from an array of [key, value] pairs.
* Each iteration sets the key/val pair on the memo object, then returns the memo for the next iteration.
*
* Use as a reduce function for an array of key/val pairs
* Each keyValueTuple should be an array with values [ key: string, value: any ]
*
* Given:
* var pairs = [ ["fookey", "fooval"], ["barkey","barval"] ]
Expand All @@ -330,14 +352,12 @@ export function applyPairs(obj: TypedMap<any>, arrayOrKey: string, val: any);
* Then:
* true === angular.equals(pairsToObj, { fookey: "fooval", barkey: "barval" })
*/
export function applyPairs(obj: TypedMap<any>, arrayOrKey: any[]);
export function applyPairs(obj: TypedMap<any>, arrayOrKey: (string|any[]), val?: any) {
let key;
if (isDefined(val)) key = arrayOrKey;
if (isArray(arrayOrKey)) [key, val] = <any[]> arrayOrKey;
export function applyPairs(memo: TypedMap<any>, keyValTuple: any[]) {
let key, value;
if (isArray(keyValTuple)) [key, value] = keyValTuple;
if (!isString(key)) throw new Error("invalid parameters to applyPairs");
obj[key] = val;
return obj;
memo[key] = value;
return memo;
}

// Checks if a value is injectable
Expand Down Expand Up @@ -373,6 +393,11 @@ export function padString(length: number, str: string) {
return str;
}

export function tail<T>(collection: T[]): T;
export function tail(collection: any[]): any {
return collection.length && collection[collection.length - 1] || undefined;
}


/**
* @ngdoc overview
Expand Down
20 changes: 10 additions & 10 deletions src/common/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ function normalizedCat(input: Category): string {
return isNumber(input) ? Category[input] : Category[Category[input]];
}

let format = pattern([
[not(isDefined), val("undefined")],
[isNull, val("null")],
[isPromise, promiseToString],
[is(Transition), invoke("toString")],
[is(Resolvable), invoke("toString")],
[isInjectable, functionToString],
[val(true), identity]
]);

function stringify(o) {
let format = pattern([
[not(isDefined), val("undefined")],
[isNull, val("null")],
[isPromise, promiseToString],
[is(Transition), invoke("toString")],
[is(Resolvable), invoke("toString")],
[isInjectable, functionToString],
[val(true), identity]
]);

return JSON.stringify(o, (key, val) => format(val)).replace(/\\"/g, '"');
}

Expand Down
4 changes: 1 addition & 3 deletions src/params/interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import ParamValues from "./paramValues";

export interface IRawParams {
[key: string]: any
}
export type IParamsOrArray = (IRawParams|IRawParams[]|ParamValues);
export type IParamsOrArray = (IRawParams|IRawParams[]);
6 changes: 0 additions & 6 deletions src/params/module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import * as param from "./param";
export {param};

import * as paramSet from "./paramSet";
export {paramSet};

import * as paramTypes from "./paramTypes";
export {paramTypes};

import * as paramValues from "./paramValues";
export {paramValues};

import * as type from "./type";
export {type};
94 changes: 71 additions & 23 deletions src/params/param.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
import {isInjectable, extend, isDefined, isString, isArray, filter, map, prop, curry} from "../common/common";
import {isInjectable, extend, isDefined, isString, isArray, filter, map, pick, prop, propEq, curry, applyPairs} from "../common/common";
import {IRawParams} from "../params/interface";
import {runtime} from "../common/angular1";
import matcherConfig from "../url/urlMatcherConfig";
import paramTypes from "./paramTypes";
import Type from "./type";

let hasOwn = Object.prototype.hasOwnProperty;
let isShorthand = cfg => ["value", "type", "squash", "array", "dynamic"].filter(hasOwn.bind(cfg || {})).length === 0;

enum DefType {
PATH, SEARCH, CONFIG
}

export default class Param {
id: string;
type: Type;
location: string;
location: DefType;
array: boolean;
squash: (boolean|string);
replace: any;
isOptional: boolean;
dynamic: boolean;
config: any;

constructor(id, type, config, location) {
constructor(id: string, type: Type, config: any, location: DefType) {
config = unwrapShorthand(config);
type = getType(config, type, location);
var arrayMode = getArrayMode();
type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
type = arrayMode ? type.$asArray(arrayMode, location === DefType.SEARCH) : type;
var isOptional = config.value !== undefined;
var dynamic = config.dynamic === true;
var squash = getSquashPolicy(config, isOptional);
var replace = getReplace(config, arrayMode, isOptional, squash);

function unwrapShorthand(config) {
var configKeys = ["value", "type", "squash", "array", "dynamic"].filter(function (key) {
return (config || {}).hasOwnProperty(key);
config = isShorthand(config) && { value: config } || config;

return extend(config, {
$$fn: isInjectable(config.value) ? config.value : () => config.value
});
var isShorthand = configKeys.length === 0;
if (isShorthand) config = {value: config};
config.$$fn = isInjectable(config.value) ? config.value : function () {
return config.value;
};
return config;
}

function getType(config, urlType, location) {
if (config.type && urlType && urlType.name !== 'string') throw new Error(`Param '${id}' has two type configurations.`);
if (config.type && urlType && urlType.name === 'string' && paramTypes.type(config.type)) return paramTypes.type(config.type);
if (urlType) return urlType;
if (!config.type) return (location === "config" ? paramTypes.type("any") : paramTypes.type("string"));
if (!config.type) return (location === DefType.CONFIG ? paramTypes.type("any") : paramTypes.type("string"));
return config.type instanceof Type ? config.type : paramTypes.type(config.type);
}

// array config: param name (param[]) overrides default settings. explicit config overrides param name.
function getArrayMode() {
var arrayDefaults = {array: (location === "search" ? "auto" : false)};
var arrayParamNomenclature = id.match(/\[\]$/) ? {array: true} : {};
var arrayDefaults = { array: (location === DefType.SEARCH ? "auto" : false) };
var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
return extend(arrayDefaults, arrayParamNomenclature, config).array;
}

Expand All @@ -60,7 +64,7 @@ export default class Param {
if (!isOptional || squash === false) return false;
if (!isDefined(squash) || squash == null) return matcherConfig.defaultSquashPolicy();
if (squash === true || isString(squash)) return squash;
throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
throw new Error(`Invalid squash policy: '${squash}'. Valid policies: false, true, or arbitrary string`);
}

function getReplace(config, arrayMode, isOptional, squash) {
Expand All @@ -69,23 +73,23 @@ export default class Param {
{from: null, to: (isOptional || arrayMode ? undefined : "")}
];
replace = isArray(config.replace) ? config.replace : [];
if (isString(squash)) replace.push({from: squash, to: undefined});
if (isString(squash)) replace.push({ from: squash, to: undefined });
configuredKeys = map(replace, prop("from"));
return filter(defaultPolicy, item => configuredKeys.indexOf(item.from) === -1).concat(replace);
}

extend(this, {id, type, location, squash, replace, isOptional, dynamic, config, array: arrayMode});
}

isDefaultValue(value: any) {
isDefaultValue(value: any): boolean {
return this.isOptional && this.type.equals(this.value(), value);
}

/**
* [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
* default value, which may be the result of an injectable function.
*/
value(value?: any) {
value(value?: any): any {
/**
* [Internal] Get the default value of a parameter, which may be an injectable function.
*/
Expand All @@ -97,19 +101,63 @@ export default class Param {
return defaultValue;
};

const hasReplaceVal = curry((val, obj) => obj.from === val);

const $replace = (value) => {
var replacement: any = map(filter(this.replace, hasReplaceVal(value)), prop("to"));
var replacement: any = map(filter(this.replace, propEq('from', value)), prop("to"));
return replacement.length ? replacement[0] : value;
};

value = $replace(value);
return !isDefined(value) ? $$getDefaultValue() : this.type.$normalize(value);
}

isSearch(): boolean {
return this.location === DefType.SEARCH;
}

validates(value: any): boolean {
// There was no parameter value, but the param is optional
if ((!isDefined(value) || value === null) && this.isOptional) return true;

// The value was not of the correct Type, and could not be decoded to the correct Type
const normalized = this.type.$normalize(value);
if (!this.type.is(normalized)) return false;

// The value was of the correct type, but when encoded, did not match the Type's regexp
const encoded = this.type.encode(normalized);
if (isString(encoded) && !this.type.pattern.exec(<string> encoded)) return false;

return true;
}

toString() {
return `{Param:${this.id} ${this.type} squash: '${this.squash}' optional: ${this.isOptional}}`;
}

}
static fromConfig(id: string, type: Type, config: any): Param {
return new Param(id, type, config, DefType.CONFIG);
}

static fromPath(id: string, type: Type, config: any): Param {
return new Param(id, type, config, DefType.PATH);
}

static fromSearch(id: string, type: Type, config: any): Param {
return new Param(id, type, config, DefType.SEARCH);
}

static values(params: Param[], values): IRawParams {
values = values || {};
return <IRawParams> params.map(param => [param.id, param.value(values[param.id])]).reduce(applyPairs, {});
}

static equals(params: Param[], values1, values2): boolean {
values1 = values1 || {};
values2 = values2 || {};
return params.map(param => param.type.equals(values1[param.id], values2[param.id])).indexOf(false) === -1;
}

static validates(params: Param[], values): boolean {
values = values || {};
return params.map(param => param.validates(values[param.id])).indexOf(false) === -1;
}
}
Loading