(before demo begins:
bin/rails g reactrb:component Foo (to preload bin/rails)
then remove all components, Todo controller, and put model back into top level directory, remove sync macros, and remove routes, close all editor tabs
then run rspec spec/components/footer_link_spec.rb so its in the command buffer
rails new .
for this demo we are have some predefined specs defined, and we will use the standard Todo example style sheet…
cp -r ../TodoTests .
move the .css file to where it belongs
edit Gemfile, add gem ‘reactive_rails_generator’ and all the stuff from spec/Gemfile
bundle install rails g reactrb:install --all bundle update
now we have our basic rails app plus everything we need for react.rb
lets add a simple todo model, where each todo has a title, and a completed flag...
bundle exec rails g model todo title:string completed:boolean
edit the migration and add: “, null: false, default: false”
bundle exec rake db:migrate bundle exec rake db:test:prepare
move the todo to public (explain why), open and add scopes (scope :completed tab)
talk about FooterLink, then run
bundle exec rails g reactrb:component FooterLink we will pass 2 parameters to our FooterLink: the scope (:all, :completed, or :active) that this link is displaying, the current scope that is being used (so we can highlight it)
add params
def render
a(:class) tab (remove ending parens)
end
explain how we use standard rspec, capybara and some react helpers to run our tests, have a look at footer_link_spec, then run
DRIVER=pg bundle exec rspec spec/components/footer_link_spec.rb
Now that we have a footer link lets build out the Footer component
bundle exec rails g reactrb:component Footer
the footer will take two params: the number “incomplete_count” (an integer) and the current_scope
add params (param :current_scope tab)
add render
def render
footer tab
end
notice how the param current_scope is getting passed from higher level components down through the system. React will determine what needs to be re-rendered as the value of the params change.
DRIVER=ff bundle exec rspec spec/components/footer_spec.rb
Now we need an element to enter a new title and if we do it right we can reuse it to edit the title.
bundle exec rails g reactrb:component EditItem
param todo tab
discuss resulting code
DRIVER=ff bundle exec rspec spec/components/edit_item_spec.rb
open up firebug, and demonstrate saving
binding.pry> quit
so we need one more component and then we can put it all together… as we can see the display consists of a list of todos, so lets build a component that displays a single todo as list item (or li) tag. Lets call our component TodoItem
bundle exec rails g reactrb:component TodoItem
we know its going to take one param (just like EditItem) of type Todo.
param :todo, type: Todo
and we can start with a very simple render method that shows the todo title in a label,
we will tag the li with the todo_item class
def render
li.todo_item do
label { todo.title }
end
end
______________________________
OPTIONAL:
DRIVER=ff bundle exec rspec spec/components/todo_item_spec.rb
now each todo should have a checkbox to the left, that indicates whether the todo is completed or not.
input.toggle(type: :checkbox, (params.todo.completed ? :checked : :unchecked) => true).
on(:click) do
params.todo.completed = !params.todo.completed
params.todo.save
end
[OPTIONAL: ...]
DRIVER=ff bundle exec rspec spec/components/todo_item_spec.rb
the next thing we need to add is a delete link.
a.destroy.on(:click) { params.todo.destroy }
[OPTIONAL: ...]
DRIVER=ff bundle exec rspec spec/components/todo_item_spec.rb
Finally we also want to edit the title using the EditTitle component, when the user double clicks on the title.
This means that the component will have to be in 1 of 2 states: Either displaying the component or editing the component.
So lets add a react “state variable” to our component and use it to determine whether we display the component, or use the EditItem component.
Now we want to switch modes when the user doubleClick s on the title: Each state has a “bang” method that is used to update the state, and trigger any necessary re-renders.
(add doubleClick handler to label)
so when we double click the component it switches to edit mode, and we just need to switch back once the user saves their changes:
on(:save) { state.editing! false }.
on(:cancel) { state.editing! false }
DRIVER=ff bundle exec rspec spec/components/todo_item_spec.rb
Our last component will be the top level “App”.
In keeping with Rails terminology we will call it Index.
bundle exec rails g reactrb:component Index
we will need a controller
bundle exec rails g controller Todos index
update routes: get 'todos' => 'todos#index'
update the controller render_component
try it out:
DRIVER=ff bundle exec rescue rspec spec/integration/index_spec.rb
(we will use rescue to see what we need to do)
we see the component exists…
first what it should do is display the components in a list, we will pick up the styles with the todo-app and todo-list classes
section.todo_app do
ul.todo_list do
Todo.all.each do |todo|
TodoItem todo: todo
end
end
DRIVER=ff bundle exec rescue rspec spec/integration/index_spec.rb
now we need a way to add a new todo… hmmm… a new_todo lets make it a state variable that will hold the new_todo. When the EditItem component saves it, we will create another new todo...
define_state :new_todo
before_mount do
state.new_todo! Todo.new
end
…
header.header do
h1 {"todos"}
EditItem(todo: state.new_todo).
on(:save) { state.new_todo! Todo.new(completed: false) }
end
DRIVER=ff bundle exec rescue rspec spec/integration/index_spec.rb
okay lets add our footer component at the bottom:
Footer(scope: :all, incomplete_count: Todo.active.count)
DRIVER=ff bundle exec rescue rspec spec/integration/index_spec.rb
cool that works…
The last thing we need to do is wire up the links (all, completed, active) in the footer. Right now they do nothing… In a real app we would use a router component (like reactive-router) that tracks link transitions, updates the URL, and uses HTML 5 history to allow uses to have the back and forward button.
We will just use a very simple implementation that demonstrates react’s flux programming pattern…
First we will “export” a state variable called scope from the top level index component. This will make it available as a class method outside of Index. We will initialize it to :all in the before_mount callback, and then use its value in place of “:all” within index.
Finally we will update scope from our FooterLink component, when the user clicks on a link.
Everything basically works now, but there are few odds an ends to make it perfect…
to_sync(:completed) { completed }
to_sync(:active) { !completed }
live-demo-notes
By Mitch VanDuyn
live-demo-notes
Live demo notes
- 1,325