-
Notifications
You must be signed in to change notification settings - Fork 18
Flux TodoMVC in Hyperloop
The intention is to follow closely the Flux TodoMVC tutorial, but do it in Hyperloop, and see what happens
####compare the following with the flux version
This example is where you should start. It walks you through creating the classic TodoMVC application using a simple client side only Hyperloop implementation.
- Opal/Ruby ??? (original had stuff about ES6)
- ES6 Overview in 350 Bullet Points
- Additional Resources
- Hyperloop
- links...
- HyperReact
- links
- React
- make a new directory
- add a file named index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hyperloop • TodoMVC</title>
grab style sheet from cdn
<link rel="stylesheet" href="todomvc-common/base.css">
grab hyper-express, hyper-ops, hyper-stores from cdn
</head>
<body>
<div data-hyperloop- = ></div>
</body>
</html>
- run server (have look how to do this)
- visit localhost:3000/index.html in your browser
- You should see a blank page that says "Hello World!"
####compare the following with the flux version
figure this out... maybe we dont need this step..
Refresh examples/my-todomvc/index.html
in your browser.
- The page should now have some styling and still say "Hello World!" in the center.
- If it doesn't, make sure
npm run watch
is still running
- If it doesn't, make sure
Set up folder structure:
src
├── containers
│ └── AppContainer.js
├── data
│ ├── TodoActions.js
│ ├── TodoActionTypes.js
│ ├── TodoDispatcher.js
│ └── TodoStore.js
├── root.js
└── views
└── AppView.js
Create the Operations (known as Actions + ActionTypes in Flux) ####compare the following with the flux version
class AddTodo < HyperOperation
param :text
end
HyperOperation takes care of setting up the dispatcher, action-types and action-creators automatically when it is subclassed.
Now we can set up our first Store! Add a file named stores/todo_store.rb. The Store will save information about all of the Todo objects in our application.
Following the original Flux TodoMVC, TodoStore will only have class methods. ####compare the following with the flux version
# stores/todo_store.rb
class TodoStore < HyperStore::Base
class << self
receives AddTodo do
# Do nothing for now, we will add logic here soon!
end
end
end
Notice how we decouple the AddTodo Operation from its implementation using the receives
method.
Let's set up a simple top level React component. Add a file components/app.rb
.
####compare the following with the flux version
class App < React::Component::Base
render(DIV) { 'Hello from Hyperloop!' }
end
Flux uses containers to further decouple stores from the application components. In Hyperloop this is not necessary as the bindings between components and stores are dynamic and automatic. ####view the flux version
Finally, let's update the our index.html file to load our app component
blah blah
We are using HyperExpress in this tutorial so that is all you have to do. In a real world app, you would compile and bundle the code server side either using a rake task or by having rails sprockets do it for you.
- Reload the page, it should look basically the same as the last step but say "Hello from Hyperloop!"
In stores/todo_store.rb
we are going to...
- add the
class Todo
inside of TodoStore (this will hold each individual Todo.) - add a private state variable to keep an array of our Todos.
- push a new Todo onto state.todos in our AddTodo receiver.
- add an
all
method (a getter) to return the array of todos.
####compare the following with the flux version
# stores/todo_store.rb
class TodoStore < HyperStore
class Todo < Struct.new(:text, :completed)
alias id object_id
end
class << self
private_state todos: []
receives AddTodo do |text|
state.todos! << Todo.new(text: text, completed: false) unless text.blank?
end
def all
state.todos
end
end
end
Notice that state.todos
returns the current value of todos, but state.todos!
indicates that we are modifying the state.
Let's fill out or application components to actually render the Todos that are being stored.
Update components/components.rb
.
####compare the following with the flux version
class AppView < React::Component::Base
render(DIV) do
Header()
exit if TodoStore.all.empty?
Main()
Footer()
end
end
class Header < React::Component::Base
render(HEADER, id: :header)
H1 { 'todos' }
end
end
class Main < React::Component::Base
render(SECTION, id: :main) do
UL(id: 'todo-list') do
TodoStore.all.each do |todo|
LI(key: todo.id) do
DIV(class: :view) do
INPUT(class: :toggle, type: :checkbox, checked: todo.complete)
.on(:change) do
#Empty handler for now, we will implement this later.
end
LABEL { todo.text }
BUTTON(class: :destroy)
.on(:click) do
# Empty handler for now, we will implement this later.
end
end
end
end
end
end
end
class Footer < React::Component::Base
render(FOOTER, id: :footer) do
SPAN(id: 'todo-count') do
STRONG { TodoStore.all.count }
' items left'
end
end
end
To make sure it all works we have to create some fake data for now.
ready? do
['My first task', 'Another task', 'Finish this tutorial'].each do |text|
AddTodo(text: text)
end
end
- Refresh the page and you should see some data that is coming from your
TodoStore
!- Keep in mind that it's not interactive yet, so no buttons will work
Let's add a few more Operations (called Actions in flux) so that the buttons do something. ####compare the following with the flux version
class DeleteTodo < HyperOperation
param :todo
end
class ToggleTodo < HyperOperation
param :todo
end
class TodoStore < HyperStore
receives DeleteTodo { |todo| state.todos!.delete todo }
receives ToggleTodo { |todo| todo.completed != todo.completed; state.todos! }
end
Now that the Store is capable of deleting or toggling a Todo, let's hook it up to our components. ####compare the following with the flux version
In our Main component, we execute the Toggle and DeleteTodo operations at the appropriate user input:
class Main < React::Component::Base
render(SECTION, id: :main) do
UL(id: 'todo-list') do
TodoStore.all.each do |todo|
LI(key: todo.id) do
DIV(class: :view) do
INPUT(class: :toggle, type: :checkbox, checked: todo.complete)
.on(:change) { ToggleTodo(todo: todo) }
LABEL { todo.text }
BUTTON(class: :destroy)
.on(:click) { DeleteTodo(todo: todo) }
end
end
end
end
end
end
In the Footer component we can now count the number of complete todos:
class Footer < React::Component::Base
render(FOOTER, id: :footer) do
remaining = TodoStore.all.select { |todo| todo.complete }.count
phrase = remaining == 1 ? ' item left' : ' items left'
SPAN(id: 'todo-count') do
STRONG { remaining }
phrase
end
end
end
- Refresh the page and you should be able to toggle todos and delete them. Toggling todos should also update the counter of todos remaining in the footer.
EDIT NAMES TO BE RUBY
Now you should be familiar enough with the structure of the todo app to implement the remaining pieces on your own. This last step outlines a good ordering for completing them. Make sure to reference the full example implementation as needed.
- Create the NewTodo view
- Create the
TodoDraftStore
which tracks the contents of the NewTodo input, it will respond to two actions:-
UPDATE_DRAFT
which changes the draft contents -
ADD_TODO
which clears the draft contents (because the todo was added and is no longer a draft) - Note: It would also be reasonable to keep track of this in React state, but in this tutorial we will make an effort to have all components be controlled so you get more experience dealing with stores.
-
- Create the
updateDraft
action and pass through container - Hook everything up to the view
- Add clear completed button to the Footer
- Create
deleteCompletedTodos
action - Add button to fire action to the footer
- Add Mark all as complete button
- Create
toggleAllTodos
action- If any todos are incomplete, this marks them all as complete
- If all todos are complete, this marks them all as incomplete
- Hook it up to Main view
- Add ability to edit todos on double click
- Create the
TodoEditStore
which tracks the ID of the Todo currently being edited - Create
startEditingTodo
andstopEditingTodo
actions - Create
editTodo
action - Create TodoItem view component with editing functionality