Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

Flux TodoMVC in Hyperloop

Mitch VanDuyn edited this page Jan 25, 2017 · 8 revisions

The intention is to follow closely the Flux TodoMVC tutorial, but do it in Hyperloop, and see what happens

hyperloop-todomvc

####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.

Prerequisites

  • Opal/Ruby ??? (original had stuff about ES6)

1. Getting started

  1. make a new directory
  2. 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>
  1. run server (have look how to do this)
  2. visit localhost:3000/index.html in your browser
  • You should see a blank page that says "Hello World!"

2. Set up TodoMVC assets

####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

3. Setting up Your App Structure

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!"

4. Rendering some Todos

In stores/todo_store.rb we are going to...

  1. add the class Todo inside of TodoStore (this will hold each individual Todo.)
  2. add a private state variable to keep an array of our Todos.
  3. push a new Todo onto state.todos in our AddTodo receiver.
  4. 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

5. Adding some interactivity

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.

6. Remaining functionality

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.

  1. 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
  1. Add clear completed button to the Footer
  • Create deleteCompletedTodos action
  • Add button to fire action to the footer
  1. 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
  1. Add ability to edit todos on double click
  • Create the TodoEditStore which tracks the ID of the Todo currently being edited
  • Create startEditingTodo and stopEditingTodo actions
  • Create editTodo action
  • Create TodoItem view component with editing functionality
Clone this wiki locally