Пишемо свою IDE з вбудованим дизайнером інтерфейсів на PHP і ExtJS

    У статті розглядаються концепти створення IDE і дизайнера інтерфейсів з використанням ExtJS і PHP. З одного боку, створення подібних редакторів досить рідкісна завдання, з іншого — концепти і прийоми можна використовувати для створення різних візуальних конфігуратор.
 
 
Як написати свою IDE з вбудованим дизайнером інтерфейсів, як зробити це швидко і з мінімальними зусиллями? Саме таке питання виникло одного разу в проекті, використовує зв'язку ExtJS і PHP. Гарячі терміни, зростаюча чергу завдань. Список завдань щодня поповнюється величезною кількістю форм введення, таблиць та звітів, все це необхідно обробляти, фільтрувати і відображати для користувача.
 
Проект використовував ORM, що недвозначно натякало на можливість автоматичної генерації схожих інтерфейсів, але, маючи багато подібностей, кожна форма і звіт були унікальними, що вимагало можливості швидкого і легкого конфігурування.
 
Побіжний пошук видав Ext Designer (Sencha Architect) — цікавий і корисний інструмент (Ext MVC ще не існувало). Ext Designer так і не допоміг вирішити поставлені завдання, але про все детальніше.
 
У той же час був помічений проект PHP-Ext — цікава обгортка для ExtJS, написана на PHP. Її можна було б використовувати для генерації цього нескінченного потоку інтерфейсів, але хотілося більшого: cмеси Ext Designer і PHP-обгортки над ExtJS, щоб це ще можна було навчити "підглядати" в базу даних і на основі таблиць будувати форми. А ще краще на основі структури об'єктів ORM, адже там є всі назви полів їх типи та валідатори.
 
Пошук подібного інструменту не увінчався успіхом.
 
 
 
З чого складатиметься IDE:
 1 обгортка для ExtJS, яка "вміє" генерувати код компонент;
 2 файл проекту з простим API додавання елементів обгортки ExtJS;
 3 складальник коду, який має в своєму розпорядженні елементи і кешируєт результат;
 4 дизайнер інтерфейсів для спрощення налаштування проекту, редактор коду;
 5 "плюшки" у вигляді готових компонент для автоматизації рутинних завдань:
 5.1 неочевидні особливості успадкування в ExtJs для початківців;
 5.2 доопределение редакторів;
 5.3 формування url для Ajax-запитів за два кліка;
 5.4 імпорт структури з бази даних, автоматичне створення форм;
 5.5 підключення зовнішніх файлів і проектів;
 5.6 локалізація інтерфейсів;
 5.7 редактори подій і методів;
 6 бекенд дизайнера, який транслює запити фронтенда в команди API роботи з проектом;
 8 генератор проектів.
 
 1 Обгортка
 
Під це завдання доведеться писати власну обгортку для ExtJS, адаптовану під потреби задумки. Оскільки обсяг бібліотеки достатньою великою, заощадимо час відокремивши набори властивостей компонент бібліотеки від опису їх поведінки. Не всі об'єкти ExtJs будуть дзеркально відображені в коді PHP. Опишемо класи обмеженої кількості об'єктів таких, як Grid, Store, Window і т.п. Всі інші об'єкти можуть фізично не існувати в коді, а створюватися на основі набору властивостей описаних в ієрархії.
 
 Таким чином, за пару днів була відтворена ієрархія властивостей компонент ExtJS, в той час ще версії 3 (кілька десятків класів). Далі можна було в міру необхідності описувати поведінку потрібних компонент, основні генерували на рівні json-конфіга.
 
Отримуємо ієрархію наступного виду (шматок діаграми, часи Zend_Framework 1):
 
 
 
Ext_Object і його спадкоємці відповідають за поведінку компонента;
Ext_Config відповідає за зберігання, валідацію і сериализацию властивостей;
Ext_Property і його спадкоємці відповідають за опис характеристик конкретного компонента ExtJS (класи з публічними властивостями, в якійсь мірі повторюють ієрархію бібліотеки ExtJs).
Ext_Virtual — клас. Об'єкти цього класу створюються фабрикою в тому випадку, якщо немає прямого спадкоємця Ext_Object, що описує поведінку компонента. Створюється на основі опису властивостей Ext_Property_xxx. Відрізняється тим, що приймає ім'я класу для емуляції, використовуючи ім'я представляється компонентам бібліотеки.
 
Ext_Object реалізує метод __ toString, який повертає ExtJS код компонента. При необхідності цей метод можна перевизначити в спадкоємців. Якщо об'єкти вкладені одна в одну, в момент приведення до рядка кореневого об'єкта весь ланцюжок з легкістю самораспакуется (перетвориться в рядок Js-коду).
 
 Як показала практика це рішення дозволило уникнути головного болю.
 
 2 Файл проекту
 
Інтерфейс на ExtJS складається з компонент, збудованих в ланцюжок і розташованих в Layout.
 
Необхідно якось зберігати налаштування зв'язків компонент проекту. В якості конфігураційного файлу можна використовувати XML або якийсь інший схожий формат.
У такому випадку щоразу при завантаженні проекту доведеться аналізувати конфігурацію і ініціалізувати об'єкти, що може зайняти тривалий час. Потрібен простий, швидкий і легкий формат.
Що якщо оголосити клас Designer_Project, який би представляв сам проект і мав простенький API по додаванню елементів, а самі елементи зберігав у структурі дерева (всередині лежав би об'єкт працює з деревовидної структурою).
На той момент був вже написаний клас Tree , який досить спритно працював з деревовидними структурами, без праці справлявся з ієрархією до 25 000 — 30 000 вкладених елементів менш ніж за секунду.
 
Об'єкт такого проекту можна серіалізовать і зберегти на диск. Більш того, від серіалізовані рядка можна взяти хеш і пересобрать код інтерфейсу тільки при наявності змін. Повторна ініціалізація інтерфейсу могла б використовувати файловий кеш, що не прогружая проект для валідації.
 
 Основна проблема Ext Designer на той час представлялася в тому, що при кожній зміні коду потрібно пересобрать додаток і публікувати скрипти.
 
Структура Designer_Project (файл проекту):
 - Системний опис контейнерів (які класи можна переміщати, які можуть містити вкладені елементи і т.д);
 - Настройки поточного проекту (назва, неймспейси, підключення файли та інше);
 - API (набір методів для роботи з проектом);
 - Tree (дерево елементів, структура проекту).
 
Примітно, що класи компонент можуть розширюватися, а список властивостей — збільшуватися. Все це в більшості випадків не викликає проблем сумісності зі старим форматом.
 
 Так з'явився файл проекту. Попутно написано кілька допоміжних класів, наприклад адаптер Designer_Storage (раптом ми передумати зберігати проекти у файлах). Проведено кілька тестів на продуктивність, результати були оптимістичними, задумка працювала спритно. Важливо відзначити, що дерево проекту знає лише про структуру вкладеності елементів, але самі об'єкти фактично не перебувають один в одному. Designer / Project.php
 
 3 Сборщик коду
 
Оскільки клас Designer_Project являє собою контейнер з простим API і абсолютно не знає, що робити зі своїм вмістом, буде потрібно допоміжний механізм, який вміє правильно розташовувати код елементів в потрібній послідовності — це клас Designer_Project_Code. Мабуть найскладніший компонент за кількістю різних умов і розгалужень. Отримуючи на вхід об'єкт проекту повинен повернути JS-код для інтерфейсу. Рекурсивно проходячи дерево проекту отримує код компонент і розташовує елементи в потрібній послідовності. Важливо відзначити, що безпосередньо код компонента видає обгортка для ExtJS, сам збирач займається розташуванням цього коду в потрібній послідовності. Він повинен визначити, які компоненти повинні бути оголошені першими, отримати їх код і вставити посилання в залежні компоненти.
 
 Код вийшов досить складний і заплутаний, багато разів рефактору, з появою нових можливостей ставав ще більш заплутаним.
 
 
 
 З часом він придбав терпимий вид і структуру. Designer / Project / Code.php
 
Для об'єктів JS, які є розширеннями базових компонент ExtJS, був використаний трюк для спрощення розташування вкладених елементів. Було створено властивість childObjects, що представляє собою список всіх вкладених елементів, таким чином до них дуже легко звертатися і лінковані в items.

 
Принцип роботи Designer_Project_Code в рекурсивному обході структури проекту, пошуку пов'язаних компонент і правильному розташуванні коду кінцевих елементів відносно один одного.
 
 Пол справи зроблено. Створено спрощений аналог обгортки PHP-EXT, що вміє складатися в проект і працюючий набагато спритніше. Функціонал обмежений, але ніхто не заважає його розвивати.
 
 4 Дизайнер
 
Прийшов час для самого цікавого — створення дизайнера інтерфейсів. Концептуально він повинен являти собою:
 - Панель з тулбаром, в якому розміщувався б список компонент, які можна розмістити в проекті (кнопки, форми, вікна, панелі);
 - Основну форму, яка б відображала результати рендеринга проекту;
 - Ієрархію компонент (використаний TreePanel);
 - Редактор властивостей (Property Grid).
 
На поточний момент дизайнер має наступний вигляд (сильно відрізняється від першого):
 
 
 
1. Панель налаштувань проекту (завантаження, збереження, перемикання режиму дизайнер / редактор коду та інше);
2. Тулбар зі списком компонент, які можна додати в проект;
3. Панель, що відображає структуру проекту, підтримує Drug & Drop переміщення елементів, при виборі елемента для нього подгружается індивідуальна панель налаштування властивостей;
4. Панель налаштування властивостей компонента (містить додаткові панелі редагування подій і методів).
5. Центральна панель (відображає результат рендеринга проекту, на цьому скріншоті — редактор локалізацій). При завантаженні проекту сервер зберігає копію його об'єкта в сесію, всі маніпуляції виробляє з нею. Таким чином, якщо впав інтерфейс можна перезавантажити вікно, зміни не будуть втрачені. Кнопка "Зберегти" скидає проект на диск.
 
Після внесення змін інтерфейс відправляє запит на сервер, потрібний контролер приймає запит, вносить зміни в об'єкт Designer_Project. Після успішного застосування змін дизайнер запрошувати перестроювання JS.
 
Основна панель дизайнера, яка відповідає за розташування елементів у проекті являє собою дерево з підтримкою drug & drop:
 
 
 
Дерево запрошувати список елементів у сервера, той, у свою чергу, витягує структуру з проекту за допомогою API. Під час перетягування відправляється запит на сервер з інструкцією, який компонент переміщаємо. API Designer_Project містить метод переміщення елементів по дереву. При кліці по вузлу дерева (виборі потрібного об'єкта) ініціюється подія itemSelected, відображається панель властивостей цього компонента.
 
 Взаємодія компонент редактора відбувається на рівні подій, окремі частини і компоненти за часту не мають уявлення про те, що знаходиться зовні. У кожного елемента визначено набір генеруються подій, на які зазвичай підписується батьківський компонент.
 
В якості редактора властивостей використаний розширений компонент Property Grid, доповнений методами спілкування з сервером (запрошувати список полів, відправляє зміну властивостей, ініціює події).
 
 У нашому випадку цей компонент називався designer.properties.Panel
 
 
 
Однією панеллю властивостей не обійтися, в деяких випадках потрібно можливість додаткового налаштування властивостей, що представляють собою редактори та вікна. При необхідності розширення списку налаштувань для цього типу об'єктів призначається індивідуальний редактор, отнаследованний від designer.properties.Panel .
 
Початковими можливостями дизайнера не вийшло вирішити всі завдання, тому до проекту був прив'язаний файл "actionJs" (файл з кодом JavaScript, використовується для того, що не можна зробити стандартними засобами, підключається після JS-проекту).
 
В якості редактора коду використовується codemirror.net / .
 
 Перші версії дизайнера могли просто і швидко побудувати інтерфейс, все інше лягало на плечі розробника. У більш пізніх версіях в дизайнера з'явилися події, можливість розширення об'єктів, додавання методів та ін
 
Як відобразити результат? Можна рендерить проекти прямо в DOM відкритої сторінки, це дуже швидко, лаги перестроювання практично непомітні, після перекидання елемента по дереву проходять частки секунди, перш ніж інтерфейс перебудується. У цього рішення є одна серйозна проблема, якщо щось йде не так в розробляється проект (невірно задано властивість чи ще щось), помилки JS викличуть обвалення всього дизайнера. Розроблюваний інтерфейс краще перенести в iframe це хоч і сповільнить відгук, але обвалення коду проекту не призведе до глобального краху. Сам iframe можна покласти в центральну панель, при необхідності запитувати оновлення змісту.
 
 Б ло б красиво, якщо елементи можна було б кидати на форму і рухати / переміщати прямо всередині розроблюваного проекту, як в усіх "дорослих" дизайнерів, але по простому це питання не вирішити. Від цієї затії довелося на якийсь час відмовитися, терміни підганяли.
Пізніше з'явився механізм взаємодії основного інтерфейсу дизайнера і самого створюваного проекту. Можна рухати, розтягувати колонки таблиць, міняти розміри вікон, все це зберігається. Принцип роботи досить простий — в розроблюваний інтерфейс під час режиму розробки додаються обробники подій, які формують команду для дизайнера і допомагають взаємодіяти з контролерами дизайнера, зовнішній інтерфейс дизайнера чекає команди і при її появі реагує. Наприклад, при переміщенні колонок таблиці відправляється запит до serverside API і оповіщається основний інтерфейс дизайнера.

 
 5 Плюшки у вигляді готових компонент, автоматизації рутинних завдань
 

5.1 Неочевидні особливості успадкування в ExtJs для початківців.

 
Перш ніж торкнутися теми доопрацювання компонент, хотілося б звернути увагу на неочевидні для новачка особливості успадкування.
 
 
Ext.define('mypanel',{
    extend:'Ext.Panel',
    someProperty:{
        a:1,b:2,c:3
    },
    someProperty2:[1,2,3]
});

Ext.define('mypanel2',{
    extend:'mypanel'
});
var a = Ext.create('mypanel');
var b = Ext.create('mypanel2');

b.someProperty.a = 100;
b.someProperty2.push(100);

console.log(a.someProperty);
console.log(b.someProperty);
console.log(a.someProperty2);
console.log(b.someProperty2);

 
Object {a = 100, b = 2, c = 3}
Object {a = 100, b = 2, c = 3}
[1, 2, 3, 100]
[1, 2, 3, 100]
 
Ці особливості важливо пам'ятати, тому що це врятує вам багато нервів. Це не єдиний підводний камінь, будьте уважні.
 
 

5.2 Редактори

 
Перевизначити редактори базових властивостей, наприклад, редактор store замінюємо з текстового поля на спадний список всіх створених у проекті сховищ (запитуються у сервера, віддаються Designer_Project API), так само і з іншими подібними властивостями (layout, align і багатьма ін.)
 
 
 

5.3 Формування url для Ajax-запитів за два кліка

 
Url в системі динамічні, наприклад, адреса панелі може змінитися, контролер переключитися. Для цього були придумані токени, що замінюють реальний шлях, оброблювані на етапі складання коду.
 
Для спрощення призначення url-адрес (ajax-запити, proxy url) написаний компонент, який аналізує файлову і кодову структуру. Reflection дозволяє отримувати і аналізувати список доступних дій для контролерів, тепер їх не потрібно писати руками, просто ткнути мишкою.
 
Принцип роботи: сканується файлова система, вибирається потрібний клас, запитується список методів, коментар до методу використовується як опису для інтерфейсу. Потрібний url автоматично прописується у властивості у вигляді шаблону.
 
 
Подібний підхід можна використовувати і для призначення іконок.
 
 
 
 

5.4 Імпорт структури з бази даних, автоматичне створення форм

Як ще полегшити життя програмісту? Звичайно ж імпортом полів у форми, сховища, таблиці виходячи зі структури БД або ORM.
 
Вибираємо таблицю / об'єкт, з якого ми хочемо імпортувати поля. Виходячи з його структури і типів даних система автоматично підставляє нам потрібні значення в осередку і заповнює ієрархію форм.
 
      

 

 
 
Наприклад, для int підставляється Ext.form.field.Number, для varchar — Ext.form.field.Text і т.д.
Попутно додається fieldLabel, якщо це поле форми або формат дати, якщо це поле сховища та ін властивості.
  
Тепер нудна процедура опису елементів займає пару кліків.
 
 

5.5 Підключення зовнішніх файлів і проектів

 
Чому б не дати можливість підключати в проект зовнішні JS-файли та інші проекти. Ми можемо створити проект, в якому опишемо специфічний компонент — редактор, пізніше підключимо його там, де він потрібен. Головне — розділити простору імен проектів так, щоб генератор коду розташовував кожен проект у своєму namespace, це не становить особливої ​​складності.
 
 
 
 Все що було потрібно — додати в Designer_Project api підтримку додавання списку файлів і проектів, призначення неймспейсів. Designer_Project_Code за пару годин був навчений розкладати код всередину неймспейсів і рекурсивно рендерить проекти.
 
Поступово дизайнер почав поповнюватися готовими компонентами такими, як вікна редагування, фільтри, поля для посилань на об'єкти, списків об'єктів і багато іншого.
 
 

5.6 Локалізація інтерфейсів

 
Довгий час залишалася не вирішеною завдання локалізації інтерфейсу. Не було ясно, як підступитися до її вирішення. Весь проект будувався на простих рішеннях, не хотілося городити город, та й часу на це особливо не було. В один момент воно знайшлося.
Сама платформа використовувала файли локалізації, які Перегенерувати при зміні в інтерфейсі управління локалізаціями. У підсумку на сторінці існував якийсь JS-об'єкт види:

appLang = {
   yes:”Да”,
   no:”Нет”,
   cancel: ‘Отмена’,
     ...
};

Ідея проста — дозволити розробнику вводити в якості значень строкових властивостей Js-код, для цього був вигаданий токен "[js:]" (під час генерації коду властивості з таким токеном оформлялися як js-код).

Було:

Стало можливим:

З урахуванням того, що інтерфейс управління локалізаціями був уже готовий, працювати з ним було одне задоволення. Реалізація рішення зайняла від сили 20 хвилин. Тепер можна локалізувати проект не змінюючи вміст файлу.

5.7 Редактори подій і методів


Наступним проривом стало додавання можливості розширення об'єктів, тепер можна додавати методи і події. Реалізація подій дуже схожа на реалізацію властивостей. Схожим чином були реалізовані і методи. У цей момент в системі з'явився поділ на стандартні події, описані в обгортці Ext, і події, створені користувачем (методи могли бути створені тільки користувачем для «розширених» (атрибут isExtended) об'єктів).






Дизайнер почав генерувати хвилі зрозумілий, читаний код.

Після цього з'являється очевидна проблема: події, методи і реакції розкидані по елементах, складно знайти де їх редагувати. Додамо в дизайнер окремі вкладки зі списками подій і методів, згруповані по об'єктах. Запитуємо у Designer_Project список об'єктів, відображаємо у вигляді Grid з угрупованням.




6 бекенд

C точки зору бекенд все досить просто, можна використовувати будь фреймворк. Потрібен набір контролерів і дій для маніпуляцій з проектом. Запускаємо програму, завантажуємо проект дизайнера, описуємо список методів, які звертаються до API Designer_Project і виконують різні маніпуляції з проектом. З особливостей — знадобиться контролер, який може вивести зібраний проект в інтерфейс і підключити потрібні JS-файли.

У нашому випадку на додаток був створений action для збірки і мініфікаціі вихідного коду самого дизайнера, це дозволило прискорити завантаження редактора.

7 Генератор проектів

Генератор являє собою шаблон з набором дій над файлом проекту.

У спрощеному вигляді:

// создать новый проект
$project = new Designer_Project();
// создать объект обертки Ext_Panel реализующий обертку для Ext.panel.Panel
$panel = Ext_Factory::object('Panel',array(
           'width'=>100,
           'height'=>200,
           'title'=>'My Panel',
           'layout'=>'fit'
));
// назначить уникальный идентификатор
$panel->setName('myPanel');
// добавили панель в проект
$project->addObject(0, $panel);
// инициализировать хранилище
$designerStorage = Designer_Factory::getStorage($config);
// сохранить проект
$designerStorage->save($projectFile , $project);


Маючи подібну структуру проекту написати генератор проектів на основі ORM не склало праці. За день було створено кілька шаблонів генерації стандартних інтерфейсів, тепер на створення типового інтерфейсу йшло пара кліків, решту часу витрачалося на нестандартне оформлення і доопрацювання.

На реалізацію першої версії дизайнера пішло 3 тижні відпустки — досить короткий термін для такої глобальної мети.




Отриманий профіт:
— Стандартні інтерфейси генерувалися одним кліком, далі доопрацьовувалися в дизайнера;
— Значно зменшилася кількість помилок в JS-коді;
— Молоді розробники легше вникали в ExtJS і розробку складного проекту;
— Допрацьовувати інтерфейс стало набагато простіше і цікавіше, елементи пересувалися одним рухом миші, без боязні забути захопити шматок коду і упустити зв'язаність з іншим елементом;
— Зросла швидкість розробки продукту і прототипів;
— Такі речі, як поміняти назву кнопки або колонки, перестали викликати батхерт під час пошуку шматка коду з ініціалізацією потрібного елемента;
— Отриманий цікавий досвід у розробці власного середовища розробки «на коліні».
— Розробка на PHP набула абсолютно нову зручну форму;
— Вдалося дізнатися багато тонкощів ExtJS, c якими не доводилося стикатися до цього моменту.

Навіщо це було все потрібно?

За роки роботи з ExtJS набридло писати одне і те ж. Ініціалізувати компоненти, налаштовувати їх, боячись пропустити кому або щось слінковать. Це завдання, як виявилося, досить легко автоматизується, в час, що звільнився можна зайнятися більш цікавими речами.

Оскільки інструмент виявився дуже корисним, з'явилося бажання поділитися ним з спільнотою, внести свій внесок у OpenSource. Дизайнер і кілька інших напрацювань були перероблені, зібрані в одну платформу DVelum, розробка якої ведеться вже кілька років. З результатами можна ознайомитися на офіційному сайті dvelum.net



Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.