/ckl

Rails 5 todo list app with jQuery and Ajax https://rubyplus.com/articles/4211-Using-Ajax-and-jQuery-in-Rails-5-Apps

Primary LanguageRuby

Watch the Screencast

 Rails 5 todo list app with jQuery and Ajax

rails new ckl --skip-spring

rails g model task name complete:boolean

seeds.rb:

Task.create! name: "Meet Mr. Miyagi", complete: true
Task.create! name: "Paint the fence", complete: true
Task.create! name: "Wax the car"
Task.create! name: "Sand the deck"
rails g controller tasks index new 

Default complete flag to false.

class CreateTasks < ActiveRecord::Migration[5.0]
  def change
    create_table :tasks do |t|
      t.string :name
      t.boolean :complete, default: false, null: false

      t.timestamps
    end
  end
end
class TasksController < ApplicationController
  def index
    @incomplete_tasks = Task.where(complete: false)
    @complete_tasks = Task.where(complete: true)
  end

  def new
    @task = Task.new
  end
  
  def create
    @task = Task.create!(allowed_params)
    
    redirect_to tasks_url
  end
  
  def update
    @task = Task.find(params[:id])
    @task.update_attributes!(allowed_params)
    
    redirect_to tasks_url
  end
  
  def destroy
    @task = Task.destroy(params[:id])
    
    redirect_to tasks_url
  end
  
  private
  
  def allowed_params
    params.require(:task).permit(:name, :complete)
  end
end

routes.rb:

Rails.application.routes.draw do
  resources :tasks
  
  root to: 'tasks#index'
end
rails db:migrate
rails db:seed

index.html.erb:

<h1>Task Dog</h1>
<%= link_to "New Task", new_task_path, id: "new_link" %>
<h2>Incomplete Tasks</h2>
<div class="tasks" id="incomplete_tasks">
  <%= render @incomplete_tasks %>
</div>

<h2>Comlpete Tasks</h2>
<div class="tasks" id="complete_tasks">
  <%= render @complete_tasks %>
</div>

task partial:

<%= form_for task do |f| %>
  <%= f.check_box :complete %>
  <%= f.submit "Update" %>
  <%= f.label :complete, task.name %>
  <%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"} %>
<% end %>

new.html.erb:

<h1>New Task</h1>
<%= render 'form' %>
<p><%= link_to "Back to tasks", tasks_path %></p>

_form.html.erb:

<%= form_for @task do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

Check the source code https://github.com/bparanj/ckl for css and the layout file changes.

new.js.erb:

$('#new_link').hide().after('<%= j render("form") %>');

Make the 'New Task' link render a form using ajax. Add remote: true to the form partial.

<%= form_for @task, remote: true do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

and the 'New Task' link:

<%= link_to "New Task", new_task_path, id: "new_link", remote: true %>

You can create a task with the inline form. You can see that there is a redirect after the task is created.

Started POST "/tasks" for ::1 at 2016-07-07 16:22:50 -0700
Processing by TasksController#create as JS
  Parameters: {"utf8"=>"✓", "task"=>{"name"=>"Buy a necklace"}, "commit"=>"Create Task"}
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "tasks" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Buy a necklace"], ["created_at", 2016-07-07 23:22:50 UTC], ["updated_at", 2016-07-07 23:22:50 UTC]]
   (2.0ms)  commit transaction
Redirected to http://localhost:3000/tasks
Completed 200 OK in 4ms (ActiveRecord: 2.3ms)

Started GET "/tasks" for ::1 at 2016-07-07 16:22:50 -0700
Processing by TasksController#index as HTML
  Rendering tasks/index.html.erb within layouts/application
  Task Load (0.3ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", false]]
  Rendered collection of tasks/_task.html.erb [6 times] (5.2ms)
  Task Load (0.2ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", true]]
  Rendered collection of tasks/_task.html.erb [2 times] (1.2ms)
  Rendered tasks/index.html.erb within layouts/application (12.0ms)
Completed 200 OK in 36ms (Views: 33.6ms | ActiveRecord: 0.5ms)

Change the create action:

def create
  @task = Task.create!(allowed_params)
  
  respond_to do |f|
    f.html { redirect_to tasks_url }
    f.js
  end
end

Create create.js.erb:

$('#new_task').remove();
$('#new_link').show();
$('#incomplete_tasks').append('<%= j render(@task) %>')

You can create a new task. The new task goes to the bottom of incomplete tasks list. There is no redirect when a new task is created.

Started POST "/tasks" for ::1 at 2016-07-07 16:30:09 -0700
Processing by TasksController#create as JS
  Parameters: {"utf8"=>"✓", "task"=>{"name"=>"Buy a puppy"}, "commit"=>"Create Task"}
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "tasks" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Buy a puppy"], ["created_at", 2016-07-07 23:30:09 UTC], ["updated_at", 2016-07-07 23:30:09 UTC]]
   (0.4ms)  commit transaction
  Rendering tasks/create.js.erb
  Rendered tasks/_task.html.erb (1.2ms)
  Rendered tasks/create.js.erb (2.9ms)
Completed 200 OK in 10ms (Views: 6.1ms | ActiveRecord: 0.7ms)

Remove link redirects as you can see in the log file:

Started DELETE "/tasks/13" for ::1 at 2016-07-07 18:13:44 -0700
Processing by TasksController#destroy as HTML
  Parameters: {"authenticity_token"=>"M60nBritB5vowdT03ZeulVOcC0NkNAfBdsKswwj5/HtqS0cdYVk6KLktRGZmNiuCvK2ITkZ/fJ5lR/BfKp1Isg==", "id"=>"13"}
  Task Load (0.2ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" = ? LIMIT ?  [["id", 13], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  SQL (0.3ms)  DELETE FROM "tasks" WHERE "tasks"."id" = ?  [["id", 13]]
   (0.5ms)  commit transaction
Redirected to http://localhost:3000/tasks
Completed 302 Found in 3ms (ActiveRecord: 1.0ms)

Started GET "/tasks" for ::1 at 2016-07-07 18:13:44 -0700
Processing by TasksController#index as HTML
  Rendering tasks/index.html.erb within layouts/application
  Task Load (0.1ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", false]]
  Rendered collection of tasks/_task.html.erb [1 times] (1.0ms)
  Task Load (0.1ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", true]]
  Rendered collection of tasks/_task.html.erb [2 times] (0.9ms)
  Rendered tasks/index.html.erb within layouts/application (4.9ms)
Completed 200 OK in 26ms (Views: 24.5ms | ActiveRecord: 0.2ms)

Add the remote flag to the remove link in task partial.

<%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>

Change the destroy action to handle the ajax call.

def destroy
  @task = Task.destroy(params[:id])
  
  respond_to do |f|
    f.html { redirect_to tasks_url }
    f.js
  end
end

Create destroy.js.erb:

$('#edit_task_<%= @task.id %>').remove();

Reload the tasks index page. Remove the task by clicking (remove):

Started DELETE "/tasks/1" for ::1 at 2016-07-07 18:26:33 -0700
Processing by TasksController#destroy as JS
  Parameters: {"id"=>"1"}
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  SQL (1.6ms)  DELETE FROM "tasks" WHERE "tasks"."id" = ?  [["id", 1]]
   (0.4ms)  commit transaction
  Rendering tasks/destroy.js.erb
  Rendered tasks/destroy.js.erb (0.4ms)
Completed 200 OK in 10ms (Views: 3.8ms | ActiveRecord: 2.2ms)

Marking a task as complete without update button. Submit the form when the checkbox is clicked.

In tasks.js

$(function() {
	$('.edit_task input[type=checkbox]').click(function() {
		alert('clicked');
	});
});

Reload the tasks index page and click on the check box. Change the javascript to submit the form when the checkbox is clicked and remove all the update buttons.

$(function() {
	$('.edit_task input[type=submit]').remove();
	$('.edit_task input[type=checkbox]').click(function() {
		$(this).parent('form').submit();
	});
});

Reload the page. Check any incomplete tasks and you will see it moved to the Complete tasks section. You can also check the complete tasks and it will go into the incomplete tasks section.

Create a new task and you will see the update button show up again. To fix this, change tasks.js:

jQuery.fn.submitOnCheck = function() {
	this.find('input[type=submit]').remove();
	this.find('input[type=checkbox]').click(function() {
		$(this).parent('form').submit();
	});
	return this;
}
$(function() {
	$('.edit_task').submitOnCheck();
});

task partial, remote: true:

<%= form_for task, remote: true do |f| %>
  <%= f.check_box :complete %>
  <%= f.submit "Update" %>
  <%= f.label :complete, task.name %>
  <%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>
<% end %>

Change the create.js.erb:

$('#new_task').remove();
$('#new_link').show();
$('#incomplete_tasks').append('<%= j render(@task) %>');
$('#edit_task_<%= @task.id %>').submitOnCheck();
def update
  @task = Task.find(params[:id])
  @task.update_attributes!(allowed_params)
  
  respond_to do |f|
    f.html { redirect_to tasks_url }
    f.js
  end
end

update.js.erb:

<% if @task.complete? %>
  $('#edit_task_<%= @task.id %>').appendTo('#complete_tasks');
<% else %>
  $('#edit_task_<%= @task.id %>').appendTo('#incomplete_tasks');
<% end %>