I have some nested elements on my page with a same handler on them which should be called only for an event target without affecting elements higher in DOM tree. To achieve this behavior I used stopPropagation method and it was ok. Then I had to add some handlers for body and other elements outside the nested divs which should be called in any case. Of course stopPropagation isn't an option now but how can I make it work?
Here is a sample:
html:
<div id="container">
<div id="nested1" class="nested">
<div id="nested2" class="nested">
<div id="nested3" class="nested">
<div id="no-handler"></div>
</div>
</div>
</div>
</div>
css:
#container {
display: block;
width: 398px;
height: 398px;
padding: 30px;
border: solid 1px #888;
}
#nested1 {
width: 336px;
height: 336px;
padding: 30px;
}
#nested2 {
width: 274px;
height: 274px;
padding: 30px;
}
#nested3 {
width: 212px;
height: 212px;
padding: 30px;
}
#no-handler {
width: 150px;
height: 150px;
padding: 30px;
border: solid 1px #888;
}
.nested {
border: solid 1px #888;
}
.nested-clicked {
background-color: red;
}
.outer-clicked {
background-color: green;
}
js:
var container = document.getElementById("container");
var nested = document.getElementsByClassName("nested");
function outerHandler(e) {
this.classList.add("outer-clicked");
}
function nestedHandler(e) {
e.stopPropagation();
this.classList.add("nested-clicked");
}
container.addEventListener("click", outerHandler, false);
document.body.addEventListener("click", outerHandler, false);
for (var i = 0; i < nested.length; i++) {
nested[i].addEventListener("click", nestedHandler, false);
}
jsfiddle link:
http://jsfiddle.net/6kgnu7fr/
clicking on .nested should add red background color to clicked element and add green color to outer body and #container
UPD:
http://jsfiddle.net/6kgnu7fr/2/
clicking on #no-event or any other element inside .nested should also call nestedHandler for this .nested element.
You can check for the event's target in your nestedHandler instead of stopping the propagation. Change the class only if the target is this so that the effet will only be applied for the div on which the event occurred:
function nestedHandler(e) {
if (e.target === this) {
this.classList.add("nested-clicked");
}
}
Edit
Following your edit, this is harder. Way to do it is to find e.target's first ancestor with the "nested" class, then doing the comparison with it instead of target:
function findAncestorWithClass(dom, targetClass){
if(!dom){
return; // (undefined)
}
if(dom.classList.contains(targetClass)){
return dom;
}
// terminal recursion
return findAncestorWithClass(dom.parentNode, targetClass);
}
This is naïve shot. You may want to look for a way to make it more efficient, e.g. by avoiding to look for the first ancestor on each .nested div.
See the working snipped below.
var container = document.getElementById("container");
var nested = document.getElementsByClassName("nested");
function outerHandler(e) {
this.classList.add("outer-clicked");
}
function findAncestorWithClass(dom, targetClass){
if(!dom){
return; // (undefined)
}
if(dom.classList.contains(targetClass)){
return dom;
}
// terminal recursion
return findAncestorWithClass(dom.parentNode, targetClass);
}
function nestedHandler(e) {
var nestedParent = findAncestorWithClass(e.target, "nested");
if (this === nestedParent) {
nestedParent.classList.add("nested-clicked");
}
}
container.addEventListener("click", outerHandler, false);
document.body.addEventListener("click", outerHandler, false);
for (var i = 0; i < nested.length; i++) {
nested[i].addEventListener("click", nestedHandler, false);
}
#container {
display: block;
width: 398px;
height: 398px;
padding: 30px;
border: solid 1px #888;
}
#nested1 {
width: 336px;
height: 336px;
padding: 30px;
}
#nested2 {
width: 274px;
height: 274px;
padding: 30px;
}
#nested3 {
width: 212px;
height: 212px;
padding: 30px;
}
#sub-nested {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
}
.nested {
border: solid 1px #888;
}
.nested-clicked {
background-color: red;
}
.outer-clicked {
background-color: green;
}
<div id="container">
<div id="nested1" class="nested">
<div id="nested2" class="nested">
<div id="nested3" class="nested">
<div id="sub-nested"></div>
</div>
</div>
</div>
</div>
Related
I don’t want use or i don't know how to use 'the CSS:after prototype' by javascript .
Now, I change it is by add height not width,and when i remove the class prototype, reback is a short time,no transtion.
What can i do for it?
this is my codepen link
<div class="block">
<div id="top">my block/div>
<div>
<button id="btn">submit</button>
</div>
</div>
.block {
height: 200px;
width: 250px;
margin:150px auto;
text-align: center;
}
#top {
margin-bottom: 20px;
height: 30px;
display: inline-block;
border-bottom: 3px solid;
transition: 1s all cubic-bezier(.46, 1, .23, 1.52);
}
.addtop {
border-bottom: 3px solid blue;
color: blue;
}
let btn = document.getElementById('btn');
btn.addEventListener('click',() => {
let topBlock = document.getElementById('top');
if(topBlock.classList.length > 0) {
topBlock.classList = [];
} else {
topBlock.classList.add('addtop');
}
});
Try this:
document.getElementById('top');
if(topBlock.classList.length > 0) {
topBlock.classList.remove('addtop');
} else {
topBlock.classList.add('addtop');
}
});
Also add to .top class:
border-bottom: 0px solid blue;
For some reason I can not remove any of the building-x elements that JavaScript generates. So I'm wondering why?
So I change my code a bit and I ended up adding building-x to the HTML to see if that will do the trick and as soon as I did that, it removed the generated HTML version of building-x but I still can not remove the generated JavaScript version of building-x.
What would I have to do to also be able to remove the JavaScript generated version of building-x?
Here is my code
document.addEventListener('DOMContentLoaded',function(){
/*<Add another building>*/
document.querySelector('#add-another-building').addEventListener('click',addAnotherBuilding);
function addAnotherBuilding(){
if(document.querySelector(".building-x")){
document.querySelector(".building-x").insertAdjacentHTML("afterend","<div class='building-x'></div>");
}
else{
document.querySelector("#first-building").insertAdjacentHTML("afterend","<div class='building-x'></div>");
}
}
/*</Add another building>*/
/*<Remove the targeted buildingX>*/
if(document.querySelector('.building-x')){
var buildingXs= document.querySelectorAll('.building-x');
for(var i=0; i < buildingXs.length; i++){
buildingXs[i].addEventListener('click',removeTheTargetedBuildingX);
}
function removeTheTargetedBuildingX(event){
var removeTheTargetedBuildingX = event.currentTarget;
removeTheTargetedBuildingX.parentNode.removeChild(removeTheTargetedBuildingX);
}
}
/*</Remove the targeted buildingX>*/
});
#buildings{
background-color: gray;
}
#first-building{
background-color: red;
height: 150px;
width: 50px;
display: inline-block;
}
#add-another-building{
margin-bottom: 25px;
display: block;
}
.building-x{
background-color: blue;
display: inline-block;
height: 100px;
width: 50px;
margin-left: 5px;
margin-right: 4px;
margin-bottom: 5px;
}
<button id='add-another-building'>Add another building</button>
<div id='buildings'>
<div id='first-building'></div><!--</first-building>-->
<div class='building-x'></div><!--</building-x>-->
</div><!--</buildings>-->
The original problem was that the part of the code that adds listeners was only run once at the beginning, when there were no building-x. Thus no js-generated building-x ever got a listener.
When you added a starting html building-x, that one got a listener but not subsequent js-generated building-x.
The solution is to call the add-listener code after adding a js-building x. In the below example, I have removed the html-starting building-x.
document.addEventListener('DOMContentLoaded',function(){
/*<Add another building>*/
document.querySelector('#add-another-building').addEventListener('click',addAnotherBuilding);
function addAnotherBuilding(){
if(document.querySelector(".building-x")){
document.querySelector(".building-x").insertAdjacentHTML("afterend","<div class='building-x'></div>");
}
else{
document.querySelector("#first-building").insertAdjacentHTML("afterend","<div class='building-x'></div>");
}
addListener();
}
/*</Add another building>*/
/*<Remove the targeted buildingX>*/
function addListener() {
var buildingXs = document.querySelectorAll('.building-x');
for(var i=0; i < buildingXs.length; i++){
if (buildingXs[i].classList.contains("listening") === false) {
buildingXs[i].addEventListener('click',removeTheTargetedBuildingX);
buildingXs[i].classList.add("listening");
}
}
}
function removeTheTargetedBuildingX(event){
var removeTheTargetedBuildingX = event.currentTarget;
removeTheTargetedBuildingX.parentNode.removeChild(removeTheTargetedBuildingX);
}
});
#buildings{
background-color: gray;
}
#first-building{
background-color: red;
height: 150px;
width: 50px;
display: inline-block;
}
#add-another-building{
margin-bottom: 25px;
display: block;
}
.building-x{
background-color: blue;
display: inline-block;
height: 100px;
width: 50px;
margin-left: 5px;
margin-right: 4px;
margin-bottom: 5px;
}
<button id='add-another-building'>Add another building</button>
<div id='buildings'>
<div id='first-building'></div><!--</first-building>-->
</div><!--</buildings>-->
I have a list of multiple items, a small image frame and with it all a description.
I need to bind a certain image + description to the list items, so if item5 is chosen it's showing one type of picture and description, and so on(all of them would be unique).
I am having a hard time figuring out how to do this since I'm new to js, I did try the basic, setting a class to an item, then in js fetching the class and changing the content.
Here I'm trying to change only the text, but that doesn't seem to be working out either: https://jsfiddle.net/8z37f15j/5/
HTML:
<div id="wrapper">
<div id="list">
<ol>
<li id="item1">items1</li>
<li>items2</li>
<li>items3</li>
<li>items4</li>
<li>items5</li>
<li>items6</li>
<li>items7</li>
<li>items8</li>
<li>items9</li>
<li>items10</li>
<li>items11</li>
<li>items12</li>
<li>items13</li>
<li>items14</li>
<li>items15</li>
</ol>
</div>
<div id="image-container">
<div id="image">
<img src="https://semantic-ui.com/images/wireframe/image.png" alt="">
</div>
<div id="description">
just a placeholder text for when nothing has been chosen.
</div>
</div>
</div>
CSS:
/* containers */
* {
font-family: Corbel;
}
#wrapper {
border: 1px solid red;
padding: 10px;
display: inline-flex;
}
#image,
#description,
#list {
border: 1px solid #472836;
box-sizing: border-box;
margin: 5px;
}
/* list */
#list {
width: 150px;
height: 250px;
background-color: #9AD2CB;
overflow-y: auto;
overflow-x: hidden;
}
#list ol {
list-style: none;
padding: 0;
margin: 0;
}
#list li {
padding: 5px;
}
#list li:nth-child(even) {
background-color: #91f2e6;
width: 100%;
height: 100%;
}
#list li:hover {
cursor: pointer;
color: red;
}
/* sub-container */
#image,
#description {
width: 150px;
height: 150px;
}
#image {
background-color: #D7EBBA;
}
#image img {
width: 100%;
height: 100%;
}
#description {
background-color: #FEFFBE;
overflow-y: auto;
overflow-x: hidden;
height: 95px;
}
JS:
var desc_area = document.getElementById('description');
var desc1 = "random text for desc1";
function item1(){
desc_area.innerHTML += desc1;
}
You can register each element as a key in a Map and create description and the source of an image for that element as a corresponding value (in this case object). I have registered only the first item but others can be done in the same way so right now you can click on the item1 and see the result.
const desc_area = document.getElementById('description');
const image = document.querySelector('img');
const item1 = document.querySelector('#item1');
const map = new Map();
// register item element as a key and object with corresponding description / image as value
map.set(item1, { desc: 'some description for item1', img: 'url/of/image' });
// you can bind on click handler for example
const list = document.querySelector('ol');
list.addEventListener('click', event => {
// if element that was registered in our map triggered the event
if (map.has(event.target)) {
// change text of description area
desc_area.textContent = map.get(event.target).desc;
// change src of the image
image.src = map.get(event.target).img;
}
});
/* containers */
* {
font-family: Corbel;
}
#wrapper {
border: 1px solid red;
padding: 10px;
display: inline-flex;
}
#image,
#description,
#list {
border: 1px solid #472836;
box-sizing: border-box;
margin: 5px;
}
/* list */
#list {
width: 150px;
height: 250px;
background-color: #9AD2CB;
overflow-y: auto;
overflow-x: hidden;
}
#list ol {
list-style: none;
padding: 0;
margin: 0;
}
#list li {
padding: 5px;
}
#list li:nth-child(even) {
background-color: #91f2e6;
width: 100%;
height: 100%;
}
#list li:hover {
cursor: pointer;
color: red;
}
/* sub-container */
#image,
#description {
width: 150px;
height: 150px;
}
#image {
background-color: #D7EBBA;
}
#image img {
width: 100%;
height: 100%;
}
#description {
background-color: #FEFFBE;
overflow-y: auto;
overflow-x: hidden;
height: 95px;
}
<div id="wrapper">
<div id="list">
<ol>
<li id="item1">items1</li>
<li>items2</li>
<li>items3</li>
<li>items4</li>
<li>items5</li>
<li>items6</li>
<li>items7</li>
<li>items8</li>
<li>items9</li>
<li>items10</li>
<li>items11</li>
<li>items12</li>
<li>items13</li>
<li>items14</li>
<li>items15</li>
</ol>
</div>
<div id="image-container">
<div id="image">
<img src="https://semantic-ui.com/images/wireframe/image.png" alt="">
</div>
<div id="description">
just a placeholder text for when nothing has been chosen.
</div>
</div>
</div>
I think I know what you mean and could easily work with data attributes. Just add data attribute of the image path and then grab that and update your image with the data. Something like
<li data-imgsrc="/path/to/item2/image" data-desc="Description for the image 2">items2</li>
<li data-imgsrc="/path/to/item3/image" data-desc="Description for the image3">items3</li>
<li data-imgsrc="/path/to/item4/image" data-desc="Description for the image4">items4</li>
Just make one listener for the li elements and grab the imgsrc and the desc and put them in the dom where needed.
I want to achieve with javascript something like when i clink on any of thumbnail (btn-1, btn-2 and btn-3) the specific class should be add to box div dynamically.
my code: JSFiddle
document.getElementById('btn-1').onclick = function() {
document.getElementById('box').className = 'bg-1';
}
#box {
background-color: darkgray;
width: 200px;
height: 200px;
}
.thumbnail {
width: 30px;
height: 30px;
border: 1px solid;
margin: 5px;
position: relative;
float: left;
}
#btn-1 {
background-color: red;
}
#btn-2 {
background-color: green;
}
#btn-3 {
background-color: blue;
}
.bg-1 {
background-color: red;
}
.bg-2 {
background-color: blue;
}
.bg-3 {
background-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="box"></div>
<div class="thumbnail" id="btn-1"></div>
<div class="thumbnail" id="btn-2"></div>
<div class="thumbnail" id="btn-3"></div>
You javascript is working, but your CSS isn't.
You need to add !important as follows to .bg-1, .bg-2 and .bg-3
.bg-1 {
background-color: red !important;
}
Otherwise the id styling is taking preference over the class styling
You can see the classname is being added if you right click on the grey div and choose inspect element in Chrome.
Instead of bothering with classes, use simply a data- attribute like: data-bg="#f00"
$('[data-bg]').css('background', function () {
$(this).on('click', () => $('#box').css('background', this.dataset.bg));
return this.dataset.bg;
});
#box {
background: darkgray;
width: 120px; height: 120px;
}
[data-bg] {
width: 30px; height: 30px;
margin: 5px;
float: left;
}
<div id="box"></div>
<div data-bg="red"></div>
<div data-bg="#00f"></div>
<div data-bg="rgb(255,0,180)"></div>
<div data-bg="linear-gradient(to right, #E100FF, #7F00FF)"></div>
<script src="//code.jquery.com/jquery-3.1.0.js"></script>
You want to use jquery .addClass() function:
$('.myButton').addClass('myNewClass');
The function would probably look something like this:
$(function () {
$('.thumbnail').click(function() {
$('#box').addClass($(this).attr('id'));
});
})
You can get all the thumbnails as an array, and then iterate through the array and dynamically add an event listener to each, which will add the desired className to box when clicked:
var thumbnails = document.getElementsByClassName('thumbnail');
Array.from(thumbnails).forEach(function(thumbnail) {
var id = thumbnail.id;
thumbnail.addEventListener('click', function() {
document.getElementById('box').className = id.replace('btn', 'bg')
});
});
I am trying to put together this project.
In my list I have fruit & veg.I want to be able to drag the right item into the correct box. Once it is in the correct box ( dropped) it should be invisible.
Hope someone can help.
HTML
<header>
<h1>THIS IS A TEST PAGE</h1>
</header>
<nav>
</nav>
<section>
<h1>Choose a Box</h1>
<ul id="fruit">Fruit
</ul>
<ul id="veg">Veg
</ul>
</section>
<article>
<ul id="dragsource">
<li id="item1" draggable="true">Apple</li>
<li id="item2" draggable="true">Banana</li>
<li id="item3" draggable="true">Orange</li>
<li id="item4" draggable="true">Potato</li>
<li id="item5" draggable="true">Carrot</li>
<li id="item6" draggable="true">Pea</li>
</ul>
</article>
JS
window.onload = function() {
var target1 = document.getElementById("fruit");
var target2 = document.getElementById("veg");
var list = document.querySelectorAll("#dragsource li");
for (i = 0; i < list.length; i++) {
list[i].draggable = true;
list[i].ondragstart = function(event) {
var event = event || window.event;
var dt = event.dataTransfer;
dt.setData("text", event.target.id);
dt.effectAllowed = "move";
};
}
target1.ondragover = function(event) {
var event = event || window.event;
event.preventDefault();
};
target2.ondragover = function(event) {
var event = event || window.event;
event.preventDefault();
};
target2.ondrop = function(event) {
var event = event || window.event;
var dt = event.dataTransfer;
event.preventDefault();
var data = dt.getData("text");
target2.appendChild(document.getElementById("data"));
};
target1.ondrop = function(event) {
var event = event || window.event;
var dt = event.dataTransfer;
event.preventDefault();
var data = dt.getData("text");
target1.appendChild(document.getElementById(data));
};
};
CSS
header {
background-color: black;
color: yellow;
text-align: center;
padding: 5px;
}
nav {
line-height: 30px;
background-color: yellow;
height: 400px;
width: 100px;
float: left;
padding: 5px;
}
body,
html {
background-color: silver;
font-family: sans-serif;
color: black;
text-align: center;
}
section {
width: 482px;
height: 220px;
float: left;
text-align: center;
padding: 5px;
}
#fruit {
width: 90px;
height: 120px;
left: 150px;
top: 150px;
padding: 10px;
border: 2px solid green;
position: absolute;
}
#veg {
width: 200px;
height: 120px;
left: 340px;
top: 150px;
padding: 10px;
border: 2px solid green;
position: absolute;
}
article {
background-color: aqua;
height: 170px;
width: 482px;
float: right;
padding: 5px;
}
ul {
margin: left;
column-count: 3;
width: 50%;
text-align: left;
list-style: none;
}
li {
list-style-type: none;
padding: 5px;
margin: 2px;
background-color: #CCCCFF;
border: 2px double #CCCCCC;
}
footer {
background-color: black;
color: white;
clear: both;
text-align: center;
padding: 5px;
}
See fiddle: https://jsfiddle.net/cq2aw1dy/3/
Before starting - it should be noted in your target2.onDrop function, the last line you say document.getElementById("data") there shouldn't be any quotes there. That will give you some issues.
Since all of your fruits have ID's I think it would serve you better to make use of classes The way to do that would be to add a class to the dropped element that tells it to become invisible.
CSS
#fruit > .hiddenDrop, #veg > .hiddenDrop {
display: none;
}
Javascript - Place this inside of your .onDrop() functions
var element = document.getElementById(data);
element.setAttribute('class', 'hiddenDrop');
target.appendChild(element);
What this does is after the element is dropped, it adds the class hiddenDrop to that element which will then change it's display property to none
EDIT
You can do it with the css like this #fruit > *{display: none;}
It is saying if you drag into the fruit div, display is set to none.
See fiddle:
https://jsfiddle.net/cq2aw1dy/4/