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
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);
|
|
}
|
|
} |