Last active
March 4, 2022 13:09
-
-
Save lbguilherme/f32370ed9f3f029653362141ab3e01e3 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. | |
component App do | |
# 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. | |
view do | |
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. | |
component Todo do | |
# 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) | |
# Declare any component properties here. Their values come from the parent. If an initial value | |
# is given, they will be optional. | |
@todo : TodoData | |
# O estilo vem de um arquivo externo usando sintaxe padrão SCSS. As regras CSS serão escopadas para | |
# afetar apenas este componente, nenhum outro. | |
style "./Todo.scss" | |
view do | |
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. | |
component AddTodoForm do | |
# Events can carry any data to the parent. | |
event on_new_todo(todo_data : TodoData) | |
state do | |
property new_todo_text = "" | |
end | |
view do | |
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. | |
App.run! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment