Rails 6 action cable chat app not working in firefox - firefox

I made a Rails 6 app and added a private messaging feature in it using action cable. The chat works fine in chrome and edge but crashes in firefox. Upon investigation in firefox, I think whenever the 'send' button in the create message form is clicked, it seems it reloads the page, disconnecting the current subscription. It says in the terminal that the async job is performed successfully before it gets disconnected. The data (in received(data)) doesn't get console logged in the browser.
I thought I just need to prevent the page to reload once the 'send' button is clicked so I tried placing preventDefault in submit listener in both application.js and conversation_channel.js but didn't work. Also added onclick: false in the submit tag in message form but didn't work also. It's maybe my theory is wrong or maybe I executed the solution wrong.
Please tell me if you need more snippets of my code:
// this is app/javascript/channels/conversation_channel.js
import consumer from "./consumer"
document.addEventListener('turbolinks:load', () => {
const conversation_element = document.getElementById('conversation-id');
const conversation_id = Number(conversation_element.getAttribute('data-conversation-id'));
const input_box = document.getElementById('message-input-box');
const send_button = document.getElementById('send-btn');
// for terminating other subscriptions when connected to a new subscription
consumer.subscriptions.subscriptions.forEach(subs => {
consumer.subscriptions.remove(subs);
});
consumer.subscriptions.create({ channel: "ConversationChannel", conversation_id: conversation_id }, {
connected() {
// Called when the subscription is ready for use on the server
console.log('Connected to conversation id: ' + conversation_id);
send_button.addEventListener('submit', function(e) { e.preventDefault(); });
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
console.log(data);
const el_user_id = document.getElementById('user-id');
const user_id = Number(el_user_id.getAttribute('data-user-id'));
let html;
user_id === data.message.user_id ? html = data.own_message : html = data.not_own_message;
const messageContainer = document.getElementById('messages-container');
messageContainer.innerHTML += html;
send_button.disabled = false;
input_box.value = '';
}
});
});
this is where the user creates a message: app/views/messages/_form.html.erb
<%= form_with scope: :message, url: item_conversation_messages_path(#conversation.item.id, #conversation.id), local: true, remote: true do |f| %>
<div class="form-inline">
<%= f.hidden_field :conversation_id, value: #conversation.id %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.text_field :content, placeholder: 'Type your message here...', class: 'form-control mr-2', id: 'message-input-box' %>
<%= f.submit 'Send', id: 'send-btn', class: 'btn btn-primary' %>
</div>
<% end %>

It was pointed out by user even_progression in my reddit post https://www.reddit.com/r/rails/comments/ol9ygj/rails_6_action_cable_chat_app_not_working_in/ that I should check the local and remote options in the message form. I set local to false and remote to true and now it works. Idk yet why but I'm about to find out.

Related

How to pass parameters in axios url?

I am trying to use Axios in my Ruby Sinatra Project.
There is a url defined in my ruby file.
post '/follow/:id' do
# do something
end
In my erb file, I am trying to pass the variable in view to my inline script.
<body>
<div id="follow_element">
<button v-on:click="followUser">Follow</button>
</div>
<script>
window.addEventListener('load', function(){
const app = new Vue({
el: "#follow_element",
methods:{
followUser(){
axios.post("/follow", {
params: {
id: <%= #user.id %>
}
})
}
}
})
})
</script>
</body>
What I want is if the user click the button, the app would hit the /follow/:id url to update result. However, I met two problems.
First, the <%= #user.id %> doesn't work (#user is a variable available in this erb). The second is the app hits the '/follow' endpoint, instead of '/follow/:id' endpoint.
Could you please give me some suggestions? Thanks very much.
Note that as you wrote this:
post '/follow/:id' do
# do something
end
You'll now accept POST requests like: /follow/oneRandomId, follow/124184965 and so on...
Right here, you are making a request to /follow, passing POST params that is not in url. This will not work:
axios.post("/follow", {
params: {
id: <%= #user.id %>
}
})
In order to fix it, you can generate an URL with the available #user.id and make the POST request, like this:
axios.post("/follow/<%= #user.id %>")
Or, if you want to pass some data to the user (POST data, not URL params), you can do this:
axios.post("/follow/<%= #user.id %>", {
name: 'A beautiful name to insert to an user with that ID'
})
If you're still getting stuck, you can also use axios(config), that I think a bit clearer about what it is doing, from axios README page:
axios({
method: 'post',
url: "/follow/<%= #user.id %>",
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});

Phoenix - submitting a form from the client-side and preventing the page from reloading

I have a form in my pug template that works and gets submitted normally using my Phoenix controller's submit function. I don't want to reload or redirect the page, so I figured I should use an AJAX request to submit the form.
This is the form:
=form_for #invoice_changeset, adaptive_invoice_path(#conn, :update, #adaptive, #invoice), [as: :invoice, method: :put, id: 'add-invoice-remarks-form'], fn f ->
.form-group
.label Remarks
= textarea f, :remarks, id: "remarks-area", role: "add-invoice-remarks", class: "textarea", placeholder: "Add Notes here (enter to submit)"
.form-group
= submit "Submit", class: "btn btn-primary btn-block"
This is the function in my controller that handles the submission of the form. It works normally, but refreshes the page.
def update(%Plug.Conn{assigns: %{adaptive: adaptive}} = conn, %{
"id" => id,
"invoice" => invoice_params
}) do
{:ok, invoice} = Billing.find_invoice(%{"id" => id})
case Billing.update_invoice(invoice, invoice_remarks) do
{:ok, invoice} ->
conn
|> put_flash(:info, "Invoice update successful!")
|> redirect(to: adaptive_invoice_path(conn, :show, adaptive, invoice))
{:error, _changeset} ->
conn
|> put_flash(:error, "Something went wrong while adding remarks!")
|> redirect(to: adaptive_invoice_path(conn, :show, adaptive, invoice))
end
end
I tried to intercept the form, and submit it with AJAX and onmount, but I receive a Phoenix.Router.NoRouteError upon clicking the submit button even though I have the correct route.
import { post } from '../../api'
onmount('[role="add-invoice-remarks"]', function () {
const $form = $('#add-invoice-remarks-form')
$form.on('submit', e => {
e.preventDefault()
const action = $form.attr('action')
const invoiceParams = {
remarks: $form.find('#remarks-area').val()
}
post(
action,
{
invoice: invoiceParams
},
_ => {
show_flash(success_content())
},
response => {
show_flash(fail_content(response))
}
)
})
})
Is there something I missed?
You're sending a POST request to a route that's expecting a PUT request.
You need a _method="put" parameter in your in addition to your invoice params.
Something like:
{
invoice: invoiceParams,
_method: "put"
}

how to use connect-flash with ajax

I have a page with a form that posts edits to a local endpoint via AJAX ($.post). I want to display a message to the user whether it went good or bad. But I can't get it to work. Here's what I have so far:
jade template (excerpt)
if message
.alert.alert-danger= message
// Tab panes
.tab-content
#admin-tab-profile.tab-pane.active(role='tabpanel')
h3 Profil
form
include partials/admin/profile
main.js (excerpt)
app.post('/admin', function(req, res) {
var data = req.body;
// find profile
profile.findById(data._id, function(err, profile) {
// update
profile.summary.da = data.da;
profile.summary.en = data.en;
// save
profile.save(function(err) {
if (err) {
req.flash('loginMessage', 'Oops! Something went wrong.');
res.status(500).send( { message: req.flash('loginMessage') } );
}
console.log("profile sucessfully updated");
req.flash('loginMessage', 'Yes! Everythings good!');
res.status(200).send( { message: req.flash('loginMessage') } );
});
});
});
app.js (excerpt)
app.use(flash()); // use connect-flash for flash messages stored in session
So what am I doing wrong? Why is not shown status messages when posting data?
Well if your using ajax you don't have to use express flash messages, you can simply use ajax.success method:
$.ajax({
method:'post',
url: '/user/singup',
data: data,
success: function(msg) {
console.log('Success!!');
console.log(msg); // loginMessage from your code
},
error: function() {
console.log('Error!!');
}
})
And send the status code from your post method in express:
res.status(200).send({ message: 'loginMessage' });
I managed to do that. I made it same as here:
https://www.npmjs.com/package/express-messages
<body>
<%- messages('my_message_template', locals) %>
<div id="messages">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<div class="alert alert-<%= type%>">
<%= message %>
</div>
<% }) %>
<% }) %>
</div>
And in $.ajax({ success:... I put this line:
$('#messages').load(location.href + " #messages");
So if ajax action was successful, it refreshed the div that id = messages.
In express I have this line:
res.status(200).json({ messages: req.flash('success','Yes! Everythings good')});
In above link, it states <%= type %> (ejs) if message is success or error (ie) and for that the styling is ie:
<style>
.alert-success{
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.alert-error{
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
</style>
This colors the background to green if message is 'success' or red if it's 'error'

How to do validate confirmaion in multistep form if confirmation field on next step?

I use client_side_validations-formtastic and client_side_validations gems.
In my model:
validates_presence_of :full_name, :parent_name
validates_confirmation_of :full_name, :parent_name, on: :create
In form
= semantic_form_for #attendee ||= Attendee.new, validate: true do |f|
= f.inputs do
= f.input :full_name, label: "Attendee Full Name", validate: { confirmation: false }
= f.input :parent_name, label: "Parent/Guardian Name", validate: { confirmation: false }
= link_to image_tag("buttons/save.png"), "#new-attendee-confirm", id: "new_attendee_save", class: "fancybox"
#new-attendee-confirm{:style => "width:600px;display: none;"}
= render partial: "attendees/new_attendee_confirm", locals: {f: f }
new_attendee_confirm
= f.input :full_name_confirmation, label: "Attendee Full Name"
= f.input :parent_name_confirmation, label: "Parent/Guardian Name"
= f.action :submit
On #new_attendee_save I added script for multistep form validation (it works if I remove validates_confirmation_of from model):
$("#new_attendee_save").bind("click", function (e) {
//If the form is valid then go to next else don't
var valid = true;
// this will cycle through all visible inputs and attempt to validate all of them.
// if validations fail 'valid' is set to false
$('[data-validate]:input:visible').each(function () {
var settings = window.ClientSideValidations.forms[this.form.id]
if (!$(this).isValid(settings.validators)) {
valid = false
}
});
if (valid) {
//code to go to next step
}
// if any of the inputs are invalid we want to disrupt the click event
return valid;
});
I need this logic. User fills out the form the first step, which triggered all validation except confirmation.
Next the user clicks on the save button, it shows some agreement and as a signature is prompted to enter the name of the child and the parent. In these fields validations should work for presence and confirmation.
Is there any way to implement this scenario?
In my way if I add validate: { confirmation: false } to first step it does not work and still shows message doesn't match confirmation.

Rails - button_to and submit

In my Rails 3.2.3 application I'm using Ajax and jQuery. There is a button on a page. By clicking this button it must be disables, a spinner must be appeared and an ajax request begins. It's working only if I don't disable the button. If I disable it then an ajax request is not run.
So what I need to do is to be able send an ajax request, show a spinner and disable a button at the same time.
<script>
$(document).ready( function() {
$("#lnk_more").click(function() {
//if I uncomment this then an ajax request won't be be sent
// $("#lnk_more").attr('disabled','disabled');
$("#spinner").show();
});
</script>
<%= button_to "More", {:controller => 'home', :action => "test_method", :page=>#current_page }, {:remote => true,:id => 'lnk_more', :method => :get} %>
<%= image_tag('ajax-loader.gif',:style => 'display:none', :id => 'spinner') %>
#result html
<form action="/home/test_method?page=1" class="button_to" data-remote="true" method="get"><div>
Any suggestions?
Check the button_to documentation. In the last example you'll find an example with :disable_with parameter, which you can use it to disable the button after submit.

Resources