diff --git a/crossroads/crossroads-tests.ts b/crossroads/crossroads-tests.ts new file mode 100644 index 0000000000..3663361125 --- /dev/null +++ b/crossroads/crossroads-tests.ts @@ -0,0 +1,227 @@ +/// + +//String rule with param: +//match '/news/123' passing "123" as param to handler +var route1 = crossroads.addRoute('/news/{id}', function(id){ + console.log(id); +}); + +//String rule with optional param: +//match '/foo/123/bar' passing "123" and "bar" as param +//match '/foo/45' passing 45 as param (slug is optional) +var route2 = crossroads.addRoute('/foo/{id}/:slug:'); +//addRoute returns a Route object +route2.matched.add(console.log, console); + +//RegExp rule: +//match '/lorem/ipsum' passing "ipsum" as param to handler +//note the capturing group around segment +var route3 = crossroads.addRoute(/^\/lorem\/([a-z]+)$/, function(id){ + console.log(id); +}); + +//String rule with rest segments: +//match '/foo/123/edit' passing "123" as argument +//match '/foo/45/asd/123/edit' passing "45/asd/123" as argument +var route4 = crossroads.addRoute('/foo/{id*}/edit'); +//addRoute returns a Route object +route4.matched.add(console.log, console); + +//Query String: +//match 'foo.php?lorem=ipsum&dolor=amet' +crossroads.addRoute('foo.php{?query}', function(query){ + // query strings are decoded into objects + console.log('lorem '+ query.lorem +' dolor sit '+ query.dolor); +}); + +var sectionRoute = crossroads.addRoute('/{section}/{id}'); +function onSectionMatch(section, id){ + console.log(section +' - '+ id); +} +sectionRoute.matched.add(onSectionMatch); +//will match `sectionRoute` passing "news" and `123` as param +crossroads.parse('/news/123'); + +//will match `sectionRoute` and pass "lorem" and "ipsum" as first arguments +crossroads.parse('/news/123', ["lorem", "ipsum"]); + +var route1 = crossroads.addRoute('/news/{id}'); +crossroads.bypassed.add(function(request){ + console.log(request); +}); +//won't match any route, triggering `bypassed` Signal +crossroads.parse('/foo'); + + +crossroads.routed.add(function(request, data){ + console.log(request); + console.log(data.route +' - '+ data.params +' - '+ data.isFirst); +}); +crossroads.parse('/news/123'); //match `route1`, triggering `routed` Signal + +var otherRouter = crossroads.create(); +otherRouter.addRoute('/news/{id}', function(id){ + console.log(id); +}); +otherRouter.parse('/news/123'); + +crossroads.routed.add(otherRouter.parse, otherRouter); +crossroads.bypassed.add(otherRouter.parse, otherRouter); +// same effect as calling: `crossroads.pipe(otherRouter)` + +crossroads.normalizeFn = crossroads.NORM_AS_OBJECT; +crossroads.addRoute('/{foo}/{bar}', function(vals){ + //can access captured values as object properties + console.log(vals.foo +' - '+ vals.bar); +}); +crossroads.parse('/lorem/ipsum'); + +crossroads.normalizeFn = crossroads.NORM_AS_ARRAY; +crossroads.addRoute('/{foo}/{bar}', function(vals){ + //can access captured values as Array items + console.log(vals[0] +' - '+ vals[1]); +}); +crossroads.parse('/dolor/amet'); + +crossroads.normalizeFn = function(request, vals){ + //make sure first argument is always "news" + return ['news', vals.id]; +}; +crossroads.addRoute('/{cat}/{id}', function(cat, id){ + console.log(cat +' - '+ id); +}); +crossroads.parse('/article/123'); + +crossroads.shouldTypecast = true; //default = false +crossroads.addRoute('/news/{id}', function(id){ + console.log(id); // 12 (remove trailing zeroes since it's typecasted) +}); +crossroads.parse('/news/00012'); + +crossroads.shouldTypecast = false; //default = false +crossroads.addRoute('/news/{id}', function(id){ + console.log(id); // "00012" (keep trailing zeroes) +}); +crossroads.parse('/news/00012'); + +var sectionRouter = crossroads.create(); +var navRouter = crossroads.create(); + +sectionRouter.pipe(navRouter); +// will also call `parse()` on `navRouter` +sectionRouter.parse('foo'); + +var navRouter = crossroads.create(); + +sectionRouter.pipe(navRouter); +// will also call `parse()` on `navRouter` +sectionRouter.parse('foo'); + +sectionRouter.unpipe(navRouter); +// won't forward url since they aren't piped anymore +sectionRouter.parse('bar'); + +var route1 = crossroads.addRoute('/news/{id}'); +route1.matched.add(function(id){ + console.log('handler 1: '+ id); +}); +route1.matched.add(function(id){ + console.log('handler 2: '+ id); +}); +crossroads.parse('/news/123'); //will trigger both handlers of `route1` + +//note that `rules` keys have the same as route pattern segments +route1.rules = { + + //match only values inside array + section : ['blog', 'news', '123'], + + //validate dates on the format "yyyy-mm-dd" + date : /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/, + + /* + * @param {string|number|boolean} value Request segment value. + * @param {string} request Value passed to crossroads.parse method. + * @param {object} valuesObj Values of all pattern segments. + * @return {boolean} If segment value is valid. + */ + id : function(value, request, valuesObj){ + if(isNaN(value)){ + return false; + }else{ + if(+value < 100 && valuesObj.section == 'blog'){ + return true; + }else if(valuesObj.section == 'news'){ + return true; + }else{ + return false; + } + } + }, + + /** + * `request_` is a special rule used to validate whole request + * It can be an Array, RegExp or Function. + * Note that request will be typecasted if value is a boolean + * or number and crossroads.shouldTypecast = true (default = false). + */ + request_ : function(request){ + return (request != '123'); + }, + + /** + * Normalize params that should be dispatched by Route.matched signal + * @param {*} request Value passed to crossroads.parse method. + * @param {object} vals All segments captured by route, it also have a + * special property `vals_` which contains all the captured values and + * also a property `request_`. + * @return {array} Array containing parameters. + */ + normalize_ : function(request, vals){ + //ignore "date" since it isn't important for the application + return [vals.section, vals.id]; + } + +}; + +route1.match("/foo/2011-05-04/2"); //false. {section} isn't valid +route1.match("/blog/20110504/2"); //false. {date} isn't valid +route1.match("/blog/2011-05-04/999"); //false. {id} is too high +route1.match("/blog/2011-05-04/2"); //true. all segments validate + + +var route1 = crossroads.addRoute(/([\-\w]+)\/([\-\w]+)\/([\-\w]+)/); + +//note that `rules` keys represent capturing group index +route1.rules = { + '0' : ['blog', 'news', '123'], + '1' : /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/, + '2' : function(value, request, valuesObj){ + return ! isNaN(value); + } +}; + +route1.match("/foo/2011-05-04/2"); //false. {0} isn't valid +route1.match("/blog/20110504/2"); //false. {1} isn't valid +route1.match("/blog/2011-05-04/abc"); //false. {2} isn't numeric +route1.match("/blog/2011-05-04/2"); //true. all segments validate + +var projectsRoute = crossroads.addRoute('/projects/:id:'); +projectsRoute.add(console.log, console); +projectsRoute.greedy = true; // greedy! + +var projectDetailRoute = crossroads.addRoute('/projects/{id}', null, 2); +projectDetailRoute.add(console.log, console); + +//match `projectsRoute` +crossroads.parse('/projects'); + +//match `projectDetailRoute` (priority 2) than `projectsRoute` +crossroads.parse('/projects/123'); + +route1.match('/foo/bar'); //false +route1.match('/news/123'); //true +route1.match('/news/foo-bar'); //true + +route1.interpolate({id: 123}); // "news/123" +route1.interpolate({id: 'foo'}); // "news/foo" diff --git a/crossroads/crossroads.d.ts b/crossroads/crossroads.d.ts new file mode 100644 index 0000000000..76fad217ac --- /dev/null +++ b/crossroads/crossroads.d.ts @@ -0,0 +1,155 @@ +// Type definitions for Crossroads.js +// Project: http://millermedeiros.github.io/crossroads.js/ +// Definitions by: Diullei Gomes +// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped + +/// + +declare module CrossroadsJs { + + export interface Route { + matched: Signal; + + /** + * Signal dispatched every time a request "leaves" the route. + */ + switched: Signal; + + /** + * Object used to configure parameters/segments validation rules. + */ + rules: any; + + /** + * If crossroads should try to match this Route even after matching another Route. + */ + greedy: boolean; + + /** + * Remove route from crossroads and destroy it, releasing memory. + */ + dispose(); + + /** + * Test if Route matches against request. Return true if request validate against route rules and pattern. + */ + match(request: any): boolean; + + /** + * Return a string that matches the route replacing the capturing groups with the values provided in the replacements object. + */ + interpolate(replacements: any): string; + + /** + * Add a listener to the signal. + * + * @param listener Signal handler function. + * @param listenercontext Context on which listener will be executed (object that should represent the `this` variable inside listener function). + * @param priority The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) + */ + add(listener: Function, listenerContext?: any, priority?: Number): SignalBinding; + } + + export interface CrossRoadsStatic { + + NORM_AS_ARRAY: Function; + + NORM_AS_OBJECT: Function; + + /** + * Creates a new route pattern listener and add it to crossroads routes collection. + * + * @param pattern String pattern or Regular Expression that should be used to match against requests. + * @param handler Function that should be executed when a request matches the Route pattern. + * @param priority Route execution priority. + */ + addRoute(pattern: any, handler?: Function, priority?: number): Route; + + /** + * Remove a single route from crossroads collection. + * + * @param route Reference to the Route object returned by crossroads.addRoute(). + */ + removeRoute(route: Route); + + /** + * Remove all routes from crossroads collection. + */ + removeAllRoutes(); + + /** + * Parse a string input and dispatch matched Signal of the first Route that matches the request. + * + * @param request String that should be evaluated and matched against Routes to define which Route handlers should be executed and which parameters should be passed to the handlers. + * @param defaultargs Array containing values passed to matched/routed/bypassed signals as first arguments. Useful for node.js in case you need to access the request and response objects. + */ + parse(request: string, ...defaultArgs: any[]); + + /** + * Get number of Routes contained on the crossroads collection. + */ + getNumRoutes(): number; + + /** + * Signal dispatched every time that crossroads.parse can't find a Route that matches the request. Useful for debuging and error handling. + */ + bypassed: Signal; + + /** + * Signal dispatched every time that crossroads.parse find a Route that matches the request. Useful for debuging and for executing tasks that should happen at each routing. + */ + routed: Signal; + + /** + * Create a new independent Router instance. + */ + create(): CrossRoadsStatic; + + /** + * Sets a default function that should be used to normalize parameters before passing them to the Route.matched, works similarly to Route.rules.normalize_. + */ + normalizeFn: Function; + + /** + * Set if crossroads should typecast route paths. Default value is false (IMPORTANT: on v0.5.0 it was true by default). + */ + shouldTypecast: boolean; + + /** + * String representation of the crossroads version number (e.g. "0.6.0"). + */ + VERSION: string; + + /** + * Sets global route matching behavior to greedy so crossroads will try to match every single route with the supplied request (if true it won't stop at first match). + */ + greedy: boolean; + + /** + * Sets if the greedy routes feature is enabled. If false it won't try to match multiple routes (faster). + */ + greedyEnabled: boolean; + + /** + * Resets the Router internal state. Will clear reference to previously matched routes (so they won't dispatch switched signal when matching a new route) and reset last request. + */ + resetState(); + + /** + * Sets if Router should care about previous state, so multiple crossroads.parse() calls passing same argument would not trigger the routed, matched and bypassed signals. + */ + ignoreState: boolean; + + /** + * Pipe routers, so all crossroads.parse() calls will be forwarded to the other router as well. + */ + pipe(router: CrossRoadsStatic); + + /** + * "Ceci n'est pas une pipe" + */ + unpipe(router: CrossRoadsStatic); + } +} + +declare var crossroads: CrossroadsJs.CrossRoadsStatic; diff --git a/js-signals/js-signals.d.ts b/js-signals/js-signals.d.ts new file mode 100644 index 0000000000..4d7d33344e --- /dev/null +++ b/js-signals/js-signals.d.ts @@ -0,0 +1,91 @@ +// Type definitions for JS-Signals +// Project: http://millermedeiros.github.io/js-signals/ +// Definitions by: Diullei Gomes +// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped + +interface SignalBinding { + active: boolean; + context: any; + params: any; + detach(); + execute(paramsArr); + getListener(): Function; + getSignal(): Signal; + isBound(): boolean; + isOnce(): boolean; +} + +interface Signal { + /** + * If Signal is active and should broadcast events. + */ + active: boolean; + + /** + * If Signal should keep record of previously dispatched parameters and automatically + * execute listener during add()/addOnce() if Signal was already dispatched before. + */ + memorize: boolean; + + /** + * Signals Version Number + */ + VERSION: string; + + /** + * Add a listener to the signal. + * + * @param listener Signal handler function. + * @param listenercontext Context on which listener will be executed (object that should represent the `this` variable inside listener function). + * @param priority The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) + */ + add(listener: Function, listenerContext?: any, priority?: Number): SignalBinding; + + /** + * Add listener to the signal that should be removed after first execution (will be executed only once). + * + * @param listener Signal handler function. + * @param listenercontext Context on which listener will be executed (object that should represent the `this` variable inside listener function). + * @param priority The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) + */ + addOnce(listener: Function, listenerContext, priority): SignalBinding; + + /** + * Dispatch/Broadcast Signal to all listeners added to the queue. + * + * @param params Parameters that should be passed to each handler. + */ + dispatch(...params: any[]); + + /** + * Remove all bindings from signal and destroy any reference to external objects (destroy Signal object). + */ + dispose(); + + /** + * Forget memorized arguments. + */ + forget(); + + /** + * Returns a number of listeners attached to the Signal. + */ + getNumListeners(): number; + + /** + * Stop propagation of the event, blocking the dispatch to next listeners on the queue. + */ + halt(); + + /** + * Check if listener was attached to Signal. + */ + has(listener: Function, context?: any): boolean; + + /** + * Remove a single listener from the dispatch queue. + */ + remove(listener: Function, context?: any): Function; + + removeAll(); +}