habrahabr

Обзор нового javascript фреймворка Htmlix

  • вторник, 18 июня 2019 г. в 00:17:47
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 находится в тестовой версии, однако уже сейчас с помощью него можно решать многие типовые задачи фронтенд разработки.