Rails 6 prompt to download on mobile - ios

I have a Rails 6 app where users can submit changes to a model and a confirmation email will be sent to site admins. The changes are submitted through a modal popup and once submitted the popup is replaced with another that confirms that the email has been sent.
The problem I am having is when this is done on a mobile device (I'm using an iPhone, not sure if this happens on Android), once the change is submitted another popup appears prompting me to download a file of size 0 with the name of the model being updated (it happens in both Safari and Chrome). This doesn't happen on the desktop version of the site or in the mobile emulator on the desktop, so I can't think of how I could diagnose this issue.
Here is the code being called just before the download popup appears:
<%= button_tag "Submit", type: 'submit',
id: 'modal-subimt',
class: 'btn btn-primary',
onClick: 'replaceModal()' %>
Here is the create function being called when the form is submitted:
def create
... # Setting the parameters for the model being changed
if #model.save
#model.send_confirmation_email()
else
# Irrelevant because the email gets sent
end
end
Here is the send_confirmation_email function being called in create:
def send_confirmation_email()
UserMailer.model_confirmation(self).deliver_now
end
And here is the model_confirmation function being called by send_confirmation_email:
def model_confirmation(model)
#model = model
recipient = <admin email>
mail to: recipient, subject: "Model Confirmation"
end
Nowhere in this code can I see where I might be prompted to download a file, but alas it is happening. Any help would be appreciated on how to properly diagnose or solve this problem. Thanks!
EDIT:
I changed my code so that the model would be updated without sending a confirmation email Also, the modal is no longer replaced with the new one verifying an email has been sent, and I am still having the same issue. This leads me to believe that the problem is something with the creation of the model.
EDIT 2:
Previously I was creating the model and remaining on the page (with the confirmation that the email was sent). I changed my controller to redirect_to request.referer to reload the page once the change is made, and when that is done I am no longer prompted to download the empty file. Unfortunately, the way I want this to work, reloading the page isn't optimal. Is there any reason that I would be prompted for a download when updating a model without reloading the page?
SOLUTION:
I was able to solve this problem by adding remote: true to the form_for line like the following:
<%= form_for(Model.new, remote: true) do |f| %>
...
<%= button_tag "Submit", type: 'submit',
id: 'modal-subimt',
class: 'btn btn-primary',
onClick: 'replaceModal()' %>
<% end %>
I'm not sure why exactly this solved my problem though so if anyone can provide some insight that would be much appreciated!

The problem is related to how you instantiated the form_for. When you submit the form to a Model.new object, form_for try to infer the controller with the create method for handling it. See more how form_for works in https://apidock.com/rails/ActionView/Helpers/FormHelper/form_for
With that said, the create method is probably outputting something or nothing instead of redirecting, that's why you get a download on a mobile device and in browsers it's just an empty page because they can handle it. If there is no render on the method, Rails will answer with a 204 No Content or an empty text page. You must check your method about this.
When you use remote: true, I will quote from the docs:
:remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the submit behavior. By default this behavior is an ajax submit.
So instead of reloading the page, UJS sends a XMLHttpRequest to the "backend" page, that's why you don't get a redirect. The result of the submit is treated without redirecting. But based on the result of the request the content of the page can come. So it's important that you give a response accordingly. In the links below you will find the right way to do it.
See more in these questions:
How does ':remote => true' works in rails
:remote => true confusion with form_for
Also, in the Rails Guides:
https://guides.rubyonrails.org/form_helpers.html
https://guides.rubyonrails.org/working_with_javascript_in_rails.html
This is also a good reference:
http://www.korenlc.com/remote-true-in-rails-forms/

Related

Rails redirect flash[:notice] persisting when not supposed to

I have a button with a redirect_to that runs my controller action, which creates a new row for my model. The first time, it creates it correctly and redirects. After my redirect, I go to the previous page through the menu and repeat the same action. After I click on the button, it redirects to the correct page (which it shouldn't yet... caching?), and then my previous flash message displays. So it's duplicated. I put a debugger statement in to see where it happens on the second run through - it actually happens before my button action executes. After running the rest of the code, it redirects (but since it redirected prematurely, redirects to same page) correctly with the corresponding (second) flash. How do I get rid of the extra initial flash message?
Here's a picture showing what I mean:
If you look at the blue loading bar right underneath the URL, it shows that the page hasn't loaded yet (I stopped it with debugger statement as shown below). However, the redirect and flash has already happened, which it isn't supposed to, since the intended redirect and flash will happen after turbolinks finishes the page load.
Initial link:
<%= link_to create_wager_from_favorite_wager_path(favorite_wager), data: { confirm: 'Create this wager?' } do %>
Create Wager
<% end %>
Controller action:
def create_wager_from
debugger
# on second run through, the redirect and flash happens before I reach this point
#favorite_wager = FavoriteWager.find(params[:id])
#anchor = params[:anchor]
set_member_statuses()
result_message = #favorite_wager.create_wager_from_favorite(current_user)
respond_to do |format|
format.html { redirect_to my_wagers_path(:game => #favorite_wager.game), notice: "Wager created successfully!" }
end
end
At this point, it follows the standard path and I'm 99% sure the rest of the code is irrelevant.
I've tried checking the flash params at the point that the page with the button action loads, but it's empty. So I'm not sure what's causing this issue. Any insight appreciated.
Update: changing to flash.now[:notice] makes the duplicates stop, but the flash only displays on the first click of the button. Then it doesn't appear anytime after. And refreshing the page will allow the error to be repeated.
To prevent elements from being visible in cached pages (including previews), remove the element on turbolinks:before-cache. For example you could include something like this in your main application JavaScript file:
addEventListener('turbolinks:before-cache', () => {
document.querySelectorAll('.flash').forEach(element => element.remove())
})
For more on this checkout https://github.com/turbolinks/turbolinks#understanding-caching
After reading up on Turbolinks, I'd determined that the cause of this issue is the natural built-in functionality in turbolinks called a "page preview". This is where it will display the previous cached page as a sort of "preview" before the server response arrives, which gives the illusion that the page already loaded.
However, the cached redirect page in my case was cached the moment it was served up, meaning the flash message was also captured into that cache. So on the second time I clicked the create button, it would load the cached page with the flash, then redirect for real and flash again (like it's suppoed to).
So the solution here is to either a. disable all page previews or b. disable turbolinks for that specific link. I chose b. because it won't affect the rest of my program, at the expense that the blue loading motion is not longer there. Here is the solution below (very simple):
Before:
<%= link_to create_wager_from_favorite_wager_path(fw, :anchor => anchor), data: { confirm: 'Create this wager?' }, class: "red-btn create-favorite-wager-btn" do %>
Create Wager
<% end %>
After:
<%= link_to create_wager_from_favorite_wager_path(fw, :anchor => anchor), data: { confirm: 'Create this wager?' }, "data-turbolinks": "false", class: "red-btn create-favorite-wager-btn" do %>
Create Wager
<% end %>

back button for form preview in rails

I have a problem while making a back button for form in rails.
I have a dynamic form that looks like this:
dynamic form
However, when I press 'preview' button, the page will redirect to another page. In this page, I have created button:
<%= button_to 'Back to edit', 'javascript:history.go(-1);', {class:'btn btn-primary'} %>
It really back to previous form, but only 1 single form displayed
returned form
Can anyone can help me???
You can do this follow as:
<%= link_to 'Back to edit', :back %>
Most browsers will cache the back action and therefore won't issue an HTTP request to your server. If you need to display fresh data, then use a redirect via javascript's window.location rather than relying on the history state.

rails form post action without changing path

I've got a view that renders a contact form. This contact form is rendered through javascript. There is also a javascript filter that the user can set viewing options in. (Depending on the settings, different markers are shown on a google map. The user can then click on the markers and in the viewbox click on a view button that renders some info and the contact form below the map)
If I were to make a normal form and use the post method with a #contact and contact routes, I would have to rerender the entire page after the #contact#create method was called. Which would mean all of the current users filter options would be unset. I could obviously save the settings, but feel like this is a hassle.
What I would like is for the contact form to call a method upon submit without actually changing paths, but I have no idea if this is even possible. (i'm using simple form so an answer for that would be preferable)
Since your question is quite broad, I'll have to answer as such:
if this is even possible
Yes it's possible.
You'll have to use ajax to send an asynchronous request to your server.
Ajax (Asynchronous Javascript And Xml) sends requests out of scope of typical HTTP; you can send/receive "hidden" data without reloading (this is what you want):
Don't worry - ajax is really simple once you understand it.
There's a great Railscast about it here:
Implementation
For you, you will just have to get your form to submit over ajax (javascript). There are two ways to do this:
Standard JS (JQuery)
Rails UJS (unobtrusive Javascript)
Basically, javascript acts as a mini browser, opening a url on your server, handling the returned data & doing what you tell it on the path:
<% form_tag contact_path, remote: true %>
<%= text_field_tag "name %>
<%= email_field_tag "email" %>
<%= submit_tag %>
<% end %>
You'll then be able to back this up with the corresponding controller action on your server:
#app/controllers/contact_forms_controller.rb
class ContactFormsController < ApplicationController
def create
... #-> your own logic here
respond_to do |format|
format.js #-> when receiving a pure xml request, this will fire
format.html
end
end
end
#app/views/contact_forms/create.js.erb
$("body").append("<%=j #variable %>");
Without going into too much detail, this is the most efficient way to achieve what you want. I can elaborate if required.

Why a ruby command runs even if a user don't activate the script yet?

I'm a Ruby user, trying to make a web service that receives user's active request. I made a button, of which class is a "btn-send-alert". Then after the html code, I put a script function.
<div class="page-title">
<button class="btn-send-alert" style="background-color: transparent;">Help Request</button>
<p>Hello</p><br>
</div>
........
<script>
$(".btn-send-alert").click(function(){
alert('hello!');
<% Smalier.class_alert(#lesson,current_user).deliver_now %>
});
</script>
The problem is, the ruby code just start on its own even before I click this button.
And if I click this button, no email is delivered any longer.
Maybe in some point, I think I'm seriously wrong but I can't find where it is. Is there way that I can make this function work correctly?
Looking forward to seeing the response!
Best
Thanks to Rich, I am now able to write a code that works fine! The below code is that code.
<%= content_tag :div, class: "page-title" do %>
<%= button_to "Help Request", support_path, method: :get, remote: true, class:"btn btn-danger", params: { lesson_id: #lesson.id, user_id: current_user.id} %>
<%= content_tag :i, "wow!" %>
////
def support
#lesson = Lesson.find_by(:id => params[:lesson_id])
current_user = User.find_by(:id => params[:user_id])
mailer.class_alert(#lesson,current_user).deliver_now
end
Above code runs well!
I'm a Ruby user
Welcome to Rails!!
Stateless
Firstly, you need to understand that Rails applications - by virtue of running through HTTP - are stateless, meaning that "state" such as User or Account have to be re-established with each new action.
In short, this means that invoking actions/commands on your system have to be done through ajax or another form of server-connectivity.
Many native developers (native apps are stateful) don't understand how Rails / web apps are able to retain "state", and thus make a bunch of mistakes with their code.
receives user's active request
Even if you understand how to set up authentication inside a Rails app, it's important to understand the virtues of it being stateless... EG the above line means you have to have a user signed in and authenticated before you can send the request.
This forms one part of your problem (I'll explain in a second)
ERB
Secondly, the other problem you have is with the ERB nature of Rails.
the ruby code just start on its own even before I click this button.
This happens because you're including pure Ruby code in your front-end scripts. This means that whenever these scripts are loaded (triggered), they will fire.
The bottom line here is you need to put this script on your server. Otherwise it will just run...
Fixes
1. ERB
<%= content_tag :div, class: "page-title" do %>
<%= button_tag "Help Request", class:"btn-send-alert" %>
<%= content_tag :p, "Hello %>
<% end %>
You'll thank me in 1+ months.
Convention over Configuration means you use as many of the Rails helpers as you can. You don't need to go stupid with it, but the more "conventional" your code is, the better it will be for future developers to improve it.
Another tip - only use HTML for formatting; CSS for styling. Don't use <br> unless you actually want to break a line.
Another tip - never use inline styling - Rails has an adequate asset pipeline into which you should put all your CSS
--
2. Ajax
Secondly, your use of Javascript is incorrect.
More specifically, you're calling a server-based function inside front-end views. To explain this a little more, I'll show you the famed MVC image I post on here a lot:
This is how Rails works (MVC - Model View Controller) - this means that whenever you deal with your application, you have to accommodate a layer of abstraction between the user & your app -- the browser.
By its nature, the browser is not stateful - it stores session cookies which you have to authenticate on the server. You cannot call "server" code in the front-end HTML/JS.
This is why your Ruby code is firing without any interaction, although I'm not sure how it's able to fire in the asset pipeline.
If you want to make it work properly, you'll need to create a controller action to invoke the mailer send function, which you'll be able to do using the following setup:
#config/routes.rb
get :support, to: "application#support", as: :support -> url.com/support
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
respond_to :js, only: :support
def support
Smalier.class_alert(#lesson.current_user).deliver_now
end
end
#app/views/controller/view.html.erb
<%= content_tag :div, class: "page-title" do %>
<%= button_to "Help Request", support_path, method: :get, class:"btn-send-alert" %>
<%= content_tag :p, "Hello" %>
<% end %>
#app/views/application/support.js.erb
alert ("Hello");
Each and every ruby code snippet embedded in ERB runs on server, in order to assemble a valid HTML or Javascript script for browsers to render.
Browsers don't understand ruby script at all, all they can understand is HTML and Javascript.
In your case (I'm supposing you're using rails since you tagged your question with ruby-on-rails), emails are delivered when rails engine is assembling HTML's.
If you want the emails being sent after the users click that button, the correct way is:
Define an action method in some controller, give it an URL (i.e. add a route in config/routes.rb), send email in that action.
When the button on the page is clicked, send an AJAX request to that URL.

Rails root form will only submit when entered directly in browser

My root view has has a form, which renders but doesn't submit when a user reaches the page from a link:
<%= link_to 'home', root_path, class: 'navbar-brand' %>
If you access the root page directly through the browser (e.g. http://localhost:3000/, or refresh after link), the form submits.
Just getting started with rails (4.2), so I'm sure there's something really basic I'm missing. I'd really appreciate advice!
Add data-no-turbolink="true" inside your view's body tag.
Was solved by looking at Form Submit button only works after reload.

Resources