You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
8.7 KiB
JavaScript

/**메뉴의 생성과 선택 시 동작을 지원한다.
*/
class MenuSupport {
/**새 MenuSupport를 생성한다.
* @param selector {string} 메뉴를 담을 컨테이너에 대한 css selector
*/
constructor(conf) {
conf = conf || {};
this._selector = ifEmpty(conf.selector, "#menus");
this._containerSelector = ifEmpty(conf.containerSelector, "#layout-menu");
this._container = document.querySelector(this._containerSelector);
let onclick = conf.onclick || (menu => 'onclick="openMenu(\'{url}\')" '.replace(/{url}/gi, wctx.url(menu.url)));
this._onclick = (menu => menu && menu.url ? onclick(menu) : "");
this._horizontal = this._container && this._container.classList.contains('menu-horizontal');
this._orientation = this._horizontal ? "horizontal" : "vertical";
this._template = this._orientation + "-menu-template";
this._menuItem = '<li data-key="{menuID}" class="menu-item" title="{title}"><a {onclick}class="menu-link{toggle}"><i class="menu-icon tf-icons {imageConf}"></i><div data-i18n="{menuName}">{menuName}</div></a>{menuSub}</li>';
this._menuSub = '<ul class="menu-sub">{children}</ul>';
this._menus = [];
this._init();
}
/**메뉴를 생성할 정보를 설정한다.
* @param menus {array} 메뉴 정보 배열<br />
* 각 메뉴 정보의 레이아웃은 다음과 같다.<br />
* {"id":메뉴 아이디, "name":"메뉴 이름", "url":"실행 url", "parentID":"상위 메뉴 아이디", "description":"메뉴 설명", "imageConf":"이미지 설정", "displayWindow":"표시 창", "children":[하위 메뉴 배열]}
* @returns MenuSupport
*/
setMenuInfo(menus) {
let setParent = menu => {
let children = menu.children || [];
if (children.length < 1) return;
children.forEach(child => {
child.parent = menu;
setParent(child);
});
};
menus.forEach(menu => setParent(menu));
this._menus = menus;
let menuItemTag = menu => {
let tag = this._menuItem
.replace(/{menuID}/gi, menu.id)
.replace(/{menuName}/gi, menu.name)
.replace(/{onclick}/gi, this._onclick(menu))
.replace(/{title}/gi, (menu.description || ""))
.replace(/{imageConf}/gi, (menu.imageConf || "bx bx-layout"));
let parent = menu.children && menu.children.length > 0;
tag = tag.replace(/{toggle}/gi, !parent ? "" : " menu-toggle");
if (!parent)
return tag.replace(/{menuSub}/gi, "");
let children = menu.children.map(child => menuItemTag(child)).join("\n\t")
return tag.replace(/{menuSub}/gi, this._menuSub.replace(/{children}/gi, children));
}
let tags = (menus || []).map(menu => menuItemTag(menu));
document.querySelector(this._selector).innerHTML = tags.join("");
return this._init();
}
_toggleMenuAwares(collapse) {
document.querySelectorAll(".menu-aware").forEach(menuAware => menuAware.style.width = collapse ? "calc(100% - 5.25rem)" : "");
}
_init() {
let menu = new Menu(this._container, {
orientation: this._orientation,
closeChildren: this._horizontal,
showDropdownOnHover: localStorage.getItem('templateCustomizer-' + this._template + '--ShowDropdownOnHover')
? localStorage.getItem('templateCustomizer-' + this._template + '--ShowDropdownOnHover') === 'true'
: window.templateCustomizer !== undefined
? window.templateCustomizer.settings.defaultShowDropdownOnHover
: true
});
window.Helpers.scrollToActive(false); //animate = false
window.Helpers.mainMenu = menu;
//Sets toggler
document.querySelectorAll('.layout-menu-toggle').forEach(item => {
item.addEventListener('click', event => {
if (event.fired) {
return delete event.fired;
}
event.fired = true;
event.preventDefault();
window.Helpers.toggleCollapsed();
if (config.enableMenuLocalStorage && !window.Helpers.isSmallScreen()) {
try {
localStorage.setItem(
'templateCustomizer-' + this._template + '--LayoutCollapsed',
String(window.Helpers.isCollapsed())
);
} catch (e) {}
}
this._toggleMenuAwares(Helpers.isCollapsed());
});
});
// Display menu toggle (layout-menu-toggle) on hover with delay
let delay = (elem, callback) => {
let timeout = null;
elem.onmouseenter = () => {
// Set timeout to be a timer which will invoke callback after 300ms (not for small screen)
timeout = !Helpers.isSmallScreen() ? setTimeout(callback, 300) : setTimeout(callback, 0);
};
elem.onmouseleave = () => {
// Clear any timers set to timeout
document.querySelector('.layout-menu-toggle').classList.remove('d-block');
clearTimeout(timeout);
};
};
if (this._container) {
delay(this._container, () => {
// not for small screen
if (!Helpers.isSmallScreen()) {
document.querySelector('.layout-menu-toggle').classList.add('d-block');
}
});
}
// Detect swipe gesture on the target element and call swipe In
window.Helpers.swipeIn('.drag-target', function (e) {
window.Helpers.setCollapsed(false);
});
// Detect swipe gesture on the target element and call swipe Out
window.Helpers.swipeOut(this._containerSelector, function (e) {
if (window.Helpers.isSmallScreen()) window.Helpers.setCollapsed(true);
});
// Display in main menu when menu scrolls
let menuInnerContainer = document.getElementsByClassName('menu-inner'),
menuInnerShadow = document.getElementsByClassName('menu-inner-shadow')[0];
if (menuInnerContainer.length > 0 && menuInnerShadow) {
menuInnerContainer[0].addEventListener('ps-scroll-y', function () {
if (this.querySelector('.ps__thumb-y').offsetTop) {
menuInnerShadow.style.display = 'block';
} else {
menuInnerShadow.style.display = 'none';
}
});
}
return this;
}
open(url) {
top.document.location.href = url;
}
/**지정한 url의 메뉴를 활성화 한다.
* @param url {string} 메뉴 url
* @returns MenuSupport
*/
setActive(url) {
let menu = this.getMenu(url);
if (!menu) return this;
document
.querySelectorAll(this._selector + " li")
.forEach(li => li.classList.remove("active", "open"));
let a = document.querySelector(this._selector + " li[data-key=\"" + menu.id + "\"]");
if (!a) return this;
let activate = (e, open) => {
e.classList.add("active");
let p = e.parentNode;
let tag = (p != this._container ? p : null) ? p.tagName : "";
if (!tag) return;
if ("li" == tag.toLowerCase()) {
p.classList.add("active");
if (open)
p.classList.add("open");
}
activate(p, true);
};
activate(a);
return this;
}
/**지정하는 url의 메뉴 정보를 반환한다.
* @param url {string} 메뉴 url
* @returns 지정하는 url의 메뉴 정보
*/
getMenu(url) {
let find = menus => {
for (let i = 0; i < menus.length; ++i) {
let menu = menus[i];
if (url == menu.url)
return menu;
let found = find(menu.children || []);
if (found)
return found;
}
return null;
};
return find(this._menus);
}
breadcrumb(url, separator = " / ") {
let menu = this.getMenu(url);
if (!menu)
return "";
let getName = menu => {
let name = menu.name;
let parent = !menu.parent ? "" : getName(menu.parent);
return parent ? parent + separator + name : name;
}
return getName(menu);
}
}
class TabControl extends Dataset {
constructor(conf) {
if (!conf)
conf = {};
conf.keymapper = info => {
if (!info.index) {
info.index = "ndx-" + new Date().getTime();
}
return info.index;
};
conf.dataGetter = obj => null;
super(conf);
this.sticky = conf.sticky || {};
this.maxCount = conf.maxCount || 8;
this.getMenu = conf.getMenu || ((url) => {throw "getMenu(url) missing"});
}
getTab(by) {
if (by.startsWith("ndx-"))
return this.getData(by, "item");
if (by != this.sticky.url) {
let found = this._items.filter(item => item.data.url == by),
empty = !found || found.length < 1;
if (empty)
return null;
found = found[0];
return !found.unreachable ? found : null;
} else {
let found = this._items.filter(item => item.data.url == this.sticky.url);
if (!found || found.length < 1) {
this.addData([this.sticky]);
}
}
}
open(url) {
if (!url
|| url.startsWith("javascript:void")
|| url == this.sticky.url)
return;
let tab = this.getTab(url);
if (tab)
return this.setCurrent(tab.data.index);
ajax.get({
url: wctx.url(url),
success: resp => {
let menu = this.getMenu(url);
if (!menu)
throw "Menu not found for " + url;
if (this.length == this.maxCount) {
let index = isEmpty(this.sticky) ? 0 : 1,
first = this._items[index];
this.close(first.data.url)
}
menu.content = resp;
this.addData([menu]);
this.open(url);
}
});
}
close(url) {
let tab = this.getTab(url);
this.erase(tab.data.index);
}
}