habrahabr

Создание 3-уровневого меню с помощью фреймворка Htmlix — часть 2 мобильная версия меню

  • среда, 26 июня 2019 г. в 00:18:10
https://habr.com/ru/post/457520/
  • JavaScript
  • Программирование


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

→ Готовый пример можно посмотреть по ссылке
→ Код примера можно скачать здесь (файлы top-menu-group-select-mobail.html, /js/top-menu-group-select-mobail.js)

Для начала рассмотрим ситуацию, когда свойств в контейнере становится очень много и html код начинает сильно расти в размерах, чтобы исправить это положение можно использовать css селекторы для поиска:

Так наш код выглядел до использования селекторов:

<! -- Массив и контейнер первого уровня внутри него -->
     <ul data-menu="array" class="nav-menu-3 pc-width">
						   
	 <li  data-item_level_1="container" data-item_level_1-over='mouseover' data-item_level_1-out='mouseout' >
		<a  href="#" >Пункт меню</a>

                <ul class="hover-non" data-item_level_1-submenuclass="class" data-item_level_1-group="group" >
                  <!-- Здесь будут содержаться контейнеры data-item_level_2="container" из виртуального массива menu_level_2 -  --->

                </u>
         </li>
     </ul>

С использованием селекторов код можно сократить до:

<! -- Массив и контейнер первого уровня внутри него -->
     <ul data-menu="array" >
						   
	 <li  data-item_level_1="container"  >
		<a  href="#" >Пункт меню</a>

                <ul  >
                  <!-- Здесь будут содержаться контейнеры data-item_level_2="container" из виртуального массива menu_level_2 -  --->

                </u>
         </li>
     </ul>

В коде выше мы убрали все свойства с html кода и оставили только data-menu=«array» и data-item_level_1=«container» аналогично это нужно сделать для всего меню.

Теперь изменим наш javascrip код:

var State = {
	
	menu: {
		
		container: "item_level_1",

		///селекторы ищут элемент внутри контейнера item_level_1 this.htmlLink.querySelector(selector) важно правильно указать селектор
            ///Если оставить селектор пустым он возьмет в качестве dom ссылку контейнера data-item_level_1="container"

		props: [  ["submenuclass", "class", "ul:first-of-type"], ["group", "group", "ul:first-of-type"],  ["toggle", 'click', ""], ["over", 'mouseover', ""], ["out",'mouseout', ""], ["listner_resize",'emiter-resize', ""], ["listner_click",'emiter-click-item_level_1', ""] ],

///теперь мы указываем название свойства, тип и селектор в массиве для каждого свойства

//В коде выше мы добавили свойство toggle - для мобильной версии меню и два слушателя для пользовательских событий  'emiter-resize' - наступает при ресайзе, 'emiter-click-item_level_1' - наступает для всех слушателей при клике по пункту меню не путать со свойством toggle которое слушает клики только по своему пункту, 'emiter-click-item_level_1' слушает все клики и записывает id пункта по которому кликнули в свойство prop

		methods:{ 
			over: function(){
						
				this.parentContainer.props.submenuclass.removeProp("hover-non");
			},
			out: function(){
						
				this.parentContainer.props.submenuclass.setProp("hover-non");
			},
			toggle: function(event){// новый метод 
						
			// проверяем был ли клик на пункте, а не на подпунктах, если подпункт то выходим
				if(event.target.parentElement.dataset['item_level_1'] == undefined)return;
				
////здесь вызываем эмитер emiter-click-item_level_1 и записываем в него новые данные id с нашим пунктом		   
                              this.rootLink.eventProps['emiter-click-item_level_1'].setEventProp(this.parentContainer.id); 	
                                                         
                                           //считаем клики для toggle  this.prop = null по умолчанию (при создании свойства)
				if(this.prop == null){
								
///меняем класс для отображения скрытого списка ul								this.parentContainer.props.submenuclass.removeProp("hover-non");
					this.prop  = 1;
								
				}else{
																 
                                       this.parentContainer.props.submenuclass.setProp("hover-non");
					this.prop  = null;
				}							
							
																		
			},
			listner_resize: function(){

///слушаем пользовательское событие 'emiter-resize' получаем из него данные ( this.emiter.getEventProp() )  и отключаем либо включаем события click и hover("mouseover", "mouseout")

				var windowSize = this.emiter.getEventProp();						
				var props = this.parentContainer.props;
						
					if(windowSize < 600){ 

						props.over.disableEvent("mouseover");				
						props.out.disableEvent("mouseout");
					
						props.toggle.enableEvent("click");
					
					}else{

						props.over.enableEvent("mouseover");
						props.out.enableEvent("mouseout");
					
						props.toggle.disableEvent("click");
				     }						
			},
//Далее слушаем пользовательское событие 'emiter-click-item_level_1' чтобы скрыть открытые пункты если они не соответствуют нашему(сделать эффект 'акордиона')

			listner_click: function(){
						                
                              //получаем id из эмитера
			      var id = this.emiter.getEventProp();

                                //если не равно нашему сворачиваем пункт меню
				if(this.parentContainer.id != id){
							
						this.parentContainer.props.submenuclass.setProp("hover-non");
						
						this.parentContainer.props.toggle.prop  = null;
								}			
						}
		}		
	},
	virtualArrayComponents: { ////Далее по аналогии делаем все тоже для второго уровня 
		
		menu_level_2:{
			container: "item_level_2",
			props: [ ["group_2","group", "ul:first-of-type"], ["submenuclass", "class", "ul:first-of-type"], ["toggle", 'click', ""], ["over", 'mouseover', ""], ["out",'mouseout', ""],["listner_resize",'emiter-resize', ""], ["listner_click",'emiter-click-item_level_2', ""]],

			methods: {
				over: function(){
						
						this.parentContainer.props.submenuclass.removeProp("hover-non");
					},
					out: function(){
						
						this.parentContainer.props.submenuclass.setProp("hover-non");
					},
					toggle: function(){
                           this.rootLink.eventProps['emiter-click-item_level_2'].setEventProp(this.parentContainer.id); 	
							if(this.prop == null){
								
								this.parentContainer.props.submenuclass.removeProp("hover-non");
								this.prop  = 1;
								
							}else{
								
								this.parentContainer.props.submenuclass.setProp("hover-non");
								this.prop  = null;
							}							
							
																		
					},
		listner_resize: function(){
				var windowSize = this.emiter.getEventProp();						
				var props = this.parentContainer.props;
						
				if(windowSize < 600){ 

						props.over.disableEvent("mouseover");				
						props.out.disableEvent("mouseout");
					
						props.toggle.enableEvent("click");
					
				}else{

					          props.over.enableEvent("mouseover");
						props.out.enableEvent("mouseout");
					
						props.toggle.disableEvent("click");
						}						
						},
				listner_click: function(){
					//console.log(this.emiter.getEventProp());
						
					var id = this.emiter.getEventProp();

					if(this.parentContainer.id != id){
							
					this.parentContainer.props.submenuclass.setProp("hover-non");
						
					this.parentContainer.props.toggle.prop  = null;
								}			
						}					
					
			}
		
	    },
		menu_level_3:{
			container: "item_level_3",
			props: [ ],
			methods: {

			}
		
	    },	

		menu_level_3_img:{
			container: "item_level_3_img",
			props: [ ],
			methods: {

			}
		
	    },		
	},	
	eventEmiters: {
		['emiter-resize']: {//добавили пользовательское событие ресайза экрана
			
			prop: null,
		},
		['emiter-click-item_level_1']: {
			
			prop: 'id', //здесь будет меняться id элемента при клике для первого
		},
		['emiter-click-item_level_2']: {
			
			prop: 'id', //здесь будет меняться id элемента при клике для второго уровня
		}

	}	
}
window.onload = function(){

///создаем экземпляр  HTMLix
var HM = new HTMLixState(State);

HM.eventProps['emiter-resize'].setEventProp(window.innerWidth);//вызываем пользовательское событие при загрузке страници

//вызываем пользовательское ['emiter-resize'] событие при ресайзе
window.onresize = function(){ HM.eventProps['emiter-resize'].setEventProp(window.innerWidth) }

console.log(HM);
}

Код выше можно сократить почти в двое убрав одинаковые методы в обьект с общими методами stateMethods и вызвав их оттуда this.rootLink.stateMethods['название метода'] в каждом контейнере.Но здесь для простоты понимания показан такой более обьемный вариант.