Respond with javascript to an HTML request? - ruby-on-rails

Is it possible to respond to an HTML request with javascript? By this I mean that I don't want the page to be refreshed and plus execute some javascript code. I wonder if this is possible by changing only the server side?
In my action, I have something selected to respond to js and html like this:
respond_to do |format|
format.js
format.html
end
And I have a .js.erb file that, of course, should be rendered when the browser requests a javascript.
Can this be done? How?

You cannot do this by only changing the server side code. If you'd like javascript to be executed upon the server's response, then the form will need to be submitted through javascript. This is because javascript will eval the response from the server and run the code.
This can somewhat be trivially added in Rails:
<%= form_for #user, remote: true %>
When it is submitted, then you can send back javascript. For example, to alert that a user was added:
/app/views/users/create.js.erb
alert("user <%= j #user.email %> was added");

Related

Turbo Stream Rails does not update page

I am new to Ruby on Rails and need help with figuring out how Turbo Stream works in Rails.
I did everything by tutorials, however, the content of the page was not updating. I decided to test how Turbo Stream will behave on simple div.
In index.html.erb file I created such div with id="test":
<div id = "test">Hallow</div>
In the same file I am creating a button that calls method refresh in controller.
<%=button_to " ", refresh_path(:task => task, format: :turbo_stream)%>
The method is called and works properly, I have already checked it without Turbo Stream.
The content of refresh.turbo_stream.erb:
<%= turbo_stream.replace 'test', partial: 'refresh' %>
The content of _refresh.html.erb
<h1>GoodBye</h1>
Here is the proof that Turbo Stream works
Indeed Turbo Stream renders the page, but I get such an output.
I tried writing respond in controller, deleting turbo_stream format (it caused another error), playing with routes, but did not come up to anything.
I am sorry if this question might have been answered already, but I tried different options and nothing worked for me. Please help!
Turbo doesn't process links with formats other than html (apparently php as well).
So when you click a link or a button, that has ".json", ".xml", or ".turbo_stream" or anything else, Turbo gives up and just sends you directly to that page where browser displays it the best it can, and it knows nothing about what to do with turbo_stream, which has Content-Type: text/vnd.turbo-stream.html, so, plain text in the browser.
To fix it, remove :format:
<%= button_to "refresh", refresh_path(task: task) %>
With this button, Turbo will send a correct request, where it will wait for html or turbo_stream response, which you can see in the request header:
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
If the server then responds with turbo_stream format: Content-Type: text/vnd.turbo-stream.html then stream instructions will execute on the current page. There are other rules, but one step at a time.
To render an appropriate response use respond_to method in your controller action:
respond_to do |format|
format.html { render :page } # renders `page.html.erb`
format.turbo_stream { render :page } # renders `page.turbo_stream.erb`
# or render <turbo-stream> inline
# format.turbo_stream { render turbo_stream: turbo_stream.replace :test, partial: "test_partial" }
end

Rails form submission not happening in the background

I have a Rails 5.2 app, where I'm using form_with, which is generating this HTML:
<form id="post-0" class="" action="/posts.js" accept-charset="UTF-8" data-remote="true" method="post">
The form is submitting successfully to the PostController's create action.
def create
#post = current_user.account.posts.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to posts_path, notice: 'Post added.' }
format.js { render :create, status: :created }
else
format.html { render :index }
format.js { render :create, status: :unprocessable_entity }
end
end
end
It is then rendering the JS view:
app/views/posts/create.js.erb
alert('created');
However, in my browser, I'm literally redirected to http://localhost:3000/posts.js where it shows the javascript code. Meaning, the form submit doesn't seem to be happening in the background.
I would have expected to still have stayed on the page the form was on, with a Javascript Alert appearing saying "created".
Is there's something silly I've missed? Seems to me it must be something with form_with that I'm not doing right.
I tried swapping it for form_for #post, remote: true, format: :js do ... but that had the same problem.
At this point, I can't tell if the problem is in View that shows the form, or in the Controller's create action. The controller is rendering create.js.erb and the URL shown is http://localhost:3000/posts.js which does seem to suggest it's a Form problem in the View.
This app is Rails 5.2. I'm not sure it's relevant, but I've removed the Asset Pipeline for Webpacker, I do have rails-ujs installed (as it is showing the confirm dialog in these link_to links as expected <%= link_to('Destroy', user, method: :delete, data: { confirm: 'Are you sure?' }) %>
Form submissions can occur locally (ie. not remote) due to a number of reasons.
Some common reasons local submission may be occurring when using form_with include:
local: true
The local attribute is optional for form_with. If not provided, form_with will by default set local: false.
true will set the form to be submitted locally (standard form submission), not remote/asynchronously.
false will set the form to be submitted remotely/asynchronously.
Any value other than false (including :false which is a symbol not a boolean) will be interpreted as the value being true (ie. submit locally).
Javascript triggered form submissions
Despite the setting of the local attribute being set to something that should trigger a remote/asynchronous form submission, the form may still be submitting locally if you're triggering the form submit event via Javascript.
In the example given in question, the HTML generated by the form_with use is as expected, so attributes provided to form_with are not the problem in this instance.
If javascript is being used to submit the form, that could be the problem. For example, even with data-remote="true" on the form, this javascript will be a problem:
document.getElementById('form-0').submit();
This submits the form, but doesn't raise a submit event for rails-ujs to intercept via an onsubmit event handler.
Working with the Rails Unobtrusive Javascript Adapter
To have the form submission triggered by javascript (whether you're using jQuery, Stimulus JS, or anything else) you need to use a mechanism that rails-ujs (or jquery-ujs) will intercept for you.
Rails provides the Rails.fire custom javascript event wrapper in rails-ujs, which can by used like this:
Rails.fire(document.getElementById('form-0'), 'submit');
You're doing a very basic mistake. By using ajax you can't use format.js. This Ruby Module is used to redirect the page. instead you should be using render json: { status: 200}
saw your job on Upwork. No need to spend $100 on this!
<%= form_with url: content_export_path, method: :post, id: 'ce_form1', data: { remote: true } %>
Look where the remote: true declaration is... (inside data hash)
Let me know if this works.

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.

Rails + ajax, my "edit.js.erb" is not being run

I'm trying to setup ajax within rails, but I am having trouble getting my "edit.js.erb" to be ran
My edit.html.haml
= f.check_box :foo, {checked: eligibility_data.foo, class: "foo", remote: true}
controller
def edit
#survey = Foo.find_by_id(params[:id])
respond_to do |format|
format.html
format.js
end
end
edit.js.erb
alert("Testing");
How can I get edit.js.erb to run? Once it's working I want to append html to my view, will I be able to place that html right into my js.erb, or will I have to make a partial of some sort?
You can't append remote: true to a checkbox and expect it to fire off.
It needs to either be attached to the parent form tag of said checkbox, or you need to write some js that watches that checkbox.
If you go the watcher route, the JS will send an ajax call when the box gets clicked on.
Here is a link on how to do AJAX with jQuery (which is built into rails)

Add a "Thank you for your order" page that is not directly visitable

When the user purchases an item we want to display a thank you page.
Then again we want this page not to be directly visitable.
In other words, if the page was to be /orders/thankyou, the user could directly navigate to it which would be rather ugly and fail aas it would have nothing to show.
how can we show this page only when in context and forbid direct navigation to it?
You can create a partial form and append it to the DOM after the purchase event is fired. In the partial view, be sure to add a redirect action if it was accessed without the purchase event firing.
For just displaying short text (and not also e.g. the order data) you could just use a flash notice. For example:
redirect_to #page, notice: "Thank you for your order!"
Ajax
Sounds like you'll be best using ajax:
#app/views/cart/index.html.erb
<%= form_tag cart_order_path, remote: true do |f| %>
... (cart form)
<%= f.submit %>
<% end %>
This (obviously very simple) form will send a "remote" (Ajax) form submission to your controller. The reason why this is important is because you will then handle the response directly in that page you just sent the request from:
#app/assets/javascripts/application.js
$(document).on("ajax:success", "#your_form_id", function(status, data, xhr) {
$("your_page_div").html(data);
});
The trick here will be to render your thank you view without a layout -
#app/controllers/cart_controller.rb
class CartController < ApplicationController
respond_to :js, only: :create
def create
... business logic here
render "thank_you", layout: false
end
end
This will render the view you want without any of the supporting "layout" HTML - giving you the chance to append that to your current view. This means that if you wanted to show the "Thank You" view without letting the user browse to it directly - that's what you'll do
How It Works
Ajax (Asynchronous Javascript and XML) is a javascript technology which basically allows you to send "pseudo requests" to your Rails backend:
Basically the same as a standard HTTP request, except handled with Javascript, Ajax gives you the ability to create the appearance of "no refresh" functionality in your app. It does this by sending requests on your behalf, through Javascript.
Ajax is typically used for small pieces of functionality on web interfaces - whereby you'll have the ability to send simple requests to the server, gaining a comparable response that you can then work into the DOM.
This is the functionality I have been proposing - whereby you'll be able to send a request to your controller (albeit using the Rails UJS engine), to which you'll then receive a response. This response can then be worked into your page, thus providing you with the ability to show the "Thank You" page without the user refreshing.
You can use any solution from the following:
Using ActionDispatch::Flash:
flash[:notice] = "Thank you for your order!"
redirect_to #page
or
redirect_to #page, notice: "Thank you for your order!"
Using alert, in show.js.haml file (assuming you use action show in orders_controller.rb):
alert("Thank you for your order!");
and then add respond_to :js, only: :show, and format.js in action show for orders_controller.rb

Resources