I'm currently learning rails and working on what I'm sure is everyone's first rails app, a simple todo list. I need to implement a checkbox next to the items to indicate whether they are complete or not. Each item has a boolean attribute called "completed" in their model. I have found a couple checkbox questions while searching but none explain the syntax very easily in the context of the index view.
Also, I really want the checkbox to work without a submit button. I know I could accomplish something like this using AngularJS's ng-model but I don't think it would be practical to implement angular for such a small thing and I don't know how angular works with rails.
If anyone could give me a pointer in the right direction, it would be greatly appreciated. Here's my index.html.erb for reference.
<h1>
To Do List
</h1>
<table>
<tr>
<% #todo_items.each do |item| %>
<!-- Checkbox here -->
<tc style="<%= 'text-decoration: line-through' if item.completed %>">
<%= link_to item.title, item %>
</tc>
<tc>
<%= item.description %>
</tc>
<tc>
<%= link_to "Edit", edit_todo_item_path(item) %>
</tc>
<tc>
<%= link_to "Delete",item, data:{:confirm => "Are you sure you want to delete this item?"}, :method => :delete %>
</tc>
<hr/>
<% end %>
</tr>
</table>
<p>
<%= link_to "Add Item", new_todo_item_path %>
</p>
This is my way, I don't know this way is right direction or not but this works for me (also different case but same of concept).
views for checkbox
You could put an id item or something into attribute of checkbox for find an object in controller if you send data to controller for get record of object, and you could define if attribute completed of record is true or false:
<%= check_box_tag :completed_item, 1, item.completed? ? true : false, { class: 'name-of-class', data: { id: item.id} } %>
controller
You need two action call set_completed and remove_completed, and also you don't need templates for them, just use format as json:
before_action :set_item, only [:set_completed, :remove_completed, :other_action]
def set_completed
#item.set_completed!
respond_to do |format|
format.json { render :json => { :success => true } }
end
end
def remove_completed
#item.remove_completed!
respond_to do |format|
format.json { render :json => { :success => true } }
end
end
private
def set_item
#item = Item.find params[:id]
end
Model
For set_completed! and remove_completed! you could define in your model
def set_default!
self.update_attributes(:completed => true)
end
def remove_default!
self.update_attributes(:completed => false)
end
routes
resources :address do
collection do
post 'set_completed'
post 'remove_completed'
end
end
Also, you need help JavaScript for handle send request from view to controller event click of checkbox:
jQuery
$(".completed_item").click(function(){
var check = $(this).is(":checked");
if (check == true){
set_completed($(this).attr('data-id'));
} else{
remove_completed($(this).attr('data-id'));
}
});
function set_completed(data_id) {
$.ajax({
type: 'POST',
url: "/items/set_completed",
data: { id: data_id},
dataType: 'json',
success: function(response){
if(response){
}else{
alert('error');
}
}
})
}
function remove_compelted(data_id) {
$.ajax({
type: 'POST',
url: "/items/set_completed",
data: { id: data_id},
dataType: 'json',
success: function(response){
if(response){
}else{
alert('error');
}
}
})
}
Related
The object Task contains a boolean field done. How to Ajax refresh the page after the change of status? And instead true - false display value complete - incomplete?
routes.rb:
resources :tasks do
member do
post 'done'
end
end
Controller:
def done
#task = Task.find(params[:id])
#task.update_attributes(:done => params[:done])
respond_to do |format|
format.js {}
end
end
View:
....
<td><%= task.done %></td>
<td><%= check_box_tag 'done', task.id , task.done, :class => "task-check", remote: true %></td>
....
<script>
$(".task-check").bind('change', function(){
$.ajax({
url: '/tasks/'+this.value+'/done',
type: 'POST',
data: {"done": this.checked},
});
});
</script>
Update
edited script
<script>
$(".task-check").bind('change', function(){
$.ajax({
url: '/tasks/'+this.value+'/done',
type: 'POST',
data: {"done": this.checked},
}).done(function(ajax_response){
location.reload();
});
});
</script>
and controller
def done
#task = Task.find(params[:id]) # make sure you have this
#task.update_attributes(:done => params["done"])
render :json => { data: "Success", is_done: params[:done] }
end
How to update page content without having to reboot ?
Controller:
def done
#task = Task.find(params[:id]) # make sure you have this
#task.update_attributes(:done => params["done"])
render :json => { data: "Success", is_done: params[:done] }
end
View:
....
<td><%= task.done %></td>
<td><%= check_box_tag 'done', task.id , task.done, :class => "task-check" %></td>
....
<script>
$(".task-check").on('change', function(e){
// removed remote-true. you can preventDefault if you need to but doubt page tries to refresh from a checkbox getting checked. e.preventDefault() if you need it
$.ajax({
type: "POST",
url: '/tasks/done',
type: 'POST',
data: {done: $('.task-check').val()},
}).done(function(ajax_response){
// update whatever you want after it completes
});
});
</script>
Didn't test this but I write these all the time. Let me know if it doesn't work post the payload and I'll figure out what's missing.
If you just want to update the value from 'incomplete' to 'complete', you can simply target the element that's called incomplete. if it has a class of, say, 'task-status' you can target that element and update in the .done part of the function. For instance:
$('.task-status').text('Complete')
should replace what previously said incomplete. If it's a text input you might use .val() instead. If it's just text on the page .text() should work.
views/vehicles/_form.html.haml
= link_to "Deactivate", "/vehicles/deactivate"
I want to pass in #vehicle in my link_to above.
How do I do this?
controllers/vehicles_controller.rb
def deactivate
#vehicle = Vehicle.find(params[:id])
#vehicle.active = 0
#vehicle.save
respond_to do |format|
format.html { redirect_to vehicles_url }
format.json { head :no_content }
end
end
To make it easy and in Rails way, you can use Rails resources:
# routes.rb
resources :vehicles do
put 'deactivate', on: :member
end
# view:
= link_to 'Deactivate', deactivate_vehicle_path(#vehicle), method: :put
Best answer already given by Marek Lipka.
There is also a way using ajax
<%= link_to 'Deactivate', javascript::void(0), :class => "deactivate" %>
Put some script:
<script>
$(".deactivate").click(function() {
$.ajax({
type: "post",
url: "/vehicles/deactivate",
data: {id: <%= #vehicle.id %>},
dataType:'script',
beforeSend: function(){
// do whatever you want
},
success: function(response){
// do whatever you want
}
});
});
</script>
This worked for me, I ended up using the Update action in my controller.
= link_to "Deactivate", vehicle_path(#vehicle, :vehicle => {:active => 0}), method: :put, :class=>'btn btn-mini'
On my index page for my Task model, I want to show a checkbox for every row that corresponds to the boolean field "complete" in my Task database table.
Currently my code gets into the method "Complete", but it does not contain the value of the checkbox that the user just did (i.e. if they just checked the box, it does not pass true to my "Complete" method).
How can i pass the value that the user just performed - either checked or un checked?
/views/tasks/index.html.erb
<% #tasks.each_with_index do |task, i| %>
<tr>
<td><%= check_box_tag 'Complete', task.complete, task.complete, :data => {:remote => true, :url => url_for( :action => 'complete', :id => task.id, :complete => task.complete ), :method => :put}, :class => 'input-large' %></td>
</tr>
<% end %>
/controllers/tasks_controller#complete
# PUT /complete/1
def complete
#task = Task.find(params[:id])
p "inside complete"
p "complete = #{params[:complete]}"
#task.complete =
if #task.update_attributes(params[:task])
p "inside update"
render :text => "success"
else
p "inside error"
end
end
The suggestion from this issue in rails/jquery-ujs github repo worked for me: https://github.com/rails/jquery-ujs/issues/440#issuecomment-197029277
For you it would be:
<%= check_box_tag 'complete', '1', task.complete, {
onchange: "$(this).data('params', 'complete=' + this.checked)",
data: { url: url_for(action: 'complete', id: task.id,), remote: true, method: :patch },
} %>
If you are using jQuery, you can write a click event.
$('.input-large').click(function() {
var checked;
if ($(this).is(':checked')) {
checked = true;
} else {
checked = false;
}
$.ajax({
type: "POST",
url: "/tasks/complete",
data: { id: $(this).data('post-id'), checked: checked }
});
});
As of Rails 4, you should be able to ditch all the JS from the original answer. The code in your question should just work due to jQuery UJS magic.
It turns out that adding remote: true to an input causes jquery-ujs to make it ajax-y in all the nice ways. Thoughtbot's "A Tour of Rails jQuery UJS" briefly touches this (and many other good things available); the "Unobtrusive scripting support for jQuery" page in the jQuery UJS wiki does a thorough job on this as well.
check_box_tag 'complete', task.complete ? 'false' : 'true', task.complete, ...
:url => url_for( :action => 'complete', :id => task.id )
This way in your controller you can get params[:complete].
And you should implement complete.js.erb to rerender checkbox, so next click will send inverse value
Or you can implement js on click event
$('.input-large').on('click', function() {
$.ajax({
type: "PUT",
url: "/tasks/complete/" + $(this).data('post-id')
data: { complete: $(this).is(':checked') }
});
});
and don't forget to place data-post-id param to your checkbox
I have a page on my site that lets users respond to invites by either accepting or declining them.
Their response is stored in the database as the boolean 'accepted', and is used to apply the classes 'selected' or 'not_selected' (selected makes the text orange) to the 'attending' div or the 'not attending' div.
<% #going, #not_going = invite.accepted ? ['selected','not_selected'] : ['not_selected','selected'] %>
<%= link_to(outing_invite_accept_path( { :outing_id => invite.outing_id, :invite_id => invite.user_id } )) do %>
<div class="attending_div <%= #going %>">
attending
</div>
<%end %>
<%= link_to(outing_invite_decline_path( { :outing_id => invite.outing_id, :invite_id => invite.user_id } )) do %>
<div class="attending_div <%= #not_going %>">
not attending</div>
</div>
<% end %>
When either div is clicked, it's diverted to the appropriate controller actions:
def invite_accept
#outing = Outing.find(params[:outing_id])
#invite = OutingGuest.find_by_outing_id_and_user_id(params[:outing_id], params[:invite_id])
#invite.update_attribute(:accepted, true)
redirect_to({:action => "index"})
end
def invite_decline
#outing = Outing.find(params[:outing_id])
#invite = OutingGuest.find_by_outing_id_and_user_id(params[:outing_id], params[:invite_id])
#invite.update_attribute(:accepted, false)
redirect_to({:action => "index"})
end
And as right now, this code works just fine. But it requires the index page be refreshed for it to take effect.
I know it's possible to update the page without a refresh using a jQuery ajax call attached to a listener on the appropriate div, but I have no idea what such a call would look like, or where to start, really...
You want to use rail's link_to :remote => true.
See http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
For dealing with callbacks you can bind to certain events that will trigger. For example:
<%= link_to "Click Me!", some_path, :class => 'ajax', :remote => true %>
<script>
jQuery(function($) {
$("a.ajax")
.bind("ajax:loading", console.log('loading'))
.bind("ajax:complete", console.log('complete'))
.bind("ajax:success", function(event, data, status, xhr) {
console.log(data);
})
.bind("ajax:failure", function(xhr, status, error) {
console.log(error);
});
});
</script>
This page is also a pretty good write up: http://www.simonecarletti.com/blog/2010/06/unobtrusive-javascript-in-rails-3/
I've got a little demo setup in which clicking a checkbox toggles an attribute via AJAX. It's working fine, but Rails REALLY wants to render something, so I've basically resorted to creating a blank toggle.js.erb file in my views.
Controller action in question:
def toggle
#task = Task.find(params[:id])
respond_to do |format|
format.js do
if (#task.status != true)
#task.status = true
else
#task.status = false
end
#task.save
render :layout => false
end
end
end
View in question:
<h1>Tasks</h1>
<ul style="list-style-type: none;">
<% #tasks.each do |task| %>
<li id="<%= dom_id(task) %>">
<%= check_box_tag(dom_id(task), value = nil, checked = task.status) %>
<%= task.action %> <%= link_to 'Edit', edit_task_path(task) %>
<%= link_to 'Delete', task, :confirm => 'Are you sure?', :method => :delete, :remote => true %>
</li>
<% end %>
</ul>
<%= link_to 'New Task', new_task_path %>
<script>
$$('input').each(function(el) {
el.observe('click', function(event) {
// Get the task ID
var elId = el.id.split("_")[1];
// Build the toggle action path
var togglePath = '/tasks/' + elId + '/toggle/';
// Create request, disable checkbox, send request,
// enable checkbox on completion
new Ajax.Request(togglePath, {
onCreate: function() {
el.disable();
},
onSuccess: function(response) {
},
onComplete: function() {
el.enable();
}
});
});
});
</script>
Without the blank toggle.js.erb file I've got in the views, Rails still gives me an error saying that it's trying to render something.
Ultimately, I'd like to both not have to have a blank toggle.js.erb file, and I'd like to get that Prototype stuff into my static JavaScript stuff and out of the view.
I'm pretty new to Rails, so there's probably an easier way to be doing this, but I'm kind of stuck here.
render :layout => false means that you want to render 'toggle' view without layout.
If you don't want render anything at all, you should use :nothing => true option
def toggle
#task = Task.find(params[:id])
#task.toggle! :status
# if it used only by AJAX call, you don't rly need for 'respond_to'
render :nothing => true
end
EDIT: In Rails4/5 you can use head :ok instead of render nothing: true, it's more preferable way to do this, but result is the same.