Javascript events firing multiple times in rails app? - javascript

I am trying to set up a form so that it submits via ajax when I hit enter. To do this I wanted to trigger the form to submit when the enter key is pressed on the input field. However the keyup event for the enter key keeps firing multiple times if the key is held down any longer than a split second which in turn sends lots of ajax requests causing the browser to crash.
I cannot figure out why the event keeps firing multiple times. Here is the view for the page:
<div class="page-content">
<div class="l-edit-header">
<h1>Edit</h1>
<div class="piece-header">
<%= image_tag #piece.user.avatar_url(:small), class: "avatar-small" %>
<div class="piece-header-info">
<h1><%= #piece.title %></h1>
<em>
By <%= link_to #piece.user.username, user_path(#piece.user) %>
<%= #piece.created_at.strftime("%B %d, %Y") %>
</em>
</div>
</div>
</div>
<div class="l-edit-main">
<div class="piece-main">
<%= image_tag #piece.image_url %>
<p id="piece-description" class="piece-main-description"><%= #piece.description %></p>
<div class="piece-main-links">
<%= link_to "Delete", piece_path(#piece), method: :delete if current_user == #piece.user %>
</div>
</div>
</div>
<div class="l-edit-side">
<div class="form-container">
<%= form_tag piece_tags_path(#piece), id: "new_tag", remote: true do %>
<%= label_tag :new_tag, "New Tag"%>
<%= text_field_tag :tag, "", data: {autocomplete_source: tags_url}, placeholder: "Add a tag and press Enter" %>
<div id="tags" class="piece-tags">
<%= render partial: "tags/delete_tag_list", locals: {piece: #piece, method: :delete} %>
</div>
<% end %>
</div>
<div class="form-container">
<%= simple_form_for #piece do |f| %>
<%= f.association :category, include_blank: false %>
<%= f.input :published, as: :hidden, input_html: {value: true} %>
<%= f.input :title %>
<%= f.input :description %>
<div class="form-submit">
<%= f.button :submit, "Publish" %>
</div>
<% end %>
</div>
</div>
</div>
and here is the Javascript for the tag form I am trying to work with:
var tagReplace = {
init: function(){
//Replace "#tags" with new updated tags html on tag create
$("#new_tag").on("ajax:success", function(e, data, status, xhr){
$("#tags").html(data);
tagReplace.init();
$("#tag").val("");
});
//Replace "#tags" with new updated tags html on teg delete
$("#tags a[data-remote]").on("ajax:success", function(e, data, status, xhr){
$("#tags").html(data);
tagReplace.init();
});
$("#new_tag").on("keydown", function(e){
if (e.which == 13){
event.preventDefault();
}
});
$("#tag").on("keyup", function(e){
if (e.which == 13){
$("#new_tag").submit();
console.log("pressed enter on new tag");
}
});
},
getTags: function(){
$.get( $("#tag").data("autocomplete-source"), tagReplace.initAutocomplete);
},
initAutocomplete: function(tagsArray){
$("#tag").autocomplete({
source: tagsArray
});
}
};
//Initalize
$(document).on('ready page:load', function () {
tagReplace.init();
});
As you can see I have prevented the default behaviour for the return key being pressed on the form and have added a console.log to count the number of times the event is being triggered.
I thought this could be to do with the fact I am using turbolinks but I can't seem to figure out why.
How can I ensure that the event only gets triggered one time for each time the enter key is pressed? At the moment the Javascript is crashing the browser when I hit enter.

You are calling tagReplace.init(); 2 times within itself and, as #Dezl explains in his answer related to this topic, "If you have delegated events bound to the document, make sure you attach them outside of the ready function, otherwise they will get rebound on every page:load event (causing the same functions to be run multiple times)."

Related

Rails - Synchronise button clicks among tabs

I have a view with bootstrap-tabs. The tabs are generated dynamically.
<%= form_with(:id => 'my-form', model: [:admin, #island], local: true) do |form| %>
<div class="tab-content bg-light" id="tabs-from-locales-content">
<%= available_locales.each_with_index do |locale, i| %>
<div
class="tab-pane fade <%= 'show active' if i == 0 %>"
id="<%= locale.downcase %>"
role="tabpanel"
aria-labelledby="<%= locale.downcase %>-tab">
<%= render partial: 'island_form', locals: {counter: i, locale: locale, f: form} %>
</div>
<% end %>
</div>
...
...
A tab represents each available localization of the app.
The model of the form contains two nested attributes. These attributes have 1 to many relationship with the model. So the user can add multiple of these from the form. Their fields can be generated dynamically:
(For simplicity I include in the question only one. This is a part of _island_form.html.erb partial.)
<div class="form-group ports-div">
<%= f.label :port %> </br>
<%= f.fields_for :ports do |builder| %>
<%= render 'port_fields', f: builder %>
<% end %>
<%= link_to_add_fields t('form.add_port'), f, :ports %>
</div>
<div class="form-group">
<%= f.label :airport %> </br>
<%= f.fields_for :airports do |builder| %>
<%= render 'airport_fields', f: builder %>
<% end %>
<%= link_to_add_fields t('form.add_airport'), f, :airports %>
</div>
And the port_fields partial:
<fieldset>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.hidden_field :_destroy %>
<%= link_to t('form.remove'), '#', class: 'remove_fields' %>
</fieldset>
The link_to_add_fields helper method:
def link_to_add_fields(name, f, association)
# Builds an instance of the association record.
new_object = f.object.send(association).klass.new
# Grabbing ruby's object id.
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + '_fields', f: builder)
end
link_to(name, '#', class: 'add_fields', data: { id: id, fields: fields.gsub('\n', '') })
end
What I want to achieve is to synchronize the addition and removal of these fields among the available tabs. So when the user clicks Add field on the first tab all the other tabs will follow this action. Same for field removal.
My relevant js file for the Add button looks like this. I have tried many combinations between the trigger, triggerHandler and stopPropagation, although most of the times I am getting StackOverflow exception and the fields are added only to the tab that I clicked the add button.
(Since I pass a class (.add_fields) in the selector isn't that supposed to be attached to all the elements with class .add_fields?)
form.on('click', '.add_fields', function (event, param) {
event.stopPropagation();
event.preventDefault();
var time = new Date().getTime();
var regexp = new RegExp($(this).data('id'), 'g');
$(this).before($(this).data('fields').replace(regexp, time));
$('.tab-pane').each(function (index) {
$('.add_fields').trigger("click", ["custom_click"]);
console.log('Tab - ' + index);
})
});
EDIT
I am getting somewhere with that code:
$('.ports-div').each(function (index) {
$(this).find('.add_fields').each(function () {
this.click();
})
});
Although, at all the tabs, instead of 1 field 6 fields are added. I added the ports-div to the div that wraps all the related elements.
Complete rewrite...
The problem as you have seen is to be that your click event handler is triggering a click on all tab-panes, including the one you are handling currently. You are also not just clicking the a single item in the loop, but all that match '.add_fields'. This then leads to the click being handled again, recursively.
To prevent this, I suggest calling a function to add your fields directly, rather than triggering a click. If triggering a click is just easiest in your situation, consider the following example that does roughly what you want without the recursive error.
https://jsfiddle.net/3ftf0j8e/1/
Dummy HTML
<a class="add_fields" id='1'>link 1</a>
<a class="add_fields" id='2'>link 2</a>
<a class="add_fields" id='3'>link 3</a>
<a class="add_fields" id='4'>link 4</a>
Sample Javascript
$(document).on('click', '.add_fields', function (event, param) {
event.preventDefault();
var clicked_id = $(this).attr('id');
console.log('clicked id:' + clicked_id);
$(this).addClass('done');
$(this).addClass('clicked');
// Just click items that have not been clicked
var els = $('.add_fields').not('.clicked');
console.log(els);
els.trigger("click");
setTimeout(function(){
if($('.add_fields').length == $('.add_fields.done').length)
$('.add_fields').removeClass('clicked');
})
});
CSS
a.clicked {
background-color: yellow;
}
a.done {
color: red;
}
As you can see now, each fires just once. The setTimeout at the end allows the DOM to update before clearing the clicked classes.

Why does jQuery only enter the function in some links?

I am relatively new to jQuery and JavaScript, so I'm having this issue, hope you can help me through it.
I am making an e-commerce page for selling cloths in Rails 4, so I am focusing more than I used to in the form of displaying forms and all of that, so it can be more attractive to the users. Because of that, I have the forms hidden and I set the values to it via jQuery.
The problem can be divided in two parts:
The first part of the problem is in the cloths#show where the user add the cloth to his cart:
Form:
<%= form_for #order_item, remote: true do |f| %>
<h5>Select a size</h5>
<div class="input-sizes">
<% #sis.each do |si| %>
<%= link_to si.size.letter, "#{si.size_id}", class:"normal-size" %>
<% end %>
</div>
<%= f.hidden_field :size_id, id: "size-input" %>
<h5>Select a color</h5>
<div class="input-colors">
<% #cos.each do |co| %>
<%= link_to "#{co.color_id}", id: "link-circle" do %>
<div id="circle" style="background: <%= co.color.hex %>">
<div id="mini-circle"></div>
</div>
<% end %>
<% end %>
</div>
<%= f.hidden_field :color_id, id: "color-input" %>
...
<% end %>
jQuery:
//Select size
$('a.normal-size').on('click', function(e){
e.preventDefault();
var value = $(this).attr("href");
$('#size-input').val(value);
$(this).removeClass("normal-size").addClass("selected").siblings().removeClass("selected").addClass("normal-size");
});
//Select Color
$('#link-circle').on('click',function(e){
e.preventDefault();
var value = $(this).attr("href");
$('#color-input').val(value);
$(this).children().children().css('opacity', '1');
return false;
});
The set of the size input works perfectly but in the color input the user only can select the first color because if the user wants to select the second, the jQuery function doesn't gets called so it goes to the link (I tried even writing e.preventDefault() and return false at the same time).
The second part of the problem comes in the cart#show when the user can see the summary of all his cloths. In this it is displayed a table with each row being a cloth, here the user can see the image of the cloth, description, selected color, selected size and price. The color and size section is made for the user so he can edit his cloth if he changes his mind and select another size or color. The issue in here is in both size and color and I don't know why. In the color section happens exactly the same as above, but in the size section he can only make one click because if he makes another one the jQuery function doesn't get called. Is like if the click only works once:
Form:
<%= form_for (order_item), remote: true,:url => "/order_items/#{order_item.id}", :html=>{:id=>'item_form_cart'} do |f| %>
<div class="input-colors col-md-1">
<% #cos.each do |co| %>
<% if co.cloth == order_item.cloth %>
<% if co.color_id == order_item.color_id %>
<%= link_to "#{co.color_id}", id: "link-circle" do %>
<div id="circle" style="background: <%= co.color.hex %>;">
<div id="mini-circle" style="opacity: 1;"></div>
</div>
<% end %>
<% else %>
<%= link_to "#{co.color_id}", id: "link-circle" do %>
<div id="circle" style="background: <%= co.color.hex %>;">
<div id="mini-circle"></div>
</div>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
<%= f.hidden_field :color_id, id: "color-input" %>
<div class="input-sizes col-md-2">
<% #sis.each do |si| %>
<% if si.cloth == order_item.cloth %>
<% if order_item.size_id == si.size_id %>
<%= link_to si.size.letter, "#{si.size_id}", class:"selected" %>
<% else %>
<%= link_to si.size.letter, "#{si.size_id}", class:"normal-size cart-el" %>
<% end %>
<% end %>
<% end %>
</div>
<%= f.hidden_field :size_id, id: "size-input-cart" %>
...
<% end %>
jQuery:
//Select size
$('a.normal-size.cart-el').on('click',function(e){
e.preventDefault();
var value = $(this).attr("href");
$(this).parent().next('#size-input-cart').val(value);
$(this).removeClass("normal-size").addClass("selected").siblings().removeClass("selected").addClass("normal-size");
$(this).closest('#item_form_cart').submit();
});
In here I only make the size function because I didn't know how to fix the color one, but this also has a bug, that only gets called the first time. This happens no matter which cloth's size you select, e.i. if I change the first cloth's size works good but if I want to change again the same cloth's size or change the size of another cloth the jQuery function doesn't get called.
Hope you can help me,
Thanks in advance.
Edit:
I have investigated more about why is happening the second problem and I saw that it can be related to Ajax, because in my form if a user changes the size or color it gets done by Ajax, as we can see here. I tried the second solution but it did not work:
$('.well').on('click','a.normal-size.cart-el',function(e){
e.preventDefault();
var value = $(this).attr("href");
$(this).parent().next('#size-input-cart').val(value);
$(this).removeClass("normal-size").addClass("selected").siblings().removeClass("selected").addClass("normal-size");
$(this).closest('#item_form_cart').submit();
});
$('.well').on('click','.link-circle.cart-el',function(e){
e.preventDefault();
var value = $(this).attr("href");
$(this).parent().next('#color-input-cart').val(value);
$(this).siblings().children().children().css('opacity', '0');
$(this).children().children().css('opacity', '1');
$(this).closest('#item_form_cart').submit();
});
And here is render each item in the view:
<div class="order_items">
<% #order_items.each do |order_item| %>
<div class = "well">
<%= render 'carts/cart_row', cloth: order_item.cloth, order_item: order_item, show_total: true %>
</div>
<% end %>
</div>
Your select color function is attached to the id "link-circle". It looks like you're potentially rendering multiple elements with the same id. Your select size function is based on a class which you can have on multiple elements. You cannot however have the same id on multiple elements according to w3 . Try changing "link-circle" to a class like so..
<%= link_to "#{co.color_id}", class: "link-circle" do %>
and the your selector to..
$('.link-circle').on('click',function(e){

Passing specific data on partials using Ajax in Rails

I'm new to rails and what I'm trying to do is the following:
I'm creating a store and unstore button to save and 'un-save' the event respectively. I used my event attributes and current user to find that stored event. By using ajax and remote functionality of rails I will able to change the behavior of the button from store to unstore or vise versa.
In my _feed.html.erb.
<% if #feed_items.any? %>
<ul>
<%= render partial: 'shared/feed_item', collection: #feed_items %>
</ul>
<% end %>
Each #feed_items contains set of data (title, category, etc).
In my _feed_item.html.erb.
<li id="<%= feed_item.id %>" class="item">
...
<div class="small-3 text-center columns">
<%= render 'shared/store_form', event: feed_item %>
</div>
...
</li>
Event symbol sends that feed_item to the store_form.
In my _store_form.html.erb.
<div id="store_form_<%= event.id %>">
<% if event.stored?(current_user) %>
<%= render 'shared/unstore', event: event %>
<% else %>
<%= render 'shared/store', event: event %>
<% end %>
</div>
I pass again a feed_item into partial.
_store.html.erb
<%= form_for(event.storages.build(saver_id: current_user.id,
organizer_id: event.user_id),
remote: true) do |f| %>
<div>
<%= f.hidden_field :organizer_id %>
<%= f.hidden_field :saved_id, value: event.id %>
</div>
<%= f.submit "store" %>
<% end %>
_unstore.html.erb
<%= form_for(event.storages.find_by(saver_id: current_user.id,
organizer_id: event.user_id) ,
html: { method: :delete },
remote: true) do |f| %>
<%= f.submit 'unstore' %>
<% end %>
create.js.erb
$("#store_form").html("<%= escape_javascript(render 'shared/store', event: event )%>")
destroy.js.erb
$("#store_form").html("<%= escape_javascript(render 'shared/unstore', event: event) %>")
And I'm getting these errors when clicking store/unstore event.
ActionView::Template::Error (undefined local variable or method `event' for #<#<Class:0x00000004d69be8>:0x00000004d68d10>):
1: $("#store_form").html("<%= escape_javascript(render 'shared/unstore', event: event) %>")
ActionView::Template::Error (undefined local variable or method `event' for #<#<Class:0x00000004d69be8>:0x00000004fdc6a0>):
1: $("#store_form").html("<%= escape_javascript(render 'shared/store', event: event )%>")
My question is, how can I pass that specific "event" data into the partial. I can't used #feed_item onto the create and destroy js because feed_item have the set of events. Is there any better approach to handle this?
In your action create and delete in the controller you should instantiate your variable event: change event by #event and change your js to:
$("#store_form").html("<%= escape_javascript(render 'shared/unstore') %>
because you donĀ“t need add instantiate vars to render calls, rails is doing for you

resetting form via jQuery not clearing ckeditor cktext_area field

I have following form
<div id="post-close-updates-form">
<%= form_for [#investment,#post_close_update], remote: true do |f| %>
<div class="form-group">
<%= f.label :content %>
<%= f.cktext_area :content %>
</div>
<%= f.submit "Update", class: "btn btn-primary" %>
<% end %>
</div>
and my jquery code is
$("#post-close-updates-form form")[0].reset();
but it is not clearing cktext_area content...while if i put normal html textarea then it works fine.
so how do i clear ckeditor cktext_area via js/jquery
ok here is the answer given by developer of ckeditor #galetahub
$("#post-close-updates-form form")[0].reset();
for (instance in CKEDITOR.instances){
CKEDITOR.instances[instance].updateElement();
}
but above one not worked for me so made some chages that works
for (instance in CKEDITOR.instances){
CKEDITOR.instances[instance].setData(" ");
}
setData() is to set the data is cktext_area
now if u want to get the data from cktext_area in js then use this
for (instance in CKEDITOR.instances){
CKEDITOR.instances[instance].getData();
}

How do I apply function to just the class a jquery ajax submission was done from?

Here is the code I'm currently using:
$.ajax({
success: function(){
$('.post_container').append('test <br />');
}
});
<% sleep 1 %>
It is similar to the code I used for my single main micropost form but with this there are several comments that use the same class and so test is being applied to all post_containers rather than the one the post was just made to. "test" text will eventually be replaced with the div that holds the actual comments users post.
Normally I would use "this" but that won't work here.
HTML:
<div class="post_content">
<div class="post_container">
<div class="userNameFontStyle">
<%= link_to current_users_username.capitalize, current_users_username %> -
<div class="post_time">
<%= time_ago_in_words(m.created_at) %> ago.
</div>
</div>
<%= simple_format h(m.content) %>
</div>
<% if m.comments.any? %>
<% comments(m.id).each do |comment| %>
<div class="comment_container">
<%= link_to image_tag(default_photo_for_commenter(comment), :class => "commenter_photo"), commenter(comment.user_id).username %>
<div class="commenter_content">
<div class="userNameFontStyle">
<%= link_to commenter(comment.user_id).username.capitalize, commenter(comment.user_id).username %> - <%= simple_format h(comment.content) %>
</div>
</div>
<div class="comment_post_time">
<%= time_ago_in_words(comment.created_at) %> ago.
</div>
</div>
<% end %>
<% end %>
<% if logged_in? %>
<%= form_for #comment, :remote => true do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :micropost_id, :value => m.id %>
<%= f.text_area :content, :placeholder => 'Post a comment...', :class => "comment_box", :rows => 0, :columns => 0 %>
<div class="commentButtons">
<%= f.submit 'Post it', :class => "commentButton" %>
<div class="cancelButton">
Cancel
</div>
</div>
<% end %>
<% end %>
</div>
</div>
How would I deal with this?
Kind regards
NEW:
$(function() {
$('.post-form').submit(function(){
var $post_content = $(this).find('.post_content');
$.ajax({
url: this.attributes['action'],
data: $(this).serialize(),
success: function() {
$post_content.append('test <br />');
}
})
return false; //this will stop the form from really submitting.
});
});
This should do what you're looking for. Simply include this javascript anywhere.
OLD:
$('form').submit(function(){
$.ajax({
url: this.attributes['action'],
data: $(this).serialize(),
//the below line might need to be adapted.
var post_content = $(this).find('.post_content');
success: function() {
$(post_content).append('test <br />');
}
})
this assumes that there is only one form on the page. However, for the code to completely work, I need to know how to differentiate which .post_content is the "submitted" post_content. How do you programatically determine that? Is there a different button for each post_content? Or are there different forms around each post_content div? Whichever way it is, you'll have to include that somehow into the js code.
This is how I had to do it in the end.
.new_comment is my form id
$('.new_comment').on('ajax:success', function(){
$(this).parent('.post_content').find('.comment_container:last').after("<BR />TEST <BR />");
});
<% sleep 1 %>
The only issue now is identifying the actual comment_container for the post just made.

Categories

Resources