1420 lines
46 KiB
JavaScript
1420 lines
46 KiB
JavaScript
// Constants
|
|
const TRANS_EVENTS = ["transitionend", "webkitTransitionEnd", "oTransitionEnd"];
|
|
const TRANS_PROPERTIES = [
|
|
"transition",
|
|
"MozTransition",
|
|
"webkitTransition",
|
|
"WebkitTransition",
|
|
"OTransition",
|
|
];
|
|
const INLINE_STYLES = `
|
|
.layout-menu-fixed .layout-navbar-full .layout-menu,
|
|
.layout-menu-fixed-offcanvas .layout-navbar-full .layout-menu {
|
|
top: {navbarHeight}px !important;
|
|
}
|
|
.layout-page {
|
|
padding-top: {navbarHeight}px !important;
|
|
}
|
|
.content-wrapper {
|
|
padding-bottom: {footerHeight}px !important;
|
|
}`;
|
|
|
|
// Guard
|
|
function requiredParam(name) {
|
|
throw new Error(`Parameter required${name ? `: \`${name}\`` : ""}`);
|
|
}
|
|
|
|
const Helpers = {
|
|
// Root Element
|
|
ROOT_EL: typeof window !== "undefined" ? document.documentElement : null,
|
|
|
|
// Large screens breakpoint
|
|
LAYOUT_BREAKPOINT: 1200,
|
|
|
|
// Resize delay in milliseconds
|
|
RESIZE_DELAY: 200,
|
|
|
|
menuPsScroll: null,
|
|
|
|
mainMenu: null,
|
|
|
|
// Internal variables
|
|
_curStyle: null,
|
|
_styleEl: null,
|
|
_resizeTimeout: null,
|
|
_resizeCallback: null,
|
|
_transitionCallback: null,
|
|
_transitionCallbackTimeout: null,
|
|
_listeners: [],
|
|
_initialized: false,
|
|
_autoUpdate: false,
|
|
_lastWindowHeight: 0,
|
|
|
|
// *******************************************************************************
|
|
// * Utilities
|
|
|
|
// ---
|
|
// Scroll To Active Menu Item
|
|
_scrollToActive(animate = false, duration = 500) {
|
|
const layoutMenu = this.getLayoutMenu();
|
|
|
|
if (!layoutMenu) return;
|
|
|
|
let activeEl = layoutMenu.querySelector(
|
|
"li.menu-item.active:not(.open)",
|
|
);
|
|
|
|
if (activeEl) {
|
|
// t = current time
|
|
// b = start value
|
|
// c = change in value
|
|
// d = duration
|
|
const easeInOutQuad = (t, b, c, d) => {
|
|
t /= d / 2;
|
|
if (t < 1) return (c / 2) * t * t + b;
|
|
t -= 1;
|
|
return (-c / 2) * (t * (t - 2) - 1) + b;
|
|
};
|
|
|
|
const element = this.getLayoutMenu().querySelector(".menu-inner");
|
|
|
|
if (typeof activeEl === "string") {
|
|
activeEl = document.querySelector(activeEl);
|
|
}
|
|
if (typeof activeEl !== "number") {
|
|
activeEl =
|
|
activeEl.getBoundingClientRect().top + element.scrollTop;
|
|
}
|
|
|
|
// If active element's top position is less than 2/3 (66%) of menu height than do not scroll
|
|
if (activeEl < parseInt((element.clientHeight * 2) / 3, 10)) return;
|
|
|
|
const start = element.scrollTop;
|
|
const change =
|
|
activeEl - start - parseInt(element.clientHeight / 2, 10);
|
|
const startDate = +new Date();
|
|
|
|
if (animate === true) {
|
|
const animateScroll = () => {
|
|
const currentDate = +new Date();
|
|
const currentTime = currentDate - startDate;
|
|
const val = easeInOutQuad(
|
|
currentTime,
|
|
start,
|
|
change,
|
|
duration,
|
|
);
|
|
element.scrollTop = val;
|
|
if (currentTime < duration) {
|
|
requestAnimationFrame(animateScroll);
|
|
} else {
|
|
element.scrollTop = change;
|
|
}
|
|
};
|
|
animateScroll();
|
|
} else {
|
|
element.scrollTop = change;
|
|
}
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Swipe In Gesture
|
|
_swipeIn(targetEl, callback) {
|
|
const { Hammer } = window;
|
|
if (typeof Hammer !== "undefined" && typeof targetEl === "string") {
|
|
// Swipe menu gesture
|
|
const swipeInElement = document.querySelector(targetEl);
|
|
|
|
if (swipeInElement) {
|
|
const hammerInstance = new Hammer(swipeInElement);
|
|
|
|
hammerInstance.on("panright", callback);
|
|
}
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Swipe Out Gesture
|
|
_swipeOut(targetEl, callback) {
|
|
const { Hammer } = window;
|
|
if (typeof Hammer !== "undefined" && typeof targetEl === "string") {
|
|
setTimeout(() => {
|
|
// Swipe menu gesture
|
|
const swipeOutElement = document.querySelector(targetEl);
|
|
|
|
if (swipeOutElement) {
|
|
const hammerInstance = new Hammer(swipeOutElement);
|
|
|
|
hammerInstance
|
|
.get("pan")
|
|
.set({
|
|
direction: Hammer.DIRECTION_ALL,
|
|
threshold: 250,
|
|
});
|
|
hammerInstance.on("panleft", callback);
|
|
}
|
|
}, 500);
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Swipe Out On Overlay Tap
|
|
_overlayTap(targetEl, callback) {
|
|
const { Hammer } = window;
|
|
|
|
if (typeof Hammer !== "undefined" && typeof targetEl === "string") {
|
|
// Swipe out overlay element
|
|
const swipeOutOverlayElement = document.querySelector(targetEl);
|
|
|
|
if (swipeOutOverlayElement) {
|
|
const hammerInstance = new Hammer(swipeOutOverlayElement);
|
|
|
|
hammerInstance.on("tap", callback);
|
|
}
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Add classes
|
|
_addClass(cls, el = this.ROOT_EL) {
|
|
if (el && el.length !== undefined) {
|
|
// Add classes to multiple elements
|
|
el.forEach((e) => {
|
|
if (e) {
|
|
cls.split(" ").forEach((c) => e.classList.add(c));
|
|
}
|
|
});
|
|
} else if (el) {
|
|
// Add classes to single element
|
|
cls.split(" ").forEach((c) => el.classList.add(c));
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Remove classes
|
|
_removeClass(cls, el = this.ROOT_EL) {
|
|
if (el && el.length !== undefined) {
|
|
// Remove classes to multiple elements
|
|
el.forEach((e) => {
|
|
if (e) {
|
|
cls.split(" ").forEach((c) => e.classList.remove(c));
|
|
}
|
|
});
|
|
} else if (el) {
|
|
// Remove classes to single element
|
|
cls.split(" ").forEach((c) => el.classList.remove(c));
|
|
}
|
|
},
|
|
|
|
// Toggle classes
|
|
_toggleClass(el = this.ROOT_EL, cls1, cls2) {
|
|
if (el.classList.contains(cls1)) {
|
|
el.classList.replace(cls1, cls2);
|
|
} else {
|
|
el.classList.replace(cls2, cls1);
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Has class
|
|
_hasClass(cls, el = this.ROOT_EL) {
|
|
let result = false;
|
|
|
|
cls.split(" ").forEach((c) => {
|
|
if (el.classList.contains(c)) result = true;
|
|
});
|
|
|
|
return result;
|
|
},
|
|
|
|
_findParent(el, cls) {
|
|
if (
|
|
(el && el.tagName.toUpperCase() === "BODY") ||
|
|
el.tagName.toUpperCase() === "HTML"
|
|
)
|
|
return null;
|
|
el = el.parentNode;
|
|
while (
|
|
el &&
|
|
el.tagName.toUpperCase() !== "BODY" &&
|
|
!el.classList.contains(cls)
|
|
) {
|
|
el = el.parentNode;
|
|
}
|
|
el = el && el.tagName.toUpperCase() !== "BODY" ? el : null;
|
|
return el;
|
|
},
|
|
|
|
// ---
|
|
// Trigger window event
|
|
_triggerWindowEvent(name) {
|
|
if (typeof window === "undefined") return;
|
|
|
|
if (document.createEvent) {
|
|
let event;
|
|
|
|
if (typeof Event === "function") {
|
|
event = new Event(name);
|
|
} else {
|
|
event = document.createEvent("Event");
|
|
event.initEvent(name, false, true);
|
|
}
|
|
|
|
window.dispatchEvent(event);
|
|
} else {
|
|
window.fireEvent(`on${name}`, document.createEventObject());
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Trigger event
|
|
_triggerEvent(name) {
|
|
this._triggerWindowEvent(`layout${name}`);
|
|
|
|
this._listeners
|
|
.filter((listener) => listener.event === name)
|
|
.forEach((listener) => listener.callback.call(null));
|
|
},
|
|
|
|
// ---
|
|
// Update style
|
|
_updateInlineStyle(navbarHeight = 0, footerHeight = 0) {
|
|
if (!this._styleEl) {
|
|
this._styleEl = document.createElement("style");
|
|
this._styleEl.type = "text/css";
|
|
document.head.appendChild(this._styleEl);
|
|
}
|
|
|
|
const newStyle = INLINE_STYLES.replace(
|
|
/\{navbarHeight\}/gi,
|
|
navbarHeight,
|
|
).replace(/\{footerHeight\}/gi, footerHeight);
|
|
|
|
if (this._curStyle !== newStyle) {
|
|
this._curStyle = newStyle;
|
|
this._styleEl.textContent = newStyle;
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Remove style
|
|
_removeInlineStyle() {
|
|
if (this._styleEl) document.head.removeChild(this._styleEl);
|
|
this._styleEl = null;
|
|
this._curStyle = null;
|
|
},
|
|
|
|
// ---
|
|
// Redraw layout menu (Safari bugfix)
|
|
_redrawLayoutMenu() {
|
|
const layoutMenu = this.getLayoutMenu();
|
|
|
|
if (layoutMenu && layoutMenu.querySelector(".menu")) {
|
|
const inner = layoutMenu.querySelector(".menu-inner");
|
|
const { scrollTop } = inner;
|
|
const pageScrollTop = document.documentElement.scrollTop;
|
|
|
|
layoutMenu.style.display = "none";
|
|
// layoutMenu.offsetHeight
|
|
layoutMenu.style.display = "";
|
|
inner.scrollTop = scrollTop;
|
|
document.documentElement.scrollTop = pageScrollTop;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
// ---
|
|
// Check for transition support
|
|
_supportsTransitionEnd() {
|
|
if (window.QUnit) return false;
|
|
|
|
const el = document.body || document.documentElement;
|
|
|
|
if (!el) return false;
|
|
|
|
let result = false;
|
|
TRANS_PROPERTIES.forEach((evnt) => {
|
|
if (typeof el.style[evnt] !== "undefined") result = true;
|
|
});
|
|
|
|
return result;
|
|
},
|
|
|
|
// ---
|
|
// Calculate current navbar height
|
|
_getNavbarHeight() {
|
|
const layoutNavbar = this.getLayoutNavbar();
|
|
|
|
if (!layoutNavbar) return 0;
|
|
if (!this.isSmallScreen())
|
|
return layoutNavbar.getBoundingClientRect().height;
|
|
|
|
// Needs some logic to get navbar height on small screens
|
|
|
|
const clonedEl = layoutNavbar.cloneNode(true);
|
|
clonedEl.id = null;
|
|
clonedEl.style.visibility = "hidden";
|
|
clonedEl.style.position = "absolute";
|
|
|
|
Array.prototype.slice
|
|
.call(clonedEl.querySelectorAll(".collapse.show"))
|
|
.forEach((el) => this._removeClass("show", el));
|
|
|
|
layoutNavbar.parentNode.insertBefore(clonedEl, layoutNavbar);
|
|
|
|
const navbarHeight = clonedEl.getBoundingClientRect().height;
|
|
|
|
clonedEl.parentNode.removeChild(clonedEl);
|
|
|
|
return navbarHeight;
|
|
},
|
|
|
|
// ---
|
|
// Get current footer height
|
|
_getFooterHeight() {
|
|
const layoutFooter = this.getLayoutFooter();
|
|
|
|
if (!layoutFooter) return 0;
|
|
|
|
return layoutFooter.getBoundingClientRect().height;
|
|
},
|
|
|
|
// ---
|
|
// Get animation duration of element
|
|
_getAnimationDuration(el) {
|
|
const duration = window.getComputedStyle(el).transitionDuration;
|
|
|
|
return (
|
|
parseFloat(duration) * (duration.indexOf("ms") !== -1 ? 1 : 1000)
|
|
);
|
|
},
|
|
|
|
// ---
|
|
// Set menu hover state
|
|
_setMenuHoverState(hovered) {
|
|
this[hovered ? "_addClass" : "_removeClass"]("layout-menu-hover");
|
|
},
|
|
|
|
// ---
|
|
// Toggle collapsed
|
|
_setCollapsed(collapsed) {
|
|
if (this.isSmallScreen()) {
|
|
if (collapsed) {
|
|
this._removeClass("layout-menu-expanded");
|
|
} else {
|
|
setTimeout(
|
|
() => {
|
|
this._addClass("layout-menu-expanded");
|
|
},
|
|
this._redrawLayoutMenu() ? 5 : 0,
|
|
);
|
|
}
|
|
} else {
|
|
this[collapsed ? "_addClass" : "_removeClass"](
|
|
"layout-menu-collapsed",
|
|
);
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Add layout sivenav toggle animationEnd event
|
|
_bindLayoutAnimationEndEvent(modifier, cb) {
|
|
const menu = this.getMenu();
|
|
const duration = menu ? this._getAnimationDuration(menu) + 50 : 0;
|
|
|
|
if (!duration) {
|
|
modifier.call(this);
|
|
cb.call(this);
|
|
return;
|
|
}
|
|
|
|
this._transitionCallback = (e) => {
|
|
if (e.target !== menu) return;
|
|
this._unbindLayoutAnimationEndEvent();
|
|
cb.call(this);
|
|
};
|
|
|
|
TRANS_EVENTS.forEach((e) => {
|
|
menu.addEventListener(e, this._transitionCallback, false);
|
|
});
|
|
|
|
modifier.call(this);
|
|
|
|
this._transitionCallbackTimeout = setTimeout(() => {
|
|
this._transitionCallback.call(this, { target: menu });
|
|
}, duration);
|
|
},
|
|
|
|
// ---
|
|
// Remove layout sivenav toggle animationEnd event
|
|
_unbindLayoutAnimationEndEvent() {
|
|
const menu = this.getMenu();
|
|
|
|
if (this._transitionCallbackTimeout) {
|
|
clearTimeout(this._transitionCallbackTimeout);
|
|
this._transitionCallbackTimeout = null;
|
|
}
|
|
|
|
if (menu && this._transitionCallback) {
|
|
TRANS_EVENTS.forEach((e) => {
|
|
menu.removeEventListener(e, this._transitionCallback, false);
|
|
});
|
|
}
|
|
|
|
if (this._transitionCallback) {
|
|
this._transitionCallback = null;
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Bind delayed window resize event
|
|
_bindWindowResizeEvent() {
|
|
this._unbindWindowResizeEvent();
|
|
|
|
const cb = () => {
|
|
if (this._resizeTimeout) {
|
|
clearTimeout(this._resizeTimeout);
|
|
this._resizeTimeout = null;
|
|
}
|
|
this._triggerEvent("resize");
|
|
};
|
|
|
|
this._resizeCallback = () => {
|
|
if (this._resizeTimeout) clearTimeout(this._resizeTimeout);
|
|
this._resizeTimeout = setTimeout(cb, this.RESIZE_DELAY);
|
|
};
|
|
|
|
window.addEventListener("resize", this._resizeCallback, false);
|
|
},
|
|
|
|
// ---
|
|
// Unbind delayed window resize event
|
|
_unbindWindowResizeEvent() {
|
|
if (this._resizeTimeout) {
|
|
clearTimeout(this._resizeTimeout);
|
|
this._resizeTimeout = null;
|
|
}
|
|
|
|
if (this._resizeCallback) {
|
|
window.removeEventListener("resize", this._resizeCallback, false);
|
|
this._resizeCallback = null;
|
|
}
|
|
},
|
|
|
|
_bindMenuMouseEvents() {
|
|
if (
|
|
this._menuMouseEnter &&
|
|
this._menuMouseLeave &&
|
|
this._windowTouchStart
|
|
)
|
|
return;
|
|
|
|
const layoutMenu = this.getLayoutMenu();
|
|
if (!layoutMenu) return this._unbindMenuMouseEvents();
|
|
|
|
if (!this._menuMouseEnter) {
|
|
this._menuMouseEnter = () => {
|
|
if (
|
|
this.isSmallScreen() ||
|
|
!this._hasClass("layout-menu-collapsed") ||
|
|
this.isOffcanvas() ||
|
|
this._hasClass("layout-transitioning")
|
|
) {
|
|
return this._setMenuHoverState(false);
|
|
}
|
|
|
|
return this._setMenuHoverState(true);
|
|
};
|
|
layoutMenu.addEventListener(
|
|
"mouseenter",
|
|
this._menuMouseEnter,
|
|
false,
|
|
);
|
|
layoutMenu.addEventListener(
|
|
"touchstart",
|
|
this._menuMouseEnter,
|
|
false,
|
|
);
|
|
}
|
|
|
|
if (!this._menuMouseLeave) {
|
|
this._menuMouseLeave = () => {
|
|
this._setMenuHoverState(false);
|
|
};
|
|
layoutMenu.addEventListener(
|
|
"mouseleave",
|
|
this._menuMouseLeave,
|
|
false,
|
|
);
|
|
}
|
|
|
|
if (!this._windowTouchStart) {
|
|
this._windowTouchStart = (e) => {
|
|
if (
|
|
!e ||
|
|
!e.target ||
|
|
!this._findParent(e.target, ".layout-menu")
|
|
) {
|
|
this._setMenuHoverState(false);
|
|
}
|
|
};
|
|
window.addEventListener("touchstart", this._windowTouchStart, true);
|
|
}
|
|
},
|
|
|
|
_unbindMenuMouseEvents() {
|
|
if (
|
|
!this._menuMouseEnter &&
|
|
!this._menuMouseLeave &&
|
|
!this._windowTouchStart
|
|
)
|
|
return;
|
|
|
|
const layoutMenu = this.getLayoutMenu();
|
|
|
|
if (this._menuMouseEnter) {
|
|
if (layoutMenu) {
|
|
layoutMenu.removeEventListener(
|
|
"mouseenter",
|
|
this._menuMouseEnter,
|
|
false,
|
|
);
|
|
layoutMenu.removeEventListener(
|
|
"touchstart",
|
|
this._menuMouseEnter,
|
|
false,
|
|
);
|
|
}
|
|
this._menuMouseEnter = null;
|
|
}
|
|
|
|
if (this._menuMouseLeave) {
|
|
if (layoutMenu) {
|
|
layoutMenu.removeEventListener(
|
|
"mouseleave",
|
|
this._menuMouseLeave,
|
|
false,
|
|
);
|
|
}
|
|
this._menuMouseLeave = null;
|
|
}
|
|
|
|
if (this._windowTouchStart) {
|
|
if (layoutMenu) {
|
|
window.addEventListener(
|
|
"touchstart",
|
|
this._windowTouchStart,
|
|
true,
|
|
);
|
|
}
|
|
this._windowTouchStart = null;
|
|
}
|
|
|
|
this._setMenuHoverState(false);
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Methods
|
|
|
|
scrollToActive(animate = false) {
|
|
this._scrollToActive(animate);
|
|
},
|
|
|
|
swipeIn(el, callback) {
|
|
this._swipeIn(el, callback);
|
|
},
|
|
|
|
swipeOut(el, callback) {
|
|
this._swipeOut(el, callback);
|
|
},
|
|
|
|
overlayTap(el, callback) {
|
|
this._overlayTap(el, callback);
|
|
},
|
|
|
|
scrollPageTo(to, duration = 500) {
|
|
// t = current time
|
|
// b = start value
|
|
// c = change in value
|
|
// d = duration
|
|
const easeInOutQuad = (t, b, c, d) => {
|
|
t /= d / 2;
|
|
if (t < 1) return (c / 2) * t * t + b;
|
|
t -= 1;
|
|
return (-c / 2) * (t * (t - 2) - 1) + b;
|
|
};
|
|
|
|
const element = document.scrollingElement;
|
|
|
|
if (typeof to === "string") {
|
|
to = document.querySelector(to);
|
|
}
|
|
if (typeof to !== "number") {
|
|
to = to.getBoundingClientRect().top + element.scrollTop;
|
|
}
|
|
|
|
const start = element.scrollTop;
|
|
const change = to - start;
|
|
const startDate = +new Date();
|
|
// const increment = 20
|
|
|
|
const animateScroll = () => {
|
|
const currentDate = +new Date();
|
|
const currentTime = currentDate - startDate;
|
|
const val = easeInOutQuad(currentTime, start, change, duration);
|
|
element.scrollTop = val;
|
|
if (currentTime < duration) {
|
|
requestAnimationFrame(animateScroll);
|
|
} else {
|
|
element.scrollTop = to;
|
|
}
|
|
};
|
|
animateScroll();
|
|
},
|
|
|
|
// ---
|
|
// Collapse / expand layout
|
|
setCollapsed(collapsed = requiredParam("collapsed"), animate = true) {
|
|
const layoutMenu = this.getLayoutMenu();
|
|
|
|
if (!layoutMenu) return;
|
|
|
|
this._unbindLayoutAnimationEndEvent();
|
|
|
|
if (animate && this._supportsTransitionEnd()) {
|
|
this._addClass("layout-transitioning");
|
|
if (collapsed) this._setMenuHoverState(false);
|
|
|
|
this._bindLayoutAnimationEndEvent(
|
|
() => {
|
|
// Collapse / Expand
|
|
this._setCollapsed(collapsed);
|
|
},
|
|
() => {
|
|
this._removeClass("layout-transitioning");
|
|
this._triggerWindowEvent("resize");
|
|
this._triggerEvent("toggle");
|
|
this._setMenuHoverState(false);
|
|
},
|
|
);
|
|
} else {
|
|
this._addClass("layout-no-transition");
|
|
if (collapsed) this._setMenuHoverState(false);
|
|
|
|
// Collapse / Expand
|
|
this._setCollapsed(collapsed);
|
|
|
|
setTimeout(() => {
|
|
this._removeClass("layout-no-transition");
|
|
this._triggerWindowEvent("resize");
|
|
this._triggerEvent("toggle");
|
|
this._setMenuHoverState(false);
|
|
}, 1);
|
|
}
|
|
},
|
|
|
|
// ---
|
|
// Toggle layout
|
|
toggleCollapsed(animate = true) {
|
|
this.setCollapsed(!this.isCollapsed(), animate);
|
|
},
|
|
|
|
// ---
|
|
// Set layout positioning
|
|
setPosition(
|
|
fixed = requiredParam("fixed"),
|
|
offcanvas = requiredParam("offcanvas"),
|
|
) {
|
|
this._removeClass(
|
|
"layout-menu-offcanvas layout-menu-fixed layout-menu-fixed-offcanvas",
|
|
);
|
|
|
|
if (!fixed && offcanvas) {
|
|
this._addClass("layout-menu-offcanvas");
|
|
} else if (fixed && !offcanvas) {
|
|
this._addClass("layout-menu-fixed");
|
|
this._redrawLayoutMenu();
|
|
} else if (fixed && offcanvas) {
|
|
this._addClass("layout-menu-fixed-offcanvas");
|
|
this._redrawLayoutMenu();
|
|
}
|
|
|
|
this.update();
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Getters
|
|
|
|
getLayoutMenu() {
|
|
return document.querySelector(".layout-menu");
|
|
},
|
|
|
|
getMenu() {
|
|
const layoutMenu = this.getLayoutMenu();
|
|
|
|
if (!layoutMenu) return null;
|
|
|
|
return !this._hasClass("menu", layoutMenu)
|
|
? layoutMenu.querySelector(".menu")
|
|
: layoutMenu;
|
|
},
|
|
|
|
getLayoutNavbar() {
|
|
return document.querySelector(".layout-navbar");
|
|
},
|
|
|
|
getLayoutFooter() {
|
|
return document.querySelector(".content-footer");
|
|
},
|
|
|
|
getLayoutContainer() {
|
|
return document.querySelector(".layout-page");
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Setters
|
|
|
|
setNavbarFixed(fixed = requiredParam("fixed")) {
|
|
this[fixed ? "_addClass" : "_removeClass"]("layout-navbar-fixed");
|
|
this.update();
|
|
},
|
|
|
|
setNavbar(type) {
|
|
if (type === "sticky") {
|
|
this._addClass("layout-navbar-fixed");
|
|
this._removeClass("layout-navbar-hidden");
|
|
} else if (type === "hidden") {
|
|
this._addClass("layout-navbar-hidden");
|
|
this._removeClass("layout-navbar-fixed");
|
|
} else {
|
|
this._removeClass("layout-navbar-hidden");
|
|
this._removeClass("layout-navbar-fixed");
|
|
}
|
|
this.update();
|
|
},
|
|
|
|
setFooterFixed(fixed = requiredParam("fixed")) {
|
|
this[fixed ? "_addClass" : "_removeClass"]("layout-footer-fixed");
|
|
this.update();
|
|
},
|
|
|
|
setContentLayout(contentLayout = requiredParam("contentLayout")) {
|
|
setTimeout(() => {
|
|
const contentArea = document.querySelector(
|
|
".content-wrapper > div",
|
|
); // For content area
|
|
const navbarArea = document.querySelector(".layout-navbar"); // For navbar area for vertical menu
|
|
const navbarAreaHorizontal = document.querySelector(
|
|
".layout-navbar > div",
|
|
); // For navbar area for horizontal
|
|
const navbarSearchInputWrapper = document.querySelector(
|
|
".layout-navbar .search-input-wrapper",
|
|
); // For navbar search input wrapper
|
|
const navbarSearchInput = document.querySelector(
|
|
".layout-navbar .search-input-wrapper .search-input",
|
|
); // For navbar search input
|
|
const footerArea = document.querySelector(".content-footer > div"); // For footer area
|
|
const containerFluid = [].slice.call(
|
|
document.querySelectorAll(".container-fluid"),
|
|
); // To get container-fluid
|
|
const containerXxl = [].slice.call(
|
|
document.querySelectorAll(".container-xxl"),
|
|
); // To get container-xxl
|
|
const verticalMenu = document.querySelector(".menu-vertical");
|
|
let horizontalMenu = false; // For horizontal menu
|
|
let horizontalMenuArea; // For horizontal menu area
|
|
// Condition to check if layout is horizontal menu
|
|
if (
|
|
document.querySelector(
|
|
".content-wrapper > .menu-horizontal > div",
|
|
)
|
|
) {
|
|
horizontalMenu = true;
|
|
horizontalMenuArea = document.querySelector(
|
|
".content-wrapper > .menu-horizontal > div",
|
|
);
|
|
}
|
|
// If compact mode layout
|
|
if (contentLayout === "compact") {
|
|
// Remove container fluid class from content area, navbar area and footer area
|
|
if (
|
|
containerFluid.some((el) =>
|
|
[contentArea, footerArea].includes(el),
|
|
)
|
|
) {
|
|
this._removeClass("container-fluid", [
|
|
contentArea,
|
|
footerArea,
|
|
]);
|
|
this._addClass("container-xxl", [contentArea, footerArea]);
|
|
}
|
|
// Navbar search input container condition is separated because it is not in starter kit
|
|
if (navbarSearchInput) {
|
|
this._removeClass("container-fluid", [navbarSearchInput]);
|
|
this._addClass("container-xxl", [navbarSearchInput]);
|
|
}
|
|
// Remove container fluid class from navbar area in vertical menu
|
|
if (verticalMenu) {
|
|
if (
|
|
containerFluid.some((el) => [navbarArea].includes(el))
|
|
) {
|
|
this._removeClass("container-fluid", [navbarArea]);
|
|
this._addClass("container-xxl", [navbarArea]);
|
|
}
|
|
}
|
|
// For horizontal menu only
|
|
if (horizontalMenu) {
|
|
this._removeClass("container-fluid", horizontalMenuArea);
|
|
this._addClass("container-xxl", horizontalMenuArea);
|
|
// For horizontal navbar only
|
|
if (navbarAreaHorizontal) {
|
|
this._removeClass(
|
|
"container-fluid",
|
|
navbarAreaHorizontal,
|
|
);
|
|
this._addClass("container-xxl", navbarAreaHorizontal);
|
|
}
|
|
// Navbar search input container condition is separated because it is not in starter kit
|
|
if (navbarSearchInputWrapper) {
|
|
this._removeClass(
|
|
"container-fluid",
|
|
navbarSearchInputWrapper,
|
|
);
|
|
this._addClass(
|
|
"container-xxl",
|
|
navbarSearchInputWrapper,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// If wide mode layout
|
|
|
|
// Remove container xxl class from content area, navbar area and footer area
|
|
if (
|
|
containerXxl.some((el) =>
|
|
[contentArea, footerArea].includes(el),
|
|
)
|
|
) {
|
|
this._removeClass("container-xxl", [
|
|
contentArea,
|
|
footerArea,
|
|
]);
|
|
this._addClass("container-fluid", [
|
|
contentArea,
|
|
footerArea,
|
|
]);
|
|
}
|
|
// Navbar search input container condition is separated because it is not in starter kit
|
|
if (navbarSearchInput) {
|
|
this._removeClass("container-xxl", [navbarSearchInput]);
|
|
this._addClass("container-fluid", [navbarSearchInput]);
|
|
}
|
|
// Remove container xxl class from navbar area in vertical menu
|
|
if (verticalMenu) {
|
|
if (containerXxl.some((el) => [navbarArea].includes(el))) {
|
|
this._removeClass("container-xxl", [navbarArea]);
|
|
this._addClass("container-fluid", [navbarArea]);
|
|
}
|
|
}
|
|
// For horizontal menu only
|
|
if (horizontalMenu) {
|
|
this._removeClass("container-xxl", horizontalMenuArea);
|
|
this._addClass("container-fluid", horizontalMenuArea);
|
|
// For horizontal navbar only
|
|
if (navbarAreaHorizontal) {
|
|
this._removeClass(
|
|
"container-xxl",
|
|
navbarAreaHorizontal,
|
|
);
|
|
this._addClass("container-fluid", navbarAreaHorizontal);
|
|
}
|
|
// Navbar search input container condition is separated because it is not in starter kit
|
|
if (navbarSearchInputWrapper) {
|
|
this._removeClass(
|
|
"container-xxl",
|
|
navbarSearchInputWrapper,
|
|
);
|
|
this._addClass(
|
|
"container-fluid",
|
|
navbarSearchInputWrapper,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}, 100);
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Update
|
|
|
|
update() {
|
|
if (
|
|
(this.getLayoutNavbar() &&
|
|
((!this.isSmallScreen() &&
|
|
this.isLayoutNavbarFull() &&
|
|
this.isFixed()) ||
|
|
this.isNavbarFixed())) ||
|
|
(this.getLayoutFooter() && this.isFooterFixed())
|
|
) {
|
|
this._updateInlineStyle(
|
|
this._getNavbarHeight(),
|
|
this._getFooterHeight(),
|
|
);
|
|
}
|
|
|
|
this._bindMenuMouseEvents();
|
|
},
|
|
|
|
setAutoUpdate(enable = requiredParam("enable")) {
|
|
if (enable && !this._autoUpdate) {
|
|
this.on("resize.Helpers:autoUpdate", () => this.update());
|
|
this._autoUpdate = true;
|
|
} else if (!enable && this._autoUpdate) {
|
|
this.off("resize.Helpers:autoUpdate");
|
|
this._autoUpdate = false;
|
|
}
|
|
},
|
|
|
|
// Update custom option based on element
|
|
updateCustomOptionCheck(el) {
|
|
if (el.checked) {
|
|
// If custom option element is radio, remove checked from the siblings (closest `.row`)
|
|
if (el.type === "radio") {
|
|
const customRadioOptionList = [].slice.call(
|
|
el.closest(".row").querySelectorAll(".custom-option"),
|
|
);
|
|
customRadioOptionList.map(function (customRadioOptionEL) {
|
|
customRadioOptionEL
|
|
.closest(".custom-option")
|
|
.classList.remove("checked");
|
|
});
|
|
}
|
|
el.closest(".custom-option").classList.add("checked");
|
|
} else {
|
|
el.closest(".custom-option").classList.remove("checked");
|
|
}
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Tests
|
|
|
|
isRtl() {
|
|
return (
|
|
document.querySelector("body").getAttribute("dir") === "rtl" ||
|
|
document.querySelector("html").getAttribute("dir") === "rtl"
|
|
);
|
|
},
|
|
|
|
isMobileDevice() {
|
|
return (
|
|
typeof window.orientation !== "undefined" ||
|
|
navigator.userAgent.indexOf("IEMobile") !== -1
|
|
);
|
|
},
|
|
|
|
isSmallScreen() {
|
|
return (
|
|
(window.innerWidth ||
|
|
document.documentElement.clientWidth ||
|
|
document.body.clientWidth) < this.LAYOUT_BREAKPOINT
|
|
);
|
|
},
|
|
|
|
isLayoutNavbarFull() {
|
|
return !!document.querySelector(".layout-wrapper.layout-navbar-full");
|
|
},
|
|
|
|
isCollapsed() {
|
|
if (this.isSmallScreen()) {
|
|
return !this._hasClass("layout-menu-expanded");
|
|
}
|
|
return this._hasClass("layout-menu-collapsed");
|
|
},
|
|
|
|
isFixed() {
|
|
return this._hasClass("layout-menu-fixed layout-menu-fixed-offcanvas");
|
|
},
|
|
|
|
isOffcanvas() {
|
|
return this._hasClass(
|
|
"layout-menu-offcanvas layout-menu-fixed-offcanvas",
|
|
);
|
|
},
|
|
|
|
isNavbarFixed() {
|
|
return (
|
|
this._hasClass("layout-navbar-fixed") ||
|
|
(!this.isSmallScreen() &&
|
|
this.isFixed() &&
|
|
this.isLayoutNavbarFull())
|
|
);
|
|
},
|
|
|
|
isFooterFixed() {
|
|
return this._hasClass("layout-footer-fixed");
|
|
},
|
|
|
|
isLightStyle() {
|
|
return document.documentElement.classList.contains("light-style");
|
|
},
|
|
|
|
isDarkStyle() {
|
|
return document.documentElement.classList.contains("dark-style");
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Events
|
|
|
|
on(event = requiredParam("event"), callback = requiredParam("callback")) {
|
|
const [_event] = event.split(".");
|
|
let [, ...namespace] = event.split(".");
|
|
// let [_event, ...namespace] = event.split('.')
|
|
namespace = namespace.join(".") || null;
|
|
|
|
this._listeners.push({ event: _event, namespace, callback });
|
|
},
|
|
|
|
off(event = requiredParam("event")) {
|
|
const [_event] = event.split(".");
|
|
let [, ...namespace] = event.split(".");
|
|
namespace = namespace.join(".") || null;
|
|
|
|
this._listeners
|
|
.filter(
|
|
(listener) =>
|
|
listener.event === _event &&
|
|
listener.namespace === namespace,
|
|
)
|
|
.forEach((listener) =>
|
|
this._listeners.splice(this._listeners.indexOf(listener), 1),
|
|
);
|
|
},
|
|
|
|
// *******************************************************************************
|
|
// * Life cycle
|
|
|
|
init() {
|
|
if (this._initialized) return;
|
|
this._initialized = true;
|
|
|
|
// Initialize `style` element
|
|
this._updateInlineStyle(0);
|
|
|
|
// Bind window resize event
|
|
this._bindWindowResizeEvent();
|
|
|
|
// Bind init event
|
|
this.off("init._Helpers");
|
|
this.on("init._Helpers", () => {
|
|
this.off("resize._Helpers:redrawMenu");
|
|
this.on("resize._Helpers:redrawMenu", () => {
|
|
// eslint-disable-next-line no-unused-expressions
|
|
this.isSmallScreen() &&
|
|
!this.isCollapsed() &&
|
|
this._redrawLayoutMenu();
|
|
});
|
|
|
|
// Force repaint in IE 10
|
|
if (
|
|
typeof document.documentMode === "number" &&
|
|
document.documentMode < 11
|
|
) {
|
|
this.off("resize._Helpers:ie10RepaintBody");
|
|
this.on("resize._Helpers:ie10RepaintBody", () => {
|
|
if (this.isFixed()) return;
|
|
const { scrollTop } = document.documentElement;
|
|
document.body.style.display = "none";
|
|
// document.body.offsetHeight
|
|
document.body.style.display = "block";
|
|
document.documentElement.scrollTop = scrollTop;
|
|
});
|
|
}
|
|
});
|
|
|
|
this._triggerEvent("init");
|
|
},
|
|
|
|
destroy() {
|
|
if (!this._initialized) return;
|
|
this._initialized = false;
|
|
|
|
this._removeClass("layout-transitioning");
|
|
this._removeInlineStyle();
|
|
this._unbindLayoutAnimationEndEvent();
|
|
this._unbindWindowResizeEvent();
|
|
this._unbindMenuMouseEvents();
|
|
this.setAutoUpdate(false);
|
|
|
|
this.off("init._Helpers");
|
|
|
|
// Remove all listeners except `init`
|
|
this._listeners
|
|
.filter((listener) => listener.event !== "init")
|
|
.forEach((listener) =>
|
|
this._listeners.splice(this._listeners.indexOf(listener), 1),
|
|
);
|
|
},
|
|
|
|
// ---
|
|
// Init Password Toggle
|
|
initPasswordToggle() {
|
|
const toggler = document.querySelectorAll(".form-password-toggle i");
|
|
if (typeof toggler !== "undefined" && toggler !== null) {
|
|
toggler.forEach((el) => {
|
|
el.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
const formPasswordToggle = el.closest(
|
|
".form-password-toggle",
|
|
);
|
|
const formPasswordToggleIcon =
|
|
formPasswordToggle.querySelector("i");
|
|
const formPasswordToggleInput =
|
|
formPasswordToggle.querySelector("input");
|
|
|
|
if (
|
|
formPasswordToggleInput.getAttribute("type") === "text"
|
|
) {
|
|
formPasswordToggleInput.setAttribute(
|
|
"type",
|
|
"password",
|
|
);
|
|
formPasswordToggleIcon.classList.replace(
|
|
"ri-eye-line",
|
|
"ri-eye-off-line",
|
|
);
|
|
} else if (
|
|
formPasswordToggleInput.getAttribute("type") ===
|
|
"password"
|
|
) {
|
|
formPasswordToggleInput.setAttribute("type", "text");
|
|
formPasswordToggleIcon.classList.replace(
|
|
"ri-eye-off-line",
|
|
"ri-eye-line",
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
//--
|
|
// Init custom option check
|
|
initCustomOptionCheck() {
|
|
const _this = this;
|
|
|
|
const custopOptionList = [].slice.call(
|
|
document.querySelectorAll(".custom-option .form-check-input"),
|
|
);
|
|
custopOptionList.map(function (customOptionEL) {
|
|
// Update custom options check on page load
|
|
_this.updateCustomOptionCheck(customOptionEL);
|
|
|
|
// Update custom options check on click
|
|
customOptionEL.addEventListener("click", (e) => {
|
|
_this.updateCustomOptionCheck(customOptionEL);
|
|
});
|
|
});
|
|
},
|
|
|
|
// ---
|
|
// Init Speech To Text
|
|
initSpeechToText() {
|
|
const SpeechRecognition =
|
|
window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
const speechToText = document.querySelectorAll(".speech-to-text");
|
|
if (SpeechRecognition !== undefined && SpeechRecognition !== null) {
|
|
if (typeof speechToText !== "undefined" && speechToText !== null) {
|
|
const recognition = new SpeechRecognition();
|
|
const toggler = document.querySelectorAll(".speech-to-text i");
|
|
toggler.forEach((el) => {
|
|
let listening = false;
|
|
el.addEventListener("click", () => {
|
|
el.closest(".input-group")
|
|
.querySelector(".form-control")
|
|
.focus();
|
|
recognition.onspeechstart = () => {
|
|
listening = true;
|
|
};
|
|
if (listening === false) {
|
|
recognition.start();
|
|
}
|
|
recognition.onerror = () => {
|
|
listening = false;
|
|
};
|
|
recognition.onresult = (event) => {
|
|
el
|
|
.closest(".input-group")
|
|
.querySelector(".form-control").value =
|
|
event.results[0][0].transcript;
|
|
};
|
|
recognition.onspeechend = () => {
|
|
listening = false;
|
|
recognition.stop();
|
|
};
|
|
});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// Tabs animation
|
|
navTabsAnimation() {
|
|
// Adding timeout to make it work on firefox
|
|
setTimeout(() => {
|
|
document.querySelectorAll(".nav-tabs").forEach((tab) => {
|
|
let slider = tab.querySelector(".tab-slider");
|
|
if (!slider) {
|
|
const sliderEle = document.createElement("span");
|
|
sliderEle.setAttribute("class", "tab-slider");
|
|
|
|
slider = tab.appendChild(sliderEle);
|
|
}
|
|
const isVertical =
|
|
tab.closest(".nav-align-left") ||
|
|
tab.closest(".nav-align-right");
|
|
const setSlider = (activeTab) => {
|
|
const tabsEl = activeTab.parentElement;
|
|
const tabsRect = tabsEl.getBoundingClientRect();
|
|
const activeTabRect = activeTab.getBoundingClientRect();
|
|
const sliderStart = activeTabRect.x - tabsRect.x;
|
|
const isBottom = tab.closest(".nav-align-bottom");
|
|
if (isVertical) {
|
|
slider.style.top = activeTabRect.y - tabsRect.y + "px";
|
|
slider.style[
|
|
tab.closest(".nav-align-right")
|
|
? "inset-inline-start"
|
|
: "inset-inline-end"
|
|
] = 0;
|
|
slider.style.height = activeTabRect.height + "px";
|
|
} else {
|
|
slider.style.left = sliderStart + "px";
|
|
slider.style.width = activeTabRect.width + "px";
|
|
if (!isBottom) {
|
|
slider.style.bottom = 0;
|
|
}
|
|
}
|
|
};
|
|
// On click
|
|
tab.addEventListener("click", (event) => {
|
|
// To avoid active state for disabled element
|
|
if (event.target.closest(".nav-item .active")) {
|
|
setSlider(event.target.closest(".nav-item"));
|
|
}
|
|
});
|
|
// On Load
|
|
setSlider(
|
|
tab.querySelector(".nav-link.active").closest(".nav-item"),
|
|
);
|
|
});
|
|
}, 50);
|
|
},
|
|
|
|
// ---
|
|
// Init Navbar Dropdown (i.e notification) PerfectScrollbar
|
|
initNavbarDropdownScrollbar() {
|
|
const scrollbarContainer = document.querySelectorAll(
|
|
".navbar-dropdown .scrollable-container",
|
|
);
|
|
const { PerfectScrollbar } = window;
|
|
|
|
if (PerfectScrollbar !== undefined) {
|
|
if (
|
|
typeof scrollbarContainer !== "undefined" &&
|
|
scrollbarContainer !== null
|
|
) {
|
|
scrollbarContainer.forEach((el) => {
|
|
// eslint-disable-next-line no-new
|
|
new PerfectScrollbar(el, {
|
|
wheelPropagation: false,
|
|
suppressScrollX: true,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// Ajax Call Promise
|
|
ajaxCall(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = new XMLHttpRequest();
|
|
req.open("GET", url);
|
|
req.onload = () =>
|
|
req.status === 200
|
|
? resolve(req.response)
|
|
: reject(Error(req.statusText));
|
|
req.onerror = (e) => reject(Error(`Network Error: ${e}`));
|
|
req.send();
|
|
});
|
|
},
|
|
|
|
// ---
|
|
// SidebarToggle (Used in Apps)
|
|
initSidebarToggle() {
|
|
const sidebarToggler = document.querySelectorAll(
|
|
'[data-bs-toggle="sidebar"]',
|
|
);
|
|
|
|
sidebarToggler.forEach((el) => {
|
|
el.addEventListener("click", () => {
|
|
const target = el.getAttribute("data-target");
|
|
const overlay = el.getAttribute("data-overlay");
|
|
const appOverlay = document.querySelectorAll(".app-overlay");
|
|
const targetEl = document.querySelectorAll(target);
|
|
|
|
targetEl.forEach((tel) => {
|
|
tel.classList.toggle("show");
|
|
if (
|
|
typeof overlay !== "undefined" &&
|
|
overlay !== null &&
|
|
overlay !== false &&
|
|
typeof appOverlay !== "undefined"
|
|
) {
|
|
if (tel.classList.contains("show")) {
|
|
appOverlay[0].classList.add("show");
|
|
} else {
|
|
appOverlay[0].classList.remove("show");
|
|
}
|
|
appOverlay[0].addEventListener("click", (e) => {
|
|
e.currentTarget.classList.remove("show");
|
|
tel.classList.remove("show");
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
},
|
|
};
|
|
|
|
// *******************************************************************************
|
|
// * Initialization
|
|
|
|
if (typeof window !== "undefined") {
|
|
Helpers.init();
|
|
|
|
if (Helpers.isMobileDevice() && window.chrome) {
|
|
document.documentElement.classList.add("layout-menu-100vh");
|
|
}
|
|
|
|
// Update layout after page load
|
|
if (document.readyState === "complete") Helpers.update();
|
|
else
|
|
document.addEventListener(
|
|
"DOMContentLoaded",
|
|
function onContentLoaded() {
|
|
Helpers.update();
|
|
document.removeEventListener(
|
|
"DOMContentLoaded",
|
|
onContentLoaded,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ---
|
|
window.Helpers = Helpers;
|
|
export { Helpers };
|