pagy_infinite_scroll 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +74 -39
- data/app/assets/javascripts/pagy_infinite_scroll/infinite_scroll_controller.js +43 -25
- data/app/assets/javascripts/pagy_infinite_scroll_controller.js +43 -25
- data/lib/generators/pagy_infinite_scroll/install/install_generator.rb +13 -1
- data/lib/generators/pagy_infinite_scroll/install/templates/initializer.rb +8 -0
- data/lib/pagy_infinite_scroll/configuration.rb +3 -1
- data/lib/pagy_infinite_scroll/engine.rb +3 -2
- data/lib/pagy_infinite_scroll/version.rb +1 -1
- data/lib/pagy_infinite_scroll/view_helper.rb +62 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b82bfda46dc2de115eeaac522f4f982df35e6517413522ff12446bef637e3b03
|
|
4
|
+
data.tar.gz: abf34ba36bdf896c79a4fb723b287d65c55d70f5487bcec92259486f41c1bb28
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2655b2c6353d27bd94f8839aeaa1203c0ab6a3f13e2f4a38b409760fefb31dcabcab48b2dc4519c1153d1688cbaea908be022dae1fb6aab8069caba61234aadf
|
|
7
|
+
data.tar.gz: 4e6d2003f79144666d2ca6f28d20bca2d29613c1ca52a1d2f1266510adadc5cef07277f0723b0d1674a239de14209050445a920b26f6d05d18977ba3151d6be8
|
data/README.md
CHANGED
|
@@ -16,7 +16,7 @@ A Rails gem that adds infinite scroll functionality using Pagy and Stimulus. Loa
|
|
|
16
16
|
Add to your Gemfile:
|
|
17
17
|
|
|
18
18
|
```ruby
|
|
19
|
-
gem 'pagy_infinite_scroll',
|
|
19
|
+
gem 'pagy_infinite_scroll', '~> 0.2.0'
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Then run:
|
|
@@ -44,6 +44,19 @@ The generator will:
|
|
|
44
44
|
|
|
45
45
|
## Quick Start
|
|
46
46
|
|
|
47
|
+
This gem provides **two approaches** for infinite scrolling:
|
|
48
|
+
|
|
49
|
+
1. **Server-Side Rendering** (Simple) - HTML rendered on the server using `.js.erb` templates
|
|
50
|
+
2. **JSON API** (Advanced) - JSON responses with client-side HTML rendering
|
|
51
|
+
|
|
52
|
+
Choose the approach that fits your needs!
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Approach 1: Server-Side Rendering (Recommended for Simple Use Cases)
|
|
57
|
+
|
|
58
|
+
This approach is simpler and requires minimal JavaScript knowledge. Perfect for standard CRUD operations.
|
|
59
|
+
|
|
47
60
|
### 1. Controller Setup
|
|
48
61
|
|
|
49
62
|
Use the gem's helper methods:
|
|
@@ -51,13 +64,61 @@ Use the gem's helper methods:
|
|
|
51
64
|
```ruby
|
|
52
65
|
class ProductsController < ApplicationController
|
|
53
66
|
def index
|
|
54
|
-
|
|
67
|
+
@pagy, @products = pagy_infinite_scroll(Product.all, limit: 50)
|
|
68
|
+
|
|
69
|
+
respond_to do |format|
|
|
70
|
+
format.html
|
|
71
|
+
format.js # Responds to .js.erb template
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. View Setup (HTML)
|
|
78
|
+
|
|
79
|
+
```erb
|
|
80
|
+
<!-- app/views/products/index.html.erb -->
|
|
81
|
+
<%= infinite_scroll_container(@pagy, products_path(format: :js),
|
|
82
|
+
data: { render_mode: 'js' }) do %>
|
|
83
|
+
<%= infinite_scroll_items_container(tag: 'div', class: 'products-list') do %>
|
|
84
|
+
<%= render @products %>
|
|
85
|
+
<% end %>
|
|
86
|
+
<%= infinite_scroll_loading_indicator %>
|
|
87
|
+
<% end %>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Create JavaScript Response Template
|
|
91
|
+
|
|
92
|
+
```erb
|
|
93
|
+
<!-- app/views/products/index.js.erb -->
|
|
94
|
+
<%= pagy_infinite_scroll_append '.products-list', @pagy, @products %>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**That's it!** The gem automatically:
|
|
98
|
+
- Renders your `_product.html.erb` partial for each item
|
|
99
|
+
- Appends the HTML to the container
|
|
100
|
+
- Updates pagination state
|
|
101
|
+
- No JavaScript customization needed!
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Approach 2: JSON API (For Complex Use Cases)
|
|
106
|
+
|
|
107
|
+
Use this approach when you need:
|
|
108
|
+
- API reusability (mobile apps, SPAs)
|
|
109
|
+
- Complex client-side logic
|
|
110
|
+
- Full control over rendering
|
|
111
|
+
|
|
112
|
+
### 1. Controller Setup
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
class ProductsController < ApplicationController
|
|
116
|
+
def index
|
|
55
117
|
@pagy, @products = pagy_infinite_scroll(Product.all, limit: 50)
|
|
56
118
|
|
|
57
119
|
respond_to do |format|
|
|
58
120
|
format.html
|
|
59
121
|
format.json do
|
|
60
|
-
# Use the JSON helper to format response
|
|
61
122
|
render json: pagy_infinite_scroll_json(@pagy, @products) { |product|
|
|
62
123
|
{
|
|
63
124
|
id: product.id,
|
|
@@ -73,45 +134,18 @@ end
|
|
|
73
134
|
|
|
74
135
|
### 2. View Setup
|
|
75
136
|
|
|
76
|
-
Add the infinite scroll container to your view:
|
|
77
|
-
|
|
78
137
|
```erb
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<!-- Container for items -->
|
|
86
|
-
<div data-pagy-infinite-scroll-target="itemsContainer">
|
|
87
|
-
<% @products.each do |product| %>
|
|
88
|
-
<div class="product-card">
|
|
89
|
-
<h3><%= product.title %></h3>
|
|
90
|
-
<p><%= product.price %></p>
|
|
91
|
-
</div>
|
|
92
|
-
<% end %>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<!-- Loading indicator -->
|
|
96
|
-
<div data-pagy-infinite-scroll-target="loadingIndicator" class="hidden">
|
|
97
|
-
<p>Loading more...</p>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
138
|
+
<%= infinite_scroll_container(@pagy, products_path(format: :json)) do %>
|
|
139
|
+
<%= infinite_scroll_items_container do %>
|
|
140
|
+
<%= render @products %>
|
|
141
|
+
<% end %>
|
|
142
|
+
<%= infinite_scroll_loading_indicator %>
|
|
143
|
+
<% end %>
|
|
100
144
|
```
|
|
101
145
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
**The gem provides the core infinite scroll functionality, but you need to tell it how to render YOUR specific HTML.**
|
|
105
|
-
|
|
106
|
-
### Why?
|
|
107
|
-
|
|
108
|
-
The gem cannot know:
|
|
109
|
-
- Your specific HTML structure (forms, checkboxes, badges, etc.)
|
|
110
|
-
- Your CSS classes and styling
|
|
111
|
-
- Your form field names
|
|
112
|
-
- Your Stimulus action targets
|
|
146
|
+
### 3. Create Custom Stimulus Controller
|
|
113
147
|
|
|
114
|
-
|
|
148
|
+
**For JSON API approach, you MUST override the `createItemHTML` method** to tell the gem how to render your specific HTML.
|
|
115
149
|
|
|
116
150
|
**For jsbundling-rails apps:**
|
|
117
151
|
|
|
@@ -221,11 +255,12 @@ Edit `config/initializers/pagy_infinite_scroll.rb`:
|
|
|
221
255
|
|
|
222
256
|
```ruby
|
|
223
257
|
PagyInfiniteScroll.configure do |config|
|
|
224
|
-
config.items_per_page = 50 # Items per page (default:
|
|
258
|
+
config.items_per_page = 50 # Items per page (default: 50)
|
|
225
259
|
config.scroll_threshold = 100 # Pixels from bottom to trigger load (default: 100)
|
|
226
260
|
config.loading_indicator = true # Show loading indicator (default: true)
|
|
227
261
|
config.preserve_state = true # Preserve URL params (default: true)
|
|
228
262
|
config.debounce_delay = 500 # Debounce for search in ms (default: 500)
|
|
263
|
+
config.render_mode = 'json' # Rendering mode: 'json' or 'js' (default: 'json')
|
|
229
264
|
end
|
|
230
265
|
```
|
|
231
266
|
|
|
@@ -10,7 +10,8 @@ export default class PagyInfiniteScrollController extends Controller {
|
|
|
10
10
|
loading: Boolean,
|
|
11
11
|
hasMore: Boolean,
|
|
12
12
|
threshold: { type: Number, default: 100 },
|
|
13
|
-
preserveState: { type: Boolean, default: true }
|
|
13
|
+
preserveState: { type: Boolean, default: true },
|
|
14
|
+
renderMode: { type: String, default: 'json' } // 'json' or 'js'
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
connect() {
|
|
@@ -19,6 +20,9 @@ export default class PagyInfiniteScrollController extends Controller {
|
|
|
19
20
|
this.loadingValue = false
|
|
20
21
|
this.boundHandleScroll = this.handleScroll.bind(this)
|
|
21
22
|
this.element.addEventListener('scroll', this.boundHandleScroll)
|
|
23
|
+
|
|
24
|
+
// Make controller accessible for server-side updates
|
|
25
|
+
this.element.pagyInfiniteScroll = this
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
disconnect() {
|
|
@@ -55,9 +59,14 @@ export default class PagyInfiniteScrollController extends Controller {
|
|
|
55
59
|
}
|
|
56
60
|
})
|
|
57
61
|
|
|
62
|
+
// Determine Accept header based on render mode
|
|
63
|
+
const acceptHeader = this.renderModeValue === 'js'
|
|
64
|
+
? 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
|
|
65
|
+
: 'application/json'
|
|
66
|
+
|
|
58
67
|
const response = await fetch(url, {
|
|
59
68
|
headers: {
|
|
60
|
-
'Accept':
|
|
69
|
+
'Accept': acceptHeader,
|
|
61
70
|
'X-Requested-With': 'XMLHttpRequest'
|
|
62
71
|
}
|
|
63
72
|
})
|
|
@@ -66,32 +75,41 @@ export default class PagyInfiniteScrollController extends Controller {
|
|
|
66
75
|
throw new Error(`HTTP error! status: ${response.status}`)
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// Handle different response types
|
|
79
|
+
if (this.renderModeValue === 'js') {
|
|
80
|
+
// For .js.erb responses, evaluate the JavaScript
|
|
81
|
+
const jsCode = await response.text()
|
|
82
|
+
eval(jsCode)
|
|
83
|
+
// Note: State is updated by the server-rendered JS via pagy_infinite_scroll_append
|
|
84
|
+
} else {
|
|
85
|
+
// For JSON responses, use the client-side rendering
|
|
86
|
+
const data = await response.json()
|
|
87
|
+
|
|
88
|
+
// Dispatch event with data for custom handling
|
|
89
|
+
const event = this.dispatch('beforeAppend', {
|
|
90
|
+
detail: { data },
|
|
91
|
+
cancelable: true
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
if (!event.defaultPrevented) {
|
|
95
|
+
this.appendItems(data)
|
|
96
|
+
}
|
|
80
97
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
// Update pagination state
|
|
99
|
+
this.pageValue = data.pagy.page
|
|
100
|
+
this.hasMoreValue = data.pagy.next !== null
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
console.log(`[PagyInfiniteScroll] Loaded page ${this.pageValue}, has more: ${this.hasMoreValue}`)
|
|
86
103
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
// Dispatch success event
|
|
105
|
+
this.dispatch('loaded', {
|
|
106
|
+
detail: {
|
|
107
|
+
page: this.pageValue,
|
|
108
|
+
hasMore: this.hasMoreValue,
|
|
109
|
+
count: data.records.length
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
95
113
|
|
|
96
114
|
} catch (error) {
|
|
97
115
|
console.error('[PagyInfiniteScroll] Error loading more items:', error)
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
loading: Boolean,
|
|
22
22
|
hasMore: Boolean,
|
|
23
23
|
threshold: { type: Number, default: 100 },
|
|
24
|
-
preserveState: { type: Boolean, default: true }
|
|
24
|
+
preserveState: { type: Boolean, default: true },
|
|
25
|
+
renderMode: { type: String, default: 'json' } // 'json' or 'js'
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
connect() {
|
|
@@ -30,6 +31,9 @@
|
|
|
30
31
|
this.loadingValue = false
|
|
31
32
|
this.boundHandleScroll = this.handleScroll.bind(this)
|
|
32
33
|
this.element.addEventListener('scroll', this.boundHandleScroll)
|
|
34
|
+
|
|
35
|
+
// Make controller accessible for server-side updates
|
|
36
|
+
this.element.pagyInfiniteScroll = this
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
disconnect() {
|
|
@@ -66,9 +70,14 @@
|
|
|
66
70
|
}
|
|
67
71
|
})
|
|
68
72
|
|
|
73
|
+
// Determine Accept header based on render mode
|
|
74
|
+
const acceptHeader = this.renderModeValue === 'js'
|
|
75
|
+
? 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
|
|
76
|
+
: 'application/json'
|
|
77
|
+
|
|
69
78
|
const response = await fetch(url, {
|
|
70
79
|
headers: {
|
|
71
|
-
'Accept':
|
|
80
|
+
'Accept': acceptHeader,
|
|
72
81
|
'X-Requested-With': 'XMLHttpRequest'
|
|
73
82
|
}
|
|
74
83
|
})
|
|
@@ -77,32 +86,41 @@
|
|
|
77
86
|
throw new Error(`HTTP error! status: ${response.status}`)
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// Handle different response types
|
|
90
|
+
if (this.renderModeValue === 'js') {
|
|
91
|
+
// For .js.erb responses, evaluate the JavaScript
|
|
92
|
+
const jsCode = await response.text()
|
|
93
|
+
eval(jsCode)
|
|
94
|
+
// Note: State is updated by the server-rendered JS via pagy_infinite_scroll_append
|
|
95
|
+
} else {
|
|
96
|
+
// For JSON responses, use the client-side rendering
|
|
97
|
+
const data = await response.json()
|
|
98
|
+
|
|
99
|
+
// Dispatch event with data for custom handling
|
|
100
|
+
const event = this.dispatch('beforeAppend', {
|
|
101
|
+
detail: { data },
|
|
102
|
+
cancelable: true
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (!event.defaultPrevented) {
|
|
106
|
+
this.appendItems(data)
|
|
107
|
+
}
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
// Update pagination state
|
|
110
|
+
this.pageValue = data.pagy.page
|
|
111
|
+
this.hasMoreValue = data.pagy.next !== null
|
|
95
112
|
|
|
96
|
-
|
|
113
|
+
console.log(`[PagyInfiniteScroll] Loaded page ${this.pageValue}, has more: ${this.hasMoreValue}`)
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
// Dispatch success event
|
|
116
|
+
this.dispatch('loaded', {
|
|
117
|
+
detail: {
|
|
118
|
+
page: this.pageValue,
|
|
119
|
+
hasMore: this.hasMoreValue,
|
|
120
|
+
count: data.records.length
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
106
124
|
|
|
107
125
|
} catch (error) {
|
|
108
126
|
console.error('[PagyInfiniteScroll] Error loading more items:', error)
|
|
@@ -53,7 +53,19 @@ module PagyInfiniteScroll
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
say "\n"
|
|
56
|
-
say " 3.
|
|
56
|
+
say " 3. Choose your rendering approach:", :yellow
|
|
57
|
+
say "\n"
|
|
58
|
+
say " Option A: Server-Side Rendering (Simpler - Recommended for most apps)"
|
|
59
|
+
say " - Use format.js in controller"
|
|
60
|
+
say " - Create .js.erb template with: <%= pagy_infinite_scroll_append '.selector', @pagy, @records %>"
|
|
61
|
+
say " - Set data-pagy-infinite-scroll-render-mode-value='js' in view"
|
|
62
|
+
say " - No JavaScript customization needed!"
|
|
63
|
+
say "\n"
|
|
64
|
+
say " Option B: JSON API (Advanced - For APIs/SPAs)"
|
|
65
|
+
say " - Use format.json in controller"
|
|
66
|
+
say " - Create custom Stimulus controller and override createItemHTML()"
|
|
67
|
+
say " - Full control over client-side rendering"
|
|
68
|
+
say "\n"
|
|
57
69
|
say " 4. See full documentation: https://github.com/hassanharoon86/pagy_infinite_scroll"
|
|
58
70
|
say "\n"
|
|
59
71
|
end
|
|
@@ -22,4 +22,12 @@ PagyInfiniteScroll.configure do |config|
|
|
|
22
22
|
# Debounce delay for search in milliseconds
|
|
23
23
|
# Default: 500
|
|
24
24
|
config.debounce_delay = 500
|
|
25
|
+
|
|
26
|
+
# Default rendering mode for infinite scroll
|
|
27
|
+
# Options: 'json' (client-side rendering) or 'js' (server-side rendering with .js.erb)
|
|
28
|
+
# Default: 'json'
|
|
29
|
+
#
|
|
30
|
+
# 'json' mode: Requires custom Stimulus controller with createItemHTML() method
|
|
31
|
+
# 'js' mode: Uses server-rendered HTML via .js.erb templates (simpler, recommended for most apps)
|
|
32
|
+
config.render_mode = 'json'
|
|
25
33
|
end
|
|
@@ -7,7 +7,8 @@ module PagyInfiniteScroll
|
|
|
7
7
|
:loading_indicator,
|
|
8
8
|
:auto_initialize,
|
|
9
9
|
:preserve_state,
|
|
10
|
-
:debounce_delay
|
|
10
|
+
:debounce_delay,
|
|
11
|
+
:render_mode
|
|
11
12
|
|
|
12
13
|
def initialize
|
|
13
14
|
@items_per_page = 50
|
|
@@ -16,6 +17,7 @@ module PagyInfiniteScroll
|
|
|
16
17
|
@auto_initialize = true
|
|
17
18
|
@preserve_state = true
|
|
18
19
|
@debounce_delay = 500 # milliseconds
|
|
20
|
+
@render_mode = 'json' # 'json' or 'js' (for .js.erb templates)
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -7,8 +7,9 @@ module PagyInfiniteScroll
|
|
|
7
7
|
# Don't isolate namespace to allow helpers to load into main app
|
|
8
8
|
engine_name 'pagy_infinite_scroll'
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
|
|
10
|
+
# Don't eager load lib directory - we manually require what we need
|
|
11
|
+
# This avoids Zeitwerk trying to autoload version.rb as a constant
|
|
12
|
+
config.eager_load_paths.delete(File.expand_path("../../", __FILE__))
|
|
12
13
|
|
|
13
14
|
# Load assets if asset pipeline is available
|
|
14
15
|
initializer "pagy_infinite_scroll.assets", before: :load_config_initializers do |app|
|
|
@@ -33,6 +33,7 @@ module PagyInfiniteScroll
|
|
|
33
33
|
pagy_infinite_scroll_page_value: pagy.page,
|
|
34
34
|
pagy_infinite_scroll_loading_value: false,
|
|
35
35
|
pagy_infinite_scroll_has_more_value: pagy.next.present?,
|
|
36
|
+
pagy_infinite_scroll_render_mode_value: PagyInfiniteScroll.config.render_mode,
|
|
36
37
|
**stimulus_data_attributes(data_attrs)
|
|
37
38
|
} do
|
|
38
39
|
block.call
|
|
@@ -93,6 +94,67 @@ module PagyInfiniteScroll
|
|
|
93
94
|
end
|
|
94
95
|
end
|
|
95
96
|
|
|
97
|
+
# Server-side rendering helper for .js.erb templates
|
|
98
|
+
# This provides a simpler alternative to the JSON API approach
|
|
99
|
+
#
|
|
100
|
+
# @param container_selector [String] jQuery/CSS selector for the container
|
|
101
|
+
# @param pagy [Pagy] The pagy object
|
|
102
|
+
# @param records [ActiveRecord::Relation, Array] Records or render options
|
|
103
|
+
# @param options [Hash] Rendering options
|
|
104
|
+
# @option options [String] :partial Partial path (if not using collection inference)
|
|
105
|
+
# @option options [Hash] :locals Additional local variables for the partial
|
|
106
|
+
#
|
|
107
|
+
# @example Simple usage (auto-detects partial from collection)
|
|
108
|
+
# <%= pagy_infinite_scroll_append "#products", @pagy, @products %>
|
|
109
|
+
#
|
|
110
|
+
# @example With explicit partial
|
|
111
|
+
# <%= pagy_infinite_scroll_append "#products", @pagy, @products, partial: "products/card" %>
|
|
112
|
+
#
|
|
113
|
+
# @example With locals
|
|
114
|
+
# <%= pagy_infinite_scroll_append "#items", @pagy, @items, partial: "items/row", locals: { show_actions: true } %>
|
|
115
|
+
#
|
|
116
|
+
def pagy_infinite_scroll_append(container_selector, pagy, records, options = {})
|
|
117
|
+
# Render the HTML for the records
|
|
118
|
+
html = if options[:partial]
|
|
119
|
+
render partial: options[:partial], collection: records, locals: options[:locals] || {}
|
|
120
|
+
else
|
|
121
|
+
render records
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Escape the HTML for JavaScript
|
|
125
|
+
escaped_html = escape_javascript(html)
|
|
126
|
+
|
|
127
|
+
# Generate JavaScript to append items and update pagination state
|
|
128
|
+
javascript = <<~JS
|
|
129
|
+
(function() {
|
|
130
|
+
var container = document.querySelector('#{container_selector}');
|
|
131
|
+
if (container) {
|
|
132
|
+
container.insertAdjacentHTML('beforeend', '#{escaped_html}');
|
|
133
|
+
|
|
134
|
+
// Dispatch event for custom handling
|
|
135
|
+
var event = new CustomEvent('pagy-infinite-scroll:appended', {
|
|
136
|
+
detail: {
|
|
137
|
+
page: #{pagy.page},
|
|
138
|
+
hasMore: #{pagy.next.present?},
|
|
139
|
+
count: #{records.size}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
document.dispatchEvent(event);
|
|
143
|
+
|
|
144
|
+
// Update controller values if using Stimulus controller
|
|
145
|
+
var scrollContainer = container.closest('[data-controller~="pagy-infinite-scroll"]');
|
|
146
|
+
if (scrollContainer && scrollContainer.pagyInfiniteScroll) {
|
|
147
|
+
scrollContainer.pagyInfiniteScroll.pageValue = #{pagy.page};
|
|
148
|
+
scrollContainer.pagyInfiniteScroll.hasMoreValue = #{pagy.next.present?};
|
|
149
|
+
scrollContainer.pagyInfiniteScroll.loadingValue = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
JS
|
|
154
|
+
|
|
155
|
+
javascript.html_safe
|
|
156
|
+
end
|
|
157
|
+
|
|
96
158
|
private
|
|
97
159
|
|
|
98
160
|
def spinner_svg
|