I'm trying to add custom contextmenus to some of the elements in a page and did it like this in a view which contains a table. The contextmenu is attached to the table header with the name "S":
list.view = function(ctrl, args) {
var contextMenuSelection =
m("div", {
id : "context-menu-bkg-01",
class : ctrl.isContextMenuVisible() === ctrl.contextMenuId ? "context-menu" : "hide",
style : ctrl.contextMenuPosition(),
}, [ m("#select.menu-item.allow-hover", {
onclick : function(e) {
args.model.callMenu({
cmdName : this.id
})
}
}, "Select all"), m("#deselect.menu-item.allow-hover", {
onclick : function(e) {
args.model.callMenu({
cmdName : this.id
})
}
}, "Deselect all"), m("#invertSel.menu-item.allow-hover", {
onclick : function(e) {
args.model.callMenu({
cmdName : this.id
})
}
}, "Invert selection") ]);
var table = m("table", [
m("tr", [ m("th", {
id : ctrl.contextMenuId,
config : ctrl.configContextMenu(),
oncontextmenu : function(e) {
console.log("2021 contextMenuShow")
e.preventDefault()
var coords = utils.getCoords(e)
var pos = {}
pos.left = coords[0] + "px"
pos.top = coords[1] + "px"
ctrl.contextMenuPosition(pos)
var id = e.currentTarget.id
ctrl.isContextMenuVisible(id)
}
}, "S"),
m("th[data-sort-by=pName]", "Name"),
m("th[data-sort-by=pSize]", "Size"),
m("th[data-sort-by=pPath]", "Path"),
m("th[data-sort-by=pMedia]", "Media") ]),
ctrl.items().map(
function(item, idx) {
return m("tr", ctrl.initRow(item, idx), {
key : item.guid
}, [ m("input[type=checkbox]", {
id : item.guid,
checked : ctrl.isSelected(item.guid)
}),
m("td", item.pName),
m("td", utils.numberWithDots(item.pSize)),
m("td", item.pPath), m("td", item.pMedia) ])
}) ])
return m("div", [contextMenuSelection, table])
}
To get the contextmenu closed after the escape key is hit or the user clicks somewhere in the page with the mouse, this function is attached to the element via the config attribute:
ctrl.configContextMenu = function() {
return function(element, isInitialized, context) {
console.log("1220 isInitialized=" + isInitialized)
if(!isInitialized) {
console.log("1225")
document.addEventListener('click', function() {
m.startComputation()
ctrl.contextMenuVisibility(0)
m.endComputation()
}, false);
document.addEventListener('keydown', function() {
console.log("1235")
m.startComputation()
ctrl.contextMenuVisibility(0)
m.endComputation()
}, false)
}
};
};
The behavior is unpredictable: If the table is empty, the custom contextmenu shows up and is hidden as expected. If the table is populated, the default contextmenu is shown instead.
Using a debugger and some breakpoints didn't get me some information what is happening except that sometimes running the debugger step by step brought up the custom contextmenu. So I assume it has something to do with a race condition between the eventListener and Mithrils draw system.
Has anybody experience with custom contextmenus and could provide me some examples?
Thanks a lot, Stefan
EDIT: As to Barneys comment regarding m.startComputation() I changed the code to the following:
var table = m("table", ctrl.sorts(ctrl.items()), [
m("tr", [ m("th", {
oncontextmenu : ctrl.onContextMenu(ctrl.contextMenuId, "context-menu context-menu-bkg", "hide" )
}, "S"), m("th[data-sort-by=pName]", "Name"),
m("th[data-sort-by=pSize]", "Size"),
m("th[data-sort-by=pPath]", "Path"),
m("th[data-sort-by=pMedia]", "Media") ]),
ctrl.items().map(function(item, idx) {
return m("tr", ctrl.initRow(item, idx), {
key : item.guid
}, [ m("input[type=checkbox]", {
id : item.guid,
checked : ctrl.isSelected(item.guid),
onclick : function(e) {
console.log("1000")
ctrl.setSelected(this.id);
}
}), m("td", item.pName), m("td", utils.numberWithDots(item.pSize)),
m("td", item.pPath), m("td", item.pMedia) ])
}) ])
And the implementing function onContextMenu:
// open a context menu
// @elementId the id of the element which resembles the context menu.
// Usually this is a div.
// @classShow the name of the css class for showing the menu
// @classHide the name of the css class for hiding the menu
vmc.onContextMenu = function(elementId, classShow, classHide) {
var callback = function(e) {
console.log("3010" + this)
var contextmenudiv = document.getElementById(elementId);
contextmenudiv.className = classHide;
document.removeEventListener("click", callback, false);
document.removeEventListener("keydown", callback, false);
}
return function(e) {
console.log("3000" + this)
var contextmenudiv = document.getElementById(elementId);
// Prevent the browser from opening the default context menu
e.preventDefault();
var coords = utils.getCoords(e);
contextmenudiv.style.left = coords[0] + "px";
contextmenudiv.style.top = coords[1] + "px";
// Display it
contextmenudiv.className = classShow;
// When you click somewhere else, hide it
document.addEventListener("click", callback, false);
document.addEventListener("keydown", callback, false);
}
};
Now this works without problems. Barney, if you could be so kind to confirm this as a viable way, I'll post it as answer.
Thanks, Stefan