В данной статье постараюсь описать все основные возможности нового javascript фреймворка Htmlix, а также рассмотреть принцип его работы на примере создания небольшого приложения.

Данное приложение — это страница простого фильтра товаров по категориям, с различными частями шаблона в карточке товара.

Полный пример данного приложения а также других, можно посмотреть по ссылке на гитхабе:

Ссылка на примеры, данный пример: page-template.html, /js/page-template.js

Wiki с перечнем всех стандартных свойств и методов приложения можно посмотреть
здесь

Рабочий пример приложения можно покликать здесь

Более легкий для понимания код можно посмотреть по ссылке:
Пошаговое руководство по созданию элементов в Htmlix

Htmlix — это микро фреймворк для построения фронтенда на javascript. Принцип его работы базируется на data- атрибутах.

Суть его работы заключается в том что мы с помощью data- свойств создаем структуру приложения в html файле добавляя свойства, обработчики событий и слушателей.
А затем перечисляем их названия в js коде(чтобы получить к ним доступ), создание всех обьектов и прикрепления событий происходит автоматически путем определения типов данных прикрепленных в 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».

Для свойств содержащих стандартные события имеется также два дополнительных метода disableEvent() и enableEvent() выключение и включение события на конкретном элементе (чтобы полностью его не удалять).

Все свойства содержатся в контейнерах (data-card=«container»), если это однотипные контейнеры и их необходимо будет добавлять, либо удалять в процессе работы приложения, групируются в массивы (data-cards=«array»).

Именование свойств происходит следующим образом: после data идет название контейнера в котором хранится данное свойство, далее идет название свойства либо функции, затем после знака = в кавычках тип данного свойства например «class», если это событие то пишется имя события например «click», если это пользовательское событие то «emiter-далее название события», например «emiter-fetch-posts». При создании объекта свойств тип важен для того чтобы определить, что необходимо делать с данным свойством. Если это событие то будет поиск одноименной функции и добавление соответствующего обработчика.

Контейнер находящийся в массиве можно удалить вызвав метод .remove() либо с корневого приложения по id вызвав this.rootLink.removeByIndex(nameArray,id), id — контейнера это его порядковый номер в массиве и может измениться при добавлении или удалении соседних контейнеров.

Далее, после добавления всех свойств и определения структуры приложения в html, идет описание приложения в javascript, путем перечисления всех имен массивов, контейнеров, свойств, и методов обработчиков событий, например:

var State = {
     cards:{	 /*  название массива (data-cards="array")  */
	container: 'card',   /* название контейнера (data-card="container"), обратите внимание контейнер находящийся в массиве называется по другому */ 

	arrayProps: ["listenrout", "listenfetch", "displayall"],   /* перечесляем список свойств и методов обьявленных в html для массива (data-cards-displayall="style")*/

	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);
				},		
			}			
		},

Создается экземпляр приложения с помощью функции:

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 при изменении).


Это был краткий обзор создания приложения, на htmlix.

Сам фреймворк имеет объектно-ориентированную структуру, где каждое свойство — это объект. Получение, запись и удаление свойств происходит с помощью методов setProp(), getProp() и removeProp(), при этом в зависимости от типа свойства приложение само оприделит каким образом оно изменит данное свойство в html, если это класс то будет вызвана функция this.htmlLink.classList.add(«class»).

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

Комментарии (3)


  1. malinichev
    17.06.2019 18:24
    +2

    Форматирование оставляет желать лучшего, поэтому статья не читабельна


  1. sfi0zy
    17.06.2019 19:38
    +3

    Желание изучить, как такие штуки работают изнутри, и сделать что-то свое — это хорошо. Даже если не взлетит (а скорее всего так и будет), то опыта даст точно. Но. Раз уж приносите свой инструмент людям, то было бы неплохо:

    1. Отформатировать код. Даже автоматическое форматирование пойдет. Сейчас оценить примеры и конструктивно покритиковать сложно — глаза вытекают.
    2. Сделать отдельный репозиторий с самим фреймворком или как-то переструктурировать этот, чтобы было понятно, что вот тут инструмент, а вот тут примеры. Вот тут один пример, а вот тут — другой. Ну и NPM + CDN для быстрого старта будет очень кстати.
    3. Набросать минимальную документацию. А еще лучше к ней добавить пару демок на CodePen, где можно по-быстрому потыкать. Как говорится, лучше один раз потыкать, чем десять раз почитать. Все это на GitHub Pages можно захостить — бесплатно и удобно.

    Я тоже делал инструмент, чтобы структурировать лапшу в поделках «для себя», и думаю вам стоит на него посмотреть.


  1. Mihail127 Автор
    18.06.2019 02:10

    Извеняюсь за не очень хорошую читабельность, постараюсь исправить формат подачи, и добавить более ясное и читаемое описание.