-
-
Save 1gor/50bb437b235b307a9df706a0936ccce4 to your computer and use it in GitHub Desktop.
React Router v4-style routing in Clearwater
This file contains hidden or 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
require 'opal' | |
require 'clearwater' | |
require 'routing' | |
class Layout | |
include Clearwater::Component | |
include Routing | |
def render | |
div([ | |
nav([ | |
Link.new({ href: '/' }, 'Home'), | |
' ', | |
Link.new({ href: '/articles' }, 'Articles'), | |
' ', | |
Link.new({ href: '/omg' }, 'OMG'), | |
]), | |
route do | |
match('articles') { ArticlesList.new } | |
# We can match it multiple times, both will be rendered. | |
match('omg') { h1('OMG') } | |
match('omg') { h2 'omg lol' } | |
miss { h1 'Home Page!' } | |
end, | |
]) | |
end | |
end | |
class ArticlesList | |
include Clearwater::Component | |
include Routing | |
def render | |
MasterDetail.new( | |
div([ | |
ul(articles.map { |article| ArticlesListItem.new(article) }), | |
]), | |
div([ | |
route do | |
# Notice we call id.to_i - the yielded value is extracted from a | |
# string so that's what we get, but we are checking against a numeric | |
# id. | |
match(':id') { |id| ArticleDetail.new(articles[id.to_i]) } | |
miss { h3 'Select an article from the list at the left' } | |
end, | |
route do | |
match(':id') { |id| h2 "id: #{id}" } | |
end, | |
]) | |
) | |
end | |
def articles | |
@articles ||= Collection.new(Array.new(20) { |id| | |
Article.new( | |
id: id + 1, | |
title: "Article ##{id + 1}", | |
body: %w(foo bar baz quux omg lol wtf bbq zomg wow so amaze).shuffle.join(' '), | |
) | |
}) | |
end | |
end | |
ArticlesListItem = Struct.new(:article) do | |
include Clearwater::Component | |
def render | |
li([ | |
Link.new({ href: "/articles/#{article.id}" }, article.title), | |
]) | |
end | |
end | |
ArticleDetail = Struct.new(:article) do | |
include Clearwater::Component | |
def render | |
if article.nil? | |
return p('Article not found :-(') | |
end | |
main([ | |
h1(article.title), | |
section(article.body), | |
]) | |
end | |
end | |
# Indexed collection, subscriptable by model id | |
Collection = Struct.new(:models) do | |
include Enumerable | |
def each | |
models.each { |item| yield item } | |
end | |
def [] id | |
models_by_id[id] | |
end | |
def models_by_id | |
@models_by_id ||= models.each_with_object({}) do |model, hash| | |
hash[model.id] = model | |
end | |
end | |
end | |
class Article | |
attr_reader :id, :title, :body | |
def initialize attributes={} | |
attributes.each do |attr, value| | |
`self[#{attr}] = #{value}` | |
end | |
end | |
end | |
MasterDetail = Struct.new(:master, :detail) do | |
include Clearwater::Component | |
def render | |
div([ | |
div({ style: Style.master }, master), | |
div({ style: Style.detail }, detail), | |
]) | |
end | |
module Style | |
module_function | |
def master | |
side_by_side '25%' | |
end | |
def detail | |
side_by_side '74%' | |
end | |
def side_by_side width | |
{ | |
display: 'inline-block', | |
vertical_align: :top, | |
width: width, | |
} | |
end | |
end | |
end | |
app = Clearwater::Application.new(component: Layout.new) | |
app.call |
This file contains hidden or 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
module Routing | |
attr_accessor :matched_path | |
def route | |
@matched = [] | |
@path_matcher = PathMatcher.new(matched_path, Bowser.window.location.path) | |
yield | |
@matched | |
end | |
def match path_segment | |
match = @path_matcher.match? path_segment | |
if match | |
@matched << ChildRoute.new(yield(match), "#{@path_matcher.current_match}/#{path_segment}") | |
end | |
end | |
def miss | |
if @matched.none? | |
@matched << ChildRoute.new(yield, matched_path) | |
end | |
end | |
ChildRoute = Struct.new(:content, :current_path_match) do | |
include Clearwater::Component | |
def render | |
# Allow for plain JS objects but also check to see if we can use the accessor | |
if `!!(#{content} && #{content}.$$class) && #{content.respond_to? :matched_path=}` | |
content.matched_path = current_path_match | |
end | |
content | |
end | |
end | |
class PathMatcher | |
attr_reader :current_match, :current_path | |
def initialize current_match, current_path | |
@current_match = current_match.to_s | |
@current_path = current_path.to_s | |
end | |
def match? segment | |
segment = segment.sub(%r(^/), '') | |
match_check = "#{current_match}/#{segment}" | |
if current_path.start_with? match_check | |
true | |
elsif segment.start_with? ':' | |
current_path.sub(%r(^#{Regexp.escape(current_match)}/?), '')[/^\w+/] | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment