I'm creating a bookmarklet that displays some information on the first element you click on after running the bookmarklet. I would love to have it so the element you are hovering over has an outline, but only before you click. After you have selected an element, the outline would no longer appear when hovering (except if it already did so before my bookmarklet).
To get the clicked element, this works fine for me:
function getClickedElement(e) {
document.removeEventListener("click", getClickedElement);
e.preventDefault();
clickedElement = e.target || e.srcElement;
// Some code that displays information on clickedElement...
}
document.addEventListener("click", getClickedElement);
But I don't know how to do the CSS. It would work like all elements gain this CSS:
:hover {
outline: 1px solid black;
}
while selecting an element, but that stops once an element has been selected. Hope that all made sense.
Small example with the principle explained!
If the user clicks on the element, add a specific class.
CSS Rule adds outline-border only if the element does not match the selector inside the :not pseudo class!
document.addEventListener('click', function(evt) {
var target = evt.target || evt.source;
if(!target.classList.contains('element')) return;
if(target.classList.contains('selected'))
target.classList.remove('selected');
else
target.classList.add('selected');
}, true);
div.element {
width:100px;
height:100px;
background-color:silver;
display:inline-block;
}
.element.selected {
background-color:black;
}
.element:not(.selected):hover {
outline: 1px solid red;
}
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
Related
I'm trying to implement a dropdown which you can click outside to close. The dropdown is part of a custom date input and is encapsulated inside the input's shadow DOM.
I want to write something like:
window.addEventListener('mousedown', function (evt) {
if (!componentNode.contains(evt.target)) {
closeDropdown();
}
});
however, the event is retargeted, so evt.target is always the outside the element. There are multiple shadow boundaries that the event will cross before reaching the window, so there seems to be no way of actually knowing if the user clicked inside my component or not.
Note: I'm not using polymer anywhere -- I need an answer which applies to generic shadow DOM, not a polymer specific hack.
You can try using the path property of the event object. Haven't found a actual reference for it and MDN doesn't yet have a page for it. HTML5Rocks has a small section about it in there shadow dom tutorials though. As such I do not know the compatibility of this across browsers.
Found the W3 Spec about event paths, not sure if this is meant exactly for the Event.path property or not, but it is the closest reference I could find.
If anyone knows an actual spec reference to Event.path (if the linked spec page isn't already it) feel free to edit it in.
It holds the path the event went through. It will contain elements that are in a shadow dom. The first element in the list ( path[0] ) should be the element that was actually clicked on. Note you will need to call contains from the shadow dom reference, eg shadowRoot.contains(e.path[0]) or some sub element within your shadow dom.
Demo: Click menu to expand, clicking anywhere except on the menu items will close menu.
var host = document.querySelector('#host');
var root = host.createShadowRoot();
d = document.createElement("div");
d.id = "shadowdiv";
d.innerHTML = `
<div id="menu">
<div class="menu-item menu-toggle">Menu</div>
<div class="menu-item">Item 1</div>
<div class="menu-item">Item 2</div>
<div class="menu-item">Item 3</div>
</div>
<div id="other">Other shadow element</div>
`;
var menuToggle = d.querySelector(".menu-toggle");
var menu = d.querySelector("#menu");
menuToggle.addEventListener("click",function(e){
menu.classList.toggle("active");
});
root.appendChild(d)
//Use document instead of window
document.addEventListener("click",function(e){
if(!menu.contains(e.path[0])){
menu.classList.remove("active");
}
});
#host::shadow #menu{
height:24px;
width:150px;
transition:height 1s;
overflow:hidden;
background:black;
color:white;
}
#host::shadow #menu.active {
height:300px;
}
#host::shadow #menu .menu-item {
height:24px;
text-align:center;
line-height:24px;
}
#host::shadow #other {
position:absolute;
right:100px;
top:0px;
background:yellow;
width:100px;
height:32px;
font-size:12px;
padding:4px;
}
<div id="host"></div>
Can't comment because of reputation, but wanted to share how it should look using composedPath. See Determine if user clicked outside shadow dom
document.addEventListener("click",function(e){
if(!e.composedPath().includes(menu)){
menu.classList.remove("active");
}
});
The event.target of the shadowRoot would be the host element. To close a <select> element within shadowDOM if event.target is not host element you can use if (evt.target !== hostElement), then call .blur() on hostElement
var input = document.querySelector("input");
var shadow = input.createShadowRoot();
var template = document.querySelector("template");
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
window.addEventListener("mousedown", function (evt) {
if (evt.target !== input) {
input.blur();
}
});
<input type="date" />
<template>
<select>
<option value="1999">1999</option>
<option value="2000">2000</option>
</select>
</template>
Another option is to check the event cursor offsets against the target element:
listener(event) {
const { top, right, bottom, left } = targetElement.getBoundingClientRect();
const { pageX, pageY } = event;
const isInside = pageX >= left && pageX <= right && pageY >= top && pageY <= bottom;
}
I have the following CSS rules:
.block:first-of-type{
border-top: 1px solid black;
}
.block:last-of-type{
border-bottom: 1px solid black;
}
This .block div is generated via AJAX and can be added multiple times to the page. The problem is that every time I add one of them, all divs are read as both first and last of the document. I assume it is because the CSS doesn't recognize the changes that happen in the DOM. How do I fix this?
all divs are read as both first and last of the document
You misunderstand what :first-of-type and :last-of-type mean.
See the spec
Same as :nth-of-type(1). The :first-of-type pseudo-class represents an element that is the first sibling of its type in the list of children of its parent element.
The important bit is children of its parent element not document.
You can see it does that quite correctly here:
function middle() {
var d = document.createElement("div");
d.appendChild(document.createTextNode("inserted in middle"));
var p = document.querySelector("div + div + div");
p.parentNode.insertBefore(d, p);
}
function first() {
var d = document.createElement("div");
d.appendChild(document.createTextNode("inserted at top"));
var p = document.querySelector("div");
p.parentNode.insertBefore(d, p);
}
setTimeout(middle, 2000);
setTimeout(first, 4000);
div:first-of-type {
border-top: 1px solid black;
}
div:last-of-type {
border-bottom: 1px solid black;
}
<div>Original First</div>
<div>Original Second</div>
<div>Original Third</div>
<div>Original Fourth</div>
<div>Original Fifth</div>
You are, presumably creating a structure such as:
<div>
<div class="block"></div>
</div>
<div>
<div class="block"></div>
</div>
<div>
<div class="block"></div>
</div>
… in which every div of that class is the first div in its parent element.
Given that layout then you would need something more akin to :first-of-type > .block as your selector.
The precise nature of the selector you need would depend on the DOM you are creating, but you haven't shared that with us.
So, I use this Javascript for hide - show effect:
function effect(id) {
var h = document.getElementById(id);
h.style.display = ((h.style.display != 'none') ? 'none' : 'inline');
}
HTML:
<div class="div">
<img src="http://i.imm.io/1jf2j.png"/>
Home
</div>
and CSS:
.div {
background: #000;
}
.div .url {
font-size: 17px;
}
Here you can test (and edit!) the code: http://codepen.io/anon/pen/dhHiw
JSFiddle doesn't work for me.
All is good. Except when you click on image. It's moved 1px above. Should I use another image?
Where is the problem? And possible solutions. Thank you!
You are basically removing the text element. Since the <div class="div"> does not have a set height, it depends on the elements inside it. When the text is not displayed (display=none), the div will resize to only the image.
You can fix this by either setting a height for the div, or by setting visibility=hidden for the text instead of display=none. When making it hidden, it still has the same dimensions, but it's invisible instead.
The question of how to detect a click on anywhere except a specified element has been answered a couple of items like here:
Event on a click everywhere on the page outside of the specific div
The problem I have is trying to figure out how to detect a click anywhere except a given element including one of it's children.
For example in this code:
http://jsfiddle.net/K5cEm/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script>
$(function() {
$(document).click(function(e) {
$('#somediv').hide();
});
$('#somediv').click(function(e) {
e.stopPropagation();
});
});
</script>
<div style="border: 1px solid red; width:100px; height: 100px" id="somediv">
<span style="display: block; border: 1px solid green; width:50px; height: 50px; margin: 0 auto" id="someSpan"></span>
</div>
Clicking anywhere outside the red div should cause it to hide. Not only that but also clicking on it's child element (the green span) should cause it to hide. The only time it shouldn't hide is if you click on it but not on the span. As it stands now, the click on the span is also considered a click on the parent div hence it doesn't hide the div if the span is clicked.
How to achieve this?
You can compare the click's target to the element in question:
$(document).click(function(e) {
if (e.target != $('#somediv')[0]) {
$('#somediv').hide();
}
});
Demo: http://jsfiddle.net/K5cEm/7/
Add this:
$('#somediv').click(function(e) {
e.stopPropagation();
}).children().click(function(e) {
$('#somediv').hide();
});
Here's your updated working fiddle: http://jsfiddle.net/K5cEm/5/
I'd do it like so:
$(function () {
var elem = $( '#somediv' )[0];
$( document ).click( function ( e ) {
if ( e.target !== elem ) {
$( elem ).hide();
}
});
});
Live demo: http://jsfiddle.net/uMLrC/
So this
var elem = $( '#somediv' )[0];
caches the reference to the DIV element. We want to cache this reference on page load, so that we don't have to query for that element repeatedly. And it improves the readability of the code, also.
I have a simple div tag. Can you please tell me how can I change the text to 'mouse in' in my onmouseover handler? and 'mouse out' in my onmouseout handler?
<div id="div1" onmouseover="alert(1);" width="100px" height="200px" border="1">
test
</div>
and why the width/height and border attributes do not work? I want to set the border to be 1 pixel with width = 100 pixels and height = 200 pixels.
Thank you.
For your CSS, add the following:
/* Using inline-block so we can set w/h while letting elements
flow around our div */
#div1 { display:inline-block;
width:100px; height:200px;
border:1px solid #000; }
For your Javascript:
/* We start wit a reference to the element via its ID */
var myDiv = document.getElementById("div1");
/* Then we add functions for our events */
myDiv.onmouseover = function() { this.innerHTML = 'mouse over'; }
myDiv.onmouseout = function() { this.innerHTML = 'mouse out'; }
Which leaves us with very clean and minimalistic markup:
<div id="div1">Hover Me!</div>
Online Demonstration
DIVs don't have the width/height/border attributes. You should use styles instead. Even better use CSS classes to style your DIV.
<style type="text/css">
.message {
width: 100px;
height: 200px;
border: solid 1px #000;
}
</style>
<div id="div1" class="message">test</div>
You might also want to consider using the jQuery hover method rather than mousein/out. While there are times that mousein/out are more appropriate, I've found that the behavior usually desired is a hover effect. This is easiest to handle in a browser-independent way with a JS framework, like jQuery.
$('#div1').hover( function() {
$(this).text('mouse in');
},
function() {
$(this).text('mouse out');
}
});
try this <div style="width:100px;height=200px;border=1px solid black" >your text</div>
to make your css work
Simply replace alert(1); with a Javascript function. You could do
this.innerHTML = 'mouse in'
and similarly for onmouseout.
As for your border problem, that is because you must you must do the following:
style="border:1px solid black"
as border is a CSS element.