-1

I have two forms on my index page, one is used for bulk_action and other one is used for filtering the data on page. When I submit my filter form, it automatically submits to the URL of above form. How can I tackle this? I want both forms location at the existing place so writing filter form above bulk_action form is not an option.

Index Page

<%= form_with url: bulk_action_salaries_path({}), local: true, method: :get do |form| %>
  <div class="row">
    <div class="col-12 ml-auto text-right">
      <%= link_to 'New Salary', new_salary_path, class: "btn btn-success btn-round" %>
      <%= form.submit 'Bulk Print', name: "bulk_print", class: "btn btn-info btn-round" %>
      <%= form.submit "Bulk Mail", name: "bulk_mail", class: "btn btn-round" %>
      <%= form.submit "Bulk Pay", name: "bulk_pay", class: "btn btn-info btn-round" %>
    </div>
  </div>
  <div class="row">
    <div class="col-md-12">
      <div class="card">
        <div class="card-body">
          <%= form_with url: salaries_path, method: :post, local: true, :html => {:id=>'form_id'} do |form| %>
            <div class="row">
              <div class="col-3">
                <%= form.select :employee_id, options_for_select(@employees.collect { |e| [e.name, e.id] }, params[:employee_id]), { :prompt => "Select Employee" }, class: 'form-control' %>
              </div>
              <div class="col-3">
                <%= form.select :month_year, options_for_select(month_years, params[:month_year]), { :prompt => "Select Month and Year" }, class: 'form-control' %>
              </div>
              <div class="col-3">
                <%= form.select :status, options_for_select({'Paid' => true, 'Unpaid' => false}, params[:status]), { :prompt => "Select Status" }, class: 'form-control' %>
              </div>
              <div class="col-3 text-right">
                <%= form.submit "Filter", :class => 'btn btn-round' %>
              </div>
            </div>
          <% end %>
        </div>
      </div>
    </div>
  </div>
  <div class="row">
    <div class="col-md-12">
      <div class="card">
        <div class="card-body">
          <div class="table-responsive">
            <table class="table">
              <thead>
              <tr>
                <th><input type="checkbox" id="select_all"></th>
                <th>Employee Name</th>
                <th>Salary Slip ID</th>
                <th>Month/Year</th>
                <th>Salary Paydate</th>
                <th>Paid?</th>
                <th></th>
              </tr>
              </thead>
              <tbody>
              <% @salaries.each do |salary| %>
                <tr>
                  <td><%= check_box_tag 'ids[]', salary.id %></td>
                  <td><%= link_to salary.employee.name, employee_path(salary.employee_id) %></td>
                  <td><%= link_to salary.id, salary_path(salary) %></td>
                  <td><%= salary.salary_month.value + '/' + salary.salary_year %></td>
                  <td><%= format_date(salary.salary_paydate) %></td>
                  <td><%= badge_active(salary.is_paid) %></td>
                  <td class="td-actions">
                    <%= link_to '<i class="material-icons">search</i>'.html_safe, salary, class: 'btn btn-success btn-round', title: 'Show Salary' %>
                    <%= link_to '<i class="material-icons">print</i>'.html_safe, salary_slip_path(salary), :target => '_blank', class: 'btn btn-info btn-round', title: 'Print Salary Slip' %>
                    <%= link_to '<i class="material-icons">email</i>'.html_safe, salary_slip_mail_salaries_path(:id => salary.id), class: 'btn btn-round', title: 'Send Salary Slip' %>
                    <%= link_to toggle_button(salary.is_paid), salary_path(salary), class: 'btn btn-info btn-round',
                                method: :patch, data: {confirm: 'Are you sure?'}, title: 'Pay/Unpay' %>
                  </td>
                </tr>
              <% end %>
              </tbody>
            </table>
          </div>
          <div class="row">
            <div class="col-12 ">
              <%= paginate @salaries %>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
<% end %>
  • 1
    I don't get why you are nesting these forms. What is this supposed to achieve? Just end the first form. Then start the second. – idmean Jan 21 '21 at 14:19
  • The bulk_action forms sends the ids[] from checkbox tag which is present before each record, while the filter form must be present below submit buttons on the page. that is why. – Ibrahim Kamal Jan 21 '21 at 14:29

2 Answers2

2

You cannot nest forms but what you can do is have elements outside a form tag and use the form html attribute to connect them. https://www.w3schools.com/html/html_form_attributes_form.asp

That way you can have a the bulk form not wrapping the whole thing but still connect the checkboxes to the bulk form.

Just close the bulk form before the divs start and add a form attribute with the bulk form id as the value for the check_box_tag.

arieljuod
  • 15,460
  • 2
  • 25
  • 36
0

Nested forms are not valid in any HTML version. Since its not permitted the behavior is not standardized and hitting the submit button on the "inner" form may cause either the inner or outer form to be submitted. Some browsers will just go into slop mode and drop the inner form tag completely.

You of course need to remove the nested call to form_with unless you want to deal with that extremely buggy behavior.

What you can do instead is use javascript to create a form dynamically when the user clicks the button on the "nested form":

document.addEventListener((event)=>{
  if (!event.target.matches('.my_special_button')) return;
  event.preventDefault();
  let dynamicForm = document.createElement('form');
  // todo use a more specific selector
  let form = document.querySelector('form');
  let makeInput = (name, value) => {
    dynamicForm.insertAdjacentHTML(
      'beforeend', 
      `<input name="${name}" value="${value}"/>`
    );
  };
  // Setup our dynamically created form
  dynamicForm.path = '/path/to/somewhere';
  dynamicForm.method = 'POST';
  dynamicForm.style.display = 'none';
  // Add the Rails anti-csrf token
  makeInput(Rails.csrfToken(), Rails.csrfParam());
  // todo copy the values from the existing form
  makeInput('employee_id', form.elements['employee_id'].value);
  // Add a submit button and click it as .submit();
  // does not fire the submit event
  dynamicForm.insertAdjacentHTML('beforeend', '<input type="submit"/>');
  document.body.appendChild(dynamicForm);
  dynamicForm.querySelector('[type="submit"]').click();
});
max
  • 96,212
  • 14
  • 104
  • 165