I have a simple form in a rails app that is rendered within a partial called roster_panel:
<div id = 'active_admin_content'>
<%= semantic_form_for #roster_item do |form| %>
<h3>Add a counselor</h3>
<div id="counselor_status_message"><%= #status_message %></div>
<%= form.inputs do %>
<%= form.input :first_name, input_html: {id: 'counselor_first_name'} %>
<%= form.input :last_name, input_html: {id: 'counselor_last_name'} %>
<%= form.input :email, input_html: {id: 'counselor_email'} %>
<div class="button_container" >
<input id="submit_counselor_add" type="button" value = "Send forms packet" class = "button" >
</div>
<% end %>
<% end %>
</div>
In my jquery code, I tie the submit click to this:
$( document ).on('turbolinks:load', function() {
$("#submit_counselor_add").click(function(){
$.get("add_counselor_info" , { id: $("#roster_id").text(), first_name: $("#counselor_first_name").val(),
last_name: $("#counselor_last_name").val(), counselor_email: $("#counselor_email").val() },
function(data){ $("#roster_panel").html(data);
}
)
});
});
This routes to this controller method:
def add_counselor_info
#roster = Roster.find(params[:id])
#group = ScheduledGroup.find(#roster.group_id)
#liaison = Liaison.find(#group.liaison_id)
#items = RosterItem.where(roster_id: #roster.id)
#roster_item = RosterItem.new(group_id: #group.id, roster_id: #roster.id,
first_name: params[:first_name], last_name: params[:last_name],
email: params[:counselor_email], youth: false, item_status: 'Unconfirmed' )
if #roster_item.save
#error_count = 0
#status_message = 'This counselor has been added and the forms package has been emailed. You may enter another counselor.'
#items = RosterItem.where(roster_id: #roster.id)
#roster_item = RosterItem.new
else
#error_count = #roster_item.errors.size
#status_message = "#{#error_count} errors prevented saving this information: "
#roster_item.errors.full_messages.each { | message | #status_message << message << ' '}
#items = RosterItem.where(roster_id: #roster.id)
end
render partial: 'roster_panel'
end
After a fresh page load, this process works fine and redisplays the form as expected. However, at that point the submit button no longer triggers the action in jquery. Other jquery functions on the page still work, however. This may have something to do with turbolinks, which I am not very familiar with.
Any help would be appreciated!
After submitting the form, the DOM is replaced by the new one, so on click event binding is being lost.
Try to bind this event through the parent element, which won't be overridden by javascript (e.g. body):
$( document ).on('turbolinks:load', function() {
$("body").on('click', '#submit_counselor_add', function() {
$.get("add_counselor_info", {
id: $("#roster_id").text(),
first_name: $("#counselor_first_name").val(),
last_name: $("#counselor_last_name").val(),
counselor_email: $("#counselor_email").val()
},function(data) {
$("#roster_panel").html(data);
});
});
});
Related
I have a simple has_many and belongs_to relationship in my rails app. I'm using simple_form and want to dynamically change the dropdown options based on the value chosen by the user.
Models
class Processor < ApplicationRecord
has_many :processor_bank_accounts
end
class ProcessorBankAccount < ApplicationRecord
belongs_to :processor
end
Form inputs
<%= simple_form_for [#customer, #transaction] do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :status, :collection => ["payment request"], include_blank: false %>
<%= f.input :processor, collection: #processors ,label_method: :name,value_method: :id,label: "Processor" , include_blank: false %>
<%= f.input :processor_bank_account, collection: #bank_accounts , label_method: :bank_name, value_method: :id, label: "Processor Bank Account" , include_blank: true %>
<%= f.input :tcurrency, collection: #currencies, include_blank: false, label: 'currency' %>
<%= f.input :amount, as: :decimal, label: 'amount' %>
</div>
<div class="form-actions text-center">
<%= f.button :submit, "Add transaction", class: "form-button"%>
</div>
<% end %>
So essentially, I need the processor_bank_account dropdown to populate based on the processor chosen by the user. In the console, this would just be: ProcessorBankAccount.where(processor: processor).
Need to load options using JS and think I need to use JSON but not sure where to go from here
One way to do this would be to use jQuery to make an AJAX call to a controller action and then let Rails handle the rest through an erb template.
So on your page, with the form, invoke the action via AJAX using something like:
<script>
$(document).ready(function() {
$('#processor_id').on('change', function() {
$.ajax({
url: '/transactions/get_processor_bank_accounts',
type: 'GET',
data: {
processor_id: this.value
},
dataType: 'script',
error: function() {
alert('An error occurred retrieving bank accounts for the selected processor.');
}
});
});
});
</script>
NB, #processor_id is the id for your dropdown control.
Next, instantiate the bank accounts within your action in your controller:
def get_processor_bank_accounts
#processor_bank_accounts = ProcessorBankAccount.where(processor_id: params[:processor_id])
end
And finally simply create a view that will be responsible for repopulating your dropdown:
$select_list = $('#processor_id');
$select_list.empty();
<% #processor_bank_accounts.each do |pba| %>
$select_list.append($('<option value="<%= pba.id %>"><%= pba.name %></option>'));
<% end %>
I came up with the following solution:
1) Add a new method to my processors controller to render the inputs for the second (dynamic) dropdown in JSON format:
def processor_bank_accounts
render json: #processor.processor_bank_accounts.each do |bap|
{ id: bap.id, name: bap.name }
end
end
2) Assign this new function to a new route in config/routes:
get 'api/bankaccounts', to: 'processors#processor_bank_accounts', as: 'bankaccounts'
3) Create a JS function to access the route with the id of the processor selected in the first dropdown and populate the second dropdown with items from the JSON array:
// select first dropdown
const processor = document.getElementById("transaction_processor");
// select second dropdown
const bapSelect = document.getElementById("transaction_processor_bank_account");
function update_baps(processor_id) {
const url = `INSERTWEBURLHERE/api/bankaccounts?id=${processor_id}`;
fetch(url)
.then(response => response.json())
.then((data) => {
bapSelect.innerHTML = ""; // clear second dropdown
data.forEach((bap) => { // go through all the BAPs
const elem = `<option value="${bap.id}">${bap.bank_name}</option>`; // create option elements to insert into the second dropdown, bank_name is the chosen label method in the form
bapSelect.insertAdjacentHTML("beforeend", elem); // insert options into the dropdown
});
});
}
4) Trigger the JS when the first dropdown field is changed:
processor.addEventListener('change', function () {
update_baps(parseInt(processor.value));
});
You should add an id to the selects so you can identify them form the script.
$('select#processor').on('change', function() {
var processor_id = this.value;
var processor_bank_account = $('select#processor_bank_account')
$.ajax({
type: "GET",
url: <%= your_path %> ,
data: { processor_id: processor_id },
success: function(data, textStatus, jqXHR){
processor_bank_account.empty();
var option = new Option(data.bank_name, data.id, false, false);
processor_bank_account.append(option);
},
error: function(jqXHR, textStatus, errorThrown){...}
})
});
I have a rails form that looks like this :
= form_for :location, :url=>'/welcome' do |f|
= f.text_field '', placeholder: 'Enter your zip code', id:'input_id'
= f.button "Continue", class: 'button-test'
So when the button continue is clicked upon , rails get the controller and execute the method /welcome
so what i am trying to do is to actually execute a simple javascript function like :
function wawa() {
alert('it works')
};
when the button continue is cliked instead of rails getting to execute the method /welcome.
How can I approach this problem using Javascript only and no library ?
You can use submit() function of jquery.
= form_for :location, :url=>'/welcome', html: {id: "id_form_location"} do |f|
= f.text_field '', placeholder: 'Enter your zip code', id:'input_id'
= f.button "Continue", class: 'button-test'
In javascript file:
$("#id_form_location").submit(function(event) {
alert('it works');
});
You need to add some JavaScript On submit Listener I suppose you need it before form is submit. If you want only on click then have a look at on click Listener also have a look at event prevent as it will help you to prevent from default behaviour of button.
Try this code
<%= form_for :location, :url=>'/welcome' do |f| %>
<%= f.text_field '', placeholder: 'Enter your zip code', id:'input_id' %>
<%= f.button "Continue", class: 'button-test', id: 'demo' %>
<% end %>
<script type="text/javascript">
document.getElementById("demo").addEventListener("click", function(event){
event.preventDefault()
alert('Hello')
});
</script>
I'm using rails4.2.0
in my e-commerce site, when a user submit his payment type, I want to redirect outside website. The flow is below.
a user choose payment type
use click submit button
-- ajax (format js) --
redirect to outside website using post method
source of 2 and 3 are like ,
create.html.erb
<%= form_for(:user,:url => { controller: "settlements", action: "settlement"}, remote: true, html: {class: :settlement_form}) do |f| %>
<%= f.radio_button :settlement_type, 0 %>paypal
<%= f.radio_button :settlement_type, 1 %>credit card
<%= f.hidden_field :email, :value => #user.email %>
<%= f.hidden_field :fee_type, :value => #user.fee_type %>
<%= f.submit "Submit", data: { disable_with: "Please wait..." }, class: "btn btn-warning" %>
settlement_controller
def settlement
user = User.new(user_params)
if user.save
# parameters for outside website
#payment_params
else
render "new"
end
end
settlement.js.erb
var form = $('<form></form>',{id:"pay",action:'http://outside_ec_site_url/hoge',method:'POST'}).hide();
var body = $('redirect');
body.append(form);
form.append($('<input/>', {type: 'hidden', name: 'something', value: <%= #payment_params[:something] %>}));
form.append($('<input/>', {type: 'hidden', name: 'something', value: <%= #payment_params[:something] %>}))
form.submit();
I fixed this problem, but I want to know better method.
changed settlement.js.erb like,
$("#redirect").html("<%= escape_javascript(render :partial => 'pay' ) %>");
and created new file _pay.html.erb
<script>
$('<form/>', {id: 'paygent',action: "outside_website_url", method: "POST"})
.append($('<input/>', {type: 'hidden', name: 'something1', value: "<%= #payment_params[:somthing1] %>"}))
.append($('<input/>', {type: 'hidden', name: 'something2', value: "<%= #paygent_params[:something2] %>"}))
.appendTo(document.body)
.submit();
</script>
then, it works.
Do you know any other method? Any idea is appriciated.
I have deployed a custom Stripe payment form in our app, and 97% of the time it works just fine. Occasionally, I see Stripe report an error on an attempted payment.
POST /v1/customers
error:
type: "invalid_request_error"
message: "You passed an empty string for 'card'. We assume empty values are an attempt to unset a parameter; however 'card' cannot be unset. You should remove 'card' from your request or supply a non-empty value"
param: "card"
This is raised when creating a customer with the Stripe token that should've been generated from the javascript:
sale = current_user.sales.create(
plan_id: plan.id,
amount: plan.price_in_cents,
stripe_token: params[:stripeToken]
)
After much troubleshooting I've confirmed that this is happening because Stripe is not getting called in the first place to create the token. In other words, this is not an issue of passing params, where the token is generated but simply not passed to the controller. For some reason, something in the javascript is not running, so the form simply submits without it calling Stripe.
Again remember that 97% of the time this issue does not occur. When it does occur it happens to the same user over and over again. I have not found any pattern with browsers (it has happened on Chrome, IE, Firefox). Also, eventually users facing this problem have been able to successfully pay using the same browser and without any settings adjustments. This makes me think there's something potentially with my server environment and not the client.
Here is my full javascript:
subscription.js
var stripeResponseHandler = function(status, response) {
var $form = $('#paid_subscription');
if (response.error) {
// Show the errors on the form
$form.find('.payment-errors').text(response.error.message);
$form.find('button').prop('disabled', false);
} else {
// token contains id, last4, and card type
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$('#stripe_card_token').val(token);
//$form.append($('<input type="hidden" name="stripeToken" />').val(token));
// and submit
$form.get(0).submit();
}
};
jQuery(function($) {
$('#card_number').payment('formatCardNumber')
$('#paid_subscription').submit(function(event) {
// var expiration = $("#card-expiry").payment("cardExpiryVal")
// $('#card-exp-month').val(expiration.month);
// $('#card-exp-year').val(expiration.year);
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
});
And here is the controller code, just in case:
charges#create
def create
token = params[:stripeToken]
bundle = Bundle.find_by_id(params[:bundle_id])
if bundle
process_bundle_sale(bundle, token)
else
plan = Plan.find(params[:plan_id])
begin
sale = current_user.sales.create(
plan_id: plan.id,
amount: plan.price_in_cents,
stripe_token: params[:stripeToken]
)
sale.process!
if sale.finished?
if current_user.subscribe_with_referer({plan_id: plan.id}, session[:http_referer])
TrackSubscriptions.track_paid_subscription(cookies, plan, sale)
flash[:success] = "Success! You now have full access to \"#{plan.title}\""
redirect_to plan_path(plan)
else
flash[:alert] = "Oops something went wrong. Please contact support and we'll get to the bottom of it."
redirect_to plan_path(plan)
end
else
flash[:alert] = sale.error
redirect_to new_plan_subscription_path(plan.slug)
#redirect_to plan_path(plan)
end
end
end
end
Finally here's the form code:
<%= simple_form_for #charge = Sale.new, :url => charges_path, :method => :post, html: { id: :paid_subscription } do |f| %>
<fieldset class = "inputWrapper">
<h2 class="sub-header">Payment</h2>
<span class="payment-errors"></span>
<%= hidden_field_tag :plan_id, #plan.id %>
<%= hidden_field_tag :stripeToken, nil,id: :stripe_card_token %>
<div class="payment-fields">
<div class="field card-number-field">
<label class="control-label">Card Number</label>
<%= text_field_tag :card_number, nil, name: nil, placeholder: "4444 1234 1234 1234",:data => {:stripe => 'number' } %>
</div>
<div class="field security-code-field">
<label class="control-label">Security Code</label>
<%= text_field_tag :card_code, nil, name: nil, placeholder: "123", :data => {:stripe => 'cvc' } %>
</div>
<div class="field expiry-field">
<label class="control-label">Exp (MM/YYYY)</label>
<div class="month-field">
<%= text_field_tag :exp_month, nil, name: nil, placeholder: "10", id: "card-exp-month", maxlength: 2, data: { stripe: "exp-month" } %>
<span class="slash"> / </span>
</div>
<div class="year-field">
<%= text_field_tag :exp_year, nil, name: nil, placeholder: "2016", id: "card-exp-year", maxlength: 4, data: { stripe: "exp-year" } %>
</div>
</div>
<div style="clear:both"></div>
</div>
<%= render 'end_form', :plan => #plan %>
<div class="submit-button">
<%= f.submit 'Register for Course', :class => "button greenButton", :error => false %>
</div>
</fieldset>
<% end %>
I'm building a payment page that lists three different subscription options and am using Stripe's checkout to manage the payments.
The page is rendering properly, and all 3 subscription options have the "buy now" button that should be linked to Stripe.
My issue is that the first button is the only one that is properly pulling up the Stripe checkout flow. Buttons 2 and 3 throw the following error:
Unknown action
The action 'index' could not be found for ChargesController
The relevant part of my payment page is:
<% #plans.each do |plan| %>
<li class="col-md-3 plan <%= 'plan-primary' if plan.highlight? %>">
<div class="img-thumbnail">
<div class="caption">
<h3><%= plan.name %></h3>
<h4><%= plan_price(plan) %></h4>
<div class="call-to-action">
<% if #subscription.nil? %>
<% if plan.highlight? %>
<%= form_tag main_app.charges_path do %>
<script src="https://checkout.stripe.com/checkout.js"></script>
<button id="customButton" class="btn btn-success">Buy Now</button>
<script>
var handler = StripeCheckout.configure({
key: '<%= 'pk_test_my_pk' %>',
image: '/assets/my_logo.png',
token: function(response) {
var tokenInput = $("<input type=hidden name=stripeToken />").val(response.id);
var emailInput = $("<input type=hidden name=stripeEmail />").val(response.email);
$("form").append(tokenInput).append(emailInput).submit();
}
});
document.getElementById('customButton').addEventListener('click', function(e) {
handler.open({
name: 'My Co',
description: 'Listing subsctiption ($50.00)',
amount: 50*100,
shippingAddress: false
});
e.preventDefault();
});
</script>
<% end %>
<% else %>
<%= form_tag main_app.charges_path do %>
<script src="https://checkout.stripe.com/checkout.js"></script>
<button id="customButton" class="btn btn-large btn-primary">Buy Now</button>
<script>
var handler = StripeCheckout.configure({
key: '<%= 'pk_test_my_pk' %>',
image: '/assets/my_logo.png',
token: function(response) {
var tokenInput = $("<input type=hidden name=stripeToken />").val(response.id);
var emailInput = $("<input type=hidden name=stripeEmail />").val(response.email);
$("form").append(tokenInput).append(emailInput).submit();
}
});
document.getElementById('customButton').addEventListener('click', function(e) {
// Open Checkout with further options
handler.open({
name: 'My Co',
description: 'Listing subsctiption ($40.00)',
amount: 40*100,
shippingAddress: false
});
e.preventDefault();
});
</script>
<% end %>
<% end %>
Ideas on why only one of the 3 buttons is working properly?
Thanks!
You can get it to seem to work by having unique button ids, e.g.
<button id="<%= dom_id(pricing, 'btn') %>
but there is another problem, with the stripe js. If you execute StripeCheckout.configure multiple times it will create multiple iframes with the same name attribute. Unfortunately this means whatever your customer tries to buy, they will always be sold the last thing you inserted, even if the stripe popup said it was selling them something else.
We used this solution: one form, and dynamically inserting the price and times:
<%= form_tag charges_path, id: 'stripe-payment-form' do %>
<%= hidden_field_tag 'amount', nil, id: 'payment_amount' %>
<%= hidden_field_tag 'name', nil, id: 'payment_name' %>
<%= hidden_field_tag 'days', nil, id: 'payment_days' %>
<% Pricing.all.each do |pricing| %>
<p>
<button id="<%= dom_id(pricing, 'btn') %>">
Buy <%= pricing.name %> for <%= number_to_currency(pricing.pounds, unit: '£') %>
</button>
</p>
<% end %>
<%= javascript_tag do %>
var handler = StripeCheckout.configure({
key: "<%= Rails.configuration.stripe[:publishable_key] %>",
image: "<%= image_path('/images/apple-icons/apple-touch-icon-144x144-precomposed.png') %>",
token: function(token, args) {
var form = $('#stripe-payment-form');
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id`
form.append($('<input type="hidden" name="stripeToken" />').val(token.id));
form.submit();
}
});
<% Pricing.all.each do |pricing| %>
document.getElementById('<%= dom_id(pricing, 'btn') %>').addEventListener('click', function(e) {
e.preventDefault();
var form = $('#stripe-payment-form');
// set the price etc for the button clicked
$('#payment_amount').val("<%= pricing.pence %>");
$('#payment_name').val("<%= pricing.name %>");
$('#payment_days').val("<%= pricing.days %>");
// Open Checkout with further options
handler.open({
name: 'Company name',
currency: 'GBP',
description: '<%= pricing.name %>',
amount: '<%= pricing.pence %>',
email: '<%= member.email %>',
});
});
<% end %>
<% end %>
<% end %>
I came across the same problem in my own app recently.
All three of your buttons have the same ID.
I know this is old, but I resolved this issue by changing the name of the handler variables (each one should have a different name) instead of changing the HTML ID's.
I recently encountered this problem and wanted to leave an alternative solution. In our app, we have two buttons on the page using stripe.js: "Buy Item" or "Pro Subscription". This method uses jQuery to just remove the second button from the DOM when the first one is clicked. If the user cancels the payment, the button is rendered back into the DOM. This is how the handler might look:
$('#firstButton').on('click', function() {
$('#secondButton').html(""); // Remove the second stripe script from the dom
handler.open({
// handler stuff
closed: function(){
$('#secondButton').html('<%= j render partial: "second_button" %>'); // Renders button back to the DOM if payment is cancelled.
}
});
});