В предыдущем посте я постарался описать механизмы обработки TV(переменных шаблона). Здесь же попытаюсь создать собственные типы ввода и вывода с максимальным использованием самой системы. Это будет уже второй пост на эту тему. Вот тут описано создание своих типов срествами максимально отделенными от MODX. Это тоже полезно. И, наверное, сложнее в реализации. Да и не было еще понимание всех механизмов.
Допустим есть на главной странице некая область для объявлений. И надо чтобы менеджер смог менять заголовок,текст, картинку. А стиль бы был постоянным. Т.е. доступны поля:
Создадим 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 в менеджере не используется. Можно конечно, подправить исходник)))...
Теперь все работает! Стоит обратить внимание что в 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-редактор для оператора. Хотя это не сложно. Обычно просят его подключить. Но не все операторы обладают чувством меры. Просто ужасные получаются иногда новости на странице. Поэтому в последнее время стараюсь использовать предварительное жесткое форматирование, а от опертора требуется вводить только текст.
Итого что стало понятно:
,name: 'tv{$tv->id}' ,id: 'tv{$tv->id}' ,value: '{$jsdata}'
Комментарии 0