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

Данный комбинированный пример познакомит вас с тонкостями работы в DVelum designer, приоткроет структуры кода проекта, поможет разобраться с кастомизацией системных компонент и редактором ссылки на список объектов (самый частый вопрос на форуме).

Рассмотрим следующую задачу:

  • есть клиент, который несколько раз посещает некую компанию;
  • в ходе визита он оставляет важную информацию для обработки оператором (в нашем случае это будет просто текст);
  • обработка данных посещения назначается одному из администраторов системы.

Необходимо разработать интерфейс таким образом, чтобы из интерфейса управления для клиента сразу же можно было создавать и прикреплять посещение. Объект Client (Клиент) будет содержать ссылку на список объектов Visit (Посещение).

Пример слегка надуманный, тем не менее, интересный в плане изучения особенностей платформы.

Интерфейс, который мы попытаемся создать, будет специфическим, генератор кода не описывает такое поведение.

Есть два варианта решения:

  • создать интерфейс вручную, описав его в дизайнере и создав контроллер;
  • доработать код, который сгенерирует кодогенератор.

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

Начнем с разработки структуры данных и создадим два объекта ORM:

Посещение (Visit):

Объект включает поля:

  • date - дата посещения (datetime);
  • info - текст обращения (text);
  • operator - оператор, которому назначена обработка обращения (link - ссылка на объект User).

Клиент (Client):

Объект включает поля:

  • adress - адрес (text);
  • name - имя (varchar 255);
  • phone - телефон (varchar 255);
  • visits - список посещений (ссылка на список объектов Visit).

Перейдем в интерфейс управления административными модулями и сгенерируем интерфейсы управления для обоих объектов. Как это делается, разбирать подробно не будем, эта процедура описана в скринкасте и других рецептах.

После создания модулей  имеется два рабочих интерфейса для управления данными посещений и клиентов. Теперь необходимо, чтобы в интерфейсе редактирования клиента на вкладке Visits вместо выбора посещения из списка открывалось окно создания нового посещения, после сохранения оно должно добавляться в список клиенту.

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

  • appRun - для обычных элементов интерфейса (например, grid , store , panel);
  • appClasses - для наследников компонент (например, EditWindow).

Таким образом, в appRun лежат уже инициализированные виджеты, а в appClasses - описания объектов, которые можно создавать методом Ext.create().

Сам проект интерфейса разбивается на две части:

  • визуальное представление (иерархия элементов интерфейса) - эта часть проекта является динамической, ее код перегенирируется каждый раз после изменения структуры проекта автоматически и пишется в отдельный кэш-файл;
  • бизнес-логика интерфейса (код взаимодействия компонент, который находится в редакторе и отвечает за поведение объектов) - этот код хранится в отдельном файле в папке js/app/actions/[projectname].js. Его можно редактировать как при помощи встроенного редактора кода, так и при помощи любого стороннего редактора. Если вы будете изменять код в стороннем редакторе, не забудьте обновить окно дизайнера интерфейса, так как на данном этапе сторонние изменения кода не отслеживаются.

 

Таким образом, мы имеем два проекта интерфейсов в одинаковом пространстве имен (автоматически сгенерированном). Нам необходимо подключить проект редактора “Посещение” в проект редактора “Клиент” для того, чтобы мы могли воспользоваться уже готовым решением, которое создала система.

Для корректной совместной работы проектов нам необходимо изменить пространство имен одного из них (чтобы они не пересекались). Перейдем в дизайнер интерфейсов и откроем проект “Посещение”.

1. Спрячем History Panel, отображающуюся в окне редактирования, для этого в свойствах editWindow укажем hideEastPanel: true;

2. Закомментируем код в редакторе интерфейсов (это не обязательно, но позволит избежать  js-ошибки при смене пространства имен);

3. Откроем настройки проекта и заменим пространства имен:

  • appClassess на visitClasses;
  • appRun на visitRun.

4. В редакторе кода переименуем функцию showEditVindow в showVisitWindow, найдем вызов этой функции далее по коду и так же переименуем.

Заменим в коде пространства имен: там где было appRun установим visitRun, мы изменили пространство имен проекта, необходимо перевести код на использование этого пространства.

Раскомментируем код (если это было сделано на шаге 3).

Ctrl + s - горячие клавиши сохранения изменений в редакторе кода (необходим фокус на редакторе).

 

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

На этом адаптация интерфейса “Посещение” завершена. В следующий раз подобная операция займет у вас не более пары минут.

Кастомизация интерфейса “Клиент”.

Первым делом прячем панель истории editWindow->hideEastPanel:true.

Теперь подключаем проект интерфейса редактирования “Посещения”.

Открываем настройки проекта, нажимаем кнопку Add Project File и находим visit.designer.dat.

После этой операции в интерфейсе редактирования клиента стал доступен редактор посещения, как будто он был определен в этом проекте. Пространство имен редактора не пересекается, поэтому элементы “Посещения” могут быть вызваны и использованы в этом проекте. Однако, элементы “Посещения” никак не отображаются в интерфейсе, но мы знаем, что они есть.

Вкладка Visits окна редактирования клиента отображает системную панель типа app.objectLink.Panel.

Системные компоненты представляют собой “монолитные” структуры (их элементы не раскладываются в дереве проекта).

 

Это может озадачить нового пользователя. Доопределение логики системных компонент далеко не лучший подход, так как теряется удобство редактирования элементов компонента в визуальном интерфейсе, тем не менее, это может понадобиться.

Используем исследовательский подход.

1. Откроем просмотр кода для editWindow (кнопка [JS]):

Бегло изучив состав кода, откроем для себя особенность структуры компонент дизайнера.

Объекты описанные в appClasses содержат объект-контейнер me.childObjects с ссылками на вложенные элементы, что позволяет с легкостью к ним обращаться.

 

Таким образом, при помощи конструкции:

var win = Ext.create("appClasses.editWindow", {});
var relatedVisits = win.childObjects.editWindow_visits;

мы можем получить ссылку на системный объект, являющийся редактором связанных “Посещений”.

Так же становится понятным, что редактор представляет собой панель типа app.objectLink.Panel, которая получает некие настройки.

Найдем файл ObjectLink.js и посмотрим реализацию этой панели:

Это небольшая надстройка над компонентом app.relatedGridPanel, которая добавляет обработчик клика “Добавить элемент” и открывает окно редактора.

Эта функциональность нам не нужна, желательно ее убрать. Из кода видно, что описание этой кнопки отсутствует в app.objectLink.Panel. Значит, она находится на уровень выше по абстракции, в самом компоненте app.relatedGridPanel.

Открываем исходный код компонента app.relatedGridPanel:

Компонент app.relatedGridPanel отнаследован от Ext.Panel и содержит toptoolar с кнопкой добавления элемента, которая инициирует событие addItemCall.

Мы заменим эту кнопку своей, открывающей редактор “Посещения”.

Осталось понять, как получить ссылку на тулбар панели. Открываем документацию ExtJS и видим, что у Ext.Panel есть метод getDockedItems, который возвращает массив компонент тулбара.

На данный момент мы имеем компонент редактора отнаследованный от app.relatedGridPanel, которая представляет собой панель с тулбаром, хранилищем и гридом.

Нам нужно:

  • переопределить кнопку тулбара, по клику вызвать редактор объекта “Посещение”;
  • после изменения записи посещения добавить запись в таблицу связанных элементов (relatedGridPanel) путем простой вставки record в хранилище.

Описание структуры record для хранилища связанных элементов находим в самом начале файла с кодом app.relatedGridPanel:

Структура достаточна проста:

  • id - идентификатор записи;
  • title - заголовок;
  • deleted - флаг удаления (в нашем случае будет false, так как запись не удалена);
  • published - флаг публикации (в нашем случае true, так как запись считается опубликованной).

Осталось отредактировать код поведения интерфейса “Клиент”.

Кастомизируем интерфейс управления клиентами

Открываем редактор кода и вносим изменения в код функции showEditWindow (эта функция отвечает за отображения окна редактора).

function showEditWindow(id){
    /**
      * Создаем объект окна редактора "Посетителя"
      * переменная  win  хранит ссылку на объект
      */
    var win = Ext.create("appClasses.editWindow", {
        dataItemId:id,
        canDelete:canDelete,
        canEdit:canEdit,
        listeners:{
           dataSaved:{
              fn:function(){appRun.dataStore.load();},
              scope:this
	    }
	}
    });
    /**
      * Получаем ссылку на редактор связанных "Посещений"
      */
    var relatedVisits = win.childObjects.editWindow_visits;
    /**
      * Получаем ссылку на  top toolbar, он один, так что будет первым
      * в массиве компонент, которые возвращает метод panel.getDockedItems()
      */
    var docked = relatedVisits.getDockedItems('toolbar[dock="top"]')[0];
    /**
      * Очищаем тулбар (удаляем кнопку выбора связанного элемента)
      */
    docked.removeAll();
    /**
      * Помещаем в тулбар свою кнопку, которая по нажатию
      * откроет редактор “Посещения” из вложенного подпроекта
      * редактора интерфейсов (мы подключили его в начале рецепта)
      */
    docked.add({
        text:'Create Item',
        handler:function(){
            /**
              * Проект редактора посещений включен в текущий
              * так что мы можем инициализировать его компонент
              * используя соответствующее пространство имен 
              */
            var visitWin = Ext.create("visitClasses.editWindow",{
                // id новой записи 0, что значит, не определено
                dataItemId:0,
                // ничего не остается, как использовать текущие
                // права доступа
                canDelete:canDelete,
                canEdit:canEdit
            });
            /**
              * Добавляем обработчик события  dataSaved 
              * редактора "Посещения"
              */
            visitWin.on('dataSaved' , function(){
                // получаем ссылку на форму с данными
                var visitForm = visitWin.editForm.getForm();
                // получаем значение заголовка объекта
                // в нашем случае заголовок это дата визита
                var visitDate = visitForm.findField('date').getValue();
                // создаем запись, которая будет вставлена в редактор
                // связанных посещений
                var record = Ext.create('app.relatedGridModel', {
                    // из формы узнаем id  нового объекта
                    id: visitForm.findField('id').getValue(),
                    // форматируем дату в нужный нам формат
                    title:Ext.Date.format(visitDate, 'd.m.Y H:i'),
                    deleted:0,
                    published:1
                });
                // вставляем запись в хранилище 
                // редактора связанных посещений
                relatedVisits.getStore().insert(0 , record);
                // закрываем редактор "посещения"
                visitWin.hide();
            });
            // Показываем окно редактора "Посещения"
            visitWin.show();
        }
    });
    // Показываем окно редактора "Клиента"
    win.show();
}

Код оформлен линейно для простоты восприятия.

Интерфейс готов для использования.

Приведенный пример является скорее демонстрацией возможностей и особенностей работы дизайнера интерфейсов.

Системные компоненты разработаны для быстрой генерации простых интерфейсов редактирования данных. Если логика вашего приложения больше чем таблица и форма, стоит задуматься о разработке интерфейса без автогенерации.

Мы рекомендуем избегать переопределения поведение системных объектов. Вы теряете гибкость настройки интерфейса. Хотя для экономии времени, например при подготовке прототипа, такое вполне допустимо.

Есть вероятность того, что дальше вам понадобится добавить в системный компонент еще что-то. Немного легче работать с интерфейсом, сделанным самостоятельно в редакторе, его проще настраивать все вложенные компоненты отображаются в дереве, работа более прозрачна и нет необходимости копаться в исходниках.