Which JS technique: select parent of targeted element? - javascript

I'm not sure that the terms in the question are correct; however I have some HTML (and unfortunately, I am stuck with its structure):
<div>
<ul>
<li>Turn left</li>
<li>Turn right</li>
</ul>
</div>
<div class="possible-outcome">
<span id="turn-left"></span>
<p>You step into a puddle.</p>
</div>
<div class="possible-outcome">
<span id="turn-right"></span>
<p>You fall into a ditch.</p>
</div>
What I would like is all possible-outcome divs to become hidden by having:
div.non-actual-outcome {display: none;}
applied to them, as soon as the user selects one of the hyperlinks, except for the div containing the span with the target id, which should have the actual-outcome class applied to it by JS, so it and it alone is displayed:
div.actual-outcome {display: block;}
When the user hits some other link to a span in a possible-outcome div, then that div will become the only one visible (until the user hits a link that isn't to one of those spans).
Here is some sample code:
div.non-actual-outcome {
display: none;
}
div.actual-outcome {
display: block;
}
<div>
<ul>
<li>Turn left</li>
<li>Turn right</li>
</ul>
</div>
<div class="possible-outcome">
<span id="turn-left"></span>
<p>You step into a puddle.</p>
</div>
<div class="possible-outcome">
<span id="turn-right"></span>
<p>You fall into a ditch.</p>
</div>
What is the simplest way to achieve this (and the correct terms in which to ask the question)?

First, add a common class to all your a elements so that they can be distinguished, for example class="tab":
<a class="tab" href="#turn-left">Turn left</a>
Second, hide all div elements by default by adding the display:none property to the CSS selector .possible-outcome (which all div elements have):
div.possible-outcome {
display: none;
}
and use only actual-outcome to hide/show the div you want. No need to use two classes for that, just one will do. If the div has the class actual-outcome then it will be shown, if not then it won't (because it has the class possible-outcome).
Third, when a .tab element get clicked, select its target span along with that span's parent and do some logic: if the span exists and its parent is not currently shown (not having the class actual-outcome), then hide the actually shown div (if exists) and show the current span's parent instead.
Note: If you want to show a div by default just add the class actual-outcome to it:
<div class="possible-outcome actual-outcome">
Full code:
document.addEventListener("click", function(ev) {
var target = ev.target; // get the element that has been clicked
if(target.classList.contains("tab")) { // if the clicked element is a .tab element
var span = document.querySelector(target.getAttribute("href")), // then select that .tab's span to show
parent = span.parentElement; // select the parent of that span (perhaps check if the span exists first to not get the "can't access property parentElement of null")
if(span && !parent.classList.contains("actual-outcome")) { // if the span exists and its parent is not currently shown (not having the class "actual-outcome")
var visibleOutcome = document.querySelector(".actual-outcome"); // then select the current shown element (which we know will have the class "actuall-outcome")
if(visibleOutcome) { // if there is one
visibleOutcome.classList.remove("actual-outcome"); // hide it by remove the class "actual-outcome" from its classList
}
parent.classList.add("actual-outcome"); // and show the current span's parent by adding that same old class
}
}
});
div.possible-outcome {
display: none;
}
div.actual-outcome {
display: block;
}
<div>
<ul>
<li><a class="tab" href="#turn-left">Turn left</a></li>
<li><a class="tab" href="#turn-right">Turn right</a></li>
</ul>
</div>
<div class="possible-outcome">
<span id="turn-left"></span>
<p>You step into a puddle.</p>
</div>
<div class="possible-outcome">
<span id="turn-right"></span>
<p>You fall into a ditch.</p>
</div>

Just for completion, the Divio Cloud interactive debugging checklist for deployments is our implementation of the solution to this problem.
It's like a choose your own adventure story in our Sphinx documentation, to help users identify why a deployment may have failed.
It was was developed on the basis of Ibrahim Mahrir's answer:
One of our actual JS experts (i.e. not me) used my working proof-of-concept based on Ibrahim's answer for the final version.
The JavaScript and CSS are both embedded in the page itself.
The HTML structure is largely determined by the output from Sphinx, hence the need to accommodate the arrangement of elements.

This is the best solution I could come up with. This does not modify the structure by adding any other classes than the ones provided.
HTML:
<div>
<ul>
<li>Turn left</li>
<li>Turn right</li>
</ul>
</div>
<div class="possible-outcome">
<span id="turn-left"></span>
<p>You step into a puddle.</p>
</div>
<div class="possible-outcome">
<span id="turn-right"></span>
<p>You fall into a ditch.</p>
</div>
CSS:
div.non-actual-outcome {
display: none;
}
div.actual-outcome {
display: block;
}
JS:
var outcomeDivs = document.getElementsByClassName('possible-outcome') // selects all divs and returns them as an array
var links = document.querySelectorAll('li') // selects all <li> and returns them as array
// This is turn left li, we will hide turn-right when clicking it.
links[0].addEventListener('click', function(){
outcomeDivs[1].classList.add('non-actual-outcome'); // hides turn-right
links[1].style.display = 'none'; // hides other link
});
// This is turn right li, we will hide turn-left when clicking it.
links[1].addEventListener('click', function(){
outcomeDivs[0].classList.add('non-actual-outcome'); // hides turn-left
links[0].style.display = 'none'; // hides other link
});
CodePen

Related

Display content related to only those button it is clicked in HTML

I have three buttons in HTML, orders and products and supplier. I want when the user clicks orders order is being shown, when the user clicks products, the product is shown, and the name of the supplier when it is clicked.
function changedata(parameter){
if(parameter==0){
document.getElementById('myorders').style.fontSize="25px";
}
else if(parameter==1){
document.getElementById('myproducts').style.fontSize="25px";
}
else{
document.getElementById('mysupplier').style.fontSize="25px";
}
}
<button class="button" onclick="changedata(0)">ORDERS</button>
<button class="button" onclick="changedata(1)">PRODUCTS</button>
<button class="button" onclick="changedata(2)">SUPPLIER</button>
<div id="myorders">
<p>Laptop, Earphone</p>
</div>
<div id="myproducts">
<p>Earphone, smart watch</p>
</div>
<div id="mysupplier">
<p>Amazon, E-kart</p>
</div>
But it won't hide data and serve my need, I'm a beginner in web development, looking for kind help to show data only when the corresponding button is pressed.
Try giving each element a default value of display:none in your css, as such -
#myorders,
#mysuppliers,
#myproducts {
font-size: 25px;
display: none;
}
This will select each element and hide them right away.
Then, when a button is pressed, you can use
document.getElementById('___').style.display = 'block';
to then show that element.
Here is the final product:
function changedata(parameter){
if(parameter==0){
document.getElementById('myorders').style.display = 'block';
}
else if(parameter==1){
document.getElementById('myproducts').style.display = 'block';
}
else{
document.getElementById('mysupplier').style.display = 'block';
}
}
#myorders,
#myproducts,
#mysupplier{
font-size: 25px;
display: none;
}
<button class="button" onclick="changedata(0)">ORDERS</button>
<button class="button" onclick="changedata(1)">PRODUCTS</button>
<button class="button" onclick="changedata(2)">SUPPLIER</button>
<div id="myorders">
<p>Laptop, Earphone</p>
</div>
<div id="myproducts">
<p>Earphone, smart watch</p>
</div>
<div id="mysupplier">
<p>Amazon, E-kart</p>
</div>
If you would like to have the element toggle between hidden and shown on each button press, I recommend toggling a class with javascript, as such:
function changedata(parameter){
if(parameter==0){
document.getElementById('myorders').classList.toggle('active');
}
else if(parameter==1){
document.getElementById('myproducts').classList.toggle('active');
}
else{
document.getElementById('mysupplier').classList.toggle('active');
}
}
#myorders,
#myproducts,
#mysupplier{
font-size: 25px;
display: none;
}
#myorders.active,
#myproducts.active,
#mysupplier.active{
display: block;
}
<button class="button" onclick="changedata(0)">ORDERS</button>
<button class="button" onclick="changedata(1)">PRODUCTS</button>
<button class="button" onclick="changedata(2)">SUPPLIER</button>
<div id="myorders">
<p>Laptop, Earphone</p>
</div>
<div id="myproducts">
<p>Earphone, smart watch</p>
</div>
<div id="mysupplier">
<p>Amazon, E-kart</p>
</div>
There are slightly easier ways to connect each div to its corresponding button, and one of them is to use data attributes. We can add a data attribute to each button the text of which matches the id of its corresponding div.
(I'm assuming that when you click on one button all the other divs are hidden, and only its div shows.)
This example uses more modern JS techniques but I'll guide you through them, comment everything, and provide documentation at the end. You don't have to understand everything here but you're probably going to bump up against these things eventually, so you might as well take a look at them now.
Here's a rundown of how this all works:
Remove the inline listeners from the buttons. Modern JS uses addEventListener.
Wrap the buttons in a container. What we're going to use is a technique called event delegation. Instead of attaching listeners to every button we attach one to the container and this captures any events that "bubble up" the DOM from its child elements. We can then call a function when a child element is clicked.
The function does a few things. First it checks to see if the clicked element was actually a button. Then it hides all the "panels" by removing a class called "show" from them ("show" sets the element's display to block - initially all panels have their display set to none). Then based on the id from the button's data attribute it forms a selector with it, and we use that to target its corresponding div and apply the "show" class.
// Cache out buttons container, and all of the panels
const buttons = document.querySelector('.buttons');
const panels = document.querySelectorAll('.panel');
// Add an event listener to the buttons container
buttons.addEventListener('click', handleClick);
// When a child element of `buttons` is clicked
function handleClick(e) {
// Check to see if its a button
if (e.target.matches('button')) {
// For every element in the `panels` node list use `classList`
// to remove the show class
panels.forEach(panel => panel.classList.remove('show'));
// "Destructure" the `id` from the button's data set
const { id } = e.target.dataset;
// Create a selector that will match the corresponding
// panel with that id. We're using a template string to
// help form the selector. Basically it says find me an element
// with a "panel" class which also has an id that matches the id of
// the button's data attribute which we just retrieved.
const selector = `.panel[id="${id}"]`;
// Select the `div` and, using classList, again add the
// show class
document.querySelector(selector).classList.add('show');
}
}
.panel { display: none; }
.show { display: block; }
.button { text-transform: uppercase; }
.button:hover { cursor: pointer; background-color: #fffff0; }
<div class="buttons">
<button data-id="myorders" class="button">Orders</button>
<button data-id="myproducts" class="button">Products</button>
<button data-id="mysupplier" class="button">Supplier</button>
</div>
<div class="panel" id="myorders"><p>Laptop, Earphone</p></div>
<div class="panel" id="myproducts"><p>Earphone, smart watch</p></div>
<div class="panel" id="mysupplier"><p>Amazon, E-kart</p></div>
Additional documentation
addEventListener
classList
Destructuring assignment
forEach
matches
querySelector
querySelectorAll
Template string

how to hide/show element according to url parameter

i have 4 url parameters like so
http://127.0.0.1:4000/post?&id=5f04698e6114e4069099d8bf#like
http://127.0.0.1:4000/post?&id=5f04698e6114e4069099d8bf#comment
http://127.0.0.1:4000/post?&id=5f04698e6114e4069099d8bf#share
http://127.0.0.1:4000/post?&id=5f04698e6114e4069099d8bf#save
and 4 divs like
html
<div id='like' class='hide'></div>
<div id='comment' class='hide'></div>
<div id='share' class='hide'></div>
<div id='save' class='hide'></div>
and
css
.hide{
display:none;
}
how do i unhide elements when a url param is searched,
for example if i search for
http://127.0.0.1:4000/post?&id=5f04698e6114e4069099d8bf#like
the div with id='like should be now visible
what i have tried
i have tried to unhide the elements on button click, and I am successful using classList.toggle('hide')
but how do I achieve the same thing with changes in url. I would be best if the classList is set acc to the url for example when there is #like in url ,the element with id like should not contain the class hide anymore. other answers are also accepted ,thanks.
No JavaScript needed. Use the CSS :target selector like:
#like.hide:target {display:block;}
.hide {
display: none;
}
#like.hide:target,
#comment.hide:target,
#share.hide:target,
#save.hide:target {
display: block;
}
<div id='like' class='hide'>like</div>
<div id='comment' class='hide'>comment</div>
<div id='share' class='hide'>share</div>
<div id='save' class='hide'>save</div>
like
comment
share
save
A JavaScript approach would be as follow:
like.style.display = "none";
You could implement if statement to trigger that line if this is what you prefer.

Can't hide other elements while clicking on another

I'm trying to make a toggle which works, but every element I click on creates a stack of these showed elements. Instead I'm trying to hide everything and display only element that I clicked on. Now I can only hide it when I click on the same element twice, which is not what I want. I want to click on one and hide previous ones that were showing.
.totalpoll-choice-image-2 is a bunch of images that always has to be shown. They are what the user clicks on to display hidden description under each image. That description shows up when I click on .totalpoll-choice-image-2. There are 5 images with that class. The next image I click on, I want to hide the previous description box.
My code:
jQuery(document).ready(function() {
var element = document.getElementsByClassName("totalpoll-choice-image-2");
var elements = Array.prototype.slice.call(Array.from( element ) );
console.log(elements);
jQuery(element).each(function(item) {
jQuery(this).unbind('click').click(function(e) {
e.stopPropagation();
var id = jQuery(this).attr("data-id");
console.log(this);
//jQuery("#" + id).css({"display": 'block !important'});
//document.getElementById(id).style.setProperty( 'display', 'block', 'important' );
var descriptionContainer = document.getElementById(id);
var thiss = jQuery(this);
console.log(thiss);
console.log(jQuery(descriptionContainer).not(thiss).hide());
jQuery(descriptionContainer).toggleClass("show");
});
})
})
You can attach event handlers to a group of DOM elements at once with jQuery. So in this case, mixing vanilla JS with jQuery isn't doing you any favors - though it is possible.
I threw together this little example of what it sounds like you're going for.
The script itself is very simple (shown below). The classes and IDs are different, but the idea should be the same:
// Assign click handlers to all items at once
$('.img').click(function(e){
// Turn off all the texts
$('.stuff').hide();
// Show the one you want
$('#' + $(e.target).data('id')).show();
})
https://codepen.io/meltingchocolate/pen/NyzKMp
You may also note that I extracted the ID from the data-id attribute using the .data() method, and attached the event listener with the .click() method. This is the typical way to apply event handlers across a group of jQuery objects.
From what I understood based on your comments you want to show only description of image that has been clicked.
Here is my solution
$('.container').on('click', 'img', function() {
$(this).closest('.container').find('.image-description').addClass('hidden');
$(this).siblings('p').removeClass('hidden');
});
https://jsfiddle.net/rtsj6r41/
Also please mind your jquery version, because unbind() is deprecated since 3.0
You can use event delegation so that you only add your event handler once to the parent of your images. This is usually the best method for keeping work the browser has to do down. Adding and removing classes is a clean method for show and hide, because you can see what is happening by looking at your html along with other benefits like being easily able to check if an item is visible with .hasClass().
jsfiddle: https://jsfiddle.net/0yL5zuab/17/
EXAMPLE HTML
< div class="main" >
<div class="image-parent">
<div class="image">
</div>
<div class="image-descr">
Some text. Some text. Some text.
</div>
</div>
<div class="image-parent">
<div class="image">
</div>
<div class="image-descr">
Some text. Some text. Some text.
</div>
</div>
<div class="image-parent">
<div class="image">
</div>
<div class="image-descr">
Some text. Some text. Some text.
</div>
</div>
<div class="clear">
</div>
</div>
EXAMPLE CSS
.image-parent{
height: 100px;
width: 200px;
float: left;
margin: 5px;
}
.image-parent .image{
background: blue;
height: 50%;
width: 100%;
}
.image-descr{
display: none;
height: 50%;
width: 100%;
}
.show-descr{
display: block;
}
.clear{
clear: both;
}
EXAMPLE JQUERY
$(".main").on("click", ".image-parent", ShowDescription);
function ShowDescription(e) {
var $parent = $(e.target).parent(".image-parent");
var $desc = $parent.find(".image-descr");
$(".image-descr").removeClass("show-descr");
$desc.addClass("show-descr");
}

JQuery show delete button on appended div

I have so far able to remove each appened elements using .remove() function of JQuery. my problem is the delete button is always showing on the first element.
I want to hide the delete button on first element and show the delete button when I append a new element.
I have set the delete button on the first element to
.delete-button:first-child{
display:none;
}
in my css but all succeeding appends do not show the delete button..
how can I do this with JQuery can it be done using CSS only?
li:first-child .delete-button { display:none }
<ul>
<li>
First <button class="delete-button">Delete</button>
</li>
<li>
Second <button class="delete-button">Delete</button>
</li>
</ul>
Making assumptions on your markup since none was provided. You can accomplish it using css.
My interpretation of your requirement: If there is only one item, do not show a delete button; if there are multiple items, show a delete button for every item.
Your attempt didn't work because .delete-button:first-child selects all elements with the delete-button class that are also the first-child of their parent element. Presumably this would be all of your buttons.
You can instead use the :only-of-type selector on the elements that contain the delete buttons, e.g., assuming they have the item class:
.item:only-of-type .delete-button { display: none; }
Or if they are li elements:
li:only-of-type .delete-button { display: none; }
That way if the item/li/whatever is the only item then its delete button will be hidden automatically, but as soon as you add additional items the delete button will be shown automatically for all items.
Here's a simple demo with a bit of JS to mock up the add and delete functionality:
$("#parent").on("click", ".delete-button", function() {
$(this).parent().remove();
});
$(".add-button").on("click", function() {
$("#parent").children().first().clone().appendTo("#parent");
});
.item:only-of-type .delete-button { display: none; }
.item { margin: 3px; padding: 2px; width: 100px; border: thin black solid; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button class="add-button">Add Item</button>
<div id="parent">
<div class="item">
<button class="delete-button">Delete</button>
<div>An item</div>
</div>
</div>
You can try,
div ul:not(:first-child) {
.delete-button{
display:none;
}
}
You might wanna consider browser compatibility before using CSS - Browser support for CSS :first-child and :last-child
Having said that, With jQuery you can write an event handler to change css of all the child elements except the last.
Consider this example from jQuery: How to listen for DOM changes?
$("element-root").bind("DOMSubtreeModified", "CustomHandler");
Yes, Its possible by css only. I hope this snippet helps.
$(document).on('click','#AppendList', function(){
$("ul").append('<li>List <button class="delete-button">Delete</button></li>');
})
li .delete-button { display:none }
li:last-child .delete-button { display: inline-block;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
First <button class="delete-button">Delete</button>
</li>
<li>
Second <button class="delete-button">Delete</button>
</li>
</ul>
<hr>
<button type="button" id="AppendList">Add List</button>

Finding the next hidden div and showing it with a jQuery selector

I'm building a slideshow in jQuery that allows the user to see four images, and page through them, forwards and backwards by appending a new div with the image to the bottom via .load, and then hiding the top div. I'm very new to programming.
I'm having trouble working out a selector to allows the user to go "back" showing the next hidden div, after the first shown div, and hiding the last showing div - faux code example below.
<div class="slideShow" >image one (display = none)</div>
<div class="slideShow" >image two (display = none)</div>
<div class="slideShow" >image three </div>
<div class="slideShow" >image four </div>
<div class="slideShow" >image five </div>
<div class="slideShow">image six </div>
<a href="#" class="scrollUp" >Scrollup</a>
<a href="#" class="scrollDown" >ScrollDown</a>
Jquery to load a new image and attach to the bottom, and hide the first div currently displaying.
$('.scrollDown').click(function() {
$('.slideShow:last').after('<div class="slideShow"></div>'); // add a new div to the bottom.
$('.appendMe:last').load('myimagescript.py'); // load in the image to the new div.
// here I need to find a way of selecting in this example the first shown image (image three) and applying a .slideUp(); to it
});
Jquery to allows the user to go back to an image that they have previously seen and hide the last shown div at the bottom
$('.scrollUp').click(function() {
// here I need to find a way of selecting in this example the first hidden div (image two) after the first shown div (image three) and applying a slideDown(); to it.
$('.slideShow:last').slideUp(); // hide the last image on the page - trouble is what happens if they user now clicks scrollDown - how do I reshow this div rather than just loading a new one?
});
I dont quite understand correctly, however this info may help...you need to match the first visible div then use .prevAll() and filter to get the hidden sibling
$('div.slideShow:visible:first').prevAll(':hidden:first').slideDown();
I've spent hours today on this site trying to do something very similar to what was posted in this question.
What I have is Previous | Next links navigation doing through a series of divs, hiding and showing.
Though what I ended up with was different than the answer here....this was the one that most got me where I needed to be.
So, thanks.
And in case anyone is interested, here's what I did:
<script language="javascript">
$(function() {
$("#firstPanel").show();
});
$(function(){
$(".nextButton").click(function () {
$(".panel:visible").next(".panel:hidden").show().prev(".panel:visible").hide();
});
});
$(function(){
$(".backButton").click(function () {
$(".panel:visible").prev(".panel:hidden").show().next(".panel:visible").hide();
});
});
</script>
<style type="text/css">
.defaultHidden { display: none; }
.navigation { display: block; width: 700px; text-align: center; }
#contentWrapper { margin-top: 20px !important; width: 700px; }
.nextButton { cursor: pointer; }
.backButton { cursor: pointer; }
</style>
<div class="navigation">
<span class="backButton"><< Previous</span> | <span class="nextButton">Next >></span></button>
</div>
<div id="contentWrapper">
<div id="firstPanel" class="panel defaultHidden">
<img src="images/quiz/Slide1.jpg" width="640" />
</div>
<div class="panel defaultHidden">
<h1>Information Here</h1>
<p>Text for the paragraph</p>
</div>
<div class="panel defaultHidden">
<h1>Information Here</h1>
<p>Text for the paragraph</p>
</div>
<div class="panel" style="display: none;">
<img src="images/quiz/Slide4.jpg" width="640" />
</div>
<div class="panel defaultHidden">
<h1>Information Here</h1>
<p>Text for the paragraph</p>
</div>
<div class="panel defaultHidden">
<img src="images/quiz/Slide6.jpg" width="640" />
</div>
Repeat ad naseum...
</div>
a shot in the dark but...
selecting the first shown div and sliding it up
$('.slideShow:visible:first').slideUp();
selecting the first hidden div after the first shown div and sliding it down...
$('.slideShow:visible:first').next('.slideShow:hidden').slideDown()
psuedo selectors FTW!
Something like the following should do the trick
$(function() {
$(".scrollUp").click(function() {
//Check if any previous click animations are still running
if ($("div.slideShow:animated").length > 0) return;
//Get the first visible div
var firstVisibleDiv = $("div.slideShow:visible:first");
//Get the first hidden element before the first available div
var hiddenDiv = firstVisibleDiv.prev("div.slideShow");
if (hiddenDiv.length === 0) return; //Hit the top so early escape
$("div.slideShow:visible:last").slideUp();
hiddenDiv.slideDown();
});
$(".scrollDown").click(function() {
if ($("div.slideShow:animated").length > 0) return;
var lastVisibleDiv = $("div.slideShow:visible:last");
if (lastVisibleDiv.next("div.slideShow").length === 0) {
//No next element load in content (or AJAX it in)
$("<div>").addClass("slideShow")
.css("display", "none")
.text("Dummy")
.insertAfter(lastVisibleDiv);
}
$("div.slideShow:visible:first").slideUp();
lastVisibleDiv.next().slideDown();
});
});
Only thing that this solution does is check if an element that was previously invisible is now being animated. This solves some of the problems regarding multiple clicks of the links that occur before the animations have completed. If using AJAX you'd have to do something similar (e.g. turn a global variable on / off - or just disable the scroll down link) to avoid multiple requests being made to the server at once...

Categories