Rails how to display tabs with turbo-frame - ruby-on-rails

In my Rails 7 app I've got CustomersController where inside there is show action with the corresponding show.slim view. Inside this view I want to have 2 tabs:
"Data", with text of "This is data page" - this tab should be active after user hits show.slim
"Customer Users", with text of "Here is the list of Customer Users".
When user hits show url (customer_path) he should be able to see Data tab as active and after click "Customer Users" he/she should not be redirected anyway but stays at the page and the text of "Here is the list of Customer Users" should be rendered. I want to use turbo-frame to control tabs switching, no extra JS needed. I'm using tailwindcss.
So what I did is that:
customers/show.slim:
div.tabs
button.tab-button[data-turbo-frame="customer_data" data-turbo-frame-target="data"] Data
button.tab-button[data-turbo-frame="customer_users" data-turbo-frame-target="users"] Customer Users
= turbo_frame_tag "data" do
h1 Data
p This is data page
= turbo_frame_tag "users" do
h1 Customer Users
p Here is the list of Customer Users
With some extra css:
.tab-button {
#apply inline-block py-2 px-4 border-b-2 font-medium text-sm focus:outline-none transition duration-150 ease-in-out;
}
.tab-button.active {
#apply border-indigo-500 text-gray-900;
}
.tab-button:hover:not(.active) {
#apply border-indigo-500 text-gray-900;
}
.tabs {
#apply border-b border-gray-200 px-4 flex justify-start;
}
Instead I'm getting this view:
Like I said, at the first glance I want to understand what should be done to make it work? It has to be turbo-frame because EOD I'll have few view components for that.

Turbo works by making requests to the back-end, so if you wish to render tab contents in the same request, you would have to use javascript for the hiding & showing of the tabs.
If you do wish to use turbo, then you would have to change the way you approach this:
Since only one tab can be open at once, you don't need two turbo frames. You should have only one.
customers/show.slim
div.tabs
button.tab-button[data-turbo-frame="tab_content"] Data
button.tab-button[data-turbo-frame="tab_content"] Customer Users
= turbo_frame_tag "tab_content"
Create a controller action for each of the tabs
app/controllers/customer_tabs_controller.rb
class CustomerTabsController < ApplicationController
def data
end
def customer_users
end
end
routes.rb
Rails.application.routes.draw do
# ...
resource :customer_tab, only: [] do
get :data
get :customer_users
end
end
Add view templates for these actions, with a turbo_frame containing the tab content. When rendered, the previous contents of the frame will be replaced by contents here.
app/views/customer_tabs/data.html.erb
= turbo_frame_tag "tab_content" do
h1 Data
p This is data page
app/views/customer_tabs/customer_users.html.erb
= turbo_frame_tag "tab_content" do
h1 Customer Users
p Here is the list of Customer Users
Then, you have to add the links to tab buttons so clicking them will render the appropriate content.
customers/show.slim
div.tabs
/ To avoid having to specify `method: :get`, `link_to` could be used
= button_to "Data", data_customer_tab_path, class: "tab-button", method: :get, data: { turbo_frame: "tab_content" }
= button_to "Customer Users", customer_users_customer_tab_path, class: "tab-button", method: :get, data: { turbo_frame: "tab_content" }
= turbo_frame_tag "tab_content"
Now, clicking the tabs will open the respective tab content. To have the first tab load automatically upon opening the page, you would have to use eager loading frames
customers/show.slim
div.tabs
= button_to "Data", data_customer_tab_path, class: "tab-button", method: :get, data: { turbo_frame: "tab_content" }
= button_to "Customer Users", customer_users_customer_tab_path, class: "tab-button", method: :get, data: { turbo_frame: "tab_content" }
/ Will make a request for the data tab immediately and display it.
= turbo_frame_tag "tab_content", src: data_customer_tab_path do
/ The content that will be shown before the request to `data_customer_tab_path` completes
p Loading...
As

Related

Trigger stimulus event when target added/removed from DOM

I have what is essentially a form with multiple pages, where there is a partial which renders the form content for each page inside of a partial that contains the header, footer, "next" button.
I need my "Next" button to respond dynamically to each page. So if the user is viewing Page 1 of the form, then I'm checking that the fields (targets) on Page 1 are not empty before enabling the "Next" button, at which point they move on to Page 2, and the next button should be disabled again until all of the fields/targets on Page 2 have been completed.
So the controller should be able to fire a function each time the page change happens, which I figured I could do by detecting when certain targets are present on the DOM. But I haven't been able to find a straightforward way to do this.
This is more or less a stripped down version of what I have
The outer wrapper:
<body data-controller="next-button">
<main>
<%= turbo_frame_tag :main, do %>
<%= yield %>
<% end %>
</main>
<%= render partial: 'application/progress_bar' %>
<% end %>
</body>
Then I also have a rails form with a text field:
<%= form.text_field :name, data: { target: "next-button.contactName"} %>
This is my controller:
export default class extends Controller {
static targets = ['contactName']
connect() {
console.log(this.contactNameTarget);
}
contactNameTargetConnected() {
console.log('foo');
}
}
When the page loads, console.log(this.contactNameTarget) outputs the HTML for contactName as expected, but contactNameTargetConnected() does nothing.
Based on the stimulus documentation regarding lifecycle callbacks, I would expect this function to fire on load, especially since it clearly can access this.contactNameTarget
I also looked at a posted solution from the stimulus team on github where they have an example that looks like it could solve my issue, but the code they've posted is deprecated, and I was not able to get it working:
const event = document.createEvent("CustomEvent")
event.initCustomEvent("inner-connected", true, true, null)
this.element.dispatchEvent(event)

Reloading page with new url params

in my web app I have a button on my homepage that takes the user to a completetask page
<%= link_to "Complete Course", home_completeTask_path(course_id: enrolment.course_id), class:"btn btn-dark" %>
This takes the user to /completeTask?course_id=2
Im wanting to also direct the user to task 0, which is just an introduction task, which i'd achieve by adding ,task_id: 0 in the link_to line above.
so the final url would include /completeTask?course_id=2&task_id=1
completeTask page
On the completeTask page, i have a list of tasks associated with each course and I want the user to be able to click one of these task buttons and be redirected/reload the same page with the selected task information.
My code line is
<%= button_to task.name, home_completeTask_path(course_id: params[:course_id].to_i, task_id: task.id ), class:"btn btn-dark" %>
This managed to change the url params when the button is clicked, yay, but it throws an error of no POST route. so I added method: :get which i believed would change the button_to from post to get.
When i run this, all params from the url are removed.
Is there an easier way to add params to url and reload a page, or how do i fix the above code lines to work as I intended.
Thanks.

Rails loop on two div

I have an issue with my loop on a Bootstrap tab.
%ul.nav.nav-pills.nav-fill{role: "tablist"}
- #matrices.each_with_index do |matrice, i|
%li.nav-item
= link_to "#matrice_#{matrice.id}", class:"nav-link #{"active show" if i.zero?}", "data-toggle" => "tab" do
= matrice.name
.tab-content
- #matrices.each_with_index do |matrice, i|
%div{id:"matrice_#{matrice.id}", class:"tab-pane #{"active show" if i.zero?}", role: "tabpanel"}
= matrice.name
First tab is display well with the content but when I click on the second tab, first tab content is also display. I want to hide the previous tab content at click.
How to fix it? Can I make one loop over two HTML div?
Haml strictly follows indentation. Seems the loop in the second tab is put under same line where first tab is.
Try the below way.
%ul.nav.nav-tabs
%li.active
%a{"data-toggle": "tab", href: "#tab_menu1"}
Graphical View
%li
%a{"data-toggle": "tab", href: "#tab_menu2"}
Tabular View
%div.tab-content
%div.tab-pane.in.active{id: "tab_menu1"}
= render partial: "menu1_view"
%div.tab-pane{id: "tab_menu2"}
= render partial: "menu2_view"
You can refer https://www.w3schools.com/bootstrap/bootstrap_tabs_pills.asp for more details.
Add your loops under the %div.tab-page tag.
Hope this helps!!

Dealing with 2 models in 1 view

I have two models I'm trying to interact with in 1 view. The first model is Room and the second is Availability. 1 Room has many Availabilities.
On 1 Rooms page, I render out availabilities through a partial like this:
<% #room.availabilities.where("booking_id is NULL").order("start_time").each_with_index do |a|%>
Beside each availability I have a button to delete and update. The delete worked out fine since it was in my loop so I could do something like this.
<%= link_to("Delete", availability_path(a.id), method: :delete, remote: true) %>
But I'm having trouble with edit. I'm trying to do it through a modal which doesn't have access to the 'a' variable from the loop. So I'm not sure how to pass in the unique availability to the form
Button:
<!-- Edit button -->
<button type="button" class="btn btn-xs btn-primary" data-toggle="modal" data-target="#editAvailability"><i class='fa fa-pencil'></i></button>
<!-- Edit Availability Form -->
<%= simple_form_for #facility.availabilities.find(???), method: :put, remote: true do |f| %>
You should be able to do this with AJAX. I have an app that has a modal dropdown that lets me toggle pieces of equipment in or out of service with a button in the dropdown. You can use a route that points towards the Availabilities controller that renders a form in the modal. Mine is simple in that it just toggles, but I don't see why you can't use a form. I would move your query out of your view and make a helper that gives you the results of your query in a variable.
I can provide more detail but need to see a lot more of your current code. If you can post your controller and all of your view code for the modal. I don't know understand why you don't have access to the variable you need in the modal? If the modal has an AJAX call you should be able to populate it with any data available to your controllers.
edit
Take a look at this: https://coderwall.com/p/ej0mhg/open-a-rails-form-with-twitter-bootstrap-modals . Be sure and read the links at the end of this article, it has some StackOverflow examples that are spot on. I'm thinking link_to is the way to go:
<%= link_to 'Update, availabilities_edit_path(a.id), {:remote => true, 'data-controls-modal' => "modal-window", 'data-backdrop' => true, 'data-keyboard' => true} %>
This should open a modal and ask the availabilities#edit controller for a JS response. Make sure you have an edit action, I don't see one in your controller:
availabilities.rb
def edit
#availability = Availability.find_by(id: params[:id])
respond_to do |format|
format.html
format.js
end
So the JS call will cause it to look in the /views/availabilities/ folder for a file called edit.js.erb with content like:
$('#editAvailability').html('<%= escape_javascript(render :partial => 'editForm') %>');
In your edit form you you now have the #availability instance variable to use in your form:
<%= simple_form_for #availability, method: :put, remote: true do |f| %>
...
So your modal gets built using an AJAX lookup that returns a form built using the needed instance variable. I'm putting this together from stuff I've written and other stuff I've read, so there will probably be some errors and tweaking to get your code working. Let me know how far this gets you.
You can store all the information necessary in your update button within your loop like this:
<%= link_to("Update", method: :put, remote: true, data: { attr1: a.attr1, attr2: a.attr2 }) %>
Then every button knows what entity they are operating for.

Button to change the rendered partial

Ruby on Rails 3. I have a page with several div blocks. I am trying to seperate them into two pages. I want a button at the bottom of the page which will change the rendered partial.
The "render 'news'" is the first partial loaded. I want the "Archived News Postings" button to change the 'news' to 'archive_news'
<%= render 'news' %>
<%= link_to render(:partial => 'archive_news'), :class => 'btn' %> <button type="button" class="archive">Archived News Postings</button>
Can I get a pointer? Thank you
It depends on what you want to achieve really. For instance, these two options could be considered:
1) You could fetch both the news items and the archived ones. Then render two divs with the corresponding news items. The one with news is shown while the other has display: none; in the CSS. This allows you to use JavaScript to toggle the divs visibility. This obviously has some disadvantages with amount of data etc. but it gives an idea what to think about.
Something along these lines:
controller:
def some_action
#items = ...
#archived_items = ...
end
view:
<div id="news">
<% #items.each do |i|
...
</div>
<div id="archived_news">
<% #archived_items.each do |i|
...
</div>
css:
#archived_news {
display: none;
}
2) Another way would be to rely on asynchronously fetching the news items as the used wants to "toggle". This can be done with AJAX. This allows for more flexibility if the data set is large. You can even have "fetch more" in some way that archived news items are loaded on demand if needed.
If you shed some light what you might need it is possible to give a more specific answer.

Resources