Giter VIP home page Giter VIP logo

cs169-todo-js's Introduction

TODO JS

Recall the Todo App from an earlier discussion: a static todo list with checkboxes that can toggle task status. THe purpose of this app demo is to transition a static, page-by-page web application to a dynamic SPA Javascript-run app. We will not be using any fancy Javascript frameworks such as Knockout or React; those are too complicated for the scope of this demo.

Provided is a slightly updated version of the Todo app. The only major difference from the previous version is the add Todo input. We can now create new Todo objects. Setup is simple, as before. Simply run:

bundle install
rake db:migrate
rake db:seed

To run the app, type in your console:

rails s

We have removed tests to expedite the process; normally, however, TDD is still optimal development practice.

Getting Away from ERB

Setting Up Handlebars

True Javascript does not play very well with ERB. This is because ERB is handled server-side, while Javascript happens client-side. To simplify things, we'll maintain all of our templates on the client side. To do this, we will use a nice Javascript library called Handlebars, which helps us render HTML files from Javascript objects.

Let's imagine the two key components we want to replace with Handlebars templates. These should be the creation form and the task list itself. Get rid of the ERB code and insert placeholder divs that will represent our content for now. These will act as containers for our Handlebars code. Ultimately, our app/views/todos/index.html.erb should look very simple:

<h1>Task List</h1>

<div id="todo-form-create-container"></div>

<div id="todo-table-container"></div>

Now let's create the Handlebars templates. These should look similar to the ERB code we just replaced, with one tiny catch: we can't use embedded Ruby. The solution is use Handlebars syntax to populate the desired fields.

Create a file app/assets/javascripts/templates/todos/todo_form_create.hbs. It should include the following:

<form id="todo-form-create" action="/todo/create" method="post">
  <div class="input-group">
    <input class="form-control" id="todo_description" name="todo[description]" type="text">
    <span class="input-group-btn">
      <button class="btn btn-default" name="button" type="submit">
        New Task
      </button>
    </span>
  </div>
</form>

This should look pretty similar to the old ERB code.

Likewise, create the Handlebars table template app/assets/javascripts/templates/todos/todo_table.hbs:

<table class="table table-striped table-bordered" style="margin-top:50px;">
  <thead>
    <tr>
      <th>Completed</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    {{#each this}}
      <tr>
        <td>
          <form class="todo-form-toggle" action="/todo/{{this.id}}/toggle" method="post">
            <button name="button" style="border:None;background:None;" type="submit">
              <span class="glyphicon glyphicon-{{#if this.completed}}check{{else}}unchecked{{/if}}"></span>
            </button>
          </form>
        </td>
        <td>{{this.description}}</td>
      </tr>
    {{/each}}
  </tbody>
</table>

Other than Ruby vs Handlebars syntax, the only major difference between the ERB and HBS files is that we get rid of Rails' form tag helper. This means we can no longer use PUT and nice authenticity token management. Let's just use a hackish rememdy. First, let's change all our routes in config/routes.rb to POST, which is supported by HTML.

post 'todo/:id/toggle', :to => 'todos#toggle', :as => 'todo_toggle'
post 'todo/create', :to => 'todos#create', :as => 'new_todo'

Next (and even more hackish), let's just forgo authenticity tokens. This is probably fine for your dev environment - hopefully nobody tries to hack your silly task list. In controllers/todos_controller.rb, let's just add one line:

class TodosController < ApplicationController
  skip_before_filter :verify_authenticity_token

Making an AJAX Call

Voilà! The easy part is done. Before we do some real Javascript, let's add a minor but infinitely important feature to the controller.
In controllers/todos_controller.rb, add the following to the index action:

def index
  @todos = Todo.all

  respond_to do |format|
    format.html
    format.json { render :json => @todos }
  end
end

This allows us to respond to JSON requests, and give the corresponding @todos object in a JSON-friendly format. Let's see how we actually consume this information.

Create a file called app/assets/javascripts/todos.coffee. In this file, write the following code:

$(document).ready( ->
  setupDocument()
)

setupDocument = ->
  $('#todo-form-create-container').html(HandlebarsTemplates['todos/todo_form_create']())
  loadTodos()

loadTodos = ->
  $.ajax({
    url: '/',
    dataType: 'json',
  }).success((data) ->
    $('#todo-table-container').html(HandlebarsTemplates['todos/todo_table'](data))
  )

This file is written in CoffeeScript, a preprocessor for Javascript. It makes it look more Pythonic and Ruby-like. Essentially, the -> defines a function.
Specifically, this file will load the #todo-form-create-container element with the Handlebars template we created for the create form. For the task list, it's a little more complicated. In loadTodos, we first make an AJAX call to the / path, which you'll recall is the index action for the Todo controller. Thanks to the controller magic you just wrote, this AJAX call will grab the JSON data and pass it directly to the Handelbars templater. Inspect the todo_table.hbs file you created earlier and try to guess what happens. Hint: the outermost this refers to the context passed in: in this instance, data.
After the templating is complete, we load the #todo-table-container element with our rendered HTML.

Making an SPA

Finally: we've got this Handlebars stuff down, and you've gotten an introduction to Javascript/CoffeeScript.
Now let's make this app interesting by adding in some user-triggered Javascript. What we want is for the forms to be handled by AJAX instead of HTML UI events.

To start, we must first allow our controllers to accept JSON requests. Add the following lines to the controllers/todos_controller.rb:

def create
  ...
  respond_to do |format|
    format.html { redirect_to root_path }
    format.json { render :json => @todo }
  end
end

def toggle
  ...
  respond_to do |format|
    format.html { redirect_to root_path }
    format.json { render :json => @todo }
  end
end

That was simple, but the rest of our Javascript won't be so straightforward. On the bright side, the rest of our edits will be restricted to app/assets/javascripts/todos.coffee.

What we essentially want is to override the form submission events. For the create form this is relatively simple. In setupDocument, after injecting the #todo-form-create-container element, add the following code:

$('#todo-form-create').submit((e) ->
  e.preventDefault()
  $.ajax({
    url: $(this).attr('action'),
    method: 'post',
    dataType: 'json',
    data: $(this).serialize(),
  }).success((data) ->
    loadTodos()
  )
)

Let's inspect this line-by-line. First, we use JQuery to grab the #todo-form-create element, which is of type form. We replace the submit event with our own function, which is the code block underneath. This function makes an AJAX call to the same URL, with the same parameters; we only change the dataType to json. Finally, when the AJAX call is successful, we call loadTodos once more to reload the task list.

Try it out in your browser. You be able to create tasks without getting that intrusive page reload animation.

Now let's move on to the task list forms for toggling completion. In loadTodos under the success callback, right after we inject the #todo-form-toggle element, add the following code:

$('.todo-form-toggle').each((i) ->
  $(this).submit((e) ->
    e.preventDefault()
    $.ajax({
      url: $(this).attr('action'),
      method: 'post',
      dataType: 'json'
    }).success((data) ->
      loadTodos()
    )
  )
)

Notice that the inner block is almost line-by-line the same as the AJAX submission for the create form. The only difference here is that we do it for each form element with class todo-form-toggle. This makes each toggle form an AJAX call and updates the task list accordingly.

Now the page should never redirect anywhere!

cs169-todo-js's People

Contributors

darryqueen avatar ksteph avatar

Watchers

 avatar  avatar

Forkers

mulukenh

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.