Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Clearwater app to fetch and render data from the GitHub API
require 'opal'
require 'clearwater'
require 'grand_central/model'
require 'bowser/http'
class Layout
include Clearwater::Component
attr_reader :users
# Setup the initial users collection, which will get updated as we go along.
# Important to note that Collection is immutable. If you're updating it, it
# needs to be reassigned. This is great for cache-invalidation performance.
# However, if you need this collection inside a subordinate routing target,
# you shouldn't store it here. It should go into a global store, like a
# GrandCentral::Store.
def initialize
@users = Collection.new
end
def render
div([
h1(Link.new({ href: '/' }, 'People on GitHub')),
button({ onclick: method(:load_users) }, 'Load Users'),
# Master-detail view of the user list and user details (when implemented)
div([
div({ style: Style.side_by_side('20%') }, user_list),
div({ style: Style.side_by_side('75%') }, outlet),
]),
])
end
# Dynamically render the user list based on the status of
def user_list
if users.loading?
p 'Fetching users...'
elsif users.any?
ul({ style: { padding: 0, margin: 0 } }, users.map { |user|
li(Link.new({ href: "/#{user.login}" }, [
# Notice we call new on this component. That means it cannot hold
# state between renders. We are generating an entirely new object.
UserAvatar.new(user),
user.login,
]))
})
elsif !users.loaded?
p 'Users not loaded yet'
else
p 'No users :-('
end
end
# Load users from the GitHub API
def load_users
@users = @users.update(loading: true)
call # Rerender on the next animation frame
# Send off an AJAX request to the GitHub API for a list of users past the
# highest user id we currently have.
Bowser::HTTP.fetch("https://api.github.com/users?since=#{last_user_id}")
.then do |response| # On success, we get a Response object back.
@users = Collection.with(response.json.map { |hash| User.new(hash) })
call
end
.fail do |exception| # On failure
alert exception.message
end
end
def last_user_id
users.last && users.last.id
end
module Style
module_function
def side_by_side width
{
display: 'inline-block',
vertical_align: :top,
width: width,
}
end
end
end
# Presentational component to represent a user's GitHub avatar.
class UserAvatar
include Clearwater::Component
def initialize user
@user = user
end
def render
img(
src: @user.avatar_url,
style: {
width: size,
height: size,
},
)
end
def size
'32px'
end
end
# Simple model to represent users fetched from the GitHub API.
class User < GrandCentral::Model
attributes(:id, :login, :avatar_url)
end
# An immutable collection. It has a few properties to represent the status
# of loading from the server.
class Collection < GrandCentral::Model
include Enumerable
attributes(:models, :loading, :loaded)
alias loading? loading
alias loaded? loaded
def self.with(models)
new(models: models, loaded: true)
end
def each &block
models.each &block
end
def last
models.last
end
def models
@models || []
end
def << value
self + [value]
end
def + enum
self.class.with(models.to_a + enum.to_a)
end
def - enum
self.class.with(models.to_a - enum.to_a)
end
end
app = Clearwater::Application.new(
component: Layout.new, # Also a long-lived component, so it can hold state
)
app.call
source 'https://rubygems.org'
gem 'clearwater', '~> 1.0.0.rc1'
gem 'opal-rails'
gem 'grand_central'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.6'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment