Erratic mouseover behavior with nested items inside mouseover layer - javascript

So let's say we have:
A container for everything
A baseDiv inside that container
//let's create a base layer
var container = document.getElementById('container')
var baseDiv = document.createElement('div')
baseDiv.id = 'baseDiv'
baseDiv.innerText = 'this is the base div'
baseDiv.addEventListener('mouseover', createLayer)
container.appendChild(baseDiv)
When the user mouses over:
A layerOnTop, of the same size is put on top of the baseDiv.
When the user mouses out:
The layerOnTop is removed.
function createLayer(){
console.log('creating layer')
layerOnTop = document.createElement('div')
layerOnTop.id = 'layerOnTop'
layerOnTop.addEventListener('mouseout',
function(){
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})
container.appendChild(layerOnTop) }
Simple and works great.
However, when layerOnTop contains elements as well (buttons, inputs), the behavior gets very erratic and starts flicking as you're technically exiting the layerOnTop.
//it contains two textareas
layerOnTop.appendChild(document.createElement('textarea'))
layerOnTop.appendChild(document.createElement('textarea'))
I wish I could use mouseenter but it doesn't seem to be supported by Chrome.
Here's my jsfiddle: http://jsfiddle.net/DjRBP/
How can I stop this? I wish I could merge the textareas and layerOnTop into one large mouseover-handling conglomerate.

You need to check in your mouse out event that it's actually leaving the element. Change your mouseout function to:
function(event) {
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
// We're not actually leaving the parent node so don't remove layer
return;
}
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})

Related

JavaScript - weird bug doesn't remove element

I have a weird problem with my simple to-do app. Its main features are adding new elements to "ul" and clicking on "li" to change its class. After clicking element it also adds an "i" with font-awesome icon and after clicking it again it should remove it. It works fine when I click added elements from top to bottom, but when I click from bottom to top, "i" is removed only from the first element from top. I can't figure out why is that, so I would appreciate any help.
Here is live demo where you can reproduce this bug:
https://michalgrochowski.github.io/to-do/
And here is the code responsible for that action:
document.getElementById("list").addEventListener("click", function(e) {
if (e.target && e.target.matches("li.item")) {
e.target.className = "itemDone";
var check = document.createElement("i");
check.className = "fa fa-check done";
e.target.appendChild(check);
} else if (e.target && e.target.matches("li.itemDone")) {
e.target.className = "item";
var done = document.getElementsByClassName("done");
var i;
for (i = 0; i < done.length; i++) {
e.target.removeChild(done[i]);
};
};
});
I've tested it in firefox and chrome, so it's not browser-related, it must be something in the code. I was thinking maybe the problem is with the loop inside the function, but I don't know how to change it.
var done = document.getElementsByClassName("done"); gets a list of all the .done elements in your document (including those who aren't child elements of e.target). So when executing e.target.removeChild(done[i]) if the current done[i] is a child of e.target, it will be removed, otherwise it will throw an error saying that the element done[i] is not a child of e.target. Try this:
var done = e.target.getElementsByClassName("done");
Note: if there could be sub-elements (children of children ...) of e.target that have the class .done. This will throw an error too. Because then the sub-children won't be direct children of e.target. removeChild work only on direct children. You can avoid trouble by using this:
var done = e.target.children; // to get the direct children only
var i;
for (i = 0; i < done.length; i++) {
if(done[i].matches('.done') // if it matches the criteria
e.target.removeChild(done[i]); // remove it
}

Find Caret Position Anywhere on page

I'm a part time newish developer working on a Chrome extension designed to work as an internal CR tool.
The concept is simple, on a keyboard shortcut, the extension gets the word next to the caret, checks it for a pattern match, and if the match is 'true' replaces the word with a canned response.
To do this, I mostly used a modified version of this answer.
I've hit a roadblock in that using this works for the active element, but it doesn't appear to work for things such as the 'compose' window in Chrome, or consistently across other services (Salesforce also seems to not like it, for example). Poking about a bit I thought this might be an issue with iFrames, so I tinkered about a bit and modified this peace of code:
function getActiveElement(document){
document = document || window.document;
if( document.body === document.activeElement || document.activeElement.tagName == 'IFRAME' ){// Check if the active element is in the main web or iframe
var iframes = document.getElementsByTagName('iframe');// Get iframes
for(var i = 0; i<iframes.length; i++ ){
var focused = getActiveElement( iframes[i].contentWindow.document );// Recall
if( focused !== false ){
return focused; // The focused
}
}
}
else return document.activeElement;
};
(Which I originally got from another SO post I can no longer find). Seems as though I'm out of luck though, as no dice.
Is there a simple way to always get the active element with the active caret on every page, even for the Gmail compose window and similar services, or am I going to be stuck writting custom code for a growing list of servcies that my code can't fetch the caret on?
My full code is here. It's rough while I just try to get this to work, so I understand there's sloppy parts of it that need tidied up:
function AlertPrevWord() {
//var text = document.activeElement; //Fetch the active element on the page, cause that's where the cursor is.
var text = getActiveElement();
console.log(text);
var caretPos = text.selectionStart;//get the position of the cursor in the element.
var word = ReturnWord(text.value, caretPos);//Get the word before the cursor.
if (word != null) {//If it's not blank
return word //send it back.
}
}
function ReturnWord(text, caretPos) {
var index = text.indexOf(caretPos);//get the index of the cursor
var preText = text.substring(0, caretPos);//get all the text between the start of the element and the cursor.
if (preText.indexOf(" ") > 0) {//if there's more then one space character
var words = preText.split(" ");//split the words by space
return words[words.length - 1]; //return last word
}
else {//Otherwise, if there's no space character
return preText;//return the word
}
}
function getActiveElement(document){
document = document || window.document;
if( document.body === document.activeElement || document.activeElement.tagName == 'IFRAME' ){// Check if the active element is in the main web or iframe
var iframes = document.getElementsByTagName('iframe');// Get iframes
for(var i = 0; i<iframes.length; i++ ){
var focused = getActiveElement( iframes[i].contentWindow.document );// Recall
if( focused !== false ){
return focused; // The focused
}
}
}
else return document.activeElement;
};
I've got it working for the Gmail window (and presumably other contenteditable elements, rather than just input elements).
Edit: the failure around linebreaks was because window.getSelection().anchorOffset returns the offset relative to that particular element, whereas ReturnWord was getting passed the text of the entire compose window (which contained multiple elements). window.getSelection().anchorNode returns the node that the offset is being calculated within.
function AlertPrevWord() {
var text = getActiveElement();
var caretPos = text.selectionStart || window.getSelection().anchorOffset;
var word = ReturnWord(text.value || window.getSelection().anchorNode.textContent, caretPos);
if (word != null) {return word;}
}
I originally used a MutationObserver to account for the Gmail compose div being created after the page load, just to attach an event listener to it.
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = mutation.addedNodes; //list of new nodes in the DOM
for (var i = 0; i < nodes.length; ++i) {
//attach key listener to nodes[i] that calls AlertPrevWord
}
});
});
observer.observe(document, {childList: true, subtree:true });
//childList:true notifies observer when nodes are added or removed
//subtree:true observes all the descendants of document as well
Edit: The delegated click handler I've been testing with. Key event handlers so far not working.
$(document).on( "click", ":text,[contenteditable='true']", function( e ) {
e.stopPropagation();
console.log(AlertPrevWord());
});

hover(): Trigger only when leaving foreign element

am constructing a slightly more complex drop down menu system using Jquery's slideDown() and slideUp() animations as well as the "hover()" event.
Now I have a certain element which triggers by "hover()", that another element is being displayed. Unfortunately it's not possible to make those two elements, the only childs of another element (since the trigger is in another table).
Still I want this new element which has been displayed, to show until my mouse leaves BOTH the new element as well as the trigger element.
Is there a way to achieve this?
Thanks in advance :)
I used .mouseenter and .mouseleave to achieve what may you want:
jsFiddle
var groups = {};
groups[1] = {
main: false,
sub: false
};
$('.menu').mouseenter(function(e) {
var $target = $(e.target);
var group = $target.attr('data-group');
var type = $target.attr('data-type');
if (!(groups[group].sub || groups[group].main)) {
$('.sub[data-group='+ group +']').toggle(true);
}
groups[group][type] = true;
});
$('.menu').mouseleave(function(e) {
var $target = $(e.target);
var group = $target.attr('data-group');
var type = $target.attr('data-type');
groups[group][type] = false;
if (!(groups[group].sub || groups[group].main)) {
$('.sub[data-group='+ group +']').toggle(false);
}
});
Just track the group of main and sub item. A little ugly, but hope it may helps.

How to get value from listbox listitem

In my firefox addon I have a <listbox>. I want to be able to work a javascript function when I left-click on an item in the box. The function should retrieve the item's textual value.
Now, my function does get called when I click on a listitem, as I've placed this in my event listener's onLoad call:
var myListBox = document.getElementById("myListBoxID");
myListBox.addEventListener("click", function(event){
var target = event.target;
while (target && target.localName != "listitem"){
target = target.parentNode;
}
if (!target){
return; // Event target isn't a list item
}
alert(target); //returns blank
alert(target.id); //returns blank
alert(target.getAttribute("value")); //returns blank
alert(target.getAttribute("text")); //returns blank
alert(target.getAttribute("id")); //returns blank
var targetid = document.getElementById(target.id);
alert(targetid); //returns null
}, false);
},
The xul goes something like this:
<listbox id="listbox1">
<listcols /><listcol flex="1"/><listcol flex="1"/></listcols>
<listitem><listcell class="column1" label="label1" value="value1"</listcell><listcell label="cell1"></listcell></listitem>
<listitem><listcell class="column2" label="label2" value="value2"</listcell></listitem><listcell label="cell2"></listcell>
</listbox>
However, I can't get it to display the text of the items. As you can see above, I don't seem to have a proper handle on the target
I've gotten the original code from here, and got the EventListener working here.
How can I get the value of the listcells? I've tried everything!
You are using this code:
while (target && target.localName != "listitem"){
target = target.parentNode;
}
It will go up from the actual click target looking for a <listitem> tag. Yet the text isn't stored in the <listitem> tag, it's in the <listcell> - so you simply should be looking for that one in the hierarchy:
while (target && target.localName != "listcell"){
target = target.parentNode;
}
alert(target.getAttribute("value"));
You can't detect a click on just a listcell. The closest you can go is to detect a click on a listitem. After that, you have to drill down using code.
So, use .childNode on your target. Since you have only two listcells in your listitem, if you are trying to capture the second cell, use childNodes[1]. `childNodes[0] will refer to the first cell.
onLoad: function(){
var mylistbox= document.getElementById("mylistboxID");
mylistbox.addEventListener("click", function(event){
var target = event.target.childNodes[1];
if (!target){
return; // In case there is not target
}
alert(target.getAttribute("label") + target.getAttribute("label"));
}, false);
},

JavaScript DOM parent/child relation on mouse event

O.K. Maybe I have asked something similar to this, but this is another problem I have. I will start by showing the code:
var MyClass = {
// Set up the events for the parent
// MyParent is a DIV
SetEvents: function() {
MyParent.onmouseover = function() { MyClass.MouseHover(MyParent); };
MyParent.onmouseout = function() { MyClass.MouseOut(MyParent); };
},
// Function activated when moving the mouse over the parent
MouseHover: function(Parent) {
if (Parent) {
// Here I create the child, when moving the mouse over the parent control
// The child is an IMG
var child = document.createElement("img");
child.id = "child";
child.src = "child.png";
child.style.float = "right";
Parent.appendChild(child);
}
},
// Function activated when moving the mouse out of the parent control
MouseOut: function(Parent) {
if (Parent) {
var child = document.getElementById("child");
if (child) {
// On mouse out I remove the child
Parent.removeChild(child);
}
}
}
}
So, this is just about a simple image appearing when I move the mouse over a div, then disappears when I move the mouse out. Yet, this is not behaving as I want.
The problem is that when, while the mouse is over the parent div, if I move it over the child img (still over the parent), the MouseOut() function is fired.
I have been searching for solutions and tricks all these days but everything was in vain. Now, I hope somebody can identify the problem. There must be some JavaScript chapter that I missed out. Thanks to everybody!
I added some images, to explain this better
UPDATES
Looks like this happens only when the IMG child is created dinamically. If I create it before and only change it's src, it works.
JSSFIDLE DEMO
You can see this script working (well, not working, better said) at http://jsfiddle.net/E9q6e/1/
You can try to check, if Parent contains the child in MouseOut():
var ref=event.toElement || event.relatedTarget; // This line is pseudocode
if(!Parent.contains(ref)){
Parent.removeChild(child);
}

Categories

Resources