What is the Rails convention for sending form data to a URL dependent on form value? - ruby-on-rails

I would like a form submitted at the url
/index/fruit
to submit the form data to
/index/:identifier
where :identifier is determined by a value of the form
What is the rails convention in this instance?
Is there a way to achieve this without a controller level redirect or javascript-updating the submit URL?
routes.rb
match 'smasher(/:action(/:id))', :controller => "customcontroller", :as => :smasher, :defaults => { :action => :index, :id => :fruit }
index.html.erb
<%= semantic_form_for :d, :url => smasher_path, :html => { :method => :get } do |f| %>
... form data ...
<%= f.input :identifier, :as => :hidden %>
<% end %>
My current implementation is similar to this answer

There's isn't really a "convention" for this, but rather one of those things where there's more than one way to do it.
One way that you could do it is still send the form to one and only one action within the controller, but then delegate in the controller which action to go to, like this:
def smasher
if params[:identifier] == 'this'
smash_this!
else
smash_that!
end
end
def smash_this!
# code goes here
end
def smash_that!
# code goes here
end

Heres the javascript version (though technically its all on an erb html template), if you're feeling up to it.
<%= f.input :identifier, :as => :hidden, :onchange => "$(this).setAction()" %>
<script>
// While you can this script block here within your erb template
// but best practice says you should have it included somehow within `<head></head>`
$(function() {
//create a method on the Jquery Object to adjust the action of the form
$.fn.setAction = function() {
var form = $(this).parents('form').first();
var action = form.attr('action')
form.attr('action', action.substr( 0, action.lastIndexOf('/')+1 ) + $(this).val());
}
});
</script>
Heres the pure javascript version:
$(function() {
//create a method on the Jquery Object to adjust the action of the form
$.fn.setAction = function() {
var form = $(this).parents('form').first();
var action = form.attr('action')
form.attr('action', action.substr( 0, action.lastIndexOf('/')+1 ) + $(this).val());
}
//we gotta bind the onchange here
$('input[name="identifier"]').change($.fn.setAction);
});

Related

Passing local variable to a partial in rails 2.x

I have the following method in my controller:
def update_fees_collection_dates_voucher
#batch = Batch.find(params[:batch_id])
#dates = #batch.fee_collection_dates
render :update do |page|
page.replace_html "fees_collection_dates", :partial =>"fees_collection_dates_voucher"
end
end
the method calls the following partial in _fees_collection_dates_voucher.html.erb file:
<%= select :fees_submission, :dates_id, #dates.map { |e| [e.full_name, e.id]},{:prompt => "#{t('select_fee_collection_date')}"},{:onChange => "#{remote_function( :url => {:action => "load_fees_voucher"},:with => "'date='+value+'&batch_id='+#batch.id") }"} %>
The partial makes a remote call to the load_fees_voucher method when a selection list value is selected or changed. However, I'm unable to pass the information in the #batch instance variable (from my original method) to the remote method via the partial. If I change the #batch.id to a hard-coded value in the last line (under :with) the code runs fine but not in the current format. Is there a different procedure to access instance variables in partials ? Thanks!
You can use the same instance variable in the partials.
Try this,
<%= select :fees_submission, :dates_id, #dates.map { |e| [e.full_name, e.id]},{:prompt => "#{t('select_fee_collection_date')}"},{:onChange => "#{remote_function( :url => {:action => "load_fees_voucher"},:with => "'date='+value+'&batch_id='+#{#batch.id}") }"} %>

Adding a value-dependent data attribute to a simple_form checkbox collection

I'm generating a list of checkboxes for a single collection like so:
= f.input :parts, as:check_boxes, collection: #parts_list
I want some checkboxes in the collection to disappear/reappear depending on the value of a select widget higher up in the form. (e.g. choosing "Tracker Robot" from the Robot select means that the "Legs" part checkbox disappears and the "Wheels" checkbox appears, etc.)
What I'd like to do is attach a computed data attribute to each individual Part checkbox, with the attribute value listing the Robots that can use that Part; then some JS will do the work of hiding/showing the checkboxes. However, I don't know how I can generate those data attributes using simple_form.
I would normally create a custom "parts" input, but there seems to be a problem with making custom collection inputs; it looks for a named method (collection_parts) inside form_builder.rb, which won't exist, and if I try and extend the FormBuilder it sends me down a major rabbit hole.
I could write some JS to load the data attrs into the generated HTML, but then I have to generate custom JS based on my Rails data, and that feels like the wrong way to do it.
Let's assume that the form is for Order model and you are changing the parts collection based on the value of a field called region.
Update the form view. Specify the id for form, region field and parts field.
= simple_form_for(#order, :html => { :id => "order-form"}) do |f|
= f.input :region, :wrapper_html => { :id => "order-form-region", |
"data-parts-url" => parts_orders_path(:id => #order.id, :region => #order.region)} |
= f.input :parts, as: check_boxes, collection: #parts_list, |
:wrapper_html => { id' => 'parts-check-box-list'} |
Add a new action called parts in the route.rb file.
resources :orders do
collection do
get :parts
end
end
Add the new action to your controller
class OrdersController < ApplicationController
# expects id and region as parameters
def parts
#order = params[:id].present? ? Order.find(params[:id]) : Order.new
#parts_list = Part.where(:region => params[:region])
end
end
Add a helper
def parts_collection(order, parts_list)
"".tap do |pc|
# to generate the markup for collection we need a dummy form
simple_form_for(order) do |f|
pc << f.input(:parts, as: check_boxes, collection: parts_list,
:wrapper_html => {:id => 'parts-check-box-list'})
end
end
end
Add a js view for the action (orders/parts.js.erb)
$('#parts-check-box-list').replaceWith('<%= j(parts_collection(#order, #parts_list)) %>');
Register data change event handlers for region field in your application.js
$(document).ready(function() {
$('#order-form').on("change", "#order-form-region", function () {
// Access the data-parts-url set in the region field to submit JS request
$.getScript($(this).attr('data-parts-url'));
});
});
I think you can do it like this:
= f.input :parts do
= f.collection_check_boxes :parts, #parts_list, :id, :to_s, item_wrapper_tag: :label, item_wrapper_class: :checkbox do |b|
- b.check_box(data: { YOUR DATA ATTRIBUTES HERE }) + b.text
this may be simpler.
Assumptions
#robots - an array containing the list of robots
#parts - a hash containing a list of parts for each robot
Sample Code
# controller
#robots = %w[tracker nontracker]
#parts = { tracker: %w[wheels lcd resistor], nontracker: %w[lcd resistor] }
# view
= f.input :robots, as: :select, collection: #robots, input_html: { id: 'robot-select' }
#parts-list
:javascript
var parts = #{#parts.to_json};
$(document).ready(function() {
$('#robot-select').change(function() {
$('#parts-list').html('');
$(parts[$(this).val()]).each(function(index, text) {
$('#parts-list').append('<input type="checkbox" value=' + text + '>' + text + '</input>')
})
})
})
you can see this working if you clone https://github.com/jvnill/simple_form_search_app and go to /robots
Some input options in SimpleForm accept a lambda that gets called for every item in a collection:
f.input :role_ids, :collection => (1..10).to_a,
:label_method => :to_i, :value_method => :to_i,
:as => :check_boxes, :required=> true,
:disabled => ->(item){ item.even? }
but input_html doesn't seem to be one of them.
The solution is probably to create a custom SimpleForm collection input that applies the data attributes itself. Not as flexible perhaps, but I think this is the only way to go for now.
There's a tutorial page on GitHub that should get you started.

Add form in sidebar

In my ActiveAdmin app, I need to have a form in the sidebar (only for the show method) that will show a single (for the moment) datepicker. Once user click on the datepicker, the whole page should reload taking into account the date selected (within the show controller).
Any idea on how to add this simple form in a sidebar ?
EDIT
I tried the following but I'm not sure how the form can target a dedicated controller other than the current model.
sidebar :history, :only => :show do
form do |f|
f.input :start_date, :as => :datepicker
end
end
How can a form in a sidebar can target another controller than the one of the current model ?
I presume you have a show controller and don't mean the show action.
make the view into a partial called "whatever" (I call it this)
So, your whatever.erb.html looks like this
<%= render "whatever" %>
If you use Jquery Ui datepicker, you can add a onSelect function
$(".date").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function() {
$('#range_form').submit();
}
}).attr( 'readOnly' , 'true' )
If you want a range add a form tag in your view with two date fields, else you just add one
<%= form_tag('/range', :id => "range_form", :remote => true) do -%>
<%= text_field_tag 'from', Date.now.strftime("%Y-%m-%d"),:class => "date"%>
<%= text_field_tag 'to', Date.now.strftime("%Y-%m-%d"), :class => "date"%>
<% end %>
For this you'll have to add a route in your routes.rb
match "/range/" => "show#todo_range", :as => :range
In your show controller
def range
time_range = ((Date.parse(params[:from]).midnight..Date.parse(params[:to]).midnight)
#whatever = Whatever.where(:date => time_range)
end
Then add a js view to handle the callback
$(".maindiv").empty().append(<%=j(render #whatever)%>)
I have not tested this exact code, so watch out for typos.
Good luck and comment if I need to edit

How to “dynamically add options” to 'form_for'?

I am using Ruby on Rails 3.2.2. In order to implement a "dynamic generated" AJAX style file upload form I would like to "dynamically add options" to the FormHelper#form_for statement if some conditions are meet. That is, at this time I am using code as-like the following (note that I am using the merge method in order to add options to the form_for method):
<%
if #article.is_true? && (#article.is_black? || && #article.is_new?)
form_options = {:multipart => true, :target => "from_target_name"}
else
form_options = {}
end
%>
<%= form_for(#article, :remote => true, :html => {:id => "form_css_id"}.merge(form_options)) do |form| %>
...
<% end %>
However, I think that the above code is too much hijacked.
Is there a better way to accomplish what I am making? For example, can I access from view templates some (unknown to me) instance variable named as-like #form and "work" on that so to change related options as well as I would like? Or, should I state a helper method somewhere? How do you advice to proceed?
BTW: Since the upload process is handled by using a HTML iframe, I am using the remotipart gem in order to implement the AJAX style file upload form - I don't know if this information could help someone...
This looks like a good candidate for a helper method. In your view:
<%= form_for(#article, :remote => true, :html => article_form_options(#article, :id => "form_css_id")) do |form| %>
...
<% end %>
In app/helpers/articles_helper.rb
module ArticlesHelper
def article_form_options(article, defaults = {})
extras = if article.is_true? && (article.is_black? || article.is_new?)
{ :multipart => true, :target => 'form_target_name' }
else
{}
end
defaults.merge(extras)
end
end
Helpers are a good place to keep logic that's too complex for a view but still related to the view.

using both :url and :function on observe_form

Is there a way to generate both a Javascript function call and an Ajax call in the same observe_form tag? For example, something like this:
<%= observe_form 'user_filter_form', :url => { :action => :process }, :function => :fix_fields %>
Thanks!!
Your best bet here is to dig into the actual JavaScript instead of relying on the helpers. The helpers can only get you so far. What you want is something along these lines:
<script type="text/javascript">
new Form.EventObserver('user_filter_form', function(element, value){
fix_fields();
new Ajax.Request("/YOUR_CONTROLLER/process");
}
</script>
However, if you really want to rely on the Rails helpers you can do something like:
<%= observe_form 'user_filter_form', :function => "fix_fields(); #{remote_function(:url => { :action => :process })}" %>
Use :before or :after options instead of :function, depending on whether you want your function called before of after the Ajax request.
See documentation of link_to_remote helper for common options that can be passed to all the Ajax helpers like observe_form

Resources