Два frontend фреймворка. Два подхода
# Содержание:
1. [Эволюция Веб приложений в двух словах](#1)
2. [Два подхода](#2)
3. [Разработка простой компоненты](#3)
4. [Разработка компоненты посложнее](#4)
5. [Повторное использование](#5)
6. [Данные и методы](#6)
7. [CSS и стилевое оформление](#7)
8. [Обработка событий](#8)
9. [Работа с формами](#9)
10. [Отладка](#10)
В статье сравниваются два подхода к созданию веб интерфейса пользователя. Один подход — это современные фреймворки с компонентным подходом, который инкапсулирует HTML, js, css. Второй подход к разработке веб-интерфейса аналогичен разработке интерфейса пользователя десктопного приложения.
### 1. Эволюция Веб приложений
#### 1.1. HTML
Сначала нам было достаточно статических страниц, написанных с помощью языка разметки html.
```html
html — это просто.
```
#### 1.2. HTML + JAVASCRIPT
После, к статичным страницам html добавили javascript.
```html
Заголовок
var h1 = document.querySelector('#app > h1');
h1.innerText = 'html + js - это гибко.'
```
#### 1.3. JAVASCRIPT FRAMEWORK
Потом, объединили html и javascript и появились frontend framework. Вот, на примере одного из популярных фреймворков Vue.js.
```html
Фреймворк {{framework}} один из лучших.
const app = new Vue({
el: '#app',
data: {
framework: 'Vue.js'
}
})
```
Удобство современных фреймворков — это реактивность, т.е. связанность данных, когда данные на странице
```html
Фреймворк {{framework}} один из лучших.
```
связаны с данными компонента
```javascript
app.framework = 'Vue js';
```
### 2. Два подхода
Несмотря на развитие технологий, все равно, на первом месте идет язык разметки — html, а javascript так и остался на подхвате, ведь так изначально применен подход в веб сайтам. Такой подход несомненно очень гибкий, и позволяет создавать немыслимые веб интерфейсы как по красоте, так и по функциональности, но для этого frontend framework предлагает свой, так называемый, декларативный язык. Представляете, вы пишите программу на html, пусть доработанном, но, все же, языке разметки.
А что, если использовать другой подход? А что если на первое место поставить javascript? Конечно, мы потеряем все то многообразие вариантов красочного и анимированного оформления веб-форм, но приобретем лаконичность и привычную атмосферу разработки десктопного приложения. Этот подход позволит создавать веб приложения на основе готовых библиотек элементов управления. А почему бы и нет, ведь десктопные приложения обходятся же ограниченным набором компонент (это не означает небольшим). И созданные приложения будут не менее красивыми и функциональными, ведь можно использовать множество тем оформления.
Попытаемся сравнить два подхода.
Первый подход будет представлять один из популярнейших фреймворков [vue.js](https://ru.vuejs.org/).
Второй подход будет представлять один новый фреймворк [ui-organizer.ru](https://ui-organizer.ru). Собственно, это прототип библиотеки, но вполне рабочий прототип.
Авторы хотели узнать, насколько второй подход может быть интересным и полезным. И востребована ли необходимость развивать его. Все дальнейшие примеры двух библиотек предназначены для демонстрации основных подходов, а не для сравнения какой фреймворк лучше.
### 3. Разработка простой компоненты
#### 3.1. Простейший компонент Vue.js
В Vue.js компоненты необходимо разрабатывать самому, либо использовать сторонние библиотеки. Для тех, кто знает html и javascript, очень просто создать нужный компонент.
index.html
```html
Фреймворк {{framework}} один из лучших.
```
index.js
```javascript
const App = {
data () {
return {
framework: 'Vue.js'
}
}
}
Vue.createApp (App).mount ('#app');
```
Как видно из примера, программный код написан на html в файле index.html и на javascript в файле index.js. Здесь опускаются вопросы компиляции и сборки приложения, так как в Vue.js процесс сборки отличается в зависимости используете ли вы typescript или javascript. В примере программный код в index.js написан на javascript, поэтому его не надо компилировать и, вообще, вы можете поместить его прямо на страницу index.html вместе с программным кодом, написанном на html. Да, программный код пишем прямо на html. Это будет заметно на более сложных примерах.
#### 3.2 Простейший компонент ui-organizer
ui-organizer не предназначен для создания компонентов. ui-organizer — это библиотека готовых элементов управления (компонентов), с помощью которых вы можете сделать нечто подобное.
main.ts
```typescript
var form: IForm =
type:'IForm',
name:'form',
flex: Flex.fixed,
grouping: Grouping.vertical
elements: [
type: 'IDataElement',
name: 'dataElement',
bindingProperty: 'el',
onAfterSetData: async function (form: IForm, elem: IDataElement, data: any){
elem.value = `Библиотека ${data} — другой фреймворк.`;
return true;
}
}
]
}
AppManager.add ([form]);
AppManager.open ('form', {«el»: «ui-organizer»}, undefined);
```
Как видите здесь html вообще не трогаем. И нам пришлось создать форму и добавить на нее элемент IDataElement. Html будет построен автоматически. Программа построит следующий html:
```html
```
Обращаем внимание на то, что программный код написан на ```typescript```. Это требует компиляции программы, чтобы получить чистый ```javascript```. Реализовать второй подход, наверное, невозможно без использования ```typescript``` — это снижает риски ошибок, ведь нам важно не написать очень хитрый код, с использованием всех возможностей ```javascript```, а создать надежное, простое в сопровождении приложение.
### 4. Разработка компоненты посложнее
#### 4.1. Компонента «Список задач» Vue.js
Сделаем список задач с кнопкой.
Сначала создаем html. Как вы можете заметить, здесь уже язык разметки превратился в полноценную программу с циклами, событиями и др.
```html
-
{{ todo.text }}
```
В файле ```typescript``` добавляем данные и реализуем обработчики событий ```reverseMessage``` и ```setFlag```. В этом примере мы уже используем typescript.
```typescript
interface ITodo {
text: string
}
const ListRendering = defineComponent ({
data () {
return {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
] as Array
isReversed: false
}
},
methods: {
reverseMessage (): void {
this.todos.forEach (item =>{
item.split ('')
.reverse ()
.join ('')
})
},
setFlag (event): void {
this.isReversed = ! this.isReversed;
}
}
})
Vue.createApp (ListRendering).mount ('#list-rendering')
```
Безусловно, данные тесно связаны с компонентом и с ними достаточно удобно работать. Еще плюс в том, что это очень гибкий инструмент и мы можем создать любой компонент (элемент управления на форме пользователя). По сути мы только что создали элемент управления, который можем использовать отдельно, а можем встроить в любой другой компонент.
#### 4.2 Форма со «Списком задач» ui-organizer
Отличие второго подхода (ui-organizer) в том, что мы не создаем новый элемент управления, а используем существующие, чтобы решить задачу. В данном случае используем два элемента управления IList и IButton, причем элемент управления IList мы доработали добавив свойство ```isReversed``` и метод ```reverseText```. У элемента IButton добавили реализацию метода ```onClick``` .
На самом деле IList и IButton это интерфейсы, а мы создаем объекты, реализующие эти интерфейсы. В тексте упростили и написали просто: «элементы управления IList и IButton».
AppManager открывает форму 'simpleForm' и передает ей массив элементов в формате JSON.
```typescript
interface IMyList extends IList {
isReversed: boolean;
reverseText: ()=>void;
}
var form: IForm =
type: 'IForm',
name: 'simpleForm',
flex: Flex.flexible,
grouping: Grouping.vertical,
elements: [
type: 'IList',
name: 'list',
isReversed: false, // Добавленное свойство
itemsElement:
type: 'IStr',
name: 'str',
bindingProperty: 'elem',
},
reverseText (): void {// Добавленный метод
this.items.forEach (item => {
var str =
item.element.value = str.split ('')
.reverse ()
.join ('')
});
this.isReversed = ! this.isReversed;
}
},
type: 'IButton',
name: 'reverse',
caption: 'Перевернуть текст',
onClick: async function (form) {
(
return true;
}
}
]
}
AppManager.add ([form]);
AppManager.open ('simpleForm', [
{ elem: «второй подход» },
{ elem: «новый фреймворк» },
{ elem: «еще один компонент» },
], undefined);
```
### 5. Компоненты и повторное использование
#### 5.1. Повторное использование в Vue.js
В Vue компоненты — это части приложения, которые можно использовать повторно. Вот например:
```javascript
Vue.component ('todo-item', {
props: ['todo'],
template: '
})
```
На Html странице укажите, где хотите вставить компонент:
```html
v-for=«item in groceryList» v-bind: todo=«item» v-bind: key=«item.id» >
```
Здесь мы указали, что для каждого item в groceryList выводим на экран элемент todo-item.
В приложении передаем в данные этот самый groceryList.
```javascript
var app = new Vue ({
el: '#app',
data: {
groceryList: [
{ id: 0, text: 'Овощи' },
{ id: 1, text: 'Сыр' },
{ id: 2, text: 'Что там ещё люди едят?' }
]
}
})
```
#### 5.2. Повторное использование в ui-organizer
Здесь повторное использование — это класс элемента, мы можем создать сколько угодно объектов этого класса. Перепишем предыдущий пример из раздела [4.2](#4.2).
```typescript
class MyList extends UIList {
constructor (_name: string, _bindingProperty: string){
this.name = _name;
this.isReversed = false;
this.itemsElement =
type: 'IStr',
name: 'str',
bindingProperty: _bindingProperty
};
}
reverseText (): void {// Добавленный метод
this.items.forEach (item => {
var str =
item.element.value = str.split ('')
.reverse ()
.join ('')
});
this.isReversed = ! this.isReversed;
}
}
var form: IForm =
type: 'IForm',
name: 'simpleForm',
flex: Flex.flexible,
grouping: Grouping.vertical,
elements: [
type: 'IButton',
name: 'reverse',
caption: 'Перевернуть текст',
onClick: async function (form) {
(
return true;
}
}
]
}
AppManager.add ([form]);
AppManager.open ('simpleForm', [
{ elem: «второй подход» },
{ elem: «новый фреймворк» },
{ elem: «еще один компонент» },
], undefined);
```
Здесь мы создали класс MyList и наследовали его от предопределенного класса UIList, который, в свою очередь, реализует интерфейс IList. При описании формы мы просто создали экземпляр MyList, где в конструктор передали название элемента 'list' и название свойства 'elem', из которого будут подставляться данные.
Как вы могли заметить, в библиотеке ui-organizer не выделено понятие «компонент», так как, в объекте уже инкапсулированы html, данные и поведение в виде методов.
### 6. Данные и методы
#### 6.1. Данные и методы компонента Vue.js
В экземпляре Vue доступны данные и некоторые предопределенные методы:
```typescript
var user = { name: 'Иван' };
var vm = new Vue ({
el: '#app',
data: user
})
vm.name === user.name; // => true
vm.name = 'Дмитрий';
user.name // => Дмитрий
vm.$data === user // => true
vm.$el === document.getElementById ('app') // => true
vm.$watch ('name', function (newValue, oldValue) {
// Этот коллбэк будет вызван, когда изменится `vm.name`
})
```
Здесь компоненту ```vm``` мы передали объект user. И теперь имеем доступ к свойствам этого объекта через компонент ```vm.name```. Это, так называемая система, реактивности, — наверно, ключевое преимущество современных фреймворков.
В примере также показано использование служебных свойств $data и $el и служебного метода $watch. Конечно, служебных свойств и методов гораздо больше.
#### 6.2. Данные и методы элемента управления ui-organizer
Здесь основной подход следующий: элементу управления передаются данные, которые каким-то образом могут измениться. Чтобы получить измененные данные обратно, нужно запросить эти данные. Например, при открытии формы, ей передаются первоначальные данные. Пользователь может изменить эти данные. При нажатии кнопки submit, мы можем запросить измененные данные формы и отправить их на сервер.
```typescript
var user = {
firstName: 'Иван'
}
var form: IForm =
type:'IForm',
name:'form',
grouping: Grouping.vertical
elements: [
type: 'IDataElement',
name: 'dataElement',
bindingProperty: 'firstName',
}
]
}
AppManager.add ([form]);
AppManager.open ('form', user, undefined);
AppManager.activeForm.getData ({}).firstName; //=>'Иван'
var dataElem = AppManager.activeForm.getElement ('dataElement');
/*№1*/AppManager.activeForm.setData ({firstName:'Дмитрий'}); //=>'Дмитрий'
/*№2*/dataElem.getData ({}).firstName == 'Дмитрий'; //=>true
/*№3*/dataElem.setData ({firstName:'Михаил'});
/*№4*/AppManager.activeForm.collectData ({}).firstName; //=>'Михаил'
AppManager.activeForm.dom; //=> HTMLElement
```
В библиотеке ui-organizer для элемента управления можем установить bindingProperty. В нашем примере bindingProperty элемента управления dataElement установлено в firstName. Это значит, что значение свойства firstName в передаваемых данных будет установлено этому элементу (см. №№1–2). Верно, также обратное, когда мы меняем данные элемента функция collectData формы возвращает новые данные (см.№№3–4).
Здесь первоначальный объект остается неизменным. Для того, чтобы получить изменения в первоначальном объекте, необходимо функции getData передать этот первоначальный объект:
```typescript
AppManager.activeForm.getData (user);
user.firstName; //=>'Михаил'
```
Служебное свойство dom возвращает HTMLElement, связанный с этим элементом управления. У каждого элемента управления на форме свой DOMHTMLElement. У каждого элемента управления доступны служебные методы, такие как addClass, getElement и другие.
### 7. CSS и стилевое оформление
#### 7.1. Работа с классами и стилями Vue.js
Для стилевого оформления можем добавлять классы css. Конечно описание этих css классов нужно настроить в соответствующем css файле, который подключен к html.
Описываем html:
```html
class=«element»
v-bind: class=»{ readonly: isReadonly, error: hasError }»
>
```
```typescript
data: {
isReadonly: true,
hasError: false
}
```
В описании компонента задаем данные, от изменения которых будет зависеть добавление классов к HTML элементу. Например, так как isReadonly = true, то класс readonly будет добавлен, а так как hasError = false, то класс error добавлен не будет. Таким образом, имеем:
```html
```
То же самое с inline стилями:
```html
```
```typescript
data: {
userPosition: 'relative',
fontSize: 1.5
}
```
Здесь значение стилей pozition и font-size определяются соответственно переменными userPosition и fontSize, описанными в секции data.
#### 7.2. сcs и стилевое оформление ui-organizer
Библиотека включает в поставку два файла: page.css, который определяет компоновку элементов управления и style.css, который определяет стилевое оформление. Вы можете заменить style.css. Кроме этого, можно для элемента IGroup указать файл .css, который будет применен для этой группы элементов. Кроме того каждый элемент имеет два метода addClass и removeClass, которые добавляют и удаляют классы HTML элемента.
```typescript
var form: IForm =
type:'IForm',
name:'form',
grouping: Grouping.vertical,
stylesheets: 'custom.css',
elements: [
type: 'IDataElement',
name: 'dataElement',
bindingProperty: 'firstName',
isSomeClass: false;
onClick: async function (){
this.isSomeClass = ! this.isSomeClass;
if (this.isSomeClass) elem.addClass («someClass»);
else elem.removeClass («someClass»);
}
}
]
}
AppManager.add ([form]);
AppManager.open ('form', undefined, undefined);
```
### 8. Обработка событий
#### 8.1. Обработка событий vue js
```html
```
```typescript
new Vue ({
el: '#app',
methods: {
warn: function (message) {
alert (message)
}
}
})
```
В vue js обработчики событий указываются прямо в html. И есть возможность подписываться на все события, возникающие в HTML элементе. В примере мы подписались на событие click html элемента button.
Вы также можете генерировать свое событие:
```typescript
this.$emit ('myevent');
```
и подписываться на него
```html
```
#### 8.2. Обработка событий ui-organizer
Подход в ui-organizer другой: вы не имеете доступа к событиям html элементов, но можете обрабатывать предопределенные для каждого элемента управления события и генерировать свои.
Например, ниже написан обработчик события onAfterLoad, в котором задаются обработчики событий show и hide элемента управления IElement:
```typescript
export var baseElem: IElement =
type: 'IElement',
name: 'baseElem',
caption: 'Базовый элемент',
onAfterLoad: async function (form: IForm, elem: IElement, data: any) {
elem.on («show», () => {
elem.addClass ('visible');
});
elem.on («hide», () => {
elem.removeClass ('visible');
})
return true;
}
}
```
Можете генерировать свои события и подписываться на них:
```typescript
export interface ISomeElement
someProperty: string;
setSomeProperty (val: string): void;
}
export interface ISomeElementEvents {
someSetted (form: IForm, element: IElement): void,
}
export var elem: ISomeElement =
type: 'IElement',
name: 'elem',
someProperty: undefined,
setSomeProperty: function (val: string) {
someProperty = val;
this.emit («someSetted», this.form, this);
}
}
```
В последнем примере определены два интерфейса для облегчения написания программы на typescript.
### 9. Работа с формами
#### 9.1. Работа с формами vue js
Для связи компонента vue js c элементом формы input имользуется директива v-model, которая указывается в html. Эта директива связывает данные компонента vue js с элементом ввода input.
```html
Введённое сообщение: {{ userText }}
```
```typescript
new Vue ({
el: '#app',
data: {
userText: 'Текст'
}
})
```
#### 9.2. Работа с формами ui-organizer
Здесь для пользователя одинаково, что работать c простым элеметом IDataElement, со списком IList или полем ввода IProperty. Все является элементом формы. Программист не работает напрямую с элементом input или textArea, а использует элементы управления. Которым передаются данные как было показано в разделе [6.2](#6.2).
Форма с полем ввода и текстом (эхо, дублирующее поле ввода) будет выглядеть следующим образом:
```typescript
var form: IForm =
type:'IForm',
name:'form',
grouping: Grouping.vertical
elements: [
type: 'IProperty',
name: 'text',
bindingProperty: 'userText',
placeHoleder: 'Введите текст'
},
type: 'IStr',
name: 'str',
bindingProperty: 'elem',
}
],
onAfterLoad: async function (){
let input: IProperty = this.getElement ('text') as IProperty;
let srt: IStr = this.getElement ('str') as IStr;
input.on ('change', (form, elem, val)=>{
str.value = val;
})
}
}
AppManager.add ([form]);
AppManager.open ('form', {«userText»: «Просто текст»}, undefined);
```
### 10. Отладка
Как и vue js, так и ui-organizer имеют возможность отладки в VSCode и в браузере.
Для отладки в VSCode нужно установить соответствующее расширение отладчика, например Debugger Chrome.
Для отладки в браузере необходимо настроить конфигурацию сборщика, например webpack.
### Выводы
Конечно же, выводы делать читателям. Авторы заинтересованная сторона и им больше по душе второй подход, т.к. они используют его в своих разработках. Во втором подходе нам нравится простота, однотипность элементов управления, что кнопка, что поле ввода, что список. Одни и те же атрибуты, одни и те же события, методы. А самое главное, — подсказки. Попробуйте создать элемент управления, и вы будете приятно удивлены, как редактор (в нашем случае VSCode) указывает на ошибки и подсказывает в трудной ситуации.