Студия разработки сайтов и приложений

Netspark.ru

Заметки и разработки

Drupal

Кнопка Load More на AJAX в OctoberCMS

Работу с AJAX в OctoberCMS я уже раньше немного затрагивал, но для бэк-энда. А теперь можно затронуть и фронт. В этой заметке мы сделаем кнопочку Load More, чтобы подгружала без перезагрузки страницы новые посты блога (из плагина RainLab.Blog).

На первый взгляд может показаться, что для этого нужно влезть в компонент Posts этого плагина, но на самом деле — вовсе не нужно. Дело в том, что раньше из документации можно было подумать, что обработчики AJAX-событий нужно описывать только в классе контроллера (если в бэк-энде), или компонента (если во фронте). И поэтому не очень было понятно, как быть, когда ты хочешь получить красивый ajax-пейджер для блога. То ли лезть в код чужого плагина, что влечет проблемы с обновлениями. То ли писать свой собственный компонент, из которого грузить компонент Posts программно. Что ведет к сомнениям в том, надо ли вообще такой негибкой CMS пользоваться.

Но то ли документацию дополнили, то ли в глазах у меня прояснилось (а может и то, и другое) — в общем, выяснилось, что в хендлеры ajax-событий годятся не только методы контроллеров/компонентов, но и функции, размещенные в секции PHP страниц и лейаутов (но не паршлов).

То есть мы можем сделать в htm-файле страницы вот так:

url = "/blog/posts"

[blogPosts allPosts]
pageNumber = 1
postsPerPage = 12
noPostsMessage = "No posts found"
sortOrder = "published_at desc"
==
function onLoadMore()
{
    // загрузить еще постов
}
==
<div class="blog-list">
  {% partial 'blogPosts' %}
</div>

И функцию onLoadMore() можно будет использовать как обработчик ajax-события. Тогда мы сможем сделать кнопку, не трогая плагин и не умножая сущности.

Допустим у нас есть такая разметка:

<div>
    {% component 'allPosts' %}
</div>
<div id="load-more-button">
    <a data-request="onLoadMore" data-request-data="page: 1" href="#">Load More</a>
</div>

Компонент allPosts — это лента постов Posts из плагина RainLab.Blog. Кнопку загрузки мы выводим вне компонента с постами, а сам компонет выглядит как-нибудь так:

<div id="all-posts-content" class="row">
    {% for post in posts %}
    <!-- Здесь выводятся посты, как обычно -->
    {% endfor %}
</div>

По клику на кнопку Load More на сервере будет вызвана функция onLoadMore, которую мы добавили в php-секцию нашей страницы:

function onLoadMore() {
    // получаем номер страницы
    $pageNumber = Input::get('page') + 1;
    // обращаемся к компоненту Posts с alias allPosts: 
    // выставляем номер страницы и готовим данные
    $this->components['allPosts']->setProperty('pageNumber', $pageNumber);
    $this->components['allPosts']->onRun();
    if ($pageNumber < $this->components['allPosts']->posts->lastPage()) {
        // для простоты мы генерируем html, но в принципе можно сделать и отдельный паршл для кнопки
        $more_link = '<a data-request="onLoadMore" data-request-data="page: ' . $pageNumber . '" href="#">Load More</a>';
    } else {
        // если мы достигли последней страницы, кнопка больше не нужна
        $more_link = '';
    }
    return [
        // если перед селектором стоит @, новое содержимое будет добавляться в конец, а не заменять старое
        '@#all-posts-content' => $this->components['allPosts']->renderPartial('@default.htm'),
        '#load-more-button' => $more_link,
    ];
}

Обратите внимание, что на сервер будут переданы данные из data-request-data, то есть номер страницы. Он используется, чтобы можно было нажимать на кнопку многократно и подгружать следующие блоки данных. Для этого мы в конце и код самой кнопки перегружаем — чтобы новый номер страницы вывести.

Вот и всё. Остальное за нас делает OctoberCMS.

Комментарии