Skip to content

Instantly share code, notes, and snippets.

@lbguilherme
Last active March 3, 2022 14:42
Show Gist options
  • Save lbguilherme/6d726bc6f38e8ecd415ef8e9de9615e8 to your computer and use it in GitHub Desktop.
Save lbguilherme/6d726bc6f38e8ecd415ef8e9de9615e8 to your computer and use it in GitHub Desktop.
Crystal front-end app, what would it look like? (Elm-style)
# This is a sample of what a front-end Crystal application could look like, for code review.
# The design was inspired by Elm and it should be very performing and compact.
# If this is ever implemented, it will use WebAssembly to run Crystal code, together with
# some JavaScript bindings to access the DOM.
# 1. Define a data model.
class Model < BaseModel
getter todos = [] of TodoModel
getter new_todo_text = ""
end
class TodoModel
property id = UUID.random
property text : String
property? done = false
def initialize(@text)
end
end
# 2. Define a set of messages (actions) that change the model.
abstract class TodoMsg < BaseMsg
property id : UUID
def initialize(@id)
end
end
class TodoChangeMsg < TodoMsg
property done : Bool
def initialize(id, @done)
super(id)
end
end
class AddTodoMsg < BaseMsg
property text : String
def initialize(@text)
end
end
class UpdateNewTodoTextMsg < BaseMsg
property text : String
def initialize(@text)
end
end
# 3. Define how each of the messages should be handled, updating the model.
# The model receives messages from changes in the UI.
class Model
def apply_msg(msg : BaseMsg)
case msg
in AddTodoMsg
@todos << TodoModel.new(msg.text)
in TodoMsg
todo = todos.find { |t| t.id == todo_id }
todo.apply_msg(msg)
in UpdateNewTodoTextMsg
@new_todo_text = msg.text
end
end
end
class TodoModel
def apply_msg(msg : TodoMsg)
case msg
in TodoChangeMsg
@done = !@done
end
end
end
# 4. Define the views of the app.
class App < BaseApp(Model)
# This view root is called on every change to the model, it should render the complete application.
# You can split the logic in several methods, nothing special there.
# The `lazy` macro takes a method call and will check if the arguments have changed since the
# previous run to either reuse the previous HTML or render it again. On top of that a virtual dom
# is used to minimize DOM operations.
def view
div(class: "app") do
h1 "Todos"
TodoList.view(@model.todos)
lazy AddTodoForm.view(@model.new_todo_text)
end
end
end
module TodoList
extend self
include HtmlView
def view(todos)
ul do
todos.each do |todo|
li do
lazy view_todo(todo)
end
end
end
end
def view_todo(todo)
span(class: "todo") do
input(type: "checkbox", checked: @todo.done?) do
on_click do
# Whenever a msg is emitted, the model is updated and the view is re-rendered.
TodoChangeMsg.new(@todo.id, [email protected]?).emit
end
end
text @todo.text
end
end
end
module AddTodoForm
extend self
include HtmlView
def view(new_todo_text)
form do
input(type: "text", placeholder: "new todo", value: new_todo_text) do
on_change do |e|
UpdateNewTodoTextMsg.new(e.target.value).emit
end
end
button(type: "submit") do
text "Add"
end
on_submit do
AddTodoMsg.new(new_todo_text).emit
end
end
end
end
# 5. Run the app!
App.new.run!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment