Why cocoon in my code not capturing callbacks? - javascript

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
}

Related

Manipulating Inserted Fields from Checkbox and Select via Jquery - Cocoon Rails

I'm having trouble accessing and using the value of a checkbox and then select field for forms dynamically added via the Cocoon Gem for Rails. I've read through many SO posts and the official Cocoon documentation and I just can't seem to get it right.
The idea is that after adding a form via Cocoon, there are hidden fields that only show up if a specific checkbox is :checked, with one of those fields that being a f.select and upon the selection of that value more fields will either show or hide.
_mortgage_fields.html.erb
...
<%= f.form_group :misc_mortgage do %>
<%= f.check_box :misc_mortgage, label: "Miscellaneous Mortgage" %>
<% end %>
<%= f.select :misc_mortgage_context, [["Assignment", "Assignment"], ["Subordination", "Subordination"], ["Modification", "Modification"]],
{ label: "Miscellaneous Mortgage Type" }, wrapper: { class: 'mtgHidden' }%>
<%= f.text_field :reference_mortgage, class: 'form-control', wrapper: { class: 'mtgHidden' } %>
<%= f.text_field :subordinated_mortgage, class: 'form-control', wrapper: { class: 'Subordination' } %>
<%= f.text_field :modification_amount, class: 'form-control', wrapper: { class: 'Modification' } %>
...
The top level form is wrapped with <div id="mtgForm">..</div>
cocoonoptions.js
$(document.ready(function() {
$('#mtgForm').on('cocoon:after-insert', function(e, misc_checkbox) {
alert('getting there');
$(misc_checkbox).find('input[type=checkbox][id*="+misc_mortgage"]').change(function() {
alert('almost there');
if ($(this).is(':checked'))
alert('there!');
$('.mtgHidden').show();
else
$('.mtgHidden').hide();
});
$(misc_checkbox).find("select[name*='misc_mortgage_context']").change(function() {
var mtg = $(this).val();
if (mtg == "Subordination") {
$('.Subordination').show();
$('.Modification').hide();
}
else if (mtg == "Modification") {
$('.Modification').show();
$('.Subordination').hide();
}
else {
$('.Subordination').hide();
$('.Modification').hide();
}
});
});
The wrapper: { ... } fields are set to display: none by CSS and then shown or hidden according to the above values via JS. This same code works (without the cocoon:after-insert part of course) on a static HTML page for adding a single item without the necessity of adding multiple items at once like Cocoon is beautifully made to do.
I've tried the code above many different ways based on different posts or sites I've found online but I can only seem to get the first test alert to fire. Including misc_checkbox.find('...') without the $(...) wrapper.
Am I going about this the wrong way or is my code just incorrect? Thanks in advance for any and all help!
Update
Of course as soon as I post the question I figured it out. The + in [id*="+misc_mortgage"] was throwing it off and I wasn't loading cocoonoptions.js correctly. Going to leave this question up so maybe it will help someone in the future.
So my code was almost correct. Once I changed
$(misc_checkbox).find('input[type=checkbox][id*="+misc_mortgage"]')
to $(misc_checkbox).find('input[type=checkbox][id*="misc_mortgage"]') and loaded the JS via
<% content_for :javascript do %>
<script type="text/javascript">
</script>
<% end %>
function at the bottom of view, everything worked.

jQuery file uploader and turbolinks not working on heroku, have tried every fix I can find on SO

I have spent hours trying to fix this. I used the Heroku guide for S3 Direct upload in Rails. I was able to get it working in my customized version. Works great in the local setup but not on Heroku. I have narrowed it down to a JS problem because the progress bar does not want to load. The JS makes the upload depend on the creation of the before it will start the upload. My JS skills suck (I keep plugging along because I have to but I hate JS with a passion, it's just not for me).
application.js:
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require jquery-table
//= require js/stellar
//= require js/nivo-lightbox.min
//= require js/custom
//= require js/css3-animate-it
//= require gritter
//= require turbolinks
//= require jquery-fileupload/basic
// = require_tree .
To get it to work in test I had to put the script in the form itself.
_form.html.erb
<div class="row">
<div class="col-md-10 col-md-offset-1 text-center inner">
<%= form_for(#repair, html: { class: "directUpload", data: { 'form-data' => (#s3_direct_post.fields), 'url' => #s3_direct_post.url, 'host' => URI.parse(#s3_direct_post.url).host } }) do |f| %>
<% if #repair.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#repair.errors.count, "error") %> prohibited this repair from being saved:</h2>
<ul>
<% #repair.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field h3">
<%= current_user.email %>
</div>
<div class="field h3">
<%= f.label 'PPE Serial #' %>
<%= f.collection_select :ppe_id, current_user.ppes, :id, :serial %>
</div>
<div class="field h4">
<%= "Entered On: #{Date.today}" %><br />
</div>
<div class="field h4" id="photoUploader" >
<%= f.fields_for :photos, #repair.photos.build do |photos_fields| %>
<%= photos_fields.label :file_url, 'Upload a Photo' %>
<%= photos_fields.file_field :file_url %>
<% end %>
<div class='progress' id='pgbar'></div>
</div>
<div class="field h4">
<%= f.label :notes, 'Please give a detailed description of repairs needed.' %><br />
<%= f.text_area :notes, class: 'note_area' %>
</div>
<div class="actions">
<%= f.submit 'Save Record', class: 'btn btn-brand' %>
</div>
<script>
$(window).on('turbolinks:load', function() {
$('.directUpload').find("input:file").each(function(i, elem) {
var fileInput = $(elem);
var form = $(fileInput.parents('form:first'));
var submitButton = form.find('input[type="submit"]');
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress' id='pgbar'></div>").append(progressBar);
$('#pgbar').remove();
fileInput.after(barContainer);
fileInput.fileupload({
fileInput: fileInput,
url: form.data('url'),
type: 'POST',
autoUpload: true,
formData: form.data('form-data'),
paramName: 'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
dataType: 'XML', // S3 returns XML if success_action_status is set to 201
replaceFileInput: false,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Uploading done");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = '//' + form.data('host') + '/' + key;
// create hidden field
var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
form.append(input);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
});
});
});
</script>
<% end %>
</div>
</div>
I have tried having the script in the application.js file, with mixed results in test but none work on Heroku. I have also tried every combination of
$(window).on('turbolinks:load', function() {
$(Document).on('turbolinks:load', function() {
$(document).ready(function(){
$(function(){
I've precompiled assets, I've disabled turbolinks for this form, disabled turbo links for this page, disabled turbolinks for this entire app, moved the order of the files in the appplication.js require list.
I wish I understood the JS better to tweak the upload script so it didn't depend on what ever it seems to depend upon to load.
I'm getting no errors in the JS console. It just loads or it doesn't depending on the environment.
Any help is much appreciated.
One note on the JS for the progress bar. In my working version on Test, turbolinks would keep creating the container div for the bar on reload. So empty bars were being created but they didn't interfere with the script at all. So I created the div then added a call to remove it so that if it exists already it would be replaced by the new one on reload.
I was finally able to work around this. The problem was not with my JS code in the upload script on the page, but the fact that Heroku refused to include the jQuery-uploader code no matter what I tried. Like I said, works great in production, but not at all on Heroku. What I did to load the jQuery-upload/basic JS into my Heroku install was to copy the three files in the jQuery-upload gem into my /public folder then add this to my initializer file for assets:
/config/initializers/assets.rb:
... #added code:
Rails.application.config.assets.precompile += %w( jquery-fileupload/basic-plus.js )
Not sure why, no matter what I did, the JS code that would load without any issue
Now when Heroku boots it loads that JS into my application.js file. Everything is working great. I have to wonder if there isn't some flaw in the gem.

How do I dynamically populate a partial based on a link click?

So I have a partial called _node.html.erb. That is rendered in different views (and from different controllers) throughout my app. On one particular view, what happens is I generate a list of events that are linkable (and unique).
What I want to happen is, whenever someone clicks one of those links, it shows the partial and populates it with the correct info. The way I was initially trying to do it is via passing data-attributes from the link to some JS that does the updating of the partial. This quickly runs into a wall when we reach the node.comments, where the partial should load all of the comments associated with the node being rendered.
For example, here is some sample code of what my _node.html.erb may look like:
<!-- Card Meta -->
<div class="card__meta">
<aside>
<%= image_tag node.user.avatar.url, class: "card__author-avatar", alt: "" %>
</aside>
<section>
<h6 class="card__author-name"><%= node.user.name %></h6>
<time class="card__date"><%= node.created_at.strftime("%B %d, %Y") %></time>
<p class="card__description"><%= node.media.try(:description) %>
<textarea class="card-form__description" name="" id="" cols="30" rows="10">Aenean lacinia bibendum nulla sed consectetur.</textarea>
</section>
</div>
<!-- End Card Meta -->
<!-- Card Comments -->
<div class="card-comments-container">
<h4 class="card-comments__title"><%= pluralize(node.comments_count, "Comment") %></h4>
<a class="card-comments__button"><i class="icon-arrow-down"></i></a>
<div class="card-comments">
<%= render partial: "nodes/comment", collection: node.comments.includes(:user).order(created_at: :desc) %>
</div>
</div>
<!-- End Card Comments -->
Here is a sample of the link that a user can press, or rather the Rails code that generates the link:
<div class="item-wrapper">
<div class="item_comment">
<h5 class="item_comment-title"><%= event.action %> on <%= link_to event.eventable.node.name, "#", data: {toggle_card: "", card_id: event.eventable.node.id, card_title: event.eventable.node.media.title, card_description: event.eventable.node.media.description, card_time: event.eventable.node.media.created_at, card_comments: event.eventable.node.comments_count, card_favorites: event.eventable.node.cached_votes_total } %></h5>
<p class="item_comment-text"><%= event.eventable.message %></p>
</div>
</div>
This is an example of the type of jQuery I was using to do it originally:
$(document).ready(function() {
var overlay = $('.card-overlay'),
$card = overlay.find('.card'),
triggers = $('a[data-toggle-card]');
overlay.hide();
overlay.click(function(e) {
if( e.target != this ) {
} else {
overlay.hide();
}
});
triggers.click(function() {
var trigger = $(this);
var tr = trigger;
overlay.show();
var card = {
id: tr.data('card-id'),
title: tr.data('card-title'),
description: tr.data('card-description'),
time: tr.data('card-time'),
comments: tr.data('card-comments'),
favorites: tr.data('card-favorites'),
};
$card.attr('id', 'card-' + card.id);
$card.find('.card__content').attr('style', 'background-image: url(' + card.image + ')');
$card.find('.card__favorite-count').html('<i class="icon-heart"></i> ' + card.favorites);
$card.find('.card__comment-count').html('<i class="icon-speech-bubble-1"></i> ' + card.comments);
$card.find('.card__title').text(card.title);
$card.find('.card__date').text(card.time);
$card.find('.card__description').text(card.description);
$card.find('textarea').attr('id', 'card-input-field-' + card.id);
var player = videojs("example_video_" + card.id, {}, function() {});
player.src(
{
src: card.video.mp4, type: 'video/mp4'
},
{
src: card.video.webm, type: 'video/webm'
},
{
src: card.video.ogv, type: 'video/ogv'
}
);
player.load();
player.ready(function() {
console.log("READY")
});
return false;
});
});
How do I do this in a more "Railsy" way without relying on too much JS/jQuery?
Edit 1
After attempting Rich Peck's suggestion, I got this error:
05:46:31 web.1 | Unexpected error while processing request: JS, accepted HTTP methods are OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, VERSION-CONTROL, REPORT, CHECKOUT, CHECKIN, UNCHECKOUT, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE-CONTROL, MKACTIVITY, ORDERPATCH, ACL, SEARCH, and PATCH
To be clear, this is what I did:
Modified my routes to have this:
resources :events do
get :node, action: :node
end
Then created an action in my EventsController called node like this:
def node
#node = current_user.nodes.find(params: [node_id])
respond_to do |format|
format.js
end
end
Then I created a app/views/events/node.js.erb, that looks like so:
$overlay = $('.card-overlay');
$node = $('<%= j render "nodes/node", locals: {node: #node} %>');
$overlay.append($node);
Then I modified the link_to tag like so:
<%= link_to event.eventable.node.name, event_node_path(event.eventable.node), remote: true, method: :js %></h5>
Okay there are two problems here:
You're trying to load & populate a partial without any extra data (IE node.comments)
Secondly, you're heavily relying on JQuery to update various attributes on the page. Although there is nothing wrong with this in itself, it does become an issue if you're looking to change page layouts etc.
A more "railsy" way to do this would be to remotely load the node directly from the server, much as Alex Lynham recommended:
#config/routes.rb
resources :cards do
get :node_id, action: :node #-> url.com/cards/:card_id/:node_id
end
#app/controllers/cards_controller.rb
class CardsController < ApplicationController
def node
#card = Card.find params[:card_id]
#node = #card.nodes.find params[:node_id]
respond_to do |format|
format.js #-> app/views/cards/node.js.erb
end
end
end
#app/views/cards/node.js.erb
$node = $("<%=j render "cards/node", locals: {node: #node} %>")
$('body').append($node);
#app/views/cards/index.html.erb
<% #cards.each do |card| %>
<%= link_to card.name, card_node_path(#card), remote: true, format: :js %>
<% end %>
This will keep the code clear and concise.
You can put all your options directly in the _node.js.erb partial. Although it still means there are some changes you have to make, it will give you the ability to change your partial & implementation without all the JQuery specialization you have now.
You need to make an AJAX request to the server, then use a js.erb partial to return what you are looking for to the frontend, probably. That's the most 'Rails-y' way I can think of.
Personally, I'd ask for JSON and then use a bit of jQuery to wrangle the return values much as you have done in your example. By calling out to the backend is the only sane way you'll be able to get the associated records, the .comments bit, which is I believe where you're stuck, if I've understood your question correctly.
EDIT:
This is quite a good tutorial on how AJAX-y stuff can be accomplished with Rails, in case you've not had much cause to do so in the past.

rails crud using jquery popover via bootstrap

I tried to add jquery popover to rails default cruds instead of redireting to views and what I am doing is rendering the form in the jquery popover:
<%= content_tag_for :tr, #order.order_items do |i| %>
<a class='btn btn-mini label-with-popover' id=<%=i.id%>><%= t('helpers.links.edit_html') %> </a>
<% end %>
<script type="text/javascript">
$(function () {
$('.label-with-popover').popover({
html : true,
content: "<%= escape_javascript(render :partial => 'form_item') %>" ,
placement: 'top'
} );
});
</script>
and here is my form_item:
<%= simple_form_for :order_item, url: admin_shop_order_path, :html => { :class => 'form-horizontal' } do |f| %>
<div class='form-inputs'>
<%= f.input :item_id , :collection => #shop.products.collect{|b| b.variants}.flatten.map{|variant| ["#{variant_full_title(variant)}", variant.id]}%>
<%= f.input :quantity %>
</div>
<div class="form-actions">
<%= f.button :submit, :class => 'btn-primary' %>
<%= link_to t("helpers.links.cancel"), admin_shop_orders_path, :class => 'btn' %>
</div>
<% end %>
but the problem appears on the edit button. to render the edit form we need the object that we want to edit(:order_item I mean) and a way to get that is by using the id and this is why I have set the anchor tag id. now we have to get that anchor id within the popover function but $(this).id doesn't work. any suggestion?
In jQuery, you need to use attr('id') to get the id of an element. Try to replace $(this).id with:
$(this).attr('id')
See jQuery's documentation for more details: http://api.jquery.com/attr/
But a prefered way to achieve this is usually to use data attributes. It's a clean way to pass data from your view to some JS code. Your link would look like this:
<a class='btn btn-mini label-with-popover' data-item-id=<%=i.id%>>...</a>
And in your JS file, you would get this value using:
$(this).data('item-id')
jQuery's documentation: http://api.jquery.com/data/
You are rendering wrongly, I think, you should try to render your partial with locals. You can also pass local variables into partials, making them even more powerful and flexible.
In your case, while rendering your partial form_item in script tag, so you can write it like this:
<script type="text/javascript">
$(function () {
$('.label-with-popover').popover({
html : true,
content: "<%= escape_javascript(render :partial => 'form_item', :locals => {order_item: #order_item}) %>" ,
placement: 'top'
} );
});
</script>
and in your form you will be able to access it like :
<%= form_for(order_item) do %>
# write your form stuff %>
<% end %>
This way you can handle your form for both create or edit operations.
First I suggest you to pass id with some text added to it(I replaced the id with "link_<%= i.id%>").
Second call a onclick function on your a tag:
<%= content_tag_for :tr, #order.order_items do |i| %>
<a class='btn btn-mini label-with-popover' id="link_<%=i.id%>" ><%= t('helpers.links.edit_html') onclick="javascript:openPopup(this.id)" %> </a>
<% end %>
and Last but not the least, get id in your function, and pass it through your partial.
<script type="text/javascript">
function openPopup(a_id) {
var id = a_id.split("link_")[1];
$('.label-with-popover').popover({
html : true,
content: "<%= escape_javascript(render :partial => 'form_item', locals => {:id => id}) %>" ,
placement: 'top'
} );
});
</script>
I am not good at javascript, but from my answer, you will get, what I wanted to explain you. and I am sure that you will find a more improved way to do this. And If you do, please post it here as well. Hope it will help. Thanks

Reference a Coffeescript event in a separate JS file

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 = -> ...

Categories