thelounge/client/js/contextMenu.js
2019-07-19 11:27:40 +01:00

169 lines
4 KiB
JavaScript

"use strict";
const $ = require("jquery");
const Mousetrap = require("mousetrap");
const templates = require("../views");
const contextMenuContainer = $("#context-menu-container");
module.exports = class ContextMenu {
constructor(contextMenuItems, contextMenuActions, selectedElement, event) {
this.previousActiveElement = document.activeElement;
this.contextMenuItems = contextMenuItems;
this.contextMenuActions = contextMenuActions;
this.selectedElement = selectedElement;
this.event = event;
}
show() {
const contextMenu = showContextMenu(
this.contextMenuItems,
this.selectedElement,
this.event
);
this.bindEvents(contextMenu);
return false;
}
hide() {
contextMenuContainer
.hide()
.empty()
.off(".contextMenu");
Mousetrap.unbind("escape");
}
bindEvents(contextMenu) {
const contextMenuActions = this.contextMenuActions;
contextMenuActions.execute = (id, ...args) =>
contextMenuActions[id] && contextMenuActions[id](...args);
const clickItem = (item) => {
const itemData = item.attr("data-data");
const contextAction = item.attr("data-action");
this.hide();
contextMenuActions.execute(contextAction, itemData);
};
contextMenu.on("click", ".context-menu-item", function() {
clickItem($(this));
});
const trap = Mousetrap(contextMenu.get(0));
trap.bind(["up", "down"], (e, key) => {
const items = contextMenu.find(".context-menu-item");
let index = items.toArray().findIndex((item) => $(item).is(":focus"));
if (key === "down") {
index = (index + 1) % items.length;
} else {
index = Math.max(index, 0) - 1;
}
items.eq(index).trigger("focus");
});
trap.bind("enter", () => {
const item = contextMenu.find(".context-menu-item:focus");
if (item.length) {
clickItem(item);
}
return false;
});
// Hide context menu when clicking or right clicking outside of it
contextMenuContainer.on("click.contextMenu contextmenu.contextMenu", (e) => {
// Do not close the menu when clicking inside of the context menu (e.g. on a divider)
if ($(e.target).prop("id") === "context-menu") {
return;
}
this.hide();
return false;
});
// Hide the context menu when pressing escape within the context menu container
Mousetrap.bind("escape", () => {
this.hide();
// Return focus to the previously focused element
$(this.previousActiveElement).trigger("focus");
return false;
});
}
};
function showContextMenu(contextMenuItems, selectedElement, event) {
const target = $(event.currentTarget);
const contextMenu = $("<ul>", {
id: "context-menu",
role: "menu",
});
for (const item of contextMenuItems) {
if (item.check(target)) {
if (item.divider) {
contextMenu.append(templates.contextmenu_divider());
} else {
contextMenu.append(
templates.contextmenu_item({
class:
typeof item.className === "function"
? item.className(target)
: item.className,
action: item.actionId,
text:
typeof item.displayName === "function"
? item.displayName(target)
: item.displayName,
data: typeof item.data === "function" ? item.data(target) : item.data,
})
);
}
}
}
contextMenuContainer.html(contextMenu).show();
contextMenu
.css(positionContextMenu(contextMenu, selectedElement, event))
.find(".context-menu-item:first-child")
.trigger("focus");
return contextMenu;
}
function positionContextMenu(contextMenu, selectedElement, e) {
let offset;
const menuWidth = contextMenu.outerWidth();
const menuHeight = contextMenu.outerHeight();
if (selectedElement.hasClass("menu")) {
offset = selectedElement.offset();
offset.left -= menuWidth - selectedElement.outerWidth();
offset.top += selectedElement.outerHeight();
return offset;
}
offset = {left: e.pageX, top: e.pageY};
if (window.innerWidth - offset.left < menuWidth) {
offset.left = window.innerWidth - menuWidth;
}
if (window.innerHeight - offset.top < menuHeight) {
offset.top = window.innerHeight - menuHeight;
}
return offset;
}