【发布时间】:2022-11-02 09:33:41
【问题描述】:
我正在尝试使用 Rails 7、Stimulus/Turbo 和 Kaminari gem 在鸡尾酒配方应用程序中实现无限滚动以进行分页。当我只是过滤从控制器发送的所有记录时,一切都正常运行。这意味着,当我向下滚动页面时,页码会正确增加,并且每个新的记录页面都会以正确的顺序更新。
但是,当我向控制器发送过滤或排序参数时,行为会变得异常不稳定。新的记录页继续从控制器发送,但它发送两个或三个重复页或将页码提前几页。
我花了太多时间试图弄清楚我在这里做错了什么,如果那里有任何亲切的灵魂可能知道我错过了什么愚蠢的东西,我将不胜感激。
这是我的鸡尾酒食谱控制器#index,我根据排序选项(所有成分、任何成分等)、鸡尾酒类别和/或一组特定选择的成分(杜松子酒、薄荷、苹果杰克)来提供记录, ETC。)
def index
@page = params[:page] || 1
category_id = params[:categoryId]
ingredient_ids = params[:ingredientIds] ? [*params[:ingredientIds]].map(&:to_i) : nil
@recipes = Recipe.alphabetical.page(@page)
case params[:sort_option]
when ''
params[:ingredientIds] ?
@recipes = Recipe.alphabetical.filter_all_recipes(ingredient_ids, category_id).page(@page) :
@recipes = Recipe.filter_all_by_category(category_id).page(@page)
respond_to do |format|
# needed to explicitly call formats: [:html] after adding turbo_stream option
format.html { render partial: 'recipe_cards', formats: [:html] }
format.turbo_stream
end
when 'All Recipes'
params[:ingredientIds] ?
@recipes = Recipe.alphabetical.filter_all_recipes(ingredient_ids, category_id).page(@page) :
@recipes = Recipe.filter_all_by_category(category_id).page(@page)
respond_to do |format|
format.html { render partial: 'recipe_cards', formats: [:html] }
format.turbo_stream
end
when 'Any Ingredient'
params[:ingredientIds] ?
@recipes = Recipe.alphabetical.match_any_subset(ingredient_ids, current_user.ingredients, category_id).page(@page) :
@recipes = Recipe.alphabetical.match_any_ingredient(current_user.ingredients, category_id).page(@page)
respond_to do |format|
format.html { render partial: 'recipe_cards', formats: [:html] }
format.turbo_stream
end
when 'All Ingredients'
params[:ingredientIds] ?
@possible_recipes = Recipe.match_all_subset(params[:recipeIds], ingredient_ids, category_id).page(@page) :
@possible_recipes = Recipe.alphabetical.match_all_ingredients(current_user.ingredients, category_id).page(@page)
respond_to do |format|
format.html { render partial: 'recipe_cards', formats: [:html] }
format.turbo_stream
end
end
end
这是我的分页刺激控制器:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
// gets/sets record fetching flag
static get fetching() { return this.fetching; }
static set fetching(bool) {
this.fetching = bool;
}
// gets url and page number from target element
static get values() { return {
url: String,
page: { type: Number, default: 1 },
};}
// adds the scroll event listener and sets fetching flag to false
connect() {
console.log("Pagination Controller Loaded");
document.addEventListener('scroll', this.scroll);
this.fetching = false;
}
// binds this to the controller rather than document
initialize() {
this.scroll = this.scroll.bind(this);
}
// calls loadRecords() when scroll reaches the bottom of the page
scroll() {
if (this.pageEnd && !this.fetching) {
this.loadRecords();
}
}
// record fetching function
async loadRecords() {
// get pre-configured url from helper method
const url = getUrl(this.urlValue, this.pageValue);
// sets fetching flag to true
this.fetching = true;
// sends a turbo_stream fetch request to the recipes controller
await fetch(url.toString(), {
headers: {
Accept: 'text/vnd.turbo-stream.html',
},
}).then(r => r.text())
.then(html => Turbo.renderStreamMessage(html));
// sets fetching flag to false
this.fetching = false;
// increments the target element's
this.pageValue += 1;
}
// sets the boundary where the loadRecords() function gets called
get pageEnd() {
const { scrollHeight, scrollTop, clientHeight } = document.documentElement;
return scrollHeight - scrollTop - clientHeight < 40; // can adjust to desired limit
}
}
// ------------- HELPER FUNCTIONS ----------------
// gets selected ingredient ids from liquor cabinet display
// options and returns them in an array
function getIngredientIds() {
var ingredientIds = [...$('.cabinet-spirits').val(),
...$('.cabinet-modifiers').val(),
...$('.cabinet-sugars').val(),
...$('.cabinet-garnishes').val()];
return ingredientIds;
}
// if there are ingredientIds, appends them as an array to searchParams
function appendIngredientIds(url) {
var ingredientIds = getIngredientIds();
if (ingredientIds.length != 0) {
ingredientIds.map(i => url.searchParams.append('ingredientIds', i));
}
return url;
}
// configures url searchParams and returns the url
function getUrl(urlValue, pageValue) {
var url = new URL(urlValue);
url.searchParams.set('page', pageValue);
url.searchParams.append('sort_option', $('.sort-options').val());
url = appendIngredientIds(url);
return url;
}
这是 index.turbo.erb:
<%= turbo_stream.append 'recipes' do %>
<% @recipes.each do |recipe| %>
<%= render partial: "recipe_card", locals: { recipe: recipe } %>
<% end %>
<% end %>
最后,我将新记录附加到的目标 div:
<div class="container-fluid mt-2 mx-3">
<div id="recipes" class="row row-cols-lg-5 row-cols-md-4 row-cols-sm-3 g-2"
data-controller='pagination'
data-pagination-target='recipes'
data-pagination-url-value='<%= recipes_url %>'
data-pagination-page-value='<%= 2 %>'>
<% @recipes.each do |recipe| %>
<%= render partial: "recipe_card", locals: { recipe: recipe } %>
<% end %>
</div>
</div>
我已经在 devTools 中监视了页面增量,看起来对于配方控制器的每个额外的 ajax 调用,分页控制器都会被调用一次额外的时间。因此,如果我按“任何成分”对结果进行排序,我会在滚动时开始获得重复的页面。如果我然后按波旁酒过滤这些结果,3 页(不一定按顺序),开始加载滚动。我觉得我可能缺少一些明显的东西。
【问题讨论】:
标签: infinite-scroll kaminari ruby-on-rails-7 stimulusjs turbo