Javascript hide/show with many elements - javascript

in my HTML i have questions and solutions. I want to hide and show solutions for each question with different buttons.
Here is my problem: I am writing a new function for each solution button. For example if i have 100 questions then i need to write 100 different Javascript function.
Is there any better way for doing this. Can i write only one function for each solution button.
Thank you very much in advance.
Here is my html:
<li>Question 1. What is x?</li>
<button class="btn btn-outline-success"
onclick="myFunction()">Solution</button>
<div id="Solution">
<div class="highlight">
<pre>
X is apple
</pre>
<li>Question 2. What is y?</li>
<button class="btn btn-outline-success"
onclick="myFunction1()">Solution</button>
<div id="Solution1">
<div class="highlight">
<pre>
Y is orange
</pre>
And here is my Javascript:
document.getElementById( 'Solution' ).style.display = 'none';
function myFunction() {
var x = document.getElementById('Solution');
if (x.style.display === 'none') {
x.style.display = 'block';
} else {
x.style.display = 'none';
}
}
document.getElementById( 'Solution1' ).style.display = 'none';
function myFunction1() {
var x = document.getElementById('Solution1');
if (x.style.display === 'none') {
x.style.display = 'block';
} else {
x.style.display = 'none';
}
}
Note:I saw many questions about hide and show but i could not solve my problem. If this is a duplicate question please warn me and i can remove.

You can reuse a single function by passing an argument that identifies which element in the document should be shown/hidden:
document.getElementById( 'Solution' ).style.display = 'none';
// The function now expects to be passed the ID of the element
function myFunction(elementID) {
var x = document.getElementById(elementID);
if (x.style.display === 'none') {
x.style.display = 'block';
} else {
x.style.display = 'none';
}
}
Now, you can call the function repeatedly and just pass different element id's each time:
<li>Question 1. What is x?</li>
<button class="btn btn-outline-success" onclick="myFunction('Solution')">Solution</button>
<div id="Solution">
<div class="highlight">
<pre>X is apple</pre>
</div>
</div>
<li>Question 2. What is y?</li>
<button class="btn btn-outline-success" onclick="myFunction('Solution1')">Solution</button>
<div id="Solution1">
<div class="highlight">
<pre>Y is orange</pre>
</div>
</div>
Now, having said that, you should avoid using inline HTML event attributes (onclick, onmouseover, etc.) whenever possible. There are many reasons why. Additionally, instead of repeatedly setting inline styles, you can make this much simpler by toggling the use of a pre-existing CSS class. Also, your HTML syntax is invalid.
Here's what a cleaned up, valid and modern version of your code would look like. All you have to do is copy the HTML structure of a question and the existing JavaScript will immediately work for it. Each question no longer even needs a name ("Solution1", "Solution2", etc.).
// Get all the buttons in a node list
var theButtons = document.querySelectorAll(".btn-outline-success");
// Turn node list into a JS Array
var buttonArray = Array.from(theButtons);
// Loop over the buttons and give each its click event handler
buttonArray.forEach(function(button){
button.addEventListener("click", function(){
// We will pass a reference to the current button to the function
myFunction(this);
});
});
// The function now expects to be passed a reference to the button that was clicked
function myFunction(element) {
// Get a reference to div that follows the button and then search that div
// for the first pre element inside of it:
var answer = element.nextElementSibling.querySelector("pre");
// All we need to do is toggle the visibility of that pre element
answer.classList.toggle("hidden");
}
/* This class will simply be added or removed to show/hide elements */
/* All answers have this applied by default*/
.hidden {
display:none;
}
li { margin-bottom:10px; }
<!--
NOTES:
1. The onclick has been removed (it is now handled in JavaScript)
2. The quesiton names ("Solution", "Solution1", etc.) are no longer needed
3. Your HTML structure was invalid. Here is the proper way to make bulleted lists.
-->
<ul>
<li>Question 1. What is x?
<button class="btn btn-outline-success">Solution</button>
<div>
<div class="highlight">
<pre class="hidden">X is apple</pre>
</div>
</div>
</li>
<li>Question 2. What is y?
<button class="btn btn-outline-success">Solution</button>
<div>
<div class="highlight">
<pre class="hidden">Y is orange</pre>
</div>
</div>
</li>
<li>Question 3. What is z?
<button class="btn btn-outline-success">Solution</button>
<div>
<div class="highlight">
<pre class="hidden">z is mango</pre>
</div>
</div>
</li>
</ul>

If I understood what you want, you can use parameters.
HTML
<li>Question 1. What is x?</li>
<button class="btn btn-outline-success" onclick="myFunction(1)">Solution</button>
<div id="Solution"></div>
<div class="highlight"></div>
<pre>
X is apple
</pre>
JS
function myFunction(var solNumber = 1) {
var x = document.getElementById('Solution'+solNumber);
if (x.style.display === 'none') {
x.style.display = 'block';
} else {
x.style.display = 'none';
}
}
If all the questions appear in the same time, you could use a for loop to show them all too.

You can do something like this:
Enclose the question in a div with class 'question' and similarly solution enclosed with class 'solution'.
Also define a single show function with parameter as the button element clicked, which passed by using this as parameter in the onclick of button.
The function simply finds the sibling div with class 'solution' and makes it visible by setting display: block
function show_solution(buttonElement) {
questionDiv = buttonElement.parentNode;
if (questionDiv.querySelector('.solution').style.display == 'none') {
questionDiv.querySelector('.solution').style.display = 'block';
} else {
questionDiv.querySelector('.solution').style.display = 'none';
}
}
.solution {
display: none;
}
.highlight {
background: #FF0;
}
<div class="question">
• Question 1. What is x?
<button class="btn btn-outline-success" onclick="show_solution(this)">Solution</button>
<div class="solution">
<div class="highlight">
<pre> X is apple </pre>
</div>
</div>
</div>
<div class="question">
• Question 2. What is y?
<button class="btn btn-outline-success" onclick="show_solution(this)">Solution</button>
<div class="solution">
<div class="highlight">
<pre> Y is orange </pre>
</div>
</div>
</div>

Well, if jquery was allowed, the solution could look like this:
$('.Solution').hide()
$('button').on('click',function(){$(this).next().show()})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li>Question 1. What is x?</li>
<button class="btn btn-outline-success">Solution</button>
<div class="Solution">
<div class="highlight">
<pre>
X is apple
</pre>
</div></div>
<li>Question 2. What is y?</li>
<button class="btn btn-outline-success">Solution</button>
<div class="Solution">
<div class="highlight">
<pre>
Y is orange
</pre>
</div></div>
Note: this would also simplify your html ... But, of course, the choice is yours!

Related

My JS Function that displays a block of HTML after pressing an OnClick button is buggy

I have a JS function that works with a button; basically, it's supposed to show the HTML code after clicking the button. However, for some reason, when I load the page, the HTML is visible before clicking the button; clicking the button once makes the code disappear, and then clicking it again makes the code re-appear. It seems like the function is doing the opposite of what I want it to do, but I have no idea why it's doing this: comparing my code to other code that does what I want it to do, I don't see any visible differences.
Here is the script:
<script>
function showTweet() {
var x = document.getElementById("tw-block-parent");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
</script>
Here is the HTML that the script is supposed to make visible:
<div id="tw-block-parent">
<div class="timeline-TweetList-tweet">
<div class="timeline-Tweet">
<div class="timeline-Tweet-brand">
<div class="Icon Icon--twitter"></div>
</div>
<div class="timeline-Tweet-author">
<div class="TweetAuthor"><a class="TweetAuthor-link" href="#channel"> </a><span class="TweetAuthor-avatar">
<div class="Avatar"> </div></span><span class="TweetAuthor-name">TwitterDev</span><span class="Icon Icon--verified"> </span><span class="TweetAuthor-screenName">#TwitterDev</span></div>
</div>
<!--This is where the tweet text goes-->
<div id="timeline-Tweet-text_1"></div>
<div class="timeline-Tweet-metadata"><span class="timeline-Tweet-timestamp">9h</span></div>
<ul class="timeline-Tweet-actions">
<li class="timeline-Tweet-action"><a class="Icon Icon--heart" href="#"></a></li>
<li class="timeline-Tweet-action"><a class="Icon Icon--share" href="#"></a></li>
</ul>
</div>
</div>
</div>
And finally here is the HTML button:
<input type="submit" onclick="onClick(); showTweet();" id="submit-button" class="instructions" value="try me!">
The 'OnClick();' function works fine as far as I know, but just in case I'll post it here too.
<script>
//This function allows different inputs to display different text blocks
function onClick() {
if (document.getElementById("user_input").value === "I hate the EU!")
{
antiEuropeExample();
}
else if (document.getElementById("user_input").value === "I hate traffic!")
{
antiTrafficExample();
}
else if (document.getElementById("user_input").value === "I hate Trump!")
{
antiTrumpExample();
}
else if (document.getElementById("user_input").value === "I hate Facebook!")
{
antiFacebookExample();
}
else
{
alert("Wrong input buddy!");
}
}
</script>
I apologise for the amount of code I've posted here, I hope the question is understandable and that you guys can help! Thank you so much :)
All <div> elements have default styling display:block;, if you want to change the initial behavior of a particular element you have to add styling to it.
in your case i would advice changing this:
<div id="tw-block-parent">
to
<div id="tw-block-parent" style="display:none;">
This would make the div invisible at the start.

How to collapse section using Javascript on single click?

I know this question has been asked multiple times and I have tried different things which are not working.
All I want to do is collapse a section on the click of a button, but I have to click button twice before it actually collapses
(In CollapseGrid is getting printed twice and then it goes to In Collapse).
How do we make it work in one go without having to click twice?
function collapseGrid(element) {
console.log("In CollapseGrid");
element.addEventListener("click", collapse);
};
function collapse() {
console.log("In Collapse");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
}
<button class="info" onClick="collapseGrid(this)">CLICK</button>
<div class="contnet">
<p>This should be collapsed.</p>
</div>
Your section gets collapsed on the second click because on the first click you are simply assigning a click listener, but not really executing the collapse function. The code you included inside collapseGrid() should be written outside of the function. Then just make the button invoke collapse() rather than collapseGrid().
document.querySelector(".info").addEventListener("click", collapse);
function collapse(e) {
var content = e.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
}
<button class="info" onClick="collapse(this)">CLICK</button>
<div class="contnet" style="display:block">
<p>This should be collapsed.</p>
</div>
Here is a simple way to achieve it with a fairly generic function and a some data attributes.
var elements = document.querySelectorAll('[data-toggle="collapse"]');
elements.forEach(element => element.addEventListener('click', collapse));
function collapse() {
var target = this.getAttribute('data-target');
var element = document.getElementById(target);
element.style.display = element.style.display == "none" ? "block" : "none";
}
<div>
<button class="info" data-toggle="collapse" data-target="collapse1">CLICK 1</button>
<div class="content" id="collapse1">
<p>This should be collapsed.</p>
</div>
</div>
<div>
<button class="info" data-toggle="collapse" data-target="collapse2">CLICK 2</button>
<div class="content" id="collapse2">
<p>This should be collapsed.</p>
</div>
</div>
Why don't you simply collapse the next element in the collapseGrid function? e.g.
function collapseGrid(me) {
var content = me.nextElementSibling;
content.style.display = content.style.display == "none" ? "block" : "none";
}
<button class="info" onClick="collapseGrid(this)">CLICK</button>
<div class="content">
<p>This should be collapsed.</p>
</div>

Get clicked element's ID and include it in a function with pure JS?

I need to display different output according to each different icon clicked without defining separate functions;
HTML:
<p onclick="expand()" id="i1">icon1</p>
<p onclick="expand()" id="i2">icon2</p>
<p onclick="expand()" id="i3">icon3</p>
<div id="blocki1"></div>
<div id="blocki2"></div>
<div id="blocki3"></div>
Can I do something like this with JS?
function expand() {
document.getElementById("block" + this.id).style.display = "block";
}
I've tried the method above which apparently didn't work, I need to a)store icon's id and b) combine the id with string. Don't sure if that's possible.
<p onclick="expand(this.id) id="i1">icon1</p>
<p onclick="expand(this.id) id="i2">icon2</p>
<p onclick="expand(this.id) id="i3">icon3</p>
<div id="blocki1"></div>
<div id="blocki2"></div>
<div id="blocki3"></div>
<script>
function expand(e) {
document.getElementById("block" + e).style.display = "block";
}}
</script>
First.. You have 4 typos. First 3 are that you don't have closing " after onclick="expand()
<p onclick="expand() id="i1">icon1</p>
<!-- There needs to be " after expand() -->
Last typo is you have extra closing } after expand function.
Now, since you're not using addEventListener API, the value of this will not be set on your expand function.
So you need to pass your current element as a parameter to the function.
<p onclick="expand(this)" id="i1">icon1</p>
<p onclick="expand(this)" id="i2">icon2</p>
<p onclick="expand(this)" id="i3">icon3</p>
<div id="blocki1">blocki1</div>
<div id="blocki2">blocki2</div>
<div id="blocki3">blocki3</div>
(Added some place holder text to divs to see if this works)
Lastly, access the current element in your function as a first parameter.
function expand(el) {
document.getElementById("block" + el.id).style.display = "block";
}
Pass parameters to the function
You need to pass some data (e.g. the reference to the object, its name, or whatever else you need) to the function you're calling.
For example, look at the sample code from https://www.w3schools.com/jsref/event_onclick.asp
<p onclick="myFunction(this, 'red')">Click me to change my text color.</p>
<script>
function myFunction(elmnt,clr) {
elmnt.style.color = clr;
}
</script>
I might approach it slightly differently by removing the inline JS, and using classes and data attributes. Here I have classes and data attributes on all the elements. I attach click event listeners to the "buttons" which call the handleClick function. This function checks the data id attribute of the button and grabs the corresponding slide, adding a "show" class to its class list.
const buttons = document.querySelectorAll('.button');
buttons.forEach(button => {
button.addEventListener('click', handleClick, false);
});
function handleClick(e) {
const id = e.target.dataset.id;
const slide = document.querySelector(`.slide[data-id="${id}"]`);
slide.classList.add('show');
}
.slide {
display: none;
}
.show {
display: block;
}
<p class="button" data-id="1">icon1</p>
<p class="button" data-id="2">icon2</p>
<p class="button" data-id="3">icon3</p>
<div class="slide" data-id="1">blocki1</div>
<div class="slide" data-id="2">blocki2</div>
<div class="slide" data-id="3">blocki3</div>
Your code should like this
<p onclick="expand(this) id="i1">icon1</p>
<p onclick="expand(this) id="i2">icon2</p>
<p onclick="expand(this) id="i3">icon3</p>
<div id="blocki1"></div>
<div id="blocki2"></div>
<div id="blocki3"></div>
<script>
function expand(elm) {
document.getElementById("block" + elm.id).style.display = "block";
}
</script>
If you are a beginner, I would suggest you to avoid practice of adding handlers in HTML, before it becomes your coding attitude.
Instead, add eventlisteners for them in js. Separation of concerns is really big theory.
And it's relativelyeasy to deal with this in event handlers
You can read more about it here
var ps = Array.from(document.querySelectorAll("p"));
for(var i=0; i< ps.length; i++){
ps[i].addEventListener(("click"), function(){
document.getElementById("block" + this.id).style.display = "block";
})
}
div{
display: none;
}
<p id="i1">icon1</p>
<p id="i2">icon2</p>
<p id="i3">icon3</p>
<div id="blocki1">This is 1</div>
<div id="blocki2">This is 2</div>
<div id="blocki3">This is 3</div>

How to get prev textarea element

I want to get a text from a textarea after clicking on button that is next to the textarea.
The problem is that I will have many textareas and every button must returns the text of the textarea that corresponds to it.
This is my code
function btnmodif(){
var mod = $(this).prev().val();
alert(mod);
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<div class="list-item-edit">
<textarea class="list_input">eggs</textarea>
<button class="btn btn-modify-item" onClick="btnmodif()">get text</button>
</div>
<div class="list-item-edit">
<textarea class="list_input">water</textarea>
<button class="btn btn-modify-item" onClick="btnmodif()">get text</button>
</div>
You have to pass object clicked to btnmodif function.
<button class="btn btn-modify-item" onClick="btnmodif(this)">get text</button>
JS
function btnmodif(button){
var mod = $(button).prev().val();
alert(mod);
};
Also, you should use .prev function.
Read more about .prev() function, here.
function btnmodif(button){
var mod = $(button).prev().val();
alert(mod);
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<div class="list-item-edit">
<textarea class="list_input">eggs</textarea>
<button class="btn btn-modify-item" onClick="btnmodif(this)">get text</button>
</div>
<div class="list-item-edit">
<textarea class="list_input">water</textarea>
<button class="btn btn-modify-item" onClick="btnmodif(this)">get text</button>
</div>
Firstly, you need to pass the clicked element as context to the function:
onClick="btnmodif(this)"
...
function btnmodif(button){
Second, if the HTML structure will remain the same (i.e. the textarea is always going to be the element immediately before the button), then you can use prev()
var mod = $(button).prev('textarea').val();
https://api.jquery.com/prev/
If that structure isn't guaranteed to be maintained, then .siblings() gives you a bit more flexibility, as it searches through all the elements at the same hierarchical level in the DOM to find what you want:
var mod = $(button).siblings('textarea').val();
https://api.jquery.com/siblings/
Here is what are you looking for.
Add this as parameter to your button.onclick
Thanks to jQuery:
Using $(element).parent(), you get your div element.
Using $(element).parent().find('.list_input'), you get your textarea element.
Using $(element).parent().find('.list_input').text() gives you the value of the textarea "related to" the clicked button.
function btnmodif(element){
var result = $(element).parent().find('.list_input').text();
alert(result);
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<div class="list-item-edit">
<textarea class="list_input">eggs</textarea>
<button class="btn btn-modify-item" onClick="btnmodif(this)">get text</button>
</div>
<div class="list-item-edit">
<textarea class="list_input">water</textarea>
<button class="btn btn-modify-item" onClick="btnmodif(this)">get text</button>
</div>
You're missing the this from the inline handler specification:
https://jsfiddle.net/3mvod6ux/
Use siblings with a selector to get the textarea's value that belong to the same block of the button clicked.
function btnmodif(button){
var mod = $(button).siblings("textarea").val();
alert(mod);
};
Another way to do this task .
Instead to use HTML event attribute this is better approach.
var btnModifyItem = $('.btn-modify-item');
btnModifyItem.click(function(){
var mod = $(this).prev().val();
alert(mod);
})
This way you can get the parent of button (and textarea) and then can get the text from child textarea:
$('button').on('click', function () {
console.log(($(this).parent().find("textarea").text()));
});

Accessing parentNode with button

I am trying to access the parentNode of a button element (div) but I get an error every time. Here is what I am trying to do. Simply put, this is a page that had 2 divs on it. I only want one div displayed at a time. There is a button on the first page that switches to the second and a button on the second that is supposed to return to the first (but doesn't because it can't locate the parentNode).
<div class="pageContent" id="page_3">
<div class="primaryPage" id="primaryPage_3">
<h2>Bills_______________________</h2>
<p>Here are my monthly Bills<br><br>blah blah</p>
<button type="button" id="billButton" onclick="newBill()">Add Bill</button>
</div>
<div class="pageContent" id="billPage">
<p>this is the bill page</p>
<button class="backButton" onclick="back()">Back</button>
</div>
</div>
function newBill(){
document.getElementById("primaryPage_3").style.display = "none";
document.getElementById("billPage").style.display = "block";
document.getElementById("billPage").setAttribute("back", "primaryPage_3");
}
function back(){
var previousPage = this.parentNode.getAttribute("back");
this.parentNode.style.display = "none";
document.getElementById(previousPage).style.display = "block";
}
Change this...
onclick="back()"
to this...
onclick="back.call(this)"
Invoking a function using .call() lets you manually set the calling context (the this value) of the function being invoked. Here it's setting it to the current element.

Categories