Rails 7 turbo_frame_tag - ruby-on-rails

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

Related

Rails Hotwire - How to search within existing form

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

New search form partial is redirecting to my main index page for the model

I have an events model and I have an address field where I have some geolocation logic being implemented. On my main index page, I'm rendering a search form partial that works just fine to be all to search all of the events in my database.
Here's the original search form:
<%= search_form_for #q,
data: { controller: "places", action: "google-maps-callback#window->places#initMap" } do |f| %>
<div class="formlook">
<div class="event-search-container">
<div class="event-name">
<%= f.label "Event name", class: "form-label" %>
<%= f.search_field :name_cont, placeholder: "Any event", class: "form-control" %>
</div>
<div class="geolocation">
<%= f.label "Geolocation", class: "form-label" %>
<%= f.search_field :address_or_city_or_state_or_country_or_continent_cont_all, class: "form-control", placeholder: "Search by continent, country, state, or city", data: { places_target: "field" } %>
</div>
<div class="dancestyle">
<%= f.label "Dance style", class: "form-label" %>
<%= f.select(:dance_styles_id_in, DanceStyle.all.pluck(:name, :id), { :include_blank => "All dance styles" }, { class: "form-select", id: "select-dancestyle", multiple: true, placeholder: "Any dance style" }) %>
</div>
<div class="eventtype">
<%= f.label "Event Type", class: "form-label" %>
<%= f.select :event_type_id_in, EventType.all.pluck(:name, :id), { :include_blank => "All event types" }, { id: "select-artist", multiple: true, placeholder: "Any event type", class: "form-select" } %>
</div>
<div class="month">
<%= f.label "Month", class: "form-label" %>
<%= f.select(:event_month_in, (Date::MONTHNAMES[1..12]), { prompt: "Any month" }, { id: "select-month", :include_blank => "Any month", class: "form-select", multiple: true }) %>
</div>
<div class="year">
<%= f.label "Year", class: "form-label" %>
<%= f.select :event_year_in, Date.today.year-2 .. Date.today.year+3, { prompt: "Any year" }, { id: "select-year", include_blank: true, class: "form-select", multiple: true } %>
<br>
</div>
</div>
</div>
<%= f.submit "Search!", class: "button-orange" %>
<% end %>
I'm now creating specific event index pages for particular cities now. So I want to be able to use the other search parameters from my main index page without the name or the address field. So I created a new html erb file with a new scope based off the event model for a particular city:
<div class="container index">
<div>
<h1>Dance Events in Austin, Texas!</h1>
<br>
<div class="help-attn">
<p>All of the search options work together! Create your own search filters to find the events you want!</p>
</div>
<%= render 'events/local_search_form' %>
<br>
<h5>Currently displaying <%= pluralize(#events.in_austin.count, "event") %>. </h5>
<br>
</div>
<h3>Upcoming Events</h3>
<div class="event-list-wrapper">
<% #events.upcoming_events.in_austin.each do |event| %>
<%= render 'event', event: event %>
<% end %>
</div>
<h3>Past Events</h3>
<div class="event-list-wrapper past-event">
<% #events.past_events.in_austin.each do |event| %>
<%= render 'event', event: event %>
<% end %>
</div>
I also needed to add the def atx_index to my event model with the same parameters as the def index since in my head they are both index pages, just one has a different scope.
From above you will see that I created a different search form partial to reflect the two fields I don't need anymore, here's the new search form partial.
<%= search_form_for #q do |f| %>
<div class="formlook">
<div class="local-search-container">
z <%= f.hidden_field :address_or_city_or_state_or_country_or_continent_cont_all, value: [""] %>
<div class="dancestyle">
<%= f.label "Dance style", class: "form-label" %>
<%= f.select(:dance_styles_id_in, DanceStyle.all.pluck(:name, :id), { :include_blank => "All dance styles" }, { class: "form-select", id: "select-dancestyle", multiple: true, placeholder: "Any dance style" }) %>
</div>
<div class="eventtype">
<%= f.label "Event Type", class: "form-label" %>
<%= f.select :event_type_id_in, EventType.all.pluck(:name, :id), { :include_blank => "All event types" }, { id: "select-artist", multiple: true, placeholder: "Any event type", class: "form-select" } %>
</div>
<div class="month">
<%= f.label "Month", class: "form-label" %>
<%= f.select(:event_month_in, (Date::MONTHNAMES[1..12]), { prompt: "Any month" }, { id: "select-month", :include_blank => "Any month", class: "form-select", multiple: true }) %>
</div>
<div class="year">
<%= f.label "Year", class: "form-label" %>
<%= f.select :event_year_in, Date.today.year-2 .. Date.today.year+3, { prompt: "Any year" }, { id: "select-year", include_blank: true, class: "form-select", multiple: true } %>
<br>
</div>
</div>
</div>
<%= f.submit "Search!", class: "button-orange" %>
<% end %>
So the specific city event index page is displaying properly following the scope but when I use the search I'm redirected to the main event index page with the search parameters that I inputted. I'm not understanding how the two different search form partials are both linked to the main event index page.
How do I get the new search form partial for the specific city index page to display the search results on its corresponding page?
The issue to my redirect form issue was that I need to set the URL of the new form I created, so I was able to get the proper path names from the routes.
My initial implementation though had my configuring the form specifically and since I wanted to reuse the form on multiple pages for the individual local cities, I was lucky to find an old 7 year old SOF article that referred to setting the url in the render line like this:
<%= render 'events/local_search_form', { url: atx_index_path } %>
and then calling that url string in the local_search_form like so:
<% if local_assigns.has_key? :url %>
<%= search_form_for #q, { url: url } do |f| %>
and now I can simply change the url path for each html.erb file I create for each individual city! I didn’t know about the local assigns!

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.

Alternative to "link_to... do ... end" aproach

I have this piece of code.
<div class="tweet" id="tweet-<%= tweet.id %>">
<%= image_tag #user.avatar.url(:thumb) %>
<div class="tweet-content">
<strong><%= #user.first_name + " " + #user.last_name %></strong>
<span class="date">- <%= tweet.created_at.strftime("%e %B %Y") %></span>
<p><%= tweet.content %></p>
<div class="tweet-action">
<% #replies_count = tweet.replies_count %>
<% #like_class = "glyphicon glyphicon-star like-tweet"+tweet.liked(current_user.id) %>
<% #like_id = "tweet-like-"+tweet.id.to_s %>
<%= link_to "", liked_tweets_path(tweet_id: tweet.id), method: :post, remote: true, class: #like_class, id: #like_id %>
<% if tweet.liked_tweets.count > 1 %>
<span><%= tweet.liked_tweets.count %></span>
<% end %>
<!-- -->
Retweet
</div>
<%= render 'tweets/form', parent: tweet %>
</div>
<div class="replies">
<!-- Here goes ajax resposnse tweets#show_resplies -->
</div>
</div>
I want to achieve that when I click on ".tweet" I want to do this action tweets_show_replies_path(parent: tweet, active: false ). When I tried to wrap the whole thing in link_to .... do ... end it failed and the link didn't wrap the ".tweet" element.
Could you please tell me how to achieve the effect that I described above?
Use some javascript and an ajax request!
$(document).ready(function() {
$( ".tweet" ).click(function() {
$.ajax({
type: "POST",
format: "js",
url: <url for tweets_show_replies_path>,
data: {
parent: <%= tweet %>,
active: false
},
dataType: "script"
});
});
});
Then in your controller, do whatever you need to and render the partial.

Rendering a collection isn't rendering all items in the collection?

This seems very elementary, so I'm not sure what I'm doing wrong. I have a #videos collection (which debugging #videos.count reveals to be 4), but the line <%= render #videos %> is only rendering 2 of the 4 items. Here is the controller method (VideosController):
def index
#video = Video.new(athlete_id: current_user.id, sport_id: current_athlete_sport.id)
#videos = current_user.videos_for_sport(current_athlete_sport).order("date DESC")
debugger
respond_with #videos
end
And the _video partial that the aforementioned <%= render #videos %> line is rendering:
<%= form_for video do |f| %>
<div class="row edit-video-container <%= video == #videos.pop ? 'last' : '' %>">
<div class="span4">
<div class="row">
<div class="span4 video-thumbnail">
<%= image_tag video.thumbnail_url || asset_path("video-encoding-placeholder.png"), {alt: "", title: "#{video.name || "My video" + video.id}"} %>
<div class="video-ribbon">
<ul>
<li class="pull-left">
<%= link_to "#video-player-modal", { data: { toggle: "modal", link: video.mp4_video_url, thumbnail: video.thumbnail_url }, role: "button", class: "video-player-link" } do %>
<i class="icon video-play"></i>
<p>Play</br>Video</p>
<% end %>
</li>
<li class="pull-left">
<%= link_to rotate_video_path(video, direction: "ccw"), :data => { :method => "put", :confirm => "Are you sure?", :type => 'json' } do %>
<i class="icon video-rotate-left"></i>
<p>Rotate</br>Left</p>
<% end %>
</li>
<li class="pull-left">
<%= link_to rotate_video_path(video, direction: "cw"), :data => { :method => "put", :confirm => "Are you sure?", :type => 'json' } do %>
<i class="icon video-rotate-right"></i>
<p>Rotate</br>Right</p>
<% end %>
</li>
<li class="pull-left">
<%= link_to video_path(video), :data => { :method => "delete", :confirm => "Are you sure?", :type => 'json' } do %>
<i class="icon video-delete"></i>
<p>Delete</br>Video</p>
<% end %>
</li>
</ul>
</div>
</div>
</div>
<div class="row">
<div class="span4">
<%= f.check_box :featured, { checked: video.featured?, class: "autosave checkbox-right", data: { event: 'change' } } %>
<%= label_tag "video-checkbox-featured-#{video.id}", "Use as featured video?", { class: "checkbox-right-label" } %>
</div>
</div>
</div>
<div class="span4">
<div class="row">
<div class="span4">
<%= label :video, :name, "Video Name" %>
<%= f.text_field :name, { class: "span4 autosave"} %>
</div>
</div>
<div class="row">
<div class="span4">
<%= label :video, :video_type_id, "Video Type" %>
<%= f.select:video_type_id, VideoType.all.collect { |vid| [vid.name, vid.id] }, { include_blank: "Choose One", selected: video.video_type_id }, { class: "chosen-select autosave", id: "", data: { event: 'change' } } %>
</div>
</div>
<div class="row">
<div class="span4">
<%= label :video, :sport_id, "Video Sport" %>
<%= f.select :sport_id, current_user.sports.collect { |sp| [sp.name, sp.id] }, { include_blank: "Choose one", selected: video.sport_id }, { class: "chosen-select autosave", id: "", data: { event: "change" } } %>
</div>
</div>
<div class="row">
<div class="span4">
<%= label :video, :date, "Date Recorded" %>
<%= f.text_field :date, {class: "autosave date datePicker span4", value: js_date(video.date) } %>
</div>
</div>
<div class="row">
<div class="span4">
<%= label :video, :uniform_number, "Uniform Number" %>
<%= f.text_field :uniform_number, { class: "autosave span4"} %>
</div>
</div>
</div>
</div>
<% end %>
Again, throwing a debugger in the controller, or a debug on the page just before the <%= render #videos %> line shows 4 videos, however only 2 are actually being rendered. At a bit of a loss here!
The problem is here:
video == #videos.pop
Pop takes out a member from your collection. You shouldn't use it here because you brake the looping.
If this is the case, I suppose you can see only the first and the last item of your collection.

Resources