MODX - TV. Собственные типы ввода и вывода

В предыдущем посте я постарался описать механизмы обработки TV(переменных шаблона). Здесь же попытаюсь создать собственные типы ввода и вывода с максимальным использованием самой системы. Это будет уже второй пост на эту тему. Вот тут описано создание своих типов срествами максимально отделенными от MODX. Это тоже полезно. И, наверное, сложнее в реализации. Да и не было еще понимание всех механизмов.

Допустим есть на главной странице некая область для объявлений. И надо чтобы менеджер смог менять заголовок,текст, картинку. А стиль бы был постоянным. Т.е. доступны поля:

  • Заголовок
  • Текст
  • Картинка(не обязательно)
  • Резюме(не обязательно)
  • Возможно еще понадобятся...

Создадим TV. Тут есть нюанс. Входные и выходные типы надо проверять на готовом ресурсе. Не в TV.

  • Элементы->Шаблоны->Новый шаблон. Название - 'тестовый'. Сохраням.
  • Элементы->Доп.поля->Новое доп.поле. Название- 'obvl'. Заголовок - 'Редактор объявления'. Доступно для шаблонов - 'тестовый'. Сохраням.
  • Русурсы->Создать->Дочерний документ. Название - 'Для тестов'. Шаблон - 'тестовый'. В содержимом ресурса пишем -[[*obvl]]. Сохраням. При обнолении страницы(F5) должна появится закладка 'Дополнительные поля'. В нем наш TV с заголовком 'Редактор объявления'. Если вбить что-нибудь в поле TV. сохранить ресурс и нажать 'Просмотреть', то увидим на готовой странице содержимое TV.
Входные параметры - для всяких проверок(валидации) на стороне менеджера(редактор ресурса). Выходные - для отображения TV на странице. И те и другие можно редактировать только в самом TV. Т.е. сразу для всех страниц. Поэтому толку от них почти никакого. Они никак не привязаны к конкретной странице. К странице привязано только значение. Поэтому если мы хотим управлять TV в разрезе страниц, то это возможно только с помощью значения TV. А оно физически - blob(безразмерное), а по модели - text. Это вовсе не значит, что если мы хотим создать десяток различных доп.полей на ресурсе - надо создавать десяток TV. Можно создать десяток полей в одном TV. Просто в значении TV будем хранить массив. Входные параметры нам пока ни к чему, поэтому их пока пропустим.

Значением TV будет литеральный JSON(типа: "{'header':'Новинка!','content':'Только в сентябре.....','footer':'до 10-го числа скидка - 10%!!!','url':'qq.png' }").

Чтоб не скакать по всей файловой структуре создадим свое пространство имен. Система(шестерня)->Пространство имен. Я создал 'obvl'. Путь к ядру - "{core_path}/components/myTVs/". Там теперь будут "самодельные" TV. Путь к активам(assets) можно оставить пустым.

Это то, что мы видим в "Входные параметры"->"Тип ввода" в редакторе TV. Если открыть список всех доступных "типов ввода" мы там, естествеено, ничего "своего" еще не увидим. Нам надо создать новый класс своего типа ввода в определенном файле. Создадим файл :".../core/components/myTVs/tv/input/obvl.class.php". Содержание пока(для отладки) -

class modTemplateVarInputRenderObvl extends modTemplateVarInputRender {
    public function process($value,array $params = array()) {
    }
    public function getTemplate() {
        return $this->modx->getOption('core_path').'components/myTVs/tv/input/tpl/obvl.tpl';
    }
}
return 'modTemplateVarInputRenderObvl';
и файл TPL для smarty '...../core/components/myTVs/tv/input/tpl/obvl.tpl'
<h3>Привет!!!</h3>    
Может показаться что можно обойтись без каталога 'tv'. Напр.: 'myTVs/input/obvl.class.php'. Здесь дело в NS(пространстве имен). TV ищутся по пути pathNS+'tv\....'. После сохранения мы должны его теперь видеть в списке типов ввода под именем "obvl". У меня все сработало. Его и выбрал как тип ввода.

Теперь попробуем обновить страницу(ресурс) c нашим TV в менеджере и..... Ничего не меняется. Порывшись пол-дня(благо был выходной) в исходниках, я выяснил что NS не работает для TV-input-render. В массив путей поиска контроллеров в этом случае добавляются пути из плагина (на событие 'OnTVInputRenderList') и стандарный путь в каталоге процессоров ('..../core/model/modx/processors/element/tv/renders/mgr/input/'). Поэтому придется добавить свой плагин.

Создадим плагин с именем myTVs и содержимым:

$corePath = $modx->getOption('core_path',null,MODX_CORE_PATH).'components/myTVs/tv/';
switch ($modx->event->name) {
    case 'OnTVInputRenderList':
        $modx->event->output($corePath.'input/');
        break;
    case 'OnTVOutputRenderList':
        $modx->event->output($corePath.'output/');
        break;
    case 'OnTVInputPropertiesList':
        $modx->event->output($corePath.'inputoptions/');
        break;
    case 'OnTVOutputRenderPropertiesList':
        $modx->event->output($corePath.'properties/');
        break;
    case 'OnManagerPageBeforeRender':
        break;
}
На события 'OnTVInputRenderList'. На всякий случай еще и на 'OnDocFormPrerender','OnTVInputPropertiesList','OnTVOutputRenderList','OnTVOutputRenderPropertiesList'

Как я понял из "родной" документации такие плагины("плагины путей") были обычным делом в предыдущих версиях. В новых предлагается использовать пространства имен(NS). Но в нашем случае NS для отрисовки TV в менеджере не используется. Можно конечно, подправить исходник)))...

ВНИМАНИЕ! В версии 2.5.7 это уже исправлено, т.е. плагин путей уже не нужен. Достаточно записи в пространсве имен. ОДНАКО!!! В версии 2.6.5 все вернулось на круги своя. Не работает без плагина путей отрисовка "кастомного" TV inputType в ресурсе. Опять нужен плагин путей.

Тестовая отрисовка TV Теперь все работает! Стоит обратить внимание что в getTemplate() указан полный путь до TPL. Обычно указывается только "хвост", тогда TPL будет искаться относительно ".../manager/templates/default/", что для меня не очень удобно. У нас же все "самодельные" контроллеры и TPL будут в одном каталоге('core/components/myTVs/').

Поначалу я вообще не понимал: к чему нужен этот пункт меню("Медиа -> Источники файлов"). До этого момента... Потому как несколько опрометчиво предоставить возможность оператору загружать картинки в любое место на сайте. Вот для этого и служат "Источники файлов". Я создал источник "obvl", создал и назначил пути в опредлеленном каталоге в assets. И в конце назначил этот новый источник нашему TV. Там все просто, поэтому не буду подробно описывать. Теперь все в порядке, оператор при открытии встроенного файл-менеджера не сможет никуда зайти кроме своего каталога.

Окончательный вариант класса котроллера ввода TV ".../core/components/myTVs/tv/input/obvl.class.php":

<?php
/**
 * @var modX $this->modx
 * @var modTemplateVar $this
 * @var array $params
 *
 * @package modx
 * @subpackage processors.element.tv.renders.mgr.input
 */
class modTemplateVarInputRenderObvl extends modTemplateVarInputRender {
    public function process($value,array $params = array()) {                           
        $this->modx->getService('fileHandler','modFileHandler', '', array('context' => $this->modx->context->get('key')));
        /** @var modMediaSource $source */
        $source = $this->tv->getSource($this->modx->resource->get('context_key'));
        if (!$source) return '';
        if (!$source->getWorkingContext()) {return '';}                                                                                       
        $source->setRequestProperties($_REQUEST);//добавляет аргумент к $source->properties                                   
        
        $source->initialize(); //получение доступа, установка свойств
        $this->modx->controller->setPlaceholder('source',$source->get('id'));
        $params = array_merge($source->getPropertyList(),$params);              
        
        if (!$source->checkPolicy('view')) {
            $this->setPlaceholder('disabled',true);
            $this->tv->set('disabled',true);
            $this->tv->set('relativeValue',$this->tv->get('value'));
        } else {
            $this->setPlaceholder('disabled',false);
            $this->tv->set('disabled',false);
            $value = $this->tv->get('value');

$data = $this->modx->fromJSON($value);    // - делаем массив из литерального json(литерального json - это и есть значение TV)
$js="";
if (is_array($data)) {$js=$this->modx->toJSON($data);}                      
else {$js="{}";}                     
            
$this->setPlaceholder('itemdata',$data);  // - для инициализации input's(полей ввода)            
$this->setPlaceholder('jsdata',$js);      // - для инициализации hiden input, где хранится литеральный json           
        if (!empty($data['img'])) {
                $params['openTo'] = $source->getOpenTo($data['img'],$params);
            }//if
            $this->tv->set('relativeValue',$value);                                       
        }//else
        
        $this->setPlaceholder('params',$params);    
        $this->setPlaceholder('tv',$this->tv);      
    }

    public function getTemplate() {
        return $this->modx->getOption('core_path').'components/myTVs/tv/input/tpl/obvl.tpl';
    }
}
return 'modTemplateVarInputRenderObvl';

В функции process - инициализация fileHandler и MediaSource для загрузки картинок. Там же назначаютися переменные для smarty через $this->setPlaceholder(....). Как аргумент получает значение(value) TV, и массив $params в котором входные свойства TV. Они не очень мне полезны, поскольку назначаются непостредственно на TV. Т.е. не привязаны к конкретному ресурсу. Потом к $params присоединяются $_REQUEST, свойства "Источника файлов" и в итоге доступны:

$params=Array
(
    [basePath] => assets/for_obvl/                               |
    [basePathRelative] => 1                                      |
    [baseUrl] => assets/for_obvl                                 |
    [baseUrlRelative] => 1                                       |-> Media Sourse properties
    [allowedFileTypes] =>                                        |
    [imageExtensions] => jpg,jpeg,png,gif                        |
    [thumbnailType] => png                                       |
    [thumbnailQuality] => 90                                     |
    [skipFiles] => .svn,.git,_notes,nbproject,.idea,.DS_Store    |
    [a] => resource/update      |
    [id] => 38                  |-> $_REQUEST
    [allowBlank] => 1       |    
    [maxLength] =>          |->  input properties
    [minLength] =>          |   
)    
    
$this->tv - сам TV
$value - значение TV

Идея в том, что в виджете в скрытом input, хранится литеральный json, в котором хранятся значения всех полей ввода. Они и посылаются на сервер при сохранении ресурса.

Теперь сделаем форму для работы с TV. Он же - виджет. У нас будет непростая форма. На ней будет несколько полей ввода разных типов, в том числе и для загрузки картинки. Это все делается в TPL-файле - '...../core/components/myTVs/tv/input/tpl/obvl.tpl'.

<h3>Картинка(фото)</h3>
<div id="tv-image-{$tv->id}"></div>
<div id="tv-image-preview-{$tv->id}" class="modx-tv-image-preview">
    {if $itemdata["tv-obvl-url"]}<img src="{$_config.connectors_url}system/phpthumb.php?w=400&src={$itemdata["tv-obvl-url"]}&source={$source}" alt="" />{/if}
</div>
<div id="tv-rest-88"></div>
<script type="text/javascript">
{literal}
Ext.onReady(function() {
{/literal}    
{literal}
var obvlPanel =new Ext.Panel({
    bodyPadding: 5, 
    layout: 'form',
    width: 600,
    title: 'Т Е К С Т',
    renderTo: 'tv-rest-88',
    items: [
{
  {/literal}    
            xtype: 'hidden'
            ,name: 'tv{$tv->id}'
            ,id: 'tv{$tv->id}'
            ,value: '{$jsdata}'
  {literal}
}
,
{
  {/literal}    
        xtype: 'textfield'
        ,name: 'tv-obvl-header'
        ,id: 'tv-obvl-header'
        ,fieldLabel: 'Заголовок'
        ,labelSeparator :':'
        ,value: '{$itemdata["tv-obvl-header"]}'
        ,anchor: '97%'
{literal}
        ,listeners: {'change':{fn:inp_change_tv,scope:this}} 
}
,
{  {/literal}    
        xtype: 'textarea'
        ,name: 'tv-obvl-content'
        ,id: 'tv-obvl-content'
        ,fieldLabel: 'Содержание'
        ,labelSeparator :':'
        ,value: '{$itemdata['tv-obvl-content']}'
        ,anchor: '97%'
{literal}
        ,listeners: {'change':{fn:inp_change_tv,scope:this}} 
}
,
{ {/literal}    
        xtype: 'textfield'
        ,name: 'tv-obvl-footer'
        ,id: 'tv-obvl-footer'
        ,fieldLabel: 'Footer(Низ, подвал)'
        ,labelSeparator :':'
        ,value: '{$itemdata['tv-obvl-footer']}'
        ,anchor: '97%'
{literal}
        ,listeners: {'change':{fn:inp_change_tv,scope:this}} 
}]
});
    var fld{/literal}{$tv->id}{literal} = MODx.load({
    {/literal}
        xtype: 'modx-panel-tv-image'
        ,renderTo: 'tv-image-{$tv->id}'
        ,tv: 'img_{$tv->id}'
        ,value: '{$itemdata['tv-obvl-url']}'
        ,relativeValue: '{$itemdata['tv-obvl-url']}'
        ,width: 400
        ,allowBlank: {if $params.allowBlank == 1 || $params.allowBlank == 'true'}true{else}false{/if}
        ,wctx: '{if $params.wctx}{$params.wctx}{else}web{/if}'
        {if $params.openTo},openTo: '{$params.openTo|replace:"'":"\\'"}'{/if}
        ,source: '{$source}'
    {literal}
        ,msgTarget: 'under'
        ,listeners: {
            'select': {fn:function(data) {   alert('select');
                MODx.fireResourceFormChange();
                var d = Ext.get('tv-image-preview-{/literal}{$tv->id}{literal}');
                if (Ext.isEmpty(data.url)) {
                    d.update('');
                }else {
                    {/literal}
                    d.update('<img src="{$_config.connectors_url}system/phpthumb.php?h=150&w=150&src='+data.url+'&wctx={$ctx}&source={$source}" alt="" />');
                    var el= document.getElementById("tv{$tv->id}");
                    {literal}
 var json_=Ext.decode(el.value,1);
 json_['tv-obvl-url']=data.url;              
 el.value=Ext.encode(json_);
                }
                                         }//function
                      }//selsect
                    }//listeners
    });//new Ext.Panel
    MODx.makeDroppable(Ext.get('tv-image-{/literal}{$tv->id}{literal}'),function(v) {
        var cb = Ext.getCmp('tvbrowser{/literal}{$tv->id}{literal}');
        if (cb) {
            cb.setValue(v);
            cb.fireEvent('select',{relativeUrl:v});
        }
        return '';
    });
});//Ext.onReady

function inp_change_tv(tf,nv,nm) // - при изменении полей обновляет литеральный json(в нем - все поля)
{
 {/literal}
 var el= document.getElementById("tv{$tv->id}");
 {literal}
 var json_=Ext.decode(el.value,1);
 json_[tf.name]=nv;
 el.value=Ext.encode(json_);
}
{/literal}
</script>
    

В итоге получаем несколько полей ввода в одном TV и с полноценным файл-менеджером для загрузки и выбора картинки:

Часть виджета для управления картинкой я позаимствовал из стандартного "входного типа" для image (почти без изменений).

На самом ресурсе в содержании напишем:

Поскольку в плагине мы определили пути для 'OnTVOutputRenderList' то наш скрипт вывода система будет искать здесь: "...../core/components/myTVs/tv/output/obl.class.php". Я не случайно избежал слово "контроллер". Поскольку контроллер вызывается только в контексте менеджера. И поэтому если посмотреть как сделан вывод TV в WWW для стандартных TV, то видно, что HTML генерится прямо в PHP. А это не совсем удобно и противоречит основной концепции modx(отделение HTML от кода (php)). В modx smarty(шаблонизатор) исползуется в админке(менеджере), и не принято его использовать при выводе страницы. Для это есть свой парсер. Но можно использовать smarty и для front-end. В этом случае архитекртура TV будет логически законченной. К тому есть мнение, что smarty работает быстрее. Есть даже "маньяки", которые используют smarty и особые php-шаблоны. Вместо стандарных. Но мы не будем так радикально уродовать modx.

"...../core/components/myTVs/tv/output/obl.class.php":    
<?php
if(!class_exists('TemplateObvlOutputRender')) {
    class TemplateObvlOutputRender extends modTemplateVarOutputRender {
        public function process($value,array $params = array()) {
        $data = $this->modx->fromJSON($value);                     //загоняем json в массив
        $data["tv-dir-MS"]="http://........../assets/for_obvl/";   //добавляем в массив путь до ресурсов(картинок) наших TV
        $templatePath=$this->modx->getOption('core_path').'components/myTVs/tv/output/';    //путь к шаблонам (TPL)        
        $this->modx->getService('smarty', 'smarty.modSmarty', '', array('template_dir' => $templatePath,)); //загружаем smarty
            $this->modx->smarty->assign('data',$data);      //передаем массив в smarty как переменную
        $tpl='obvl.tpl';                                             
        $this->modx->smarty->setTemplatePath($templatePath);         
        return $this->modx->smarty->fetch($tpl);            //запуск шаблонизатора(smarty)         
                                }//process
    }//class
}//if
return 'TemplateObvlOutputRender';
И сам шаблон вывода(TPL) -
"...../core/components/myTVs/tv/output/obvl.tpl":
<div id="wr" style="position:relative;width:100%;">
<div class='jumbotron' style="position:relative;width:660px; margin:10px auto;padding:20px;">
  <div class='panel panel-primary'>    
     <div class='panel-heading'>
        <h2 class='panel-title text-center'>{$data["tv-obvl-header"]}</h2>
     </div>
     <div class='panel-body'>
        <img src='{$data["tv-dir-MS"]}{$data["tv-obvl-url"]}'></img>
        <p>{$data["tv-obvl-content"]}</p>
        <span class='glyphicon glyphicon-ok label label-danger pull-right'> {$data["tv-obvl-footer"]}</span>
     </div>   
  </div>     
</div>    
</div> 
   
    
    

В итоге на странице(контекст WWW) -

В результате получилось сделать входной тип TV с несколькими полями ввода, с встроенным файл-менеджером. Причем и контроллеры и виджет и шаблон вывода находятся в одном каталоге, что очень удобно для редактирования. Контроллер реализован классически - как класс. Здесь я умышленно не использовал html-редактор для оператора. Хотя это не сложно. Обычно просят его подключить. Но не все операторы обладают чувством меры. Просто ужасные получаются иногда новости на странице. Поэтому в последнее время стараюсь использовать предварительное жесткое форматирование, а от опертора требуется вводить только текст.

Итого что стало понятно:

  • В пространстве имен прописываем путь, где система будет искать php-классы с нашими типами ввода-вывода. Напр.: '{core_path}/components/myTVs/'
  • Если тип ввода не появился в выборе, значит текущая версия modx недоделанная - тогда пишем плагин путей
  • Тип ввода
    • в каталоге путь_в_простр_имен+tv/input/ создаем наши классы. Каждый класс в отдельном файле. Эти файлы должны возвращать имя класса. Тогда тип появится при выборе.
    • Класс является потомком класса modTemplateVarInputRender
    • Класс должен иметь метод process:
      • Надо определить функцию getTemplate. Которая возвращает путь до шаблона формы.
      • Обрабатывается источник файлов(права доступа, id источника и т.д.).
      • Назначаются плейсхолдеры для смарти
    • Все данные хранятся в value как json(можно и в другом фомате, если вам мало гемороя)
    • Виджет лучше делать на extJS. Но тут руки развязаны. Можно на чем угодно. В принципе.
    • Smarty не надо инициализировать. Он уже подключен.
    • Одно поле делается скрытным. Для хранения json. При изменении какого-либо поля, обработчик extJS собирает все поля в json и переписывает это скрытое поле. С него и уходит на сервер через ajax.
    • Серверная часть полностью автоматизирована. Вообще ничего не надо делать. Json будет записан в значение TV через коннектора и процессоры. Для этого достаточно скрытых полей id,name и value:
      ,name: 'tv{$tv->id}'
      ,id: 'tv{$tv->id}'
      ,value: '{$jsdata}'
                          
  • Тип вывода
    • в каталоге путь_в_простр_имен+tv/output/ создаем наши классы. Каждый класс в отдельном файле. Эти файлы должны возвращать имя класса. Тогда тип появится при выборе.
    • Класс должен иметь метод process.
    • Автовызов шаблона не предусмотрен. Поэтому тут полная свобода действий. Можно сразу вернуть "пропаренный" html. Можно плейсходлеры нагененрить и расставить тэги для front-end. Можно припахать smarty.
Можно и не заморачиваться, а просто использовать каталоги, где сам modx ищет встроенные типы:
  • Типы ввода -'core/model/modx/processors/element/tv/renders/mgr/input/newInpType.php'
  • Каталог шаблонов - 'manager/templates/default/...'
Но это не очень удобно. Поэтому лучше забыть про этот вариант.


Комментарии 0






Разрешённые теги: <b><i><br>Добавить новый комментарий: