habrahabr

Объектно-ориентированная альтернатива jquery

  • воскресенье, 19 июля 2020 г. в 00:29:57
https://habr.com/ru/post/509502/
  • Open source
  • JavaScript
  • Программирование


Много кто использует jquery в своих проектах, в связи с простотой и легкостью, с которой его можно подключить на страницу, несмотря на то что существует достаточное количество объектно ориентированных фреймворков с компонентным подходом. Однако почти все они создают html разметку из javascript кода, что не всегда удобно, для совместного их использования с другими библиотеками, также создание разметки на js занимает какое-то время на стороне клиента, либо приходится делать серверный рендеринг на node.js что исключает их использование с другими языками например php.

Получается нужно постоянно выбирать, сравнивать все плюсы и минусы, или учить несколько фреймворков для разных проектов, для одного jquery, второго angular, а для будущего присматриваться к vue.js.

Было-бы здорово иметь один инструмент который может справиться со всеми требуемыми задачами, а именно:

  1. Встраиваться в уже отданную сервером страницу, без необходимости серверного рендеринга на node.js и возможность использования его с любыми другими библиотеками
  2. Иметь объектно-ориентированный, компонентный подход построения приложения, четкую и понятную структуру, возможность наследования свойств.
  3. Легкость в изучении, небольшой размер, производительность, возможность применять его как в маленьких так и больших проектах, а также для создания виджетов, слайдеров и т.д.

Как вы наверное уже догадались, здесь я как раз и хочу его представить — htmlix.js

Фреймворк встраивается в уже отданную сервером страницу с помощью data- свойств.
Таким образом нам не нужно создавать шаблоны на каком либо другом языке в js коде, мы просто используем уже готовый html.

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

Уже готовый, похожий пример можно покликать здесь.

После ввода имени пользователя в форму, приложение отобразит его имя в приветствии, кнопке выхода, отобразит сообщение о новом посетителе и создаст новый контейнер в массиве с пользователями, а также сохранит данные в localStorage. После перезагрузки страницы, возьмет имя из localStorage. При клике по кнопке «выйти из профиля» удалит данные со всех компонентов и переменную из localStorage и отобразит сообщение о выходе посетителя.

Давайте создадим четыре компонента: форму входа — form, приветствие — greeting, кнопку выхода — logout и массив со всеми посетителями — users_array.

Создадим html разметку всех компонентов:

<!--  компонент - контейнер форма с двумя свойствами input c типом свойства "inputvalue" и  click с типом "click" -->

<form data-form="container" class="card col-12">
   <div class="form-group">
       <label for="">Введите имя</label>
      <textarea data-form-input="inputvalue" class="form-control" rows="1"></textarea>
    </div>
    <button data-form-click="click" type="submit">Submit</button>
</form>

<!-- компонент - контейнер приветствие и свойство user_name с типом - "text" -->

<div data-greeting="container" class="col-6 card">            
    <p>Привет: <span  data-greeting-user_name="text">guest</span></p>
</div>


<!-- компонент - контейнер кнопка выхода и свойство user_name с типом - "text" -->

<div data-logout="container" class="col-6 card">        
      <a href="#"> Выйти из профиля: ( <span  data-logout-user_name="text"></span>  )
      </a>                
</div>


<!-- компонент - массив пользователей, изначально с двумя контейнерами, в каждом контейнере свойство user_name - "text", и свойством массива  message с типом "text" для отображения сообщений --> 

<div data-users_array="array">
     <p  data-users_array-message="text" > </p>		
	 <div  class="row">
		
            <div data-user="container" class="col-4 card">
		<p>пользователь - <span data-user-user_name="text">user_name_1</span> 
                </p>
	     </div>
			
             <div data-user="container" class="col-4 card">
		 <p>пользователь - <span data-user-user_name="text">user_name_2</span> 
                 </p>
	      </div>
		
        </div>	
</div>	
		

В коде выше мы создали четыре компонента: три из них с типом «container» и один с типом «array» — массив со всеми посетителями. Контейнер — это компонент, в котором сгрупированы, какие либо html свойства, например текст, атрибут, слушатель события и т.д. Все свойства имеют определенный тип. Массив это компонент для работы с однотипными контейнерами, у массива так-же как и у контейнера могут быть различные свойства.

В примере выше мы указали свойства в html разметке с помощью data- атрибутов, также свойства можно указать с помощью селекторов из javascript кода, чтобы не писать их в разметке.

Теперь перенесем их в javascript:

var StateMap = {

    form: {//форма входа
        container: "form",
        props: ["input", "click"],
        methods: {

            click: function(){
                event.preventDefault(); //отменяем перезагрузку страницы
                 
                //this - указывает на свойство, в обработчике события которого мы находимся, parent - доступ к контейнеру из свойства
                //получаем данные свойства input 
                var text = this.parent.props.input.getProp(); 
                console.log(text);                          
            }           
        },      
    },  
    greeting: {//приветствие

        container: "greeting",
        props: [ "user_name", ], 
        methods: {
            }
    },
    logout: { //кнопка выхода

        container: "logout",
        props: [ "user_name", ], 
        methods: {
        },  
    },
    users_array: { //массив с пользователями 
        selector: "div.row", //уточняющий селектор
	arrayProps: [ "message"], //свойство массива 

        container: "user",  //контейнер массива
        props: [ "user_name", ], //свойство контейнера
        methods: {        
        },          
    },
}
window.onload = function(){//создаем экземпляр приложения htmlix

    var HM = new HTMLixState(StateMap);
    console.log(HM);    
}

В примере выше мы перенесли компоненты в javascript код. В компоненте form свойстве click мы записываем данные введенные пользователем в переменную text и выводим их пока-что в консоль. Далее эти данные мы будем использовать во всех оставшихся компонентах. Для того чтобы компоненты узнали о том что данные обновились мы будем использовать пользовательские события.

Добавим событие «emiter-set-name»:

eventEmiters: {  //добавляем объект со всеми пользовательскими событиями в описание приложения
		
   ["emiter-set-name"]: {prop: ""}, //событие с начальными данными
		
},


///вызываем событие в свойстве click компонента form и передаем в него полученные данные
click: function(){
        event.preventDefault(); 
        var text = this.parent.props.input.getProp(); 
        this.rootLink.eventProps["emiter-set-name"].setEventProp(text); 

         //сохраняем имя пользователя в localStorage                 
         window.localStorage.setItem('user_name', name); 
} 


//слушаем событие во всех остальных компонентах и обновляем их свойства
greeting: {
     container: "greeting",
     props: [ "user_name", ['listen_set_name', "emiter-set-name", "" ],], //создали свойство слушатель события в компоненте 
     methods: {
			
          listen_set_name: function(){
		//получили данные из события и обновили свойство user_name
		this.parent.props.user_name.setProp( this.emiter.getEventProp() );
	  }			
       },		
},


logout: { 
		
   container: "logout",
   props: [ "user_name",  ['listen_set_name', "emiter-set-name", "" ] ], 
   methods: {
			
	 listen_set_name: function(){//слушаем событие и обновляем свойство user_name
				
	    this.parent.props.user_name.setProp( this.emiter.getEventProp() );
	},			
    },	
},



users_array: {  
	selector: "div.row",
	arrayProps: [    "message",  ['listen_set_name', "emiter-set-name", ""],	], 
	arrayMethods: {
							
	   listen_set_name: function(){//слушаем событие в свойстве массива и создаем новый контейнер с полученными данными
				
	            this.parent.add( {user_name: this.emiter.getEventProp()} );
                    
                   //выводим сообщение о новом посетителе
                    this.parent.props.message.setProp("новый посетитель - "+this.emiter.getEventProp()); 
								
	    },		
	container: "user",
	props: [ "user_name", ],
	methods: {
	},        	
},



window.onload = function(){

	var HM = new HTMLixState(StateMap);
        
        //проверяем есть ли переменная  user_name в  localStorage и при ее наличии вызываем событие 
	var name = window.localStorage.getItem('user_name');	
	if(name != null)HM.eventProps["emiter-set-name"].setEventProp(name); 

	console.log(HM);
	
}	


В коде выше мы создали пользовательское событие которое одновременно служит хранилищем для меняющихся данных, затем в каждом компоненте добавили свойство-слушатель данного события, чтобы при обновлении данных произвести изменения в каждом компоненте.

Теперь создадим событие «emiter-exit-user» для выхода пользователя:

	eventEmiters: {
		/*      ...        */
		["emiter-exit-user"]: {prop: ""}, 
		
	}

        
  //вызываем событие в компоненте logout и передаем в него имя пользователя
logout: { 		
    container: "logout",
    props: [ /* ...  */,  ["exit", "click", "a:first-of-type"],], //создали новое свойство - слушатель события "click" и указали его вторым способом с помощью массива 

     methods: {
           /* ... */
	   exit: function(){
	      //получаем имя пользователя
	     var user_name = this.parent.props.user_name;

	     //вызываем событие		
	     this.rootLink.eventProps["emiter-exit-user"].setEventProp( user_name.getProp() ); 
	       
             //удаляем пользователя из localStorage
	     window.localStorage.removeItem('user_name');
                
              //очищаем свойство компонента
	      user_name.setProp("");
	    }			
	},	
},


users_array: {  
	selector: "div.row",
	arrayProps: [ /* .. */,   ['listen_exit_user', "emiter-exit-user", ""], ], //добавили свойство - слушатель события  "emiter-exit-user"

	arrayMethods: {
		/* ... */
		listen_exit_user: function(){
			//data - доступ ко всем контейнерам из массива
			this.parent.data.forEach(container=>{
					
				if(container.props.user_name.getProp() == this.emiter.getEventProp()){
						
				         container.remove(true); //удалили контейнер 
				}					
			});

                 //добавили сообщение			
                 this.parent.props.message.setProp( this.emiter.getEventProp()+" - покинул сайт");
			},			
		},		
		container: "user",
		props: [ "user_name", ],
		methods: {
		},        	
},	


greeting: {
		
		container: "greeting",
		props: [/* ... */, ['listen_exit_user', "emiter-exit-user", ""] ], 
		methods: {
			
			listen_exit_user: function(){ 
			       //удалили имя пользователя из свойства
				this.parent.props.user_name.setProp("");
			}			
		},		
	},


Это был краткий обзор основных принципов построения приложения на htmlix, полный код данного примера, вместе с использованием вспомогательных методов можно посмотреть здесь.