MODX - комплексный(составной) TV

Создание комплексных(составных) TV template variable (переменные шаблона ресурса)

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

Пусть наш TV будет иметь ID=6

План действий такой:

  • PHP-процессор (на сервере) должен передать в TPL(для SMARTY) литеральный(как строка) JSON (PHP не знает что такое JSON)
  • При инициализации в скрытый input с name="tv6" загрузим литеральный JSON. Это сделает Smarty.
  • Создать панель (в ExtJS)
  • В панель поместить "выстраданный" комбобокс(выбор поездов) и два текстовых поля(прибытие и убытие)
  • При отрисовке полей панели нашего TV заполним их "парсонув" литеральный JSON(из скрытого input c name="tv6")
  • Для манипуляций с DOM надо присвоить вменяемые ID отображаемым полям. Путь будет "tv6-"+имя_поля_в_базе
  • При изменении любого поля - вызвать обработчик для обновления JSON в input(#tv6)
Ну и все. Наверное... .

Процессор "core/model/modx/processors/element/tv/renders/mgr/input/" он же контроллер ввода (есть некоторый сумбур в терминологии)

В пред. посте я заблуждался, когда указал путь "core/model/modx/processors/element/tv/renders/mgr/inputproporties/". По этому пути ищется процессор для отображения параметров TV. При редактировании самого TV. Пока не понимаю я для чего у TV параметры.

     
 <?php
if (!empty($this->value)) 
{                        
 $data = $modx->fromJSON($this->value);
  if (!is_array($data))
  {
    $this->value="{}";  
    $this->processedValue="{}"; 
  }                      
}                        
return $modx->controller->fetchTemplate('element/tv/renders/input/Vologda-2.tpl');
/*
 - я тут упростил код(аналог gellaryitem), чтоб попусту не напрягать Smarty
 - если значение не json или кривой json, то в значение TV просто пишется пустой json
 - $this->value="{}" никакого эффекта не возымело, оказалось есть еще одна переменная $this->processedValue!
*/

Проведя ряд тестов на сервере я выяснил, что код выполняется в контексте объекта modTemplateVarInputRender. Полей очень много. Важные для меня на сей момент:id=6, value="............" это поля нашего TV. Но легче от этого не стало... Как только я не пытался обнулить tv->value, Smarty где-то брал прежнее значение. Просканировав все массивы в modTemplateVarInputRender я нашел еще одно место где есть прежнее значение TV. Это - [_fields][value] где-то в дебрях xPDO. Можно, конечно, и его обнулить. Но это уж слишком. Мало ли где еще вытает. Вообще странно, зачем в куче мест дублировать одно и то же значение.

Чтобы надежно контролировать содержание TV.value используем прием из gellary. В процессоре присвоим его значение переменной Smarty. И далее будем работать только с ней. Мимо xPDO.

Окончательный рабочий вариант:

 
<?php
include_once($_SERVER['DOCUMENT_ROOT']."/aset_test.php"); delete_test_file();
$data = $modx->fromJSON($this->value);
$js="";
if (is_array($data)) {$js=$modx->toJSON($data);}                      
else {$js="{}";}
$modx->smarty->assign('itemjson',$js);

return $modx->controller->fetchTemplate('element/tv/renders/input/Vologda-2.tpl');

/*
  - 'itemjson' - переменная которой присвоен JSON и которая будет видна Smarty при обработке TPL("$itemjson")
*/

TPL 'element/tv/renders/input/Vologda-2.tpl' отрисовка нашего TV в менеджере

 
 <h3>Привет из Вологды!</h3>
<h4>$itemjson="{$itemjson}" $tv->id={$tv->id}</h4>
<div id="tv_vologda_fo_repl"></div>
<script type="text/javascript">
// <![CDATA[
{literal}
var VologdaPanel =new Ext.Panel({
    bodyPadding: 5,  
    width: 300,
    title: 'Filters',
    renderTo: 'tv_vologda_fo_repl',
    items: [{
{/literal}    
     xtype: 'modx-combo'
    ,hiddenName: 'tv{$tv->id}'
    ,id: 'tv_vologda'
    ,width: 300
    ,value: '{$tv->value}'
    ,displayField:'name'
    ,valueField:'name'
    ,url: '/mdx/assets/aset_test/connector_v2.php'
{literal}  },{  {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_in'
        ,id: 'tv{$tv->id}-vol2_in'
        ,fieldLabel: 'Прибытие'
        ,value: 'Тут будет прибытие'
        ,anchor: '97%'
{literal} },{ {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_out'
        ,id: 'tv{$tv->id}-vol2_out'
        ,fieldLabel: 'Отправление'
        ,value: 'Тут будет отправление'
        ,anchor: '97%'
{literal}}
]
});
// ]]>
{/literal}


 

Все выводит. Пока коряво. Ну это исправимо. При выводе элементов нужно следить за уникальностью ID. Я тут пол-дня разбирался: почему не выводит все текстовые поля. Оказалось у некоторых были одинаковые ID.

Тут я вывел панель на чистом ExtJS. Потому что "xtype modx-panel" не знаю зачем и создан. Ничего нового он не добавляет. И вот тут встает ряд вопросов.

  • Надо как-то заполнить значения полей из JSON
  • Определить способ обновления всех полей в JSON, а JSON "сидит" в hidden input("name='tv6'"). Т.к. при submit формы именно с hidden улетит на сервер наш JSON

Загрузка полей

Тут вижу два варианта:

  • На сервере назначить переменные для Smarty
  • На стороне броузера покорячится с JSON
Проще будет подсунуть Smarty переменную.

В процессоре на время (для отладки) подменим переменную $data и добавим новую переменную Smarty

    
$data =array("name"=>"Знач name","in"=>"Value in","out"=>"Value out");
......
......
......
$modx->smarty->assign('itemdata',$data);

И подправим TPL

....
var VologdaPanel =new Ext.Panel({
    bodyPadding: 5,  
    width: 300,
    title: 'Поезда',
    renderTo: 'tv_vologda_fo_repl',
    items: [{
{/literal}    
     xtype: 'modx-combo'
    //,hiddenName: 'tv{$tv->id}'
    ,id: 'tv_vologda'
    ,width: 300
    ,value: '{$itemdata['name']}'
    ,displayField:'name'
    ,valueField:'name'
    ,url: '/mdx/assets/aset_test/connector_v2.php'
{literal}  },{  {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_in'
        ,id: 'tv{$tv->id}-vol2_in'
        ,fieldLabel: 'Прибытие'
        ,value: '{$itemdata['in']}'
        ,anchor: '97%'
{literal} },{ {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_out'
        ,id: 'tv{$tv->id}-vol2_out'
        ,fieldLabel: 'Отправление'
        ,value: '{$itemdata['out']}'
        ,anchor: '97%'
{literal}}
]
});
.....

Отлично! Не ожидал что это будет очень просто. Smarty - рулит))).

Обновление JSON

Напомню, что отправляется на сервер именно JSON, в нашем примере он сидит в hidden input c name="tv6". А поскольку не охота вклиниватся в handler при сохранении, то при каждом изменении элементов надо обновлять JSON. Для этого напишем функцию и назначим ее как handler для каждого елемента. Причем попытаемся сделать это несколько иначе, чем принято в MODx.

В последний input добавим обработчик("listeners") на изменение. А саму функцию прямо тут-же после панели и припишем. Это не в традициях MODX. Там все функции, как правило, методы объектов. Ну на это много причин. По мне дак так намного нагляднее и проще в отладке.

{literal} },{ {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_out'
        ,id: 'tv{$tv->id}-vol2_out'
        ,fieldLabel: 'Отправление'
        ,value: '{$itemdata['out']}'
        ,anchor: '97%'
{literal}
        ,listeners: {'change':vol_2_inp_change,scope:this} 
}]
});

function vol_2_inp_change(tf,nv,nm) 
{
 args_="tf.name="+tf.name+"  nv="+nv+"  nm="+nm;
 alert(args_);
}

// ]]>

{/literal}

Alert нам выдал:"tf.name=vol2_out nv=Value out_new nm=Value out".

А это значит что tf - объект "xtype: 'textfield'", nv - новое значение, nm - старое.

После очередного чтения документации по extJS, окончательный вариант всего скрипта:

       <h3>Привет из Вологды!</h3>
<div id="tv_vologda_fo_repl"></div>
<input type="hidden" id="tv{$tv->id}" name="tv{$tv->id}" value='{$itemjson}'/>
<script type="text/javascript">
// <![CDATA[

var vologda_hidden_id={$tv->id};

{literal}
var VologdaPanel =new Ext.Panel({
    bodyPadding: 5, 
   layout: 'form',
    width: 300,
    title: 'Поезда',
    renderTo: 'tv_vologda_fo_repl',
{/literal}    
    hiddenName: 'tv{$tv->id}',
{literal}
    items: [{
{/literal}    
     xtype: 'modx-combo'
    ,id: 'tv_vologda'
    ,name: 'vol_TuTu'
    ,width: 300
    ,value: '{$itemdata['vol_TuTu']}'
    ,displayField:'name'
    ,valueField:'name'
    ,url: '/mdx/assets/aset_test/connector_v2.php'
{literal}
    ,listeners: {'change':vol_2_inp_change,scope:this} 
},{  {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_in'
        ,id: 'tv{$tv->id}-vol2_in'
        ,fieldLabel: 'Прибытие'
        ,labelSeparator :':'
        ,value: '{$itemdata['vol2_in']}'
        ,anchor: '97%'
{literal}
        ,listeners: {'change':vol_2_inp_change,scope:this} 

},{ {/literal}    
        xtype: 'textfield'
        ,name: 'vol2_out'
        ,id: 'tv{$tv->id}-vol2_out'
        ,fieldLabel: 'Отправление'
        ,labelSeparator :':'
        ,value: '{$itemdata['vol2_out']}'
        ,anchor: '97%'
{literal}
        ,listeners: {'change':vol_2_inp_change,scope:this} 
}]

});

function vol_2_inp_change(tf,nv,nm) 
{
 {/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}

Ну вот и все. Скрипт обновления json я сделал иначе чем в gellary. Там уж больно замороченно. Там создается массив имен полей ввода. При каждом изменении по этому массиву обновляется объект, содержащий все значения полей с их именами. И в конце обновляется hidden. И каждая операция в отдельной функции. Это, возможно, сделано для удобства регенерации изображения при смене параметров.

Я же нахожу hidden-input. Считываю и создаю json. В нем по имени меняю значение. И сохраняю преобразованный в литерал json. Этот стиль, конечно, не приветствуется гуру MODX, но мне как-то так удобнее и несравненно проще.

Для закрепления в памяти, зафиксируем изменяемые файлы:

  • Процессор(контроллер ввода): "/core/model/modx/processors/element/tv/renders/mgr/input/"
  • TPL(шаблон): '/manager/templates/default/element/tv/renders/input/Vologda-2.tpl'

Итак с собственным составным типом ввода покончено). Теперь это не кажется уж таким заумным. Но что-бы это все нормально отображалось во фронт-энде(для посетителей сайта), надо написать свой тип вывода.

Собственный тип вывода для составного типа

"MODx Revolution lets you create custom output types fairly easily."

из документации MODX

Согласно документации для создания своего типа вывода понадобится 3 файла.

  1. Контроллер свойств: "/core/model/modx/processors/element/tv/renders/mgr/properties/Vologda-2.php"
  2. TPL(шаблон) контроллера свойств: "/manager/templates/default/element/tv/renders/properties/Vologda-2.tpl"
  3. Контроллер вывода: "core/model/modx/processors/element/tv/renders/web/output/Vologda-2.php"

В принципе можно обойтись одним(поcледним) файлом.

Контроллер свойств

 <?php
 return $modx->smarty->fetch('element/tv/renders/properties/button.tpl');
 

И вот тут ждет нас сюрприз! Оказалось, что свойства вывода можно назначить только на сам TV(не на ресурс). Что в общем-то делает это бессмысленным. Мне лично, в таком случае, намного проще все это сделать в контроллере вывода. Что я и сделаю. А если нужно какие-то свойства делать настраиваемыми в ресурсе, то можно добавить полей в "своем типе ввода", но использовать их при выводе.

Итак в итоге игнорируем контроллер свойств вывода и используем только контроллер вывода.

Контроллер вывода "core/model/modx/processors/element/tv/renders/web/output/Vologda-2.php"

<?php
$data = $modx->fromJSON($value);
$o= '';
$o.="<div class='jumbotron'>";
 $o.="<h3>Женщины! <small>Производится посадка на поезд:</small></h3>";
 $o.="<h4 class='text-warning'>".$data['vol_TuTu']."</h4>";
  
 $o.="<div class='row'>";
    $o.="<div class='col-md-3 text-right btn-success'>Прибыл:</div>";
    $o.="<div class='col-md-9'><span class='badge'>".$data['vol2_in']."</span></div>";
    $o.="<div class='clearfix'> </div>";
    $o.="<div class='col-md-3 text-right btn-info'>Отправление:</div>";
    $o.="<div class='col-md-9'><span class='badge'>".$data['vol2_out']."</span></div>";
 $o.="</div>";
 
 $o.="</div>";//row
$o.="</div>";//jumbotron
return $o;
Ну тут чистый PHP. В переменной $value - наш json(литеральный). Загоняем его в ассоциативный массив $data, а далее уж без проблем.... Тонкость тут одна: надо весь вывод накопить в переменной "$o" и вернуть ее в конце скрипта.

Далее в ресурсе(странице) устанавливаем поля нашего доп поля: и в контент добавляем [[*aset55]].

И при просмотре станицы получаем:

Ну вот и все! Из всего этого я извлек несколько уроков:

  1. Нельзя полагаться полностью даже на официальную документацию(видать пишут ее явно не разработчики)
  2. Не пожалеть времени и иногда побродить по исходникам установленных расширений
  3. Некоторые вещи эфективнее делать на чистом javaScript и PHP (иначе можно заблудиться в каскадах классов....)
  4. Ну и главное стараться понять откуда ноги растут. И для чего... .



продолжение следует.....


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






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