I want to implement model based dropdowns where in when value for one dropdown(model) is selected it serves the basis for selection to be in second dropdown.
And hence I am requiring the id from first dropdown to be used to apply filter for second dropdown.
<div class="study-search">
<%= form_with(url: show_subjects_path(:site_id), method: :get, html: {style: 'font-size:16px'}) do |f| %>
<%= f.label 'Select Study' %>
<%= f.select :id, Study.all.collect {|study| [study.title, study.id] }, {:include_blank => "--Choose--"}%>
<%= f.label 'Select Site' %>
<%= f.select :site_id, Site.where(study_id: :id).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
<div class="btn btn-sm", style="display: inline">
<%= f.submit "GO" %>
</div>
<% end %>
</div>
the :id used in the below snippet is not the correct way. Please suggest the proper way to do so.
<%= f.select :site_id, Site.where(study_id: :id).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
enter image description here
There are multiple ways to solve this with different levels of complexity. I'll start from the least complex (but probably clunky) to the most complex
The first two options will do a full page refresh which isn't as bad as it sounds, specially if you're using Turbo Drive (formerly known as Turbolinks).
The caveat with these two options is that the navigation will always be a Turbo visit navigation meaning that each time, you'll create a new page in the navigation stack. You can overcome this by overriding your form submission with Stimulus and making a Turbo GET request with a replace action. See the Turbo Drive docs for more on this.
Option 1: The simplest one
What you have there can already achieve what you expect if the user presses the Go button when they change the first select box. You'll just have to change the Site select to filter by the params sent by the form:
<%= f.select :site_id, Site.where(study_id: params[:site_id]).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
After the page renders again you'll have a new select box with the filtered data.
This approach is very clunky but it set's a great foundation for you to optimize it.
Option 2: Automate sending the form on change
Now if you add some Javascript, you'll be able to detect when the first select changes and submit the form when it does. For this, I suggest you use Stimulus JS
Check out the docs to learn more about Stimulus if you haven't used it to be able to follow the following
Create a Stimulus JS controller to submit the form when select changes
import { Controller } from "stimulus"
export default class extends Controller {
submit() {
// add some code that submits a form. An easy way to do it is to use Rails UJS to do it but it's no longer recommended by the community since it'll be deprecated soon.
Rails.fire(this.element, 'submit');
}
}
Add your stimulus controller to the form.
Note that the controller and action keywords are added to the form_with method and the first f.select method.
<div class="study-search">
<%= form_with(url: show_subjects_path(:site_id), method: :get, data: {controller: 'form'}, html: {style: 'font-size:16px'}) do |f| %>
<%= f.label 'Select Study' %>
<%= f.select :id, Study.all.collect {|study| [study.title, study.id] }, {:include_blank => "--Choose--"}, { data: {action: 'form#submit' }%>
<%= f.label 'Select Site' %>
<%= f.select :site_id, Site.where(study_id: params[:site_id]).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
<div class="btn btn-sm", style="display: inline">
<%= f.submit "GO" %>
</div>
<% end %>
</div>
This will essentially mimic the user clicking the Go button whenever the first select changes
Option 3: Use a Turbo Frame
If you're in the latest version of rails and you've included the hotwire-rails gem, you'll be able to leverage all the new technologies coming to Rails and the HTML over the wire techniques that come with it.
The newest versions of turbo now handle forms (as opposed to Turbolinks). Please refer to the Turbo Frames documentation for techniques on how to do this. With a turbo frame you're essentially doing the same thing as with options 1 and 2 which is sending a GET request and getting a full page back as a response BUT with the difference that with Turbo frames you'll be able to selectively replace parts of your DOM.
I'm not an expert yet on turbo frames so I won't post an exact example.
Option 4: Use Turbo Streams
This is the most complex option but is still relatively simple as far as your environment is setup with Action Cable. What Turbo Stream does is allow you to target an update to your DOM with 5 basic dom actions: append, prepend, replace, update and remove. These operations will be streamed back to the front end from the server using a web socket connection.
In your case you probably want to replace or update the select with the new list of options.
I'm not an expert yet on turbo streams so I won't post an exact example.
Option 5: Check out StimulusReflex
From StimulusReflex's site:
[Stimulus Reflex is] A new way to craft modern, reactive web interfaces with Ruby on Rails.
We extend the capabilities of both Rails and Stimulus by intercepting user interactions and passing them to Rails over real-time websockets. These interactions are processed by Reflex actions that change application state. The current page is quickly re-rendered and the changes are sent to the client using CableReady. The page is then morphed to reflect the new application state. This entire round-trip allows us to update the UI in 20-30ms without flicker or expensive page loads.
With Stimulus Reflex you get a very seamless experience to do these kinds of interactive pages but it requires an extra dependency. It's definitely worth a try but it's also a new dependency you'll bring in your code base (assuming you adopt the new Rails defaults coming in version 7 which will include Hotwire)
Option 6: 100% front-end
Using Stimulus JS you can potentially load all options from all sites when the page loads and filter using just javascript. It's doable but it's definitely more complex because you'll need to write a lot more javascript and your initial page load might be slow if you have too many sites to search from. I would not recommend this approach because it might add unnecessary complexity to your app but it is an option.
Final thoughts
Regardless of what you do, if you want for the form to be submitted when the user changes something on the front-end (like changing the value of a select field or typing something into a text field, or hovering or any other action that can be tracked in the front-end) you need to write a bit of javascript. I strongly suggest you check out StimulusJS which is part of Hotwire, the newest addition to the rails ecosystem for the front end which will be the default way of building your apps with the upcoming Rails 7 release.
I hope this gives you some pointers.
Some extra links to catch up on all things Hotwire (Stimulus, Turbo etc)
Go Rails has awesome resources on the subject
https://www.youtube.com/results?search_query=go+rails+stimulus
https://www.youtube.com/watch?v=AdktV7r2BQk
https://www.youtube.com/watch?v=Q7uOPVfZ3Go
Related
This is a process question on updating model values for an existing record. Say I have a user model. Users sign up with a name and email via devise authentication. Then I have two more forms on the site to collect additional inputs.
Form 1 stores payment data and uses the def update method. Let's say the update method has some stripe code.
Form 2 just asks for a users favorite color and wants it saved into the model. Can form 2 still use the update method with some if statement or do we need a custom route pointing to a method like def color.
I'm having trouble updating a model using form 2. And instead of posting details (done that. no solution yet) I wanted some idea on what the best process would be. It should be pretty simple to insert data into a model. I think I'm missing something basic.
Can form 2 still use the update method
Yes
Ajax
What you'll be looking for is ajax - Asynchronous Javascript & XML - a method of sending requests to your Rails server out of scope of your typical HTTP flow.
You have to remember Rails, by virtue of being built on HTTP, is stateless. This means that it has absolutely no "memory" unless, of course, you store data for future use.
The reason this is important is because when you send your "update" requests to your backend, it won't care if you've already sent a request; it will just take the data you send & blindly perform the tasks it wants:
#view
<%= form_for #user, method: :patch, remote: true, id: "form1" do |f| %>
<%= f.text_field :color %>
<%= f.submit %>
<% end %>
<%= form_for #user, method :patch, remote: true, id: "form2" do |f| %>
<%= f.text_field :other_input %>
<%= f.submit %>
<% end %>
You can read up more about Rails UJS here - both forms send the request to the update method, and will send different data. This will give you the ability to send the required data you need through two separate forms
--
Response
If you wanted to then capture the response from the forms, you'll be able to create the following Javascript:
#app/assets/javascripts/application.js
$(document).on("ajax:success", "#form1", function(status, data, xhr){
// something here
});
I am new to ruby on rails and working through the Rails Tutorial book and also Rails Programming from pragmatic studio. However for my project I need to find solutions now so sad I can't spend more time on the researching.
My question is, once I am able to add, show and edit forms I am being instructed to create partials, ie _forms.html.erb and then rendering those forms on the edit, show and new pages.
On my _forms.html.erb partial, how can I implement some flow using if statements based on the page that the form is being rendered for.
For example, when _form.html.erb is being rendered for the show page I want certain form_for labels/fields to be set to readonly: true. At the bottom of the form I want submit, edit, change buttons based on the page aswell.
So far I am trying to use the parems[:action] == "new" or "edit" etc to implement the control flow as follows:
Old code under the edit.html.erb file:
<%= f.label :patform_type %>
<%= f.text_field :patform_type,autofocus: true %>
New code under the _form.html.erb file:
<%= f.label :patform_type %>
<%= f.text_field :patform_type %>
<% if params[:action] == "new" %>
<%= ,autofocus: true %>
<% end %>
My original code has been influenced by these posts:
Rails not editable text field
How to disable all form_for input fields in Ruby on Rails app?
Once I get this right then I am hoping I can use it to wrap it around other elements like the submit, edit buttons etc or other fields.
Also if someone knows a better way can you please let me know as I don't know what I don't know.
Thanks for any assistance/ideas you can provide.
You probably have to stick with a bunch of if/else statements if you need such depth of logic within the form, but I would recommend having the forms typed into their respective erb file, and not be rendered from a partial with a ton of logic.
A partial is meant for repeated code, and your code is not exactly what I would describe as repeatable. It is also not immediately understandable and will contain code that is essentially a waste of time to read:
For example, if I am reading the edit action's view and I see:
if params[:action] == "new"
It will be testing for an action that isn't even relevant to the current view, unlike logic such as:
if current_user.admin?
which will be more suitable for partial-based logic.
Hope that helps, enjoy RoR
I have an application where the user can create a project that gets submitted into the database.
<div class="project_name">
Project Name:
<%= f.text_field :project_name,:maxlength => 30 %>
</div>
<%= label_tag :new_client, "Client:" %><br/>
<%= text_field_tag :new_client, nil, :maxlength => 30%>
Or
<%= f.select( :new_client, Project.all.map {|p| [p.new_client]}.uniq, :prompt => "Select an new_client") %>
</div>
Here they have the option to submit text for a new project name, and then have the option to select either an existing client, or enter a new one, which will get stored in the database, and will later be available in the drop down for the next project created.
I am trying to add a third option where they can pick an industry. This time however, the user can pick many industries for the one project. What would be the best way to go about this?
I was thinking having a dropdown with all the most common industries prepopulated in another table, and if the user wants another they can click a button to bring up another drop down. If the industry isn't present, the could enter one in a text field, that would get saved with that project entry, AND saved into the new industry table which would then be available for the next user.
Hopefully someone can point me in the right direction. I am new to rails, so go easy.
You should check this videos http://railscasts.com/episodes/196-nested-model-form-part-1
For this kind of situation i use recordselect gem for selecting existing objects, and then use a jquery template to add the objects to current form. But you have to do some js stuff to do this.
Instead i think you can also use a simple dropdown with :multiple => true option to select multiple objects.
The best way to do this is not very simple... but i think you should start with the rails casts complex form examples to figure out what you can do with nested forms.
I have a set of buttons displaying on my webpage. The effect of clicking one of the buttons needs to be that a call is made to an external API (and maybe the response being received, and updating something on the page).
Some additional information: these buttons are placed on the page by a partial, and make up part of a list of users. The buttons are intended to activate and deactivate the users being listed. I'm not sure if this setup will affect the best approach for doing what I want to do, so I thought it would be worth mentioning.
How should this be done? Should the buttons be links to some controller within my rails app? Wouldn't that require the page to be reloaded when the button is hit? Can I offload that request to ajax?, etc.
I don't know the best way to approach this, and any guidance would prove invaluable.
Ok. I believe I have found a good implementation of this.
The trick is to create a form encapsulating the button in order to hit the proper controller when the button is clicked. In my case, I used the rails form_tag function to generate my button within my _list_item.html.erb partial view for my Developer controller as follows:
<div id=<%= list_item.id %>>
<%= form_tag "/Developer/toggle", :method => "get", :remote => true do %>
<p>
<% if list_item.inactive? %>
<%= submit_tag "Activate", :name => nil %>
<input type="hidden" name="act" value="activate" />
<% else %>
<%= submit_tag "Deactivate", :name => nil %>
<input type="hidden" name="act" value="deactivate" />
<% end %>
</p>
<input type="hidden" name="dev_id" value=<%=list_item.id%> />
<% end %>
</div>
There are 2 things that should be called to attention within this partial.
Since this is a partial rendered as part of a list, you want to give each list item a unique id so that your javascript will act on only that element. This is done in the first line, <div id=<%= list_item.id %>>, which I know will be unique because each Developer in the list necessarily has a unique id.
:remote => true is a necessary argument to the form_for function this is what causes the request to be made in the background as opposed to loading a new page.
This form, when submitted hits my the Developer#toggle action with two parameters: act, which is either activate or deactivate and id which is the id of the Developer we are acting on. Both of these parameters are hidden fields within the form.
After the form is submitted, inside of the controller, I just obtain an instance of the correct Developer (in my case, doing so is rather complicated, but in most cases it's probably something like #dev = Developer.find(id)), and performs the steps necessary to activate/deactivate the developer.
Lastly, I created a toggle.js.erb file within the view directory for my Developer controller which gets rendered once the controller has completed its task. This file simply obtains the element (through the unique id we gave it in the partial) and replaces the inner html by re-rendering the partial as follows:
document.getElementById("<%=escape_javascript(#dev.id)%>").innerHTML="<%=escape_javascript(render :partial => 'developer/list_item', :object => #dev) %>";
The result is the partial being re-rendered after the developers active status has changed, resulting in the appropriate Activate or Deactivate button.
I realize that this answer is highly focused on my particular application, especially needing to deal with the toggling of active vs. inactive, however I believe it is easily simplified and adapted to other cases that may require less complexity.
You could do this task with Rails but then page refresh must be done. If you use Javascript/AJAX then page shouldn't be refreshed. Example (jQuery):
$(":button").click(function(){
//Your Code here..
});
EDIT:
Above is jQuery code. What you want to do is some action to happen when you click on button. If you want to call external API then you could use ajax. Look for documentation for POST and GET in jQuery: http://api.jquery.com/jQuery.post/ and http://api.jquery.com/jQuery.get/.
Google for rails jquery tutorial.
I am trying to have a way of confirming the information entered before actually saving it to the DB
Considered making an individual confirmation page as discussed here
Ruby on Rails: Confirmation Page for ActiveRecord Object Creation
However my form includes an attached file using paperclip which makes it more of a hassle to implement.
I thought of just having a :confirm => pop up that would show the information that the user
had just entered.
The problem is how to show the information that the user had just entered, for example
<% form_for #entry, :html => { :multipart => true } do |f| %>
<%= f.error_messages %>
<%= f.label :name %><br />
<%= f.text_field :name %>
<%= f.label :file %><br />
<%= f.file_field :file %>
<%= f.submit 'Create', :confirm => "????? " %>
<% end %>
Given that your loading attachments it may not be a bad idea to render a staging view including information derived from the attachment allowing the user to confirm. As in display the file if it's an image, or the first paragraph of text if it's a text file, etc.
It's going to take more work than the just adding a confirm pop up, but I feel the enhanced user experience is worth the extra effort.
I'm not familiar with the way that paperclip works. So you're mostly on your own for the intimate details.
You will probably have to create a record before the staging view can be rendered with the sample of the uploaded file. To accomplish that I'd set up an "active" column on the model in question, that defaults to false.
Usage would look something like this:
User complets new form.
Attachment is updated and records are saved, with the active field set to false.
Redirected to confirmation page that is essentially the show page with a confirm link/button and a cancel link/button
a. When the confirm link/button is clicked it sends a request to the controller triggering the update action on this record setting active to true.
b. When the cancel link/button is clicked it sends a request to the controller trigering the destroy action on this record.
All that's left is to set up a recurring task to remove objects that are inactive and were crated long enough ago that it's safe to assume the user has just ended the browser session.
The confirm option for the Rails submit method can only take a text value.
If you want to dynamically generate the confirm text, one way you could do it is to write your own HTML submit tag, and write custom javascript to analyse the fields you want to use in your generated text.
Or you could use the Rails submit method, but use something like JQuery to add an event handler to it.
I'd just throw my js into an onclick handler. That's all Rails does.
<%= f.submit 'Create', :onclick => "confirm('Name is ' + $F('entry_name'));" %>
(note, didn't test that, but it looks close. confirm is a core js function, not part of any lib)