I have an application that uses a Kendo UI Chart with a legend. When the user clicks on a legend item, the Kendo onLegendItemClick(e) method gets called. However the event that gets passed to this function does not contain the originalEvent, so there is no way to distinguish between right and left clicks.
Here is the relevant API reference: https://docs.telerik.com/kendo-ui/api/javascript/dataviz/ui/chart/events/legenditemclick
I tried adding an event listener and capturing the 'mousedown' event before onLegendItemClick is invoked as shown below. However, this approach will fail on touch screen devices (iPads, tablets, mobile devices, etc).
document.addEventListener("mousedown", saveMouseDown, true);
function saveMouseDown(ev) {
$scope.mouseDownEvent = ev;
}
$scope.$on("$destroy", function () {
document.removeEventListener(saveMouseDown);
});
The application has a separate directive for handling right clicks. Is there a way to prevent Kendo from calling the onLegendItemClick(e) method when a user right clicks the legend item?
Add "click" to the list of saved events1:
document.addEventListener("mousedown click", saveEvent, true);
function saveEvent(ev) {
$scope.savedEvent = ev;
}
$scope.$on("$destroy", function () {
document.removeEventListener(saveEvent);
});
Related question: How to prevent right click from deselecting marker in Kendo-UI
Related
I've got a map with custom overlays (based on https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup).
The custom overlay content includes a link/anchor tag and I would like to allow the user to right click the link and select "Open in new tab", however right clicks are cancelled by the map and I am unable to figure out how to prevent that behaviour.
If you compare the sample of the custom overlay linked above with the default Info Window https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/infowindow-simple you can notice that the custom overlay does NOT show the context menu when right clicking on the "Hello World" text, whereas the Info Window DOES show the context menu. In the dev tools I noticed an event handler on the Info Window that somehow allows the context menu (removing that handler stops the context menu coming up), however as it's in the minified Google maps code I'm not able to make sense of it.
I have tried the following:
google.maps.event.addListener(map, 'rightclick', function (e) {
var event = e.ya;
var element = event.target;
if (element.nodeName === "A") {
event.stopImmediatePropagation();
event.stopPropagation();
return true;
}
});
Code is executed, but there's still no context menu. Instead it breaks something on the map as the map then moves with the mouse as if I still had the mouse down (looks like I prevented the mouseup handler).
I also tried to set preventMapHitsFrom on the custom overlay (https://developers.google.com/maps/documentation/javascript/reference/overlay-view#OverlayView.preventMapHitsAndGesturesFrom), which made the above not fire any more, but still no context menu.
I was also able to attach an event handler myself (excuse the jQuery):
$(document).on("contextmenu", ".map-popup__link", function (e) {
e.stopImmediatePropagation();
return true;
});
But again not sure how to prevent the event being cancelled. I also tried to trigger a new event on the same element, but that just creates a loop (obviously) without solving the problem.
Based on https://stackoverflow.com/a/7414594/1397352
I have modified the Popup.prototype.onAdd function to
Popup.prototype.onAdd = function () {
this.getPanes().floatPane.appendChild(this.containerDiv);
this.getPanes().overlayMouseTarget.appendChild(this.containerDiv);
// set this as locally scoped var so event does not get confused
var me = this;
// Add a listener - we'll accept clicks anywhere on this div, but you may want
// to validate the click i.e. verify it occurred in some portion of your overlay.
google.maps.event.addDomListener(this.containerDiv, 'contextmenu', function () {
google.maps.event.trigger(me, 'contextmenu');
});
};
Breakpoint in event handler gets hit, but again no context menu shows.
Has anyone got this to work with custom overlays?
Thanks to MrUpsidown's comment I was able to find a solution in the (archived) infobox library: https://github.com/googlemaps/v3-utility-library/blob/master/archive/infobox/src/infobox.js#L231
It looks like I was close in my first attempt, but should have set event.cancelBubble = true;
Final solution:
Popup.prototype.onAdd = function () {
this.getPanes().floatPane.appendChild(this.containerDiv);
// This handler allows right click events on anchor tags within the popup
var allowAnchorRightClicksHandler = function (e) {
if (e.target.nodeName === "A") {
e.cancelBubble = true;
if (e.stopPropagation) {
e.stopPropagation();
}
}
};
this.contextListener_ = google.maps.event.addDomListener(this.containerDiv, "contextmenu", allowAnchorRightClicksHandler);
};
Popup.prototype.onRemove = function () {
if (this.contextListener_) {
google.maps.event.removeListener(this.contextListener_);
this.contextListener_ = null;
}
if (this.containerDiv.parentElement) {
this.containerDiv.parentElement.removeChild(this.containerDiv);
}
};
see https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup for remainder of Popup code
I have a kendo grid with its dataSource, the grid has an editor dialogue with save button. I need to prevent the save button being double clicked. The onsave functions fire when the save button is clicked. I have a requestEnd event that fires when the save is to be re-enabled.
The problem: onSave1 looks to fail 1 time in 100 . It's based on adding an additional click handler, invoking preventDefault(). Is it fundamentally flawed?
Is onSave2 any better?
onSave1: function (e) {
$(event.srcElement)
.addClass("k-state-disabled")
.bind("click", disable = function (e) { e.preventDefault(); return false; })
this.dataSource.one("requestEnd", function () {
$("[data-role=window] .k-grid-update")
.off("click", disable)
.removeClass("k-state-disabled");
})
}
onSave2: function (e) {
$(event.srcElement)
.removeClass(".k-grid-update")
.addClass("k-state-disabled")
.addClass("disabledMarker");
this.dataSource.one("requestEnd", function () {
$("[data-role=window] .disabledMarker")
.addClass(".k-grid-update")
.removeClass("k-state-disabled")
.removeClass("disabledMarker");
})
}
First Jquery bind has been deprecated since version 3.0 so I would recommend not using it anymore. http://api.jquery.com/bind/
You do not need an onClick event or in your case bind because onSave is already being called during the click. So simply disable the button onSave. Second you should use complete or whatever kendo grid uses for when the save event is finished instead or requestEnd. Code listed below.
onSave: {$(event.srcElement)addClass("k-state-disabled")}, complete:{ $(event.srcElement)removeClass("k-state-disabled")};
I am using AmCharts and would like to know how to stop the clickGraphItem event propagating to a clickGraph event.
The reason I want to stop it is this:
When I click on a bubble in an XY chart I use Mustache to render a little form under the graph with info contained in the dataContext of the clicked item.
As there are many bubbles on the plot I would like to be able to highlight the selected item on the bubble chart after it is clicked so as to let the user know what point the info is being edited for.
However if the user clicks in empty space on the graph (the clickGraph event on it's own) I want to fire event.chart.validateData(); so that the clicked bubble goes back to original colors.
The problem I am encountering is that when I click on the item I get first the clickGraphItem event firing but that also triggers the clickGraph event.
I have tried to put event.event.stopPropagation() at the end of the "Render Mustache and change fill of bubble" method but this does nothing.
The function I am using to handle both events is below:
function showPointForm(event) {
var ev = event
if (ev.item && ev.item.dataContext.data != undefined) {
var bullet = ev.item.bulletGraphics.node;
bullet.setAttribute("stroke",colors.info);
bullet.setAttribute("fill",colors.warning);
$(document).find('#form-area').html(
Mustache.render($(document).find('#form-template').html(),{
data : ev.item.dataContext.data
})
)
}else{
$(document).find('#form-area').html('')
event.chart.validateData();
}
ev.event.stopPropagation()
}
var chart = AmCharts.makeChart('div',configObject)
chart.addListener("clickGraphItem",showPointForm);
chart.addListener("clickGraph",showPointForm);
The idea here is that if the event is from an item, it will change the fill of the bullet and the stroke, and then render a form with some data prop from the item. If the event does not have an item and data, the rendered form get whiped and the chart is redrawn to original styles.
The stopping of event propagation does nothing here to stop the clickGraph event being fired
Any ideas on what I am doing wrong here?
Thanks
SOLUTION:
It turns out that what I was attempting to achieve isn't supported through the standard AmCharts API. The clickGraph event will not fire in empty space on a bubble/XY chart. Instead I attached an event in to the click event of the .amcharts-plot-area.
function showPointForm(event) {
var ev = event
if (ev.item && ev.item.dataContext.data != undefined) {
$(document).find('.highlight').each(function(){
$(this).attr('class', 'amcharts-graph-bullet');
})
var bullet = ev.item.bulletGraphics.node;
bullet.setAttribute("class",'highlight amcharts-graph-bullet');
$(document).find('#form-area').html(
Mustache.render($(document).find('#form-template').html(),{
data : ev.item.dataContext.data
})
)
}else{
$(document).find('#form-area').html('')
event.chart.validateData();
}
}
function reset(){
$(document).find('#form-area').html('')
timelineChart.validateData();
}
var chart = AmCharts.makeChart('div',configObject)
chart.addListener("clickGraphItem",showPointForm);
$(document).on("click",'.amcharts-plot-area',reset);
So now when I click on a bubble, all bubbles that were previously styled with the highlight class have that class removed. Then the clicked bubble has it added.
When there is a click event in the amcharts-plot-area however, the reset() function is called which removes all the highlighted classes again.
This solves my issue but as stated below the stopping of event propagation is not possible.
There isn't a way to prevent chart event propagation. The chart event name is passed in the type property, so you can use that to determine which flow you want. It's not pretty, but that's all you can do:
function showPointForm(event) {
if (event.type == "clickGraphItem") {
// logic for clickGraphItem only
}
else {
// logic for clickGraph
}
}
You could also set a custom flag inside the chart when the clickGraphItem is fired to indicate that it was just triggered so you know not to perform any additional logic if it was bubbled, for example:
function clickGraphHandler(event) {
if (event.type == "clickGraphItem") {
event.chart.clickGraphItemFired = true;
console.log('clickGraphItem')
} else {
if (event.chart.clickGraphItemFired) {
event.chart.clickGraphItemFired = false;
console.log('clickGraph - bubbled from clickGraphItem');
} else {
console.log('clickGraph - not bubbled from clickGraphItem')
}
}
}
Demo
I have hooked up a simple long touch function that after 500ms uses the "open" API command to open the context menu. The menu opens. However, on "touchend" the menu disappears. It only stays if I touchmove over the context menu before "touchend". Is there a way to prevent this sort of behaviour? From the source code, only a "touchstart" in a different part of the dom should trigger a close event.
Code is below, in case useful. Not that a delegate of tr is required by my context menu - to explain the targetTr variable use below.
var mobDevice_onLongTouch,
mobDevice_touchTimer,
mobDevice_longPressDuration = 500; //length of time we want the user to touch before we do something
//handle long press on the datatable
var touchArea = document.querySelector("#table");
touchArea.addEventListener("touchstart", touchAreaTouchStart, false);
touchArea.addEventListener("touchend", touchAreaTouchEnd, false);
function touchAreaTouchStart(e) {
var targetTr = $(e.target).closest('tr');
mobDevice_touchTimer = setTimeout(function () { touchArea_onLongTouch(targetTr) }, mobDevice_longPressDuration)
};
function touchAreaTouchEnd(e) {
if (mobDevice_touchTimer) {
clearTimeout(mobDevice_touchTimer) //reset the clock
}
};
function touchArea_onLongTouch(target) {
$('#table').contextmenu('open', target);
};
I solved this. ContextMenu was working fine, but the DOM control I was touching on registered a change event (to highlight a table row) on touchend. So the context menu popped up during touch and hold, then got cleared by a DOM change at touchend.
The solution was to manually add the highlight table row event to touchstart and preventDefault on touchend (when the touch target was inside the table)
I've created a fiddle to reproduce the problem.
https://jsfiddle.net/rvwp47Lz/23/
callback: function (key, option) {
console.log("You clicked the test button", this);
// Need the iframe contents to regain focus so the mouse events get caught
setTimeout(function () {
$iframe[0].contentWindow.focus();
}, 100);
}
Basically, what I want to happen is the mouse move events to be caught after closing the context menu.
I can call focus on the iFrame's body or document but it doesn't seem to have any effect.
After you right click one of the items within the iframe and select an item, the mousemove event on the iframes body is no longer called (you can also notice that the hover CSS effect on the items are no longer working).
Ideas?
After some debugging and playing around with jQuery.contextMenu's code it seems the issue actually comes from the itemClick function. I added comments to the code and will add an issue to their github for a possible fix (unless there's some reason they're disabling default here)
// contextMenu item click
itemClick: function (e) {
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot,
key = data.contextMenuKey,
callback;
// abort if the key is unknown or disabled or is a menu
if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-submenu, .context-menu-separator, .' + root.classNames.notSelectable)) {
return;
}
// This line is causing the issue since it's preventing the default actions which puts
// mouse events back into place. Chrome must disable mouse move events when the contextmenu event
// gets triggered to improve performance.
//e.preventDefault();
e.stopImmediatePropagation();