backbrace 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,602 @@
1
+ /** @license
2
+ * crossroads <http://millermedeiros.github.com/crossroads.js/>
3
+ * License: MIT
4
+ * Author: Miller Medeiros
5
+ * Version: 0.9.0 (2012/5/28 23:9)
6
+ */
7
+
8
+ (function (define) {
9
+ define(['signals'], function (signals) {
10
+
11
+ var crossroads,
12
+ UNDEF;
13
+
14
+ // Helpers -----------
15
+ //====================
16
+
17
+ function arrayIndexOf(arr, val) {
18
+ if (arr.indexOf) {
19
+ return arr.indexOf(val);
20
+ } else {
21
+ //Array.indexOf doesn't work on IE 6-7
22
+ var n = arr.length;
23
+ while (n--) {
24
+ if (arr[n] === val) {
25
+ return n;
26
+ }
27
+ }
28
+ return -1;
29
+ }
30
+ }
31
+
32
+ function isKind(val, kind) {
33
+ return '[object ' + kind + ']' === Object.prototype.toString.call(val);
34
+ }
35
+
36
+ function isRegExp(val) {
37
+ return isKind(val, 'RegExp');
38
+ }
39
+
40
+ function isArray(val) {
41
+ return isKind(val, 'Array');
42
+ }
43
+
44
+ function isFunction(val) {
45
+ return typeof val === 'function';
46
+ }
47
+
48
+ //borrowed from AMD-utils
49
+ function typecastValue(val) {
50
+ var r;
51
+ if (val === null || val === 'null') {
52
+ r = null;
53
+ } else if (val === 'true') {
54
+ r = true;
55
+ } else if (val === 'false') {
56
+ r = false;
57
+ } else if (val === UNDEF || val === 'undefined') {
58
+ r = UNDEF;
59
+ } else if (val === '' || isNaN(val)) {
60
+ //isNaN('') returns false
61
+ r = val;
62
+ } else {
63
+ //parseFloat(null || '') returns NaN
64
+ r = parseFloat(val);
65
+ }
66
+ return r;
67
+ }
68
+
69
+ function typecastArrayValues(values) {
70
+ var n = values.length,
71
+ result = [];
72
+ while (n--) {
73
+ result[n] = typecastValue(values[n]);
74
+ }
75
+ return result;
76
+ }
77
+
78
+ //borrowed from AMD-Utils
79
+ function decodeQueryString(str) {
80
+ var queryArr = (str || '').replace('?', '').split('&'),
81
+ n = queryArr.length,
82
+ obj = {},
83
+ item, val;
84
+ while (n--) {
85
+ item = queryArr[n].split('=');
86
+ val = typecastValue(item[1]);
87
+ obj[item[0]] = (typeof val === 'string') ? decodeURIComponent(val) : val;
88
+ }
89
+ return obj;
90
+ }
91
+
92
+
93
+ // Crossroads --------
94
+ //====================
95
+
96
+ /**
97
+ * @constructor
98
+ */
99
+ function Crossroads() {
100
+ this._routes = [];
101
+ this._prevRoutes = [];
102
+ this.bypassed = new signals.Signal();
103
+ this.routed = new signals.Signal();
104
+ }
105
+
106
+ Crossroads.prototype = {
107
+
108
+ greedy:false,
109
+
110
+ greedyEnabled:true,
111
+
112
+ normalizeFn:null,
113
+
114
+ create:function () {
115
+ return new Crossroads();
116
+ },
117
+
118
+ shouldTypecast:false,
119
+
120
+ addRoute:function (pattern, callback, priority) {
121
+ var route = new Route(pattern, callback, priority, this);
122
+ this._sortedInsert(route);
123
+ return route;
124
+ },
125
+
126
+ removeRoute:function (route) {
127
+ var i = arrayIndexOf(this._routes, route);
128
+ if (i !== -1) {
129
+ this._routes.splice(i, 1);
130
+ }
131
+ route._destroy();
132
+ },
133
+
134
+ removeAllRoutes:function () {
135
+ var n = this.getNumRoutes();
136
+ while (n--) {
137
+ this._routes[n]._destroy();
138
+ }
139
+ this._routes.length = 0;
140
+ },
141
+
142
+ parse:function (request, defaultArgs) {
143
+ request = request || '';
144
+ defaultArgs = defaultArgs || [];
145
+
146
+ var routes = this._getMatchedRoutes(request),
147
+ i = 0,
148
+ n = routes.length,
149
+ cur;
150
+
151
+ if (n) {
152
+ this._notifyPrevRoutes(routes, request);
153
+ this._prevRoutes = routes;
154
+ //shold be incremental loop, execute routes in order
155
+ while (i < n) {
156
+ cur = routes[i];
157
+ cur.route.matched.dispatch.apply(cur.route.matched, defaultArgs.concat(cur.params));
158
+ cur.isFirst = !i;
159
+ this.routed.dispatch.apply(this.routed, defaultArgs.concat([request, cur]));
160
+ i += 1;
161
+ }
162
+ } else {
163
+ this.bypassed.dispatch.apply(this.bypassed, defaultArgs.concat([request]));
164
+ }
165
+ },
166
+
167
+ _notifyPrevRoutes:function (matchedRoutes, request) {
168
+ var i = 0, prev;
169
+ while (prev = this._prevRoutes[i++]) {
170
+ //check if switched exist since route may be disposed
171
+ if (prev.route.switched && this._didSwitch(prev.route, matchedRoutes)) {
172
+ prev.route.switched.dispatch(request);
173
+ }
174
+ }
175
+ },
176
+
177
+ _didSwitch:function (route, matchedRoutes) {
178
+ var matched,
179
+ i = 0;
180
+ while (matched = matchedRoutes[i++]) {
181
+ // only dispatch switched if it is going to a different route
182
+ if (matched.route === route) {
183
+ return false;
184
+ }
185
+ }
186
+ return true;
187
+ },
188
+
189
+ getNumRoutes:function () {
190
+ return this._routes.length;
191
+ },
192
+
193
+ _sortedInsert:function (route) {
194
+ //simplified insertion sort
195
+ var routes = this._routes,
196
+ n = routes.length;
197
+ do {
198
+ --n;
199
+ } while (routes[n] && route._priority <= routes[n]._priority);
200
+ routes.splice(n + 1, 0, route);
201
+ },
202
+
203
+ _getMatchedRoutes:function (request) {
204
+ var res = [],
205
+ routes = this._routes,
206
+ n = routes.length,
207
+ route;
208
+ //should be decrement loop since higher priorities are added at the end of array
209
+ while (route = routes[--n]) {
210
+ if ((!res.length || this.greedy || route.greedy) && route.match(request)) {
211
+ res.push({
212
+ route:route,
213
+ params:route._getParamsArray(request)
214
+ });
215
+ }
216
+ if (!this.greedyEnabled && res.length) {
217
+ break;
218
+ }
219
+ }
220
+ return res;
221
+ },
222
+
223
+ toString:function () {
224
+ return '[crossroads numRoutes:' + this.getNumRoutes() + ']';
225
+ }
226
+ };
227
+
228
+ //"static" instance
229
+ crossroads = new Crossroads();
230
+ crossroads.VERSION = '0.9.0';
231
+
232
+ crossroads.NORM_AS_ARRAY = function (req, vals) {
233
+ return [vals.vals_];
234
+ };
235
+
236
+ crossroads.NORM_AS_OBJECT = function (req, vals) {
237
+ return [vals];
238
+ };
239
+
240
+
241
+ // Route --------------
242
+ //=====================
243
+
244
+ /**
245
+ * @constructor
246
+ */
247
+ function Route(pattern, callback, priority, router) {
248
+ var isRegexPattern = isRegExp(pattern),
249
+ patternLexer = crossroads.patternLexer;
250
+ this._router = router;
251
+ this._pattern = pattern;
252
+ this._paramsIds = isRegexPattern ? null : patternLexer.getParamIds(this._pattern);
253
+ this._optionalParamsIds = isRegexPattern ? null : patternLexer.getOptionalParamsIds(this._pattern);
254
+ this._matchRegexp = isRegexPattern ? pattern : patternLexer.compilePattern(pattern);
255
+ this.matched = new signals.Signal();
256
+ this.switched = new signals.Signal();
257
+ if (callback) {
258
+ this.matched.add(callback);
259
+ }
260
+ this._priority = priority || 0;
261
+ }
262
+
263
+ Route.prototype = {
264
+
265
+ greedy:false,
266
+
267
+ rules:void(0),
268
+
269
+ match:function (request) {
270
+ request = request || '';
271
+ return this._matchRegexp.test(request) && this._validateParams(request); //validate params even if regexp because of `request_` rule.
272
+ },
273
+
274
+ _validateParams:function (request) {
275
+ var rules = this.rules,
276
+ values = this._getParamsObject(request),
277
+ key;
278
+ for (key in rules) {
279
+ // normalize_ isn't a validation rule... (#39)
280
+ if (key !== 'normalize_' && rules.hasOwnProperty(key) && !this._isValidParam(request, key, values)) {
281
+ return false;
282
+ }
283
+ }
284
+ return true;
285
+ },
286
+
287
+ _isValidParam:function (request, prop, values) {
288
+ var validationRule = this.rules[prop],
289
+ val = values[prop],
290
+ isValid = false,
291
+ isQuery = (prop.indexOf('?') === 0);
292
+
293
+ if (val == null && this._optionalParamsIds && arrayIndexOf(this._optionalParamsIds, prop) !== -1) {
294
+ isValid = true;
295
+ }
296
+ else if (isRegExp(validationRule)) {
297
+ if (isQuery) {
298
+ val = values[prop + '_']; //use raw string
299
+ }
300
+ isValid = validationRule.test(val);
301
+ }
302
+ else if (isArray(validationRule)) {
303
+ if (isQuery) {
304
+ val = values[prop + '_']; //use raw string
305
+ }
306
+ isValid = arrayIndexOf(validationRule, val) !== -1;
307
+ }
308
+ else if (isFunction(validationRule)) {
309
+ isValid = validationRule(val, request, values);
310
+ }
311
+
312
+ return isValid; //fail silently if validationRule is from an unsupported type
313
+ },
314
+
315
+ _getParamsObject:function (request) {
316
+ var shouldTypecast = this._router.shouldTypecast,
317
+ values = crossroads.patternLexer.getParamValues(request, this._matchRegexp, shouldTypecast),
318
+ o = {},
319
+ n = values.length,
320
+ param, val;
321
+ while (n--) {
322
+ val = values[n];
323
+ if (this._paramsIds) {
324
+ param = this._paramsIds[n];
325
+ if (param.indexOf('?') === 0 && val) {
326
+ //make a copy of the original string so array and
327
+ //RegExp validation can be applied properly
328
+ o[param + '_'] = val;
329
+ //update vals_ array as well since it will be used
330
+ //during dispatch
331
+ val = decodeQueryString(val);
332
+ values[n] = val;
333
+ }
334
+ o[param] = val;
335
+ }
336
+ //alias to paths and for RegExp pattern
337
+ o[n] = val;
338
+ }
339
+ o.request_ = shouldTypecast ? typecastValue(request) : request;
340
+ o.vals_ = values;
341
+ return o;
342
+ },
343
+
344
+ _getParamsArray:function (request) {
345
+ var norm = this.rules ? this.rules.normalize_ : null,
346
+ params;
347
+ norm = norm || this._router.normalizeFn; // default normalize
348
+ if (norm && isFunction(norm)) {
349
+ params = norm(request, this._getParamsObject(request));
350
+ } else {
351
+ params = this._getParamsObject(request).vals_;
352
+ }
353
+ return params;
354
+ },
355
+
356
+ interpolate:function (replacements) {
357
+ var str = crossroads.patternLexer.interpolate(this._pattern, replacements);
358
+ if (!this._validateParams(str)) {
359
+ throw new Error('Generated string doesn\'t validate against `Route.rules`.');
360
+ }
361
+ return str;
362
+ },
363
+
364
+ dispose:function () {
365
+ this._router.removeRoute(this);
366
+ },
367
+
368
+ _destroy:function () {
369
+ this.matched.dispose();
370
+ this.switched.dispose();
371
+ this.matched = this.switched = this._pattern = this._matchRegexp = null;
372
+ },
373
+
374
+ toString:function () {
375
+ return '[Route pattern:"' + this._pattern + '", numListeners:' + this.matched.getNumListeners() + ']';
376
+ }
377
+
378
+ };
379
+
380
+
381
+ // Pattern Lexer ------
382
+ //=====================
383
+
384
+ crossroads.patternLexer = (function () {
385
+
386
+ var
387
+ //match chars that should be escaped on string regexp
388
+ ESCAPE_CHARS_REGEXP = /[\\.+*?\^$\[\](){}\/'#]/g,
389
+
390
+ //trailing slashes (begin/end of string)
391
+ LOOSE_SLASHES_REGEXP = /^\/|\/$/g,
392
+ LEGACY_SLASHES_REGEXP = /\/$/g,
393
+
394
+ //params - everything between `{ }` or `: :`
395
+ PARAMS_REGEXP = /(?:\{|:)([^}:]+)(?:\}|:)/g,
396
+
397
+ //used to save params during compile (avoid escaping things that
398
+ //shouldn't be escaped).
399
+ TOKENS = {
400
+ 'OS':{
401
+ //optional slashes
402
+ //slash between `::` or `}:` or `\w:` or `:{?` or `}{?` or `\w{?`
403
+ rgx:/([:}]|\w(?=\/))\/?(:|(?:\{\?))/g,
404
+ save:'$1{{id}}$2',
405
+ res:'\\/?'
406
+ },
407
+ 'RS':{
408
+ //required slashes
409
+ //used to insert slash between `:{` and `}{`
410
+ rgx:/([:}])\/?(\{)/g,
411
+ save:'$1{{id}}$2',
412
+ res:'\\/'
413
+ },
414
+ 'RQ':{
415
+ //required query string - everything in between `{? }`
416
+ rgx:/\{\?([^}]+)\}/g,
417
+ //everything from `?` till `#` or end of string
418
+ res:'\\?([^#]+)'
419
+ },
420
+ 'OQ':{
421
+ //optional query string - everything in between `:? :`
422
+ rgx:/:\?([^:]+):/g,
423
+ //everything from `?` till `#` or end of string
424
+ res:'(?:\\?([^#]*))?'
425
+ },
426
+ 'OR':{
427
+ //optional rest - everything in between `: *:`
428
+ rgx:/:([^:]+)\*:/g,
429
+ res:'(.*)?' // optional group to avoid passing empty string as captured
430
+ },
431
+ 'RR':{
432
+ //rest param - everything in between `{ *}`
433
+ rgx:/\{([^}]+)\*\}/g,
434
+ res:'(.+)'
435
+ },
436
+ // required/optional params should come after rest segments
437
+ 'RP':{
438
+ //required params - everything between `{ }`
439
+ rgx:/\{([^}]+)\}/g,
440
+ res:'([^\\/?]+)'
441
+ },
442
+ 'OP':{
443
+ //optional params - everything between `: :`
444
+ rgx:/:([^:]+):/g,
445
+ res:'([^\\/?]+)?\/?'
446
+ }
447
+ },
448
+
449
+ LOOSE_SLASH = 1,
450
+ STRICT_SLASH = 2,
451
+ LEGACY_SLASH = 3,
452
+
453
+ _slashMode = LOOSE_SLASH;
454
+
455
+
456
+ function precompileTokens() {
457
+ var key, cur;
458
+ for (key in TOKENS) {
459
+ if (TOKENS.hasOwnProperty(key)) {
460
+ cur = TOKENS[key];
461
+ cur.id = '__CR_' + key + '__';
462
+ cur.save = ('save' in cur) ? cur.save.replace('{{id}}', cur.id) : cur.id;
463
+ cur.rRestore = new RegExp(cur.id, 'g');
464
+ }
465
+ }
466
+ }
467
+
468
+ precompileTokens();
469
+
470
+
471
+ function captureVals(regex, pattern) {
472
+ var vals = [], match;
473
+ while (match = regex.exec(pattern)) {
474
+ vals.push(match[1]);
475
+ }
476
+ return vals;
477
+ }
478
+
479
+ function getParamIds(pattern) {
480
+ return captureVals(PARAMS_REGEXP, pattern);
481
+ }
482
+
483
+ function getOptionalParamsIds(pattern) {
484
+ return captureVals(TOKENS.OP.rgx, pattern);
485
+ }
486
+
487
+ function compilePattern(pattern) {
488
+ pattern = pattern || '';
489
+
490
+ if (pattern) {
491
+ if (_slashMode === LOOSE_SLASH) {
492
+ pattern = pattern.replace(LOOSE_SLASHES_REGEXP, '');
493
+ }
494
+ else if (_slashMode === LEGACY_SLASH) {
495
+ pattern = pattern.replace(LEGACY_SLASHES_REGEXP, '');
496
+ }
497
+
498
+ //save tokens
499
+ pattern = replaceTokens(pattern, 'rgx', 'save');
500
+ //regexp escape
501
+ pattern = pattern.replace(ESCAPE_CHARS_REGEXP, '\\$&');
502
+ //restore tokens
503
+ pattern = replaceTokens(pattern, 'rRestore', 'res');
504
+
505
+ if (_slashMode === LOOSE_SLASH) {
506
+ pattern = '\\/?' + pattern;
507
+ }
508
+ }
509
+
510
+ if (_slashMode !== STRICT_SLASH) {
511
+ //single slash is treated as empty and end slash is optional
512
+ pattern += '\\/?';
513
+ }
514
+ return new RegExp('^' + pattern + '$');
515
+ }
516
+
517
+ function replaceTokens(pattern, regexpName, replaceName) {
518
+ var cur, key;
519
+ for (key in TOKENS) {
520
+ if (TOKENS.hasOwnProperty(key)) {
521
+ cur = TOKENS[key];
522
+ pattern = pattern.replace(cur[regexpName], cur[replaceName]);
523
+ }
524
+ }
525
+ return pattern;
526
+ }
527
+
528
+ function getParamValues(request, regexp, shouldTypecast) {
529
+ var vals = regexp.exec(request);
530
+ if (vals) {
531
+ vals.shift();
532
+ if (shouldTypecast) {
533
+ vals = typecastArrayValues(vals);
534
+ }
535
+ }
536
+ return vals;
537
+ }
538
+
539
+ function interpolate(pattern, replacements) {
540
+ if (typeof pattern !== 'string') {
541
+ throw new Error('Route pattern should be a string.');
542
+ }
543
+
544
+ var replaceFn = function (match, prop) {
545
+ var val;
546
+ if (prop in replacements) {
547
+ val = replacements[prop];
548
+ if (match.indexOf('*') === -1 && val.indexOf('/') !== -1) {
549
+ throw new Error('Invalid value "' + val + '" for segment "' + match + '".');
550
+ }
551
+ }
552
+ else if (match.indexOf('{') !== -1) {
553
+ throw new Error('The segment ' + match + ' is required.');
554
+ }
555
+ else {
556
+ val = '';
557
+ }
558
+ return val;
559
+ };
560
+
561
+ if (!TOKENS.OS.trail) {
562
+ TOKENS.OS.trail = new RegExp('(?:' + TOKENS.OS.id + ')+$');
563
+ }
564
+
565
+ return pattern
566
+ .replace(TOKENS.OS.rgx, TOKENS.OS.save)
567
+ .replace(PARAMS_REGEXP, replaceFn)
568
+ .replace(TOKENS.OS.trail, '')// remove trailing
569
+ .replace(TOKENS.OS.rRestore, '/'); // add slash between segments
570
+ }
571
+
572
+ //API
573
+ return {
574
+ strict:function () {
575
+ _slashMode = STRICT_SLASH;
576
+ },
577
+ loose:function () {
578
+ _slashMode = LOOSE_SLASH;
579
+ },
580
+ legacy:function () {
581
+ _slashMode = LEGACY_SLASH;
582
+ },
583
+ getParamIds:getParamIds,
584
+ getOptionalParamsIds:getOptionalParamsIds,
585
+ getParamValues:getParamValues,
586
+ compilePattern:compilePattern,
587
+ interpolate:interpolate
588
+ };
589
+
590
+ }());
591
+
592
+
593
+ return crossroads;
594
+ });
595
+ }(typeof define === 'function' && define.amd ? define : function (deps, factory) {
596
+ if (typeof module !== 'undefined' && module.exports) { //Node
597
+ module.exports = factory(require(deps[0]));
598
+ } else {
599
+ /*jshint sub:true */
600
+ window['crossroads'] = factory(window[deps[0]]);
601
+ }
602
+ }));