I'm looking for a solution to restore a removed attribute. I'm not an experienced programmer, so I'm not sure where to start when sharing my code, so I'll try to give some context below.
I have an image of a map that has several hidden overlays. These overlays are activated by a series of adjacent buttons.
Each of these buttons has a mouseover and mouseout event, which temporarily reveals the overlay. They also have an onclick event that permanently displays the overlay. I've used a .removeAtribute function to remove the mouseout event so that my overlay is permanent.
All other layers are still visible with the mouseover and mouseout events (so that you can make comparisons).
When I onclick another overlay button, it clears the previous one, however, now the mouseout event for the previously selected button is still inactive, so hovering over it causes the overlay to appear permanently.
How can I restore the mouseout event after I've removed it?
I have tried to use .setAttribute("onmouseout"), but I've had no luck in making that work.
Hopefully, this all makes some sense; I'll post some of my code below, which might help give further context.
function btn01On() {
document.getElementById("btn01").removeAttribute("onmouseout");
}
function btnClear() {
document.getElementById("btn01").setAttribute("onmouseout");
}
<button id="btn01" class="map-button map-button1"
onclick="MM_showHideLayers('InfoCurrentExc','','show','OverlayCurrentExc','','show');btn01On();" onmouseover="MM_showHideLayers('OverlayCurrentExc','','show')" onmouseout="MM_showHideLayers('OverlayCurrentExc','','show')">
Current Excavation
</button>
Usually setting up event handlers using onevent attribute, (although oldest method for event handling) is not the recommended way, See https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#dom_event_handler_list.
I recommend using EventTarget.addEventListener and EventTarget.removeEventListener for your case. Try the code below
const mouseoutHandler = function() {
MM_showHideLayers('OverlayCurrentExc', '', 'show');
};
const mouseOverHandler = function() {
MM_showHideLayers('OverlayCurrentExc', '', 'show');
};
const button01 = document.getElementById("btn01");
button01.addEventListener("mouseover", mouseOverHandler);
function btn01On() {
if (!mouseoutListener)
button01.addEventListener("mouseout", mouseoutHandler);
}
function btnClear() {
if (mouseoutListener) {
button01.removeEventListener("mouseout", mouseoutListener);
mouseoutListener = null;
}
}
const clickHandler = function() {
MM_showHideLayers('InfoCurrentExc', '', 'show', 'OverlayCurrentExc', '', 'show');
btn01On();
}
button01.addEventListener("click", clickHandler);
I was lucky enough to find someone who had a fix for this problem. I'll share the code below for anyone who might land here with a similar request.
I don't fully understand how this code works so if someone has a good explanation feel free to share it.
// Remove mouse outs
function btn01On() {
document.getElementById("btn01").removeAttribute("onmouseout");
}
// keep mouse outs
const buttonIds = ["btn01"];
const mouseOuts = {};
buttonIds.forEach((id) => {
const el = document.getElementById(id);
if (el) {
mouseOuts[id] = el.getAttribute('onmouseout');
}
});
const restoreMouseOutEvent = () => {
buttonIds.forEach((id) => {
const el = document.getElementById(id);
if (el && mouseOuts[id]) {
el.setAttribute('onmouseout', mouseOuts[id]);
}
});
}
Related
When I do a search for images it loads images from Pixabay, which are added to a container via fetch without a page reload, I need to click on the newly generated images to get the ID of the image, which then converts the image ID to an URL, and gets added into the input.
My workaround is to find dom elements with the class ".dePixaImage" using the click, and mouseover event and then remove those events with removeEventListener which works fine to a certain point, but then when I apply a new search nothing happens because the new dom elements are being loaded and the event listener has been stopped.
If I do not remove the removeEventListener, then the for each loop keeps on multiplying, which results in a lot of fetch requests when the mouse is moved, or if a click occurs, which is completely unnecessary and uses up all the API requests.
window.addEventListener("click", deViewImage);
window.addEventListener("mouseover", deViewImage);
function deViewImage() {
let deViewImageAttr = "";
let dePixaImage = document.querySelectorAll(".dePixaImage");
if (dePixaImage) {
dePixaImage.forEach((e) => {
e.addEventListener("click", function () {
// id of the image from Pixabay, added via fetch
deViewImageAttr = e.getAttribute("id");
// call fetch function and pass ID
pixabayImageUrlFromId(deViewImageAttr);
});
window.removeEventListener("click", deViewImage);
window.removeEventListener("mouseover", deViewImage);
});
}
}
Maybe there is a better solution to listen for the dom elements when they get changed frequently?
I see from your comment what you are really looking for is attaching a click handler to dynamically added elements.
That is rather easy with event delegation and not having a click handler inside another click handler. This will work with objects in the DOM and new objects added to the DOM later. You only need to add the code once, and it won't be duplicated.
function pixabayImageUrlFromId(id) {
console.log(id)
}
document.body.addEventListener("click", deViewImage);
document.body.addEventListener("mouseover", deViewImage);
function deViewImage(e) {
let obj = e.target;
if (obj.classList.contains("dePixaImage")) {
pixabayImageUrlFromId(obj.dataset.id);
}
}
<div class="dePixaImage" data-id="1111111">ssss</div>
Thanks, #imvain, with little modification I no longer needed mouseover, here is the solution now with the above help.
function pixabayImageUrlFromId(id) {
let imageUrlInput = document.querySelector(
'[data-test-id="control-attributes-de_image_img_url"] input'
);
let URL = "https://pixabay.com/api/?key=" + API_KEY + "&id=" + id;
fetch(URL)
.then(function (resp) {
return resp.json();
})
.then(function (data) {
let imgUrl = data.hits[0].largeImageURL;
imageUrlInput.value = imgUrl;
imageUrlInput.dispatchEvent(new Event("input"));
});
}
document.addEventListener("click", deViewImage);
function deViewImage(e) {
let obj = e.target;
if (obj.classList.contains("dePixaImage")) {
pixabayImageUrlFromId(obj.getAttribute("id"));
}
}
This is my code, I've tried using setAttribute and don't understand why it doesn't work. I've also tried using .transform with the same result.
setAttribute is located in the ondrop event function.
interact('.dropzone')
.dropzone({
// Require a 75% element overlap for a drop to be possible
overlap: 0.75,
// listen for drop related events:
ondropactivate: function (event) {
// add active dropzone feedback
event.target.classList.add('drop-active')
},
ondragenter: function (event) {
var draggableElement = event.relatedTarget
var dropzoneElement = event.target
// feedback the possibility of a drop
dropzoneElement.classList.add('drop-target')
draggableElement.classList.add('can-drop')
},
ondragleave: function (event) {
// remove the drop feedback style
event.target.classList.remove('drop-target')
event.relatedTarget.classList.remove('can-drop')
},
ondrop: function (event) {
console.log("here")
event.relatedTarget.setAttribute('resize-drag', 'width:100px')
event.relatedTarget.setAttribute('resize-drag', 'height:100px')
},
ondropdeactivate: function (event) {
// remove active dropzone feedback
event.target.classList.remove('drop-active')
event.target.classList.remove('drop-target')
}
})
Just for debugging purposes, can you try adding a classList ? That way you'll see the class appended
Create a custom class to change something visible such as the background as example. That might help you.
Also, try to provide a sandbox if you can, it's easier to help that way!
I have a situation in which i have to prevent all MouseClick events until the page loads.
i have 1 javascript function defined on page load like
onload="init();"
Now in function init(), we are showing tree and select a particular node of it.
function init() {
ExpandAncestors(node);
ExpandNode(node);
setTimeout("treeScrollToView()", 1000);
}
Now i want to prevent all the mouse click event on tree/page until whole tree is not fully shown.
I have searched through some of the posts related to my question but that uses event.preventDefault() but i dont have Event object here.
Any help is greatly appreciated.
You can use:
CSS
body {
pointer-events:none;
}
and then on page load reactivate them
$(document).ready(() => {
$('body').css('pointer-events', 'all') //activate all pointer-events on body
})
Explanation
pointer-events:none; blocks all mouse interaction with the elements it's applied to - Since the body is usually the parent of all the elements in your page, it would case them not to react to any mouse interaction at all.
Keep in mind that all mouse interaction would be blocked this way, not only mouse clicks but mouse hover, mouse up's etc etc..
I think the basic need is to prevent user from clicking the tree area. I would prefer to display an overlay div rather than playing with the tree mouse click events.
You can show a loading overlay on the tree part until it is loaded. once done, you can hide the loading and show your original tree.
Ref: How to completely DISABLE any MOUSE CLICK
JavaScript Only
You can have an event listener along with a boolean. onclick disables a click. oncontextmenu disables right clicks.
(function(){window.onload = function () {
var allowClicks = false;
document.onclick = function (e) { !allowClicks&&e.preventDefault(); }
document.oncontextmenu = function (e) { !allowClicks&&e.preventDefault(); }
document.getElementById('myElement').onload = function () { allowClicks = true; }
}());
myElement is your element which you can replace with whatever
Use this with one element
If you want to disable mouse clicks for just one element, do:
(function(){window.onload = function () {
var allowClicks = false,
elem = document.getElementById('myElement');
elem.onclick = function (e) { !allowClicks&&e.preventDefault(); }
elem.oncontextmenu = function (e) { !allowClicks&&e.preventDefault(); }
elem.onload = function () { allowClicks = true; }
}());
onload="init();" here you can have event object.
pass event as argument.
onload="init(event);"
now you can use that in init() function.
Try utilizing $.holdReady()
$.holdReady(true);
$(window).off("click");
$("*").each(function(i, el) {
this.onclick = function(e) {
e.preventDefault();
}
});
function init() {
$("div").on("click", function() {
alert($.now())
})
}
setTimeout(function() {
$.holdReady(false);
}, 7000)
$(function() {
init()
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<body>
<div>click</div>
click
</body>
Edit: I think I got the solution! I want to try and fix this myself before I ask for further help = )
First script inhibits the second one from functioning as the click event from the first one overides the second one. Because the second one does not function it is impossible to open the drop down menu to select a list item to trigger the first scripts click.
What I tried was replacing all return false statements with event.stopPropagation(). Didnt work however. Tried re-ordering my scripts but that failed as well. I was thinking of making my second script target another parent div but that didnt work either.I also tried event.stopImmediatePropagation() and .bind methods.
Any idea?
First script that makes the drop down function. Contains click event.
function DropDown(el) {
this.f = el;
this.placeholder = this.f.children('span');
this.opts = this.f.find('ul.dropdown > li');
this.val = '';
this.index = -1;
this.initEvents();
}
DropDown.prototype = {
initEvents : function() {
var obj = this;
obj.f.on('click', function(event){
$(this).toggleClass('active');
return false;
});
obj.opts.on('click',function(){
var opt = $(this);
obj.val = opt.text();
obj.index = opt.index();
obj.placeholder.text(obj.val);
});
},
getValue : function() {
return this.val;
},
getIndex : function() {
return this.index;
}
}
$(function() {
var f = new DropDown( $('#f') );
$(document).click(function() {
// all dropdowns
$('.filter-buttons').removeClass('active');
});
});
Second script that does the filtering, also contains click event:
jQuery(document).ready(function(e) {
var t = $(".filter-container");
t.imagesLoaded(function() {
t.isotope({
itemSelector: "figure",
filter: "*",
resizable: false,
animationEngine: "jquery"
})
});
$(".filter-buttons a").click(function(evt) {
var n = $(this).parents(".filter-buttons");
n.find(".selected").removeClass("selected");
$(this).addClass("selected");
var r = $(this).attr("data-filter");
t.isotope({
filter: r
});
evt.preventDefault();
});
$(window).resize(function() {
var n = $(window).width();
t.isotope("reLayout")
}).trigger("resize")
});
html structure
<div id="f" class="filter-buttons" tabindex="1">
<span>Choose Genre</span>
<ul class="dropdown">
<li>All</li>
<li>Electronic</li>
<li>Popular</a></li>
</ul>
</div>
This doesn't really solve your problem but I was bored while drinking my coffee and felt like helping you write your dropdown plugin a little nicer
My comments below are inline with code. For uninterrupted code, see DropDown complete paste.
We start with your standard jQuery wrapper (function($){ ... })(jQuery)
(function($) {
// dropdown constructor
function DropDown($elem) {
First we'll make some private vars to store information. By using this.foo = ... we expose things (probably) unnecessarily. If you need access to these vars, you can always create functions to read them. This is much better encapsulation imo.
// private vars
var $placeholder = $elem.children("span");
var $opts = $elem.find("ul.dropdown > li")
var value = "";
var index = -1;
Now we'll define our event listeners and functions those event listeners might depend on. What's nice here is that these functions don't have to access everything via this.* or as you were writing obj.f.* etc.
// private functions
function onParentClick(event) {
$elem.toggleClass("active");
event.preventDefault();
}
function onChildClick(event) {
setValue($(this));
event.preventDefault();
}
function setValue($opt) {
value = $opt.text();
index = $opt.index();
$placeholder.text(value);
}
Here's some property descriptors to read the index and value
// properties for reading .index and .value
Object.defineProperty(this, "value", {
get: function() { return value; }
});
Object.defineProperty(this, "index", {
get: function() { return index; }
});
Lastly, let's track each instance of DropDown in an array so that the user doesn't have to define a special listener to deactivate each
// track each instance of
DropDown._instances.push(this);
}
This is the array we'll use to track instances
// store all instances in array
DropDown._instances = [];
This event listener deactivate each "registered" instance of DropDown
// deactivate all
DropDown.deactiveAll = function deactiveAll(event) {
$.each(DropDown._instances, function(idx, $elem) {
$elem.removeClass("active");
});
}
Here's the document listener defined right in the plugin! The user no longer has to set this up
// listener to deactiveAll dropdowns
$(document).click(DropDown.deactiveAll);
Might as well make it a jQuery plugin since everything in our DropDown constructor relies upon jQuery. This let's the user do var x = $("foo").dropdown();
// jQuery plugin
$.fn.dropdown = function dropdown() {
return new DropDown($(this));
};
Close the wrapper
})(jQuery);
Now here's how you use it
$(function() {
var x = $('#f').dropdown();
// get the value
f.value;
// get the index
f.index;
});
Anyway, yeah I know this doesn't really help you with your click listeners, but I hope this is still useful information to you. Off to the Post Office now!
I think you're going to need to simplify this to figure out what's going on. There's actually not enough information to see what elements the events are being attached to here.
For argument's sake, open the console and try the following:
$(document).on('click', function() { console.log('first'); return false; });
$(document).on('click', function() { console.log('second'); return false; });
Then click in the page. You'll see that both events are triggered. It might well be that your code is actually attaching the events to different elements (you don't say anywhere). If that's the case then you need to understand how event bubbling works in the DOM.
When you trigger an event, say a click on an element, that event will fire on that element, and then on it's parent, then grandparent etc all the way to the root node at the top.
You can change this behaviour by calling functions in the event itself. evt.stopPropagation tells the event to not bubble up to the ancestor nodes. evt.preventDefault tells the browser not to carry out the default behaviour for a node (eg, moving to the page specified in the href for an A tag).
In jQuery, return false from an event handler is a shortcut for, evt.preventDefault and evt.stopPropagation. So that will stop the event dead in its tracks.
I imagine you have something like:
<div event_two_on_here>
<a event_one_on_here>
</div>
If the thing that handles event_one_on_here calls stopPropagation then event_two_on_here will never even know it has happened. Calling stopPropagation explicitly, or implicitly (return false) will kill the event before it travels to the parent node/event handler.
UPDATE: In your case the issue is that the handler on .filter-buttons a is stopping the propagation (so #f doesn't get to run its handler).
$(".filter-buttons a").click(function(evt) {
// your code here...
// Don't do this - it stops the event from bubbling up to the #f div
// return false;
// instead, you'll probably just want to prevent the browser default
// behaviour so it doesn't jump to the top of the page ('url/#')
evt.preventDefault();
});
I have created a fiddle of my function here( http://jsfiddle.net/rhy5K/10/ ) . Now i want to disable the button click i.e play/pause if the sound is playing like Get ready,5,4,3,2,1 .
I know only how to disable the form submit button , but I am very confused how to disable the click in my case the hyperlinks.
Explanation using code example:
I want to disable this
PLAY
click, while interpreter is executing the below code:
var playGetReady = function (done) {
var ids = ['audiosource', 'a_5', 'a_4', 'a_3', 'a_2', 'a_1'],
playNext = function () {
var id = ids.shift();
document.getElementById(id).play();
if (ids.length) {
setTimeout(playNext, 1000);
} else {
done();
}
};
playNext();
};
Warning: This JS fiddle demo may play sound on load
You may try this (Changes in following function), but not sure if this is you want and maybe there are other ways to do it.
App.prototype.start = function () {
var self = this;
// unbind for a while
self.$button.unbind('click', self.buttonHandler); // <--
var start = function () {
// start countdown
self.intervalHandle = setInterval($.proxy(self.tick, self), 1000);
// bind again
self.$button.click($.proxy(self.buttonHandler, self)); // <--
// change button text to PAUSE
self.$button.text('PAUSE');
};
if (this.newTimer) {
playGetReady(start);
} else {
start();
}
};
DEMO.
In jquery, it can be done easily by cancel default action. Here's the sample.
$("#btn_start").click(function(event){
if(not_active_flag){
// Prevent anchor to active
return false;
}else{
// Anchor active as usual
return true;
}
});
In your case, the link will ultimately call this.buttonHandler, which has the following code:
App.prototype.buttonHandler = function (e) {
e.preventDefault(); // prevent anchor default action
this.toggle(); // toggle play/pause
};
Because buttonHandler is attached before playGetReady is executed, it is not possible to let playGetReady attach a click handler to that anchor element that uses .stopImmediatePropagation() to prevent the other click handler from executing.
In this case #gp.'s solution in the comments is most likely the best solution. In your case you might even be able to use a local variable in your app. If you use a global variable, reference it with window.yourglobalvariable. If you use a local variable, make sure you define it somewhere and reference it with this.yourvariable. Change your buttonHandler to:
App.prototype.buttonHandler = function (e) {
e.preventDefault(); // prevent anchor default action
if( this.soundready )
this.toggle(); // toggle play/pause
};
On the appropiate place make this variable false to prevent the 'button' from working. When the button should work, change the variable to true. I think that should be just before done() in the code you have in your question, but you probably have a better idea in what order the code is executed.