Skip to content

Instantly share code, notes, and snippets.

@lbguilherme
Last active March 4, 2022 13:09
Show Gist options
  • Save lbguilherme/f32370ed9f3f029653362141ab3e01e3 to your computer and use it in GitHub Desktop.
Save lbguilherme/f32370ed9f3f029653362141ab3e01e3 to your computer and use it in GitHub Desktop.
Crystal front-end app, what would it look like?
# 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