Permalink
Browse files

Rails-style optional route segments

  • Loading branch information...
1 parent 8739e7e commit 546ad2f4a4fd0481385fd9095b593ef79e8e2c92 Jason King committed Jul 19, 2012
Showing with 56 additions and 10 deletions.
  1. +10 −0 README.md
  2. +8 −4 lib/batman.js
  3. +8 −4 lib/dist/batman.node.js
  4. +9 −2 src/routing/route.coffee
  5. +21 −0 tests/batman/routes/route_test.coffee
View
@@ -227,6 +227,16 @@ For the FAQ route, `'app#faq'` specifies the `faq` function on `BatBelt.AppContr
`@root 'app#index'` is just a shorthand for `@route '/', 'app#index'`.
+You can also mark a segment of your route as optional, allowing URLs to match with or without those optional segments. Eg. `@route 'calendar(/:type(/:date))', calendar#index` will match all of the following URLs:
+
+```
+/calendar
+/calendar/y
+/calendar/m/2012-11
+```
+
+When the segments are missing, your controller action is handed an `undefined` value for that param.
+
The `@resources` macro takes a resource name which should ideally be the underscored-pluralized name of one of your models. It sets up three routes, as if you'd used the `@route` macro like so:
```coffeescript
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
@@ -8,7 +8,9 @@ class Batman.Route extends Batman.Object
queryParam: '(?:\\?.+)?'
namedOrSplat: /[:|\*]([\w\d]+)/g
namePrefix: '[:|\*]'
- escapeRegExp: /[-[\]{}()+?.,\\^$|#\s]/g
+ escapeRegExp: /[-[\]{}+?.,\\^$|#\s]/g
+ openOptParam: /\(/g
+ closeOptParam: /\)/g
optionKeys: ['member', 'collection']
testKeys: ['controller', 'action']
@@ -21,6 +23,7 @@ class Batman.Route extends Batman.Object
regexp = ///
^
#{pattern
+ .replace(regexps.openOptParam, '(?:').replace(regexps.closeOptParam, ')?') # allow (/:foo) wrappers to indicate optional param
.replace(regexps.namedParam, '([^\/]+)')
.replace(regexps.splatParam, '(.*?)') }
#{regexps.queryParam}
@@ -51,15 +54,19 @@ class Batman.Route extends Batman.Object
pathFromParams: (argumentParams) ->
params = Batman.extend {}, argumentParams
path = @get('templatePath')
+ regexps = @constructor.regexps
# Replace the names in the template with their values from params
for name in @get('namedArguments')
- regexp = ///#{@constructor.regexps.namePrefix}#{name}///
+ regexp = ///#{regexps.namePrefix}#{name}///
newPath = path.replace regexp, (if params[name]? then params[name] else '')
if newPath != path
delete params[name]
path = newPath
+
+ path = path.replace(regexps.openOptParam, '').replace(regexps.closeOptParam, '').replace(/\/+$/, '')
+
for key in @testKeys
delete params[key]
@@ -163,3 +163,24 @@ test "controller/action routes should call the controller's dispatch function",
@route.dispatch params
equal searchSpy.lastCallArguments[0], "duplicate"
equal searchSpy.lastCallArguments[1].id, "20"
+
+test "routes should build paths with optional segments", 3, ->
+ route = new Batman.Route "/calendar(/:type(/:date))", {}
+
+ equal route.pathFromParams({}), "/calendar"
+ equal route.pathFromParams({type: "m"}), "/calendar/m"
+ equal route.pathFromParams({type: "m", date: "2012-11"}), "/calendar/m/2012-11"
+
+test "routes with optional segments should parse params", ->
+ type = 'm'
+ date = '2012-11'
+ route = new Batman.Route "/calendar(/:type(/:date))", {}
+
+ path = "/calendar/#{type}/#{date}"
+ deepEqual route.paramsFromPath(path), { path, type, date }
+
+ path = "/calendar/#{type}"
+ deepEqual route.paramsFromPath(path), { path, type, date: undefined }
+
+ path = "/calendar"
+ deepEqual route.paramsFromPath(path), { path, type: undefined, date: undefined }

0 comments on commit 546ad2f

Please sign in to comment.