Created
July 27, 2016 23:27
-
-
Save jgaskins/da9fa65ac20809f17db58024197797f0 to your computer and use it in GitHub Desktop.
Clearwater app to fetch and render data from the GitHub API
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 '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 |
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
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