This question already has answers here:
How to check if element is visible after scrolling?
(46 answers)
Closed 1 year ago.
Basically, I am wondering if there is a way to automatically run a function when an element becomes hidden or visible, not on a user click but automatically in another script.
I don't want this to just run one time, because the elements (such as a slider) constantly change from visible to hidden.
Would this be something that jQuery can do with bind? Such as binding the element's visibility to a function (I don't know how to write this)
If you need me to elaborate more on what I'm trying to do, let me know. Thanks
Pseudocode:
$('#element').bind('display:none', function);
function(){
//do something when element is display:none
}
$('#element').bind('display:block', function2);
function2(){
//do opposite of function
}
There are no events in JQuery to detect css changes.
Refer here: onHide() type event in jQuery
It is possible:
DOM L2 Events module defines mutation events; one of them - DOMAttrModified is the one you need. Granted, these are not widely implemented, but are supported in at least Gecko and Opera browsers.
Source: Event detect when css property changed using Jquery
Without events, you can use setInterval function, like this:
var maxTime = 5000, // 5 seconds
startTime = Date.now();
var interval = setInterval(function () {
if ($('#element').is(':visible')) {
// visible, do something
clearInterval(interval);
} else {
// still hidden
if (Date.now() - startTime > maxTime) {
// hidden even after 'maxTime'. stop checking.
clearInterval(interval);
}
}
},
100 // 0.1 second (wait time between checks)
);
Note that using setInterval this way, for keeping a watch, may affect your page's performance.
7th July 2018:
Since this answer is getting some visibility and up-votes recently, here is additional update on detecting css changes:
Mutation Events have been now replaced by the more performance friendly Mutation Observer.
The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature which was part of the DOM3 Events specification.
Refer: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
(function() {
var ev = new $.Event('display'),
orig = $.fn.css;
$.fn.css = function() {
orig.apply(this, arguments);
$(this).trigger(ev);
}
})();
$('#element').bind('display', function(e) {
alert("display has changed to :" + $(this).attr('style') );
});
$('#element').css("display", "none")// i change the style in this line !!
$('#element').css("display", "block")// i change the style in this line !!
http://fiddle.jshell.net/prollygeek/gM8J2/3/
changes will be alerted.
Tried this on firefox, works http://jsfiddle.net/Tm26Q/1/
$(function(){
/** Just to mimic a blinking box on the page**/
setInterval(function(){$("div#box").hide();},2001);
setInterval(function(){$("div#box").show();},1000);
/**/
});
$("div#box").on("DOMAttrModified",
function(){if($(this).is(":visible"))console.log("visible");});
UPDATE
Currently the Mutation Events (like DOMAttrModified used in the
solution) are replaced by MutationObserver, You can use that to
detect DOM node changes like in the above case.
I just Improved ProllyGeek`s answer
Someone may find it useful.
you can access displayChanged(event, state) event when .show(), .hide() or .toggle() is called on element
(function() {
var eventDisplay = new $.Event('displayChanged'),
origShow = $.fn.show,
origHide = $.fn.hide;
//
$.fn.show = function() {
origShow.apply(this, arguments);
$(this).trigger(eventDisplay,['show']);
};
//
$.fn.hide = function() {
origHide.apply(this, arguments);
$(this).trigger(eventDisplay,['hide']);
};
//
})();
$('#header').on('displayChanged', function(e,state) {
console.log(state);
});
$('#header').toggle(); // .show() .hide() supported
A catch-all jQuery custom event based on an extension of it's core methods like it was proposed by different people in this thread:
(function() {
var ev = new $.Event('event.css.jquery'),
css = $.fn.css,
show = $.fn.show,
hide = $.fn.hide;
// extends css()
$.fn.css = function() {
css.apply(this, arguments);
$(this).trigger(ev);
};
// extends show()
$.fn.show = function() {
show.apply(this, arguments);
$(this).trigger(ev);
};
// extends hide()
$.fn.hide = function() {
hide.apply(this, arguments);
$(this).trigger(ev);
};
})();
An external library then, uses sth like $('selector').css('property', value).
As we don't want to alter the library's code but we DO want to extend it's behavior we do sth like:
$('#element').on('event.css.jquery', function(e) {
// ...more code here...
});
Example: user clicks on a panel that is built by a library. The library shows/hides elements based on user interaction. We want to add a sensor that shows that sth has been hidden/shown because of that interaction and should be called after the library's function.
Another example: jsfiddle.
I like plugin https://github.com/hazzik/livequery It works without timers!
Simple usage
$('.some:visible').livequery( function(){ ... } );
But you need to fix a mistake. Replace line
$jQlq.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');
to
$jQlq.registerPlugin('show', 'append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');
Related
I've inherited some JS (that I can't change) that fires a bunch of events:
jQuery(document).trigger('section:' + section);
// where "section" changes dynamically
And I want to observe for ALL of these events, and parse out the value for section, and do something different depending on it's contents.
If it didn't change I could do this:
jQuery(document).on('section:top', doStuff );
But how do I observe an event if I only know the first part of that event name?
You cannot listen for all events in the style of $().on('section:*'), unfortunately. If you can change the code, I would do the following:
jQuery(document).trigger({
type: 'section',
section: section
});
Then you listen for it and don't need to parse anything out
jQuery(document).on('section', function(e){
if (e.section === 'top') {
// Something happened to the top section
}
});
If you want to minimize your code changes, leave the old event in there, that way existing code will be unaffected.
A different approach would be to use event namespaces.
jQuery(document).trigger('section.' + section);
jQuery(document).on('section', function(e){
if (e.namespace === 'top') {
// Something happened to the top section
}
});
I, however, prefer the first approach because event namespaces are most commonly used for a different purpose: to be able to remove events without being forced to keep a reference to the handler itself. See http://css-tricks.com/namespaced-events-jquery/ and http://ejohn.org/apps/workshop/adv-talk/#13. I prefer to use styles that other developers are used to, if they do the job.
I'm really not sure about your use case but you could overwrite $.fn.trigger method:
(function ($) {
var oldTrigger = $.fn.trigger;
$.fn.trigger = function () {
if (arguments[0].match(/^section:/)) {
doStuff(arguments[0].split(':')[1]);
}
return oldTrigger.apply(this, arguments);
};
})(jQuery);
var section = "top";
jQuery(document).trigger('section:' + section);
function doStuff(section) {
alert(section);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Here's what I ended up doing.
It's a combination of Juan Mendes's solution, and using a method from the prototype library
Originally, there was a function that ran this code:
myObject.adjustSection(section) {
jQuery(document).trigger('section:' + section);
}
// I couldn't edit this function
So I extended the function with prototype's wrap method, since my project used prototype as well as jQuery.
// My custom function wrapper
// extend adjustSection to include new event trigger
myObject.prototype.adjustSection = myObject.prototype.adjustSection.wrap(
function(parentFunction, section) {
// call original function
parentFunction(section);
// fire event w/section info
jQuery(document).trigger({
type: 'adjustSection',
section: section
});
}
);
Then, it runs the original one, but also fires my custom event that includes the section info.
Now, I can do this to observe that event and get the section type:
jQuery(document).on('adjustSection', function(event) {
event.section; // contains the section I need
});
Of course, this means I have to utilize both prototype and jquery within the same scope, which isn't the best thing in the world. But it worked.
Well, this is not my first jQuery plugin but the first I think others would benefit from. So for me its the first time to make sure every possible application works as predicted.
My plugin handles modal less overlays and therefore has to listen for some events. If the plugin is re-initialized the old listeners seem to be still in place and cause malfunction.
My solution to this looks like this:
var oldSettings = $(_this).data('mlOverlaySettings');
if(oldSettings) {
$(oldSettings.target).unbind('click.mlOverlay');
$(document).unbind('click.mlOverlay');
$(document).unbind('keyup.mlOverlay');
}
For me it seems like the problem has anything todo with saving the plugin state with the $.data function and not properly released references.
$(_this).data('mlOverlaySettings', settings);
$(_this).data('mlIsOverlayVisible', false);
Additional resources
Plugin Demo
Plugin documentation and full source code
Unbinding the old event's is somewhat ugly. Am I doing something wrong here or is this always required?
You should prevent reinitialization in the jQuery boilerplate method:
(function($) {
var PLUGIN_IDENTIFIER = "my-plugin";
...plugin definition etc
//jQuery boilerplate
$.fn.myPlugin = function(opts) {
return this.each(function() {
var instance = $(this).data(PLUGIN_IDENTIFIER);
//Prevent reinit on this element
if (!instance) {
instance = new MyPlugin(this, opts);
$(this).data(PLUGIN_IDENTIFIER, instance);
}
//Method call
if (typeof opts === "string") {
instance[opts].apply(instance, [].slice.call(arguments, 1));
}
});
};
})();
You should always provide a "destroy" method that removes the .data and event listeners it added too. So it's only possible to reinitialize after calling "destroy" which conveniently removed the event listeners too.
Here's an example implementation of a pretty standard destroy method:
function MyPlugin(element, opts) {
this.element = $(element);
this.opts = $.extend(defaults, $(element).data(), opts);
//Other instance state
//absolutely do not use $.data for this, you should only occupy one $.data
//slot for your plugin for the same reason you only occupy one slot on
//$.fn
}
MyPlugin.prototype.destroy = function() {
this.element.removeData(PLUGIN_IDENTIFIER);
this.element.off(".myplugin"); //Remove all events off the element that belong to the plugin's namespace
//.remove() any helper elements created by the plugin
this.element = null;
};
As the question states, what I'm attempting to do is have a function that is called when a DOM element is removed from the DOM, much like a destructor.
I looked into unload, but from my understanding that's only called when the browser navigates away from the page.
Thanks in advance!
It is possible to use the special events to build removal tracking. I've created an example that allows to bind to an removed event. Note that this approach only works when the removal is initiated by jQuery (calling $(…).remove()) for example. For a more general solution use DOMNodeRemoved but that wouldn't work in IE.
To enable tracking for an element call $(…).trackRemoval() and the element will fire a removed event when you remove it or one of its parents.
// Create a scope so that our variables are not global
(function(){
/**
* True while unbinding removal tracking
*/
var isUntracking = false;
/**
* A reference that is only known here that nobody else can play with our special event.
*/
var dummy = function(){};
/**
* Special event to track removals. This could have any name but is invoked during jQuery's cleanup on removal to detach event handlers.
*/
jQuery.event.special.elementRemoved = {
remove: function(o){
if(o.handler===dummy && !isUntracking){
$(this).trigger('removed');
}
}
};
/**
* Starts removal tracking on an element
*/
jQuery.fn.trackRemoval = function(){
this.bind('elementRemoved', dummy);
};
/**
* Stops removal tracking on an element
*/
jQuery.fn.untrackRemoval = function(){
isUntracking = true;
this.unbind('elementRemoved', dummy);
isUntracking = false;
};
})();
The jsFiddle contains sample code for usage.
I don't know if it's that what you're looking for. Not really pretty code, just to show what I would like to use:
// Just for this script's sake, you'll want to do it differently
var body = $("body");
var element = $('#loadingBlock');
// Bind the "destructor" firing event
body.bind("elementDeleted", function (element) {
// your "destructor" code
});
// Trigger the event, delete element, can be placed in a function, overloaded, etc.
body.trigger("elementDeleted", element);
element.remove();
There are of course solutions based on watching the DOM directly but the problem is the browser compatibility. You should probably check out mutation events.
Try this:
(function($) {
var _remove = $.fn.remove;
$.fn.remove = function() {
this.trigger('remove'); // notify removal
return _remove.apply(this, arguments); // call original
};
})(jQuery);
usage:
$(element).bind('remove', callback);
...
// some time later
$(element).remove();
See a working example at http://jsfiddle.net/alnitak/25Pnn/
You could always Overload the Remove function (all javascript objects can be dynamically changes in runtime) and replace it with your own function that triggers an event you can use to react to remove.
According to the jQuery plugin development guides from the Internet, the common practice of developing a jQuery plugin would be:
(function($) {
$.fn.myplugin = function(options){
//...
//Plugin common characteristic
//e.g. default settings
//...
//Attach to each desired DOM element
return this.each(function(){
//Instantiation stuff...
});
}
})(jQuery);
$(document).ready(function(){
$(".someclass").myplugin();
})
It seems to me that, if the elements with class "someclass" have been attached to another plugin, once those elements are going to attach to "myplugin", they will lose the original relationship to the previously attached plugin. I'm not sure if my thinking is completely correct. Please advise if any mis-understood.
Thank you!
William Choi
An element isn't "attached" to a plug-in. A plug-in just adds further methods to the jQuery wrapper for a matched set of elements. So just as the jQuery wrapper has parent and find, it also has the plug-in's myplugin method. These can all coexist as long as there are no naming conflicts.
It's true that if two different plug-ins both try to change something about the elements that cannot be two things at once (a plug-in that changes the foreground color to "blue" and another changing the foreground color to "red"), then they'd collide if you called both of the two plug-ins methods on the same element set. But that's just like two calls to css.
In particular, remember that there can be multiple event handlers assigned to the same event on the same element, so plug-ins that hook events need not necessarily conflict with one another (unless one of them stops the event during handling).
Here's an example of two plug-ins that act on the matched set of elements, but in non-conflicting ways:
plugin1.js:
(function($) {
$.fn.foo = function() {
this.css("background-color", "#b00");
return this;
};
})(jQuery);
plugin2.js:
(function($) {
$.fn.bar = function() {
this.css("color", "white");
return this;
};
})(jQuery);
Usage:
$("#target").foo();
$("#target").bar();
or even
$("#target").foo().bar();
Live example
Now, if both the foo and bar plug-ins tried to set the foreground color, the one called later would win.
Here's an example of a pair of plug-ins that both want to handle the click event, but do so in a cooperative way:
plugin1.js:
(function($) {
$.fn.foo = function() {
this.click(function() {
$("<p>Click received by foo</p>").appendTo(document.body);
});
return this;
};
})(jQuery);
plugin2.js:
(function($) {
$.fn.bar = function() {
this.click(function() {
$("<p>Click received by bar</p>").appendTo(document.body);
});
return this;
};
})(jQuery);
Usage:
jQuery(function($) {
$("#target").foo().bar();
});
Live example
There's no magical relationship going on. There's no central registry or snap-ins that "belong" to any one element or to any one plug-in.
Javascript objects are just hacked-up functions; when you "attach a plugin" to an element, you're just calling some third-party library function that does something to that element, and possibly stores some internal data to assist with its animation throughout the session.
So there is nothing legally stopping you from "attaching" multiple plug-ins to the same element, though of course whether they'll be logically compatible is quite another question.
One of the nice things about MooTools, is that it lets you easily assign/fire events to objects, for example:
var playerSingleton = new (new Class({
Implements: [Events],
initialize: function() {},
setVolume: function() {
// do some stuff..
this.fireEvent('volumeChanged')
}
}));
// Somewhere else...
playerSingleton.addEvent('volumeChanged', function() {
// do something when volume changes
});
playerSingleton.setVolume(75);
// bam our event fires.
How would something like this be done with jQuery?
I know there's .bind and .trigger, but it seems like the only way to do this is to bind/fire events to the window object:
$(window).bind('volumeChanged', fn);
Is there anything better than this, more like the MooTools approach?
jQuery's bind and trigger seem to work on normal objects. Haven't seen the source code to see how it works (if it's part of the public API or not), but it does. See this discussion from last year poking around the same idea.
player is a regular object, with methods to set volume, and add listeners for volume change. an example here.
var player = {
setVolume: function() {
$(this).trigger("volumeChanged");
},
addVolumeChangeHandler: function(fn) {
$(this).bind("volumeChanged", fn);
}
};
// add a listener
player.addVolumeChangeHandler(function() {
alert("volume has been changed");
});
// change volume (should fire the attached listener)
player.setVolume(); // alerts "volume has been changed"