Template event selector 'not' isn't working - javascript

In this Meteor template code, when the canvas is clicked, it prints out the canvas element to the console but it is expected not to fire the event.
How can it be made so that it fires if the element which is clicked is not a canvas?
Template.myTemp.events({
'click *:not(canvas)': function(e) {
e.stopPropagation();
console.log(e.target);
});

Definitely an interesting problem as the :not selector is supported by Blaze, and works properly with other HTML elements. You might want to open an issue about this in the Blaze repo.
The above being said, there are a few different ways you can work around this. You could add a check in your event handler to make sure you don't do anything with canvas related events:
Template.myTemp.events({
'click *'(event, instance) {
if (event.target.nodeName.toLowerCase() !== 'canvas') {
// Handle non-canvas events ...
}
},
});
Another option involves chaining your event handlers, if you want to be able to filter out canvas events specifically. For example:
Template.myTemp.events({
'click canvas'(event, instance) {
event.stopImmediatePropagation();
// Handle canvas click events only ...
},
'click *'(event, instance) {
// Handle all click events except canvas click events, since they're
// captured and handled above ...
},
});

Related

How to enable touchmove in a mobile element while disabling scrolling

I'm building an app using svg-pencil to allow users to draw images. Normally I would use atrament to enable drawing on canvas, but this project requires the output to be an SVG that I can then analyze at the <path> level.
SVG-pencil is great but is not mobile friendly since it was developed some time ago. Using atrament as a guide, I'm trying to make it responsive to touch events as well as mouse events.
The first step was to just add the touch event listeners to the part of svg-pencil's event registration:
this.listeners = {
mousedown: function (ev) { self._onmousedown(ev) },
mouseup: function (ev) { self._onmouseup(ev) },
mousemove: function (ev) { self._onmousemove(ev) },
// added these three lines
touchstart: function (ev) { self._onmousedown(ev) },
touchend: function (ev) { self._onmouseup(ev) },
touchmove: function (ev) { self._onmousemove(ev) }
};
The trouble is that, on mobile, the default behavior for touchmove is to scroll the page. So I added two event listeners to the <svg> element that the module creates to try and prevent this:
var svg = p.element;
svg.addEventListener('touchmove', function(e) {
e.stopImmediatePropagation();
// e.preventDefault();
});
svg.addEventListener('touchstart', function(e) {
e.stopImmediatePropagation();
// e.preventDefault();
});
This successfully fires the app's mousestart and mouseend events in a mobile emulator or a real phone if you just click the drawable area, and DOES successfully disable scrolling within the svg area if you drag inside it, but the mousemove event registered to touchmove above does not fire more than once. As you can see, I tried preventDefault as well with no luck.
I'm not an expert in event bubbling, but somehow it appears that the stopPropogation call on the svg element is also canceling the event inside svg-pencil.
Here's an example. There's a console log on every event. It works fine in regular browswer, but you can see the problem if you select a mobile emulator in Chrome's devtools or try it in Xcode's simulator.
The full, slightly modified code is here.
I also tried adding the preventDefault inside svg-pencil's event listeners with no luck.

Is it possible to assign prevent default for all the events of an element at once? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
jQuery - How can I bind all events on a DOM element?
Imagine, if we want to make some element completely initeractable.
We could, of course bind a prevent default for a click event as follows:
$('form *').bind('click', function(event) {
event.preventDefault();
});
But that's just one event, and there are many more, like hover, focus, selectstart, and many more.
We could specify them all in one line like 'click focus hover dblclick blur selectstart' but that doesn't make much sense and is not easy to maintain.
So, is it possible to bind an event listener without discriminating for the type of the event? Maybe some native JavaScript listeners allow it?
No such possibility because not all elements support same events and not all events behave in the same way. You always have to explicitly provide a list of events whether defined statically or dynamically by a script that spits out event names.
Even though I linked to a script that creates an array of event names, these are made on one element only. You should of course be generating this with a more complex and slower script that enumerates over all elements in question and adds missing events. Using Javascript objects as associative array for faster searching whether a particular event has been added or not.
A better suggestion
What you're trying to do is likely a highly over-engineered solution. When I'm creating a demo clickable interface that should disable some elements (be it links, buttons or anything else) I rather do it by defining a CSS class that disables an element in question and have a simple script that does disabling afterwards.
You could leverage this even further by also providing which events you'd like to disable on particular element (with default being a click event).
<!-- no events; use defaults -->
No-follow link
<button class="disable">Nothing happens</button>
<!-- provide events -->
No-follow link
<form class="disable" data-events="submit">...</form>
Script
$(function() {
var disable = function(evt) {
evt.preventDefault();
console.log("Prevented on " + evt.target.tagName);
};
$(".disable").each(function() {
var ctx = $(this);
ctx.bind(ctx.data("events") || "click", disable);
});
});
Using smart defaults
Upper example defines one single event default. click event. This is fine and works in majority of cases, but not in all. form elements for instance would always have to define submit event that should be disabled. So. Smart defaults then. We should also consider the fact that list events that need supression is usually short. And if we cover majority of cases using defaults we only have a small overhead on those elements that actually do deviate from defaults.
$(function() {
// click is still default event
// this object defines per element events that aren't just click
var extraDefaults = {
form: "submit"
};
var disable = function(evt) {
evt.preventDefault();
console.log("Prevented on " + evt.target.tagName);
};
$(".disable").each(function() {
var ctx = $(this);
ctx.bind(
// use inline-defined events
ctx.data("events") ||
// use extra defaults if present
extraDefaults[this.tagName.toLower()] ||
// just use default click event
"click",
disable);
});
});
You can bind most jQuery events like this :
$("#elementID").on(Object.keys(jQuery.event.fixHooks).join(" "), function(e) {
e.preventDefault();
});
This will preventDefault on the following events :
click dblclick mousedown mouseup mousemove mouseover mouseout
mouseenter mouseleave keydown keypress keyup contextmenu
FIDDLE
Well after considering all the options, it still does not look convenient for all this event hustling. As it also has to bind the handlers for each event individually the script will hit the performance as well.
I am going to stick with a much simpler solution, just putting a div with transparent bg on top to cover our element.
$('form').css('position','relative').prepend($('<div class="mask" style="position:absolute;z-index:9000;height:100%;width:100%;background-image:url(1px_transparent.png);"></div>'));
Which is going to automatically fill the whole area of the element, alternatively, we can use a half-transparent picture so it will be also understood by a user that this is locked element, and would not cause confusion.
And to unlock we simply remove the .mask div from our element.
EDIT
New Fiddle: http://jsfiddle.net/YAdXk/8/
Actually we can disable tabbing by setting tabindex attribute to -1
.find('input,textarea,select').attr('tabindex','-1');
The updated fiddle prevents from tabbing as well.
EDIT2
OR, we can extend jQuery to use our custom lock() and unlock() functions on any element.
See the last fiddle: http://jsfiddle.net/YAdXk/13/
(function($) {
$.fn.lock= function() {
return this.each(function() {
$(this).css('position','relative').prepend($('<div class="mask" style="position:absolute;z-index:9000;height:100%;width:100%;background-image:url('+transparent_picture+');"></div>')).find('input,textarea,select').attr('tabindex','-1');
});
};
$.fn.unlock= function() {
return this.each(function() {
$(this).find('*').removeAttr('tabindex').filter('.mask').remove();
});
};
})( jQuery )
var all_events = "click blur focus mouse"; //etc...
$('form *').bind(all_events, function(event) {
event.preventDefault();
});
Now is easier to maintain ;)
jQuery defines all shortcut event types here, so you can use that string to store all events for re-use:
var events = "blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu";
$('button').bind(events, function() {
// hey
});
Yes, it is possible, to catch all events of one type at once! But you'll need to specify all the event types explicitly.
Your code example of "form *" is inefficient, and would not catch events on elements that are added after your code executes.
Because of the bubbling effect of javascript events, you can assign a catch all event handler on the most parent element, eigther $("form") or $("body"), and add preventDefault() to that.
Example code:
$("a").on("click", function() {
$("body").append("<p>Clicked...</p>");
});
$("body").on("click", function(e) {
e.preventDefault();
});
with:
<div>
<p>Click on me</p>
</div>​
On JSfiddle: http://jsfiddle.net/erlang/EHeBK/
The concept of catching all events on a parent element, is often referred to as event delegation.

Trigger right-click

I am trying to late-bind context menus to elements, using the ContextMenu plugin. So on the first right-click on those elements, I would like to :
intercept the right-click through a live event on a certain "uncontextmenued" class,
determine if the data('events').contextmenu exists,
if not, attach the context-menu (and change the class to avoid re-throwing this live process),
re-throw the right-click event to show the right-click.
I'm having trouble with the last item. jQuery allows to .click() or to .trigger('click'), which simulate a left-click, but there seems not to be a way to fire a right-click event through trigger.
Or is there?
You can trigger it by
$('#element').trigger({
type: 'mousedown',
which: 3
});
http://api.jquery.com/trigger/#example-5
There is a newer way to do this:
$('#element').triggerHandler('contextmenu');
Documentation can be found here.
Similar to this, but I'm not sure if you may be referring to jQuery UI data, but.
$('#element').mousedown(function(event)
{
if(event.which == 3)
{
if(typeof($(this).data('events')) === 'undefined')
{
$(this).data('events', { somedata: 'hello' });
}
else
{
// "re-throw" right click context menu
}
}
});

How can I stop event propagation with Backbone.js?

Using a Backbone.js View, say I want to include the following events:
events: {
'click a': 'link',
'click': 'openPanel'
}
How can I avoid openPanel to be fired when I click on a link. What I want is to have a clickable box which will trigger an action, but this box can have elements which should trigger other actions, and not the parent action. Think for example Twitter.com, and links in Tweets/right hand panel.
I've been using e.stopImmediatePropagation(); in order to keep the event from propagating. I wish there was a shorter way to do this. I would like return false; but that is due to my familiarity with jQuery
The JQuery preventDefault method would also be a good option.
window.LocationViewLI = Backbone.View.extend({
tagName: "li",
template: _.template('<%= name %>'),
events: {
"click a": "handleClick"
},
handleClick: function(event) {
event.preventDefault();
console.log("LocationViewLI handleClick", this.model.escape("name") );
// debugger;
},
...
Each of your event handlers will be passed an event object when it's triggered. Inside your handler, you need to leverage jQuery's event.stopPropagation() method. For example:
link: function(event) {
//do some stuff here
event.stopPropagation();
}
Two other methods that might work for you:
1
events: {
'click a': 'link',
'click *:not(a, a *)': 'openPanel'
}
Then openPanel will not capture click events on any <a> or child of an <a> (in case you have an icon in your <a> tag).
2
At the top of the openPanel method, make sure the event target wasn't an <a>:
openPanel: function(event) {
// Don't open the panel if the event target (the element that was
// clicked) is an <a> or any element within an <a>
if (event && event.target && $(event.target).is('a, a *')) return;
// otherwise it's safe to open the panel as usual
}
Note that both of these methods still allow the openPanel function to be called from elsewhere (from a parent view or another function on this view, for example). Just don't pass an event argument and it'll be fine. You also don't have to do anything special in your link function -- just handle the click event and move on. Although you'll probably still want to call event.preventDefault().
Return "false" in your "link" function.

jQuery Multiple Events

I know how to bind multiple events and all that stuff. What I want to do is have multiple events occur to trigger a function.
Like
$(this).click and $(this).mousemove triggers a function
Is there a way to do this? Is it possible or am I just dreaming.
With a better understanding now, one thing you could do is have one event bind and unbind the other:
Example: http://jsfiddle.net/ZMeUv/
$(myselector).mousedown( function() {
$(document).mousemove(function() {
// do something
});
$(document).mouseup(function() {
$(this).unbind(); // unbind events from document
});
});
This prevents the mousemove from constantly firing when you have no need for it.
You can use jQuery's special events to package everything nicely and optimize things in the process. A mousedown and mousemove combo also commonly goes by the name "drag", so here's an example of creating a drag event that you can bind to elements. Note, that this code is specific to jQuery 1.4.2
One of the advantages to using this is that you only bind the mousemove, mouseout, and mousedown handlers once each element, no matter how many times that element is bound to the drag event. Now this isn't the most optimal way of doing it, and you can setup just 3 handlers on the document and manage everything with it, which is equally easy to do with the special events API. It just provides a nicely packaged way of building complex interactions than would be possible with just native events or custom events, in the jQuery sense.
$("..").bind("drag", function() {
...
});
I will try and add more documentation on what's actually going on, as it looks pretty unintuitive, I must confess. Checkout another nice article on the topic.
See an example of this here. To create this custom special event, use:
jQuery.event.special.drag = {
// invoked each time we bind drag to an element
add: function(obj) {
var originalHandler = obj.handler;
obj.handler = function(event) {
var el = jQuery(this);
if(el.data('mousePressed')) {
return originalHandler.apply(this, arguments);
}
};
},
// invoked only the first time drag is bound per element
setup: function(data, namespaces) {
var el = jQuery(this);
el.data('mousePressed', false);
el.bind('mousedown', function() {
jQuery(this).data('mousePressed', true);
});
jQuery(document).bind('mouseup', function() {
el.data('mousePressed', false);
});
el.bind('mousemove', jQuery.event.special.drag.handler);
},
// invoked when all drag events are removed from element
teardown: function(namespaces) {
var el = jQuery(this);
jQuery.removeData(this, 'mousePressed');
el.unbind('mousedown');
el.unbind('mouseup');
},
// our wrapper event is bound to "mousemove" and not "bind"
// change event type, so all attached drag handlers are fired
handler: function(event) {
event.type = 'drag';
jQuery.event.handle.apply(this, arguments);
}
};
Try something like this?
var isDown = false;
$(sel).mousedown(function() {
isDown = true;
});
$(sel).mouseup(function() {
isDown = false;
});
$(sel).mousemove(function() {
if (isDown) {
// Mouse is clicked and is moving.
}
});
If I'm reading your question correctly, you're asking about requiring the combination of multiple events to trigger a single function. It's possible to achieve this sort of thing, but I think it will depend greatly on the specific events and the logic or illogic of their combination. For example, the mousemove event:
...is triggered whenever the mouse
pointer moves, even for a pixel. This
means that hundreds of events can be
generated over a very small amount of
time.
Contrast that with the mousedown event, which is -- well, one per click. How to combine? The jQuery API goes on to state:
A common pattern is to bind the
mousemove handler from within a
mousedown hander [sic], and to unbind it
from a corresponding mouseup handler.
If implementing this sequence of
events, remember that the mouseup
event might be sent to a different
HTML element than the mousemove event
was. To account for this, the mouseup
handler should typically be bound to
an element high up in the DOM tree,
such as <body>.
Perhaps another approach would be to create a primitive finite state machine to ingest as inputs the various relevant events you have in mind, update its state accordingly, and then trigger a custom event when appropriate states are achieved. This all smells a little bit like reinventing the wheel, but maybe your requirement is very specific or unusual.
References: jQuery API: mousemove()
Alright, thanks for your idea Patrick. It reminded of a way I had done something like this in Java.
var m_down = false;
$(this).mousedown(function() {
m_down = true;
});
$(this).mouseup(function() {
m_down = false;
});
$(this).mousemove(function() {
// Code to occur here
});

Categories