Last active
March 4, 2022 11:08
-
-
Save lbguilherme/cebd904a385f6dc880a04bd8ca8100f1 to your computer and use it in GitHub Desktop.
Crystal front-end app, what would it look like?
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 React, Angular and Flutter 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. | |
class TodoData | |
property text : String | |
property? done = false | |
def initialize(@text) | |
end | |
end | |
# A component can have immutable properties, a mutable state and events. It represents | |
# an UI element on the HTML DOM. Only itself can change its own state, the changes will | |
# trigger a re-render. Rendering uses a virtual DOM and a diffing algorithm. Children are | |
# only rendered if their properties changed, always preserving the state. | |
struct App < Component | |
# The component state is declared using the state macro. It is internally an inner class | |
# that can have properties and methods. Nothing special here except for the `mutate` | |
# that must be used whenever the state changes. It will schedule a re-render of the | |
# component on the next UI tick. Property setters handle it automatically. | |
state do | |
property todos = [] of TodoData | |
def add(todo_data) | |
mutate do | |
todos << todo_data | |
end | |
end | |
end | |
# This method should be cheap to run and it will produce a virtual DOM. It can access | |
# instance variables from the component and any data from the state. Running it should | |
# never cause the state to change. The `html` macro takes a string interpolation as argument | |
# and will correctly parse the HTML and replace the values in a safe way. Interpolation can | |
# only happen in some known locations. The attributes of each element can be defined with | |
# interpolation too. Event attributes (starting with "on_") register handlers to be invoked | |
# when the event fires. | |
def view | |
html " | |
<div class='app'> | |
<h1>Todos</h1> | |
<ul> | |
#{state.todos.map { |todo| | |
html " | |
<li> | |
<Todo todo=#{todo} on_done_changed=#{state.mutate { todo.done = done }}/> | |
</li> | |
" | |
}} | |
</ul> | |
<AddTodoForm on_new_todo=#{state.add(todo)}/> | |
</div> | |
" | |
end | |
end | |
# Represents a single Todo item. It is stateless. | |
struct Todo < Component | |
# Events are pretty much just a Proc in a instance var, but this helper macro makes things easier. | |
# The parent can listen to the event and trigger actions (like updating the state). | |
event on_done_changed(done : Bool) | |
# The initialize method should be cheap, as it will run every time the parent needs to render. | |
# Expensive initialization should happen in the state's `initialize` method. | |
def initialize(@todo : TodoData) | |
end | |
def view | |
html " | |
<span class='todo'> | |
<input type='checkbox' checked=#{@todo.done?} on_click=#{on_done_changed([email protected]?)}/> | |
#{@todo.text} | |
</span> | |
" | |
end | |
end | |
# This is a form, it uses the common pattern of keeping a state variable in sync with the | |
# form input value. | |
struct AddTodoForm < Component | |
# Events can carry any data to the parent. | |
event on_new_todo(todo_data : TodoData) | |
state do | |
property new_todo_text = "" | |
end | |
def view | |
html " | |
<form on_submit=#{on_new_todo(TodoData.new(state.new_todo_text))}> | |
<input type='text' placeholder='new todo' value=#{state.new_todo_text} on_change=#{state.new_todo_text = e.target.value}/> | |
<button type='submit'>Add</button> | |
</form> | |
" | |
end | |
end | |
# Bootstraps the application, rendering the App component inside the <body> tag. | |
bootstrap App.new |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment