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['название метода'] в каждом контейнере.Но здесь для простоты понимания показан такой более обьемный вариант.