FlexitimeTracker/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/js/knockout.contextmenu.js
2017-04-13 21:02:01 +01:00

362 lines
14 KiB
JavaScript

/* knockout.contextmenu v1.0.0
Nicolás Escalante - nlante@gmail.com
Issues: https://github.com/nescalante/knockout.contextmenu/issues
License: MIT */
(function (undefined) {
"use strict";
// client
if (typeof ko !== undefined + "") {
bindContextMenu(ko);
}
// node
if (typeof module !== undefined + "" && module.exports && typeof require !== undefined + "") {
bindContextMenu(require("knockout"));
}
function bindContextMenu(ko) {
var currentMenu;
var elementMapping = [];
var utils = ko.utils;
var registerEvent = utils.registerEventHandler;
var isObservable = ko.isObservable;
registerEvent(document, "click", function (event) {
var button = event.which || event.button;
if (!event.defaultPrevented && button < 2) {
hideCurrentMenu();
}
});
utils.contextMenu = {
getMenuFor: function (element, event) {
var result = getMapping(element);
if (result) {
return result.get(event);
}
},
openMenuFor: function (element, event) {
var result = getMapping(element);
if (result) {
return result.open(event);
}
},
};
ko.bindingHandlers.contextMenu = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var eventsToHandle = valueAccessor() || {};
var allBindings = allBindingsAccessor();
var defaultClass = allBindings.contextMenuClass || "context-menu";
var activeElement;
// bind on click? bind on context click?
if (allBindings.bindMenuOnClick) {
registerEvent(element, "click", openMenu);
}
if (allBindings.bindMenuOnContextMenu === undefined || allBindings.bindMenuOnContextMenu) {
registerEvent(element, "contextmenu", openMenu);
}
elementMapping.push({
element: element,
get: function () {
return activeElement;
},
open: openMenu,
hide: function () {
if (activeElement) {
activeElement.hide();
}
}
});
function mouseX(evt) {
if (evt.pageX) {
return evt.pageX;
} else if (evt.clientX) {
return evt.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
} else {
return null;
}
}
function mouseY(evt) {
if (evt.pageY) {
return evt.pageY;
} else if (evt.clientY) {
return evt.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
} else {
return null;
}
}
function openMenu(event) {
activeElement = getMenu(event);
var menuElement = activeElement.element;
hideCurrentMenu();
if (menuElement) {
// make visibility hidden, then add to DOM so that we can get the height/width of the menu
menuElement.style.visibility = "hidden";
(document.body || document).appendChild(menuElement);
// set location
if (event) {
var bottomOfViewport = window.innerHeight + window.pageYOffset;
var rightOfViewport = window.innerWidth + window.pageXOffset;
if (mouseY(event) + menuElement.offsetHeight > bottomOfViewport) {
menuElement.style.top = 1 * (bottomOfViewport - menuElement.offsetHeight - 10) + "px";
} else {
menuElement.style.top = mouseY(event) + "px";
}
if (mouseX(event) + menuElement.offsetWidth > rightOfViewport) {
menuElement.style.left = 1 * (rightOfViewport - menuElement.offsetWidth - 10) + "px";
} else {
menuElement.style.left = mouseX(event) + "px";
}
event.preventDefault();
event.stopPropagation();
} else {
menuElement.style.top = (element.offsetTop + element.offsetHeight) + "px";
menuElement.style.left = (element.offsetLeft + element.offsetWidth) + "px";
}
// now set to visible
menuElement.style.visibility = "";
}
// replace current menu with the recently created
currentMenu = menuElement;
return activeElement;
}
function getMenu(event) {
var menu;
var hasChecks = false;
var elements = [];
var actions = [];
var items = [];
var props = Object.keys(
ko.isObservable(eventsToHandle) ?
eventsToHandle() :
eventsToHandle
);
props.forEach(function (eventNameOutsideClosure) {
pushItem(eventNameOutsideClosure);
});
if (elements.length) {
menu = document.createElement("div");
menu.className = defaultClass;
// you may need padding to menus that has checks
menu.innerHTML = "<ul class=\"" + (hasChecks ? "has-checks" : "") + '">' +
elements.join("") +
"</ul>";
// map items to actions
elements.forEach(function (item, index) {
registerEvent(menu.children[0].children[index], "click", function (event) {
var result = actions[index](viewModel, event);
if (!result && event) {
event.preventDefault();
}
});
});
}
return {
element: menu,
items: items,
open: openMenu,
hide: function () {
if (menu && menu.parentNode) {
menu.parentNode.removeChild(menu);
}
currentMenu = null;
},
};
function pushItem(eventName) {
var item = getMenuProperties(eventName);
var classes = [];
var id = "";
var liHtml;
if (item.isVisible) {
hasChecks = hasChecks || item.isBoolean;
if (item.id) {
id = item.id;
}
// set css classes
if (item.isChecked) {
classes.push("checked");
}
if (item.isDisabled) {
classes.push("disabled");
}
if (item.isSeparator) {
classes.push("separator");
}
if (item.url) {
classes.push("with-url");
}
liHtml = "<li " + (id ? ('id="' + id + '" ') : "") +
' class="' + classes.join(" ") + '">' +
item.html +
"</li>";
elements.push(liHtml);
actions.push(item.action);
}
items.push(item);
}
}
function getMenuProperties(eventName) {
var text = "";
var html = "";
var currentEvent = ko.isObservable(eventsToHandle) ?
eventsToHandle()[eventName] :
eventsToHandle[eventName];
var item = currentEvent || {};
var id = item.id;
var url = (isObservable(item.url) ? item.url() : item.url);
var isVisible = item.visible === undefined || item.visible === null ||
(isObservable(item.visible) && item.visible()) ||
(!isObservable(item.visible) && !!item.visible);
var isChecked = false;
var isEnabled = !item.disabled ||
(isObservable(item.disabled) && !item.disabled()) ||
(isObservable(item.enabled) && item.enabled()) ||
(!isObservable(item.enabled) && !!item.enabled);
var isBoolean = false;
var isDisabled = !isEnabled;
var isSeparator = !!currentEvent.separator;
if (!isSeparator) {
text = isObservable(item.text) ? item.text() : item.text;
if (!text) {
text = eventName;
}
if (url) {
html = '<a href="' + url + '">' + text + "</a>";
} else {
html = text;
}
}
if ((isObservable(item) && typeof item() === "boolean") ||
(isObservable(item.action) && typeof item.action() === "boolean")) {
isBoolean = true;
if ((item.action && item.action()) ||
(typeof item === "function" && item())) {
isChecked = true;
}
}
return {
html: html,
text: text,
url: url,
id: id,
isVisible: isVisible,
isChecked: isChecked,
isEnabled: isEnabled,
isDisabled: isDisabled,
isBoolean: isBoolean,
isSeparator: isSeparator,
action: action
};
function action(viewModel, event) {
var error = eventName + " option must have an action or an url.";
if (isDisabled) {
return false;
}
// check if option is a boolean
if (isObservable(item) && typeof item() === "boolean") {
item(!item());
}
// is an object? well, lets check it properties
else if (typeof item === "object") {
// check if has an action or if its a separator
if (!item.action && !url && !isSeparator) {
throw error;
}
// evaluate action
else if (item.action) {
if (isObservable(item.action) && typeof item.action() === "boolean") {
item.action(!item.action());
} else {
item.action(viewModel, event);
}
}
}
// its not an observable, should be a function
else if (typeof item === "function") {
item(viewModel, event);
}
// nothing to do with this
else {
throw error;
}
return true;
}
}
}
};
function hideCurrentMenu() {
if (currentMenu && currentMenu.parentNode) {
currentMenu.parentNode.removeChild(currentMenu);
}
currentMenu = null;
}
function getMapping(element) {
var i = 0;
for (; i < elementMapping.length; i++) {
if (elementMapping[i].element === element) {
return elementMapping[i];
}
}
}
}
})();