https://habr.com/ru/post/456454/- JavaScript
- Программирование
В данной статье постараюсь описать все основные возможности нового javascript фреймворка Htmlix, а также рассмотреть принцип его работы на примере создания небольшого приложения.
Данное приложение — это страница простого фильтра товаров по категориям, с различными частями шаблона в карточке товара.
Полный пример данного приложения а текже других, можно посмотреть по ссылке на гитхабе:
Ссылка на примеры, данный пример: page-template.html, /js/page-template.js
Htmlix — это микро фреймворк для построения фронтенда на javascript. Принцип его работы базируется на data- атрибутах.
Для начала работы в html файле делается предварительное описание структуры приложения путем прикрепления data атрибутов с указанием их типа (тип атрибута в кавычках),
например:
<div class=" row" data-cards="array" data-cards-listenfetch="emiter-fetch-posts" data-cards- listenrout="emiter-router" data-cards-displayall="style">
<div data-card="container" class="col-4 card-in">
<h5 data-card-title="text">Название 1</h5>
<a data-card-click="click" data-card-href="href" href="/page/card?id=0">
<img data-card-srcimg="src" src="/img/images.jpg" />
</a>
<p data-card-paragraf="text">Краткое описание 1</p>
</div>
<div data-card="container" class="col-4 card-in">
<h5 data-card-title="text">Название 2</h5>
<a data-card-click="click" data-card-href="href" href="/page/card?id=1">
<img data-card-srcimg="src" src="/img/Thumbnail_300x300.png" />
</a>
<p data-card-paragraf="text">Краткое описание 2</p>
</div>
.......................
data-cards=«array» — создаем массив однотипных элементов
data-card=«container» — контейнеры это набор однотипных элементов, либо это один элемент
если он не помещен в массив, контейнеры содержат все изменяемые и получаемые свойства, а также обработчики и слушатели событий.
data-card-title=«text» — свойство которое содержит доступ к тексту из javscript (this.title.getProp(), .setProp())
data-card-click=«click» — прикрепляет обработчик события click к данному тегу
data-cards-listenfetch=«emiter-fetch-posts» — прикрепляет обработчик пользовательского события «emiter-fetch-posts» к свойству контейнера.
Пользовательские события оповещают все свойства к которым они прикреплены при помощи вызова функции из любого участка кода с названием события. Например this.eventProps[«emiter-fetch-posts»].setProp(«posts») — вызовет на всех слушателях, событие «emiter-fetch-posts», в котором можно будет получить измененные данные «posts»
Именование свойств происходит следующим образом: после data идет название контейнера в котором хранится данное свойство, далее идет название свойства либо функции, затем после знака = в кавычках тип данного свойства например «class», если это событие то пишется имя события например «click», если это пользовательское событие то «emiter-далее название события», например «emiter-fetch-posts». При создании объекта свойств тип важен для того чтобы определить, что необходимо делать с данным свойством. Если это событие то будет поиск одноименной функции и добавление соответствующего обработчика.
Далее, после добавления всех свойств и определения структуры приложения в html, идет описание приложения в javascript, путем перечисления всех имен массивов, контейнеров, свойств, и методов обработчиков событий, например:
var State = {
cards:{ <!-- название массива (data-cards="array") -->
container: 'card', <!-- название контейнера (data-card="container") -->
arrayProps: ["listenrout", "listenfetch", "displayall"], <!-- свойства массива -->
arrayMethods: { <! методы массива -->
listenrout: function(){
<!-- слушаем событие ["emiter-router"] (в html - listenrout="emiter-router") -->
if(this.emiter.prop == "/page-template.html"){
<!-- получаем данные события -->
this.parentContainer.props.displayall.setProp("")
<!-- доступ к другим свойствам находящимся в том же массиве (либо контейнере), что и данное свойство -->
}else{
this.parentContainer.props.displayall.setProp("display: none;")
}
},
listenfetch: function(){
<!-- слушаем событие ["emiter-fetch-posts"] (в html - listenrout="emiter-fetch-postsr") -->
var newArray = this.emiter.prop;
this.rootLink.clearContainer(this.pathToContainer);
for(var i =0; i< newArray.length; i++){
this.rootLink.createContainerInArr(this.pathToContainer, {
<! -- создаем новые контейнеры для данных пришедших с сервера предварительно удалив старые -- >
title: newArray[i].title,
paragraf: newArray[i].paragraf_short,
href: newArray[i].href,
srcimg: newArray[i].srcimg
});
}
this.rootLink.stateProperties.cards = newArray;
<!-- перезаписываем массив с карточками товара -->
}
},
props: ['title','paragraf',"click", 'srcimg', "href"],
<!-- перечисляем список свойств и методов объявленных в html -->
methods: {
click: function(event){ <!-- обработчик события click (data-card-click="click")-->
event.preventDefault();
var href = this.parentContainer.props.href.getProp();
<!-- получаем свойство (data-card-href="href") находящееся в том же контейнере что и обработчик события -->
var cardId = href.split("?")[1].split("=")[1];
window.history.pushState(
null,
href,
href
);
<!-- вызываем два пользовательских события и передаем им новые данные -->
this.rootLink.eventProps["emiter-router"].setEventProp(href);
this.rootLink.eventProps["emiter-single-id"].setEventProp(cardId);
}
}
},
.......................
Пользовательские события необходимо также регистрировать в описании объекта, пример:
eventEmiters: {
["emiter-router"]: { <!-- регистрация событий с начальными данными -->
prop: "/page-template.html"
},
["emiter-single-id"]: {
prop: "0"
},
["emiter-fetch-posts"]: {
prop: "",
},
Затем при вызове к примеру: this.rootLink.eventProps[«emiter-router»].setEventProp(href);
все слушатели события [«emiter-router»] (listenrout=«emiter-router» — в html коде) смогут получить новые данные в своих обработчиках и обновить себя на их основании, как в коде ниже:
listenrout: function(){
<!- слушаем событие ["emiter-router"] (в html-listenrout="emiter-router") -->
if(this.emiter.prop == "/page-template.html"){<!-- получаем данные события -->
this.parentContainer.props.displayall.setProp("");
<!-- доступ к другим свойствам находящимся в том же массиве (либо контейнере), что и данное свойство -->
}else{
this.parentContainer.props.displayall.setProp("display: none;")
}
},
Также в описание можно добавить статические переменные (переменные которые не вызывают обработчиков событий при их изменении):
stateProperties: {
cards: "",
},
Общие для всего приложения методы:
stateMethods: {
fetchPosts: function(nameFile, callb){
fetch('/json/'+nameFile+'.json')
.then((response) => {
if(response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
})
.then((json) => {
callb(json);
})
.catch((error) => {
console.log(error);
});
},
},
Компоненты которые будут загружены асинхронно с html шаблона компонентов (template.html)
fetchComponents: {
variants1: {
<!-- данный компонент будет загружен асинхронно, с директории по умолчанию /templete/template.html -->
container: "variant1",
props: ["clickvariant", "text"],
methods: {
clickvariant: function(event){
event.preventDefault();
this.rootLink.eventProps["emiter-chose-variant"].setEventProp(this.parentContainer.props.text.getProp());
//console.log(this);
},
}
},
Это был краткий обзор создания приложения, на htmlix. Создается экземпляр приложения с помощью функции:
window.onload = function (){
var HM = new HTMLixState(State); <!-- создаем экземпляр приложения -->
HM.stateMethods.fetchPosts('category1', function(jsonData){ HM.stateProperties.cards = jsonData });
console.log(HM);
}
В консоли можно посмотреть структуру созданного объекта где:
- description- обьект свойство содержит описание приложения (всех объектов), список всех свойств, контейнеров, методов, 'событийных' переменных и т.д.
- eventProps — список всех обьявленных 'событийных' переменных (emiter) и их свойств (prop) вызывающих события для всех подписчиков(используются для обновления DOM)
- state — содержит все контейнеры и массивы с готовыми обьектами, методами, ссылками и т. д.
- stateMethods- методы для общего пользования (не конкретным свойством)
- stateProperties- статические переменные(не обновляют DOM при изменении).
Сам фреймворк имеет объектно-ориентированную структуру, где каждое свойство — это объект,
имеющий доступ к html тегу по ссылке .htmlLink, доступ к контейнеру .parentContainer и всему приложению .rootLink. Получение, запись и удаление свойств происходит с помощью методов setProp(), getProp() и removeProp(), при этом в зависимости от типа свойства приложение само оприделит каким образом оно изменит данное свойство в html, если это класс то будет вызвана функция this.htmlLink.classList.add(«class»). Для свойств содержащих событие имеется также два дополнительных метода disableEvent() и enableEvent() выключение и включение события на конкретном элементе (чтобы полностью его не удалять). Для свойств содержащих вариант html шаблона (data-cardsingle-render=«render-variant») есть метод render(«name-component») параметром которого является имя компонента при регистрации в js файле.
Все свойства содержатся в контейнерах, если это однотипные контейнеры (data-card=«container») и их необходимо будет добавлять, либо удалять в процессе работы приложения, групируются в массивы(data-cards=«array»).
Контейнер находящийся в массиве можно удалить вызвав метод .remove() либо с корневого приложения по id вызвав this.rootLink.removeByIndex(nameArray,id), id — контейнера это его порядковый номер в массиве и может измениться при добавлении или удалении соседних контейнеров.
Методов в фремворке пока не очень много, с ними можно познакомиться посмотрев исходный код где есть краткое описание к основным используемым в ходе работы. Поэтому тем кто неплохо знает javscript разобраться не должно составить большого труда. Пока что htmlix находится в тестовой версии, однако уже сейчас с помощью него можно решать многие типовые задачи фронтенд разработки.