I would like to cancel every event handler defined in a page with JavaScript. In other words, if the page previously set up many event listeners on many different DOM levels, this method would simply delete every one of them. Is there a way to do this in JavaScript?
For clearing all the events dynamically attached to elements within the body of the page, you can do:
document.body.innerHTML = document.body.innerHTML;
For clearing events attached to the window object, you can do:
window.onscroll = function() {};
and so on..
Nope there's no native event list to see what is bound to what. JQuery has its own, if you want better event management.
Here's how it's done in JQuery:
(function($) {
$.eventReport = function(selector, root) {
var s = [];
$(selector || '*', root).andSelf().each(function() {
var e = $.data(this, 'events');
if(!e) return;
s.push(this.tagName);
if(this.id) s.push('#', this.id);
if(this.className) s.push('.', this.className);
for(var p in e) s.push('\n', p);
s.push('\n\n');
});
return s.join('');
}
$.fn.eventReport = function(selector) {
return $.eventReport(selector, this);
}
})(jQuery);
You can call it various ways to suit your needs:
// all events
alert($.eventReport());
// just events on inputs
alert($.eventReport('input'));
// just events assigned to this element
alert($.eventReport('#myelement'));
// events assigned to inputs in this element
alert($.eventReport('input', '#myelement'));
alert($('#myelement').eventReport('input')); // same result
// just events assigned to this element's children
alert($('#myelement').eventReport());
alert($.eventReport('*', '#myelement'); // same result
From there, unbinding is simple.
Related
I would like to separate my code from my UI in my web page and I would like to know if there is an event I can use that will tell me if an element is created so I can assign an event handler to it at the time it's created.
For example, if I had two elements, and they each have event handlers, how can I tell when those elements have been created and then add them?
What I have so far is to add a listener to the document to a addElement event, if one exists. I don't know if one exists. If it does than, check if the element matches any existing id's in a dictionary of functions. So something like this:
Code from separate script file:
<script>
// event handler for BorderContainer1282
function doSomething1() {
}
// event handler for Button925
function doSomething2() {
}
var clickHandlersDictionary = {};
clickHandlersDictionary["BorderContainer1282"] = doSomething1;
clickHandlersDictionary["Button925"] = doSomething2;
function createComplete() {
document.addEventListener("added", addElementEventHandlers);
}
function addElementEventHandlers(event) {
var element = event.currentTarget;
if (clickHandlersDictionary[element.id]!=null) {
element.addEventListener("click", clickHandlersDictionary[element.id]);
}
}
</script>
And the HTML:
<body onReadyState="creationComplete()">
<div id="BorderContainer1282">
<input id="Button925" type="button" value="Button">
</div >
</body>
I think what I might be trying to do is dependency injection but I'm not sure. I may eventually try and support states and that case event listeners would need to be added and also removed when an HTML element is created.
You can use a MutationObserver. MutationObservers allow you to detect changes to DOM and react accordingly.
Take the following for example:
/*****
Setting up some of your code
*****/
function doSomething1() {
console.log('Do something 1');
}
function doSomething2() {
console.log('Do something 2');
}
var dictionary = {
BorderContainer1282: doSomething1,
Button925: doSomething2
};
/*****
Setting up the observer
*****/
// select the target node
var target = document.getElementById('target');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
Object.keys(dictionary).forEach(function(key) {
var query = target.querySelector('#' + key);
if (query) {
query.addEventListener('click', dictionary[key]);
}
});
});
});
// pass in the target node, as well as the observer options
observer.observe(target, {
childList: true
});
/*****
Just adds to the DOM when you click on the button
*****/
function addToDom(button) {
button.parentNode.removeChild(button);
var parentDiv = document.createElement('div');
parentDiv.textContent = 'Parent';
var childDiv1 = document.createElement('div');
childDiv1.textContent = 'BorderContainer1282 (Click Me)';
childDiv1.id = 'BorderContainer1282';
var childDiv2 = document.createElement('div');
childDiv2.textContent = 'Button925 (Click Me)';
childDiv2.id = 'Button925';
parentDiv.appendChild(childDiv1);
parentDiv.appendChild(childDiv2);
target.appendChild(parentDiv);
}
<button onclick="addToDom(this)">Add to DOM</button>
<div id="target"></div>
The MutationObserver picks up changes to the target element (which could be any element, including just the body). In this particular case, it searches the added nodes for any matches to elements in the dictionary. If if finds them, it adds the corresponding function to the click listener.
You need to use event delegation.
If your target div, where you expect new elements to be created, has an id of tgt, you could code an event listener like so:
document.getElementById("tgt").addEventListener("click",function(event){
alert(event.target.id);
});
I'll try to explain my problem:
I have a website where the user dynamically adds elements. They all belong to the "toBuy" class. Whenever a new element is added to this class I need to attach a click-handler to only this element but not to all others. To keep my code clean I want to have a function that does this work. Here is what i've tried:
this is how the stuff is added:
$("#addItemButton").click(function(){
var item= $('#item').val();
$('#item').val("");
var quantity= $('#quantity').val();
$('#quantity').val("");
var comment=$('#addComment').val();
$('#addComment').val("");
//construct new html
var newitem="<div class='toBuyItem'><div class='item'>";
newitem+=item;
newitem+="</div><div class='quantity'>";
newitem+=quantity;
newitem+="</div><div class='comment'><img src='img/comment";
if(comment==""){
newitem+="_none"
}
newitem+=".png' alt='Comment'></div><div class='itemComment'>"
newitem+=comment;
newitem+="</div></div>";
$("#toBuyItems" ).prepend( newitem );
toggle("#addItemClicked");
initializeEventListeners();
});
then this is the initializeEventListeners function (which I also run when the page loads so that the existing elements have the event handlers already:
function initializeEventListeners(){
$(".toBuyItem").click(function(){
console.log($(this).html());
console.log($(this).has('.itemComment').length);
if($(this).has('.itemComment').length != 0){
console.log("toggling");
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
});
}
function toggle(item){
$( item ).slideToggle(500);
}
now apparently what happens is that when a new element is added the existing elements get a new event handler for clicking (so they have it twice). Meaning that they toggle on and off with just one click. Probably it's damn simple but I cannot wrap my head around it....
EDIT:
so this works:
$(document).on('click', '.toBuyItem', function(){
if($(this).has('.itemComment').length != 0){
console.log("toggling");
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
});
Use jquery's on method. This way you have to add event only once. This will be added automatically to dynamically added elements.
$(document/parentSelector).on('click', '.toBuyItem', function() {
// Event handler code here
});
If you are using parentSelector in the above syntax, it has to be present at the time of adding event.
Docs: https://api.jquery.com/on
You can use jQuery.on method. It can attach handlers to all existing in the DOM and created in future tags of the selector. Syntax is as follows:
$(document).on('click', '.toBuyItem', function(){
//do onClick stuff
})
As others have suggested, you can delegate click handling to document or some suitable container element, and that's probably what I would do.
But you could alternatively define a named click handler, which would be available to be attached to elements already present on page load, and (scope permitting) to elements added later.
You might choose to write ...
function buy() {
if($(this).has('.itemComment').length != 0) {
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
}
function initializeEventListeners() {
$(".toBuyItem").on('click', buy);
}
$("#addItemButton").on('click', function() {
var item = $('#item').val(),
quantity = $('#quantity').val(),
comment = $('#addComment').val();
$('#item', '#quantity', '#addComment').val("");
//construct and append a new item
var $newitem = $('<div class="toBuyItem"><div class="item">' + item + '</div><div class="quantity">' + quantity + '</div><div class="comment"><img alt="Comment"></div><div class="itemComment">' + comment + '</div></div>').prependTo("#toBuyItems").on('click', buy);// <<<<< here, you benefit from having named the click handler
$newitem.find(".comment img").attr('src', comment ? 'img/comment.png' : 'img/comment_none.png');
toggle("#addItemClicked");
});
When my loadNav() function loops through the nav array and assigns a click listener to a dynamically-created element, all seems well. If I break on the first loop through the navItem.id is 'newGL' and the second time through it is 'litGL', as it should be.
But when I check the listeners after the loop is done, they both have an id of 'litGL' (and also a type of 'b'). But the elements themselves have the correct navItem.text, so I guess I’m not understanding how variables are assigned within the passed function to the .click method.
Any hints why my first click listener is getting overwritten with the values meant for the second one?
var nav = [
{
id:'newGL',
text:'new',
type:'a'
},
{
id:'litGL',
text:'lit',
type:'b'
}
]
function loadNav(nav){
for(item in nav){
var navItem = nav[item];
var element = $("<div>" + navItem.text + "</div>");
element.click(function(){
getContent(navItem.id,navItem.type);
//in practice I'm getting two click listeners with a navItem.id of 'litGL' and a navItem.type of 'b'
});
$('#horzNav').append(element);
}
}
The immediate problem is that the navItem is not the same value when the event is triggered much later. To fix this common issue use a scoping IIFE (immediately invoked function expression):
function loadNav(nav){
for(item in nav){
var navItem = nav[item];
var element = $("<div>" + navItem.text + "</div>");
(function(navItem){
element.click(function(){
getContent(navItem.id,navItem.type);
//in practice I'm getting two click listeners with a navItem.id of 'litGL' and a navItem.type of 'b'
});
})(navItem);
$('#horzNav').append(element);
}
}
Better solutions involve putting the require attributes into the injected elements, then extracting them at event time. This simplifies the event handler and removes the dependency on the original navItem variable.
e.g. something like:
function loadNav(nav){
for(var i = 0; i < nav.length; i++){
var navItem = nav[i];
var element = $("div", {id: navItem.id}).html(navItem.text).data('type', navitem.type ).appendTo('#horzNav');
}
}
and use a delegated event handler with a selector:
$(function () {
$(document).on("click", ".navItem", function () {
getContent($(this).attr("id"), $(this).data("type"));
});
});
This works by listening for the event (e.g. click) to bubble up to a non-changing ancestor element (e.g. document), then applying the selector to the items in the bubble-chain, then applying the function only to those matching element that caused the event.
The upshot of this is the the items only need to match at event time and not when the event was registered. Great for dynamically added items.
document is the best default if no other ancestor else is closer/convenient. Do not use body for delegated events as it has a bug (styling can cause it to not get mouse events).
Your issue is scoping, but you really need to delegate that event (otherwise if you had 100 elements, that'd be 100 events :/), then you can specify the properties you need within the markup using data attributes, something like:
$(function () {
$(document).on("click", ".navItem", function () {
getContent($(this).attr("id"), $(this).data("type"));
});
});
Then your loadNav would be:
function loadNav(nav){
for(item in nav){
var navItem = nav[item];
var element = $(document.createElement("div"));
element.html(navItem.text);
element.prop("id", navItem.id);
element.data("type", navItem.type);
$('#horzNav').append(element);
}
}
Currently dojo uses on method to connect event to handler.
btn = new Button();
btn.on('click', function () {console.log('do something');});
this will call the attached function when the button gets clicked.
however, according to the documents, removing existing handlers should be done in the following way
handler = btn.on('click', function () {console.log('do something');});
handler.remove();
this is not the way I want to remove event handler.
I do not store the handler reference anywhere. But I want to add a new 'click' event by doing
btn.on('click', function () {console.log('do something different');});
so that it replaces the existing 'click' event handler and add a new one.
Is there any way to achieve what I want?
Thanks!
That's not possible, the framework tells you to do it in the way by creating a reference to the event handler. This is similar to how other frameworks like jQuery work.
jQuery has of course a mechanism to remove all event handlers by using the off() function, but that's not available in Dojo either. Like Chris Hayes suggested in the comments, you can implement such a feature by yourself, either by wrapping it inside another module, or by using aspects on the dojo/on module.
For example, you can wrap it inside a new module:
// Saving the event handlers
var on2 = function(dom, event, callback) {
on2.handlers = [];
if (on2.handlers[event] === undefined) {
on2.handlers[event] = [];
}
var handler = on(dom, event, callback);
on2.handlers[event].push({
node: dom,
handler: handler
});
return handler;
};
// Off functionality
lang.mixin(on2, on, {
off: function(dom, event) {
if (this.handlers[event] !== undefined) {
array.forEach(this.handlers[event], function(handler) {
if (handler.node === dom) {
handler.handler.remove();
}
});
}
}
});
And then you can use it:
on2(dom.byId("test"), "click", function() {
console.log("test 1 2 3"); // Old event handler
});
on2.off(dom.byId("test"), "click"); // Remove old event handlers
on2(dom.byId("test"), "click", function() {
console.log("test 4 5 6"); // New event handler
});
This should work fine, as you can see in this fiddle: http://jsfiddle.net/X7H3F/
btn = new Button();
btn.attr('id','myButton');
query("#myButton").on('click', function () {console.log('do something');});
Do the same thing when you want to replace your handler. Like,
query("#myButton").on('click', function () {console.log('do something different');});
Hope that helps :)
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.