Rails. jQuery form submit on field blur - javascript

In my app there's an order model with has_many order_items.
Order page:
<div id="cart-rows">
<% #order_items.each do |order_item| %>
<%= render 'orders/cart_row', product: order_item.product, order_item: order_item %>
<% end %>
</div>
cart_row partial (each for one order_item):
<div class="cart-row col-xs-12" id="order-item-<%= order_item.id %>">
<h3><%= product.name %></h3>
<div class="col-md-9 col-sm-8 col-xs-9 cart-form-wrapper">
<%= form_for order_item, remote: true do |f| %>
<%= f.text_field :color_quantity, value: order_item.color_quantity.to_i,
min: 0, class: "form-control cart-quantity" %>
<h4>
x <%= order_item.var_price("color") %>
<i class='glyphicon glyphicon-ruble'></i>
</h4>
<% if order_item.color_quantity > 0 %>
<h4>
= <%= order_item.color_quantity * order_item.var_price("color") %>
<i class='glyphicon glyphicon-ruble'></i>
</h4>
<% end %>
<%= f.hidden_field :product_id, value: product.id %>
<%= f.submit "", class: "btn-hidden submit-cart-row" %>
<h4>
Total: <%= order_item.item_total %>
<i class='glyphicon glyphicon-ruble'></i>
</h4>
<% end %>
</div>
In each row there's a form with each product types and quantity of it.
When form submits, information (each type total price and its quantity) in this current cart_row recalculates with jquery:
$(document).ready(function() {
$('.cart-quantity').blur(function() {
$(this).closest("form").submit();
});
});
I want to submit it each time on field blur.
The problem is that it works but only for the first blur. When I change another field and blur it nothing happens. Otherwise if I hit enter forms update normally.
I don't see any errors in the console. Server log is empty because nothing happens: on blur action doesn't fire. It must be some javascript error but I cannot trace it.
Order items controller:
class OrderItemsController < ApplicationController
before_action :set_order
before_action :set_order_item, only: [:update, :destroy]
def create
#order_item = #order.order_items.new(order_item_params)
#order.save
session[:order_id] = #order.id
end
def update
#order_item.update_attributes(order_item_params)
#order_items = #order.order_items
end
def destroy
#order_item.destroy
#order_items = #order.order_items
end
private
def set_order
#order = current_order
end
def set_order_item
#order_item = #order.order_items.find(params[:id])
end
def order_item_params
params.require(:order_item).permit(:color_quantity, :high_quantity, :spec_quantity, :product_id)
end
end
Thank you.

I found what was causing the error.
I was using a sloppy method to update cart partial, something like
$(".shopping-cart").load(document.URL + ' .shopping-cart');
So this was a reason.

Related

Rails + Javascript: Create one parent model and multiple child model in same time

I have a parent model (paper) and a nested child model (tape) and want to create multiple child with a parent in same time without using any gems.
The input fields of tape should be dynamically added through clicking “+”.
If I write “3.times { #paper.tapes.build }” in controller, I can create 1 paper and 3 tapes in same time.
But if how many times the input field should be added is depend on the user (if user click "+" 10 times, 1 parent model and 10 child model should be saved in one time), how should I modify the code?
Here is my codes.
Paper Model
has_many :tapes
accepts_nested_attributes_for :tapes, allow_destroy: true
Tape Model
belongs_to :paper, optional: true
Controller
def new
#paper = Paper.new
3.times { #paper.tapes.build }
end
def create
#paper = Paper.new(paper_params)
#paper.user_id = current_user.id
if #paper.save
redirect_to action_list_paper_path(#paper.id)
else
render 'new'
end
end
Viewer
<%= nested_form_for(#paper) do |f| %>
<div class="container">
<%= stylesheet_link_tag 'papers', media: 'all', 'data-turbolinks-track': 'reload' %>
<div class= "paper_form">
<div class="col-md-6">
<%= f.label(:content, "Name") %>
<%= f.text_area :content %>
</div>
</div>
</div>
<div class="tape_form">
<%= f.fields_for :tapes do |tape| %>
<div id="input_pluralBox">
<div id="input_plural">
<div class="col-md-4">
<%= tape.label :label %>
<%= tape.text_field :label %>
</div>
</div>
<input type="button" value="+" class="add pluralBtn">
<input type="button" value="-" class="del pluralBtn">
</div>
</div>
<% end %>
</div>
</div>
<% end %>
Javascript (The input column “label” will be added with "+" through javascript).
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).on("click", ".add", function() {
$(this).parent().clone(true).insertAfter($(this).parent());
});
$(document).on("click", ".del", function() {
var target = $(this).parent();
if (target.parent().children().length > 1) {
target.remove();
}
});
</script>
An example of how to add nested records in a form:
<%= form.fields_for :headers, get_headers(client) do |builder| %>
<div>
<div class="header-fields form-row">
<%= builder.hidden_field :id %>
<div class="form-group col col-md-4">
<%= builder.label :key %>
<%= builder.text_field :key, class: 'form-control' %>
</div>
<div class="form-group col col-md-6">
<%= builder.label :value %>
<%= builder.text_field :value, class: 'form-control', value: builder.object.value %>
</div>
<% if action_name == "edit" %>
<div class="form-group col col-md-2">
<div class="form-check">
<%= builder.check_box :_destroy, class: 'form-check-input' %>
<%= builder.label :_destroy, "Destroy?", class: 'form-check-label' %>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
Helper function
def get_headers(client)
return client.headers if client.headers.any?
[Header.new]
end
Controller params
def client_params
params.require(:client).permit(headers_attributes: [:id, :key, :value, :_destroy])
end
Model:
class Client < ApplicationRecord
has_many :headers, dependent: :destroy
accepts_nested_attributes_for :headers, allow_destroy: true
end
Then JS as required for adding / removing rows.

How to display validation error in rails using AJAX

I need to display the validation error of my post model. I have used AJAX call to create a post into the index page of the post. So that when the NewPost button is clicked it will display the new post form partial into the index page. While creating the post in index page if there is any validation errors found, the errors are displayed and it renders the new post form partial in js format again so that the new post form appears twice along with the validation error.
Posts controller:
def create
#post = #topic.posts.build(post_params)
#post.user = current_user
respond_to do |format|
if #post.save
# Success
else
format.js { render 'posts/new' }
end
end
end
Create.js.erb file:
alert("Post created Successfully");
$('.new_post').hide();
$('.new_one').show();
$('.post_div').prepend('<%= j render #post %>');
Index.html.erb file:
<div class="container" >
<% if params[:topic_id].present? %>
<h2 align="center"><span class="em-text"><%= #topic.topicname %> Posts</span></h2><hr>
<%= link_to "New Post", new_topic_post_path, :class => 'btn btn-primary new_one' ,remote: true%> <br><br><br>
<% else %>
<h2 align="center"><span class="em-text">All Posts</span></h2><hr>
<% end %>
<div class="post_div">
<%= render #posts %>
</div>
<%= will_paginate #posts, renderer: BootstrapPagination::Rails %>
</div>
Post partial for new post form:
<%= form_for [#topic, #post],remote: true,html: {multipart: true}, url: topic_posts_path do |f| %>
<% if #post.errors.any? %>
<% #post.errors.full_messages.each do |msg| %>
<div class="alert alert-danger"><%= msg %></div>
<% end %>
<% end %>
<div class="form-group">
<%= f.label "Title" %><br/>
<%= f.text_field(:title,{:class => 'form-control', :placeholder => 'Enter the Title'}) %>
</div>
<div class="form-group">
<%= f.label "Body" %><br/>
<%= f.text_area(:body, {:class => 'form-control', :placeholder => 'Enter the Post Body'}) %>
</div>
<div class="form-group">
<%= f.submit({:class => 'btn btn-primary'})%>
</div>
<% end %>
new.js.erb file:
$('.new_one').hide().after("<%= j render 'form' %>")
Use can like this
<% if #post.errors.any? %>
alert("ERROR(S):\n<%= j #post.errors.full_messages.join("\n") %>")
Check the post whether it has any errors if error exists alert the error else do something

Adding search function within form

I am adding a search function within another form. I would like to search through the things within the other form.
Interest_index.html.erb
<%= form_for :interest, url: interest_index_path do |f| %>
<p class="col-md-8" align="left"><%= f.submit "Save Schedule", class: "btn btn-primary btn-lg btn-block"%></p>
<br>
<div class="field form-group col-lg-12">
<h1><%= f.label :interests, "Schedule" %></h1>
<br>
<div class="row inline">
<%= form_tag interest_index_path, method: :get do %>
<p>
<div class="col-lg-offset-2 col-lg-7" id="one">
<%= text_field_tag :search, params[:search], class: "form-control", placeholder: "Search" %>
</div>
<div id="two">
<%= submit_tag "Search", name: nil, class: "btn btn-default" %>
</div>
</p>
<% end %>
</div>
<% #subjects.each do |subject| %>
<div class="field">
<p><h4><%= f.check_box :interests, {:multiple => true}, subject.id, nil %><%= subject.name %></h4></p>
</div>
<% end %>
</div>
<% end %>
I have the search form within a bigger form to search through the subjects which will be chosen and then submitted with the main form. I have tried using basic search as i a new to rails but it does not work.
I think usually to have a form within a form you would need to make nested attributes, but this being a search form I am not sure how to do that or whether that would work.
Also the way i have it set up is I have created a lot of subjects which will be displayed under the interests index. Under interests the user picks the subjects of their interests
here are the controllers and models
Interests_controller
class InterestController < ApplicationController
def index
if params[:query].present?
#subjects = Subject.search(params[:query], load: true)
else
#subjects = Subject.all.sort_by(&:name)
end
end
def create
current_user.interests = params[:interests].to_json
if current_user.save
else
flash[:danger] = "Something went wrong!"
end
redirect_to interest_index_path
end
end
** Interest Model**
class Interest < ActiveRecord::Base
searchkick
include Tire::Model::Search
include Tire::Model::Callbacks
def self.search(search)
if search
where('name LIKE ?', "%#{search}")
else
scoped
end
end
end
Subject model
class Subject < ActiveRecord::Base
has_many :posts
has_many :users, through: :posts
validates_presence_of :name
validates_uniqueness_of :name
end
Subject_controller
class SubjectController < ApplicationController
def index
#subject = Subject.all.includes(:posts => [:user])
#interests_array = []
if current_user.interests != "null"
JSON.parse(current_user.interests).each do |f|
if Subject.find(f)
#interests_array.push(Subject.find(f))
end
end
end
end
end
Got it, there was no need for nested forms. I was also working with the wrong model.

How to show a notice after submitting a simple form

I'm using Simple Form and Ajax and I want to show a notice(it can be flash) something like "Successfully Submitted", after successful submission of a form. How can I achieve this?
This my controller:
def create
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.user_id = current_user.id
#order.save
session[:order_id] = #order.id
end
and my form in views:
<%= form_for OrderItem.new, html: {class: "add-to-cart"}, remote: true do |f| %>
<div class="input-group">
<%= f.hidden_field :quantity, value: 1, min: 1 %>
<div class="input-group-btn">
<%= f.hidden_field :product_id, value: product.id %>
<%= f.submit "Add to Cart", data: { confirm: 'Are you sure?'}, class: "btn btn-default black-background white" %>
</div>
</div>
<% end %>
</div>
You can do it responding to a javascript request when form is submitted.
Controller:
# app/controllers/mycontroller.rb
def create
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.user_id = current_user.id
#order.save
session[:order_id] = #order.id
respond_to do |format|
format.js { flash[:notice] = "Created order" }
end
end
And then create a view to show the message:
# app/mycontroller/create.js.erb
<% flash.each do |key, value| %>
$('.add-to-cart').append('<%= j content_tag :div, value, class: "flash #{key}" %>')
<% end %>
It should work!
You can use bootstrap alert to achieve it.
Controller:
def create
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.user_id = current_user.id
if #order.save
flash[:success] = "Successfully Submitted"
end
session[:order_id] = #order.id
end
You can display that flash message wherever you want to using this
View:
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

Create.js.erb Javascript asking for partial

I'm trying to get my my create action to work in my rails app. When I have these two lines
$('#pit_form').remove(); //remove form
$('#new_link').show(); //show new link again
it functions properly, and removes the form plus adds the link back, but I can't show the new pit with my escape. I've tried a few things but it always gives me this error
ActionView::Template::Error (Missing partial pits/_pit with {:locale=>[:en], :formats=>[:js, :html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in:
* "/Users/markhustad/projects/fire_2/app/views"
* "/usr/local/rvm/gems/ruby-2.1.1/gems/devise-3.2.4/app/views"
):
1: $('#pit_form').remove(); //remove form
2: $('#new_link').show(); //show new link again
3: $('#pit_index').append(<%= j (render(#pit)) %>);
app/views/pits/create.js.erb:3:in `_app_views_pits_create_js_erb___3365989432298988625_2214355520'
app/controllers/pits_controller.rb:20:in `create'
when I add this line or something similar(not sure why I need _pit partial)
$('#pit_index').append(<%= j (render(#pit)) %>);
My pits controller currently
class PitsController < ApplicationController
before_action :current_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def new
#pit = Pit.new
end
def index
#pit = Pit.all
#user = User.find_by(params[:id])
#pits = Pit.paginate(:page => params[:page]).order('created_at DESC').group_by { |pit| pit.created_at.strftime("%B %Y") }
end
def create
#pit = current_user.pits.create(pit_params)
respond_to do |format|
format.html { redirect_to pits_path}
format.js
end
end
def show
#pit = Pit.find(params[:id])
end
def edit
end
def update
#pit = Pit.find(pit_params[:id])
if #pit.update_attributes(pit_params)
redirect_to #pit
end
end
def destroy
#pit = Pit.find(params[:id])
#pit.destroy
end
def upvote
#pit = Pit.find(params[:pit_id])
#pit.upvote_from current_user
redirect_to pit_path(#pit)
end
def downvote
#pit = Pit.find(params[:pit_id])
#pit.downvote_from current_user
redirect_to pit_path(#pit)
end
private
def correct_user
#pit = current_user.pits.find_by_id(params[:id])
redirect_to root_path if #pit.nil?
end
def pit_params
params.require(:pit).permit(:topic, :summary, :image, :video_url, :author, :user_id)
end
end
Pits index
<div class = "container list-pits">
<%= link_to "Add New Pit", new_pit_path, id: "new_link", remote: true, class: "btn btn-default" %>
<br>
<br>
<% #pit.each do |pit| %>
<div class = "container">
<div class = "well", id = "pit_index">
<h3 id="pit-title"><%= link_to pit.topic, pit_path(pit) %></h3>
<p>by <%= link_to pit.author, '#' %></p>
<br>
<p><%= pit.summary %></p>
<p>Replies (<%= pit.comments.count %>)</p>
<br>
<p>Pit Created by: <%= link_to pit.user.name, pit.user %> on <%= pit.created_at %></p>
<%= link_to "View Pit", pit_path(pit), class: "btn btn-primary" %>
<%= link_to "Delete Pit", pit_path(pit), remote: true, method: :delete, data: { confirm: 'Are you sure?' } %>
</div>
</div>
<% end %>
</div>
_form in Pits
<div class="container new-pit">
<%= render 'devise/shared/error_messages', obj: #pit %>
<%= form_for #pit, remote: true, :html => {:multipart => true} do |f| %>
<div class = "form-horizontal", id = "pit_form">
<div class="form-group">
<%= f.label :topic, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.text_field :topic, class: "form-control", autofocus: true %>
</div>
</div>
<div class="form-group">
<%= f.label :author, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.text_field :author, class: "form-control", :placeholder => "Enter name of book author, lecturer, or presenter" %>
</div>
</div>
<div class="form-group">
<%= f.label :summary, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.text_area :summary, class: "form-control", :placeholder => "Present an argument for or against the material presented and state why you think it is accurate or not" %>
</div>
</div>
<div class="form-group">
<%= f.label :image, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.file_field :image, class: "form-control" %>
</div>
</div>
<div class="form-group">
<%= f.label :video, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.text_field :video_url, class: "form-control", :placeholder => "example: //www.youtube.com/embed/hBHYdK9xtYs (video is optional)" %>
<p class = "youtube-instruction">Must use the shared -> embed option</p>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<%= f.submit "Start Pit", class: "btn btn-primary" %>
</div>
</div>
</div>
</div>
</div>
<% end %>
I know I'm close here but need a bit more direction. Thanks.
You need two things. First, a pit.js.erb file that has:
$('#pit_form').remove();
$('#new_link').show();
$('#pit_index').append(<%= j (render(#pit)) %>);
I think you already have this.
You also need a _pit.html.erb file under the /pits folder that describes the layout of individual pit items on the pit index.
The order of operations goes like this:
Send a create request via Ajax. On forms, this is typically done with the remote: true option, which uses Unobstrusive JavaScript.
Process the response in the controller's create action using format.js. By default, this renders the .js.erb file that corresponds to the action name (i.e. create.js.erb).
Inside this js.erb file, you can also embed more render commands. In your specific case, you are trying to render a _pit.html.erb or a _pit.js.erb template, both of which are missing.
If you already have the pit template in a specific folder, you can specify it in the render action, like this:
$('#pit_index').append("<%= j (render 'myfolder/pit') %>");
I hope this helps.

Categories