Attaching functionality to a button in rails 3 - ruby-on-rails

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.

Related

How to implement Nested Dropdowns in rails

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

How do I add autofocus to a form field if it is the edit form?

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

Design pattern for side bar with dynamic content in Rails

I would like to have a right side bar with content changes for each page.
For example, when I am in Friends page, the side bar should display New Friends.
When I am in Account page, the side bar should display Recent Activities.
How should I go about this to respect Rails design patterns? I heard about Cells gem, but I am not sure if I use it.
here is one way, in your layout add a named yield section
<div id="main-content">
<%= yield %>
</div>
<div id="side-content">
<%= yield(:side_bar) %>
</div>
Then in your views put content into the named yield using content_for
# friends view ....
<% content_for(:side_bar) do %>
<%= render :partial => "shared/new_friends" %>
<% end %>
# account view ....
<% content_for(:side_bar) do %>
<%= render :partial => "shared/recent_activity" %>
<% end %>
this requires you to be explicit about what content appears in the side bar for every view,
maybe having it do it dynamically is better? probably depends on the specific situation and your preference
see also - http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield
I came by this question in a moment of a big design change in our views. After thinking about the sidebar problem a bit, I realized that there's no best solution (as always). There are better solutions for each case.
I'll compare 3 solutions here:
using content_for(:sidebar) and yield(:sidebar)
using the partials approach
using the Cells gem
1. Using content_for(:sidebar) and yield(:sidebar)
This is good for cases when each link (each controller action) you access renders a different sidebar. In this case, each view you access will have the content_for(:sidebar) part.
If your sidebar view depends only on the state of some variable in the session, for example, the sidebar should not be rendered for every link you access.
Then you should use a good caching system like turbolinks, to avoid rendering many times the same thing, or use something like the Cells gem with a javascript to render only the main part of the layout.
2. Using partials
Using partials is always good to eliminate duplication. If your sidebar is very simple and is changed for every controller, you can render it as a partial. But if you're rendering different partials in the same controller, according to some state, it may be an indication that you have business logic in your views, which should be avoided.
3. Using the Cells gem
Very good design pattern when you have to render your sidebar from a different controller than the rest of the view each time.
It takes a lot of business logic out of the view, which sure is a good practice.
Here you have an action calling a view. Inside that view, there is a statement render_cell(:sidebar, params). This statement will do some business logic and render the view of the sidebar. It's as if the first action called other controller actions to render specific parts of your view (called cells)
If you make changes to the sidebar only, you may have to create other simple action, so that a javascript will request it. This action will call the render_cell(:sidebar) method again to respond with the view.
It's a very interesting approach.
Other ideas:
Your sidebar could be rendered only with javascript from the same
action.
Your sidebar could be rendered by an angular controller, and rails sends jsons with the sidebar objects. (look for "One page apps")
try something like this
<div class="sidebar">
<% if current_page?(controller => "friends", :action => "show") %>
<h4>New Friends</h4>
<% elseif current_page?(controller => "accounts", :action => "show") %>
<h4>Recent Activities</h4>
<% end %>
</div>
If the above code fits what you are trying to do(looks like this is what you want to achieve), then stick with it, else it may be beneficial to go with some gems. Also checkout helper page on how to use current_page? method. Hope it helps

How to show the content of the form just entered in the ":confirm =>" option on Ruby on Rails

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)

Should I be putting html input tags in my view?

I'm new to rails, and was wondering if I should be putting code like the second line inside my view:
<%= text_field_tag 'new_ingredient' %>
<input type=button ID="btnAddIngredient" Value="Add" onclick="addIngredient();" />
or is there a helper I should be using to generate the tag instead? I know about the form and tag helpers, but I don't want this particular button to be a form submission button. It's just a button that manipulates some items via javascript.
I'm guessing I should be using a helper, but I'm still trying to get familiar with the Rails API documentation and can't seem to find what I am looking for.
Depending on your approach, it may also be worth noting that putting JS function calls into on-click events is considered bad-form and will be on its way out in Rails 3, I believe. Depending on your JS framework, its better to listen for the click event on the button and act on that.
Running with jQuery, you could do something like this in the view:
<%= button_to "Add", :url => "#", :id => "btnAddIngredient" %>
And this in your application.js or other JS file:
$("#btnAddIngredient").click(function() {
addIngredient();
});
you can use button_to_function, like this
button_to_function "Add", :id => "btnAddIngredient", :onclick => "addIngredient();"
hope it helps =)
more details here: http://api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html#M001757

Resources