$('#clickableElement').bind({
mousedown: function(e)
{
console.log('mousedown on element');
$(document).bind('mouseup',function(e){
console.log('mouseup caught');
//Do some magic here
$(this).unbind('mouseup');
});
},
mouseup:function(e)
{
//mouseup within element, no use here.
}
});
I'm trying to catch the mouseup event from a mousedown that's released inside or outside of an element. This code almost works, but the problem is the unbind('mouseup') which is unbinding other scripts attached to the mouseup event (jqueryui). If unbind() is not set then the code gets stacked within mouseup event and called x number of times, where x is the times you've mousedowned.
Route 1: is there some kind of self destructing function that calls itself once and destroys?
Route 2: any way to copy/clone the mouseup function prior to inserting the code, then unbind, then set as previous?
Ideally I'd like to keep this code structure for neatness as I have lots of clickable elements, so binding the document.mouseup outside of element.mousedown would be messy.
Here's the Fiddle I forgot to add http://jsfiddle.net/9gFNk/
Can giv your click event a namespace so only that namespaced event gets unbound, and not any others
$(document).on('mouseup.clickableElement',function(e){
console.log('mouseup caught');
//Do some magic here
$(this).off('mouseup.clickableElement');
});
I created a global object to catch mouse events from the document. It's currently set for mouseup only but can be easily expanded for others. The mouseup code is still customizable within the mousedown functions of the clickable elements so it this handy if you have lots of clickable things like I do.
var MouseCatcher=function()
{
this.init=function()
{
var mc = this;
$(document).bind({
mouseup:function(e)
{
mc.mouseup();
}
});
}
this.mouseup=function()
{
return false;
}
}
var mouseCatcher = new MouseCatcher();
mouseCatcher.init();
$('#clickableElement').bind({
mousedown: function(e)
{
console.log('mousedown on element');
mouseCatcher.mouseup=function()
{
console.log('mouseup called from MouseCatcher');
this.mouseup = function(){return false;}
}
},
mouseup:function(e)
{
//mouseup within element, no use here.
}
});
With "on" event its possible, its may not be an exact solution. Please refer this code
$(document).on('mousedown', function() {
$('#clickableElement').css('display', 'none');
$(document).bind('mouseup', function() {
$('#clickableElement').css('display', 'block');
});
});
http://jsfiddle.net/9gFNk/13/
Related
I am working on building a simple script where a user click's either a blue button or a red button. When the blue button's are clicked the one the user clicks on should fade out, which works fine. However if the user clicks the red button then the fade out on the blue will stop. Like I said the blue buttons work but the red one doesn't.
Looking at various questions and answers on here and other sites I believe that the code I have is correct and it seems that the reason it won't work is because they don't match, i.e. I am not actually removing the add event.
The code I have is below and any help would be appreciated, I am using Adobe Animate to code in:
instance = this;
instance.stop();
//Buttons array
var lowerQuestions = [instance.BTN1, instance.BTN2, instance.BTN4];
//Add an event listener to each button in the array
addEventListeners();
function addEventListeners(){
lowerQuestions.forEach(function(element) {
element.addEventListener("click", function(){
console.log('add listener');
addButtonValue(element);
},false);
});
}
//Remove event listeners when BTN3 is clicked
instance.BTN3.addEventListener("click", removeEventListeners)
function removeEventListeners(){
console.log('prevent');
lowerQuestions.forEach(function(element) {
element.removeEventListener("click", function(){
console.log('remove listener');
addButtonValue(element);
//console.log('hit me here');
},false);
});
}
//Event listener function
function addButtonValue(element){
instance.addEventListener("tick", fadeOut);
element.alpha = 1;
function fadeOut(){
element.alpha -= 0.15;
if(element.alpha <= 0){
instance.removeEventListener("tick", fadeOut);}
}
}
For remove event listener of the element you have two choice. 1- make a copy of element and replace with this one. 2- put name for listener function and pass it to remove event listener.
In your code i suggest first solution. This code can help you, for every single element that you want remove listener should run this code :
function removeEventListeners(){
console.log('prevent');
lowerQuestions.forEach(function(element) {
var cln = element.cloneNode(true);
element.parentNode.appendChild(cln);
element.parentNode.removeChild(element);
});
}
Are anonymous functions able to handle removeEventListener? discusses why anonymous function expressions are not great for event listeners that need to be removed - function expressions produce a different function object each time they are executed, so the remove function never matches the added function.
A simple solution in this case is to create the listener using a standard named function declaration:
function buttonClicked(){
addButtonValue( this);
}
addEventListeners();
function addEventListeners(){
lowerQuestions.forEach(function(element) {
element.addEventListener("click", buttonClicked, false);
});
}
which make the listeners removable by name:
//...
lowerQuestions.forEach(function(element) {
element.removeEventListener("click", buttonClicked, false);
});
//...
Finding myself in a bit of a strange position where I have to reference the tooltip within an instantiation for all tooltips.
$('body').tooltip({
selector: '[data-toggle="tooltip"]',
html: true,
animation: false,
}).on("mouseenter", function (e) {
e.stopPropagation();
var _this = e.target;
$(_this).tooltip("show");
$(".tooltip").on("mouseleave", function () {
$(_this).tooltip('hide');
});
}).on("mouseleave", function (e) {
e.stopPropagation();
var _this = e.target;
setTimeout(function () {
if (!$(".tooltip:hover").length) {
$(_this).tooltip("hide");
}
}, 300);
});
That being said, how can I:
Reference the actual element that is triggering this jQuery call
Keep the tooltip open while either the actual tooltip or element that generated it are being hovered over?
Here is a link to a JSFiddle prototype:
https://jsfiddle.net/cwv57weu/8/
Within your '.on()' call, you can add an 'event' argument to your anonymous function. This will contain all of the data from the event including the element which triggered the event (it will be referenced as 'target').
}).on("mouseenter", function (event) {
$(event.target).tooltip("show");
})
the event argument contains a ton of data, I would play around with it by console.log(event) within your anonymous function to get a feel as to what data is available to you.
Use event.target.
$('body').tooltip({
selector: '[data-toggle="tooltip"]',
html: true,
animation: false,
}).on("mouseenter", function (e) {
var _this = e.target;
$(_this).tooltip("show");
$(".tooltip").one("mouseleave", function () {
$(_this).tooltip('hide');
});
}).on("mouseleave", function (e) {
var _this = e.target;
setTimeout(function () {
if (!$(".tooltip:hover").length) {
$(_this).tooltip("hide");
}
}, 300);
});
e.target is the actual element that the event originated on, while this is the element that the event listener was attached to (equivalent to e.currentTarget).
Note that because of event bubbling, the event will fire on all the containing elements up to body. You may want to use e.stopPropagation() to prevent bubbling, so you only process the deepest element.
I also changed the mouseleave handler on .tooltip to use .one(). Otherwise, every time you enter something, you'll add another mouseleave handler to all the tooltips, without removing the previous one, and soon there will be thousands of handlers running (this is why it's generally wrong to bind event handlers inside other event handlers). I'm not really sure you need both that mouseleave handler and the one you attach to body.
I'm trying to create a slider (ex: http://jqueryui.com/slider/) in pure javascript (no libraries) and am having trouble getting the addEventListener / removeEventListener to operate as I'm expecting them. Basically, I keep running into an issue where I lose the scope of the mousemove listener object when I drill down into the mouseup listener, and am unable to remove the mousemove event listener and stop the dragging process.
I've also tried to create an object with an EventListener interface (for example, as described here: http://ajaxian.com/archives/an-alternative-way-to-addeventlistener) but run into the same problem. I'd really like to pursue that route if possible.
How about some code. Here's the HTML:
<div class="slider">
<div class="slider-bar"></div>
<div id="slider-1" class="slider-ball"></div>
</div>
Here's an example of some javascript:
<script type="text/javascript">
function Slider(elem) {
this.elem = elem;
this.listen = function() {
this.elem.addEventListener('mousedown', this.mouseDown, false);
document.addEventListener('mouseup', this.mouseUp, false);
}
this.mouseDown = function(e) {
e.stopPropagation();
document.addEventListener('mousemove', this.mouseDrag, false);
}
this.mouseUp = function() {
document.removeEventListener('mousemove', this.mouseDrag, false);
}
this.mouseDrag = function(e) {
// code to move the slider-ball on its X axis.
}
}
newSlider = new Slider(document.getElementById('slider-1'));
newSlider.listen();
</script>
I've tried many different approaches – prototype methods, a singleton function, internal methods (e.g. above), but in all of these cases when I run something along the lines of this.addEventListener('mousedown', this.mouseDown, false); I lose the scope of this object in mouseDown. I've used bind() to pass the value along – this.addEventListener('mousedown', this.mouseDown.bind(null, this), false); to pass the DOM object along while keeping the event parameter in mouseDrag. Cool! Except in those cases I've been consistently unable to ever remove the event listener. The only exception to this is if I set the mousemove event listener via this.elem.addEventListener..., but in this case my mousemove function only runs when the cursor is moving while over the object and not anywhere in the document, which is the desired behavior.
I could do this in a bunch of standalone functions using global vars to reference the attached events, but I'm trying to learn to do this within an object.
use bind.
You have 2 choice:
when you attach the event
when you attache function function to this.
Like this:
function Slider(elem) {
this.elem = elem;
this.listen = function() {
this.elem.addEventListener('mousedown', this.mouseDown.bind(this), false);
document.addEventListener('mouseup', this.mouseUp.bind(this), false);
}
this.mouseDown = function(e) {
e.stopPropagation();
document.addEventListener('mousemove', this.mouseDrag, false);
}
this.mouseUp = function() {
document.removeEventListener('mousemove', this.mouseDrag, false);
}
this.mouseDrag = function(e) {
// code to move the slider-ball on its X axis.
}.bind(this);
}
I have a bunch of elements that get three different classes: neutral, markedV and markedX. When a user clicks one of these elements, the classes toggle once: neutral -> markedV -> markedX -> neutral. Every click will switch the class and execute a function.
$(document).ready(function(){
$(".neutral").click(function markV(event) {
alert("Good!");
$(this).addClass("markedV").removeClass("neutral");
$(this).unbind("click");
$(this).click(markX(event));
});
$(".markedV").click(function markX(event) {
alert("Bad!");
$(this).addClass("markedX").removeClass("markedV");
$(this).unbind("click");
$(this).click(neutral(event));
});
$(".markedX").click(function neutral(event) {
alert("Ok!");
$(this).addClass("neutral").removeClass("markedX");
$(this).unbind("click");
$(this).click(markV(event));
});
});
But obviously this doesn't work. I think I have three obstacles:
How to properly bind the changing element to the already defined function, sometimes before it's actually defined?
How to make sure to pass the event to the newly bound function [I guess it's NOT accomplished by sending 'event' to the function like in markX(event)]
The whole thing looks repetitive, the only thing that's changing is the alert action (Though each function will act differently, not necessarily alert). Is there a more elegant solution to this?
There's no need to constantly bind and unbind the event handler.
You should have one handler for all these options:
$(document).ready(function() {
var classes = ['neutral', 'markedV', 'markedX'],
methods = {
neutral: function (e) { alert('Good!') },
markedV: function (e) { alert('Bad!') },
markedX: function (e) { alert('Ok!') },
};
$( '.' + classes.join(',.') ).click(function (e) {
var $this = $(this);
$.each(classes, function (i, v) {
if ( $this.hasClass(v) ) {
methods[v].call(this, e);
$this.removeClass(v).addClass( classes[i + 1] || classes[0] );
return false;
}
});
});
});
Here's the fiddle: http://jsfiddle.net/m3CyX/
For such cases you need to attach the event to a higher parent and Delegate the event .
Remember that events are attached to the Elements and not to the classes.
Try this approach
$(document).ready(function () {
$(document).on('click', function (e) {
var $target = e.target;
if ($target.hasClass('markedV')) {
alert("Good!");
$target.addClass("markedV").removeClass("neutral");
} else if ($target.hasClass('markedV')) {
alert("Bad!");
$target.addClass("markedX").removeClass("markedV");
} else if ($target.hasClass('markedX')) {
alert("Ok!");
$target.addClass("neutral").removeClass("markedX");
}
});
});
OR as #Bergi Suggested
$(document).ready(function () {
$(document).on('click', 'markedV',function (e) {
alert("Good!");
$(this).addClass("markedV").removeClass("neutral");
});
$(document).on('click', 'markedX',function (e) {
alert("Bad!");
$(this).addClass("markedX").removeClass("markedV");
});
$(document).on('click', 'neutral',function (e) {
alert("Ok!");
$(this).addClass("neutral").removeClass("markedX");
});
});
Here document can be replaced with any static parent container..
How to properly bind the changing element to the already defined function, sometimes before it's actually defined?
You don't bind elements to functions, you bind handler functions to events on elements. You can't use a function before it is defined (yet you might use a function above the location in the code where it was declared - called "hoisting").
How to make sure to pass the event to the newly bound function [I guess it's NOT accomplished by sending 'event' to the function like in markX(event)]
That is what happens implicitly when the handler is called. You only need to pass the function - do not call it! Yet your problem is that you cannot access the named function expressions from outside.
The whole thing looks repetitive, the only thing that's changing is the alert action (Though each function will act differently, not necessarily alert). Is there a more elegant solution to this?
Yes. Use only one handler, and decide dynamically what to do in the current state. Do not steadily bind and unbind handlers. Or use event delegation.
I have an anchor tag on my page, I want an event attached to it, which will fire when the display of this element change.
How can I write this event, and catch whenever the display of this element changes?
This is my way of doing on onShow, as a jQuery plugin. It may or may not perform exactly what you are doing, however.
(function($){
$.fn.extend({
onShow: function(callback, unbind){
return this.each(function(){
var _this = this;
var bindopt = (unbind==undefined)?true:unbind;
if($.isFunction(callback)){
if($(_this).is(':hidden')){
var checkVis = function(){
if($(_this).is(':visible')){
callback.call(_this);
if(bindopt){
$('body').unbind('click keyup keydown', checkVis);
}
}
}
$('body').bind('click keyup keydown', checkVis);
}
else{
callback.call(_this);
}
}
});
}
});
})(jQuery);
You can call this inside the $(document).ready() function and use a callback to fire when the element is shown, as so.
$(document).ready(function(){
$('#myelement').onShow(function(){
alert('this element is now shown');
});
});
It works by binding a click, keyup, and keydown event to the body to check if the element is shown, because these events are most likely to cause an element to be shown and are very frequently performed by the user. This may not be extremely elegant but gets the job done. Also, once the element is shown, these events are unbinded from the body as to not keep firing and slowing down performance.
You can't get an onshow event directly in JavaScript. Do remember that the following methods are non-standard.
IN IE you can use
onpropertychange event
Fires after the property of an element
changes
and for Mozilla
you can use
watch
Watches for a property to be assigned
a value and runs a function when that
occurs.
You could also override jQuery's default show method:
var orgShow = $.fn.show;
$.fn.show = function()
{
$(this).trigger( 'myOnShowEvent' );
orgShow.apply( this, arguments );
return this;
}
Now just bind your code to the event:
$('#foo').bind( "myOnShowEvent", function()
{
console.log( "SHOWN!" )
});
The code from this link worked for me: http://viralpatel.net/blogs/jquery-trigger-custom-event-show-hide-element/
(function ($) {
$.each(['show', 'hide'], function (i, ev) {
var el = $.fn[ev];
$.fn[ev] = function () {
this.trigger(ev);
return el.apply(this, arguments);
};
});
})(jQuery);
$('#foo').on('show', function() {
console.log('#foo is now visible');
});
$('#foo').on('hide', function() {
console.log('#foo is hidden');
});
However the callback function gets called first and then the element is shown/hidden. So if you have some operation related to the same selector and it needs to be done after being shown or hidden, the temporary fix is to add a timeout for few milliseconds.