Rails auto clicking a button to refresh div - javascript

I'm new to programming and teaching myself Rails as I build an app.
I have a page with a list of jobs. Each job is rendered via a job partial which has a div which renders a status partial via a "Update Status" button with ajax.
The button works as expected and refreshes each job's status when clicked.
I am now trying to make the status div auto-refresh every x seconds.
After trying different methods, I've implemented a javascript which simply periodically refreshes the button (which I can later hide if I want). It was suggested here by #de_an777: Rails 3 - Periodic Refresh of DIV.
This seems to almost work for me.
The problem is that it doesn't refresh ALL jobs in the list. So if I have 2 jobs in the list, I see two refresh operations happening in the server log, but both of them refresh only the first job.
When inspecting the job id for each button element in the browser inspector, they all seem to have the correct id via job[:id].
I've been struggling with this for days, changing my routes and methods but cannot make it work. Hoping that someone can direct me in the right way...
Jobs_controller.rb:
def update
#job = Job.find(params[:id])
#some updating logic
respond_to do |format|
format.js
end
end
index.html.erb:
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>Job List</h1>
</section>
<section class="job_form">
<%= link_to "New Job", upload_path, class: "btn btn-lg btn-primary" %>
</section>
</aside>
</div>
<%= render #jobs %>
_job.html.rb:
<span class="jobs">
<p> Job ID: <%= job.id %></p>
<p> Job Name: <%= job.name != "" ? job.name : "Untitled" %>
<p> Source File: <%= job.source_file %></p>
<% #job = job %>
<div id="status_div_<%= #job.id %>">
<%= render :partial => 'jobs/status', locals: {job: job} %>
</div>
<%= button_to("Update Status", jobs_update_path(id: job.id), remote: true, method: :patch, id: "refresh_#{job[:id]}") %>
</span>
</li>
<script type="text/javascript">
function doSomething() {
$('#refresh_<%= job[:id] %>').click();
}
setInterval('doSomething()',10000);
</script>
routes:
get 'new_job' => 'jobs#new'
get 'upload' => 'jobs#upload'
patch "jobs/update", to: "jobs#update"
resources :jobs
update.js.erb:
$('#status_div_<%= #job.id %>').html("<%= j render :partial => 'status', object: #job, as: :job %>");
_status.html.erb:
<p> Status: <%= job[:status] %></p>

Related

TurboFrame & Stimulus Search form fails to execute search

I am going through a software engineering book called Modern Front-End Development for Rails, Second Edition by Noel Rappin. In one section of the book, I am trying to create a search form with turbo and stimulus so that when I type in input, it will automatically submit through javascript and the page that I am on will show the desired search result that I want. I've followed the book and double checked my code but it seems that the search bar is not working for one reason or another.
What makes this feature difficult to troubleshoot is that I do not see any errors in the rails terminal, no output in the browser console tab, no error logging, etc. It simply does not do a thing when I type. Below is the following pieces of code that is supposed to make the search work.
app/views/schedules/_search_form.html.erb
<%= turbo_frame_tag("search-form") do%>
<div data-controller="search">
<%= form_with(
url: concerts_url,
method: "get",
data: {
"turbo-frame": "search-results",
"search-target": "form",
action: "input->search#submit"
}
) do%>
<div class="flex justify-center">
<div class="w-4/5">
<%= text_field_tag(
"query", "",
placeholder: "Search concerts",
type: "search",
id: "search_query",
"data-search-target": "input",
class: "w-full px-3 py-2
border border-gray-400 rounded-lg"
) %>
</div>
</div>
<% end %>
<%= turbo_frame_tag("search-results") %>
</div>
<%end%>
app/javascript/controllers/search_controller.ts
import { Controller } from "#hotwired/stimulus"
import "form-request-submit-polyfill"
export default class SearchController extends Controller {
static targets = ["form", "input"]
formTarget: HTMLFormElement
submit(): void {
this.formTarget.requestSubmit()
}
}
app/javascript/controllers/index.js
import { application } from "./application"
import SearchController from "./search_controller.ts"
application.register("search", SearchController)
app/controllers/concerts_controller.rb
def index
#query = params[:query]
#concerts = Concert.all
end
app/views/concerts/_search_result.html.erb
<article class="my-6 max-w-screen-lg">
<div>
<%= concert.start_time.by_example("Jan 2 #3:04PM") %>
<div class="font-bold text-xl">
<%= link_to(concert, data: {"turbo-frame": "_top"}) do %>
<%= highlight(concert.name, /#{query}/i) %>
<%end %>
<div class="float-right">
<% if concert.sold_out? %>
Sold out
<% else %>
<%= pluralize(concert.unsold_ticket_count, "Tickets") %>
Remaining
<%end%>
</div>
</div>
</div>
<div>
<%= highlight(concert.bands.map(&:name).join(", "), /#{query}/i) %>
</div>
<div><%= concert.genre_tags.split(",").to_sentence %></div>
<div><%= concert.venue.name %></div>
</article>
These 5 pieces should work together and should highlight on my page the search term that I want. But like I mentioned before, nothing is being submitted, going to the controller, or updating. It simply doesn't do anything with no errors reported. How can I troubleshoot this feature effectively? If there is something glaring that I am not seeing I would appreciate the help.

Issue with resetting form after submission using Hotwire & Stimulus.js Rails 6

I've been testing Hotwire, using the demo that was put up by DHH. I have a default rails 6 setup & I know that it re-creates the javascript folder structure from the rails 5 < asset pipeline. The issue I am having is the form will not reset the text field after submission - despite setting up the stimulus controller for using this particular action. How can I reset the form for hotwire after the form is submitted by the user? My code is below
new.html.erb
<%= turbo_frame_tag 'new_conversation_comment', target: '_top' do %>
<%= form_with(model: [#conversation, #conversation_comment],
data: { controller: "reset_form", action: 'turbo:submit-end->reset_form#reset' }, html: {class: 'form-inline'}) do |form| %>
<%= form.submit 'Post', class: 'btn btn-primary mb-2', style: 'float: right;', 'data-reset_form-target': 'button' %>
<div style="overflow: hidden; padding-right: .5em;">
<%= form.text_field :content, class: 'form-control' %>
</div>
<% end %>
_conversation_comment.html.erb
<div class="p-1">
<%= conversation_comment.content %>
</div>
show.html.erb
<div class="p-2">
<%= turbo_stream_from #conversation %>
<%= turbo_frame_tag 'conversation' do %>
....
<% end %>
<div class="conversation-comments-container" id="conversation_comments">
<%= render #conversation.conversation_comments %>
</div>
<hr>
<%= turbo_frame_tag 'new_conversation_comment', src: new_conversation_conversation_comment_path(#conversation), target: :_top %>
</div>
assets/javascripts/controllers/reset_form_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
reset() {
this.element.reset()
}
}
Disclaimer: I'm using Webpack 4.0
Depending on setup underscore between controller name can be an issue. Replacing it with dash worked in my case.
turbo:submit-end->reset-form#reset
Disclaimer: I'm using Webpack 4.0
I ran into the same thing, also using Webpack.
The solution for me was:
Change the underscores to dashes, like Canbey said.
Move the stimulus controller from app/assets/javascripts/controllers/ to app/javascripts/controllers/ (somewhat per the stimulus docs so that webpack imported it properly — my application.js file was importing controllers from that directory rather than app/assets/javascripts.
A half SJR, half Hotwire approach would be this:
Working example here: https://rails-react-bootstrap.herokuapp.com/rooms
new.html.erb
<%= turbo_frame_tag 'new_note' do %>
<%= form_with model: [#room, #note] do |form| %>
<!-- Allow body validation error without message -->
<div class="form-group">
<%= form.rich_text_area :body, class: 'form-control comments', id: 'bodyText' %>
</div><!-- .form-group -->
<div class="form-group d-flex justify-content-center">
<%= form.submit 'Add Note', class: 'btn btn-lg btn-tayberry btn-block' %>
</div><!-- .form-group -->
<% end %>
<% end %>
create_js.erb
// clear note textarea after submit
var input = document.getElementById("bodyText");
input.value = '';
notes_controller
def create
#note = #room.notes.create!(note_params)
# #note.user = current_user
respond_to do |format|
if #note.save
format.turbo_stream
format.html { redirect_to #room }
format.js
else
format.html { render action: 'new' }
end
end
end
If you still need an answer, here is how you can achieve what you need:
In your controller respond to requests like that:
format.html { redirect_to conversations_url }
Note: redirect to the page where new_conversation_comment frame is defined (i.e same page from where you are submitting form). Redirect will happen, but Hotwire will substitute form for you with a new one and append comment where you specified.
It is a bit magic.
I suggest you to check this video:
https://gorails.com/episodes/hotwire-rails?autoplay=1

How do I make javascript change the element of specific element in rails

I have a little bit of a problem with the id names for my rails app where I want javascript to disable/enable a field with the press of a button.
I have a big index page where people can rate a bunch of pictures on the index page itself. For this I am using the #posts.each do |post| method.
The user should then be able to rate the picture with a slider, and after the slider was used, the range should be disabled. If the user wants to change the rating it is possible to click on "CHANGE", which enables the slider again.
Problem I have right now is with the enable function. I have a bunch of posts on the index page, and and all of the sliders and buttons have the same class names and ids. I have tried to give each slider and button a specific id with id:'ratingPost#{post.id}', but the problem then is that I cannot get javascript to know what is the postid that was just clicked.
Can you help me here?
Thank you very much!
My code is here:
#posts_index.html.erb
<% #posts.each do |post| %>
...
<% if post.ratings.find_by(user_id: current_user.id) %>
<%= form_for [post, post.ratings.find_by(user_id: current_user.id)] do |f| %>
<%= f.range_field :score, class:"form-control-range slider", id:"ratingPost", onMouseUp:"submit()", onTouchEnd:"submit()", :disabled => true, data: { source: post} %>
<% end %>
<% else %>
<%= form_for [post, #rating] do |f| %>
<%= f.range_field :score, class:"form-control-range slider", id:"formControlRange", onMouseUp:"submit()", onTouchEnd:"submit()"%>
<% end %>
<% end %>
...
<% if post.ratings.find_by(user_id: current_user.id) %>
<h2><%= post.ratings.where(user_id: current_user.id).last.score %>%</h2>
<button id="RatingChangeButton"><p>CHANGE</p></button>
<% end %>
</div>
</div>
</div>
<% end %>
<script>
document.getElementById("RatingChangeButton").addEventListener("click", enableRating);
function enableRating() {
document.getElementById("ratingPost").disabled=false;
}
</script>
Ideally you should not have multiple elements with the same ID in your HTML, instead use class.
Now to your question, what you can do is store the id of the post in a data attribute for your buttons and rating fields, and access that id in the javascript function to identify the corresponding slider. Something like,
https://codepen.io/anujmiddha/pen/NWRwwLL
<p>Post 1: <input class="ratingPost" data-post-id="1" disabled></p>
<p>Post 2: <input class="ratingPost" data-post-id="2" disabled></p>
<p>
<button class="RatingChangeButton" data-post-id="1" onClick="enableRating(this)">
<p>CHANGE 1</p></button>
</p>
<p>
<button class="RatingChangeButton" data-post-id="2" onClick="enableRating(this)">
<p>CHANGE 2</p></button>
</p>
function enableRating(view) {
let element = document.querySelector('[data-post-id="' + view.dataset.postId + '"],[class="RatingChangeButton"]');
element.disabled = false;
element.value = "enabled";
}

Rails/JS button onclick to re-render partial without entering into DB

I wish to reload a partial-form with a button(Add). I'm new and don't know how to simply display fields in partial like listings one under the other as many times Add button is clicked. I can't find a relevant example. all AJAX examples mention js.erb when object is saved in DB.
<div class="panel-body">
<%= render partial: "degrees/form", :locals => { :f => f } %>
<%= f.link_to (fa_icon 'plus').to_s + " Add Another Qualification ", render(:partial => 'degrees/form', :locals => { :f => f }), class: "btn btn-primary" %>
</div>
Here, #application is the main form trying to display degree fields. Partial is simply two text-fields- one for selecting educational degree and second its detail.Here's partial
<%= f.fields_for [Degree.new], :url => { :action => "index" } do |ff| %>
<div class = "form-group" }>
<%= ff.select :level, options_for_select(Job::EDUCATION, params[:level]), include_blank: "Select Degree", class: 'span-2' %>
<%= ff.text_field :description, :class => 'span5' %>
</div>
<% ff.submit "Add Another Degree", class: 'btn btn-primary' %>
You don't necessary need to save things to pass away to .js.erb... you can just instantiate...
But if your example is precise, you are missing the remote: true flag for the link... And the partial is not defined on the link... you need to make a view responding to the ajax...
Form example
<div class="panel-body">
<%= link_to new_thing_path, remote: true do %>
<i class="fa fa-plus">
Add another qualification
<% end %>
<div class="new-things-container">
</div>
</div>
Controller answering to the ajax request
class ThingsController < ApplicationController
def new
#thing = Thing.new
respond_with #thing
end
end
View for the ajax request rendering a partial inside a specified div
//views/things/new.js.erb
$('.panel-body .new-things-controller').append("<%= render partial: 'degrees/form', locals: { thing: #thing } %>")

Displaying flash messages on modal dialog box on submit rails

I am trying to build the modal box for registering emails addresses in rails.
I have done it but can not able to find the way to display the flash messages on modal dialog box when registration fails.
On submit the box gets closed and message is only getting display on home page. I do not have good knowledge of web or rails, but would be nice if I can get some pointers.
The functionality is working fine expect on fail, dialog box should display the messages on it instead of on home page. I know where is issue in my code but do not know approach to do it. Here is my code for dialog box
<div id="register" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<%= form_for(#email, url: home_esave_path, :html => {class:"model-dialog"}) do |f| %>
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Register</h4>
</div>
<div class="modal-body">
<div class="form-group">
<%= f.text_field :email, class:"form-control", placeholder: "Enter a valid gmail address" %>
</div>
<%= f.show_simple_captcha(:email=>"email", :label => "Human Authentication", :refresh_button_text => "Refresh text", class:"form-control", :placeholder => "Enter the code") %>
<%= f.submit "Register me!", :class => "btn register_button" %>
</div>
<div class="modal-footer" style="text-align:left">
<!-- show in case of success -->
<% if #email.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#email.errors.count, "error") %> prohibited
this email from being saved:
</h2>
<ul>
<% #email.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
<% end %>
</div>
</div>
my controller code to get the messages
def save
#email = Email.new(user_params)
if !user_params.nil?
if #email.save_with_captcha
flash[:success] = "Success! The email address #{#email.email} has been successfully registered. We will contact you as soon as we have available rooms for new beta testers."
redirect_to :action => 'index'
else
if #email.valid?
flash[:error] = "The captcha is not correct, enter again the captcha."
redirect_to :action => 'index'
else
flash[:error] = "You have put invalid #{#email.email}"
redirect_to :action => 'index'
end
end
end
end
Flash messages I am displaying outside this model which is obvious that will be displayed on home page. But I did not find way to display it on model dialog box.
here is how I am displaying the messages
<% flash.each do |type, message| %>
<div class="alert <%= alert_for(type) %> fade in">
<button class="close" data-dismiss="alert">×</button>
<span class="sr-only">Close</span>
<%= message %>
</div>
<% end %>
Thanks for suggestions
Flash is designed as a way to pass data between (controller) actions (see documentation for more information). In your case, you specifically, do not want an action, so I don't think there is a way to do this.
You can however generate a message that looks like your other flash messages using Ajax. Here is one way to do it.
You will need the following components:
Remote call from modal
Add remote: true or :remote => true to your form within the modal. This tells Rails that you want an Ajax call to the server when the form is submitted, not a full page post.
Controller changes
You'll need to respond to formats other than HTML. Something like this:
def save
if !user_params.nil?
#email = Email.new(user_params)
#email.save_with_captcha
respond_to do |format|
format.html { redirect_to #email, notice: 'This should not happen.' }
format.json { render action: 'show' }
format.js { render action: 'show' }
end
end
end
Javascript to insert/clear "flash" error message in your modal
View file - show.js.erb (can be named differently but name should match action above).
$('#build_error').remove();
<% if #email.errors.any? %>
var build_error = "<div class='alert alert-danger' id='build_error'>";
build_error += "The form contains <%= pluralize(#email.errors.count, 'error') %>.";
build_error += "<ul id='error_explanation'>";
<% #email.errors.full_messages.each do |msg| %>
build_error += "<li><%= j msg %></li>";
<% end %>
build_error += "</ul>";
build_error += "</div>";
$(build_error).insertBefore( "#xxxxxx" );
<% else %>
$('#email-modal').modal_success();
<% end %>
Basically, you'll need to clear any previous "flash" error message (the first line) and then either display the new one (you'll need to find somewhere in the DOM to attach it - see xxxxxx) or close the modal. Pretty brute force but it works for me.
Hopefully you get the idea - I doubt any of this will work as is so you will need to understand the concept.

Categories

Resources