How to use Ajax for nested form on active admin rails - ruby-on-rails

Please help me out with this problem. I am new on rails
I am trying to use the ajax method to get values on the active admin nested form. But only the top of the form gets dynamic values. After changing values on the top one form on the nested form then ajax makes a request then it changes the values(i.e. update the form values).
But after adding a new buy line item then ajax makes a request but doesn't update the values on the form i.e.same values for every new buy line item.
my js code
app/assets/javascripts/buys.js
// for nested buyline items of buys resources
$('.lineItem').change(function () {
$.ajax({
url: '/admin/get_buys_buy_line_item',
type: 'GET',
dataType: "json",
data: {
product_id: $('.lineBuyProduct').val(),
buy_quantity: $(".lineBuyQuantity").val(),
buy_quantity_unit: $(".lineBuyUnit").val(),
buy_price: $(".lineBuyAmount").val(),
},
success: (data) => {
alert(data);
// if (data == null) {
// document.getElementById('lineQuantity').value = ' ';
// document.getElementById('lineAmount').value = ' ';
// }
// else {
// document.getElementsByClassName("linebuyQuantity").value = data['1'];
// document.getElementsByClassName('linebuyAmount').value = data[0];
// console.log("Dynamic select OK!")
// }
}
});
});
});
My active admin forms
f.inputs 'Line Items', class: "lineItem" do
table do
thead do
tr do
th 'S.N', class: 'form-table__col'
th 'Product', class: 'form-table__col'
th 'Quantity', class: 'form-table__col'
th 'Unit', class: 'form-table__col'
th 'Amount', class: 'form-table__col'
th 'Expiry Date', class: 'form-table__col'
th 'Destroy', class: 'form-table__col'
end
end
end
f.has_many :buy_line_items, new_record: 'Add Buy Line Item', heading: false, allow_destroy: true do |x|
x.input :sn, label: false
x.input :product, label: false, as: :select2, collection: Product.drop_down_options,
input_html: { required: true, class: 'lineBuyProduct' }
x.input :buy_quantity, label: false, input_html: { required: true, class: 'lineBuyQuantity' }
x.input :buy_quantity_unit, label: false, collection: Unit.all.pluck(:name),
input_html: { class: 'lineBuyUnit' }
x.input :buy_price, label: false,
input_html: { required: true, class: 'lineBuyAmount' }
x.input :expiry_date, as: :date_picker, input_html: { style: 'width:auto' }, label: false
end
end
Some of my screenshots of how my program behaves and what my expectations are
In this image my first selection of product on buy line iteme then on request we can see json are renderd
But in this image after selecting another product again same json data are rendered that means doesn't update with different respective product values
And there are also some problems on active admin blaze theme, after adding class on form then it changes to legend color and add item button colors to light brown
This picture is about after removing custom class name lineItem.But, after removing this class then ajax doesn't hit for that form
Might be i faced such problem due to nested form's class on active admin.While i used default class of those form from inspect then it even doesn't hit to that form for ajax request.
So, please expert team help me to slove this problem.

If you want to send the data to create a new entity(record), you need to use the http POST method: https://api.jquery.com/jquery.post/
The GET request it just retrieves an already existing entity. Probably that's why you can read some data after an ajax call, but it's not updated.
More about the differences between GET and POST methods:
When should I use GET or POST method? What's the difference between them?
If this fix doesn't work, check the rails logs.

I have never used anything related to Ruby, and I'm not completely sure if I understood your question. I'm assuming you have problems with the AJAX behavior for new items in the list. Considering that, I think your problem could be related to attaching the "change" event to the new items. I think there might be many different other ways to get the behavior you want, but I will just elaborate on your solution using JS.
From the activeAdmin's code you can see they trigger an event called "has_many_add:after" when a new item was successfully added to its parent. Considering that, I would try changing your Javascript code to:
// for nested buyline items of buys resources
$("#buy_line_items").on("has_many_add:after", function(event, fieldset, parent){
fieldset.change(function () {
$.ajax({
url: '/admin/get_buys_buy_line_item',
type: 'GET',
dataType: "json",
data: {
product_id: $(this).find('.lineBuyProduct').val(),
buy_quantity: $(this).find(".lineBuyQuantity").val(),
buy_quantity_unit: $(this).find(".lineBuyUnit").val(),
buy_price: $(this).find(".lineBuyAmount").val()
},
success: (data) => {
alert(data);
// if (data == null) {
// document.getElementById('lineQuantity').value = ' ';
// document.getElementById('lineAmount').value = ' ';
// }
// else {
// document.getElementsByClassName("linebuyQuantity").value = data['1'];
// document.getElementsByClassName('linebuyAmount').value = data[0];
// console.log("Dynamic select OK!")
// }
}
});
});
});
I'm very confident that this will work, mainly because I don't have how to try it (if you give me the generated HTML maybe it'd help me). You can try it and reach back to see if it worked or if any adjustment is required.
You might want to change your commented lines accordingly. Also about the identifier "#buy_line_items" I'm not sure if it exists in the rendered HTML so you might need to adjust that as well.

Related

How to include both a class and data attributes in a form select in Rails 7?

In my Rails 7 app, I am using Stimulus and need to add data attributes to a form select to link it to a JavaScript controller. I am also trying to format the field (not the options) with a class.
Here is the form select element:
<%= f.select(:repeat, Batch.repeats, {class: "class_name"}, { data: { batch_repeat_target: "input", action: "change->batch-repeat#toggle" }}) %>
The above code results in the data attributes being applied to the select field, but leaving the class out.
I also tried to flip the class and the data attributes, as follows:
<%= f.select(:repeat, Batch.repeats, { data: { batch_repeat_target: "input", action: "change->batch-repeat#toggle" }}, { class: "class_name" }) %>
The result was the opposite of the first approach: this time, the field was styled per the class, but the data attributes were not associated with the select element.
Per this question, I became aware that:
select helper takes two options hashes, one for select, and the
second for html options. So all you need is to give default empty
options as first param after list of items and then add your class to
html_options.
From there, I thought that I needed to include both the class and the data attributes in the first option hash, and leave the second one empty, along the following lines:
<%= f.select(:repeat, Batch.repeats, { { class: "class_name" }, { data: { batch_repeat_target: "input", action: "change->batch-repeat#toggle" }}}, {}) %>
However, the above revised code resulted in an ActionView::SyntaxErrorInTemplate in BatchesController#new error.
In yet another attempt, I tried to shuffle things around, leaving the first option hash empty and including both the class and the data attributes in the second one, as follows:
<%= f.select(:repeat, Batch.repeats, {}, { { class: "class_name" }, { data: { batch_repeat_target: "input", action: "change->batch-repeat#toggle" }}}) %>
That revision also resulted in an ActionView::SyntaxErrorInTemplate in BatchesController#new error.
The question referenced above is more than 11 years old and: is there a different convention now, particularly in Rails 7, with regards to form select elements? How can I both include a class and data attributes here?
A hash looks like this: { key1: value1, key2: value2 }
So, take your attempted code: { { class: "class_name" } } - the inner hash there is { class: "class_name" } so let's assign that to a variable: value1 = { class: "class_name" } and then let's use that variable in your attempted code: { value1 }
See how that's not a hash, there's no key for the value. A hash must always have keys and values. This is why you're getting the syntax errors in your second two examples.
So let me go through your other two attempts:
<%= f.select(:repeat, Batch.repeats, {class: "class_name"}, { data: { batch_repeat_target: "input", action: "change->batch-repeat#toggle" }}) %>
So here, :repeat is the first argument, Batch.repeats is the second, the third argument (which is supposed to be the select options) is the hash with a single key/value pair: { class: "class_name" }, and the fourth argument (which is supposed to be the html options) is the hash with a single key/value pair: { data: { the: data, hash: here }
Your second attempt:
<%= f.select(:repeat, Batch.repeats, { data: { batch_repeat_target: "input", action: "change->batch-repeat#toggle" }}, { class: "class_name" }) %>
First two arguments the same, the third argument (the select options) is now the hash with the :data key/value, and the fourth argument (the html options) is now the hash with the single :class key/value.
What you want to do is provide an empty hash for the third argument (the select options), and then a hash with TWO key/value pairs for the fourth argument (the html options):
{ class: "class_name", data: { the: data, hash: here } }
(Note, I intentionally didn't feed you the full answer, because I feel that you're the sort of person who will benefit from discovering it on your own, but say so if I've misjudged, happy to edit this answer if you need it)

Using same form as local and remote both in rails for filtering purposes

Scenario:
I have a form that does some filtering. For the sake of simplicity, let's assume I have a form that has three input options:
<%= form_tag(some_path,method: :get) do %>
#..checkboxes for option 1
#..radiobuttons for option 2
#..checkboxes for option 3
<%= submit_tag "submit" %>
<% end %>
<p>You have a total of: COUNT results.</p>
Required Output:
What I want is the functionality when a user clicks on any checkbox or radio button, (essentially a change in any input field), by ajax request should be generated to a path that returns a COUNT of total results, and I will update the COUNT inside p tag with that returned count number.
And when the user clicks on submit button, the default GET request should be generated.
I added this script for ajax request, and it is working perfectly fine.
<script>
$(".option").change(function(){
var type = $("input[name='type[]']:checked").map(function () {
return this.value;
}).get();
$.ajax({
url: "/gre",
type: "put",
dataType: "json",
data: {custom: 'true',type: type},
success: function (response) {
var count = response["count"];
$('#count').html('Your session will have a total of '+ count + ' questions.');
}
});
});

Load model association dynamically in Rails form before submit

So I'm not sure how to achieve this or if it's even possible and having a tough time searching for similar examples/answers. But is there a way to dynamically load a model association within a form before you submit it, like without knowing what the instance variable would be ahead of time?
I have models as follows:
Property
has_many :owners, inverse_of: :property
has_many :orders
Owners
belongs_to :property
Orders
belongs_to :property
has_many :owners, through: :property
In my orders.new form I have select boxes to choose a property to create the association for the new order (using Select2 plugin):
<%= bootstrap_form_for(#orders, ...) do |f| %>
...
<%= f.select :property_id, options_from_collection_for_select(Property.all, "id", "full_address"),
{ label: "Property:", include_blank: true }, { id: "orderPropSelect", data: { placeholder: "Select a Property"} } %>
...
<% end %>
So when someone SELECTS a property for the new order, is there a way I can load what owners that property already has associated to it before the form is submitted? Even just being able to see what owners are already there is okay (being able to edit them would be even better but I realize thats probably more complex)
The following code of course doesn't work but looking for something along the lines of:
<%= f.static_control label: "Property Owners" do %>
<% :property_id.owners.each do |owner| %>
<%= owner.name %>
<% end %>
<% end %>
I've tried variations of fields_for but I don't know how to tell the nested fields to be based off what is chosen in the above select (of course loading different owners based on what property is chosen. The errors that I get are undefined method owners for nil:NilClass which are appropriate because I know I'm not telling rails where to look correctly.
So is this possible and if so, how would I achieve this?
(I use bootstrap forms gem incase anyone is wondering about form syntax. I also have cocoon loaded for other forms so if there's a way to use that then I'm not opposed.)
Update Working code, slightly modified for extraneous conditions.
$("#orderPropSelect").off().on('change', function() {
var id = $(this).val();
console.log(id);
if (id !== '') {
$.ajax({
url: '/properties/' + id + '/owners',
dataType: "json",
success: function (data) {
owners_html = '';
$.each(data['owners'], function () {
owners_html += '<p>' + this + '</p>';
});
if (owners_html === '') {
$("#propOwnersShow").html('<p>No owner added yet.</p>');
} else {
$("#propOwnersShow").html($(owners_html));
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
} else {
$('#propOwnersShow').html('Please select a Property below to view its associated owners.')
}
});
You need to make sure it matches your underlying routes & stuff, and probably handle the case where there aren't owners, so to hide the owners div. If you want to do more complex stuff you could instead of .pluck build a better array/hash with id's that you can then also use to build elements that you can interact with (e.g. remove them from the list)
# your property controller
before_action :allowed, only [:owners]
def owners
owners = Property.find(params[:id]).owners.pluck(:name)
respond_to |format|
format.json { render json: { owners: owners, success: true } }
end
end
def allowed
# logic to define if this user is allowed to request this data, if you have devise, it could be
user_signed_in? && request.xhr?
end
# your routes.rb
get "properties/:id/owners", to: "properties#owners", as: "property_owners"
# or if you have resources
resources :properties do
member do
get :owners
end
end
# js file
$("#property_id").off().on('change', function() {
var id = $(this).val();
$.ajax({
url: '/properties/'+id+'/owners',
dataType: "json",
success: function(data) {
owners_html = '';
$.each(data['owners'], function() {
owners_html += '<p>'+this+'</p>';
});
$("selector_where_you_want_to_show_owners").html($(owners_html));
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
UPDATE:
You can prevent the issue with no Owners by using find_by instead and making sure that you always return an [], this way simplifying the logic on the front-side as well.
# on your controller instead use:
def owners
# .find_by will return "nil" if no properties are found, which
# then we can leverage with .try. .try will "try" calling those
# chained methods without raising an error. It usually returns
# "nil" if the parent is "nil" but .try with .pluck though will
# always return an [] no matter what the ancestors returned.
owners = Property.find_by(id: params[:id]).try(:owners).try(:pluck, :name]
# actually looking at your relationships it seems you could instead
# .where, since that will return an empty array, like so:
# owners = Owner.where(property_id: params[:id]).pluck(:name)
# this only does one database query where the other one does 2
# here we're simply making sure we add something to the array so
# that then on the front-end you can always deal with an array
# without worrying with the "no properties found". .blank? will
# return "true" if the value is nil, empty string, empty hash or
# empty array which works fine for this. So if there's no value in
# the array we add the "no owners" string.
owners << 'No owner added yet.' if owners.blank?
respond_to |format|
format.json { render json: { owners: owners, success: true } }
end
end
# And then since you'll always be returning an array you no longer
# have to worry about an empty array and your ajax function will
# look like this:
$.ajax({
url: '/properties/' + id + '/owners',
dataType: "json",
success: function (data) {
owners_html = '';
$.each(data['owners'], function () {
owners_html += '<p>' + this + '</p>';
});
$("#propOwnersShow").html($(owners_html));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
Hope this helps

Rails check_box_tag - get all the values checked in view(haml) file

So I have multiple checkboxes on my page. I collect all of them like shown in the code below. I would like to access the values of the array before passing it on to the controller
= check_box_tag "names[]", ob.name, false, class: 'cbx'
I am able to pass them with my older code
%fieldset.actions
= form.submit "Upgrade", :class => 'button'
Logs:
Processing by SomeController#create as HTML Parameters:
{"utf8"=>"✓",
"names"=>["ron", "jacob"], "commit"=>"NameButton"}
Ok. So i would like to access all values in my haml files. Is there a way before i submit my form, I can access which checkboxes are selected.
I would like to pass the names[] to my controller as a parameter.
=link_to script_name1, { :action => 'create', :names => 'dontknowhowtopassnames[]' }, :method => :post
Rails version - 3.2.17
You can do that using Javascript.
The exact implementation depends on what exactly you want to do with those values, but you could, for example, use the change event to track and maintain an array of all checked values :
/*
* should properly init this array if some of your checkboxes are already
* checked when the form is loaded
*/
var names = [];
$(document).ready(function() {
$(document).on('change', '.cbx', function() {
var name = $(this).val();
var idx = names.indexOf(name);
if ($(this).prop('checked') && idx === -1) {
names.push(name);
} else if (!($(this).prop('checked')) && idx >= 0) {
names.splice(idx, 1);
}
});
});
Updated with complementary answer:
To submit a form with a link instead of a button:
In your view, replace
%fieldset.actions
= form.submit "Upgrade", :class => 'button'
with
= link_to "Submit", "#", :class => 'submit_link'
Then in your Javascript, add the following inside the $(document).ready body:
$(document).on('click', 'a.submit_link', function(e) {
$(this).closest('form').submit();
e.preventDefault();
});
And that's it. You're making your life very complicated by trying to serialize the form data on your own while the form's submit() method can take care of it for you. You can serialize data on your own, and it's sometimes useful, for instance when dealing with AJAX, but in your case the default submit() is perfect for the job.

Cocoon add association, how to limit number of associations

I am creating a system that stores cards using Ruby/Rails/HAML - In this case there is a Card class that has many Colours (this is also a class). When creating and editing a card I am using the Cocoon gem to allow me to dynamically add colour associations.
The problem I am having is that in the Card model, a card is limited to having a maximum of 5 colours. Yet the interface allows adding of unlimited colours resulting in an error.
Is there a way in Cocoon to limit the number of associations that can be added to a form, so that this limit is not exceeded?
This is the code for a form to add/edit a Card
= simple_form_for #card, multipart: true do |c|
= c.input :name, label: "Name of the card"
= c.input :cost, label: "Cost of the card"
#colours
= c.simple_fields_for :colours do |colour|
= render "colour_fields", f: colour
.links
= link_to_add_association 'add colour', c, :colours
And this is the colour_fields form
.nested-fields
= f.input :value, as: :select, collection: Colour::VALUES, selected: f.object.value, include_blank: false
= link_to_remove_association "remove colour", f
Thanks in advance.
I would use javascript for this. You can bind to an event that is triggered upon insert of a new item: on this event count how many items there are, and hide the link if needed.
Likewise, when loading the page do the same.
So in code that would look like:
$(function() {
function check_to_hide_or_show_add_link() {
if ($('#colours .nested-fields:visible').length == 5) {
$('#colours .links a').hide();
} else {
$('#colours .links a').show();
}
}
$('#colours').on('cocoon:after-insert', function() {
check_to_hide_or_show_add_link();
});
$('#colours').on('cocoon:after-remove', function() {
check_to_hide_or_show_add_link();
});
check_to_hide_or_show_add_link();
});
Something like this should work. Note this code is not tested :)
Hope this helps.
I had a similar task to do and what I ended up doing was putting an onclick event on the "Add" link. This onclick event will execute before the code to add the fields is executed.
In your case, the code would look something like the following:
= link_to_add_association 'add colour', c, :colours, class: 'add-colour-link'
Then in your JavaScript (CoffeeScript in this case):
$('.add-colour-link').on 'click', (e) ->
if $('#colours .nested-fields:visible').size() < 5
return true #continue the execution
else
#maybe display the modal to the user that only a maximum of 5 is allowed
return false #prevent further execution

Resources