I am new to rails and trying to change the value of a boolean via a checkbox and using jquery ajax:
<%- #tasks.each do |task| %>
<div class="task-wrapper">
<%= check_box_tag 'completed', task.id , task.completed, :class => "task-check" %>
<%= content_tag :span, task.task %>
<%= content_tag :span, task.deadline %>
</div>
<% end %>
and the javascript:
$(".task-check").bind('change', function(){
if (this.checked){
var bool = this.checked ? 1 : 0;
$.ajax({
url: '/todos/toggle',
type: 'POST',
data: '{"task_id":"'+ this.value +'", "bool":"'+ bool +'"}'
});
}
else {
alert("no");
}
});
then the controller:
def toggle(task_id, bool)
#task = Todo.find_by_id(task_id)
if #task != nil?
#task.update_attributes(:completed => bool)
else
set_flash "Error, please try again"
end
end
finally the routes:
resources :todos do
member do
post 'toggle'
end
end
also tried collection but gives the same error.
when ever i try it i get a 404 error on the action.
what is the problem?
thanks
As of Rails 4, there's a way to do this without needing any additional JS or CSS:
<%= check_box_tag 'completed', task.id, task.completed,
data: {
remote: true,
url: url_for(action: :toggle, id: task.id),
method: "POST"
} %>
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.
Try the following (leaving everything else as is):
the javascript:
$(".task-check").bind('change', function(){
if (this.checked){
$.ajax({
url: '/todos/'+this.value+'/toggle',
type: 'POST',
data: {"completed": this.checked}
});
}
else {
alert("no");
}
});
the controller:
def toggle
#task = Todo.find(params[:id])
if #task.update_attributes(:completed => params[:completed])
# ... update successful
else
# ... update failed
end
end
Take a look at bundle exec rake routes to show you the paths that rails generates. In the case of your post 'toggle' which is a member you get a path like /todos/:id/toggle, hence the updated url in the ajax.
In the controller the :id from the path ends up in params[:id]. The data from the ajax request also ends up in the params hash, hence params[:completed].
Related
I have a situation where I'm posting comments using channels and upon updating rails to 6.1 I've gotten a weird error. The create method looks as follows
def create
offer = Offer.find(comment_params[:offer_id])
#comment = Comment.new(
user_id: current_user.id,
offer_id: offer.id,
content: comment_params[:content],
attachment_file: comment_params[:attachment_file]
)
if #comment.save
CommentChannel.broadcast_to offer, message: render_comment(#comment)
render json: { success: true }
else
render json: { success: true }
end
end
and the render_comment method is as follows
def render_comment(comment)
render_to_string(partial: "offers/comment", locals: {comment: comment}, layout: false)
end
This is working as it were, but now suddenly whenever I'm creating a new comment it redirects to this page
the comment gets created and everything seems to work as it did except for this random redirect. What might be the cause of this? I assumed it had something to do with render_to_string, but couldn't figure this out.
Also here is the form
<%= form_with model: Comment.new, id: 'new-comment' do |f| %>
<%= f.hidden_field :offer_id, value: #offer.id %>
<div>
<%= f.text_area :content %>
<div class="mt-3 flex items-center justify-between">
<%= f.submit "Comment", id:"comment-button",%>
</div>
<% end %>
What you're seeing is not a redirect. You're sending a non XHR request and the server is returning the rendered JSON and an application/json content type.
You can see that as it has triggered the fancypants JSON syntax highlighting and formatting in the browser.
What you really want it something like:
# POST /offers/1/comments.json
def create
offer = Offer.find(params[:offer_id])
comment = offer.comments.new(comment_params) do |c|
c.user = current_user.id
end
if comment.save
CommentChannel.broadcast_to offer, message: render_comment(comment)
render json: comment,
status: :created,
location: comment
else
render json: { errors: comment.errors.full_messages },
status: :unprocessable_entity
end
end
# ...
private
def comment_params
params.require(:comment)
.permit(:content, :attachment_file)
end
This give a much better API and you can test the response code in your integration tests (you are testing aren't you? ;) ) instead of parsing the JSON.
A 201 Created response does not have to contain the entity in the response but its really useful for ajax to get the newly created object back.
You should also nest the route instead of using a hidden input:
# routes.rb
resources :offers do
resources :comments, only: [:create]
end
<%= form_with model: [offer, comment],
id: 'new-comment',
"data-type" => 'json',
remote: true do |f| %>
<%= f.text_area :content %>
<div class="mt-3 flex items-center justify-between">
<%= f.submit "Comment", id:"comment-button",%>
</div>
<% end %>
Since Rails 6.1 form_with does not default to remote: true and instead you can opt in with the config.action_view.form_with_generates_remote_forms option. Rails UJS defaults to application/javascript as the type for XHR requests.
Since you're sending the correct response codes it will trigger the appropriate ajax handler methods:
let form = document.getElementById('new-comment');
// Handle when a comment is created
form.addEventListener("ajax:success", (event) => {
const [data, status, xhr] = event.detail;
console.log(data); // the newly created comment
});
// Handle invalid input
form.addEventListener("ajax:error", (event) => {
const [data, status, xhr] = event.detail;
console.log(data.errors); // the errors
});
Right now I am displaying notifications for a user on a view. I have the ajax working where it removes the item from the database by clicking the close link. However, what I can't figure out is how to have the view update without actually refreshing the page.
Am I going to have to use a partial of some sort? I haven't really done anything like this and googling is really not helping me since I seem to be going down the rabbit hole.
Right now for my view I have:
<% current_user.notifications.each do |n| %>
<div class="alert alert-info">
<%= link_to 'x', n, :method => :delete, :remote => true, :class => 'delete_notification close' %>
<%= n.title %>
<%= n.description %>
</div>
<% end %>
and then I have my js:
$('.delete_notification').bind('ajax:success', function() {
$(this).closest('tr').fadeOut();
});
I'm not really sure how to ask the question since I am lost on what the 'next step' is. I will provide more information if/when I know what else I need to provide.
Thanks in advance!
UPDATE:
I accepted an answer seeing as it was extremely helpful and exactly what I was looking for!
What I ended up comming up with is:
$(function(){
$(".delete_notification").click(function(){
var $this = $(this);
var url = $this.data('url');
$.ajax(url, {
method: 'DELETE',
type: 'POST',
dataType: 'json',
success: function() {
var parent = $this.parent();
parent.fadeOut("slow");
}
});
});
});
The link_to's callback option (:update) is removed since Rails 3, so I'd rather implement the AJAX link myself.
<% current_user.notifications.each do |n| %>
<div class="alert alert-info">
<%= link_to 'x', n, :class => 'delete_notification close' %>
<%= n.title %>
<%= n.description %>
</div>
<% end %>
Setup AJAX so that it passes Rails' CSRF protection:
$.ajaxSetup({
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))}
});
And finally the effect you want:
$('.delete_notification').click(function(ev) {
ev.preventDefault(); // prevent refreshing page
var $this = $(this);
var url = $this.attr('href');
$.ajax(url, {method: 'DELETE'}).done(function() {
$this.closest('tr').fadeOut();
});
});
Maybe you should also modify your controller (I haven't tested this):
class NotificationsController < ApplicationController
# Other actions ...
def destroy
# Your logic (remove rendering or redirection)
head :no_content
end
end
Create a js template in your view called destroy.js.erb, and in that file include the javascript you want to run - something like this:
$('#delete_notification_<%= params[:id] %>').closest('tr').fadeOut();
In your view make sure you specify the ID of the link:
<%= link_to 'x', n, method: :delete, remote: true, class: 'delete_notification close' id: "delete_notification_#{n.object.id}" %>
I'm trying to rewrite the following button code so that instead of redirecting me to the show page, it just creates the object and the current page stays displayed.
<div class="colors">
<li class="colors" id="greys">
<%= button_to "some text", votes_path(color: 'grey', kid_id: current_kid, scoop_id: scoop.id, :method => :create), class: 'grey color-button' %>
</li>
</div>
You should use a remote flag to send the request via javascript. And possibly give feedback to the user.
To send a request via js in rails you have to add remote: true to the options hash:
<div class="colors">
<li class="colors" id="greys">
<%= button_to "some text", votes_path(color: 'grey', kid_id: current_kid, scoop_id: scoop.id, :method => :create), class: 'grey color-button', remote: true %>
</li>
</div>
In your controller you can do special responses by respondig to js
#votes controller
def create
# ...
respond_to do |format|
format.js render
end
end
In your create.js.erb you can write javascript code with embeded ruby to give responses. You can even render partials here.
alert('create.js.erb')
First, have some id for your button:
<%= button_to "some text", votes_path(color: 'grey', kid_id: current_kid, scoop_id: scoop.id, :method => :create), class: 'grey color-button' id: 'buttonID' %>
And then, in your Javascript code, whenever the button is clicked, send a request to server, create the new record, and then update the current page, so that it shows the record has been created.
I would show you the sculpture of how to do it:
$("buttonID").click(function() {
$.ajax({
url: "some_url" // Your URL, for example, /votes
type: "POST", // The type of request
data: { color: "grey" } // Send data to server in this format
success: function() {
// Here you can update the page so that the user could
// know the data has been posted, and no need to press
// the button again
}
});
});
Problem description:
I have a view with set of links:
<% #feeds.each do |f| %>
<div class="feed">
<span class="feed_counts"> <%= f.display_counts %> </span>
<%= link_to "refresh", { :controller => 'Feeds', :action => "refresh_feed", :feed_id => f.id}, :remote => true, :class => 'refresh_feed_link' %>
</div>
<% end %>
Users click on the link and launch next controller method:
def refresh_feed
#feed = Feed.find(params[:feed_id])
#feed.parse
end
Now I want to change the content of the corresponding span-element to #feed.total_count value.
My attempts:
Well, as I know there is a two way's to do it without reloading whole the page:
Way 1:
I can include js in my layout:
<%= render :partial => 'shared/partial_js' %>
and use this code in the partial_js:
<script type="text/javascript">
$().ready(function() {
$('.refresh_feed_link').bind('click', function() {
$(this).closest('.feed').find('span.feed_counts').text('woo');
});
});
</script>
In this case I have '$(this)' and I can find the corresponding 'span' element. But I don't have any possibility to get my #feed varible value.
Way 2:
I can add
respond_to do | format |
format.js {render :layout => false}
end
to my controller and create refresh_feed.js.erb file. In this JS file I can use my variable as <% #feed.total_count %>, but I don't know which of my multiple links was clicked. In the other words the $(this) variable in this file will be (window) and I cannot find corresponding span-element.
Question:
Is there any way to get what I want ?
Thanks in advance.
There are lots of ways to do this. Using the wayds that you described, here's a simple solution for the "which link was clicked" problem: dom_id.
1) Make a partial: app/views/feeds/_feed.html.erb
<%= div_for feed do %>
<span class="feed_counts"> <%= feed.display_counts %> </span>
<%= link_to "refresh", { :controller => 'Feeds', :action => "refresh_feed", :feed_id => feed.id}, :remote => true, :class => 'refresh_feed_link' %>
<% end %>
2) In your view:
<%= render #feeds %>
3) In your refresh_feed.js.erb file:
$('<%= dom_id(#feed) %>').replaceWith('<%= escape_javascript( render #feed ) %>');
There's another way that I personally like better, but it will take a me a little while to write it up, so I'll leave this for you here while I write up the other way.
Second Way
Here's how I do it, using CoffeeScript and HAML because they're easier to type. You can just convert this to plain JS and ERB and it will work the same.
I would setup my routes like so:
resources :feeds do
get "refresh_feed", :on => :member
Assuming you've got a "feed" partial, app/views/feeds/_feed.html.haml:
= div_for feed, :class => 'feed_widget' do
%span.feed_counts= f.display_counts
= link_to "Refresh", refresh_feed_path(f), :class => 'refresh_link'
In any view:
= render #feeds
// or, more explicit:
= render :partial => 'feed/feeds', :collection => #feeds, :as => :feed
Now, in app/assets/javascripts/feeds.js.coffee
# Global Scope for CoffeesScript, ignore for JS
root = exports ? this
# Check page if there are feed widgets on it and initialize a handler for each one.
jQuery ->
if $('div.feed_widget').length
$('div.feed_widget').each ->
new FeedWidget $(this)
root.FeedWidget = (container) ->
#container = container
#refresh_link = #container.find('a.refresh_link')
#feed_counts = #container.find('span.feed_counts')
this.initialize()
root.FeedWidget.prototype =
initialize: ->
self = this
#feed_counts.click (event) ->
event.preventDefault()
$.ajax
type: 'GET'
url: self.refresh_link.attr 'href'
dataType: 'json'
error: (xhr, status, error) ->
console.log error
console.log xhr
success: (data, status, xhr) ->
self.feed_counts.text data.feed_counts
Then in your controller:
def refresh_feed
#feed = Feed.find(params[:id]) #assuming you have a resourceful route to this.
#feed.parse
respond_to do |format|
format.js { render :json => { feed_counts: #feed.counts }, :status => :ok } # use whatever method gets you the feed count here.
end
end
Now you are getting a JSON response that is just the feed count, and you have a single JS listener/handler widget that will automatically show up (and function) and place you render that partial. Cool huh?
Note, the above code is not tested since I don't have your app, so you'll have to refine it for your needs. But ask questions and I'll do my best to answer.
Good luck!
How can I use jQuery UJS to submit a form after the change event of a select box fires in Rails?
My view looks like this:
<% for perm in #permissions %>
<%= form_for [#brand,perm], { :remote => true } do |f| %>
<tr id="permission_<%= perm.id %>">
<td><%= perm.configurable.name %></td>
<td><%= f.select :action, ['none', 'read', 'update', 'manage'] %></td>
</tr>
<% end %>
<% end %>
Pretty straightforward. My controller is like so:
class PermissionsController < ApplicationController
before_filter :authenticate_user!
respond_to :html, :js
load_and_authorize_resource :brand
load_and_authorize_resource :permission, :through => :brand
def index
end
def update
#permission = Permission.find(params[:id])
#permission.update_attributes(params[:permission])
end
end
And in my application.js:
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults
$(function() {
$("#permission_action").change(function() {
this.form.submit(); // This needs to be an AJAX call, but how?
});
})
Please note that the form submission fires just fine. But it is doing a regular post rather than an AJAX submission. When I place a submit button on the form and use that, the AJAX submission works fine. I need to change:
this.form.submit();
to an AJAX call, but I don't know how. Can anyone help?
Looking through the jquery-ujs sources, i figured that this might be the safest way:
// in application.js
jQuery.fn.ajaxSubmit = function() {
this.trigger('submit.rails');
};
it just fires the event as would .submit() but in a ujs flavour, so you would call
$('.my_form').ajaxSubmit();
I see you are already using jQuery that's good. Following code was taken from here: http://railscasts.com/episodes/136-jquery
I strongly advise you get familiar with these screen casts they help a ton (understatement).
// public/javascripts/application.js
jQuery.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
})
jQuery.fn.submitWithAjax = function() {
this.submit(function() {
$.post(this.action, $(this).serialize(), null, "script");
return false;
})
return this;
};
$(document).ready(function() {
$("#new_review").submitWithAjax();
})