Last active
March 3, 2022 14:42
-
-
Save lbguilherme/6d726bc6f38e8ecd415ef8e9de9615e8 to your computer and use it in GitHub Desktop.
Crystal front-end app, what would it look like? (Elm-style)
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
# 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