«Бесконечная» лента записей с ajax дозагрузкой на Yii

На многих новых сайтах всё чаще встречается вывод списка новостей или других сущностей в виде бесконечно подгружающейся ленты. На некоторых сайтах подгрузка выполняется автоматически (на twitter.com или vk.com), на других – вручную, то есть в конце списка вместо стандартного переключателя страниц имеется кнопка «Показать ещё». Освежим в памяти работу с ClistView и попробуем реализовать подобный функционал на своём сайте.
Довольно часто такую бесконечную «стену» делают где ни попадя, не задумываясь, нужна она или нет. Но мы не будем касаться здесь этической стороны.
Итак, нам нужно подгружать записи используя Ajax. Сначала отвлечёмся на разбор работы Ajax обновлений стандартного списка ClistView.
Вывод списка записей с ClistView
В самом простом случае с использованием встроенных средств Yii Frmework мы можем выводить список постов с Ajax переходами по страницам так:
Контроллер controllers/PostController.php:
class PostController extends Controller { public function actionIndex() { $criteria = new CDbCriteria; $criteria->order = 't.create_datetime DESC'; $dataProvider = new CActiveDataProvider('Post', array( 'criteria'=>$criteria, 'pagination'=>array( 'pageSize'=>10, ), )); $this->render('index', array( 'dataProvider'=>$dataProvider, )); } }
Представление views/post/index.php:
<?php $this->pageTitle = 'Блог'; $this->breadcrumbs = array( 'Блог', ); <h1>Блог</h1> $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'itemView'=>'_view', 'ajaxUpdate'=>true, 'template'=>"{items}\n{pager}", ));
Элемент списка views/post/_view.php:
<?php <article> <h2><?php echo Chtml::link($data->getUrl(), $data->title); </h2> echo $data->short; </article>
Здесь у нас всего одно действие actionIndex() и одно представление index.php для него. Но в блоге могут быть вывод записей из категории, записей по тегу, по дате и т.д. Все они будут использовать свои шаблоны index.php, category.php, tag.php, date.php, но одинаковый общий список. Целесообразно по принципу шаблонов Wordpress вынести формирование списка в отдельный файл _loop.php.
Все шаблоны типа index.php теперь ссылаются на файл списка _loop.php:
views/post/index.php:
<?php $this->pageTitle = 'Блог'; $this->breadcrumbs = array( 'Блог', ); <h1>Блог</h1> $this->renderPartial('_loop', array('dataProvider'=>$dataProvider));
views/post/_loop.php:
<?php $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'itemView'=>'_view', 'ajaxUpdate'=>true, 'template'=>"{items}\n{pager}", ));
Теперь все наши списки нормально выводятся и переход по страницам происходит по Ajax. Но в этом простейшем способе кроются некоторые подводные камни.
Снижение нагрузки при обновлении по ajax
Ajax запрос обращается по ссылкам, которые прописаны в ячейках списка номеров страниц, то есть фактически к тому же actionIndex(), который независимо от способа запроса рендерит полную страницу сайта в строке $this->render('index', array(...)). Обработчик Ajax ответа только выделяет из всего HTML-содержимого код своего списка. Эта тема уже поднималась на Habrahabr. Теперь мы можем решить эту проблему.
При Ajax запросе наш контроллер должен возвращать не всю страницу в шаблоне, а только код списка. Так как мы вынесли список в отдельный файл _loop.php, ничто не мешает генерировать только его вызывая $this->renderPartial('_loop', array(...)):
class PostController extends Controller { public function actionIndex() { $criteria = new CDbCriteria; $criteria->order = 't.create_datetime DESC'; $dataProvider = new CActiveDataProvider('Post', array( 'criteria'=>$criteria, 'pagination'=>array( 'pageSize'=>10, ), )); if (Yii::app()->request->isAjaxRequest){ $this->renderPartial('_loop', array( 'dataProvider'=>$dataProvider, )); Yii::app()->end(); } else { $this->render('index', array( 'dataProvider'=>$dataProvider, )); } } }
Теперь при Ajax запросе будет возвращаться только список без генерации всего шаблона сайта. Этот способ подойдёт и для CGridView, где из файла admin.php таблицу можно вынести в файл _grid.php.
Добавляем подгружающуюся ленту
Действия нашего контроллера уже способны оптимизировать свою работу при Ajax запросах. Рассмотрим теперь непосредственно организацию ленты. Нам нужно:
- Создать на странице блок
<div id="listView"></div>; - В блоке вывести стандартный список первых 10 новостей со навигацией по страницам;
- Если включен JavaScript и страниц больше одной, то скрыть навигатор и отобразить кнопку «Показать ещё»;
- Запомнить в JavaScript номер текущей страницы
- Навесить на эту кнопку обработчик, который бы по щелчку увеличивал номер текущей страницы на единицу и загружал новую порцию записей и добавлял в блок
#listView; - Если записи закончились (
page>=pageCount), то скрыть кнопку.
Если JavaScript у пользователя отключен, то наш скрипт не сработает и останется стандартный навигатор.
Также нам нужно добавить индикацию процесса загрузки (можно, например, показывать/скрывать анимированное изображение или дабавлять/удалять класс-примесь кнопке) и защиту от частых нажатий (выставлять флаг на всё время згрузки).
Представление views/post/_loop.php с учётом этого может быть примерно таким:
<div id="listView"> <?php $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'itemView'=>'_view', 'ajaxUpdate'=>false, 'template'=>"{items}\n{pager}", 'pager'=>array( 'htmlOptions'=>array( 'class'=>'paginator' ) ), )); </div> if ($dataProvider->totalItemCount > $dataProvider->pagination->pageSize): <p id="loading" style="display:none"><img src=" echo Yii::app()->request->baseUrl; /images/loading.gif" alt="" /></p> <p id="showMore">Показать ещё</p> <script type="text/javascript"> /*<![CDATA[*/ (function($) { // скрываем стандартный навигатор $('.paginator').hide(); // запоминаем текущую страницу и их максимальное количество var page = parseInt(' echo (int)Yii::app()->request->getParam('page', 1); '); var pageCount = parseInt(' echo (int)$dataProvider->pagination->pageCount; '); var loadingFlag = false; $('#showMore').click(function() { // защита от повторных нажатий if (!loadingFlag) { // выставляем блокировку loadingFlag = true; // отображаем анимацию загрузки $('#loading').show(); $.ajax({ type: 'post', url: window.location.href, data: { // передаём номер нужной страницы методом POST 'page': page + 1, ' echo Yii::app()->request->csrfTokenName; ': ' echo Yii::app()->request->csrfToken; ' }, success: function(data) { // увеличиваем номер текущей страницы и снимаем блокировку page++; loadingFlag = false; // прячем анимацию загрузки $('#loading').hide(); // вставляем полученные записи после имеющихся в наш блок $('#listView').append(data); // если достигли максимальной страницы, то прячем кнопку if (page >= pageCount) $('#showMore').hide(); } }); } return false; }) })(jQuery); /*]]>*/ </script> endif;
Здесь мы отключили за ненадобностью встроенную поддержку ajax, так как будем делать переходы в своём скрипте.
Это представление уже не надо возвращать по Ajax. Оно должно выводиться единожды и подгружать только голый список. Для этого добавим представление loopAjax.php с кодом подгружаемого списка без прочих лишних элементов:
views/post/_loopAjax.php
<?php $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'itemView'=>'_view', 'ajaxUpdate'=>false, 'template' => "{items}", ));
Кроме переименования _loop в _loopAjax в контроллере необходимо произвести ещё несколько изменений. Все они касаются передачи номера необходимой страницы контроллеру.
Можно заметить, что номер страницы из нашего скрипта передаётся посредством переменной page в POST запросе (а не в GET), а в классе CPagination, используемом CActiveDataProvider, для определения номера текущей страницы используется именно GET.
В зависимости от используемых роутов при различных построениях ЧПУ номер страницы в URL может находиться в любом месте, например
http://site.com/blog?page=2 http://site.com/blog/page-2 http://site.com/page/2 http://site.com/blog/2 http://site.com/page-2
Наш JavaScript код не может воспользоваться функцией Yii::app()->createUrl() для генерации ссылок, поэтому номер необходимой страницы проще передать в POST запросе, отправленном на текущий адрес window.location.href и в контроллере произвести подмену $_GET['page']=$_POST['page'].
Кроме того, нужно явно указать имя параметра page в поле pageVar, иначе по умолчанию он будет использовать параметр по имени нашей модели, то есть Post_page.
class PostController extends Controller { public function actionIndex() { $this->processPageRequest('page'); $criteria = new CDbCriteria; $criteria->order = 't.create_datetime DESC'; $dataProvider = new CActiveDataProvider('Post', array( 'criteria'=>$criteria, 'pagination'=>array( 'pageSize'=>10, 'pageVar' =>'page', ), )); if (Yii::app()->request->isAjaxRequest){ $this->renderPartial('_loopAjax', array( 'dataProvider'=>$dataProvider, )); Yii::app()->end(); } else { $this->render('index', array( 'dataProvider'=>$dataProvider, )); } } protected function processPageRequest($param='page') { if (Yii::app()->request->isAjaxRequest && isset($_POST[$param])) $_GET[$param] = Yii::app()->request->getPost($param); } }
Подмену значения $_GET['page'] мы производим в методе processPageRequest(). Его можно при желании либо поднять в базовый контроллер, либо (чтобы избавиться от лишней строки $this->processPageRequest('page');) вынести в отдельный фильтр.
После всех этих манипуляций мы получим «бесконечную» ленту, подгружающую записи и становящуюся всё длиннее и длиннее по щелчку мыщи. Для автоматической загрузки новых записей при прокрутке страницы нужно обработчик щелчка $('#showMore').click() заменить на обработчик прокрутки страницы $('html').scroll() с проверкой на появление нашего блока #showMore в видимой части окна.
Для большего удобства можно вынести код кнопки и обработчика в отдельный настраиваемый виджет, которому бы передавались, например, $dataProvider и CSS-идентификатор стандартного навигатора по страницам.
AОпечатка: $this->processCurrentPage('page');
Надо: $this->processPageRequest('page');
Для автоматической подгрузки:
$(window).scroll(function() { if (!loadingFlag) { if ($(window).scrollTop() == $(document).height() - $(window).height()) { loadingFlag = true; ... // если достигли максимальной страницы, то прячем кнопку if (page >= pageCount) loadingFlag = true; } }); } } return false; })Хмм, не уверен, где разместить loadingFlag = true;, который вначале.
Дмитрий ЕлисеевСпасибо, исправил.
Alexander – chapaty.orgу вас все равно $this->processCurrentPage('page'); стоит, такого метода нет в контроллере.
helloworldСдейлайте в виде екстеншена
Дмитрий ЕлисеевА какой формат расширения для этого лучше подойдёт?
helloworldвот на примере инфинит скролла: http://www.yiiframework.com/extension/inifinite-scroll-pager
assdЗдравствуйте. Спасибо за отличный пример. А какой у Вас action для _view? Мне не совсем понятно с выборкой.
Дмитрий ЕлисеевЭто представление _view.php выводит каждую запись в _loop.php:
<?php $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'itemView'=>'_view', )); ?>
assdТогда во _view должно быть так?
Дмитрий ЕлисеевПочти:
<article> <h2><?php echo Chtml::link(CHtml::encode($data->title), $data->getUrl()); ?></h2> <?php echo $data->short; ?> </article>Текущая модель передаётся в переменную $data, номер записи в $index, а this виджета CListView в $widget.
assdСпасибо, разобрался уже после того как спросил)
СвятославЗдравствуйте. Прошу прощения, не слишком силен в yii. Допустим во время того, как пользователь осуществляет прокрутку ленты, добавляется несколько новых записей в базу. Будут ли при этом продублированы последние добавленные на страницу записи?
Дмитрий ЕлисеевДа, новые будут сдвигать старые вниз. Это и обычного постраничного отображения касается.
СвятославПодскажите пожалуйста, а как решить эту проблему? Может необходимо при первой загрузке страницы сохранить в javascript переменной время первой загрузки, и потом передавать его контроллеру, а из базы выбирать данные, у которых время добавления не позже переданного?
Дмитрий ЕлисеевМожно и так.
СвятославСпасибо)
IvanЗдравствуйте!
Спасибо за пример. Все сделал, работает. Данные подгружает, но Js не работает в подгруженных данных. Как побороть это?
У меня подгружаются картинки, с возможностью проголосовать за них, так вот это голосование не работает...
Дмитрий ЕлисеевНавешивайте голосования live-привязкой через on():
$(document).on('click', 'span.vote', function(){...});вместо обычного click():
$('span.vote').click(function(){...});
IvanСпасибо! Попробую, отпишусь потом.
Владимир 108495791184666567385в views/post/_loop.php используется и jQuery и $
думаю стоит писать все в одном стиле
Дмитрий ЕлисеевИсправил. Спасибо!
Александр – rooland.ruСпасибо за статью. Вопрос, почему когда нажимаю первый раз на "Показать ещё", он загружает новые 10 статей и по идеи в базе больше нет статей, но кнопка не исчезает и если нажать ещё то появляются те, к-е уже выводились.
Александр – rooland.ruЕщё показывает несколько раз "Показать ещё" после первого нажатия.
АнтонПодскажите, пожалуйста, почему JavaScript в Вашем примере не может воспользоваться Yii::app()->createUrl()?
Спасибо.
Дмитрий ЕлисеевYii::app()->createUrl() – это PHP. В JavaScript без ajax это сделать будет проблематично.
Антониз Вашего же примера
$.ajax({ type: 'post', url: window.location.href, data: { // передаём номер нужной страницы методом POST 'page': page + 1, '<?php echo Yii::app()->request->csrfTokenName; ?>': '<?php echo Yii::app()->request->csrfToken; ?>'
Дмитрий ЕлисеевНу попробуйте.
Александр – rooland.ruСвою проблему решил. Вместо "_loopAjax", написал "_loop", поэтому не работало, исправил, теперь работает.
Дмитрий ЕлисеевА я уж подумал, что я в $ переименовал не так.
Александр – rooland.ru:)
Дмитрий ЕрмолаОтличный пример. Вообще у Вас на сайте замечательный материал, уже нашел для себя много полезного и интересного. В случаях когда делают "бесконечную ленту" делают еще и догрузку новых записей в начало списка, естественно с помощью AJAX. Было бы здорово если бы Вы описали и этот нюанс.
Спасибо :)
Дмитрий ЕлисеевНапример, элементы можно пометить:
Теперь в JavaScript по setTimeout() через каждые несколько секунд получать крайний идентификатор записи на странице:
var last_id = $('.item:first-child').data('id');и передавать его в Ajax запросе.
А контроллер должен найти записи с id больше last_id и сгенерировать ответ через renderPartial(). В success коллбэке Ajax запроса клиентский скрипт теперь получает этот ответ, добавляет элементы вверх через prepend() и запускает следующий setTimeout().
Дмитрий ЕрмолаПожалуй это будет оптимальный вариант. Еще был вариант посмотреть в сторону "WebSockets", но поскольку технология еще не всеми браузерами поддерживается (или не всеми корректно), решил пока не углубляться в них.
Спасибо за подсказку.
СаматДобрый день!
у меня вопрос возник, а как реализовать pagination так что бы он при переходе на следующий страницу или при нажатии на любую из страничек делалэто без перезагрузки страницы?
Дмитрий ЕлисеевУ CListView или CGridView указать 'ajaxUpdate' => true.
White WhaleЕсть один плохой момент, каждый раз при добавлении новых записей вместо добавления в список items, генерируется новый список.
<div class="posts" id="posts"> <div id="yw0" class="list-view"> <div class="items"></div> </div> <div id="yw0" class="list-view"> <div class="items"></div> </div> </div>
Дмитрий ЕлисеевДа, так и есть. Но если это критично, то можно сделать озвлечение элементов из data и вставлять прямо в items:
$('#listView .items').append(data);
Дмитрий ПоплавскийМожет можете подсказать, как извлечь из data только div item?
Дмитрий ЕлисеевДостаточно как-то обозначить закрывающий блок в _view.php:
И на клиенте выбрать внутренности с помощью регулярного выражения вроде:
AkzholГрузит последние два элемента с базы данных. Видимо нет никакой проверки на то что эти записи уже выводились. Помогите пожалуйста!
AkzholГрузит последние два элемента с базы данных снова и снова при прокрутке вниз*. Видимо нет никакой проверки на то что эти записи уже выводились. Помогите пожалуйста!
AkzholА все исправил.
if (page >= pageCount)
loadingFlag = true;
написал совсем в другом месте
ЕвгенийЗдравствуйте, сделал ленту, отлично работает и автору респект!
Столкнулся с тем что необходимо добавить пользовательский поиск, чтобы была возможность ввода параметров для поиска и вывод данных сохраняя при этом ленту, делал сначала форму путем добавления модели и формы, но вот по нажатию поиска столкнулся с проблемой как сделать вывод на основе атрибутов этого самого поиска? какие идеи есть по этому поводу?
ЕвгенийРеализовал, все работает. Для примера из этой статьи
Перед $dataProvider в контроллере добавляем
$model=new Post; $model->unsetAttributes(); // чистим атрибуты поиска if(isset($_GET['Post'])) { $model->attributes=$_GET['Post']; $newDataProvider=$model->search(); $criteria->mergeWith($newDataProvider->criteria); }Далее в контроллере меняем
$this->render('index', array( 'dataProvider'=>$dataProvider, ));на следующий код
$this->render('index', array( 'dataProvider'=>$dataProvider, 'model'=>$model, ));и во вьюшке в данном случае views/post/index.php добавляем форму поиска через виджет CActiveForm
ВсеволодПодскажите пожалуйста, а как сделать, чтобы вначале при загрузке страницы не показывало ни одного блока, а при нажатии на кнопку уже выводило по три блока (или столько, сколько указано в размере "page")?
Дмитрий ЕлисеевСо стороны сервера сразу не скажу, а на клиенте можно сразу убрать:
$('#listView .items').html('');И тогда page надо считать не с единицы, а с нуля.
МаксимНе работает. На странице не выводит ни одного элемента, при этом есть надпись "показать еще". При нажатии на нее подгружается лоад-гифка и всё.
Контроллер:
if (Yii::app()->request->isAjaxRequest){ $this->renderPartial('_loopAjax', array( 'dataProvider'=>$dataProvider, )); Yii::app()->end(); }Представление:
<ul class="catalog"> <?php $this->renderPartial('_loop', array('dataProvider'=>$dataProvider)); ?> </ul>
МаксимСоврал. Только при подгрузке у меня подгружаются не элементы,а весь layout
$this->processPageRequest('page'); $this->settings = User::model()->findByPk(1); $main = Pages::model()->findByPk(1); $dataProvider = new CActiveDataProvider('Product', array( 'criteria'=>array(), 'pagination'=>array( 'pageSize'=>9, 'pageVar' =>'page', ), )); if (Yii::app()->request->isAjaxRequest){ $this->renderPartial('_loopAjax', array( 'dataProvider'=>$dataProvider, )); Yii::app()->end(); } else { $this->layout ='//layouts/catalog'; $this->render('catalog', array( 'dataProvider'=>$dataProvider, 'main'=>$main, ) ); }
Дмитрий ЕлисеевА что внутри ответа с layout? Обычно он весь подгружется при выводе ошибки.
МаксимС этим разобрался. Сейчас возникли проблемы связать это все еще с аякс-фильтром. Запутался уже) Буду очень благодарен небольшому примеру. Сейчас у меня так.
_loop.php:
<div id="listView"> <? $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'ajaxUpdate'=>false, 'itemView'=>'item', 'template'=>'{items}{pager}', 'pager'=>array( 'htmlOptions'=>array( 'class'=>'paginator' ) ), 'id'=>'ajaxListView', )); ?> </div> <?php if ($dataProvider->totalItemCount > $dataProvider->pagination->pageSize && CHtml::normalizeUrl('')!=='/'): ?> <a id="showMore" class="more-catalog" href=""> Показать еще </a> <script type="text/javascript"> (function($) { $('.pager').hide(); var page = parseInt('<?php echo (int)Yii::app()->request->getParam('page', 1); ?>'); var pageCount = parseInt('<?php echo (int)$dataProvider->pagination->pageCount; ?>'); var loadingFlag = false; $('.filter-check').change(function() { var inc = []; $('input[name="inc[]"]:checked').each(function() {inc.push($(this).attr('value'));}); $.ajax({ type: 'post', url: '/catalog', data: { 'inc': inc, '<?php echo Yii::app()->request->csrfTokenName; ?>': '<?php echo Yii::app()->request->csrfToken; ?>' }, success: function(data) { $('#listView').empty(); $('#listView').append(data); } }); return false; }) $('.more-catalog').click(function() { var inc = []; $('input[name="inc[]"]:checked').each(function() {inc.push($(this).attr('value'));}); if (!loadingFlag) { loadingFlag = true; $.ajax({ type: 'post', url: '/catalog', data: { 'page':page+1, 'inc':inc, '<?php echo Yii::app()->request->csrfTokenName; ?>': '<?php echo Yii::app()->request->csrfToken; ?>' }, success: function(data) { page++; loadingFlag = false; $('#listView').append(data); if (page >= pageCount) $('#showMore').hide(); } }); } return false; }) })(jQuery); /*]]>*/ </script> <?php endif; ?>_loopAjax:
<?php $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$dataProvider, 'itemView'=>'item', 'ajaxUpdate'=>false, 'template' => "{items}", )); ?>контроллер:
if (Yii::app()->request->isAjaxRequest){ $dataProvider = $this->searchFilter(); $this->renderPartial('_loopAjax', array( 'dataProvider'=>$dataProvider, )); Yii::app()->end(); }Вроде выборка правильная, но проблема в том, что если кнопка "Показать еще" была нажата, то она больше не появится при фильтрации
МаксимИ кнопка появляется в начале всегда (даже если элемента 3, при нажатии добавляются они же)
Дмитрий ЕлисеевПросто не забудьте учесть, что при фильтрации нужно получить новый pageCount и сбросить page=1.
dmitriiВаш метод инфинит скролла имеет баг
Conditions: limitPage = 10, countItems = 11
Вы передаете page=2, Вам по идеи должно вернуть 1 item, но за пару сек. до этого кто то постит в ленту несколько сообщений в итоге вам вернется 9, 10, 11[id]
9, 10 - вернулись в первом запросе, получаем дублирование контента, решать этот вопрос средствами frontend это костыль.
Эту проблему нужно решать на уровне backend, солюшен состоит в том что бы передавать id последнего item со страници
В кондишенах имеем
$criteria->compare('id', $lastIdPrevPage, false, '>');
ИгорьСпасибо!
Сразу интегрировал !!!!!!!!!!!!!1
Владимр – m-design.byТут у меня проблема возникла, как вывести сообщения в обратном порядке после выборки из бд.
Вот критерии:
$criteria = new CDbCriteria; $criteria->condition = '...'; $criteria->params = array(...); $criteria->order = 't.created_at DESC'; $dataProvider = new CActiveDataProvider('UserMessages', array( 'criteria'=>$criteria, 'pagination'=>array( 'pageSize'=>5, ), ));
Владимр – m-design.byКак видно из критерии сортировка идёт по desc, а как мне при рендере отсортировать сообщения по ACS, что-бы новые сообщения отображались последними в списке?
Может быть есть что-либо из коробки?
Пока не придумал ничего умнее чем:
<?php $dataProvider->setData(array_reverse($dataProvider->getData()));?> <?php $this->renderPartial('_loop', array('dataProvider'=>$dataProvider)); ?>
Дмитрий ЕлисеевНу если нужно именно после выборки отсортировать, то можно и так.
МаксимДмитрий, большое спасибо за пример. Всё отлично работает!
Подскажите, как организовать добавку /page/номер_страницы в url при нажатии кнопки "Показать ещё"?
Дмитрий ЕлисеевИспользуйте history API.
МаксимСпасибо!
ГеннадийСкажите, а на сколько увеличивается нагрузка на сервер при таком подходе? Может быть лучше передавать только данные о постах в JSON, а потом уже javascript-ом их обворачивать в нужные дивы и вставлять? У меня просто проект где нагрузка на сервер будет немаленькая и вот думаю, делать способом как у Вас или все же передавать только данные в JSON, а уже остальную их обработку делать на javascript-ом. По логике нагрузку на сервер я таким образом сделаю немного меньше, но получается нехороший код тогда. Нужно, например, помимо php шаблона вывода поста делать шаблон для вставки данных в js. При изменении одного из них - надо менять второй. Ну и скорее всего в абсолютном выражении такой подход будет медленнее, ибо придется делать много замен в цикле в js. Чтобы из конструкции а-ля
%post_title%
%post_date%
получилась конструкция с именем и датой поста, которые пришли в JSON с сервера. Как думаете, какие еще моменты я не учел для выбора? На что еще стоит обратить внимание? Чтобы Вы посоветовали?
Дмитрий ЕлисеевТак как в контроллере сейчас есть условие:
if (Yii::app()->request->isAjaxRequest){ $this->renderPartial('_loop', array( 'dataProvider'=>$dataProvider)); Yii::app()->end(); } else { $this->render('index', array('dataProvider'=>$dataProvider)); }то кроме _loop ничего лишнего из шаблона сервере рендериться не будет. А если делать возврат через JSON, то помимо самих записей нужно будет возвращать связанные модели и генерируемые поля (URL и т.п.). Так что разница будет небольшая.
ГеннадийЧто ничего лишнего не рендерится это понятно. Видимо, мне нужно было сразу сказать, что выводятся у меня не точно такие же посты как у Вас, а совсем другие данные. Так вот в этих данных, если делать через renderPartial, больше html кода, чем самих данных. Один элемент(аналог Вашего поста) у меня занимает 23 строки(примерно одинакового размера). Из них только 3 строки это данные с сервера, а остальное верстка. Относительно нагрузки на сервер у меня для одного элемента получается выбор: либо в JSON-е отправить эти 3 строки и обернуть их в верстку js-ом, либо грузить все 23 строки сразу с сервера, подождав пока отработает рендеринг. Теперь понимаете, что я имею в виду?
Дмитрий ЕлисеевТеперь понятно.
AndreyДобрый день,
В документации указано. что можно создавать Action, указав actionId:
http://www.yiiframework.com/doc/api/1.1/CController#createAction-detail
Но тут ничего не сказано о том, как передать туда параметры...
У меня 3 вопроса:
1. Можно ли сделать то, что я описал выше?
2. Есть ли более лояльный способ ajax переключения с actionCreate на actionUpdate. Обязательна поддержка загрузки картинок. Дело в том, что некоторые блоки формы должные открываться только после сохранения основной модели.
3. Возможно это лучше провернуть подменяя сценарии как то ?
Пока у меня такой быдлокод, но место , где я в isAjaxRequest блоке создаю action, не работает:
public function actionCreate() { if (Yii::app()->request->isAjaxRequest ) { $model = new Hotels(); if(isset($_POST['Hotels'])) { $model->setAttributes($_POST['Hotels'], true); $model->save(); // Вызов чужого контроллера и action $p = Yii::app()->createController('sadmin/hotels'); // /update $act = $p[0]->createAction('update', ['id'=>$model->id]); $act->run(); Yii::app()->end(); } else { $model = new Hotels(); $this->render('create', [ 'model'=>$model, ]); }
AndreyНашел:
Yii::app()->runController('sadmin/hotels/update/id/1416');
Только почему то не работает в isAjaxRequest, только в основном блоке экшена.
Почему-то данный метод отрабатывает в основном теле экшена, но при isAjaxRequest ничего не генерируется(
public function actionCreate() { if (Yii::app()->request->isAjaxRequest { // Тут не работает = ((( Yii::app()->runController('sadmin/hotels/update/id/1416'); Yii::app()->end(); } else { $model = new Hotels(); // тут работает Yii::app()->runController('sadmin/hotels/update/id/1416'); $this->render('create', ['model'=>$model,]); } }
Дмитрий ЕлисеевНе пробовал, так что не знаю. Если так и не получится, то попробуйте вынести код из actionUpdate в обычный метод и вызывать его здесь.
AndreyБуду разбираться тогда, спасибо, что не прошли мимо!
Дмитрий ЕлисеевДополнил комментарий.
AndreyБлагодарю, пробую нечто подобное сделать, хочется fullAjax на старости лет)
AndreyА есть ли способ собрать action create и action update в один action без костылей вроде input type="hidden" и js-a , который подменяет значение этого поля?
AndreyajaxSubmitButton на success ничего не возвращает:
echo CHtml::ajaxSubmitButton ( $model->isNewRecord ? 'Добавить номера' : 'Обновить', Yii::app()->createUrl('sadmin/hotels/create'), [ 'success'=>'js:function(data) { displayMessage(data); $("#hotels-form").remove(); $("#crud-hotel").append(data); }', 'error'=>'js:function() { alert("Плохо"); }', ], [ 'id'=>'add-hotel', 'class'=>'btn btn-primary btn-lg center-block', ] );Сама кнопка точно отрабатывает, если подкинуть в isAjaxRequest какие нибудь die($value), они возвращаются
РоманДобрый день. А как такое сделать на Yii2? Что-то не выходит никак сделать)
Дмитрий ЕлисеевВ что именно не выходит?
АлександрНе выходит спрятать пагинацию и добавлять вывод по клику.
Александрvar page = parseInt('<?php echo (int)Yii::$app->request->get("page", 1); ?>');возвращает NaN
Остальное, кажется, удалось победить
АлександрВсе получилось, кроме вывода _loopAjax
Похоже, что вообще не срабатывает в контроллере if (Yii::$app->request->isAjax) - пробовал вывести var_dump и setFlash - тишина.
Страница передается, запрос формируется GET http://test.loc/shop/new?page=2
только гифка мигает... и кнопка пропадает. когда прокликиваю до конца.
И с токенами не разобрался - может в них дело?
ShvarzВот опять Я, привет Дмитрий.
Вопрос такой , мне нужен пример добавление комментариев с ajax , не обязательно древовидный.
Или статьи на эту тему, искал , но нашел не рабочие.
Подскажи где посмотреть наглядный пример
Дмитрий ЕлисеевПример jQuery.ajax?
ЕленаКак сделать, чтобы подгрузка блоков происходила заранее, а не по достижению блоком нижнего края окна браузера, т.е, как в соцсетях?
MatЕлена, если Вы не понимаете сути бесконечной подзагрузки.... она собственно в том и заключается, чтобы \по достижению блоком нижнего края\ грузился новый контент. "Заранее" итак грузится первая \портянка\ можете её сделать небольшой, записей 50 (самый частый запрос), потом подгружать по 100... как вариант.
Всё остальное это CSS, JS ... вы там можете играться как угодно. в конце концов дайте прелоадер пользователю, чтобы он видел - идёт загрузка.
(у всех разная скорость прокрутки + разный контент)
Елена – girls-art.photosСпасибо, уже давно сделала) А как сделать, чтобы при загрузке лайтбокса грузились не все миниатюры в диафильм, а например 50?
https://girls-art.photos/baleriny/#bwg90/34583
Сергей ЗейлеДобрый день, Дмитрий! Поясните, пожалуйста, почему мы используем в ajax запросе метод post? Если использовать метод get, то мы можем не использовать processPageRequest...
АлексейПодскажите
csrf всегда нужно отправлять в ajax запросах?
Что будет если его не оправить?
Что с ним делать в контроллере?
в yii2.
Дмитрий ЕлисеевВ Yii2 он отправляется и принимается сам незаметно.
АлексейЗдравствуйте. Вы не могли бы показать, как сделать загрузку без кнопки - при прокручивании до конца списка?
Алексейразобрался
$(window).scroll(function() { if ($(window).scrollTop() + $(window).height() >= $(document).height() - 500 && !loadingFlag) loadPhoto() });
DmitriyДмитрий, здравствуйте. Вопрос, если используется ArrayDataProvider, можно ли как-то сделать бесконечный скрол (если на странице - статический контент с массива в гриде) ?