Встраиваем виджеты в текст страницы в Yii
Давным давно в тридевя... в шаблонизаторе моей второй по счёту старой CMS вставка виджетов в шаблон была релизована посредством использования старой доброй клинописи вида {{WidgetName|param1=val1;param2=val2}}. Этот код можно было вставлять даже в текст, что давало неоценимую возможность компоновать страницы любой сложности из виджетов прямо в текстовом редакторе админки.
Похожий функционал часто используют плагины WordPress (например, для вывода виджетов плагинов DDSitemapGen и ContactForm7 в текст страницы нужно добавить специфическую строку). Этот функционал я перенёс и в систему на Yii.
Вот так, например, выглядит главная страница сайта glazschool.ru при редактировании в админке:
На любую страницу можно легко вывести форму обратной связи, блок комментирования или кнопки «Поделиться», просто добавив в её текст вызовы Contactform, Comments или Share. Вызов виджетов доступен только из разрешённого списка и только в тексте страницы. Это не позволяет злоумышленнику вызывать виджеты в комментариях или в личных сообщениях.
Итак, попробуем вставить виджет последних записей в блоге в текст этой записи:
Последние записи:
Использование событий Events в Yii
При программировании на компонентном языке с поддержкой графического интерфейса часто приходится иметь дело с событиями. Любой визуальный объект в каждом из этих языков может обладать свойствами, методами и событиями. Но этими языками событийный подход не ограничивается. Попробуем по аналогии с реализацией в других языках разобраться с работой с ними в Yii.
Сквозной поиск для сайта на Yii
При разработке любого более-менее крупного проекта на Yii у программиста может возникнуть необходимость внедрения поиска. И если для интернет-магазина будет достаточно искать только по каталогу, то для информационного сайта нужно обеспечить сквозной поиск по нескольким сущностям сразу. В конце этого урока мы рассмотрим готовые решения по поиску, а в начале для образовательных целей напишем свой велосипед.
Как видите, этот виджет удачно вставился.
Перейдём к реализации. Для начала создаём наш виджет /componetns/widgets/LastPostsWidget.php
class LastPostsWidget extends CWidget { public $tpl='default'; public $limit=3; public function run() { $posts = Post::model()->findAll(array( 'condition'=>'public=1', 'order'=>'date DESC', 'limit'=>$this->limit, )); $this->render('LastPosts/' . $this->tpl, array( 'posts'=>$posts, )); } }
Виджет выбирает из базы 3 последних записи и передаёт в представление /componetns/widgets/views/LastPosts/default.php, которое, собственно, и выводит анонсы записей. Код его приводить не будем.
Теперь необходимо создать файл /components/DInlineWidgetsBehavior.php с нужным нам поведением:
...и подключить наше поведение к контроллеру:
class Controller extends CController { public function behaviors() { return array( 'InlineWidgetsBehavior'=>array( 'class'=>'DInlineWidgetsBehavior', 'location'=>'application.components.widgets', 'startBlock'=> '{{w:', 'endBlock'=> '}}', 'widgets'=>array( 'Share', 'Comments', 'blog.widgets.LastPostsWidget', }, ), ); } }
Удобнее вывести список доступных виджетов в конфигурационный файл
'params'=>array( 'runtimeWidgets'=>array( 'Share', 'Comments', 'ContactWidget', 'blog.widgets.LastPostsWidget', ), ),
и передавать список поведению в виде
class Controller extends CController { public function behaviors() { return array( 'InlineWidgetsBehavior'=>array( 'class'=>'DInlineWidgetsBehavior', 'location'=>'application.components.widgets', 'startBlock'=> '{{w:', 'endBlock'=> '}}', 'widgets'=>Yii::app()->params['runtimeWidgets'], ), ); } }
Параметр location нужно указать лишь в случае, когда виджеты находятся в отдельной папке, не указанной в директиве import файла конфигурации приложения. При его указании поведение будет само вызывать метод Yii::import() для подключения каждого виджета. Этот параметр игнорируется для виджетов, указанных с полным путём (вроде 'blog.widgets.LastPosts'). Поля startBlock и endBlock можно использовать для указания своих начальных и конечных блоков.
Теперь мы можем включить в текст страниц команды подстановки виджетов в любом удобном нам виде. Кроме того, в поведении предусмотрено кеширование. Пример текста страницы:
<h2>Последние записи</h2> <p>{{w:LastPostsWidget}}</p>
Если в проекте имена всех публичных виджетов имеют одинаковый суффикс Widget, то его можно вынести в параметр classSuffix
class Controller extends CController { public function behaviors() { return array( 'InlineWidgetsBehavior'=>array( 'class'=>'DInlineWidgetsBehavior', 'classSuffix'=> 'Widget', 'startBlock'=> '{{w:', 'endBlock'=> '}}', 'widgets'=>Yii::app()->params['runtimeWidgets'], ), ); } }
и вызывать виджеты по имени без суффикса Widget
'params'=>array( 'runtimeWidgets'=>array( 'Share', 'Comments', 'Contact', 'blog.widgets.LastPosts', ), ),
Всем виджетам можно указывать простые параметры и время кэширования:
<h2>Последние записи</h2> <p>{{w:LastPosts}}</p> <h2>Последние 4 записи</h2> <p>{{w:LastPosts|limit=4}}</p> <h2>Последние записи списком</h2> <p>{{w:LastPosts|tpl=list}}</p> <h2>Последние 5 записей списком, кешируемые на 300 секунд</h2> <p>{{w:LastPosts|limit=5;tpl=list|cache=300}}</p> <h2>Фантазия...</h2> <p>{{w:youtube|id=qwer12345}}</p> <p>{{w:flash|file=/banners/banner1.swf;w=320;h=240}}</p> <p>{{w:gallery|folder=vecherinka2012}}</p> <p>{{w:submenu|parent=services}}</p>
В представлении достаточно теперь пропустить текст страницы через обработчик:
echo $this->decodeWidgets($model->text);
...и текст выведется с выполненными виджетами.




Комментарии
Дмитрий, а что за форум на сайте школы? самописный?
ээ, увидел снизу, извини за оффтоп.
хмм, у меня почему-то выводится сначала код, генерируемый виджетом, а уже потом - код страницы.
и получается фигня....
даже если вызываю не так:
echo $this->decodeWidgets($model->text);
а так:
$this->decodeWidgets($model->text);
такое ощущение, что где-то что-то с ob_start ob_end_clean напутано....
Разбираюсь...
Дмитрий, не сильно разбираюсь в преимуществе работы с бихавером. Почему то из-за этого пробела, не могу его использовать... не осознаю его нужность. тогда как в ваших работах, я удивляюсь, почти повсеместное применение. откройте секрет, в чем тотальное преимущество, в отличие от стандартного подхода построения виджета? Вот, не использую я данное поведение, и мне кажется так же удобно работает виджет.., может что недопонимаю?
было б вообще замечательно, если бы вы про поведение разжевали тему на простых примерах, думаю было-бы многим интересно.
спасибо.
Виджеты и так стандартные. А именно это поведение предоставляет шаблонизатор и позволяет вставлять те же виджеты прямо в текст статьи из БД. Или Вам интересно, для чего вообще поведения нужны?
ну, я так и понял, просто дело в выводе.
а по поводу "поведения"... Ваша подача написания, действительно очень импонирует, и если бы вы нашли время для создания хорошей статьи по этой теме, многим было б, полезно.
просто фр-к хочется использовать максимально, а такие пробелы - не очень хорошо.
Спасибо за тему. Может скоро и напишу.
Рассказал на примерах про использование поведений.
Дмитрий, не могли бы вы выложить код на какой-нибудь другой ресурс. Репозиторий на GitHub по какой-то причине выдает ошибку 500. А поведение выглядит очень интересным. Как раз под задачу, которая возникла буквально вот...
Спасибо. Уже не надо репозиторий GitHub ожил.
GitHub сейчас глючит. А пока можно взять на странице расширения.
У меня конфликтовала эта строка
После того как закоментировал всё заработало. Ясно одно, приложение спотыкается при попытке записи в кэш. Чем это может быть вызвано?
Наверное у Вас не указан компонент кэша в конфиге. Можете пока поставить заглушку:
return array( 'components'=>array( ... 'cache'=>array( 'class'=>'system.caching.CDummyCache', ), ), );