Наверх

Как работают дополнения MODX. Часть 2 — ExtJS Panel

Для того, чтобы добавить свои стили и скрипты на страницу дополнения, в классе modExtraManagerController есть метод loadCustomCssJs. Давайте, воспользуемся им, чтобы запустить наш первый ExtJS-скрипт.
<?php
class ThingsIndexManagerController extends modExtraManagerController {
    public function getPageTitle() {
        return 'Things';
    }
    public function getTemplateFile() {
        return dirname(__FILE__) . '/home.tpl';
    }
    public function loadCustomCssJs() {
        $this->addHtml("<script>
            Ext.onReady(function() {
                var title = 'Мой заголовок';
                var msg = 'Модель DOM готова...';
                Ext.MessageBox.alert(title,msg);
            });
        </script>");
    }
}

Пример кода я взял из статьи, найденной в Яндексе по запросу «Уроки ExtJS». Единственное — нам не надо подключать скрипты и стили самого ExtJS, так как за нас это уже сделал MODX.

Писать JavaScript внутри PHP — то ещё извращение, поэтому JS-скрипты лучше выносить в отдельные файлы и подключать с помощью addJavascript:
<?php
class ThingsIndexManagerController extends modExtraManagerController {
    public function getPageTitle() {
        return 'Things';
    }
    public function getTemplateFile() {
        return dirname(__FILE__) . '/home.tpl';
    }
    public function loadCustomCssJs() {
        $this->addJavascript('/things/home.js');
    }
}

Чтобы каждый раз не очищать кеш браузера, можно указать дополнительный параметр у файла. Не забудьте потом удалить его, когда закончите разработку:
$this->addJavascript('/things/home.js?time=' .time());

В ExtJS принят объектно-ориентированный подход. В JavaScript как такового ООП нет и каждый фреймворк реализует его по-своему. Если объяснить простыми словами, то всё в ExtJS является объектами: дерево ресурсов, панель редактирования ресурса, панель системных настроек. Даже любое поле или чекбокс — это объект. Каждый объект может находиться внутри другого объекта — для реализации вложенности у объектов ExtJS есть свойство items.

ExtJS items

Если у объекта есть родительский элемент, то объект будет размещён (или отрендерен) внутри этого родительского элемента. Но вы можете изменить это поведение, указав свойство renderTo — оно указывает, где отобразить конкретный объект. Обычно свойство renderTo указывается только у тех объектов, у которых нет родителя.

И ещё одно важное свойство — это xtype.

ExtJS xtype

В этом свойстве указывается тип объекта. По аналогии можно сказать, что xtype — это класс объекта. В зависимости от xtype у объекта могут быть дополнительные свойства, методы, может быть прописано какое-то поведение и даже набор дочерних объектов.

Вся работа с ExtJS обычно построена следующим образом:

  1. Ищем наиболее подходящий для задачи xtype. Для поиска можно воспользоваться документацией. Она на английском, но по названию объектов можно хотя бы сориентироваться. Например, если нам нужно отобразить какое-то окошко, идём в раздел Ext.Window и видим, что нужный нам xtype — это window: https://ilyaut.ru/cloud/1WpUHf.png

  2. Решаем, внутри какого объекта отобразить наш новый объект, вставляем его внутрь соответствующего свойства items

  3. Переопределяем те свойства и методы объекта, в которых мы хотим изменить поведение.

Давайте, уберём из контроллера метод getTemplateFile и отобразим заголовок с помощью ExtJS. Файл home.tpl тоже можно удалить, он нам больше не понадобится — всю HTML-структуру будет создавать ExtJS.

MODX создаёт DIV с id='modx-panel-holder' на странице компонента. Вот в нём мы и будем создавать всю внутреннюю структуру. Добавить объект в этот div можно с помощью команды MODx.add(). Загрузку нашего компонента будем производить по окончании загрузки страницы. Воспользуемся аналогом записи $(document).ready():
Ext.onReady(function() { // Когда страница загрузилась
    MODx.add({ // Вставляем на страницу объект
        xtype: 'panel', // Тип объекта - Панель
        items: [{ // Внутри панели будет другой объект
            html: '<h2>Things</h2>', // который представляет из себя HTML-блок
        } ]
    });
});

Обновляем страницу компонента и видим, что ничего не изменилось)) Но это на первый взгляд. Посмотрите в инспекторе кода — ExtJS вставил заголовок внутрь дива modx-panel-holder, но не напрямую, а сгенерировал целый набор вложенных дивов. Так он работает.

Преимущество ExtJS в том, что мы можем избавиться от дублирования кода и, в то же время, использовать его повторно, если нам это нужно. Для этого определим новый ExtJS-компонент — тогда мы сможем обращаться к нему из любого места и даже переопределять некоторые его свойства:
Things = function (config) { // Создаём объект Things
    config = config || {}; // Определяем настройки объекта
    Ext.applyIf(config, { // Указываем состав объекта
        components: [{
            xtype: 'panel',
            items: [{
                html: '<h2>Things</h2>',
            } ]
        } ]
    });
    Things.superclass.constructor.call(this, config); // Магия =)
};
Ext.extend(Things, MODx.Component); // Расширяем объект MODx.Component нашим объектом
Ext.reg('things', Things); // Регистрируем xtype для нашего объекта

Ext.onReady(function() {
    MODx.add({
        xtype: 'things', // Командуем загрузить наш новый объект
    });
});

Как же нам теперь переопределить свойства нашего объекта? Нужно всего лишь передать их внутри config. У нашего объекта есть только свойство components. Давайте его и переопределим, ради эксперимента:
// ...
Ext.onReady(function() {
    MODx.add({
        xtype: 'things',
        components: [{ // Указываем новый состав объекта
            xtype: 'panel',
            items: [{
                html: '<h2>Things override</h2>',
            } ]
        } ]
    });
});

Компонент — это, обычно, просто контейнер, который содержит в себе объекты, которые мы собираемся использовать. Поэтому часто можно увидеть, что сам компонент не содержит элементов. А уже его дочерние объекты являются разными сущностями — страницы, панели, таблицы, окна и прочее.

Для удобства доступа к тем или иным дочерним объектам, разделим их на группы:
// Создаём компонент - главный объект
var Things = function (config) {
    config = config || {};
    Things.superclass.constructor.call(this, config);
};
Ext.extend(Things, MODx.Component, { // Перечисляем группы, внутрь которых будем "складывать" объекты
    panel: {}, page: {}, window: {}, grid: {}, tree: {}, combo: {}, config: {}, view: {}, utils: {}
});
Ext.reg('things', Things);
// Мы не будем вставлять компонент на страницу с помощью MODx.add,
// поэтому нужно создать экземпляр нашего класса
// чтобы можно было обращатсья к его свойствам
Things = new Things();

// Создаём внутри компонента главную панель
// (через точку в JS обозначется вложенность массива, то есть мы создаём
// объект Home внутри panel, который, в свою очередь, находится в Things)
Things.panel.Home = function (config) {
    config = config || {};
    Ext.apply(config, {
        // xtype можно не указывать, так как внутри MODx.Panel xtype уже указан
        // А вот если вы захотите xtype изменить, его можно указать здесь
        items: [{
            html: '<h2>Things</h2>'
        } ]
    });
    Things.panel.Home.superclass.constructor.call(this, config); // Опять магия
};
Ext.extend(Things.panel.Home, MODx.Panel); // Наша панель расширяет объект MODX.Panel
Ext.reg('things-panel-home', Things.panel.Home); // Регистрируем новый xtype для панели

Ext.onReady(function() {
    MODx.add({ // На странице выводим сразу нашу панель
        xtype: 'things-panel-home'
    });
});

ExtJS TabPanel

Немного украсим нашу страничку и, заодно, посмотрим, как добавлять на неё элементы. Для начала добавим панели класс container, чтобы появились отступы от других элементов админки. А так же добавим блок с табами и описанием. У объекта табов xtype — tabpanel, но в MODX есть свой объект табов, который расширяет Ext.TabPanel. Этот объект добавляет CSS-классы, чтобы табы смотрелись более органично внутри админки. Кроме того, табы MODX могут быть ещё и вертикальными. Поэтому мы будем использовать более «навороченные» MODX-табы:
// ...
Things.panel.Home = function (config) {
    config = config || {};
    Ext.apply(config, {
        cls: 'container', // Добавляем отступы
        items: [{
            html: '<h2>Things</h2>'
        }, {
            xtype: 'modx-tabs', // Добавляем объект табов в нашу панель
            items: [{ // Перечисляем, какие именно табы нам нужны
                title: 'Things 1', // Заголовок первого таба
                items: [{ // А внутри таба HTML-блок с классом panel-desc
                    html: 'Things 1 description', 
                    cls: 'panel-desc',
                } ]
            }, {
                title: 'Things 2', // Заголовок второго таба
                items: [{ // Внутри таба ещё один HTML-блок с классом panel-desc
                    html: 'Things 2 description', 
                    cls: 'panel-desc',
                } ]
            } ]
        } ]
    });
    Things.panel.Home.superclass.constructor.call(this, config); // Чёртова магия =)
};
Ext.extend(Things.panel.Home, MODx.Panel);
Ext.reg('things-panel-home', Things.panel.Home);
// ...

Как вы поняли, кроме документации по ExtJS, нужно смотреть ещё и в исходный код MODX. В основном, все стандартные ExtJS-объекты в MODX лежат в папке /manager/assets/modext/widgets/

Примеры кода у нас начали разбухать, поэтому я буду пропускать некоторые строки, будьте внимательны, чтобы не затереть нужное.

В следующей статье «оживим» наш интерфейс — рассмотрим отправку запросов и обработку ответа.


8 комментариев

  1. Кирилл Киселев 27 апреля 2019, 12:03 # 0
    А почему объект «Home» c заглавной буквы? Things.panel.Home
    1. Анатолий 04 марта 2020, 12:48 # 0
      Илья.
      Работа проделана колоссальная. Спасибо Вам за нее.
      Но повторить ваш урок успешно не получается.
      Если бы вы
      Выложили файлики
      home.js и index.class.php
      для скачивания.
      Было бы очень окей.
      Где-то закралась ошибочка, и не получается это урок
      1. Илья Уткин 10 марта 2020, 09:51 # 0
        У меня этих файлов нет, всё что есть — в этих уроках)
      2. Станислав Рогозин 29 марта 2021, 15:07(Комментарий был изменён) # 0
        Добрый день Илья и кто читает этот комментарий. Еще раз — спасибо вам и за эти статьи, и за все дополнения, подсказки, материалы по modx. Честно говоря стыдоба спрашивать, но уже вторую ночь застрял на одном моменте, из-за которого прошел все стадии гнева, депрессии и запоя. Суть в чем, решил делать компонент страничку в админке с нуля. Тут надо отдать должное, вы все хорошо расписали и все прошло как по маслу. Но я пытаюсь сделать, чтобы весь Ext.JS загружался в div, указанный в файле .tpl и в теории (а так же подобию других дополнений) это не должно было быть сложным. Я создал div с id=«test»
        Далее в файле c extjs
        Thing.page.Home = function(config) {
            config = config || {};
            Ext.applyIf(config, {
                components: [{
                    xtype: 'thing-panel-home',
                    renderTo: 'test'
                ]},
            });
            Thing.page.Home.superclass.constructor.call(this, config);
        };
        
        Ext.extend(Thing.page.Home, MODx.Component);
        Ext.reg('thing-page-list', Thing.page.Home);
        thing-panel-home — зарегистрированная панелька рабочая.
        ну и в контроллере
        $this->addHtml('<script type="text/javascript">
                            Ext.onReady(function() {
                             MODx.load({ xtype: 'thing-page-list'});
                             });</script>');
        Но, вместо того, чтобы подгружаться в div все добавляется после него. Я пришел к выводу, что extjs грузится раньше, чем div и поэтому не находит его и просто загружает. Хотя, по моей теории, должна быть ошибка.
        Я разделил все на 3 js файла, вначале подгружаю тот, где
        var Thing= function (config) {
            config = config || {};
            Thing.superclass.constructor.call(this, config);
        };
        Ext.extend(Thing, Ext.Component, { 
        	page: {},
        	window: {},
        	grid: {},
        	tree: {},
        	panel: {},
        	combo: {},
        	config: {}
        });
        Ext.reg('Thing', Thing);
        Thing= new Thing();
        
        и сама панелька
        Thing.panel.Home = function (config) {
        	config = config || {};
        	Ext.apply(config, {
         baseCls: 'modx-formpanel', //пробовал это
         cls: 'container',
        		items: [
        			{
        				html: 'Проверка'
        			},
        			{
        				xtype: 'panel',
        				cls: 'container',
        				items: [{
        					html: 'Тестовая панелька',
        					cls: 'panel-desc',
        				]}
        			}
        		]
        	});
        	Thing.panel.Home.superclass.constructor.call(this, config); 
        };
        Ext.extend(Thing.panel.Home, MODx.Panel); 
        Ext.reg('thing-panel-home', Thing.panel.Home); 
        
        Потом гриды и файл инициализации ранее. tpl вообще загружаю в конце контроллера. И я не знаю что делать. что уже только не перебирал. В див не грузится, только после него. Все работает, все функционирует. Я даже панель упрастил. Классы переименовывал. Будто что-то надо добавить, но что — не понимаю. Кто-нибудь, может подсказать, что случилось.
        Вот контроллер мой
        class StmdbWelcomeManagerController extends modExtraManagerController {
           public function getPageTitle() {
               return 'Тестовая страница';
           }
        
               function loadCustomCssJs()
                {
        
                    $assets = $this->modx->getOption('assets_url');
                    $this->addJavascript('/assets/components/thing/js/conf.js?time=' . time());
                    $this->addJavascript('/assets/components/thing/js/index.js?time=' . time());
                    $this->addJavascript('/assets/components/thing/js/thing.grid.js?time=' . time());
        
                    $this->addHtml('<script type="text/javascript">
          Thing.config.connector_url = "' . $assets . 'components/thing/connector.php";
                            Ext.onReady(function() {
                             MODx.load({ xtype: \'films-page-list\'});
                             });
                                              </script>');
               }
            public function getTemplateFile() {
                return 'welcome.tpl';
            }
        }
        
        1. Илья Уткин 30 марта 2021, 13:42 # 0
          Привет! Во-первых, нужно открыть консоль браузера и посмотреть, нет ли каких ошибок. Во-вторых, проверить все наименования xtype — я вижу films-page-list и thing-panel-home — какой из них правильный?
          1. Станислав Рогозин 31 марта 2021, 01:18(Комментарий был изменён) # 0
            Сорри, это я уже в процессе переделывания прислал. thing-panel-home, как и описывалось в Thing.page.Home. В консоли — никаких ошибок. Собственно, если в load был неправильный тип, то просто бы не грузилось ничего. А так все работает, но не в слое с необходимым id, а ниже. Я уже думаю, может быть это уже не работает в новой версии modx? я посмотрел на другом проекте, где движок modx и где установлено много известных расширений, там тоже дополнения не рендерятся в нужный слой, хотя они описаны также. Например, bannerY. слой
            <div id="bannery-panel-home-div"></div>
            — пустой. Или versionX, слой
            <div id="versionx"></div>
            — пустой. Справедливости отмечу, что вы renderTo нигде не описываете, можно сказать — импровизация для tpl. У вас есть какой-то свой пример с renderTo, может правда это уже нигде не работает?
            Почему это очень удобно (если вдруг кто из новичков прочтет), иначе tpl файл будет выглядеть как position:absolute, а вся панелька будет «скроллиться» под него. Тут или полностью отказаться от шаблона и просто делать слои в extjs или еще потратить бессонные ночи :-) Я уже думал делал класс в классе, точнее в этом контроллере все рендерить, а потом require_once файл с вызовом панели.

            Благодаря вашим урокам, я, кстати, еще в одни дебри полез :-) но на это можете не отвечать. получаю в handler, через get запросом данные и с помощью Ext.getCmp('id_поля').setValue(); присваиваю дальше. Одно из таких значений — путь к картинке. Что тоже работает и хорошо, т.е. кнопка отработала нажатие в texfield появился адрес к jpg. И вот решаю теперь задачу по кнопке preview рядом с этим полем, открыть условный window.PrevImg, который отрендерит картинку. Ну а на полном серьезе, вы рассмотрели здесь основные вещи при создании CMP, без различных вспомогательных дополнений, что катализирует на творчество и подтягивание знаний. А уж ExtJs — это просто жесть. Даже их страница docs по версии 3.4.0 — дремучая вещь.
            1. Станислав Рогозин 31 марта 2021, 02:52(Комментарий был изменён) # 0
              Сорри, что я еще раз. То, что писал курсивом реализовал так
              {xtype: 'button', text: 'Просмотр',
              handler: function() {
              var imgsSrc = Ext.getCmp('poster').getValue();
              console.log(imgsSrc);
              MODx.load({
              xtype: 'prev-images',
              items: [{html:'<img src="' + imgsSrc + '">']},
              }).show();
              }
              Thing.window.PrevImg = function (config) {
                  config = config || {};
                  Ext.applyIf(config, {
                      cls: 'container',
                      width: 300,
                      height: 400,
                      activeItem: 0,
                   });
              Thing.window.PrevImg.superclass.constructor.call(this, config);
              };
              можно было и без переменной обойтись.

              прошу прощения, что к теме renderTo вообще не относится, не мог сам себе не ответить, на тот курсив, чтобы не отвлекать на лишнее.
        2. Илья 23 августа 2022, 14:42 # 0
          Илья, здравствуйте!
          Использую modx3 и заготовку modExtra3.
          Что хочу сделать:
          1. Отображать список «предметов» на странице компонента, (тут все ок из коробки), запретил только добавление, поскольку надо связывать «предмет» с ресурсом.
          2. На вкладке ресурса добавлять «предметы» и сохранять их с указанием id ресурса в бд.
          Проблема — не получается добавить вкладку компонента на страницу редактирования ресурса, можете показать как это правильно сделать в моем случае?

          Авторизация

          через сервис Loginza:


          Шаблоны MODX

          1 2 Дальше »

          Объектная
          модель
          MODX