Skip to content

Instantly share code, notes, and snippets.

@dantiel
Last active October 15, 2017 17:59
Show Gist options
  • Save dantiel/c3e60425e8e0395ebd7568830848ba78 to your computer and use it in GitHub Desktop.
Save dantiel/c3e60425e8e0395ebd7568830848ba78 to your computer and use it in GitHub Desktop.
Lightweight React Routing Class
# A convenient custom routing class.
class Routes
@host: 'http://hostname.com'
# Holds routes that will further be created via `Routes.create`.
@routes: []
# Exclude these keys from creating child routes as they are objects that are
# in use for definition of the route.
@blacklist: ['progress']
# Just lists all routes for introspection.
@inspect: =>
_align = (str = '', width, align = 'left') ->
str = str[0..width - 2] # trim
space = width - str.length
padding = switch align
when 'right' then [space, 0]
when 'center'then [Math.floor(.5 * space), Math.ceil(.5 * space)]
else [0, space]
[pad_left, pad_right] = (('' for [0..p]).join '·' for p in padding)
"#{pad_left}#{str}#{pad_right}"
make_header = (columns) ->
(_align ":#{c.toUpperCase()}:", o.size, 'center' for c, o of columns).join '·'
make_line = (data, columns) ->
(_align data[c], o.size, o.align for c, o of columns).join ' '
columns =
title: size: 20, align: 'center'
name: size: 27, align: 'right'
method: size: 10
path: size: 43
_header = make_header columns
_body = (make_line route, columns for route in @routes).join '\n'
[_header, _body].join '\n'
# Recursively create routes and child routes from route definitions.
@create: (routes, parent_route = null) =>
_routes = for name, config of routes
do =>
name = "#{parent_route.name}_#{name}" if parent_route?
# Progress definition is inherited from parent route.
if config.progress? or parent_route?.progress?
progress = Object.assign {}, parent_route?.progress, config.progress
route =
name: name
# If present title will automatically be rendered into scene header.
title: config.title
index: if @routes? then @routes.length else 0
scene: config.scene
url: -> Routes.host + config.path
path: config.path
method: config.method?.toUpperCase()
progress: progress
parent: parent_route.name if parent_route?
showNavbar: parent_route? and config.title and not config.progress
# A child route is just an extra fields on a route definition that is of
# type `object`.
child_routes = {}
child_route_names =
for k, v of config when v instanceof Object and k not in @blacklist
child_routes[k] = v
k
child_routes = Routes.create child_routes, route # recurse, k?
# Creating convenient static helper methods for each route that will be
# accessible as `Routes.route_name_path` and `Routes.route_name_url`.
#
# Route paths may contain params, thus returning a function that will
# then return a path with replaced placeholders.
# Example:
# ```
# # route definition
# follow:
# path: '/users/:id/follow'
# ```
#
# ```
# Routes.follow_path(1)
# # => /users/1/follow
# ```
#
if 'string' is typeof config.path and ':' in config.path
Routes["#{name}_path"] = do (path = config.path) -> (o...) =>
p = path + ''
# Replace via object’s keys,
f = encodeURIComponent
if o[0] instanceof Object
(p = p.replace m, f o[0][m[1..]]) for m in p.match /\:\w+/g
# or named via splat.
else
(p = p.replace m, f o[i]) for m, i in p.match /\:\w+/g
p
Routes["#{name}_url"] = do (path = Routes["#{name}_path"], @host) ->
(o...) => @host + path(o...)
else
Routes["#{name}_path"] = config.path
Routes["#{name}_url"] = @host + config.path
# Child routes are accessible via `route.children`.
route.children = child_route_names if child_route_names.length
# Each route is statically passed to the Routes class as well.
@["#{name}"] = route
@routes.push _routes...
_routes
# Navigate to a route’s scene component (navigator has to be set).
# Parameter might be a route name or the route object itself.
@navigate: (route) ->
route_name = route.name unless route instanceof String
route = @[route_name or route]
unless route?.scene?
console.error "Route `#{route_name or route}´ doesn’t provide a scene."
if route in @navigator.getCurrentRoutes()
@navigator.jumpTo route
else @navigator.push route
@getInitialRoute: (loginState) ->
switch loginState
when 'logged_in' then Routes.home
else Routes.sign_in
# Navigator has to be set externally and should not be swapped later on.
@navigator: null
# Routes may be added as follows:
Routes.create
sign_in:
path: '/sign_in'
method: 'post'
scene: 'sign_in'
title: 'Please Sign In'
# This route doesn't have a path, hence doesn’t provide an api endpoint, only linking to a scene component.
home:
scene: 'home'
title: 'Please Sign In'
# Calling `Routes.home_living_room_url channel: 'nbc'` would yield `http://hostname.com/tv/nbc`
living_room:
path: '/tv/:channel'
method: 'get'
scene: 'sofa'
module.exports = Routes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment