Reference a Coffeescript event in a separate JS file - javascript

Basically I already have a piece of coffeescript that animates a drop down menu:
menu_in = -> $('.cart_pulldown').slideDown(250)
menu_out = -> $('.cart_pulldown').slideU(150)
$('#store_menu').hoverIntent(over: menu_in, out: menu_out, timeout: 150)
And I want to tie this to the add-to-cart-button action so that the menu slideDown/slideUp sequence happens when a user adds to their cart, heres that js code:
function set_product_page_variant_state() {
var varel = $('div#product-social-links');
var cart_link = $("#add-to-cart-button");
if(varel && cart_link) {
if(variant_id = varel.attr('data-variant-id')) {
$.post('/flash_sales/get_state.json', {'variant_ids[]': [variant_id]}, function(data) {
var state = data[variant_id];
if(state) {
if(state=='on_hold') {
cart_link.text("On Hold").show();
} else if(state=='in_my_cart') {
// TODO: this is funking ugly and slow to load, this whole thing needs a good old fashion refactorin'.
cart_link.text("In My Cart")
.hide()
.after("<a href='/cart' class='action-button add_to_cart' id='#add-to-cart-button'>In My Cart</a>")
.remove()
} else if(state=='available') {
cart_link.removeAttr('disabled').show();
} else if(state=='sold_out') {
cart_link.text("Sold Out").show()
} else {
// something went wrong, enable the button
cart_link.removeAttr('disabled').show()
}
} else { cart_link.removeAttr('disabled').show() }
});
} else { cart_link.removeAttr('disabled').show() }
}
}
and just to be thorough, here is the associated html:
<div id="cart-form">
<%= form_for :order, :url => spree.populate_orders_url do |f| %>
<div id="inside-product-cart-form" itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<% if #product.price %>
<div>
<div class="add-to-cart">
<%= hidden_field_tag "variants[#{#variant.id}]", 1 %>
<%= button_tag "Add to Cart", :class => "hidden action-button add_to_cart", :type => :submit, :disabled => true, :id => "add-to-cart-button" %>
</div>
</div>
<% end %>
</div>
<% end %>
</div>
Any advice is greatly appreciated, thanks in advance!

You can use jQuery delegate events in your Coffeescript file. For example, to show the menu for 500ms before triggering menu_out:
$(document).on 'click', '#add-to-cart-button', (event) ->
menu_in()
setTimeout 500, menu_out

Since CoffeeScript puts your code in a closure you need to manually attach global variables to the window, like window.menu_in = -> ...

Related

Rails 6: execute a JS function after rendering partial

Rails & Javascript beginner here,
On a training project, I made flash messages disappear after few seconds using JQuery. A visitor would send AJAX request to add a product to his cart, then a flash partial 'Added to cart' appears and automatically fades out after few seconds.
# application.html.erb
<div id='flash' class='flex-column'>
<%= render partial: 'shared/flash' %>
</div>
# shared/_flash.html.erb
<% flash.each do |key, value| %>
<%= display_flash(key, value) %>
<%= javascript_pack_tag 'custom/flash' %>
# this works, but injects the script each times the partial is rendered
<% end %>
# helpers/application_helper.rb
def display_flash(key, value)
def display_flash(key, value)
div_class = [flash_class(key), 'text-center fade show alert-dismissible'].join(' ')
content_tag(:div, class: div_class) do
content_tag(:p, value) +
button_tag(class: 'ml-auto close', 'data-dismiss': 'alert', type: 'button') do
content_tag(:span, '×'.html_safe, 'aria-hidden': 'true') +
content_tag(:span, 'Close', class: 'sr-only')
end
end
end
end
// app/javascript/packs/custom/flash.js
function closeFlash(){
let lastAlert = $('#flash .alert:last')
function fadeFlash() {
lastAlert.animate( {opacity: 0}, 2000, function() {
$(this).hide('slow', function() {
$(this).remove()
});
});
};
setTimeout(fadeFlash, 2000)
};
closeFlash();
The issue with this is that it pollutes my DOM with unnecessary <script> tags:
This could be fixed, but is there a suitable way to execute one specific javascript function after rendering a (AJAX) partial ?
In my case, executing closeFlash() located in packs/custom/flash.js each time a partial is rendered.
Thanks for your help and your time
EDIT Solution:
From Amit Patel answer and this post
# app/views/shared/_flash.html.erb
<% flash.each do |key, value| %>
<%= display_flash(key, value) %>
<script>
$(function() {
closeFlash();
});
</script>
<% end %>
// app/javascript/packs/custom/flash.js
window.closeFlash = function() {
let lastAlert = $('#flash .alert:last')
function fadeFlash() {
lastAlert.animate( {opacity: 0}, 2000, function() {
$(this).hide('slow', function() {
$(this).remove()
});
});
};
setTimeout(fadeFlash, 2000)
};
It doesn't inject the whole function but (I believe) the minimal javascript code to call it.
Move <%= javascript_pack_tag 'custom/flash' %> to your layout or your application.js` so that it is available across the app.
modify your template like
application.html.erb
<div id='flash' class='flex-column'>
<%= render partial: 'shared/flash' %>
<script>
$(function(){
closeFlash();
});
</script>
</div>
A more recent approach would be to set a Stimulus controller.

Why cocoon in my code not capturing callbacks?

I have a functional nested form in ruby make with cocoon. The problem is that I am trying to use before-insert and after-insert and it does nothing.
My miew:
<div class="row" id="street_enter_itineraries">
<table>
<div class="street_enter_itineraries" >
<%= f.fields_for :street_enter_itineraries do |builder| %>
<%= render 'street_enter_itinerary_fields', f:builder %>
<% end %>
</div>
</table>
<div class="row">
<%= link_to_add_association "Añadir calle de entrada",f, :street_enter_itineraries, class: 'btn btn-info', data: {association_insertion_node: '.street_enter_itineraries', association_insertion_method: :append} %>
</div>
</div>
My javascript
$(document).ready(function() {
$('#street_enter_itineraries')
.on('cocoon:before-insert', function() {
var allIds= document.getElementsByClassName("tipocalleentradaidentificar");
if(allIds.length > 3){
event.preventDefault();
}
})
.on('cocoon:after-insert', function() {
refreshID();
})
});
I have seen this post: Cocoon add association, how to limit number of associations because of it is what I want to do. I have tried this option too, and console is empty:
$(document).on('cocoon:after-insert', '.content form', function(e) {
console.log('Something');
});
Thanks in advance.
Your jquery selector is wrong, you forgot the # at the start to signify searching for an id. So you wrote $('street_enter_itineraries') and you should write
$('#street_enter_itineraries')
[EDIT] Making the js turbolinks is relatively easy, either make sure it is triggered upon turbolinks:load, instead of using the document ready event (in your case probably the easiest)
$(document).on('turbolinks:load', function(e) {
$('#street_enter_itineraries') ...
})
or register the cocoon handlers on the document with the correct selector:
$(document).on('cocoon:before-insert', '#street_enter_itineraties', function(e) {
// do something here
}

Button to toggle text field, true and false

I have a form to create a true/false question. I am trying to have a button to toggle the value of a text_field from true and false. (I really do not care what way it is done, I just want the user to be able to easily change the answer to true or false.
This is what I have, it only toggles one time to false and only works when I click the text field not the button. I am trying to learn javascript, so bear with me...
<%= f.label :content, "Answer" %>
<%= f.text_field :content, :value => 'True', :readonly => true, :class => "d-id", :id => "answer" %>
<%= button_tag "Toggle True/False", :id => "answer", :class => "btn btn-small btn-inverse", :type => "button" %>
<script>
function checkAnswer() {
if ($("#answer").val('False'))
{
$("#answer").val('True');
}
if ($("#answer").val('True'))
{
$("#answer").val('False');
}
}
$(document).ready(function(){
$('#answer').click(function () {
checkAnswer();
});
})
</script>
http://jsfiddle.net/jayblanchard/ene5w/
I cleaned up your syntax some and came up with this -
function checkAnswer() {
var answer = $('#answer').val();
if ('False' == answer) {
$("#answer").val('True');
} else {
$("#answer").val('False');
}
}
Also, you have two elements with the same ID which is not allowed in an HTML page. You'll get wonky results. You could change these to classes.
Changed to a button instead of a link - http://jsfiddle.net/jayblanchard/ene5w/1/
Change the text of the button - http://jsfiddle.net/jayblanchard/ene5w/2/
Try this:
if ($("#answer").val() === 'False') {
$("#answer").val('True');
} else {
$("#answer").val('False');
}

JQuery: Run script on ajax added form elements

I have a form element which is being pulled in on request with ajax. I am then trying to perform an ajax request on the inserted text box to find a location as it is typed. The code works on the first textbox but simply fails when the second one is inserted. I've tried to get the script to reload itself when the ajax has completed but it still won't work. Help would be much appreciated.
Form.html.erb - Sets up the rails nested form and pulls in partial
<%= nested_form_for(#post, :html=> {:multipart => true, :class=> "new_blog_post", :id=> "new_blog_post"}) do |f| %>
...
<fieldset>
<%= render :partial => 'search_locations', :locals => { :f => f } %>
</fieldset>
<p><%= f.link_to_add "Add a location", :locations %></p>
...
<% end %>
partial.html.erb - Pulled in on page load and then when 'Add a location' button is pressed
<fieldset>
<%= f.fields_for :locations do |m| %>
<%= m.text_field :name ,:class=>"localename", :placeholder=> "Name of the location", :autocomplete => "off" %>
<%= m.text_field :longitude, :class => "long" %>
<%= m.text_field :latitude, :class => "lat" %>
<div class="latlong">
<p class="help-block">Enter the name of the town or city visited in this blog entry.</p>
</div>
<%= m.link_to_remove "Remove this location" %>
<% end %>
</fieldset>
Javascript (placed at bottom of form)
<script type="text/javascript">
function locationchecker() {
// Rails to multiply the script 20 times
<% (0..20).each do |i| %>
// when the #search field changes
$(".localename:eq(<%=i%>)").keyup(function() {
// get the value of searchfield
var location<%=i%> = $(".localename:eq(<%=i%>)").val();
//Take the value of the textbox and pull it from geocoder
$.get('/locations/location?location='+location<%=i%>,this.value, function(searchone<%=i%>) {
$(".latlong:eq(<%=i%>)").html(searchone<%=i%>);
})
// Upon complete run the script again
.complete(function(searchone<%=i%>) { locationchecker });
});
<%end%>
}
// load script on doc ready
$(document).ready(locationchecker);
</script>
Help would be great!
Thanks in advance,
James
you should use the .on()(jQuery api doc) method to attach your keyup event handler, like so :
$('#myForm').on('keyup','.localename',function() {
// stuff here
var location = $(this).val(); // this = element on which the event was triggered
});

jQuery and Rails

I'm writing a Rails app and need to plug in this little bit of jQuery code, but I don't really know how to get it to work. Here's my controller code:
class ChatroomController < ApplicationController
def send_data
#role = Role.find_by_id(session[:role_id])
render :juggernaut do |page|
page.insert_html :bottom, 'chat_data', "<b>#{#role.name}:</b> #{h params[:chat_input]}<br>"
end
render :nothing => true
end
end
and view code:
<h2>Chat</h2>
<html>
<head>
<%= javascript_include_tag :defaults, :juggernaut %>
<%= juggernaut %>
</head>
<body>
<div id='chat_data', class="chatbox">
</div>
<br>
<%= form_remote_tag(
:url => { :action => :send_data },
:complete => "$('chat_input').value = ''" ) %>
<%= text_area_tag( 'chat_input', '', { :rows => 3, :cols => 70, :id => 'chat_input'} ) %>
<%= submit_tag "Send" %>
</form>
</body>
</html>
Now, I need to make the chatroom always scroll down to the bottom when any user sends a new message. But also, when the current user has manually scrolled up, disable this sort of behaviour. I found some jQuery code here: Scrolling Overflowed DIVs with JavaScript
Now I don't know to get it to work. I pasted into application.js:
$("#chat_data").each( function()
{
var scrollHeight = Math.max(this.scrollHeight, this.clientHeight);
this.scrollTop = scrollHeight - this.clientHeight;
});
I've also added <%= javascript_include_tag 'jquery', 'application' %> to the head of my view.
But when my chatroom log fills up, the scrollbar appears but does not automatically move to the bottom as new messages come through.
The problem seems to be that the code you've inserted is only run once, at the beginning of the script.
I don't know a lot about jquery so this is just a general solution.
function sub(data) {
$('#chat_data').each( function () {
var s_top = this.scrollHeight - this.clientHeight;
var scl = this.scrollTop == s_top;
this.innerHTML += '<br/>' + data;
if ( scl ) this.scrollTop = s_top + this.clientHeight;
})
};
problem is that now when you receive new data from the server you must add it into the #chat_data by calling sub("text that goes into chat window").
you'll have to replace
page.insert_html ....
with something that sends the rjs to the client, like:
page.call( :sub, data)
hope it helps.

Categories