I am struggling to understand how a custom event type is linked to a specific user action/trigger. All documentation seems to dispatch the event without any user interaction.
In the following example I want the event to be dispatched once a user has been hovering on the element for 3 seconds.
var img = document.createElement('img');img.src = 'http://placehold.it/100x100';
document.body.appendChild(img)
var event = new CustomEvent("hoveredforthreeseconds");
img.addEventListener('hoveredforthreeseconds', function(e) { console.log(e.type)}, true);
var thetrigger = function (element, event) {
var timeout = null;
element.addEventListener('mouseover',function() {
timeout = setTimeout(element.dispatchEvent(event), 3000);
},true);
element.addEventListener('mouseout', function() {
clearTimeout(timeout);
},true);
};
I have a trigger but no logical way of connecting it to the event.
I was thinking about creating an object called CustomEventTrigger which is essentially CustomEvent but has a third parameter for the trigger and also creating a method called addCustomEventListener, which works the same as addEventListener but when initialised it then passes the target Element to the custom event trigger which then dispatches the event when it's instructed to.
Custom events have to be triggered programatically through dispatchEvent, they are not fired by the DOM. You will always need to explictly call them in your code, such as in response to a user-generated event such as onmouseover, or a change of state such as onload.
You're very close to a working implementation, however you're immediately invoking dispatchEvent in your setTimeout. If you save it into a closure (as below) you can invoke dispatchEvent while passing your element after setTimeout has finished the timeout.
It's also good practice to declare your variables at the top of a file, to avoid possible scope issues.
var img = document.createElement('img'), timeout, event, thetrigger;
img.src = 'http://placehold.it/100x100';
document.body.appendChild(img);
img.addEventListener("hoveredForThreeSeconds", afterHover, false);
thetrigger = function (element, event) {
timeout = null;
element.addEventListener('mouseover',function() {
timeout = setTimeout(function(){ element.dispatchEvent(event) }, 3000);
},true);
element.addEventListener('mouseout', function() {
clearTimeout(timeout);
},true);
};
function afterHover(e) {
console.log("Event is called: " + e.type);
}
event = new CustomEvent("hoveredForThreeSeconds");
thetrigger(img, event);
I have created a method called addCustomEventListener, which works the same as addEventListener but when initialised passes the target Element to the custom event trigger which dispatches the event when it says, so in this case it only dispatches if the timeout reaches 3 seconds.
var img = document.getElementById('img');
window.mouseover3000 = new CustomEvent('mouseover3000', {
detail: {
trigger: function(element, type) {
timeout = null;
element.addEventListener('mouseover', function() {
timeout = setTimeout(function() {
element.dispatchEvent(window[type])
}, 3000);
}, false);
element.addEventListener('mouseout', function() {
clearTimeout(timeout);
}, false)
}
}
});
window.tripleclick = new CustomEvent('tripleclick', {
detail: {
trigger: function(element, type) {
element.addEventListener('click', function(e) {
if(e.detail ===3){
element.dispatchEvent(window[type])
}
}, false);
}
}
});
EventTarget.prototype.addCustomEventListener = function(type, listener, useCapture, wantsUntrusted) {
this.addEventListener(type, listener, useCapture, wantsUntrusted);
window[type].detail.trigger(this, type);
}
var eventTypeImage = function(e) {
this.src = "http://placehold.it/200x200?text=" + e.type;
}
img.addEventListener('mouseout', eventTypeImage, false);
img.addEventListener('mouseover', eventTypeImage, false);
img.addCustomEventListener('mouseover3000', eventTypeImage, false);
img.addCustomEventListener('tripleclick', eventTypeImage, false);
<img id="img" src="http://placehold.it/200x200?text=No+hover" ;/>
I think this could be useful to others so please feel free to improve on this.
Related
I have one input which have event listener onblur and button which have event listener onclick.
This is how I organized it
document.addEventListener("DOMContentLoaded", function() {
window.searchButton = document.getElementById("searchButton");
window.searchInput = document.getElementById("searchInput");
searchInput.onfocus = searchFocus;
searchInput.onblur = searchBlur;
searchButton.onclick = search;
});
var resize = function(self, newSize) {
self.setAttribute("size", newSize);
};
var searchFocus = function() {
event.stopPropagation();
resize(searchInput, 30);
};
var searchBlur = function() {
if (searchInput.value === "") {
resize(searchInput, 10);
}
};
var search = function() {
event.stopPropagation();
};
But when the input is focused and I am clicking on the button, working onblur function, but onclick function isn't working. Why and how can I fix that?
Your search callback should have event in its param list. Same is true for searchFocus
var search = function(event){
event.stopPropagation();
};
Also, you might want to check that your DOMContentLoaded handler is firing. Depending upon how your scripts are organized/loaded, it is possible that the DOM has already loaded and that event fired before you register the handler. That's a common oversight.
How to detect if DOMContentLoaded was fired
I'm adding checkboxchange event to checkbox here. (moveButton is a checkBox because im using CSS checkbox hack)
var self = this;
this.moveButton.addEventListener("change", function(e){self.toggleMove(e, self)});
if the checkbox is checked it adds an eventListener to body.document
DR.prototype.toggleMove = function(e, self){
if(self.moveButton.checked){
document.body.addEventListener("click", function bodyEvent(e){self.removeableEventHandler(e, self)}, false);
}else{
console.log("unchecked");
document.body.removeEventListener("click", function bodyEvent(e){self.removeableEventHandler(e, self)}, false);
}
}
if i don't wrap self.removeableEventHandler in a function i am unable to attach self to the function, but when i wrap it in a function i will be unable to remove the event when the checkbox is unchecked.
DR.prototype.removeableEventHandler = function(e, self){
console.log(e.clientX, e.clientY);
self.ele.style.top = e.clientY + "px";
self.ele.style.left = e.clientX + "px";
};
So it seems to be like I'm having a bit of a scope conundrum here. Not sure how to fix it. I'm trying to make a form moveable when the checkbox is checked and then removing the move event when the checkbox is unchecked.
removeEventListener works by passing the original function reference. If you pass a copy it wont work.
You can do:
DR.prototype.toggleMove = (function () {
var boundBodyEvent;
function bodyEvent(e) {
this.removeableEventHandler(e);
}
return function (e) {
if (this.moveButton.checked) {
boundBodyEvent= bodyEvent.bind(this);
document.body.addEventListener("click", boundBodyEvent, false);
} else {
document.body.removeEventListener("click", boundBodyEvent, false);
}
};
}());
I don't think you need to pass self around, that seems strange to me. I'm using bind to override the this in bodyEvent to refernce your DR instance instead of the DOM Element.
I'm also using immediate invocation to avoid having to put the bodyEvent in the global scope.
Alternatively, you could also not bother removing the event listener and have a switch inside the event listener:
DR.prototype.init = function () {
var self = this;
document.body.addEventListener("click", function (e) {
if (self.moveButton.checked) {
self.removeableEventHandler(e);
}
}, false);
}
removeEventListener callbak function need to be reference to the same function as in addEventListener, try this:
function bodyEvent(e) {
self.removeableEventHandler(e, self);
}
DR.prototype.toggleMove = function(e, self) {
if (self.moveButton.checked) {
document.body.addEventListener("click", bodyEvent, false);
} else {
console.log("unchecked");
document.body.removeEventListener("click", bodyEvent, false);
}
};
Is it possible to avoid twice firing events by browsers, but not based on timing (in case your resize event execution lasts long that solution is bad)
window.blockResize = false;
$(window).resize(function() {
if (window.blockResize) return;
//do stuff
window.blockResize = true;
setTimeout(function(){window.blockResize = false},200);
});
Ok, it's still based on timers, but It works in my case now.
You can use setTimeout() and clearTimeout() in conjunction with jQuery.data:
$(window).resize(function() {
clearTimeout($.data(this, 'resizeTimer'));
$.data(this, 'resizeTimer', setTimeout(function() {
//do something
alert("Haven't resized in 200ms!");
}, 200));
});
Update
I wrote an extension to enhance jQuery's default on (& bind)-event-handler. It attaches an event handler function for one or more events to the selected elements if the event was not triggered for a given interval. This is useful if you want to fire a callback only after a delay, like the resize event, or else.
https://github.com/yckart/jquery.unevent.js
;(function ($) {
var methods = { on: $.fn.on, bind: $.fn.bind };
$.each(methods, function(k){
$.fn[k] = function () {
var args = [].slice.call(arguments),
delay = args.pop(),
fn = args.pop(),
timer;
args.push(function () {
var self = this,
arg = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(self, [].slice.call(arg));
}, delay);
});
return methods[k].apply(this, isNaN(delay) ? arguments : args);
};
});
}(jQuery));
Use it like any other on or bind-event handler, except that you can pass an extra parameter as a last:
$(window).on('resize', function(e) {
console.log(e.type + '-event was 200ms not triggered');
}, 200);
http://jsfiddle.net/ARTsinn/EqqHx/
Example:
domNode.onmouseover = function() {
this.innerHTML = "The mighty mouse is over me!"
}
domNode.onmouseover = function() {
this.style.backgroundColor = "yellow";
}
In this example the text won't change, but the thing is also that I don't always know what was assigned before, so is there a way to tell to js: Run everything that was eventually assigned without knowing what was that and then run my function?
It's possible to do this by passing the current event handler to the new handler:
domNode.onmouseover = function()
{
console.log('first handler');
}
domNode.onmouseover = (function (current)
{
return function()
{
current();//call handler that was set when this handler was created
console.log('new handler');
};
})(domNode.onmouseover);//pass reference to current handler
See this fiddle, to see it in actionYou can keep on doing this as much as you want/need:
domNode.onmouseover = function()
{
console.log('first handler');
}
domNode.onmouseover = (function (current)
{
return function()
{
current();
console.log('second handler');
};
})(domNode.onmouseover);
domNode.onmouseover = (function (current)
{
return function()
{
current();
console.log('third handler');
};
})(domNode.onmouseover);
This will log:
first handler
second handler
third handler
That's all there is to it!
First of all, place it in a document.ready. (not sure if you done that)
If you want 2 actions for one action place it in once function.
You can also create 2 functions and call them in your mouseover.
$(document).ready(function(){
domNode.onmouseover = function() {
this.innerHTML = "The mighty mouse is over me!"
this.style.backgroundColor = "yellow";
}
});
I have an element on my page that I need to attach onclick and ondblclick event handlers to. When a single click happens, it should do something different than a double-click. When I first started trying to make this work, my head started spinning. Obviously, onclick will always fire when you double-click. So I tried using a timeout-based structure like this...
window.onload = function() {
var timer;
var el = document.getElementById('testButton');
el.onclick = function() {
timer = setTimeout(function() { alert('Single'); }, 150);
}
el.ondblclick = function() {
clearTimeout(timer);
alert('Double');
}
}
But I got inconsistent results (using IE8). It would work properly alot of times, but sometimes I would get the "Single" alert two times.
Has anybody done this before? Is there a more effective way?
Like Matt, I had a much better experience when I increased the timeout value slightly. Also, to mitigate the problem of single click firing twice (which I was unable to reproduce with the higher timer anyway), I added a line to the single click handler:
el.onclick = function() {
if (timer) clearTimeout(timer);
timer = setTimeout(function() { alert('Single'); }, 250);
}
This way, if click is already set to fire, it will clear itself to avoid duplicate 'Single' alerts.
If you're getting 2 alerts, it would seem your threshold for detecing a double click is too small. Try increasing 150 to 300ms.
Also - I'm not sure that you are guaranteed the order in which click and dblclick are fired. So, when your dblclick gets fired, it clears out the first click event, but if it fires before the second 'click' event, this second event will still fire on its own, and you'll end up with both a double click event firing and a single click event firing.
I see two possible solutions to this potential problem:
1) Set another timeout for actually firing the double-click event. Mark in your code that the double click event is about to fire. Then, when the 2nd 'single click' event fires, it can check on this state, and say "oops, dbl click pending, so I'll do nothing"
2) The second option is to swap your target functions out based on click events. It might look something like this:
window.onload = function() {
var timer;
var el = document.getElementById('testButton');
var firing = false;
var singleClick = function(){
alert('Single');
};
var doubleClick = function(){
alert('Double');
};
var firingFunc = singleClick;
el.onclick = function() {
// Detect the 2nd single click event, so we can stop it
if(firing)
return;
firing = true;
timer = setTimeout(function() {
firingFunc();
// Always revert back to singleClick firing function
firingFunc = singleClick;
firing = false;
}, 150);
}
el.ondblclick = function() {
firingFunc = doubleClick;
// Now, when the original timeout of your single click finishes,
// firingFunc will be pointing to your doubleClick handler
}
}
Basically what is happening here is you let the original timeout you set continue. It will always call firingFunc(); The only thing that changes is what firingFunc() is actually pointing to. Once the double click is detected, it sets it to doubleClick. And then we always revert back to singleClick once the timeout expires.
We also have a "firing" variable in there so we know to intercept the 2nd single click event.
Another alternative is to ignore dblclick events entirely, and just detect it with the single clicks and the timer:
window.onload = function() {
var timer;
var el = document.getElementById('testButton');
var firing = false;
var singleClick = function(){
alert('Single');
};
var doubleClick = function(){
alert('Double');
};
var firingFunc = singleClick;
el.onclick = function() {
// Detect the 2nd single click event, so we can set it to doubleClick
if(firing){
firingFunc = doubleClick;
return;
}
firing = true;
timer = setTimeout(function() {
firingFunc();
// Always revert back to singleClick firing function
firingFunc = singleClick;
firing = false;
}, 150);
}
}
This is untested :)
Simple:
obj.onclick=function(e){
if(obj.timerID){
clearTimeout(obj.timerID);
obj.timerID=null;
console.log("double")
}
else{
obj.timerID=setTimeout(function(){
obj.timerID=null;
console.log("single")
},250)}
}//onclick
Small fix
if(typeof dbtimer != "undefined"){
dbclearTimeout(timer);
timer = undefined;
//double click
}else{
dbtimer = setTimeout(function() {
dbtimer = undefined;
//single click
}, 250);
}
, cellclick :
function(){
setTimeout(function(){
if (this.dblclickchk) return;
setTimeout(function(){
click event......
},100);
},500);
}
, celldblclick :
function(){
setTimeout(function(){
this.dblclickchk = true;
setTimeout(function(){
dblclick event.....
},100);
setTimeout(function(){
this.dblclickchk = false;
},3000);
},1);
}
I found by accident that this works (it's a case with Bing Maps):
pushpin.clickTimer = -1;
Microsoft.Maps.Events.addHandler(pushpin, 'click', (pushpin) {
return function () {
if (pushpin.clickTimer == -1) {
pushpin.clickTimer = setTimeout((function (pushpin) {
return function () {
alert('Single Clic!');
pushpin.clickTimer = -1;
// single click handle code here
}
}(pushpin)), 300);
}
}
}(pushpin)));
Microsoft.Maps.Events.addHandler(pushpin, 'dblclick', (function (pushpin) {
return function () {
alert('Double Click!');
clearTimeout(pushpin.clickTimer);
pushpin.clickTimer = -1;
// double click handle here
}
}(pushpin)));
It looks like the click event masks the dblclick event, and this usage is clearing it when we add a timeout. So, hopefully, this will work also with non Bing Maps cases, after a slight adaptation, but I didn't try it.