Rails Hotwire - How to search within existing form - ruby-on-rails

I have form that has a section that displays a series of checkboxes for what groups a user manages. I want to add a search box at the top of the checkboxes that allows them to search a name and have it filter the results. From there they can check whatever ones they need to and submit the main form.
The only way I have ever done this is by triggering a from submission. It is not working (not doing anything) I assume because it is within another form. Is there a way to get this to work like I have it below, or is there a better way to set up this filter?
cardrequest form:
<%= form_with(model: cardrequest, id: dom_id(cardrequest), class: "contents") do |form| %>
form fields for cardrequest ...
<div class="mb-4 space-y-4">
<%= form_with url: search_cardrequests_path, method: :get, data: { controller: "search-form", search_form_target: "form", turbo_frame: "cgs" } do |c| %>
<div>
<label for="add-guests">Select Cardholder Groups</label>
<div class="relative">
<%= c.search_field :search, data: { action: "input->search-form#search" }, value: params[:search] %>
</div>
<% end %>
</div>
<div data-controller="checkbox-select-all">
<label>
<input type="checkbox" data-checkbox-select-all-target="checkboxAll" />
<span class="btn_primary_small">Select All / Deselect All</span>
</label>
<%= turbo_frame_tag "cgs" do %>
<div class="space-y-3">
<%= form.collection_check_boxes :cardholdergroup_ids, current_user.cardholdergroups, :id, :friendlyname do |g| %>
<div class="flex items-center mr-4">
<%= g.check_box data: { checkbox_select_all_target: 'checkbox' } %>
<%= g.label %>
</div>
<% end %>
</div>
<% end %>
</div>
<% end %>
Stimulus controller:
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = [ "form" ]
search() {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.formTarget.requestSubmit()
}, 200)
}
}
cardrequests controller search method:
def search
cardholdergroups = current_user.cardholdergroups
#cardholdergroups = cardholdergroups.where("friendlyname LIKE ?", "%#{search}%") if params[:search].present?
render(partial: 'cgs', locals: { cardholdergroups: #cardholdergroups })
end
routes:
resources :cardrequests do
collection do
get 'search'
end
end

Related

Rails 7 turbo_frame_tag

I have two forms on the same page.
The first one:
<%= form_with url: admin_users_path, method: :get,
class: "d-none d-md-inline-block me-auto w-100",
data: {
autosave_delay_value: 300,
controller: "autosave",
turbo_action: "advance",
turbo_frame: "users"
} do %>
<div class="input-group input-group-merge">
<%= text_field_tag "search", params[:search],
class: "form-control bg-white",
data: { action: 'keyup->autosave#save' },
placeholder: t("search", scope: "shared.action_name") %>
<span class="input-group-text bg-white">
<%= svg_icon("search-1", height: 16) %>
</span>
</div>
<% end %>
And second one:
<%= form_with url: admin_users_path, method: :get,
class: "d-none d-md-inline-block me-auto w-100",
data: {
controller: "autosave",
turbo_action: "advance",
turbo_frame: "users"
} do %>
<%= check_box_tag :confirmed, true, false, data: { action: 'autosave#save' }%>
<% end %>
To display filtered data i use:
<%= turbo_frame_tag "users" do %>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle table-edge table-nowrap mb-0">
<thead class="thead-light">
<tr>....
....
If I search for a user by name in the first form, for example, type "John" the url looks like this: http://localhost:3000/admin/users?search=john.
If I check the box in the second form the address changes to:
http://localhost:3000/admin/users?confirmed=true
This way I lose the search parameter.
The page is not reloaded, I just change the results.
How to pass this parameter between these forms?
Regards
Rails 7, Hotwire, Stimulus, Ruby 3.0.0, turb_frame_tag

Rails - Search results are not filtered. Returning all availability

The task scope: A user selects an option from a drop down menu (Single/Double bedroom), clicks on search button and gets all related results. Then on the search page the user can further refine results by using available filters (TV/Shower). For some reason none of these actions modify the search results- the page displays all available listings in the database, rather than the ones that match the criteria..
What am I doing wrong?
Here is what I have so far:
HOME SEARCH BAR
<%= form_tag search_path, method: :get do %>
<div class="row">
<div class="col-md-7">
<%= select_tag :bedroom, options_for_select([['Single', 1], ['Double', 2]]), class: "form-control" %>
</div>
<div class="col-md-2">
<%= submit_tag "Search", class: "btn btn-normal btn-block" %>
</div>
</div>
<% end %>
SEARCH PAGE
<div class="col-sm-3">
<%= search_form_for #search, url: search_path, remote: true do |f| %>
<div class="row">
<div>
<%= check_box_tag "q[is_tv_eq]", true %> TV
</div>
<div>
<%= check_box_tag "q[is_shower_eq]", true %> Shower
</div>
</div>
<div class="row text-center">
<%= f.submit "Search", class: "btn btn-form" %>
</div>
<% end %>
<%= render partial: "rooms/rooms_list", locals: {rooms: #arrRooms} %>
</div>
ROOM LIST PARTIAL
<% rooms.each do |room| %>
<div class="row">
<%= image_tag room.cover_photo(:medium) %>
<%= link_to room.user_id, room %>
<%= room.price %> - <%= room.bedroom %>
<div id="star_<%= room.id %>"></div> <%= pluralize(room.average_rating, "review") %>
</div>
<script>
$('#star_<%= room.id %>').raty({
path: '/assets',
readOnly: true,
score: <%= room.average_rating %>
});
</script>
<% end %>
ROOM MODULE
class CreateRooms < ActiveRecord::Migration[5.0]
def change
create_table :rooms do |t|
t.string :bedroom
t.integer :price
t.boolean :active
t.timestamps
end
end
end
PAGE CONTROLLER
def search
# STEP 1
if params[:search].present? && params[:search].strip != ""
session[:loc_search] = params[:search]
end
# STEP 2
if session[:loc_search] && session[:loc_search] != ""
#rooms_bedroom = Room.where(active: true, bedroom: session[:loc_search]).order(:price)
else
#rooms_bedroom = Room.where(active: true).all
end
# STEP 3
#search = #rooms_bedroom.ransack(params[:q])
#rooms = #search.result
#arRoooms = #rooms.to_a
end
DEFINED THE BEDROOM OPTIONS IN THE ROOM OVERVIEW PAGE (not sure if that helps)
<%= form_for #room do |f| %>
<div class="form-group">
<label> Bedroom Type </label>
<%= f.select :bedroom, [["Single", "Single"], ["Double", "Double"]],
id: "bedroom", prompt: "Select...", class: "form-control" %>
</div>
</div>
<div><%= f.submit "Save", class: "btn btn-normal" %></div>
<% end %>
Thanks

How to perform POST operation in Rails using AJAX

Scenario
I have created a simple application for learning Rails and Bootstrap. What this application does is it allows users to give nicknames to celebrities and like the nicknames as well. You can check it at http://celebnicknames.herokuapp.com
I have got a link_to button for posting likes for a celebrity in my home/index.html.erb as:
<%= link_to home_updatelike_path(id: nickname.id, search: #search ), method: :post, remote: true, class: "update_like btn btn-xs btn3d pull-right" do %>
<i class="glyphicon glyphicon-thumbs-up"></i>
<% end %>
The corresponding code in my home_controller.rb for the post method is:
def updatelike
#like = Like.new(:nickname_id => params[:id], :ip_address => request.remote_ip)
#like.save
respond_to do |format|
format.html { redirect_to root_path("utf8"=>"✓", "search"=>params[:search], "commit"=>"Search") }
format.json { head :no_content }
format.js { render :layout => false }
end
end
The code in my updatelike.js.erb is as:
$('.update_like').bind('ajax:success', function() {
});
Now, what I want to achieve is to submit a like without performing a page reload.
Problem:
Whenever I press the button, the database operation is done, but the change is not reflected in the page unless I reload it. Please guide me through this noob question and also provide links to resources which explain using AJAX with Rails.
Edit
Here is the entire home/index.html.erb
<div class="jumbotron col-md-6 col-md-offset-3">
<h1 class='text-center'>Nicknamer</h1>
</div>
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2 bottom50">
<%= form_tag(root_path, :method => "get", id: "search-form", type: "search") do %>
<div class = "form-group" >
<div class = "col-sm-9 " >
<%= text_field_tag :search, params[:search], placeholder: "Name", class: "form-control" %>
</div>
<div class = "col-sm-3" >
<%= submit_tag "Search", class: "btn btn-default btn-block btn-success"%>
</div>
</div>
<% end %>
</div>
</div>
<% #names.each do |name| %>
<div class="col-md-6 col-xs-8 col-md-offset-3 col-xs-offset-2 bottom50">
<!-- ===== vCard Navigation ===== -->
<div class="row w">
<div class="col-md-4">
<% if name.sex == true %>
<%= image_tag("mal.jpg", :alt => "", class: "img-responsive") %>
<% else %>
<%= image_tag("fem.jpg", :alt => "", class: "img-responsive") %>
<% end %>
</div><!-- col-md-4 -->
<!-- ===== vCard Content ===== -->
<div class="col-md-8">
<div class="row w1">
<div class="col-xs-10 col-xs-offset-1">
<h3><%= "#{name.name}" %></h3>
<hr>
<h5>Also known as</h5>
<% name.nicknames.each do |nickname| %>
<div class = "row w2 bottom10">
<% if nickname.name_id == name.id %>
<div class="col-xs-7">
<%= nickname.nickname %>
</div>
<div class="col-xs-3 text-right">
<%= pluralize(nickname.likes.count, 'like') %>
</div>
<div class="col-xs-1">
<%= link_to home_updatelike_path(id: nickname.id, search: #search ), method: :post, remote: true, class: "update_like btn btn-xs btn3d pull-right" do %>
<i class="glyphicon glyphicon-thumbs-up"></i>
<% end %>
</div>
<% end %><!-- if -->
</div><!-- row w2 -->
<% end %><!-- do -->
<div class = "row w3 bottom30">
<%= form_for #addnickname, as: :addnickname, url: {action: "addnickname"} do |f| %>
<div class = "form-group" >
<%= f.hidden_field :name_id, :value => name.id %>
<%= f.hidden_field :search, :value => #search %>
<div class = "col-xs-9">
<%= f.text_field :nickname , :required => true, class: "form-control" %>
</div>
<div class = "col-xs-3">
<%= f.submit "Add", class: "btn btn-default btn-info"%>
</div>
</div>
<% end %>
</div>
</div><!-- col-xs-10 -->
</div><!-- row w1 -->
</div><!-- col-md-8 -->
</div><!-- row w -->
</div><!-- col-lg-6 -->
<% end %>
</div><!-- /.container -->
and this is the entire home_controller.rb
class HomeController < ApplicationController
def index
#addnickname = Nickname.new
if params[:search]
#search = params[:search]
#names = Name.search(params[:search])
else
#names = Name.all
end
end
def addnickname
#addnickname = Nickname.new(:nickname => params[:addnickname][:nickname], :name_id => params[:addnickname][:name_id])
if #addnickname.save
redirect_to root_path("utf8"=>"✓", "search"=>params[:addnickname][:search], "commit"=>"Search")
else
render :new
end
end
def updatelike
#like = Like.new(:nickname_id => params[:id], :ip_address => request.remote_ip)
#like.save
respond_to do |format|
format.html { redirect_to root_path("utf8"=>"✓", "search"=>params[:search], "commit"=>"Search") }
format.json { head :no_content }
format.js { render :layout => false }
end
end
private
def addnickname_params
params.require(:addnickname).permit(:name_id, :nickname, :search)
end
end
Ok, so you need a way to identify the div with the number that needs to change:
<div class="col-xs-3 text-right">
<%= pluralize(nickname.likes.count, 'like') %>
</div>
The easiest way to do this would be to have an id attribute on the div that corresponds to the (unique) database id of the nickname. Rails has a helper method for this called div_for. So you could change the above code to this:
<%= div_for(nickname, class: "col-xs-3 text-right") do %>
<%= pluralize(nickname.likes.count, 'like') %>
<% end %>
That will generate a div with an id like nickname_1 (if this id of the nickname is 1).
Now in your javascript template you can easily target the div that needs to be updated:
$("nickname_#{#like.nickname.id}").html(#like.nickname.likes.count);
The ajax:success binding is unnecessary here since you are rendering a javascript template. In the template you can access instance variables set in the controller - just like you can from an HTML template. The above code is meant to replace the content of the div that contains the likes count with the new count.

How do I make the like counter to go down when I click the dislike button and vice versa?

this is the controller with like action:
def like
like = Like.create(like: params[:like], user: current_user, story: #story)
respond_to do|format|
if like.valid?
format.js
else
format.js {render status: 403, js: "alert('You can only like/dislike a story once')"}
end
end
this is the model that has the counter from model:
def thumbs_up_total
self.likes.where(like: true).size
end
def thumbs_down_total
self.likes.where(like: false).size
end
this is the View. I am getting the counter from the model. 'thumbs up' and 'thumbs down':
<div class="pull-right">
<%= link_to like_story_path(story, like: true), method: :post, data: { remote: true } do %>
<div class="likes"></div>
<% end %>
<div id = "like-<%= story.id %>">
<%= story.thumbs_up_total %>
</div>
<%= link_to like_story_path(story, like: false), method: :post, data: { remote: true } do %>
<div class="dislikes"></div>
<% end %>
<div id="dislike-<%= story.id %>">
<%= story.thumbs_down_total %>
</div>
</div>
I guess you are asking for something like this, correct?
<div class="pull-right">
<%= link_to like_story_path(story, like: true), method: :post, data: { remote: true } do %>
<div class="likes"></div>
<% end %>
<div id = "like-<%= story.id %>">
<%= story.thumbs_up_total - story.thumbs_down_total%>
</div>
<%= link_to like_story_path(story, like: false), method: :post, data: { remote: true } do %>
<div class="dislikes"></div>
<% end %>
<div id="dislike-<%= story.id %>">
<%= story.thumbs_down_total - story.thumbs_up_total %>
</div>
</div>
Move parts of your view to partials, createlike.js and fill it with code to render the partials.
First change your view code to this
<div class="pull-right">
<%= link_to like_story_path(story, like: true), method: :post, data: { remote: true } do %>
<div class="likes"></div>
<% end %>
<%= render "thumbs_up" %>
<%= link_to like_story_path(story, like: false), method: :post, data: { remote: true } do %>
<div class="dislikes"></div>
<% end %>
<%= render "thumbs_down" %>>
</div>
Then create two new partials.
# _thumbs_up.html..erb
<div id = "like-<%= story.id %>" class="thumbs-up">
<%= story.thumbs_up_total %>
</div>
# _thumbs_down.html..erb
<div id = "like-<%= story.id %>" class="thumbs-down">
<%= story.thumbs_down_total %>
</div>
Add a file called like.js
# like.js
$(".thumbs-up").html("<%= j(render("thumbs_up")) %>");
$(".thumbs-down").html("<%= j(render("thumbs_down")) %>");
like.js will then be called when your links are clicked. It will re-render your partials and update your thumb counts on click.

Rails - Drop down list with 'other' that allows creation of new model object

I currently have a rather complicated _request_form for creating new Requests in my website. Currently, when creating a request, employees must choose their name from a dropdown menu like so:
<%= f.collection_select :name, Employee.all(:order => 'name'), :name, :name %>
This selects puts the right Employee in the Request. However, on the off chance the employee isn't in database I'd like an other option in the collection_select that spawns two textboxes (for Employee name and email), and upon form submission makes the new Employee.
I assume this requires some sort of fancy Ajax, but my limited Rails knowledge doesn't extend that far!
Edit:
Here's my full view:
<%= javascript_include_tag :defaults, "nested_form" %>
<div class="request_form">
<% if !#request.errors.empty? %>
<div class="alert alert-error">
<ul>
<% #request.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="well">
<%= nested_form_for #request, html: { multipart: true } do |f| %>
<%= f.label :title %><br />
<%= f.text_field :title %><br /><br />
<%= f.label :name, 'Submitted By' %><br />
<%= f.select :name, Employee.sorted_employees_list.map { |value| [ value, value ] }, :id => "employee_box" %><br />
<div id="new_employee_data">
</div>
<%= f.label :description %><br />
<%= f.text_area :description %><br /><br />
<%= f.label :attachment_uploader, 'Attachments' %><%= f.file_field :attachment_uploader, :multiple => true, name: "data_files[attachment_uploader][]" %><br />
<% unless #request.data_files.empty? %>
<%= f.label :attachment_uploader, 'Current Attachments:' %><br />
<% end %>
<%= f.fields_for :data_files do |attachment| %>
<% if !attachment.object.new_record? %>
<%= attachment.label :attachment_uploader, 'Delete: ' + attachment.object.attachment_uploader_url.split("/").last %>
<%= attachment.check_box :_destroy %>
<% end %>
<% end %>
</div>
</div>
<script>
$(document).ready(function(){
$('#employee_box').append("<option>Other</option>");
});
$('#employee_box').change(function() {
if( $('#employee_box').val() === 'other' ) {
$('#new_employee_data').append("<input type='text' id='employee_name' placeholder='Employee Name'> <br/> <br /></input><input type='email' id='employee_email' placeholder='Employee Email'> </input>");
}else {
$('#employee_name').remove();
$('#employee_email').remove();
}
});
</script>
This includes #Kirti's suggestion. However, I can't seem to make it work!
Popup dialog is good choice, I think!
1) Add to your Gemfile, and run bundle:
gem 'jquery-ui-rails'
2) Activate jquery-ui javascript (application.js):
//= require jquery.ui.dialog
3) Link jquery-ui stylesheets (application.css):
*= require jquery.ui.dialog
4) Prepare data for select (employees_controller.rb)
def new
#prices = Price.all.map{|p| [p.price, p.id] }
#prices << ['Create New', 'new_id']
end
5) Display select component on view (employees/new.html.erb):
<%= select_tag :employee, options_for_select(#employees) %>
<div id="new_employee_dialog">
<label for="name" type="text">Employee name:</label>
<input name="name" type="text"/>
<label for="email" type="text">Employee email:</label>
<input name="email" type="email"/>
</div>
6) This javascript work with dialog window and send ajax request (assets/javascripts/employees.js.coffee):
$ ->
$("#new_employee_dialog").dialog
modal: true
width: 400
height: 300
autoOpen: false
draggable: false
dialogClass: "without-header"
buttons: [
text: "Cancel"
click: ->
$(this).dialog "close"
,
text: "Ok"
click: ->
modalForm = $(this)
$.post "/users/form_create",
employee_name: $(modalForm).find("input[name='name']").val()
employee_email: $(modalForm).find("input[name='email']").val()
, (data, status) ->
if data['status'] == 'ok'
modalForm.dialog "close"
alert "Ok"
else
alert "Oops"
]
$('#employee').change ->
selected_employee_id = jQuery("#employee").val()
console.log('selected id: ' + selected_employee_id )
if selected_employee_id == 'new_id'
$("#new_employee_dialog").dialog("open");
7) Create method to catch ajax request on server-side (employees_controller.rb):
def form_create
employee_name = params[:employee_name]
employee_email = params[:employee_email]
# create new user..
respond_to do |format|
format.json { render :json => {status: 'ok'} }
end
end
8) Add it to routes.rb:
post 'users/form_create' => 'users#form_create'
Add an empty div tag(placeholder) where you want to spawn the two input fields:
<div id="new_employee_data">
</div>
Add the following jQuery at the bottom of the view:
<script>
$(document).ready(function(){
$('#request_name').append("<option value='other'>Other</option>");
});
$('#request_name').change(function() {
if( $('#request_name').val() === 'other' ) {
$('#new_employee_data').append("<input type='text' id='employee_name' placeholder='Employee Name'> <br/> <br /></input><input type='email' id='employee_email' placeholder='Employee Email'> </input>");
}else {
$('#employee_name').remove();
$('#employee_email').remove();
}
});
</script>
where,
replace #request_name with the id generated for your collection_select.
You will also need to add code for creating the new employee in the action which is executed at form submission.
NOTE: I am no expert in AJAX but you could adapt the above jQuery and take it from there.

Resources