Статьи

Бесконечная прокрутка в Rails: основы

no_load_more

Нумерация страниц — очень распространенная и широко используемая навигационная техника, и на то есть веские причины. Прежде всего, рассмотрим производительность. Загрузка всех доступных записей в одном запросе может быть очень дорогой. Более того, пользователь может быть заинтересован только в нескольких самых последних записях (т. Е. В последних публикациях в блоге) и не хочет ждать загрузки и отображения всех записей. Кроме того, разбиение на страницы облегчает чтение страницы, не заполняя ее содержимым.

В настоящее время многие веб-сайты используют немного другую технику, называемую бесконечной прокруткой (или бесконечной страницей ). По сути, когда пользователь прокручивает страницу вниз, все больше записей загружаются асинхронно с использованием AJAX. Таким образом, прокрутка кажется более естественной и может быть проще для пользователя, чем постоянное нажатие на ссылку «Следующая страница».

В этой статье я собираюсь объяснить, как реализовать бесконечную прокрутку вместо классической нумерации страниц.

Сначала мы подготовим наш демонстрационный проект, реализуя базовую нумерацию страниц, используя гем will_paginate . Эта нумерация страниц станет бесконечной прокруткой по мере прохождения учебника. Это потребует написания некоторого кода JavaScript (и CoffeeScript) вместе с нашим Ruby.

Предоставленное решение откатится на нумерацию страниц по умолчанию, если у пользователя отключен JavaScript в браузере. Наконец, наш взгляд практически не требует изменений, поэтому вы можете легко реализовать это на любом веб-сайте.

Другие предметы, которые будут покрыты:

  • Как реализовать кнопку «Загрузить еще» вместо бесконечной прокрутки, очень похожую на
    используется на SitePoint .
  • Некоторые ошибки и потенциальные проблемы, в частности, как может помочь History API и шпионская прокрутка.

Рабочую демонстрацию можно найти по адресу http://sitepoint-infinite-scrolling.herokuapp.com .

Исходный код можно найти на GitHub .

Звучит неплохо? Давай прокатимся!

Подготовка проекта

В этой статье я буду использовать Rails 3.2.16, но вы можете реализовать то же решение с Rails 4.

Давайте создадим очень простой блог. На данный момент он будет отображать только демонстрационные сообщения.

 $ rails new infinite_scrolling -T 

-T здесь означает, что мы хотим пропустить генерацию набора тестов (я предпочитаю RSpec, но, конечно, вы можете опустить этот флаг).

Мы собираемся подключить некоторые драгоценные камни, которые пригодятся:

Gemfile

 gem 'will_paginate', '~> 3.0.5' gem 'betterlorem', '~> 0.1.2' gem 'bootstrap-sass', '~> 3.0.3.0' gem 'bootstrap-will_paginate', '~> 0.0.10' 

will_paginate будет, ну, ну же, разбивать наши записи Я более подробно расскажу об этом драгоценном камне в следующем разделе. betterlorem создает демо-текст в наших записях. Существуют и другие подобные драгоценные камни, которые создают текст «Lorem Ipsum», но я считаю, что он наиболее удобен для нашего случая (мы собираемся использовать его в seeds.rb , а не в представлении).

Нет смысла создавать удостоенный наград дизайн, поэтому в качестве быстрого и простого (хотя и не самого маленького, учитывая вес) решения мы будем использовать Twitter Bootstrap 3. Самоцвет bootstrap-sass добавляет его в наш проект Rails. bootstrap-will_paginate содержит некоторые стили Bootstrap для самой нумерации страниц.

Не забывай бегать

 $ bundle install 

Теперь добавьте

 //= require bootstrap 

в application.js и

 @import "bootstrap"; 

в application.css.scss чтобы включить все стили и сценарии Bootstrap. Конечно, в реальном приложении вы бы выбрали только необходимые компоненты.

Модель

Там будет только одна таблица: пост. Это будет очень просто и содержать следующее
колонки:

  • id (целое число, первичный ключ)
  • title (строка)
  • body (текст)
  • created_at (дата / время)
  • updated_at (datetime)

Бег

 $ rails g model Post title:string body:text $ rake db:migrate 

создаст соответствующую миграцию и затем применяет ее к базе данных.

Следующим шагом является создание некоторых тестовых данных. Самый простой способ сделать это — использовать seeds.rb

seeds.rb

 50.times { |i| Post.create(title: "Post #{i}", body: BetterLorem.p(5, false, false)) } 

Это создает 50 сообщений с body созданным BetterLorem . Каждый набор генерируемого контента состоит из 5 параграфов. Последние два аргумента говорят BetterLorem обернуть текст в тег p и включить конечный период.

Бег

 $ rake db:seed 

заполнит нашу базу данных несколькими тестовыми сообщениями. Потрясающие!

Последнее, что нужно сделать — это создать PostsController с методами index и show вместе с соответствующими представлениями ( index.html.erb и show.html.erb ). Также не забудьте настроить маршруты:

routes.rb

 resources :posts, only: [:index, :show] root to: 'posts#index' 

Наконец, если вы используете Rails 3, обязательно удалите файл public/index.html .

Контроллер

Мы готовы перейти к веселой части. Сначала давайте отобразим постраничные посты с усеченным телом. Для этого мы будем использовать will_paginate — простой, но удобный драгоценный камень от Mislav Marohnić, который работает с Ruby on Rails, Sinatra, Merb, DataMapper и Sequel.

Существует альтернатива этому решению — kaminari от Akira Matsuda, который является более мощным и более сложным. Вы также можете попробовать. По сути, не имеет значения, какой драгоценный камень вы используете.

В нашем контроллере:

posts_controller.rb

 @posts = Post.paginate(page: params[:page], per_page: 15).order('created_at DESC') 

Вызов метода paginate принимает параметр page сообщающий ему, какой параметр GET использовать для получения требуемого номера страницы. Параметр per_page указывает, сколько записей должно отображаться на странице. Опция per_page может быть указана для всей модели или для всего проекта следующим образом:

post.rb

 class Post self.per_page = 10 end 

will_paginate.rb (в инициализаторе)

 WillPaginate.per_page = 10 

Метод paginate возвращает ActiveRecord::Relation поэтому мы можем связать вызов метода order .

Вид

index.html.erb

 <div class="page-header"> <h1>My posts</h1> </div> <div id="my-posts"> <%= render @posts %> </div> <div id="infinite-scrolling"> <%= will_paginate %> </div> 

Заголовок страницы указывается с помощью класса Bootstrap. Следующий блок, #my-posts , содержит наши постраничные посты. При использовании render @posts каждый пост из массива отображается с использованием частичного _post.html.erb . Последний блок, #infinite-scrolling , содержит элементы управления нумерацией страниц.

Обратите внимание, что will_paginate достаточно умен, чтобы понять, что мы хотим @posts на страницы @posts . Вы можете указать это явно так: will_paginate @posts .

Вот наш пост частичный.

_post.html.erb

 <div> <h2><%= link_to post.title, post_path(post) %></h2> <small><em><%= post.timestamp %></em></small> <p><%= truncate(strip_tags(post.body), length: 600) %></p> </div> 

Мы заключаем каждое сообщение в div , а затем отображаем заголовок, который служит ссылкой для чтения всего сообщения. Отметка времени указывает, когда сообщение было создано. Этот метод timestamp определяется внутри модели следующим образом:

post.rb

 def timestamp created_at.strftime('%d %B %Y %H:%M:%S') end 

Наконец, мы используем метод strip_tags чтобы удалить все теги из записи и strip_tags чтобы удалить все, кроме первых 600 символов. На этом наша работа заканчивается представлениями (я опустил разметку для layout.html.erb и show.html.erb как это не так важно; вы можете проверить это в репозитории GitHub ).

Бесконечная прокрутка

Мы готовы превратить нашу нумерацию страниц в бесконечную прокрутку. jQuery поможет нам с этой задачей.

Создайте новый файл pagination.js.coffee в каталоге javascripts .

pagination.js.coffee

 jQuery -> if $('#infinite-scrolling').size() > 0 $(window).on 'scroll', -> more_posts_url = $('.pagination .next_page a').attr('href') if more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 60 $('.pagination').html('<img src="/assets/ajax-loader.gif" alt="Loading..." title="Loading..." />') $.getScript more_posts_url return return 

Здесь мы привязываем событие scroll к окну, только если на странице присутствует разбиение на страницы. Когда пользователь прокручивает, извлекает ссылку на следующую страницу — при ее посещении Rails загружает записи с этой страницы (нам все еще нужно внести некоторые изменения в контроллер, чтобы это работало).

Затем убедитесь, что URL-адрес присутствует, и пользователь прокрутил до конца страницы минус 60px . Это когда мы хотим загрузить больше сообщений. Это значение 60px является произвольным, и вы, вероятно, захотите изменить его для своего случая.

Если эти условия выполняются, мы заменяем нашу пагинацию «загрузочным» GIF-изображением, которое можно бесплатно загрузить с ajaxload.info . Последнее, что нужно сделать, — это выполнить асинхронный запрос, используя URL, который мы получили ранее. $.getScript загрузит JS-скрипт с сервера и затем выполнит его.

Обратите внимание на две инструкции по return . По умолчанию CoffeeScript вернет последнее выражение (та же концепция применима к Ruby), но здесь мы не хотим, чтобы функция jQuery или обработчик событий возвращали что-либо, поэтому указание return означает «ничего не возвращать».

PostController#index метод PostController#index должен отвечать как на HTML, так и на JavaScript. Мы собираемся использовать respond_to для достижения этого:

posts_controller.rb

 @posts = Post.paginate(page: params[:page], per_page: 15).order('created_at DESC') respond_to do |format| format.html format.js end 

Последнее, что нужно сделать, это создать представление, которое будет отображаться при ответе с помощью JS:

index.js.erb

 $('#my-posts').append('<%= j render @posts %>'); <% if @posts.next_page %> $('.pagination').replaceWith('<%= j will_paginate @posts %>'); <% else %> $(window).off('scroll'); $('.pagination').remove(); <% end %> 

Мы обрабатываем больше постов, добавляя их в блок #my-posts . После этого проверьте, не осталось ли еще страниц. Если это так, замените текущий блок нумерации страниц (который в данный момент содержит изображение «загрузки») новой нумерацией страниц. В противном случае мы удаляем элементы управления нумерацией страниц и отсоединяем событие scroll от window , так как больше нет смысла его слушать.

На данный момент наша бесконечная прокрутка готова. Даже если у пользователя отключен JavaScript в браузере, он будет представлен с нумерацией страниц по умолчанию, к которой применен стиль, благодаря bootstrap-will_paginate .

Стоит упомянуть одну вещь: прокрутка вызовет множество событий scroll . Если вы хотите отложить обработку этого события, вы можете использовать библиотеку OpenSource BindWithDelay, написанную Брайаном Гринстедом. Чтобы использовать его, просто скачайте его и включите в проект. Затем внесите следующие изменения в скрипт:

pagination.js.coffee

 $(window).bindWithDelay 'scroll', -> # the code , 100 

Это задержит запуск события на 100 мс. $(window).off('scroll'); внутри index.js.erb все равно будет index.js.erb события, поэтому никаких изменений там не требуется.

На этом первая часть статьи заканчивается. В следующей части мы поговорим о кнопке «Загрузить еще» и некоторых проблемах, возникающих при использовании бесконечной прокрутки. Спасибо за чтение и до скорой встречи!