Still learning how to use Ajax in Rails.
I have a simple controller:
class PagineController < ApplicationController
def cerca
end
def trova
respond_to do |format|
format.js {render layout: false }
end
end
end
routes.rb:
get '/cerca', to: 'pagine#cerca'
post '/cerca', to: 'pagine#trova'
This is trova.js.erb:
$('#search_table').html("<%= escape_javascript(render partial: 'search_result') %>");
This is cerca.html.erb:
<%= form_tag '/cerca', remote: true do %>
<%= text_field_tag :name, params[:name], placeholder: 'Inserisci parte
del nome del gioco' %>
<%= submit_tag 'Cerca' %>
<% end %>
<h1> Results </h1>
<div id="search_table">
<p>Here the partial</p>
</div>
and this is the _search_result.html.erb
<p> I'm running well </p>
I'm expecting that, when i submit a search with the button, "Here the partial" will be substituted with "I'm running well".
But this is not happens.
In the log i see that _search_result.html.erb is correctly rendered, so all should run.
Why don't?
Ok. I've resolved (for future references if someone needs), using different js:
document.getElementById("search_table").innerHTML = "<%= j render 'search_result' %>"
Related
Creating In-Place-Editing of a single attribute of a model using Turbo Frames (not using a gem such as Best_In_Place as it requires jQuery and is not working well with Rails 7) This implemenation is using ONLY turboframes.
To accomplish this I followed this tutorial: https://nts.strzibny.name/single-attribute-in-place-editing-turbo/ (written in January 2022)
The tutorial does not match Ruby 3.2.0, Rails 7.0.4 perfectly and needs a one variable adjustment on the show page to work.
Unfortunately, there is no validation feedback currently in this tutorials method as the turbo_frame form implemented does not have it included.
Question: how to properly add validation feedback and routing of errors? (preferably a turbo_frames only solution)
Summary of tutorial:
create new app and scaffold one model: User name:string
changes to UsersController (a new action on the controller to edit a single attribute, and adding edit_name to before_action list)
before_action :set_user, only: %i[ show edit edit_name update destroy ]
# GET /users/1/edit_name
def edit_name
end
add to routes.rb (a new route for editing a single specific attribute)
resources :users do
member do
get 'edit_name'
end
end
create view/users/edit_name.html.erb (a new view page to support editing a specific attribute, (here a name)).
<%= turbo_frame_tag "name_#{user.id}" do %>
<%= form_with model: #user, url: user_path(#user) do |form| %>
<%= form.text_field :name %>
<%= form.submit "Save" %>
<% end %>
<% end %>
additions on _user.html.erb file (the link to the created turbo frame form edit_name.html.erb)
<%= turbo_frame_tag "name_#{user.id}" do %>
Name: <%= link_to #user.name, edit_name_user_path(#user) %>
<% end %>
Upon starter the app server I get errors about #user being nil:Class.
In order to get the tutorial to work I have to change the _user.html.erb file to use a local variable for user in the link.
edited again _user.html.erb (changing instance variable #user to local variable user)
<%= turbo_frame_tag "name_#{user.id}" do %>
Name: <%= link_to user.name, edit_name_user_path(user) %>
<% end %>
With this change, the tutorial works, allowing single attribute in place editing through turbo frames! But no model validation feedback is implemented.
Below, I attempt to deal with validation, first adding validation to models/user.rb
class User < ApplicationRecord
validates :name, presence: true
validates :name, comparison: { other_than: "Jason" }
end
PROPOSED SOLUTION:
CREATE a new turbo_stream file for editing errors that pop up (it has an error in the turbo_frame tag that it is targeting, it needs to be able to target any parent turboframe where the single attribute edit was initiated)
<%= turbo_stream.replace"name_#{#user.id}" do %>
<%= form_with model: #user, url: user_path(#user) do |form| %>
<% if #user.errors.any? %>
<div style="color: red">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if #user.errors[:name].any? %>
<%= form.label :name, style: "display: block" %> <%= form.text_field :name %>
<% end %>
<% if #user.errors[:active].any? %>
<%= form.label :active, style: "display: block" %> <%= form.check_box :active %>
<% end %>
<%= form.submit "Save" %>
<% end %>
<% end %>
and edit the UsersController.rb update method to deal with turbo stream errors
# PATCH/PUT /users/1 or /users/1.json
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_url(#user), notice: "User was successfully updated." }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #user.errors, status: :unprocessable_entity }
format.turbo_stream do
if #user.errors[:name].any?
#user.name = nil #so that it does not repopulate the form with the bad data
if #user.errors[:active].any?
#user.active = nil
end
render :edit_errors, status: :unprocessable_entity
end
end
end
end
This all works except for after entering a succesful edit on the form produced after an invalid entry, it renders the show for that entry only, rather than all of them.
What would be a 'dry'er method of doing all of this? (and how do I target updating just the one frame from the turbo stream so that only the one field gets updated after success on validation)?
Philosophically, is any of this worth it now compared to just using jQuery and the Gem Best_In_Place??? Seems like the number of changes are piling up and the code will get ugly if supporting such functionality across multiple attributes?
Since the initial issue is resolved, I'll just add some other ways you can do this. It's gonna be a little more work to do this yourself and you won't have all the functionality that some gem could give you. On the other hand, it's a lot less code and you have full control over everything. Besides, if you just need to have this one field to be editable, installing a gem and jquery is too much overhead.
Setup:
# rails v7.0.4.2
$ rails new hello_edit_in_place -c tailwind
$ cd hello_edit_in_place
$ bin/rails g scaffold User email first_name last_name --skip-timestamps
$ bin/rails db:migrate
$ bin/rails runner "User.create(email: 'admin#localhost', first_name: 'super', last_name: 'admin')"
$ open http://localhost:3000/users
$ bin/dev
class User < ApplicationRecord
validates :email, presence: true, length: {minimum: 3}
end
Turbo Frame
I'll just modify the default form and won't touch the controller as a quick example:
# app/views/users/_form.html.erb
# NOTE: this lets you render this partial and pass a local `:attribute` or
# get attribute from url params.
<% if attribute ||= params[:attribute] %>
<%= turbo_frame_tag dom_id(user, attribute) do %>
# NOTE: send `attrtibute` back in case of validation error, so this page
# can be rendered again with params[:attribute] set.
# V
<%= form_with model: user, url: user_path(user, attribute:) do |f| %>
<%= f.text_field attribute %>
# NOTE: show validation errors
<%= safe_join user.errors.full_messages_for(attribute), tag.br %>
<%= f.submit "save" %>
<% end %>
<% end %>
<% else %>
# original form here
<% end %>
# app/views/users/_user.html.erb
# NOTE: there is no need to have the whole set up for each individual
# attribute
<% user.attribute_names.reject{|a| a =~ /^(id|something_else)$/}.each do |attribute| %>
<%= tag.div attribute, class: "mt-4 block mb-1 font-medium" %> # tag.div - so that i can keep rb syntax highlight for stackoverflow
<%= turbo_frame_tag dom_id(user, attribute) do %>
<%= link_to edit_user_path(user, attribute:) do %>
<%= user.public_send(attribute).presence || "—".html_safe %>
<% end %>
<% end %>
<% end %>
That's it, every attribute is rendered, is editable and email shows validation errors. Also because all turbo_frame_tags have a unique id, everything works with multiple users on the index page.
Turbo Stream
You can also use turbo_stream to have more flexibility and make it even more dynamic, but it's a bit more of a set up. Also, add ability to edit full name in place, with first_name and last_name fields together:
# config/routes.rb
# NOTE: to not mess with default actions, add new routes
resources :users do
member do
get "edit_attribute/:attribute", action: :edit_attribute, as: :edit_attribute
patch "update_attribute/:attribute", action: :update_attribute, as: :update_attribute
end
end
# app/views/users/_user.html.erb
# Renders user attributes.
# Required locals: user.
<%= render "attribute", user:, attribute: :email %>
<%= render "attribute", user:, attribute: :name %>
# app/views/users/_attribute.html.erb
# Renders editable attribute.
# Required locals: attribute, user.
<%= tag.div id: dom_id(user, attribute) do %>
<%= tag.div attribute, class: "mt-4 block mb-1 font-medium" %>
# NOTE: to make a GET turbo_stream request vvvvvvvvvvvvvvvvvvvvvvvvvv
<%= link_to edit_attribute_user_path(user, attribute:), data: {turbo_stream: true} do %>
# far from perfect, but gotta start somewhere
<% if user.attribute_names.include? attribute.to_s %>
<%= user.public_send(attribute) %>
<% else %>
# if user doesn't have provided attribute, try to render a partial
<%= render attribute.to_s, user: %>
<% end %>
<% end %>
<% end %>
# app/views/users/_name.html.erb
# Renders custom editable attribute value.
# Required locals: user.
<%= user.first_name %>
<%= user.last_name %>
# app/views/users/_edit_attribute.html.erb
# Renders editable attribute form.
# Required locals: attribute, user.
<%= form_with model: user, url: update_attribute_user_path(user, attribute:) do |f| %>
<% if user.attribute_names.include? attribute.to_s %>
<%= f.text_field attribute %>
<% else %>
# NOTE: same as before but with `_fields` suffix,
# so this requires `name_fields` partial.
<%= render "#{attribute}_fields", f: %>
<% end %>
<%= f.submit "save" %>
<% end %>
# app/views/users/_name_fields.html.erb
# Renders custom attribute form fields.
# Requires locals:
# f - form builder.
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
# app/controllers/users_controller.rb
# GET /users/:id/edit_attribute/:attribute
def edit_attribute
attribute = params[:attribute]
respond_to do |format|
format.turbo_stream do
# render form
render turbo_stream: turbo_stream.update(
helpers.dom_id(user, attribute),
partial: "edit_attribute",
locals: {user:, attribute:}
)
end
end
end
# PATCH /users/:id/update_attribute/:attribute
def update_attribute
attribute = params[:attribute]
attribute_id = helpers.dom_id(user, attribute)
respond_to do |format|
if user.update(user_params)
format.turbo_stream do
# render updated attribute
render turbo_stream: turbo_stream.replace(
attribute_id,
partial: "attribute",
locals: {user:, attribute:}
)
end
else
format.turbo_stream do
# render errors
render turbo_stream: turbo_stream.append(
attribute_id,
html: (
helpers.tag.div id: "#{attribute_id}_errors" do
# FIXME: doesn't render `first_name` `last_name` errors
helpers.safe_join user.errors.full_messages_for(attribute), helpers.tag.br
end
)
)
end
end
end
end
private
def user
#user ||= User.find(params[:id])
end
I want to flash a notice/error if the email is/isn't saved, without using a redirect. I am using Rails 4 for the project, and here is my code:
layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>FarFlung Jobs</title>
<!-- /.You can include style.css into stylesheet_link_tag too. If you do so, dont forget to add style.css in asset.rb -->
<%= stylesheet_link_tag 'application', 'jobs', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'config', 'sharebutton', 'jobsalert', 'modernizr'%>
<%= render 'payola/transactions/stripe_header' %>
<%= csrf_meta_tags %>
</head>
<body>
<%= render 'shared/navbar' %>
<div>
<% flash.each do |name, msg| %>
<%= content_tag :div, msg, class: 'alert alert-info' %>
<% end %>
</div>
<%= yield %>
<%= render 'shared/footer' %>
</body>
</html>
users/new.html.erb
<section class="cd-form-wrapper cd-container">
<div class="column panel panel-default">
<div class="cd-filter"><h4>SUBSCRIBE FOR JOBS ALERT</h4></div>
<%= simple_form_for User.new do |f| %>
<%= f.input :email, label: false, :placeholder => 'Enter your email address...', :input_html => { :class => 'newsletter-form-field-styling' } %>
<%= f.button :submit, 'SUBMIT', :class => 'btn-block newsletter-form-styling btn-primary submit' %>
<% end %>
</div>
</section>
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(secure_params)
if #user.valid?
#user.save
flash.now[:notice] = "#{#user.email} is signed up for Jobs Alert."
else
flash.now[:alert] = 'Error Subscribing!'
end
end
private
def secure_params
params.require(:user).permit(:email)
end
end
How do I get the Rails flash message working to appear on my subscription form without redirecting the page?
You can submit your form passing remote: true. It'll (as you can expect) remotely submit your form, so you can return an json an render the expected flash. Ex:
Your form:
simple_form_for User.new, remote: true, class: 'your-form' do
<your code>
Your controller
def create
#user = User.new(secure_params)
if #user.save
render json: { status: 'success', message: "#{#user.email} is signed up for Jobs Alert." }
else
render json: { status: 'failure', message: 'Error Subscribing!' }
end
end
Your JS (perhaps new.js - and be sure to include it in your view)
// you can specify your bind container, here I just used the document
$(document).on('ajax:success', '.your-form', function(e, data) {
if(data.status == 'success'){
showSuccessFlash(data);
}else{
showErrorFlash(data);
}
});
Explaining:
Using the remote: true, your page will wait for an ajax answer, which you can get listening to ajax:success (or other bindings).
Then, it will receive the json and will store in the data variable. So you will get the data.status and the data.message, which you can show to your user as a feedback.
More infor about the remote: true http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html#form-for
More info about the JS callbacks https://github.com/rails/jquery-ujs/wiki/ajax
You can use a gem like Gon that will monitor variables and handle all of the Ajax for you automatically.
It's very easy to setup, and you can have it perform the update at whatever time interval you choose. If you're simply transporting a variable, this will save you some Ajax coding. And it will update your page without any kind of redirect, so it does exactly what you're looking for.
However, one other tweak I figured out to solve this problem is with/without redirect is using application helper to make your Flash Global and used on any View of your choice.
Move your Flash Message to Partials e.g.
shared/_flash_messages.html.erb
<div class="text-center">
<% flash.each do |name, msg| %>
<%= content_tag :div, msg, class: 'alert alert-info' %>
<% end %>
</div>
Define custom helper method for your flash messages using the exact same old rails method use to have. Such that the method renders our partials and the object will be parsed along to your partials.
helpers/application_helper.rb
module ApplicationHelper
def flash_messages_for(object)
render(:partial => 'shared/flash_messages', :locals => {:object => object})
end
end
Inside my View, you can call it with erb tags and call the form object on it such as <%= flash_messages_for(User.new) %> or <%= flash_messages_for(#user) %>. See code below:
users/new.html.erb
<section class="cd-form-wrapper cd-container">
<div class="column panel panel-default">
<%= flash_messages_for(User.new) %>
<div class="cd-filter"><h4>SUBSCRIBE FOR JOBS ALERT</h4></div>
<%= simple_form_for(User.new) do |f| %>
<%= f.input :email, label: false, :placeholder => 'Enter your email address...', :input_html => { :class => 'newsletter-form-field-styling' } %>
<%= f.button :submit, 'SUBMIT', :class => 'btn-block newsletter-form-styling btn-primary submit' %>
<% end %>
</div>
With this, my error messages are flashed on any view or form I call <%= flash_messages_for(User.new) %> on.
You can refer to Kevin Skoglund formerrors Ruby on Rails Tutorials on Lynda.com Click to watch
I am trying to render a partial with ajax when submitting a form.
Here is my code:
index.html.erb
<% #inbox.each do |conversation| %>
<div class="message">
<div id="messages">
<%= render conversation.messages %>
</div>
<div class="inner-message">
<%= form_tag({controller: "conversations", action: "reply", id: conversation.id}, {remote: true, method: :post}) do %>
<%= hidden_field_tag :recipient_id, current_user.id %>
<%= hidden_field_tag :subject, "#{current_user.name}" %>
<div class="form-group">
<%= text_area_tag :body, nil, class: "form-control", placeholder: "Odgovori" %>
</div>
<div class="form-group">
<%= submit_tag 'Pošlji', class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
<% end %>
index.js.erb
$("#messages").html("<%= escape_javascript(render conversation.messages) %>")
conversations_controller.rb
def reply
conversation = current_user.mailbox.conversations.find(params[:id])
current_user.reply_to_conversation(conversation, params[:body])
respond_to do |format|
format.html { redirect_to messages_path }
format.js { redirect_to messages_path }
end
end
when I submit the form, I get an undefined local variable error:
ActionView::Template::Error (undefined local variable or method
`conversation' for #<#:0x007fd287172fa8>)
How do I pass the local variable from the loop to the .js.erb view?
Thanks!
I usually don't do much rendering of js in applications so I'm a bit rusty on the specifics. However there are a couple of problems with your code.
First by issuing a redirect your instructing the browser to load a new url . Any variables such as 'conversation' that you would have set would be forgotten.
As the Stan Wiechers alluded you need to use an instance variable (e.g. #conversation) if you want to preserve conversation for the view. Unfortunately that won't help you in this case because of the redirect which wipes out all variables not stored in the session, cookies, or flash hash.
What I think you want to do is render your partial in stead of redirecting. Typically when you are using ajax you don't want to reload the page on the server side. In Rails you would typically render json or in your case a js partial.
Try
format.js{render partial:[PARTIAL NAME], locals:{conversation: conversation} }
This will render the partial without redirecting and will pass your local variable. If you change 'conversation', to #conversation then you can leave off the locals:{conversation: conversation} but your partial should reference
#conversation
not
conversation
hope that helps
I have two different controllers (scholarships_controller, scholarships_browse_controller) that look at the scholarship model. I want my Ransack search function to show the search results on the current page. So, for example, if I am on /scholarships_browse and I use the search functionality, I want it to use the scholarships_browse_controller and show the results on /scholarships_browse.
Currently, if I search on /scholarships_browse it uses the scholarships_controller and redirects to /scholarships to show the results (instead of /scholarships_browse).
Code for ScholarshipsBrowseController and ScholarshipsController
def index
#search = Scholarship.search(params[:q])
#scholarships = #search.result.page(params[:page]).per(3) #allows 3 scholarships on a page at a time
#search.build_condition
respond_to do |format|
format.html # index.html.erb
format.json { render json: #scholarships }
end
end
Code for scholarships browse index.html.erb:
<%= search_form_for #search do |f| %>
<%= f.condition_fields do |c| %>
<div class="field">
<%= c.attribute_fields do |a| %>
<%= a.attribute_select %>
<% end %>
<%= c.predicate_select compounds: false, only: [:cont, :eq, :gt, :lt] %>
<%= c.value_fields do |v| %>
<%= v.text_field :value %>
<% end %>
</div>
<% end %>
<div class="actions"><%= f.submit "Search" %></div>
<% end %>
So, I guess specifically I'm asking how do I make sure I am using the ScholarshipsBrowseController index instead of ScholarshipsController index when I am on /scholarships_browse ?
On your view:
<%= search_form_for #search, url: RAILS_ROUTEHERE do |f| %>
...
<%- end %>
search_form_for its an extension for form_for, so you can use the :url parameter to tell the form what should be the action of it (you can check the page source code when you render it on browser, you can check the tag <form action=""> to make sure it points to the right route.
I am new to rails so sorry if sometimes I don't make much sense. Here is what I am trying to do. I am trying to build a vote system. So next to a blog post there is a link that says 'vote' (will probably say like later). So far I have working: when the vote button is clicked, a value of '1' is delivered to the vote table and then that particular posts vote records display beneath the vote via AJAX (I copied a comment functionality). Instead of rendering all the number '1's below, I want it to render the updated count.
My vote table has the columns 'vote' and 'post_id' that are successfully being entered. My thinking was that I could just change my partial template to do this. Here is the code:
votes_controller:
class VotesController < ApplicationController
def create
#post = Post.find(params[:post_id])
#vote = #post.votes.create!(params[:vote])
respond_to do |format|
format.html { redirect_to #post}
format.js
end
end
end
def count
#post = Post.find(params[:post_id])
#vote = calculate :count
respond_to do |format|
format.html { redirect_to #post}
format.js
end
end
end
Here is the page where is is showing, /posts/show.html.erb:
<div id="backto"<%= link_to 'Back to all BattleCries', posts_path %></div>
<%= render :partial => #post %><br/>
<p5>Add a Comment</p5>
<div id="belt">
<div id="belttext">
<% remote_form_for [#post, Comment.new] do |f| %>
<p>
<%= f.text_area ( :body, :class => "commentarea") %>
</p>
<%= f.submit "Add Comment"%>
<% end %>
</div>
<div id="beltbottom">
</div>
</div><br/>
<br/><p5>Comment Stream </p5>
<div id="comments">
<%= render :partial => #post.comments %>
</div>
<p>
<% remote_form_for [#post, Vote.new] do |f| %>
<p>
<%= f.hidden_field :vote, :value => '1' %>
</p>
<%= f.submit "Vote" %>
<% end %>
<div id="vote">
<div id="votes">
<%= render :partial => #post.votes %>
</div>
</div>
</p>
Here is the :partial, /votes/_vote.html.erb: (this is where I thought I would just need to change it to vote.count, or post.count or something but can't get it to work).
<% div_for vote do %>
<%= h(vote.vote) %>
<% end %>
Here is the /votes/create.js.rjs file:
page.insert_html :bottom, :votes, :partial => #vote
page[#vote].visual_effect :highlight
I hope that all makes sense.
I think it's repeating because your .rjs is "inserting at the bottom" of the div instead of "replacing" ... you probably want page.replace_html
It would be better to have a DIV or SPAN tag that contains the number of votes for a post ... then have your .rjs file update the DIV's inner_html with the number of votes (which would be #post.votes.count) ... I don't think you really need a partial.
You probably want:
<%= #post.votes.count %>
You also probably want to use replace instead of insert_html - does that make sense? Insert is just adding more elements to the DOM whereas replace will replace the element.