Skip to content

Commit bdb559f

Browse files
jmilljr24maebeale
andauthored
Resource Index - Lazy load with turbo frame (#587)
* lazy load resource index * add turbo-false to edit button * add search on input * add select type to form auto-submit * use radio button * update button style with stimulus * stack kinds filter * add url share and resource count with turbo * add animate-fade for user input visual indication * use shared partial * Adding back in the total to workshops * use plural kinds * fix kinds --------- Co-authored-by: maebeale <[email protected]>
1 parent fbde76d commit bdb559f

File tree

12 files changed

+179
-113
lines changed

12 files changed

+179
-113
lines changed

app/controllers/resources_controller.rb

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
class ResourcesController < ApplicationController
2-
32
def index
4-
per_page = params[:number_of_items_per_page].presence || 25
5-
unpaginated = Resource.where(kind: Resource::PUBLISHED_KINDS) #TODO - #FIXME brittle
6-
.includes(:main_image, :gallery_images, :attachments)
7-
filtered = unpaginated.search_by_params(params)
8-
.by_created
9-
@resources = filtered.paginate(page: params[:page], per_page: per_page).decorate
10-
11-
@count_display = if filtered.count == unpaginated.count
12-
unpaginated.count
13-
else
14-
"#{filtered.count}/#{unpaginated.count}"
15-
end
16-
@sortable_fields = Resource::PUBLISHED_KINDS
3+
if turbo_frame_request?
4+
per_page = params[:number_of_items_per_page].presence || 25
5+
unfiltered = Resource.where(kind: Resource::PUBLISHED_KINDS) # TODO - #FIXME brittle
6+
.includes(:main_image, :gallery_images, :attachments)
7+
filtered = unfiltered.search_by_params(params)
8+
.by_created
9+
@resources = filtered.paginate(page: params[:page], per_page: per_page)
10+
11+
total_count = unfiltered.count
12+
filtered_count = filtered.count
13+
@count_display = if filtered_count == total_count
14+
total_count
15+
else
16+
"#{filtered_count}/#{total_count}"
17+
end
18+
19+
render :resource_results
20+
else
21+
render :index
22+
end
1723
end
1824

1925
def stories
@@ -58,11 +64,11 @@ def update
5864
@resource = Resource.find(params[:id])
5965
@resource.user ||= current_user
6066
if @resource.update(resource_params)
61-
flash[:notice] = 'Resource updated.'
67+
flash[:notice] = "Resource updated."
6268
redirect_to resources_path
6369
else
6470
set_form_variables
65-
flash[:alert] = 'Failed to update Resource.'
71+
flash[:alert] = "Failed to update Resource."
6672
render :edit
6773
end
6874
end
@@ -73,32 +79,31 @@ def destroy
7379
redirect_to resources_path, notice: "Resource was successfully destroyed."
7480
end
7581

76-
7782
def search
7883
process_search
7984
@sortable_fields = Resource::PUBLISHED_KINDS
8085
render :index
8186
end
8287

8388
def download
84-
if params[:attachment_id].to_i > 0
85-
attachment = Attachment.where(owner_type: "Resource", id: params[:attachment_id]).last
89+
attachment = if params[:attachment_id].to_i > 0
90+
Attachment.where(owner_type: "Resource", id: params[:attachment_id]).last
8691
else
87-
attachment = Resource.find(params[:resource_id]).download_attachment
92+
Resource.find(params[:resource_id]).download_attachment
8893
end
8994

9095
if attachment&.file&.blob.present?
9196
redirect_to rails_blob_url(attachment.file, disposition: "attachment")
9297
else
9398
if params[:from] == "resources_index"
9499
path = resources_path
95-
elsif params[:from] == "dashboard_index"
96-
path = authenticated_root_path
97-
else
98-
resource_path(params[:resource_id])
99-
end
100+
elsif params[:from] == "dashboard_index"
101+
path = authenticated_root_path
102+
else
103+
resource_path(params[:resource_id])
104+
end
100105
redirect_to path,
101-
alert: "File not found or not attached."
106+
alert: "File not found or not attached."
102107
end
103108
end
104109

@@ -110,8 +115,8 @@ def set_form_variables
110115

111116
@windows_types = WindowsType.all
112117
@authors = User.active.or(User.where(id: @resource.user_id))
113-
.order(:first_name, :last_name)
114-
.map{|u| [u.full_name, u.id] }
118+
.order(:first_name, :last_name)
119+
.map { |u| [u.full_name, u.id] }
115120
end
116121

117122
def process_search

app/frontend/javascript/controllers/collection_controller.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ import { Controller } from "@hotwired/stimulus"
22

33
// Connects to data-controller="collection"
44
export default class extends Controller {
5-
5+
static classes = [ "unselected", "selected" ]
66
connect() {
77
this.element.addEventListener("change", (event) => {
8-
if (event.target.type === "checkbox" || event.target.type === "radio") {
9-
this.submitForm()
8+
const { type } = event.target;
9+
10+
if (type === "checkbox") {
11+
this.toggleClass(event.target);
1012
}
11-
})
1213

14+
if (
15+
type === "checkbox" ||
16+
type === "radio" ||
17+
type === "select-one" ||
18+
type === "select-multiple"
19+
) {
20+
this.submitForm();
21+
}
22+
});
1323
this.element.addEventListener("input", (event) => {
1424
if (event.target.type === "text") {
1525
this.debouncedSubmit()
@@ -27,4 +37,19 @@ export default class extends Controller {
2737
this.submitForm()
2838
}, 400)
2939
}
40+
41+
toggleClass(el) {
42+
const button = el.closest("label")
43+
if (!button || !this.selectedClasses) return
44+
45+
// Toggle selected classes
46+
this.selectedClasses.forEach(cls => {
47+
button.classList.toggle(cls)
48+
})
49+
50+
// Toggle unselected classes
51+
this.unselectedClasses.forEach(cls => {
52+
button.classList.toggle(cls)
53+
})
54+
}
3055
}

app/models/resource.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ class Resource < ApplicationRecord
5959
scope :category_names, ->(names) { tag_names(:categories, names) }
6060
scope :sector_names, ->(names) { tag_names(:sectors, names) }
6161
scope :featured, -> (featured=nil) { featured.present? ? where(featured: featured) : where(featured: true) }
62-
scope :kind, -> (kind) { where("kind like ?", kind ) }
63-
scope :leader_spotlights, -> { kind("LeaderSpotlight") }
62+
scope :kinds, ->(kinds) {
63+
kinds = Array(kinds).flatten.map(&:to_s)
64+
where(kind: kinds)
65+
}
66+
scope :leader_spotlights, -> { kinds("LeaderSpotlight") }
6467
scope :published_kinds, -> { where(kind: PUBLISHED_KINDS) }
6568
scope :published, ->(published=nil) {
6669
if ["true", "false"].include?(published)
@@ -86,7 +89,7 @@ def self.search_by_params(params)
8689
resources = resources.category_names(params[:category_names]) if params[:category_names].present?
8790
resources = resources.windows_type_name(params[:windows_type_name]) if params[:windows_type_name].present?
8891
resources = resources.title(params[:title]) if params[:title].present?
89-
resources = resources.kind(params[:kind]) if params[:kind].present?
92+
resources = resources.kinds(params[:kinds]) if params[:kinds].present?
9093
resources = resources.published_search(params[:published_search]) if params[:published_search].present?
9194
resources = resources.featured(params[:featured]) if params[:featured].present?
9295
resources

app/views/resources/_resource_card.html.erb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
<%= link_to(
7272
content_tag(:span, "", class: "far fa-edit"),
7373
edit_resource_path(resource.object),
74-
class: "admin-only bg-blue-100 btn btn-secondary-outline"
74+
class: "admin-only bg-blue-100 btn btn-secondary-outline",
75+
data: {turbo: false}
7576
) %>
7677
<% end %>
7778

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div id="resource_count">
2+
<hr class="border-gray-300 my-6">
3+
<div class="filters-applied w-full flex gap-3 items-center">
4+
<h3 class="text-sm font-semibold uppercase text-gray-500 tracking-wide">Resources ( <%= @count_display %>)</h3>
5+
<%= render "shared/copy_url" %>
6+
</div>
7+
</div>
Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
<%= form_tag resources_path, method: :get, class: "space-y-2" do %>
1+
<% default_btn = "text-gray-600 bg-white border border-gray-300
2+
hover:bg-gray-100 hover:text-gray-800 shadow-sm" %>
3+
4+
<% selected_btn = "text-white #{ DomainTheme.bg_class_for(:resources, intensity: 600) } border-indigo-600
5+
#{ DomainTheme.bg_class_for(:resources, intensity: 700, hover: true) } shadow-md ring-2 ring-indigo-300" %>
6+
7+
<%= form_tag resources_path, method: :get,
8+
data: {controller: "collection share-url",
9+
collection_unselected_class: default_btn,
10+
collection_selected_class: selected_btn,
11+
turbo_frame: "resource_results" },
12+
autocomplete: "off",
13+
class: "space-y-2" do %>
214
<!-- Search Fields -->
315
<div class="flex flex-wrap md:flex-nowrap items-end gap-6">
4-
516
<!-- KEYWORDS -->
617
<div class="flex-1 min-w-[200px] pb-2">
718
<label for="query" class="block text-xs font-semibold uppercase text-gray-500 tracking-wide mb-1">
@@ -11,38 +22,34 @@
1122
<%= text_field_tag :query, params[:query],
1223
class: "w-full bg-white border border-gray-300 rounded-lg px-3 py-2 pr-10
1324
focus:ring-blue-500 focus:border-blue-500" %>
14-
<button type="submit"
15-
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700">
25+
<button type="submit" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700">
1626
<i class="fa fa-search"></i>
1727
</button>
1828
</div>
1929
</div>
2030

2131
<!-- PUBLISHED -->
2232
<div class="min-w-[150px] admin-only bg-blue-100 p-2 rounded-md">
23-
<label for="published"
24-
class="block text-xs font-semibold uppercase text-gray-600 tracking-wide mb-1">
33+
<label for="published" class="block text-xs font-semibold uppercase text-gray-600 tracking-wide mb-1">
2534
Published
2635
</label>
2736
<%= select_tag :published_search,
2837
options_for_select(
2938
[["All", ""], ["Published", "true"], ["Hidden", "false"]],
3039
params[:published_search]
3140
),
32-
class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full",
33-
onchange: "this.form.submit();" %>
41+
class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full" %>
3442
</div>
3543

3644
<!-- KINDS -->
37-
<div class="min-w-[150px]">
38-
<%= render "search_boxes_kinds", sortable_fields: Resource::PUBLISHED_KINDS %>
39-
</div>
45+
<div class="min-w-[150px]"><%= render "search_boxes_kinds", default_btn: default_btn %></div>
4046

4147
<!-- CLEAR FILTERS -->
4248
<div class="min-w-[120px] pb-3">
4349
<label class="block text-xs font-semibold uppercase text-gray-500 tracking-wide mb-1">&nbsp;</label>
4450
<%= link_to "Clear filters", resources_path, class: "btn btn-utility" %>
4551
</div>
46-
4752
</div>
53+
54+
<%= render "resource_count" %>
4855
<% end %>
Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,26 @@
1-
<% default_btn = "text-gray-600 bg-white border border-gray-300
2-
hover:bg-gray-100 hover:text-gray-800 shadow-sm" %>
3-
4-
<% selected_btn = "text-white #{ DomainTheme.bg_class_for(:resources, intensity: 600) } border-indigo-600
5-
#{ DomainTheme.bg_class_for(:resources, intensity: 700, hover: true) } shadow-md ring-2 ring-indigo-300" %>
6-
71
<div class="mb-3">
82
<h3 class="text-xs font-semibold text-gray-500 tracking-wide mb-1 uppercase">
93
Kinds (click to filter)
104
</h3>
115

126
<div class="flex flex-wrap gap-2">
13-
<% sortable_fields.each do |sortable_field| %>
7+
<% Resource::PUBLISHED_KINDS.each do |sortable_field| %>
148
<% name = sortable_field.is_a?(String) ? sortable_field : sortable_field.name %>
159

16-
<label for="kind_<%= name %>"
17-
class="
10+
<label
11+
for="kind_<%= name %>"
12+
class="
1813
inline-flex items-center cursor-pointer
1914
px-4 py-2 rounded-lg font-medium text-sm
2015
transition-all duration-150
21-
<%= name.to_s == params[:kind] ? selected_btn : default_btn %>
22-
">
23-
<%= check_box_tag(:kind, name, name.to_s == params[:kind],
24-
id: "kind_#{name}",
25-
class: "hidden") %>
16+
<%= default_btn %>
17+
"
18+
>
19+
<%= check_box_tag("kinds[]", name, Array(params[:kinds]).include?(name),
20+
id: "kind_#{name}",
21+
class: "hidden") %>
2622
<%= name.titleize.pluralize %>
2723
</label>
28-
29-
<script>
30-
document.addEventListener("turbo:load", function () {
31-
const el = document.getElementById("kind_<%= name %>")
32-
if (el) {
33-
el.addEventListener("change", function () {
34-
const url = new URL(window.location.href)
35-
url.searchParams.set("kind", "<%= name %>")
36-
url.searchParams.delete("show_all")
37-
window.location.href = url.toString()
38-
})
39-
}
40-
})
41-
</script>
42-
4324
<% end %>
4425
</div>
4526
</div>

0 commit comments

Comments
 (0)