Rails 5 cross model ajax form not updating index - javascript

Class Actual is managing the creation and deletion of objects in class Friendgroup. The form page
<ul id=friendgroups> <%= render #friendgroups %> </ul>
<%= form_with model: Friendgroup.new, data: {'js-friendgroup-form' => true} do |form| %>
Name <%= form.text_field :name %>
<%= form.submit %>
<% end %>
calls the collection of friendgroups properly on page load, the controller for friendgroups does create records:
def create
params[:friendgroup][:user_id] = current_user.id
#friendgroup = Friendgroup.new(friendgroup_params)
authorize #friendgroup
if #friendgroup.save
render partial: 'friendgroup', locals: {friendgroup: #friendgroup}
end
end
On page load, existing objects are loaded:
<li data-js-friendgroup-id=<%= friendgroup.id %>>
<%= friendgroup.name %> <%= link_to "Delete", friendgroup, remote: true, method: :delete, data: { confirm: "Are you sure you want to delete '#{friendgroup.name}'?" } %>
However, the AJAX actions are not percolating back to the index, leading me to conclude that the javascript functions are no binding to the proper classes/items:
//= require jquery3
//= require jquery-ui
//= require rails-ujs
$(document).ready(function() {
$('[data-js-friendgroup-form]').on("ajax:success", function(event, data, status, xhr){
var friendgroup = $(xhr.responseText).hide();
$('#friendgroups').append(friendgroups);
friendgroup.fadeIn(1000);
// $('#friendgroups').append(xhr.responseText);
});
$('[data-js-friendgroup-id]').on("ajax:success", function(event, data, status, xhr){
var friendgroup_id = xhr.responseJSON.id;
$('[data-js-friendgroup-id=' + friendgroup_id + ']').hide();
});
});
Another possible culprit is the console error
TypeError: url.indexOf is not a function being activated by jQuery3, but that has little to do with the above code.
How can these cross model actions run properly?

bad eyes question
$('#friendgroups').append(friendgroup);
instead of
$('#friendgroups').append(friendgroups);

Related

Rails 4.2, AJAX form is submitted exponentially to the server on each subsequent triggering

I have a select on a form (company select), changing the option selected will update the possible options in another select (address select). The options of the address select are updated by submitting an AJAX request. The options of the address select are successfully updated each time.
However, when the first company is selected the AJAX get request is sent 1 time. When a second company is selected, the AJAX request is sent 2 times. Then 4, then 8, then 16, then 32 etc.
Not ideal...
Final note, i've been trying to follow Brandon Hilkert's advice on organizing javascript.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html lang="en">
<head>
<%# Disables turbolinks caching of pages, to allow smooth animations %>
<meta name="turbolinks-cache-control" content="no-preview">
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
<%= favicon_link_tag 'logos_and_banners/ecl_logo.png' %>
<title>Environmental Concern Ltd - Your future in our hands.</title>
<%# Geocompete JQuery plugin %>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?key=AIzaSyDfo9YlYY6BnemhdpDMlQbo6li3RyRYO-0&libraries=places"></script>
<%= render partial: 'shared/flash' %>
<%= render 'shared/navigation/navigation' %>
<%# console %>
</html>
app/views/licenses/_form.html.erb
<%= simple_form_for([#company, #license], remote: true, html: {role: 'form'}) do |f| %>
<%# errors will be loaded here via AJAX %>
<div id="form-errors" class="alert alert-danger collapse"></div>
<form role="form">
<div class="form-inputs">
<%= f.association :company,
collection: Company.sorted_by('company_name_asc'),
label_method: :company_name,
value_method: :id,
include_blank: false %>
<%= f.association :address,
collection: #company.addresses.sorted_by('site_name_asc'),
label_method: :site_name,
value_method: :id,
include_blank: true %>
<span class="help-block">
Certain License types are related to a specific Address, others are only linked to the Company.
</span>
<%= f.input :license_type,
collection: License::ALL_VALID_LICENSE_TYPES %>
<%= f.input :license_name, as: :hidden%>
<%= f.input :license_no %>
<%= f.input :expiry_date,
as: :string,
input_html: {
data: {
provide: 'datepicker', date_format: 'yyyy-mm-dd'
}
}%>
</div>
<%= render partial: 'shared/edit_new/save_button', locals: {model_name: find_model_name_for_view(#license)} %>
</form>
<% end %>
app/assets/javascripts/application.js
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= require bootstrap-sprockets
//= require cocoon
//= require turbolinks
//= require data-confirm-modal
//= require jquery.geocomplete
//= require bootstrap-datepicker/core
//= require underscore
//= require gmaps/google
//
//= require init
//= require licenses/app.form.js
app/assets/javascripts/init.js
window.App || (window.App = {});
// Define init function on the App object
App.init = function(){
// Bootstrap tooltips. Must be manually enabled.
$(function () {
$('[data-tooltip="true"]').tooltip();
})
};
// Call the init function on every page transition.
// $(document).ready functions don't fire with turbolinks
$(document).on("turbolinks:load", function() {
return App.init();
});
app/assets/javascripts/licenses/app.form.js
App.license = (function() {
function licensesFormBehaviour() {
//Update address select based on Company choice
$('select#license_company_id').change(function() {
return $.ajax({
url: 'addresses',
type: 'GET',
dataType: 'script',
data: {
company_id: $("select#license_company_id option:selected").val()
},
error: function(jqXHR, textStatus, errorThrown) {
return console.log("AJAX Error: " + textStatus);
}
});
});
// Several other unrelated methods, which function fine.
// I've omitted them to make the code block shorter.
return licensesFormBehaviour;
})();
$(document).on("ajaxSuccess", function() {
//Licenses form only
if (!($("form.edit_license").length > 0) && !($("form.new_license").length > 0) ) {
return;
}
//invoke
var license = new App.license();
return license;
});
Thanks for you time,
Patrick.
Edit:
Response, requested by fanta.
AJAX response
// Within the append() I've omitted around 30 additional <option> tags, for readability.
$("#license_address_id").empty().append("<option value=\"\"></option><option value=\"197\">Berge Knolls<\/option>");
You mentioned that you load in the form via ajax, and hence initialise the form on ajaxSuccess, but as fanta mentioned in the comments, licences are also being initialised after the select request is complete, resulting in multiple instances.
To solve this, you could store the licence instance in a data property, and only initialise if it does not already exist on the form element:
$(document).on("ajaxSuccess", function() {
$("form.edit_license, form.new_license").each(function(i, form) {
var $form = $(form);
var license = $form.data('license');
if (!license) $form.data('license', new App.license());
})
});
Bonus stuff: Rails (or rather jquery-ujs) already gives you the select/AJAX behaviour for free! :)
If you add data-remote, data-url, and data-method to your select element, jquery-ujs will serialise the value of the element make the request for you. In your case, with simple_form, this is a little trickier, but if the documentation is correct, then you may be able to do something like:
<%= f.input :company do %>
<%= f.select
:company,
options_from_collection_for_select(
Company.sorted_by('company_name_asc'), 'id', 'company_name'
),
{ include_blank: false },
data: {
remote: true,
method: 'get',
url: addresses_path
}
%>
<% end %>
When the select changes, jquery-ujs, will GET to /addresses?company_id=:selected_id, where your response can take over.
Hope that helps!

AJAX request not finding model id

I'm trying to call an ajax method on my index page which updates an attribute. The attribute is updated (successfully) on a page refresh as the task (this is for a to do list app) is moved from the incomplete list to the complete list. However, when calling the ajax request using a button click, I receive the following error on my rails console:
ActiveRecord::RecordNotFound (Couldn't find Task with 'id'=:id [WHERE "tasks"."user_id" = ?]):
app/controllers/tasks_controller.rb:47:in `complete'
Here is my ajax request:
$(document).ready(function() {
console.log("working");
$('.task_submit_box').click(function() {
$.ajax({url: 'tasks#complete', type: "PATCH", success: function(result) {
$('.task_area').html(result);
console.log('test');
}});
});
});
my controller method:
def complete
#task = current_user.tasks.find(params[:id])
#task.update_attribute(:completed_at, Time.now)
#task.update_attribute(:completed, true)
redirect_to root_path
end
My index page area where it should be updated:
<ul class="wrapper_task_list">
<% #task.each do |task| %>
<% if task.completed == false %>
<li>
<div class="task_area", id="incomplete_task_area">
<%= link_to task.title, task_path(task), class: 'wrapper_task_name'%>
<%= form_for task, remote: true do |f| %>
<%= f.check_box :completed, class: 'task_check_box' %>
<%= f.submit 'update', class: 'task_submit_box' %>
<% end %>
</div>
</li>
<% end %>
<% end %>
</ul>
I'm lost in trying to match the ajax request with the correct task/:id. my routes specifically mention the url listed in the ajax request:
PATCH /tasks/:id/complete(.:format)
Where's my id?
$.ajax({url: 'tasks#complete'
You seem to be using Rails' route syntax here. Those are not valid urls. Your url must look like this:
url: "/tasks/1/complete"
where number should be dynamic, of course. (hint: just fetch form's action attribute and use that as url).
How to actually do things correctly here?
See that remote: true there? It means that you don't have to manually handle submit clicks. It will perform ajax for you. You just have to create a JS view, where you will do whatever it is you do when item is completed.
// views/tasks/complete.js.erb
$('#task-<%= #task.id %>').addClass('completed')
Section 14.2.5 of Michael Hartl's rails tutorial covers almost this exact scenario: ajax-powered button that changes something and changes have to be reflected on the page.

Javascript only executing the first time when sending XHR requests with Remote true in form_for

I have a form for filtering products, and want to change by AJAX the results on the partial for results:
<%= form_for :pin, url: filter_path, method: :get, remote: true do |f| %>
<%= f.radio_button :category, category %>
<%= f.range_field :max_value , type: "range", value: "1000", min: "1", max: "1000" %>
<%= f.submit "Buscar", class: "margin_top width100" %>
<% end %>
In the controller, I respond to JS with a partial:
respond_to do |format|
format.js { render "filter_products" }
format.html {render "index"}
end
And lastly, on my filter_products.js, I change the partial with the new results:
$(document).ready(function(){
var newProducts = '<%= j(render partial: "product_list", collection: #pins) %>';
$("#product-list").replaceWith(newProducts);
});
Where product_list is my partial with the new products:
<% #pins.each do |pin| %>
<%= render partial: "product_card", locals: {pin: pin} %>
<% end %>
Its working OK, but only the first time I submit the form. The second time on, its sending XHR requests with 200 responses, but it's not replacing the old products on the view.
Am I missing something?
Thanks!
The replaceWith wasn't working, instead, I used html.
$("#product-list").replaceWith(newProducts);
to
$("#product-list").html(newProducts);
I think it's because Rails Turbolinks. Try to add https://github.com/kossnocorp/jquery.turbolinks
From documentation:
Gemfile:
gem 'jquery-turbolinks'
Add it to your JavaScript manifest file, in this order:
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//
// ... your other scripts here ...
//
//= require turbolinks

How to get ajax comments working on Rails 4

I'm trying to get my comments to be saved with ajax so that the page does not reload. Here's what I've done so far, but it's not working.
How can I get this to work?
In my view file
<%= form_for [#book, inspiration, #comment], :html => { :class => "comment-form" } do |f| %>
<%= f.text_field :content, placeholder: "Add a comment & hit enter.", :id => 'comment_submit' %>
<% end %>
Also in that file, I've include this:
<script>
$(document).on('keypress', '#comment_submit', function(event) {
if ( event.which == 13 ) {
event.preventDefault();
var myData = $( "form" ).serialize();
$.ajax({
type: "POST",
data: myData,
url: <%= inspiration_comments_path(inspiration) %>,
success: function(data){
if(data){
alert('success')
}
}
});
}
});
</script>
The comments are supposed to be submitted by pressing the enter key only. There's no button.
Another level of difficulty, these comments are about books. Those books are all shown on the page, and when you click on one book, it brings up a modal with information about it with the comments section. I tried giving the id selector a dynamic name, but that didn't change anything with the page load.
Thanks for your help.
Rails has built in support for Ajax. You don't have to write your own jQuery to submit the comment. Use remote: true option with form_for and Rails will handle it for you. You will have to write a JavaScript handler, though, and make sure your controller responds in JavaScript.
For example:
<%= form_for [#book, inspiration, #comment], remote: true do |f| %>
# ...
Then, your controller would have something like this:
def create
#comment = Comment.new(comment_params)
#comment.save
respond_to do |format|
format.html # default without js
format.js # make sure you have a create.js.erb that will handle updating the dom
end
end
Then you will want to create a comments/create.js.erb handler. This handler will have the necessary JavaScript to handle updating the dom, like inserting the comment, for example.
Check out the Rails Guides for more information.

Like button Ajax in Ruby on Rails

I have a Ruby on Rails project with a model User and a model Content, among others. I wanted to make possible for a user to "like" a content, and I've done that with the acts_as_votable gem.
At the moment, the liking system is working but I'm refreshing the page every time the like button (link_to) is pressed.
I'd like to do this using Ajax, in order to update the button and the likes counter without the need to refresh the page.
In my Content -> Show view, this is what I have:
<% if user_signed_in? %>
<% if current_user.liked? #content %>
<%= link_to "Dislike", dislike_content_path(#content), class: 'vote', method: :put %>
<% else %>
<%= link_to "Like", like_content_path(#content), class: 'vote', method: :put %>
<% end %>
<span> · </span>
<% end %>
<%= #content.get_likes.size %> users like this
<br>
The Content controller does this to like/dislike:
def like
#content = Content.find(params[:id])
#content.liked_by current_user
redirect_to #content
end
def dislike
#content = Content.find(params[:id])
#content.disliked_by current_user
redirect_to #content
end
And in my routes.rb file, this is what I have:
resources :contents do
member do
put "like", to: "contents#like"
put "dislike", to: "contents#dislike"
end
end
As I said, the liking system is working fine, but does not update the likes counter nor the like button after a user presses it. Instead, to trick that, I call redirect_to #content in the controller action.
How could I implement this with a simple Ajax call? Is there another way to do it?
You can do so in various ways, the simple way goes like this:
Preparations
Include Rails UJS and jQuery in your application.js (if not already done so):
//= require jquery
//= require jquery_ujs
Add remote: true to your link_to helpers:
<%= link_to "Like", '...', class: 'vote', method: :put, remote: true %>
Let the controller answer with a non-redirect on AJAX requests:
def like
#content = Content.find(params[:id])
#content.liked_by current_user
if request.xhr?
head :ok
else
redirect_to #content
end
end
Advanced behaviour
You probably want to update the "n users liked this" display. To archieve that, follow these steps:
Add a handle to your counter value, so you can find it later with JS:
<span class="votes-count" data-id="<%= #content.id %>">
<%= #content.get_likes.size %>
</span>
users like this
Please also note the use of data-id, not id. If this snippet gets used often, I'd refactor it into a helper method.
Let the controller answer with the count and not simply an "OK" (plus add some information to find the counter on the page; the keys are arbitrary):
#…
if request.xhr?
render json: { count: #content.get_likes.size, id: params[:id] }
else
#…
Build a JS (I'd prefer CoffeeScript) to react on the AJAX request:
# Rails creates this event, when the link_to(remote: true)
# successfully executes
$(document).on 'ajax:success', 'a.vote', (status,data,xhr)->
# the `data` parameter is the decoded JSON object
$(".votes-count[data-id=#{data.id}]").text data.count
return
Again, we're using the data-id attribute, to update only the affected counters.
Toggle between states
To dynamically change the link from "like" to "dislike" and vice versa, you need these modifications:
Modify your view:
<% if current_user.liked? #content %>
<%= link_to "Dislike", dislike_content_path(#content), class: 'vote', method: :put, remote: true, data: { toggle_text: 'Like', toggle_href: like_content_path(#content), id: #content.id } %>
<% else %>
<%= link_to "Like", like_content_path(#content), class: 'vote', method: :put, remote: true, data: { toggle_text: 'Dislike', toggle_href: dislike_content_path(#content), id: #content.id } %>
<% end %>
Again: This should go into a helper method (e.g. vote_link current_user, #content).
And your CoffeeScript:
$(document).on 'ajax:success', 'a.vote', (status,data,xhr)->
# update counter
$(".votes-count[data-id=#{data.id}]").text data.count
# toggle links
$("a.vote[data-id=#{data.id}]").each ->
$a = $(this)
href = $a.attr 'href'
text = $a.text()
$a.text($a.data('toggle-text')).attr 'href', $a.data('toggle-href')
$a.data('toggle-text', text).data 'toggle-href', href
return
return

Resources