Last active
October 15, 2017 17:59
-
-
Save dantiel/c3e60425e8e0395ebd7568830848ba78 to your computer and use it in GitHub Desktop.
Lightweight React Routing Class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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