I was trying to use querySelector to find active elements in a page. I assumed that a handler bound to a mousedown event on the document would trigger after the event had bubbled its way back from the target, which means the :active pseudoclass should already be applied.
document.addEventListener("mousedown",function(e){
console.log(document.querySelector("*:active"));// logs null
// expected value was the target of the mousedown event, that is,
console.log(e.target);
});
My question is: at what point exactly does the the :active pseudo-class apply? Note that when I log the value, the mousedown event has already triggered on the target.
See http://jsfiddle.net/tK67w/2/ for an example. An interesting thing to note here is that if you set a breakpoint within the handler, you can see the css rule I defined for a:active already applying, although querySelector is returning null
EDIT:
Credit goes to TJ for coming up with a much better demonstration. The problem still stands though: in browsers other than IE and Chrome, how can I get an HTMLCollection of all active elements as they become active?
I believe the issue is that as you're using querySelector, you're only getting the first active element. But your anchor is much further down the tree.
Update: Interesting, I'm not getting anything with Firefox or Opera, but I am with Chrome. The below are Chrome results. See more on that below.
Consider (live copy):
document.addEventListener("mousedown", handler, false);
function handler(e){
console.log(
"event " + e.type + ": " +
Array.prototype.join.call(document.querySelectorAll("*:active")));
}
When I click the anchor, I see this in the console:
event mousedown: [object HTMLHtmlElement],[object HTMLBodyElement],[object HTMLDivElement],http://fiddle.jshell.net/_display/#
Note the URL at the end, which is the default toString for HTMLAnchroElement instances, which is triggered by the join.
Since querySelectorAll is required to return the elements in document order, if you want the most specific active element, you'd use the last one. Example (live copy):
(function() {
document.addEventListener("mousedown",handler, false);
function handler(e){
var active = document.querySelectorAll("*:active");
var specific = active && active.length && active[active.length-1];
display("Most specific active element: " +
(specific ? specific.tagName : "(none)"));
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
})();
In my case (using Chrome), I see the tag name of the most specific element (the anchor if I click the link, etc.).
It seems like Chrome is following the spec and taht Firefox and Opera are not. From Section 6.6.1.2 of the CSS3 Selectors spec:
The :active pseudo-class applies while an element is being activated by the user. For example, between the times the user presses the mouse button and releases it.
It seems to me that :active should therefore apply in the above. This assertion is backed up if we use this CSS:
*:active {
background-color: red;
}
...with this JavaScript:
(function() {
document.addEventListener("mousedown", mouseDown, false);
document.addEventListener("mouseup", mouseUp, false);
function mouseDown(){
var active = document.querySelectorAll("*:active");
var specific = active && active.length && active[active.length-1];
display("Mouse down: Most specific active element: " +
(specific ? specific.tagName : "(none)"));
}
function mouseUp() {
display("Mouse up");
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
})();
Live Copy
With all three browsers I tried (Chrome, Firefox, Opera), the element gets a red background while the mouse is down, and goes white again when I release it; but the mousedown handler doesn't see the :active element in Firefox or Opera, just Chrome.
But I'm not a CSS specification lawyer. :-)
Seems to be set after the frame has been rendered, or maybe after the current execution queue, that's at least the case with Firefox.
Got results using setTimeout without delay (works with requestAnimationFrame too):
document.addEventListener('mousedown', e => {
setTimeout(_ => console.log(document.querySelectorAll(':active')));
});
Related
In this project am working on I have button that has and image inside it for styling purposes... I am trying to implement the functionality such that when the button is clicked I use event.target.parentNode.parentNode... a number of times to get an HTML5 data attribute(postid) from one the topmost parent divs, in Firefox I need to call parentNode 4 times and it works in Chrome it does not work I need to call parentNode 5 times, this means the two browsers are referring to different parents. I added $(event.target).parent().parent().css('border','5px solid black'); to find out which target each browser is considering. I found Chrome considers the event.target to be the image(which is what is actually clicked) but Firefox considers the button to be the event.target(which is where the event was actually attached)..... How can I make this button and its event code portable on both browsers without removing the image inside the button tag(that worked).
The code looks like
$(document).on('click', '.opinion', function(event) {
try {
post_id = event.target.parentNode.parentNode.parentNode.parentNode.dataset['postid'];
//$(event.target).parent().parent().css('border','5px solid black');
url = $('#opinionUrl' + post_id).val();
token = $('#token').val();
} catch (e) {
alert(e);
}
///Some more code to do stuff
});
<button class="icon opinion">
<img class="icon" src="{{URL::asset('assets/icons/haha.png')}}">
</button>
Why use jQuery and chain parentNode or .parent()?
.closest() will do the job for you https://api.jquery.com/closest/
post_id = $(this).closest("[data-postid]").data().postid;
I'm trying to show/hide some of text in a button.
the button is
<button id="SOS" onmouseover="show()" onmouseout="hide();">
<p>S.O.S</p>
<div id="sos_left"> <?=$text_to_show_hide?></div>
</button>
and the javascript code is
<script type="text/javascript">
function show()
{
sos_left=document.getElementById('sos_left');
alert("mouseover");
sos_left.style.color = "red";
sos_left.style.fontSize = "28";
}
function hide(){
sos_left=document.getElementById('sos_left');
alert("mouseout");
sos_left.style.color = "blue";
sos_left.style.fontSize = "0";
}
</script>
the thing is that the mouse out alerts even when I'm mouse overing.
NOTE: I can't use jquery because the site is vbulletin based and I use this code on one of the templates.
The problem is that mouseover and mouseout events bubble up, and this means that when your cursor enters and exits from elements that are descendants of your button, the event listener defined on the button is triggered too.
What you can do is to check if the element that generated the event is actually the <button> element. Fix the DOM like this:
<button id="SOS" onmouseover="show(event)" onmouseout="hide(event);">...
Then your JS code:
function show(e) {
if ((e.target || e.srcElement).id !== "SOS") return;
...
function hide(e) {
var tgt = e.target || e.srcElement;
if (tgt.id !== "SOS") return;
// If the cursor enter in one of the descendants, mouseout is fired, but
// we don't want to handle this
if (tgt.contains) {
if (tgt.contains(e.relatedTarget || e.toElement)) return;
} else if (this.compareDocumentPosition)
if (tgt.compareDocumentPosition(e.relatedTarget)
& Node.DOCUMENT_POSITION_CONTAINS) return;
...
In Internet Explorer (and now in Opera too) there are these events mouseenter and mouseleave that behave very similarly, but don't bubble up. For other browsers they're emulated in common frameworks like jQuery.
On a final note, I'd suggest you to use some more modern method to attach your event listeners than the traditional one. Plus, the way you define sos_left implies that it becomes a global variable. Use the keyword var in front of the definition.
you dont hide anything..
use display:none to "remove" element, or visibility:hidden to hide element.
to "re-add" the element, use display: block or visibility:visible, if you used visibility attribute to hide.
try each both to see the difference.
another problem is,
you try to use sos_left as variable, but you didn't declare it as variable.
use var sos_left instead of.
That's because you apply the event to the div not the button. Try this:
sos_button=document.getElementById('SOS');
I enjoy running custom scripts on pages that I do not own or control. Many times these pages have dynamically created content that I would like to apply a function to.
Is this possible? If so, how can I do this? Ideally I am looking for something live jQuery's live method, except instead of binding an event like click it would be more like an event that happens when the element is loaded in the DOM. load event would work for some elements but I don't think for all...
For this question, assume that you cannot look at or change the code that is inserting the DOM nodes. I would like a technique that I could use in a userscript or bookmarklet that could be used across multiple unrelated sites.
Edit: I am looking for something to use on my invert colors bookmarklet: JavaScript: Invert color on all elements of a page
Assuming you're running a browser like Firefox or Chrome, you could listen for the DOMNodeInserted event:
$(document).on('DOMNodeInserted', function(e) {
$(e.target).css({ color : '#c00' });
});
$('body').append('<div>test</div>');
Fiddle: http://jsfiddle.net/VeF6g/ (probably fails in IE)
Update:
The event is deprecated. You should use a MutationObserver:
var observer = new MutationObserver(function(mutationList) {
for (var mutation of mutationList) {
for (var child of mutation.addedNodes) {
child.style.color = '#c00';
}
}
});
observer.observe(document, {childList: true, subtree: true});
// ready? Then stop listening with
observer.disconnect();
More information here: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
If you don't have access to a library like jQuery, here is the syntax in only-JavaScript :
document.addEventListener('DOMNodeInserted', nodeInsert)
function nodeInsert () {
event.srcElement.style.color = '#ffffff'
}
I think it should work in most browsers.
Here is one nice way to handle the node insertion event. set animations for the element that you are going to add using css.
<style type="text/css">
/* set up the keyframes; remember to create prefixed keyframes too! */
#keyframes nodeInserted {
from { opacity: 0.99; }
to { opacity: 1; }
}
#parent > button {
animation-duration: 0.001s;
animation-name: nodeInserted;
}
</style>
#parent is the id for the div, in which i am going to append button's
dynamically.
<script type="text/javascript">
var insertListener = function(event){
if (event.animationName == "nodeInserted") {
// This is the debug for knowing our listener worked!
// event.target is the new node!
console.warn("Another node has been inserted! ", event, event.target);
}
}
document.addEventListener("animationstart", insertListener, false); // standard + firefox
document.addEventListener("MSAnimationStart", insertListener, false); // IE
document.addEventListener("webkitAnimationStart", insertListener, false); // Chrome + Safari
setInterval(function(){
var btn = document.createElement("BUTTON");
var t = document.createTextNode("CLICK ME"); // Create a text node
btn.appendChild(t);
document.getElementById("parent").appendChild(btn);
}, 2000);
</script>
This is rather difficult to accomplish, because there is no viable event for reacting to DOM changes. I would rather stick to event delegation instead.
But there are some events that you may find useful or interesting. On Mozilla Developer Network's list of DOM events you can see eg.:
DOMNodeInserted,
DOMNodeInsertedIntoDocument,
DOMNodeRemoved,
DOMElementNameChanged,
All of them however are marked as W3C drafts.
A generic way to detect node insertion is to use the DOMNodeInserted mutation event.
I am interested in every node actually. I am changing the colors of everything on the page.
For this purpose, a better solution is to inject a dynamic stylesheet, sufficed with the !important flag. If you only want to change colors, I recommend the Stylish extension (Stylish for Chrome) instead of GreaseMonkey.
I am adding a custom data attribute data-js-href to various HTML elements, and these elements should behave just like a link when clicked. If a link within such an element is clicked, the link should take precedence and the data-js-href functionality should be ignored, though. Furthermore, the solution also needs to work with elements that are dynamically added at a later time.
So far, I have come up with the following solution. It basically checks if the click was performed on a link, or any child element of a link (think <a href='…'><img src='…' alt='…' /></a>).
// Make all elements with a `data-js-href` attribute clickable
$$('body').addEvent('click:relay([data-js-href])',
function(event, clicked) {
var link = clicked.get('data-js-href');
if (link && !event.target.match('a')) {
var parents = event.target.getParents();
for (var i = 0; i < parents.length && parents[i] != clicked; i++) {
if (parents[i].match('a')) {
return;
}
}
document.location.href = link;
}
});
It works, but it feels very clumsy, and I think that there has to be a more elegant solution. I tried something along the lines of
$$('body').addEvent('click:relay([data-js-href] a)',
function(event, clicked) {
event.stopPropagation();
}
but to no avail. (I littered the code with some console.log() messages to verify the behavior.) Any idea is welcome.
you can do this with 2 delegated events - no reverse lookups and it's cheap as they will share the same event. the downside is, it is the same event so it will fire for both and there's no stopping it via the event methods (already bubbled, it's a single event that stacks up multiple pseudo event callbacks and executes them in order--the event has stopped but the callbacks continue) That's perhaps an inconsistency in mootools event vs delegation implementation but it's a subject of another issue.
Workarounds for now can be:
to have the 2 event handlers communicate through each other. It will scale and work with any new els added.
to add the delegators on 2 different elements. eg. document.body and #mainWrap.
http://jsfiddle.net/dimitar/J59PD/4/
var showURL = function(howLong) {
// debug.
return function() {
console.log(window.location.href);
}.delay(howLong || 1000);
};
document.id(document.body).addEvents({
"click:relay([data-js-href] a))": function(e) {
// performance on lookup for repeat clicks.
var parent = this.retrieve("parent");
if (!parent) {
parent = this.getParent("[data-js-href]");
this.store("parent", parent);
}
// communicate it's a dummy event to parent delegator.
parent.store("linkEvent", e);
// let it bubble...
},
"click:relay([data-js-href])": function(e) {
// show where we have gone.
showURL(1500);
if (this.retrieve("linkEvent")) {
this.eliminate("linkEvent");
return;
}
var prop = this.get("data-js-href");
if (prop)
window.location.href = prop;
}
});
Discussed this with Ibolmo and Keeto from the mootools team on IRC as well when my initial attempt failed to work and both callbacks fired despite the event.stop: http://jsfiddle.net/dimitar/J59PD/
As a result, there was briefly a ticket open on the mootools github issues: https://github.com/mootools/mootools-core/issues/2105 but it then went into a discussion of what the right thing to do from the library standpoint is and how viable it is to pursue changing the way things work so...
On the front page of a site I am building, several <div>s use the CSS :hover pseudo-class to add a border when the mouse is over them. One of the <div>s contains a <form> which, using jQuery, will keep the border if an input within it has focus. This works perfectly except that IE6 does not support :hover on any elements other than <a>s. So, for this browser only we are using jQuery to mimic CSS :hover using the $(#element).hover() method. The only problem is, now that jQuery handles both the form focus() and hover(), when an input has focus then the user moves the mouse in and out, the border goes away.
I was thinking we could use some kind of conditional to stop this behavior. For instance, if we tested on mouse out if any of the inputs had focus, we could stop the border from going away. AFAIK, there is no :focus selector in jQuery, so I'm not sure how to make this happen. Any ideas?
jQuery 1.6+
jQuery added a :focus selector so we no longer need to add it ourselves. Just use $("..").is(":focus")
jQuery 1.5 and below
Edit: As times change, we find better methods for testing focus, the new favorite is this gist from Ben Alman:
jQuery.expr[':'].focus = function( elem ) {
return elem === document.activeElement && ( elem.type || elem.href );
};
Quoted from Mathias Bynens here:
Note that the (elem.type || elem.href) test was added to filter out false positives like body. This way, we make sure to filter out all elements except form controls and hyperlinks.
You're defining a new selector. See Plugins/Authoring. Then you can do:
if ($("...").is(":focus")) {
...
}
or:
$("input:focus").doStuff();
Any jQuery
If you just want to figure out which element has focus, you can use
$(document.activeElement)
If you aren't sure if the version will be 1.6 or lower, you can add the :focus selector if it is missing:
(function ( $ ) {
var filters = $.expr[":"];
if ( !filters.focus ) {
filters.focus = function( elem ) {
return elem === document.activeElement && ( elem.type || elem.href );
};
}
})( jQuery );
CSS:
.focus {
border-color:red;
}
JQuery:
$(document).ready(function() {
$('input').blur(function() {
$('input').removeClass("focus");
})
.focus(function() {
$(this).addClass("focus")
});
});
Here’s a more robust answer than the currently accepted one:
jQuery.expr[':'].focus = function(elem) {
return elem === document.activeElement && (elem.type || elem.href);
};
Note that the (elem.type || elem.href) test was added to filter out false positives like body. This way, we make sure to filter out all elements except form controls and hyperlinks.
(Taken from this gist by Ben Alman.)
April 2015 Update
Since this question has been around a while, and some new conventions have come into play, I feel that I should mention the .live method has been depreciated.
In its place, the .on method has now been introduced.
Their documentation is quite useful in explaining how it works;
The .on() method attaches event handlers to the currently selected set
of elements in the jQuery object. As of jQuery 1.7, the .on() method
provides all functionality required for attaching event handlers. For
help in converting from older jQuery event methods, see .bind(),
.delegate(), and .live().
So, in order for you to target the 'input focused' event, you can use this in a script. Something like:
$('input').on("focus", function(){
//do some stuff
});
This is quite robust and even allows you to use the TAB key as well.
I'm not entirely sure what you're after but this sounds like it can be achieved by storing the state of the input elements (or the div?) as a variable:
$('div').each(function(){
var childInputHasFocus = false;
$(this).hover(function(){
if (childInputHasFocus) {
// do something
} else { }
}, function() {
if (childInputHasFocus) {
// do something
} else { }
});
$('input', this)
.focus(function(){
childInputHasFocus = true;
})
.blur(function(){
childInputHasFocus = false;
});
});
An alternative to using classes to mark the state of an element is the internal data store functionality.
P.S.: You are able to store booleans and whatever you desire using the data() function. It's not just about strings :)
$("...").mouseover(function ()
{
// store state on element
}).mouseout(function ()
{
// remove stored state on element
});
And then it's just a matter of accessing the state of elements.
if anyone cares there is a much better way to capture focus now, $(foo).focus(...)
http://api.jquery.com/focus/
Have you thought about using mouseOver and mouseOut to simulate this. Also look into mouseEnter and mouseLeave
Keep track of both states (hovered, focused) as true/false flags, and whenever one changes, run a function that removes border if both are false, otherwise shows border.
So: onfocus sets focused = true, onblur sets focused = false. onmouseover sets hovered = true, onmouseout sets hovered = false. After each of these events run a function that adds/removes border.
As far as I know, you can't ask the browser if any input on the screen has focus, you have to set up some sort of focus tracking.
I usually have a variable called "noFocus" and set it to true. Then I add a focus event to all inputs that makes noFocus false. Then I add a blur event to all inputs that set noFocus back to true.
I have a MooTools class that handles this quite easily, I'm sure you could create a jquery plugin to do the same.
Once that's created, you could do check noFocus before doing any border swapping.
There is no :focus, but there is :selected
http://docs.jquery.com/Selectors/selected
but if you want to change how things look based on what is selected you should probably be working with the blur events.
http://docs.jquery.com/Events/blur
There is a plugin to check if an element is focused: http://plugins.jquery.com/project/focused
$('input').each(function(){
if ($(this) == $.focused()) {
$(this).addClass('focused');
}
})
I had a .live("focus") event set to select() (highlight) the contents of a text input so that the user wouldn't have to select it before typing a new value.
$(formObj).select();
Because of quirks between different browsers, the select would sometimes be superseded by the click that caused it, and it would deselect the contents right after in favor of placing the cursor within the text field (worked mostly ok in FF but failed in IE)
I thought I could solve this by putting a slight delay on the select...
setTimeout(function(){$(formObj).select();},200);
This worked fine and the select would persist, but a funny problem arose.. If you tabbed from one field to the next, the focus would switch to the next field before the select took place. Since select steals focus, the focus would then go back and trigger a new "focus" event. This ended up in a cascade of input selects dancing all over the screen.
A workable solution would be to check that the field still has focus before executing the select(), but as mentioned, there's no simple way to check... I ended up just dispensing with the whole auto highlight, rather than turning what should be a single jQuery select() call into a huge function laden with subroutines...
What I wound up doing is creating an arbitrary class called .elementhasfocus which is added and removed within the jQuery focus() function. When the hover() function runs on mouse out, it checks for .elementhasfocus:
if(!$("#quotebox").is(".boxhasfocus")) $(this).removeClass("box_border");
So if it doesn't have that class (read: no elements within the div have focus) the border is removed. Otherwise, nothing happens.
Simple
<input type="text" />
<script>
$("input").focusin(function() {
alert("I am in Focus");
});
</script>