/** @module menu */ //todo: instance, options, auto-options-by-node //ok: getChainActiveMenuDataList Скрытие меню открывающихся по клику / 3 depth базовый - след 2 __PluginManager__.register('menu',function(module, exports, require, global) { var Collection = require('Collection'); var Action = require('Action'); var userAction = require('userAction'); var ActionCollection = require('ActionCollection'); var support = require('support'); var Viewer = require('Viewer'); var align = require('align'); var libModule = require('lib'); var dataStructures = require('dataStructures'); const MENU_PLUGIN_NAME = 'menu'; const MENU_ITEM_PLUGIN_NAME = 'menuItem'; const MENU_CLASS = 'kit-menu'; const MENU_ITEM_CLASS = 'kit-menu-item'; /** * @class kitMenuClassDefaultOptions */ var defaultMenuOptions = { parent: null, eventType: 'hint', animation: '', hideDelay: 200, alignOffset: [0, 0], click: (function (event, menu) { console.log('Menu click'); }), hover: (function (event, menu) { console.log('Menu hover'); }), dblclick: (function (event, menu) { console.log('Menu dblclick'); }), hide: (function () { }) }; var MenuList = []; var MenuListIncrement = 0; /** * @typedef kitMenuItemHandler * @param event {MouseEvent} * @param menuItem {MenuItem} * @param menu {Menu} */ /** * @class kit.menu.Class * @param options * @param items */ var Menu = function(options = {}, items = []) { this.id = ++MenuListIncrement; MenuList[this.id] = this; this.itemList = {}; this.node = null; this.bInit = false; this.options = defaultMenuOptions; this.createNode(); //с пула нужна базовая нода this.setOptions(options); this.addItemList(items); }; Menu.prototype.getItemById = function(itemId) { if(this.itemList.hasOwnProperty(itemId)) { return this.itemList[itemId]; } return false; }; /** * хуета * @return {null} */ Menu.prototype.getNode = function () { return this.node; }; Menu.prototype.getParent = function () { return this.options.parent; }; Menu.prototype.createNode = function () { this.node = document.createElement('div'); this.node.classList.add(MENU_CLASS); this.node.style.display = 'none'; this.node.setAttribute('data-menu-id', this.id); this.node.kit = this.node.kit || {}; this.node.kit[MENU_PLUGIN_NAME] = this; }; Menu.prototype.isActive = function () { if(this.viewer) { return this.viewer.isShown(); } return false; }; Menu.prototype.hide = function (options = {}) { if(this.viewer) { this.viewer.hide(options); } return this; }; Menu.prototype.show = function (options = {}) { if(this.viewer) { this.viewer.show(options); } return this; }; Menu.prototype.setPosition = function () { //console.log('setPosition', this.id); if(!(this.getParent() instanceof Element)) { console.warn('Menu.options.parent NOT instanceof Element'); return false; } if(!support.isA(this.align, align.Class)){ return; } this.node.style.position = 'absolute'; this.node.style.width = 'fit-content'; //var parentNode = this.getParentMenuNode() || this.getParent(); var parentNode = this.getParent(); var hAlign = 'right'; var hDirection = 'right'; var parentMenu = this.getParentMenu(); if(parentMenu && parentMenu instanceof Menu) { hDirection = parentMenu.align.condition.horizontal.direction; hAlign = parentMenu.align.condition.horizontal.align; } this.align.offset = this.options.alignOffset; this.align .setNode(this.node) .setParentNode(parentNode) .set(hAlign, 'top', hDirection, 'bottom'); }; Menu.prototype.setPositionRecursive = function(){ console.log('setPositionRecursive', this.id); var list = getChainActiveMenuDataList(this.id); list.forEach(function (data) { data.menu.setPosition(); }) }; /** * под сомнением * рассмотреть под призмой нескольких меню для родительской ноды * @return {Menu} | null */ Menu.prototype.getParentMenu = function() { var p = this.getParent(); if(p instanceof Element && p.hasOwnProperty('kit') && p.kit[MENU_ITEM_PLUGIN_NAME] instanceof MenuItem) { return p.kit[MENU_ITEM_PLUGIN_NAME].getMenu(); } }; Menu.prototype.getParentMenuNode = function() { var parentMenu = this.getParentMenu(), parentMenuNode; if(parentMenu) { parentMenuNode = parentMenu.node; } return parentMenuNode; }; /** * * @param options {kitMenuItemOptions} * @return {MenuItem} */ Menu.prototype.addItem = function (options = {}){ var id; if(options instanceof MenuItem) { options.parentId = this.id; id = options.options.id; this.itemList[id] = options; } else if(options instanceof Object){ if(!options.id) { options.id = support.uniqueId(8); } id = options.id; this.itemList[id] = (new MenuItem(this.id)).setOptions(options).init(); } return this.itemList[id]; }; Menu.prototype.addItemList = function(itemList = []){ var result = []; itemList.forEach((function(element, index, array){ result.push(this.addItem(element)); }).bind(this)); return result; }; Menu.prototype.sortItems = function() { this.itemList = Object.entries(this.itemList) .sort(function(a, b){ return Number(a[1].options.sort) - Number(b[1].options.sort); }) .reduce((r, [k, v]) => ({ ...r, [k]: v }), {}); }; Menu.prototype.render = function() { //console.log('Menu: render', this.id); this.sortItems(); this.node.innerHTML = ''; if(global.config.debug) { this.node.innerHTML = '
'+this.id+'
'; } for (var i in this.itemList) { if(this.itemList.hasOwnProperty(i)) { this.node.append(this.itemList[i].getNode()); } } return this; }; /** * @param parentNode * @return {boolean} */ Menu.prototype.setParent = function(parentNode) { // console.log('Menu: setParent ' + parentNode); parentNode = support.getNode(parentNode); if(!(parentNode instanceof Element)) { //console.warn('Menu.setParent: Не удалось получить parent. ' + parentNode); return false; } this.options.parent = parentNode; if(this.bInit) { this.init(true); } }; Menu.prototype.setEventType = function(type = 'hint') { this.options.eventType = type; if(this.bInit) { this.init(); } }; Menu.prototype.init = function () { var self = this, isActiveBefore = this.isActive(); if(!(this.options.parent instanceof Element)) { console.warn('Menu.init: Не установлен parent'); return false; } if(this.viewer instanceof Viewer) { this.viewer.stop(); } this.render(); if(this.node.parentElement !== this.options.parent) { this.node.remove(); this.options.parent.prepend(this.node); } if(this.align instanceof align.Class) { this.align.unset(); } this.align = new align.Class(this.node, this.options.parent); if(this.actions instanceof ActionCollection) { this.actions.stop(); } var ua = new userAction[support.str.ucFirst(this.options.eventType)](this.options.parent, this.node); if(ua.viewer instanceof Viewer) { this.viewer = ua.viewer; ua.viewer.onHideBefore.push(function () { var list = getChainActiveMenuDataList(self.id); //console.log('скрыт', self.id, 'цепочка', (new kit.Collection(list)).pluck('id').toArray().join('-')); list.shift(); list.reverse().forEach(function (data) { if(data.menu.viewer) { data.menu.viewer.hide({onHideBefore: false}); } }); }); ua.viewer.onHide.push(function () { }); ua.viewer.onShow.push(function (options = {}) { //pre('onShow arguments', self.id, arguments[0]); self.setPosition.call(self); if(options.recursiveMode) { return; } var list = getChainActiveMenuDataList(self.id); //console.log('показан', self.id, 'цепочка', (new kit.Collection(list)).pluck('id').toArray().join('-')); list.shift(); list.forEach(function (data) { if(data.menu.viewer) { data.menu.viewer.show({recursiveMode: true, force: true}); } }); }); } this.actions = new ActionCollection([ua]); this.actions.run(); if(this.isActive() || isActiveBefore) { this.show({force: true}); } else { this.hide(); } this.node.setAttribute('data-kit-menu-init', 1); this.bInit = true; }; /** * @param options {kitMenuClassDefaultOptions} * @return {Menu} */ Menu.prototype.setOptions = function (options = {}) { this.options = Object.assign({}, this.options, options); this.setParent(this.options.parent); return this; }; /** * @class kit.menu.MenuItem * @param parentId */ function MenuItem(parentId) { var _thisMenuItem = this; this.parentId = Number(parentId); this.node = Element; this.bInit = false; this.bActive = false; this.child = null; /** * @class kitMenuItemOptions */ this.options = { id: '', icon: libModule.svg.options, arrow: libModule.svg.arrowMenu, label: 'menu', href: 'javascript:void(0);', class: '', sort: 500, visible: true, /** @type {kitMenuItemHandler} */ hover: this.defaultHoverHandler, /** @type {kitMenuItemHandler} */ click: this.defaultClickHandler, dblclick: this.defaultDblClickHandler, }; this.handleEvent = function(event) { event.stopPropagation(); switch(event.type) { case 'mouseover': this.options.hover(event, _thisMenuItem, MenuList[_thisMenuItem.parentId]); break; case 'click': this.options.click(event, _thisMenuItem, MenuList[_thisMenuItem.parentId]); break; case 'dblclick': this.options.dblclick(event, _thisMenuItem, MenuList[_thisMenuItem.parentId]); break; } }; this.createNode(); } /** * @param event {MouseEvent} * @param menuItem {MenuItem} * @param menu {Menu} */ MenuItem.prototype.defaultHoverHandler = function (event, menuItem, menu) { console.log('hover: ' + menuItem.options.label); }; /** * @param event {MouseEvent} * @param menuItem {MenuItem} * @param menu {Menu} */ MenuItem.prototype.defaultClickHandler = function (event, menuItem, menu) { console.log('click: ' + menuItem.options.label); menu.hide(); }; /** * @param event {MouseEvent} * @param menuItem {MenuItem} * @param menu {Menu} */ MenuItem.prototype.defaultDblClickHandler = function (event, menuItem, menu) { console.log('dblclick: ' + menuItem.options.label); }; MenuItem.prototype.getMenu = function () { return MenuList[this.parentId]; }; MenuItem.prototype.getParent = function () { if(MenuList[this.parentId] instanceof Menu) { return MenuList[this.parentId].node; } return null; }; MenuItem.prototype.getId = function () { return this.options.id; }; /** * @returns HTMLElement */ MenuItem.prototype.getNode = function () { return this.node; }; MenuItem.prototype.createNode = function () { this.node = document.createElement('a'); this.node.style.position = 'relative'; this.node.dataset.parentId = this.parentId; support.setNodeData(this.node, 'menuItem', this); }; /** * @param content HTMLElement | string * @returns {MenuItem} */ MenuItem.prototype.setLabel = function (content = '') { this.options.label = content; var wrap = this.getNodeScopeElementByClassName('kit-menu-item__label'); this.setContent(wrap, content); return this; }; /** * @param href * @returns {MenuItem} */ MenuItem.prototype.setHref = function (href = 'javascript:void(0);') { this.options.href = href; this.getNode().setAttribute('href', href); return this; }; /** * @param content HTMLElement | string * @returns {MenuItem} */ MenuItem.prototype.setIcon = function (content = '') { this.options.icon = content; var wrap = this.getNodeScopeElementByClassName('kit-menu-item__icon'); this.setContent(wrap, content); return this; }; /** * @param sort int * @returns {MenuItem} */ MenuItem.prototype.setSort = function (sort = 500) { this.options.sort = Number(sort); return this; }; /** * @param content HTMLElement | string * @returns {MenuItem} */ MenuItem.prototype.setArrow = function (content = '') { this.options.arrow = content; var wrap = this.getNodeScopeElementByClassName('kit-menu-item__arrow'); this.setContent(wrap, content); return this; }; MenuItem.prototype.setId = function (id = '') { if(id) { this.options.id = id; } this.node.id = this.options.id; return this; }; MenuItem.prototype.setVisible = function (visible = true) { this.options.visible = visible; this.node.style.display = this.options.visible?'':'none'; return this; }; /** * @param {Menu} menu * @return {MenuItem} */ MenuItem.prototype.setChild = function (menu) { if(menu instanceof Menu) { menu.setParent(this.node); menu.init(); } else if( Array.isArray(menu) ) { var menuClass; menuClass = new Menu(); menu.forEach(function (item, i) { if(typeof item === 'object') { menuClass.addItem(item); } }); menuClass.setParent(this.node); menuClass.init(); } return this; }; MenuItem.prototype.addClass = function (addClass = '') { if(addClass) { this.node.classList.add(addClass); } return this; }; /** * @param options Object * @return {MenuItem} */ MenuItem.prototype.setOptions = function (options = {}) { /** * @type {kitMenuItemOptions} */ this.options = Object.assign({}, this.options, options); return this; }; /** * @return {MenuItem} */ MenuItem.prototype.render = function () { this.setLabel(this.options.label); this.setHref(this.options.href); this.setId(this.options.id); this.addClass(MENU_ITEM_CLASS); this.addClass(this.options.class); this.setChild(this.options.child); this.setIcon(this.options.icon); this.setArrow(this.options.arrow); this.setVisible(this.options.visible); this.setSort(this.options.sort); return this; }; MenuItem.prototype.init = function () { if(this.bInit) { return this; } this.render(); this.node.removeEventListener('click', this, false); this.node.removeEventListener('dblclick', this, false); this.node.addEventListener('click', this, false); this.node.addEventListener('dblclick', this, false); this.bInit = true; return this; }; MenuItem.prototype.reInit = function () { this.render(); this.node.removeEventListener('click', this, false); this.node.removeEventListener('dblclick', this, false); this.node.addEventListener('click', this, false); this.node.addEventListener('dblclick', this, false); return this; }; MenuItem.prototype.remove = function () { this.node.removeEventListener('click', this, false); this.node.removeEventListener('dblclick', this, false); var id = this.options.id; //console.log('remove item' + id); setTimeout((function(parentId, id){ return function() { if(MenuList.hasOwnProperty(parentId) && MenuList[parentId].itemList.hasOwnProperty(id)) { delete MenuList[parentId].itemList[id]; MenuList[parentId].init(); } }; })(this.parentId, this.options.id),0); }; /** * @param className * @returns HTMLElement */ MenuItem.prototype.getNodeScopeElementByClassName = function(className = ''){ var wrap; this.node && (wrap = this.node.querySelector(':scope > .' + className)); if(!wrap) { wrap = document.createElement('div'); wrap.classList.add(className); this.node.append(wrap); } return wrap; }; /** * @param selector HTMLElement | string * @param content HTMLElement | string * @return boolean */ MenuItem.prototype.setContent = function(selector, content){ support.innerHtml(selector, content); return true; }; var getInstance = function(options = {}, items = []) { if(options instanceof Menu) { return options; } if(typeof options === 'string') { options = document.querySelector(options); } return support.getNodeData(options, MENU_PLUGIN_NAME) || new Menu(options, items); }; var getActiveMenuDataList = function(){ var idList = [], dataList = [], refList = []; function getParentId(id) { var parentId = false, menu = MenuList[id], parentMenu; if(refList.hasOwnProperty(id) && refList[id].parentMenu) { return refList[id].parentMenu.id; } if(menu) { refList[id] = refList[id] || {}; parentMenu = refList[id].parentMenu = menu.getParentMenu(); } parentMenu && (parentId = parentMenu.id); return parentId; } MenuList.forEach(function(menu){ var pos, posId, id, parentId; if(!((menu instanceof Menu) && menu.isActive())) { return; } id = menu.id; refList[id] = refList[id] || {}; refList[id].menu = menu; parentId = getParentId(id); var depth = function (id){ return function() { var pid = refList[id].data.parentId; return 1 + (refList.hasOwnProperty(pid)?(support.valueEx(refList[pid].data.depth)):0); }; }; !( parentId!==false && ( ((pos=idList.indexOf(parentId))!==-1) ? ( (idList.indexOf(id)===-1)&&(idList.splice(pos+1,0,id), dataList.splice(pos+1,0, refList[id].data = {id:id, depth: depth(id), parentId: parentId, type: 1})) ) : ( ( refList[parentId] = refList[parentId] || {}, (posId=idList.indexOf(id))!==-1 )? ( idList.splice(posId,0,parentId), dataList.splice(posId,0,refList[parentId].data = {id:parentId, depth: depth(parentId), parentId: getParentId(parentId), type: 5}) ): ( idList.push(parentId), dataList.push(refList[parentId].data = {id:parentId, depth: depth(parentId), parentId: getParentId(parentId), type: 2}), idList.push(id), dataList.push(refList[id].data = {id:id, depth: depth(id), parentId: parentId, type: 3}) ) ), 0 ) ) &&(idList.indexOf(id)===-1)&&(idList.push(id), dataList.push(refList[id].data = {id:id, depth: 1, parentId: 0, type: 4})); }); var result = []; dataList.forEach(function (data) { data.depth = support.valueEx(data.depth); data.menu = refList[data.id].menu; if(data.menu) { result.push(data); } }); return result; }; var getChainActiveMenuDataList = function (id) { var dataList = getActiveMenuDataList(), result = [], depth = 0; dataList.forEach(function(data) { if(data.id === id) { depth = data.depth; } if(depth === data.depth && data.id !== id || depth > data.depth) { depth = 0; } if(depth) { result.push(data); } }); //pre(id, (new Collection(result).pluck('id').toArray().join('-'))); return result; }; var reInitAllMenu = function(){ var dataList = getActiveMenuDataList(); dataList.forEach(function(data) { data.menu.init(); }); }; var reCalcAllMenu = function(){ console.log('reCalcAllMenu'); var dataList = getActiveMenuDataList(); dataList.forEach(function(data) { data.menu.setPosition(); }); }; var autoload = function(doc = document, selector = '.kit-menu'){ if(!(doc instanceof Node)) { return false; } var menuList = doc.querySelectorAll(selector); menuList.forEach( function(menuNode, currentIndex, listObj) { if(!menuNode.kit || !menuNode.kit.hasOwnProperty(MENU_PLUGIN_NAME)) { } } ); }; var reCalcAllMenuLimiter = new dataStructures.StackExecutionLimiter(this, reCalcAllMenu, 250); var globalHandlers = { resize: function(){ reCalcAllMenuLimiter.exec(); }, }; window.addEventListener('resize', globalHandlers.resize); /** * @memberOf kit */ var menu = { MenuItem: MenuItem, Class: Menu, getInstance: getInstance, reInitAllMenu: reInitAllMenu, reCalcAllMenu: reCalcAllMenu, autoload: autoload, getActiveMenuDataList: getActiveMenuDataList, getChainActiveMenuDataList: getChainActiveMenuDataList, MenuList: MenuList, }; return module.exports = menu; });