Hamster-fox.ru Техническая оптимизация и обновление MODX Revolution. Часть 2. Шаблоны и скрипты.
В продолжение предыдущего топика. В данном топике я просто выложу некоторые Smarty-шаблоны с сайта, а так же код процессора, который я использовал на замену Wayfinder-у. Под катом много кода и комментов. 1. Smarty-шаблоны. Как я и говорил не раз, phpTemplates+Smarty — это то, что нам позволяет значительно снизить нагрузку на MODX-сайт. Но помимо этого Smarty-шаблоны имеют одну офигенную штуку, которой в MODX-шаблонизации просто нет, а именно — наследование/расширение шаблонов. Давайте рассмотрим это на примере Smarty-шаблонов из Hamster-а. Основной шаблон (используется остальными расширяющими шаблонами.)
{config name=site_name assign=site_name}
{* HEAD *}
<link rel="shortcut icon" href="/assets/images/favicon.ico" type="image/ico" />
<link rel="stylesheet" media="all" href="/assets/hamster/css/style.css" />
<link rel="stylesheet" media="all" href="/assets/hamster/css/prettyPhoto.css" />
<link type="text/css" rel="stylesheet" href="/assets/components/minishop/css/web/jquery.stickr.css">
<!--[if IE]>
<![endif]-->
<section id="main">
<header>
{* Header *}
<a id="logo" title="{$site_name}" href="/"></a>
<nav id="menu">
{*snippet name=Wayfinder params="startId=`0`&level=`1`"*}
{assign var=params value=[
"startId" => 0
,"level" => 1
,"cacheable" => true
,"id" => "mainMenu"
]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"}
</nav>
<div id="phone_order">
Заказ по телефону:<br />+7 (495) 221-90-21<br />+7 (495) 221-90-23<br />+7 (925) 092-28-33
</div>
<div id="user_panel">
<a id="cartLink" href="{link id=4}" title="Корзина">Корзина</a>
<span class="uLogin">[[!uLogin? &providers="vkontakte,facebook,odnoklassniki,twitter,mailru,google" &hidden="" &userGroups="Authorized" ]]</span>
</div>
{* Eof Header *}
</header>
<section id="columns">
<aside id="catalog">
{* Catalog.nav *}
<h3><a href="{link id=2}" title="Каталог товаров">Каталог товаров</a>:</h3>
<div id="product_lists">
{*snippet name=Wayfinder params="startId=`1` &level=`1` &rowTpl=`listRowTpl`"*}
{assign var=params value=[
"startId" => 1
,"level" => 1
,"cacheable" => true
,"id" => "secondMenu"
]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"}
</div>
<div id="search">
{* Search *}
<form id="search_form" action="{link id=6}">
<input type="text" placeholder="Поиск по артикулу или названию" name="search[text]" /> <input type="submit" value="Искать" />
</form>
{* Eof Search *}
</div>
<div id="catalog_tree">
{*snippet name="Wayfinder@MainCatalogMenu"*}
{assign var=params value=[
"startId" => 2
,"level" => 4
,"sortBy" => "pagetitle"
,"levelClass" => "level"
,"where" => [
"template" => 2
]
,"cacheable" =>true
,"id" => "catalog"
]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"}
</div>
{* Eof Catalog.nav *}
</aside>
<article id="content">
{block name=Breadcrumbs}<div id="breadcrumbs">{snippet name=Breadcrumbs params="showHomeCrumb=`0` ¤tAsLink=`0` &showCurrentCrumb=`0`"}</div>{/block}
{block name=content}
{field name=content}
{/block}
</article>
<div class="clear"></div>
</section>
</section>
</section>
<footer>
{* Footer *}
<section id="footers">
<section id="brands">
<div class="smartlist">
{* БрендыХамстерФокс *}
{snippet name=brands_slider}
{* Eof БрендыХамстерФокс *}
</div>
</section>
<aside class="left">
© Хамстер-Фокс.2012<br />
Все права защищены.
</aside>
<aside class="right">
</aside>
<div class="clear"></div>
</section>
{literal}
<!-- Yandex.Metrika counter -->
<noscript><div><img src="//mc.yandex.ru/watch/19623109" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
{/literal}
{* Eof Footer *}
</footer>
Заметка: Элементы {*… *} — это комментарии, то есть не обрабатываются и никуда не выводятся. В некоторых комментариях имеются вызовы сниппетов, на замену которым использованы процессоры или типа того. И вот здесь мы сразу рассмотрим что же такое «наследование шаблонов» и почему у нас сразу такой большой шаблон, а не разбросанный на отдельные кусочки, чтобы эти кусочки можно было использовать в других шаблонах (как это традиционно используется в MODX-шаблонах). Для этого давайте опять посмотрим на исходный MODX-шаблон:
[[$Head]]
{block name=content}
{if $modx->resource->parent == 3}Бренд «{field name=pagetitle}»{else}{field name=pagetitle}{/if}
{block name=content}
{field name=pagetitle}
{field name=content}
<div class="post-comments" id="comments">[[!Quip?
&thread=`blog-post-[[*id]]`
&threaded=`1`
&dateFormat=`%d/%m/%Y %H:%I`
&tplComment=`commentTpl`
&closeAfter=`30`
]]
<br /><br />
[[!QuipReply?
&thread=`blog-post-[[*id]]`
&requireAuth=`1`
&moderate=`0`
&tplAddComment=`commentWithUlogin`
&tplLoginToComment=`authToComment`&closeAfter=`30`
]]
</div>
{/block} В общем, со Smarty-шаблонами творить можно что угодно. 2. Замена Wayfinder. Внимание! Если вы используете Smarty-блоки, внимательно читайте этот комментарий, чтобы избежать бесконечной рекурсии. Вот это, пожалуй, лучшая наработка и того, что было сделано на Hamster-е. Планирую ее в дальнейшем оформить в пакет и постепенно дорабатывать. Для начала разберем, как это дело работает (кстати, при этом мы увидим еще один интересный фокус со Smarty-шаблонами). Сам процессор. «Сердце» модуля — этот процессор. Его главное предназначение — сделать выборку документов, участвующих в формировании менюшки. Вот его код: <?php
class modWebMenuGetCatalogMenuProcessor extends modProcessor{
protected $activeIDs = array(); // ID of active parents
public function initialize(){
$this->setDefaultProperties(array(
'id' => 'menu', // Menu id
'cacheable' => false,
'startId' => $this->modx->resource->id,
'level' => 1,
'sortBy' => 'menuindex',
'sortOrder' => 'ASC',
'levelClass' => '',
'activeClass' => 'active',
'ignoreHidden' => false,
'showUnpublished' => false,
));
return parent::initialize();
}
public function process() {
$output = '';
// get active parents
if(!empty($this->modx->resource) AND $this->modx->resource instanceOf modResource){
$resource = $this->modx->resource;
$this->activeIDs[] = $resource->id;
while($resource = $resource->getOne('Parent')){
$this->activeIDs[] = $resource->id;
}
}
// get menu items
if(!$items = $this->getMenuItems()){
return;
}
// prepare menu items
$items = $this->prepareMenu($items);
return array(
'success' => true,
'message' => '',
'object' => $items,
);
}
public function getMenuItems(){
$items = array();
$startId = $this->getProperty('startId');
$level = $this->getProperty('level');
$cacheable = $this->getProperty('cacheable');
$id = $this->getProperty('id', 'menu');
$cacheKey = $this->modx->context->key."/{$id}/{$startId}";
if($cacheable){
if($fromCache = $this->modx->cacheManager->get($cacheKey)){
return $fromCache;
}
}
//else
if($items = $this->getItems($startId, $level)){
if($cacheable){
$this->modx->cacheManager->set($cacheKey, $items);
}
}
return $items;
}
protected function getItems($parent, $level){
$level--;
$items = array();
$q = $this->modx->newQuery('modResource');
$where = $this->getDefaultConditions();
$where['parent'] = $parent;
$q->where($where);
$q->select(array(
'id', 'parent', 'pagetitle', 'longtitle', 'description', 'menutitle', 'link_attributes', 'uri', 'alias',
));
$q->sortby($this->getProperty('sortBy'), $this->getProperty('sortOrder'));
if($q->prepare() && $q->stmt->execute()){
while($row = $q->stmt->fetch(PDO::FETCH_ASSOC)){
if($level>0){
$row['childs'] = $this->getItems($row['id'], $level);
}
else{
$row['childs'] = array();
}
$items[$row['id']] = $row;
}
}
return $items;
}
protected function prepareMenu(array & $items, $currentlevel=1){
$levelClass = $this->getProperty('levelClass');
$activeClass = $this->getProperty('activeClass');
foreach($items as &$item){
$cls = array();
if($levelClass){
$cls[] = "{$levelClass}{$currentlevel}";
}
$item['linktext'] = ($item['menutitle'] ? $item['menutitle'] : $item['pagetitle']);
if(in_array($item['id'], $this->activeIDs)){
if($activeClass){
$cls[] = $activeClass;
}
}
$item['cls'] = implode(" ", $cls);
if($item['childs']){
$item['childs'] = $this->prepareMenu($item['childs'], $currentlevel+1);
}
}
return $items;
}
protected function getDefaultConditions(){
$where = array(
'deleted' => 0,
);
if(!$this->getProperty('showUnpublished')){
$where['published'] = true;
}
if(!$this->getProperty('ignoreHidden')){
$where['hidemenu'] = false;
}
if($_where = $this->getProperty('where')){
$where = array_merge($where, $_where);
}
return $where;
}
} return 'modWebMenuGetCatalogMenuProcessor'; ?> Помимо выборки документов, этот процессор умеет кешировать результат и в дальнейшем использовать его для формирования меню. Возвращает процессор массив документов (элементов меню). Конечно процессор не все поля документов получает, а только самые необходимые. В дальнейшем я планирую его серьезно доработать/переработать, чтобы он был еще более гибкий (есть ряд мыслей, включая ввод методов типа setSelection и объединение методов getItems и prepareMenu), но это чуть позже. Далее остается только набить эти данные в шаблоны, чтобы сформировать конечный HTML-код шаблона. И вот здесь как раз я и покажу еще одну фишку Smarty-шаблонов, которая мне очень понравилась :-) Итак, рассмотрим пример вызова этого процессора и формирование менюшки. Вот код: {assign var=params value=[ "startId" => 2 ,"level" => 4 ,"sort" => "sortBy" ,"levelClass" => "level" ,"where" => [ "template" => 2 ] ,"cacheable" =>true ,"id" => "catalog" ]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"} Что здесь происходит? 1. Набиваем массив параметров, которые мы передадим в процессор: {assign var=params value=[
"startId" => 2 // Стартовый раздел
,"level" => 4 // Количество уровней вложенности
,"sortBy" => "pagetitle" // Сортировка по заголовку
,"levelClass" => "level" // класс уровня (будет level1, level2 и т.п.)
,"where" => [ // Условия поиска
"template" => 2 // документы с шаблоном 2
]
,"cacheable" =>true // Кешировать результат
// ID меню (использется в формировании ключа кеша,
// чтобы случайно не пересекся с кешем других менюшек)
,"id" => "catalog"
]} 2. Вызываем процессор: {processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result} ns — это namespace, то есть пространство имен модуля в MODX.
assign=result — это присваиваем полученный результат переменной $result. 3. Присваиваем массив полученных элементов переменной $items: {assign var=items value=$result.object} 4. Подгружаем Smarty-шаблон, в котором будет выполняться оформление этого массива в конечный код менюшки: {include file="inc/menu/catalog/outer.tpl"} Вот код этого шаблончика:
-
{foreach $items as $item}
{* Wrapper *}
{include file="inc/menu/catalog/row.tpl"}
{/foreach}