PJAX в Yii2 с примерами

Виджет Pjax это JQuery плагин, который позволяет совмещать ajax и pushState для быстрой навигации по сайту. Он хорош не только в навигации, так же плагин будет полезен для организации асинхронного обмена информации между клиентом и сервером. Актуально для вывода таблиц, разбитых на несколько страниц. Для реализации голосования (лайков), обновление корзины в интернет-магазинах и многого другого. pjax example [ru]

Pjax позволяет не только отправлять данные на сервер, но и получая данные от сервера, обновлять блок на странице без перезагрузки всей страницы.

Работает это так: клиент отправляет запрос на сервер, кликнув по кнопке или ссылке, и получает в ответ от сервера необходимую информацию. Затем он обновляет старую информацию на новую попутно заменяя URL в адресной строке браузера (опционально). Это позволяет избавиться от обновления всей страницы, обновляя лишь нужную часть.

Особенность его работы в том, что обычный запрос загружает и обновляет страницу целиком, в то врем я как Pjax работает лишь с отдельной частью страницы. Приятно то, что Yii2 уже из коробки позволяет использовать Pjax и для этого нужно выполнить 2 простых шага.

Шаг 1. В верхней части view в конструкции use указать namespace виджета

<code class="php">use yii\widgets\Pjax;</code>

Шаг 2. Разместить блок обновляемой информации между вызовами Pjax::begin() и Pjax::end() символизирующих начало и конец блока.

<code class="php"><?php Pjax::begin(); ?>
    Часть страницы, которая будет обновляться
<?php Pjax::end(); ?>
</code>

Pjax поддерживает множество опций для конфигурирования. Настройки позволяют определить ссылки и формы, которые будут обновлять блок внутри Pjax (по умолчанию любая ссылка или отправка формы будет выполнять AJAX запрос). Установить правила для работы со строкой адреса URL. Время ожидания, после которого страница будет перезагружена в силу отсутствия ответа от сервера.

Примеры Pjax в Yii2

Лучше всего знакомиться на практике, чем рассказывать как там все устроено в теории. Ниже будет представлено 7 примеров, от самых легких и простых к сложным и наиболее применимым на практике.Каждый пример можно попробовать в действии пройдя по ссылке в заголовке.

Обновление блока внутри виджета

pjax example [ru] Для начала разберем самый простой пример. Есть некоторая информация на странице, которую нужно обновлять при нажатии на ссылку. Пусть некоторой информацией будет время сервера.

View

<code class="php"><?php Pjax::begin(); ?>
    <?= Html::a(
        'Обновить',
        ['/example/pjax/pjax-example-1'],
        ['class' => 'btn btn-lg btn-primary']
    ) ?>
    <p>Время сервера: <?= $time ?></p>
<?php Pjax::end(); ?></code>

В переменной $time от сервера придет время для представления по адресу /example/pjax/pjax-example-1. Кнопка "Обновить" каждый раз перерисовывается вместе с $time. Как было упомянуто выше, так происходит потому, что ссылка находится внутри обновляемого блока, а значит без особых параметров автоматически совершает AJAX запрос.

Controller

<code class="php"><?php
    public function actionPjaxExample1()
    {
        return $this->render('pjax_example_1', [
            'time' => date('H:i:s'),
        ]);
    }
?></code>

Когда Pjax отправляет запрос, он совершает вызов действия (action), и получает в ответ данные. Далее виджет обновит лишь часть страницы, которая находится между блоками Pjax::begin() и Pjax::end() без перезагрузки ее целиком. Вот и я вся магия. Рассмотрим пример, где происходит все тоже самое, только с регулярным обновлением блока.

Обновление блока внутри виджета с интервалом в 3 секунды

Изменения будут минимальными и коснуться только части View

<code class="php"><?php
$script = <<< JS
$(document).ready(function() {
    setInterval(function(){
        $('#refreshButton').click();
    }, 3000);
});
JS;
$this->registerJs($script);
?>
<?php Pjax::begin(); ?>
    <?= Html::a(
    'Обновить',
    ['/example/pjax/pjax-example-2'],
    ['class' => 'btn btn-lg btn-primary', 'id' => 'refreshButton']
    ) ?>
    <p>Время сервера: <?= $time ?></p>
<?php Pjax::end(); ?></code>

Добавился небольшой блок javascript, который с интервалом в 3000 миллисекунд (3 секунды) имитирует нажатие кнопки "Обновить". Этот пример будет удобен на тот случай, если нужно реализовать ротацию, например, изображений (если изображений очень много, то нет необходимости сразу загружать их все). Или подойдет для проверки уведомлений у авторизованного пользователя. Делая запросы на сервер с интервалом, пользователь в один момент сможет узнать, что для него появилось новое уведомление и ознакомится с ним.

Двигаемся дальше. Следующий простой пример будет демонстрировать ajax навигацию по сайту.

Навигация с использованием виджета Pjax

pjax example [ru] AJAX навигация становится намного проще, если использовать виджет Pjax. Потребуется совсем немного усилий, что бы реализовать вывод нескольких страниц без перезагрузки.

Предположим, что все меню состоит только из 2 разделов. Каждая кнопка ведет на уникальный адрес, получает информацию и выводит ее ниже. В качестве такой информации тут будет выведена дата или время.

View

<code class="php"><?php Pjax::begin(); ?>
    <?= Html::a(
        'Показать время',
        ['/example/pjax/pjax-example-3?action=time'],
        ['class' => 'btn btn-lg btn-primary']
    ) ?>
    <?= Html::a(
        'Показать дату',
        ['/example/pjax/pjax-example-3?action=date'],
        ['class' => 'btn btn-lg btn-success']
    ) ?>
    <p>Ответ сервера: <?= $data ?></p>
<?php Pjax::end(); ?>
</code>

Стоит обратить внимание на тот факт, что в этом примере видно как изменяется адресная строка в зависимости от нажатой кнопки. В предыдущих примерах этого не было заметно в связи с тем, что запрос выполнялся на тот же адрес.

Controller

<code class="php"><?php
    public function actionPjaxExample3($action = 'time')
    {
        if ($action === 'time') {
            return $this->render('pjax_example_3', [
                'data' => date('H:i:s'),
            ]);
        } else {
            return $this->render('pjax_example_3', [
                'data' => date('Y-M-d'),
            ]);
        }
    }
?></code>

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

Использование Pjax для нескольких блоков

pjax example [ru]Pjax может использоваться на одной странице множество раз. На этом примере виджет инициализирован 2 раза.

Каждый блок работает независимо. Даже без каких либо параметров виджеты не пересекаются друг с другом. Оба ссылаются на один action, для примера это достаточно, но для реальных задач это может быть неприемлемо.

В качестве ответа от сервера используется класс Security, не все же только время печатать. Стоит сказать о Security несколько слов. Этот класс предоставляет набор методов для обработки общих задач связанных с безопасностью. Так метод generateRandomString может быть использован, например, для создания токена при восстановления пароля. Другой метод generateRandomKey отличается от прошлого только тем, что генерирует на строку из привычных символов, а байты, поэтому получается такая забавная строчка с вопросиками.

View

<code class="php"><?php Pjax::begin(); ?>
    <?= Html::a(
        'Случайная строка',
        ['/example/pjax/pjax-example-4?action=string'],
        ['class' => 'btn btn-lg btn-primary']
    ) ?>
    <p>Ответ сервера: <?=(isset($string)) ? $string : '' ?></p>
<?php Pjax::end(); ?>
<?php Pjax::begin(); ?>
    <?= Html::a(
        'Случайный ключ',
        ['/example/pjax/pjax-example-4?action=key'],
        ['class' => 'btn btn-lg btn-success']
    ) ?>
    <p>Ответ сервера: <?=(isset($key)) ? $key : '' ?></p>
<?php Pjax::end(); ?>
</code>

Controller

<code class="php"><?php
    public function actionPjaxExample4($action = 'string')
    {
        $security = new \yii\base\Security();
        if ($action === 'string') {
            return $this->render('pjax_example_4', [
                'string' => $security->generateRandomString(),
            ]);
        } else {
            return $this->render('pjax_example_4', [
                'key' => $security->generateRandomKey(),
            ]);
        }
    }
?></code>

Отправка данных на сервер с помощью Pjax методом POST

pjax example [ru]Бывает так, что необходимо передавать такие данные на сервер, для которых метод HTTP GET не подойдет, тогда нужно воспользоваться другим методом, например POST. До этого момента примеры демонстрировали лишь получение данных, в этом примере будет продемонстрирована отправка данных.

Нет никаких сложностей с тем, что бы поместить в Pjax веб-форму с произвольным количеством полей, которые могут иметь произвольные типы. Вот пример: веб-форма, которая содержит два элемента, поле ввода и кнопку для отправки. Для примера в поле ввода можно передать некоторую строку текста, в контроллере будет получен md5 hash и выведен на экран.

View

<code class="php"><?php Pjax::begin(); ?>
    <?= Html::beginForm(['/example/pjax/pjax-example-5'], 'post', ['data-pjax' => '', 'class' => 'form-inline']); ?>
        <?= Html::input('text', 'string', Yii::$app->request->post('string'), ['class' => 'form-control']) ?>
        <?= Html::submitButton('Вычислить MD5', ['class' => 'btn btn-lg btn-primary']) ?>
    <?= Html::endForm() ?>
    <h3><?= $md5 ?></h3>
<?php Pjax::end(); ?></code>

Controller

<code class="php"><?php
    public function actionPjaxExample5()
    {
        return $this->render('pjax_example_5', [
            'md5' => md5(Yii::$app->request->post('string'))
        ]);
    }
?></code>

Pjax c параметром enablePushState и сессии(Session) между запросами

pjax example [ru]Вот и пришло время, наконец-то, задать хоть один параметр для виджета. Выбор пал на параметр enablePushState. Но сначала краткая справка о том, что такое pushState, вдруг кто-то еще не знает. Метод pushState принадлежит объекту window.history, который в свою очередь является HTML5 API и может управляться браузерными скриптовыми языками, такими как javascript. Задачей pushState является запись URL в историю браузера, для ajax сайтов это оcособенно актуально, это дает возможность при использовании кнопок назад и вперед видеть актуальный адрес при переходе вперед или назад.

Итак, по умолчанию enablePushState включен, а значит имеет значение true. Если явно указать значение false для этого параметра, то при нажатии кнопок в адресную строку не будет записываться никаких значений.

View

<code class="php"><?php Pjax::begin(['enablePushState' => false]); ?>
    <?= Html::a('', ['/example/pjax/pjax-example-6', 'vote' => 'up'], ['class' => 'btn btn-lg btn-warning glyphicon glyphicon-arrow-up']) ?>
    <?= Html::a('', ['/example/pjax/pjax-example-6', 'vote' => 'down'], ['class' => 'btn btn-lg btn-warning glyphicon glyphicon-arrow-down']) ?>
    <p><?= Yii::$app->session->get('votes', 0) ?></p>
<?php Pjax::end(); ?>
<?php Pjax::begin(['enablePushState' => true]); ?>
    <?= Html::a('', ['/example/pjax/pjax-example-6', 'vote' => 'up'], ['class' => 'btn btn-lg btn-warning glyphicon glyphicon-arrow-up']) ?>
    <?= Html::a('', ['/example/pjax/pjax-example-6', 'vote' => 'down'], ['class' => 'btn btn-lg btn-warning glyphicon glyphicon-arrow-down']) ?>
    <p><?= Yii::$app->session->get('votes', 0) ?></p>
<?php Pjax::end(); ?>
</code>

В контроллере и представлении используются сессии (компонент Session). Объект Yii уже имеет инициализированную сессию, ей можно и воспользоваться для такой простой задачи.

Controller

<code class="php"><?php
    public function actionPjaxExample6($vote = null)
    {
        $votes = Yii::$app->session->get('votes', 0);
        if ($vote === 'up') {
            Yii::$app->session->set('votes', ++$votes);
        } elseif ($vote === 'down') {
            Yii::$app->session->set('votes', --$votes);
        }
        return $this->render('pjax_example_6');
    }
?>
</code>

GridView в Pjax

pjax example [ru] Следующий пример содержит в контейнере Pjax виджет GridView. GridView довольно популярный виджет, его можно встретить в некоторых представлениях, которые генерирует Gii. GridView занимается тем, что профессионально строит таблицы. Достаточно сформировать для него dataProvider и вот уже готовая таблица. Конечно, есть еще масса настроек для виджета в целом, и для выводе столбцов в частности. Для примера, данные сформированы в массиве и инициализирован ArrayDataProvider из него (из массива).

Controller

<code class="php">$array = [
    ['id'=>1, 'name'=>'Sam','age'=> '21', 'height'=> '190'],
    ['id'=>2, 'name'=>'John','age'=> '34', 'height'=> '156'],
    ['id'=>3, 'name'=>'Alex','age'=> '29', 'height'=> '178'],
    ['id'=>4, 'name'=>'David','age'=> '31', 'height'=> '188'],
    ['id'=>5, 'name'=>'Max','age'=> '26', 'height'=> '184'],
];
$dataProvider = new \yii\data\ArrayDataProvider([
    'key' => 'id',
    'allModels' => $array,
    'sort' => [
        'attributes' => ['name'],
    ],
    'pagination' => [
        'pageSize' => 3,
    ],
]);
</code>

Теперь вложим GridView в Pjax и переход по страницам будет осуществляться без перезагрузки страницы. Кроме того, работает поиск по колонке Age.

<code class="php"><?php Pjax::begin(); ?>
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            'id',
            [
                'attribute' => 'name',
                'value' => 'name',
            ],
            [
                'attribute' => 'age',
                'filter' => '<input class="form-control" name="filterage" value="'. $searchModel['age'] .'" type="text">',
                'value' => 'age',
            ],
            'height:ntext',
        ],
    ]); ?>
<?php Pjax::end(); ?></code>

Если бы не Pjax, то переходы по страницам и поиск по колонке были бы гораздо медленнее (из-за обновления всей страницы).

Хоть в виджете Pjax нет особой магии, так как это всего лишь одна из реализаций технологий pushState + ajax, но благодаря ему разработчик лишается необходимости создавать и поддерживать самостоятельные решения. Этот виджет можно назвать по-настоящему умным решением, пригодным для множества сценариев использования. Если что-то пойдет не так, например, какой-то запрос будет выполняться слишком долго, то произойдет перезагрузка страницы.

Ссылки по теме (на английском):


comments powered by HyperComments