How can I submit a form on input change with Turbo Streams? - ruby-on-rails-5

I have a form I want to submit automatically whenever any input field is changed. I am using Turbo Streams, and if I use onchange: "this.form.submit()" it isn't captured by Turbo Streams and Rails uses a standard HTML response. It works fine when clicking the submit button. How can I work around this?

There is a discussion on the hotwire forum, where Mark Godwin figured out why form.submit() isn't working with turbo:
Turbo intercepts form submission events, but weirdly, the JS formElement.submit() method does not trigger the submit event.
And Jacob Daddario figures out that you can use form.requestSubmit() instead:
It turns out that the turbo-stream mechanism listens for form submission events, and for some reason the submit() function does not emit a form submission event. That means that it’ll bring back a normal HTML response. That said, it looks like there’s another method, requestSubmit() which does issue a submit event.
So you can change your code slightly, and use requestSubmit() if a browser supports it, and use submit() if not:
onchange: "this.form.requestSubmit ? this.form.requestSubmit() : this.form.submit()"
Update:
As BenKoshy pointed out, in Turbo 7.1.0, a polyfill was added so you can use form.requestSubmit() without checking for browser support, so you can add this to your input field:
onchange: "this.form.requestSubmit()"

I need to implement this for an app with lots of forms. I wound up using Stimulus. Below is the whole controller:
import { Controller } from "stimulus"
const _ = require("lodash")
export default class extends Controller {
connect() {
let that = this;
that.element.addEventListener('change', _.debounce(that.handleChange, 500))
}
handleChange(event) {
event.preventDefault()
// event.target.name // => "user[answer]"
// event.target.value // => <user input string>
event.target.form.requestSubmit()
}
}
and here it's used in a form with a single text input. NOTE the controller is attached to the form, not to the inputs.
<%= turbo_frame_tag dom_id(form_model) do %>
<%= form_with model: form_model,
format: :turbo_stream,
html: { data: { controller: "buttonless-form" } } do |f| %>
<%= f.hidden_field :question_id, value: question.id %>
<%= f.text_field :answer_value, class: "input shadow wide", placeholder: "Enter your answer here" %>
<% end %>
<div id=<%= "question_#{question.id}_output" %>>
<p> <!-- feedback to the user shows up here via Turbo -->
</div>
<% end %> <!-- end turbo frame -->

Related

Re-using a stimulus action elsewhere on the page?

I am wiring up a slack clone (html/css) and have it so that the reference drawer opens/closes when I click on the x. I am wanting to also open/close it from the navigation area and thought I could just take the same link_to and call it from a different part of the page.
But when I do that, if I'm calling it from within a different target, I get an error
Error invoking action "click->navigation#toggle_reference_drawer"
Error: Missing target element "navigation.referenceDrawer"
How can I use code inside a data-target to trigger a different data-target?
i.e. what I'm trying to get working is
--navigation partial (link_to doesn't work) --
<div data-navigation-target="storyNavLinks">
<div class ="story">
<%= link_to "[x]", "#", data: { action: "click->navigation#hide_reference_drawer" } %>
</div>
</div>
-- application partial (link_to works) --
<div data-navigation-target="referenceDrawer">
<div class='reference box'>
<%= link_to "[x]", "#", data: { action: "click->navigation#hide_reference_drawer" } %>
</div>
</div>
Not sure where I'm going wrong.. I figured as long as the target being referenced is unique and on the page it shouldn't matter where it's being called from?
You have to make sure your data-controller attribute is on an element that wraps both targets. If that is not possible you can always include the controller twice but the targets will only be scoped to each instance so you will need to add them twice as well.

Pass -express-flash-messages- in an EJS file

I've a user controller which has a basic authentication logic. I'm using an ejs view engine . I'm finding trouble accessing these flash messages in my view, i.e. .ejs file. What should be the right approach. I dug through some information and found out that we can pass parameters during a res.redirect. However, I don't want to do that, since it will appear in the URL. Is there a solution to this, something like what pug engine has.
if (!user || !user.authenticate(req.body.password)) {
req.flash('error', 'Invalid email or password!')
res.redirect('/login')
return
}
You could pass your flash messages to your views using res.locals object:
app.get('/login', function(req, res) {
res.render('login', { errorMessage: req.flash('error')[0] });
});
Then in your view:
<% if (errorMessage) { %>
<div class="error">
<p><%= errorMessage %></p> <!-- Invalid email or password! -->
</div>
<% } %>

Jade equivalent of <%= %>

I'm trying to implement toastr notifications on my express application using express-toastr (https://github.com/kamaln7/express-toastr). The documentation says that after including the following in the controller:
if (err)
{
req.toastr.error('Invalid credentials.');
} else {
req.toastr.success('Successfully logged in.', "You're in!");
}
we have to include the libraries in the on the views. That is okay. But we also have to include something like this :
<%= req.toastr.render() %>
What is the equivalent of this on jade?
Thanks
Use:
div #{req.toastr.render()}
related post: https://www.filosophy.org/post/34/using_javascript_functions_within_the_jade_templating_language/

Rails+Backbone+Faye messaging, how to instantiate model and remove its element from DOM for all subscribers?

I am using Rails + Backbone + Faye to make a sample chat application.
I'm currently able to use Faye's messaging capabilities to write to the DOM on a create event, though I'm not actually instantiating a backbone model. Ala Ryan Bates' tutorial I'm just calling inside of
create.js.erb
<% broadcast "/messages/new" do %>
$("#chats-table").append("<%= escape_javascript render :partial => "chat", :locals => { :chat => #chat } %>");
<% end %>
And publishing it in another javascript:
faye.subscribe("/messages/new", function(data) {
eval(data);
});
I'd like to refactor this a bit and leverage backbone's models. A good use case would be the delete method.
My chat model is bound to a click event, delete which calls:
model.destroy();
this.remove();
Backbone will call the delete method and put a delete request to /entity/id
That also dispatches rails' /views/delete.js.erb'.
In there I call a helper method which publishes a message with Ruby code.
<% broadcast "/messages/delete" do %>
<%= #chat.to_json.html_safe; %>
<% end %>
listener
var faye = new Faye.Client('http://0.0.0.0:9292/faye');
faye.subscribe("/messages/delete", function(data) {
});
Here, I was wondering if i could instantiate the deleted backbone model somehow so I could push that event onto everybody's screen and remove it from the DOM. Basically, I would like to call this.remove(); inside the faye client instead of in the chat model. Is this even possible?
Well, you should do remove on the model and let the UI listen for the event and refresh itself. Once the UI reflects the model changes you are golden.
The problem you have here is that Backbone collections/models are not an identity map. So the model object you are dealing with in the view is not the same you will instantiate and remove from the faye callback. If your messages collection is globally accessible, then I suggest you get the instance from there are remove it.

In Rails 3, how can you make a div a button (a clickable div that links to a new page)

I know that there are options to do this manually, such as here : How do you make the entire DIV clickable to go to another page?
However, in Rails 3 we make links like this:
<%= link_to "name", url %>
And I am wondering -- is there a proper Rails way to make a whole div a button. I realize I could make a button. However, let's say I want to fill it with content like text and a picture - then how would I make that whole div clickable in a rails way?
For example:
<%= #story.title %>
<%= #story.blurb %>
In this example, I would want to make #story clickable, with the rails generated content that I specified.. Any ideas?
For those interested, the answer is:
<div class="class_name">
<%= link_to content_tag(:span, "Text for the LInk"), route %>
</div>
And then, in the CSS set .class_name to position:relative; and:
.class_name span {
width:100%;
height:100%;
position:absolute;
z-index:2;
top:0px;
left:0px;
}
The link will now assume the size of its parent container, and thus give the impression the whole container is a link.
I think that button_to is what you need :
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to
I might use jquery if you really wanted to do this
$(document).ready(function() {
$('#somediv').click(function(event){
document.location = 'http://someplace.com';
});
});
You can't make a "clickable" raw div like that in a rails way... the concept doesn't make any sense. That's just not how it works
(added missing close parens for click)
not tested