This question already has answers here:
Vanilla JavaScript equivalent of jQuery's $.ready() - how to call a function when the page/DOM is ready for it [duplicate]
(10 answers)
Closed 4 years ago.
Hello I have these divs:
<div id="layoutGroup1">
<h2>UK Map</h2>
<div div style="width: 650px; height: 700px;" id="MapDIV"></div>
<div id="userUpdateDIV"></div>
<div id="BarChartDIV"></div>
<div id="divPack1"></div>
</div>
<div id="layoutGroup2">
<div id="tree"></div>
</div>
<div id="layoutGroup3">
<div id="map"></div>
</div>
I want by having three buttons on the screen and clicking to hide the two divs and display only one.
button id="userButton" onclick ="showOnClick('layoutGroup1');">ECA </button
function showOnClick(element) {
if (element == 'layoutGroup1') {
document.getElementById('layoutGroup1').style.display == 'block';
document.getElementById('layoutGroup2').style.display == 'none';
document.getElementById('layoutGroup3').style.display == 'none';
} else if (element == 'layoutGroup2') {
document.getElementById("layoutGroup1").style.display == 'none';
document.getElementById("layoutGroup2").style.display == 'block';
document.getElementById('layoutGroup3').style.display == 'none';
} else {
document.getElementById("layoutGroup3").style.display == "block";
document.getElementById("layoutGroup1").style.display == "none";
document.getElementById("layoutGroup2").style.display == "none";
}
}
Above is the function I use, although it gives me an error that the getElementByID is null.
Getting null from getElementById() for elements in your HTML
Your specific problem of getting null from your calls to getElementById() is probably caused by your JavaScript running prior to the HTML of your page being fully loaded (i.e. the elements don't exist in the DOM yet, thus null). However, while that is likely the problem, the we can not know that is the problem because your question does not show us the relationship between your HTML and your JavaScript (i.e. it does not show how and when the JavaScript is loaded/run in the page).
The solution to the problem of JavaScript running prior to the elements in the page being available is to delay the execution of your JavaScript until the page has loaded. There are multiple ways to do this. One is to just have your <script> tags at the bottom of your HTML. However, delaying until the page has loaded is usually accomplished by wrapping your code (or just your initialization code) in a function which is then assigned as a listener for one of the variety of events which are triggered at the various stages of the <document> being ready. The most common to use is the <document>'s DOMContentLoaded event. You can do this with the following code:
//Wait to run your initialization code until the DOM is fully loaded. This is needed
// when wanting to access elements that are later in the HTML than the <script>.
if(document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', afterLoaded);
} else {
//The DOMContentLoaded event has already fired. Just run the code.
afterLoaded();
}
afterLoaded() {
//Your initialization code goes here. This is from where your code should start
// running if it wants to access elements placed in the DOM by your HTML files.
// If you are wanting to access DOM elements inserted by JavaScript, you may need
// to delay more, or use a MutationObserver to see when they are inserted.
});
That event can also be accessed as document.onready. Accessing it this way can cause problems when multiple scripts try to do so, as only one can use this method. Thus, it is much better to use the addEventListener() method to listen for this, or any other, event.
Other aspects of your code
In his answer, gavgrif makes some good points regarding the structure of your code including separating your HTML from your JavaScript by using JavaScript to add your event listeners and eliminating the string of if statements by first setting all to be not visible, then set the one you want to be visible. In his answer, it is implied that you have to use jQuery to think about the problem using a different structure. jQuery provides many convenient features. One of its most important feature is cross browser compatibility. However, it also provides a large number of predefined methods which allow short syntax access to commonly used features, which, in most cases, implicitly iterate over all elements which are selected. This all comes at the cost of 85KiB of minimized code. Thus, jQuery is inappropriate if you are only doing a few things.
You can implement the same functionality that gavgrif showed in his answer using vanilla JavaScript.
document.addEventListener('DOMContentLoaded', function(){
//Wait to add event listeners until the DOM is fully loaded. This is needed
// when wanting to access elements that are later in the HTML than the <script>.
queryAll('.showDiv').forEach(function(el){
el.addEventListener('click',showOnClick);
});
});
function showOnClick(event){
var groupNumber=this.value;
queryAll('.layoutGroups').forEach(function(el){
el.style.display='none'
});
document.querySelector('#layoutGroup'+groupNumber).style.display='block';
}
function queryAll(selector){
return asArray(document.querySelectorAll(selector))
}
function asArray(obj){
var newArr = [];
newArr.push.apply(newArr, obj);
return newArr;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class='showDiv' value="1">1</button>
<button class='showDiv' value="2">2</button>
<button class='showDiv' value="3">3</button>
<div class="layoutGroups" id="layoutGroup1">
<h2>UK Map</h2>
<div div style="width: 650px; height: 700px;"id = "MapDIV"></div>
<div id="userUpdateDIV"></div>
<div id = "BarChartDIV"></div>
<div id="divPack1"></div>
</div>
<div class="layoutGroups" id="layoutGroup2">
<div id= "tree">Tree</div>
</div>
<div class="layoutGroups" id="layoutGroup3">
<div id = "map">Map</div>
</div>
Code that is a bit more general purpose/reusable:
In general, I would prefer to have generic show() and hide() functions, as they might be re-used elsewhere. In addition, the following makes asArray() more robust by handing multiple types of input (most of which is not needed here).
document.addEventListener('DOMContentLoaded', function(){
//Wait to add event listeners until the DOM is fully loaded. This is needed
// when wanting to access elements that are later in the HTML than the <script>.
queryAll('.showDiv').forEach(function(el) {
el.addEventListener('click',showOnClick)
});
});
function showOnClick(event){
var groupNumber = this.value;
hide(queryAll('.layoutGroups'));
show(queryDoc('#layoutGroup'+groupNumber));
}
function hide(arraylikeOrElement) {
setDisplay(arraylikeOrElement,'none')
}
function show(arraylikeOrElement) {
setDisplay(arraylikeOrElement,'block')
}
function setDisplay(arraylikeOrElement,text) {
setAStyle(arraylikeOrElement,'display',text);
}
function setAStyle(arraylikeOrElement,which,text) {
asArray(arraylikeOrElement).forEach(function(el) {
el.style[which]=text;
});
}
function queryAll(selector){
//Returns all matches in the document
return asArray(document.querySelectorAll(selector));
}
function queryDoc(selector){
//Returns only the first match in the document (useful for IDs). This is faster
// than querySelectorAll because it does not search the entire DOM. It stops
// after the first match.
return document.querySelector(selector);
}
function asArray(obj) {
//accepts Arrays, array-like Objects (e.g. NodeLists), single elements, primitives
// returns an array, even if the array only has one entry
var newArr = [];
if(typeof obj !== 'object' || obj instanceof Node) {
return [obj];
}
if(Array.isArray(obj)){
return obj;
}
if(obj === null) {
return null;
}
if(typeof obj.length === 'number') {
//NodeList and other array-like objects: faster in most browsers and
// more compatible than Array.from().
newArr.push.apply(newArr, obj);
return newArr;
}
if(typeof obj.nextNode === 'function') {
//e.g. TreeWalkers, NodeIterator
var currentNode;
while(currentNode = nodeIter.nextNode()) {
newArr.push(currentNode);
}
return newArr;
}
if(typeof Array.from === 'function') {
return Array.from(obj);
}
//Could make this much more complex to handle more types of Objects, but not in
// this demo code.
//Indicate that we don't know what to do with the Object
return null;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class='showDiv' value="1">1</button>
<button class='showDiv' value="2">2</button>
<button class='showDiv' value="3">3</button>
<div class="layoutGroups" id="layoutGroup1">
<h2>UK Map</h2>
<div div style="width: 650px; height: 700px;"id = "MapDIV"></div>
<div id="userUpdateDIV"></div>
<div id = "BarChartDIV"></div>
<div id="divPack1"></div>
</div>
<div class="layoutGroups" id="layoutGroup2">
<div id= "tree">Tree</div>
</div>
<div class="layoutGroups" id="layoutGroup3">
<div id = "map">Map</div>
</div>
More compact code:
If you are looking for brevity of code, you could do something like the following [Note: Using ES6 syntax could further reduce the number of characters used.]:
var d=document,q=function(s){return Array.prototype.slice.call(d.querySelectorAll(s))};
d.onready=function(){ //Using document.ready is not a good idea, use addEventListener.
q('.showDiv').forEach(function(e){e.addEventListener('click',function(){
var element=this.value;
q('.layoutGroups').forEach(function(e){e.style.display='none'});
q('#layoutGroup'+element)[0].style.display='block';
})})
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class='showDiv' value="1">1</button>
<button class='showDiv' value="2">2</button>
<button class='showDiv' value="3">3</button>
<div class="layoutGroups" id="layoutGroup1">
<h2>UK Map</h2>
<div div style="width: 650px; height: 700px;"id = "MapDIV"></div>
<div id="userUpdateDIV"></div>
<div id = "BarChartDIV"></div>
<div id="divPack1"></div>
</div>
<div class="layoutGroups" id="layoutGroup2">
<div id= "tree">Tree</div>
</div>
<div class="layoutGroups" id="layoutGroup3">
<div id = "map">Map</div>
</div>
The above code snippets use the HTML provided in gavgrif's answer.
function showOnClick(element){
if(element=='layoutGroup1'){
document.getElementById('layoutGroup1').style.display='block';
document.getElementById('layoutGroup2').style.display='none';
document.getElementById('layoutGroup3').style.display='none';
}
else if(element=='layoutGroup2'){
document.getElementById("layoutGroup1").style.display='none';
document.getElementById("layoutGroup2").style.display='block';
document.getElementById('layoutGroup3').style.display='none';
}
else{
document.getElementById("layoutGroup3").style.display="block";
document.getElementById("layoutGroup1").style.display="none";
document.getElementById("layoutGroup2").style.display="none";
}
}
#layoutGroup1, #layoutGroup2, #layoutGroup3{
display: none;
}
<button id="userButton1" onclick ="showOnClick('layoutGroup1');">ECA </button>
<button id="userButton2" onclick ="showOnClick('layoutGroup2');">button 2 </button>
<button id="userButton3" onclick ="showOnClick('layoutGroup3');">button 3 </button>
<div id="layoutGroup1">
<h2>UK Map</h2>
<div div style="width: 650px; height: 700px;" id="MapDIV"></div>
<div id="userUpdateDIV">this is layoutGroup1</div>
<div id="BarChartDIV"></div>
<div id="divPack1"></div>
</div>
<div id="layoutGroup2">
<div id="tree">this is layoutGroup2</div>
</div>
<div id="layoutGroup3">
<div id="map">this is layoutGroup3</div>
</div>
This is your corrected function
function showOnClick(element){
if(element=='layoutGroup1'){
document.getElementById('layoutGroup1').style.display='block';
document.getElementById('layoutGroup2').style.display='none';
document.getElementById('layoutGroup3').style.display='none';
}
else if(element=='layoutGroup2'){
document.getElementById("layoutGroup1").style.display='none';
document.getElementById("layoutGroup2").style.display='block';
document.getElementById('layoutGroup3').style.display='none';
}
else{
document.getElementById("layoutGroup3").style.display="block";
document.getElementById("layoutGroup1").style.display="none";
document.getElementById("layoutGroup2").style.display="none";
}
}
In if conditions == stands for comparing LHs to RHS and = in display='block' works as assignment operator, assigning value on he right to the object on the left
Though you did not specify jQuery for this question - there is a VERY simple jquery approach. Have the three buttons each with a value that corresponds to the div's. Then in the onclick event (and note the separation of the javascript from the html by removing the inline click handler - better code structure) - you simply get the value of the clicked button, hide() all the divs (using a common class) and then show() the desired one using its id. This could also be done with adding / removing a class of hidden (eg .hidden{display:none}, and it could be done with vanilla javascript - but all those if's.... Note that I added some text to div2 and 3 so that they can be seen to toggle on their respective button clicks.
Note also I am not suggesting to load the entire jQuery library for this one function - too much weight for this small function, but just offering an opnion on allowing a small function to be a part of the larger picture of code structure.
$(document).ready(function(){
$('.showDiv').click(function(){
var element = $(this).val();
$('.layoutGroups').hide();
$('#layoutGroup'+element).show();
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class='showDiv' value="1">1</button>
<button class='showDiv' value="2">2</button>
<button class='showDiv' value="3">3</button>
<div class="layoutGroups" id="layoutGroup1">
<h2>UK Map</h2>
<div div style="width: 650px; height: 700px;"id = "MapDIV"></div>
<div id="userUpdateDIV"></div>
<div id = "BarChartDIV"></div>
<div id="divPack1"></div>
</div>
<div class="layoutGroups" id="layoutGroup2">
<div id= "tree">Tree</div>
</div>
<div class="layoutGroups" id="layoutGroup3">
<div id = "map">Map</div>
</div>
Related
Is there a way to group several variables and click events into one function?
I'd like to only have one function that would call all the classes with the same name but different numbers (from 1 to 3 in this case) so I don't have to make multiple functions like this:
function step1() {
$("#step-1").fadeIn();
}
function step2() {
$("#step-2").fadeIn();
}
function step3() {
$("#step-3").fadeIn();
}
And
$("#step-1-btn").click(function(){
step1();
});
$("#step-2-btn").click(function(){
step2();
});
$("#step-3-btn").click(function(){
step3();
});
I'm sure there is an answer to this question already, but I can't phrase it good enough to find it...
It's not 100% clear to me what you want to achieve, but you can try something like this:
$("button[id^=step-]").click(function() {
var id = $(this).attr("id").split('-')[1]
step(id);
});
function step(num) {
console.log("you called step with number:" + num)
}
The click event will work for any button where the id starts with step-. Then it will split the id and take the number from it and pass it to your function.
Demo (With fadein example)
$("button[id^=step-]").click(function() {
var id = $(this).attr("id").split('-')[1]
step(id);
});
function step(num) {
console.log("you called step with number:" + num)
$('div[id^=step-]').hide();
$("#step-"+num).fadeIn();
}
#step-1,#step-2,#step-3 {
height:200px;
width:200px;
display:none;
background-color:yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="step-1-btn">step 1</button>
<button id="step-2-btn">step 2</button>
<button id="step-3-btn">step 3</button>
<div id="step-1">1</div>
<div id="step-2">2</div>
<div id="step-3">3</div>
Don't use sensitive integers stripped out of classNames, IDs etc. It's dirty coding and error prone.
Here's a far better suggestion, use the data attribute instead, where the selector to fadeIn is stored right inside such an i.e: data-show attribute:
$("[data-show]").on("click", function() {
const selector = $(this).data("show");
$(selector).fadeIn();
});
.step {
display: none;
}
<button type="button" data-show="#step-1">1</button>
<button type="button" data-show="#step-2">2</button>
<button type="button" data-show="#step-3">3</button>
<div class="step" id="step-1">Step 1</div>
<div class="step" id="step-2">Step 2</div>
<div class="step" id="step-3">Step 3</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Docs:
Attribute selectors [MDN]
jQuery .data()
I have jQuery code and a need to repeat it 4x times with changing index.
So I used for loop from JS and inside get jQuery code.
Am I using the right way to pass variables from JS to jQuery?
It should select elements .info-btn of parent elements: #info-box-pr-1, #info-box-pr-2, ...
jQuery(document).ready(function() {
for (let i = 1; i < 5; i++) {
jQuery("#info-box-pr-" + i + ".less-info-box .info-btn").click(function() {
jQuery("#info-box-pr-" + i + ".more-info-box").css("display", "block");
});
jQuery("#info-box-pr-" + i + ".more-info-box .info-btn").click(function() {
jQuery("#info-box-pr-" + i + ".more-info-box").css("display", "none");
});
}
});
jQuery is a framework that relies on Javascript, so you're not passing variables between them. You're simply concatenating the i value to a string, which is fine.
That being said, I would suggest avoiding incremental id attributes, as it leads to more code complexity than necessary.
For example, you could remove the loop and id and make the code infinitely extensible by using common class names along with DOM traversal methods (eg. closest(), next(), prev()) to relate the .info-btn to the .more-info-box.
jQuery($ => {
$('.less-info-box .info-btn').on('click', e => $(e.target).closest('.less-info-box').hide().next().show());
$('.more-info-box .info-btn').on('click', e => $(e.target).closest('.more-info-box').hide().prev().show());
});
.more-info-box { display: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="less-info-box">
Less info
<button class="info-btn">More</button>
</div>
<div class="more-info-box">
More information...
<button class="info-btn">Less</button>
</div>
<div class="less-info-box">
Less info
<button class="info-btn">More</button>
</div>
<div class="more-info-box">
More information...
<button class="info-btn">Less</button>
</div>
<div class="less-info-box">
Less info
<button class="info-btn">More</button>
</div>
<div class="more-info-box">
More information...
<button class="info-btn">Less</button>
</div>
You don't really need jQuery for this. Use Event Delegation to handle things on the document level. For example:
document.addEventListener(`click`, handle);
function handle(evt) {
if (evt.target.id.startsWith('info-box')) {
// hide all div#more-...
document.querySelectorAll(`.more`).forEach(m => m.classList.add(`hidden`));
// display the div with id #more-[id of the clicked button]
document.querySelector(`#more-${evt.target.id}`).classList.remove(`hidden`);
}
}
.hidden {
display: none;
}
<button id="info-box-pr-1">info 1</button>
<button id="info-box-pr-2">info 2</button>
<button id="info-box-pr-3">info 3</button>
<div id="more-info-box-pr-1" class="more hidden">more infobox 1</div>
<div id="more-info-box-pr-2" class="more hidden">more infobox 2</div>
<div id="more-info-box-pr-3" class="more hidden">more infobox 3</div>
<div class="gallery-container">
<?php while (have_rows('gallery')): ?>
[...]
<div class="toggle-container">
<button class="toggle-button active" onclick="gridView()">Grid</button>
<button class="toggle-button" onclick="listView()">List</button>
</div>
<div class="gallery-items grid-items">
[...Gallery Items...]
</div>
<?php endwhile; ?>
</div>
What would be the best way to select specific elements on a page when the elements are created with a while loop shown above. It's an ever-growing list and elements can also be removed.
In this example I am generating a page full of small galleries together with the toggle buttons for the Grid/List view next to each gallery.
I am trying to make all of those buttons work with just the gallery they are generated together with.
I know how to select them based on their index manually, but I don't know how I could tweak the code to be able to make it work with every small gallery separately.
This is what I came up with to make it work with the first gallery:
<script>
const button = document.getElementsByClassName('toggle-button');
const element = document.getElementsByClassName('gallery-items');
function listView() {
if ( element[0].classList.contains('grid-items') ){
element[0].classList.remove("grid-items");
}
button[0].classList.toggle('active');
button[1].classList.toggle('active');
}
function gridView() {
if ( !element[0].classList.contains('grid-items') ){
element[0].classList.add("grid-items");
}
button[0].classList.toggle('active');
button[1].classList.toggle('active');
}
</script>
You might consider using event delegation instead: add a click listener to .gallery-container. If the clicked target is a .toggle-button, run the appropriate logic, selecting the relevant surrounding elements on click:
document.querySelector('.gallery-container').addEventListener('click', ({ target }) => {
if (!target.matches('.toggle-button')) {
return;
}
const toggleContainer = target.parentElement;
const btns = toggleContainer.children;
if (target === btns[0]) {
btns[0].classList.add('active');
btns[1].classList.remove('active');
} else {
btns[0].classList.remove('active');
btns[1].classList.add('active');
}
const galleryItems = toggleContainer.nextElementSibling;
if (target === btns[0]) {
galleryItems.classList.add('grid-items');
} else {
galleryItems.classList.remove('grid-items');
}
});
.active {
background-color: yellow;
}
.grid-items {
background-color: red;
}
<div class="gallery-container">
<div class="toggle-container">
<button class="toggle-button active">Grid</button>
<button class="toggle-button">List</button>
</div>
<div class="gallery-items grid-items">
[...Gallery Items...]
</div>
<div class="toggle-container">
<button class="toggle-button active">Grid</button>
<button class="toggle-button">List</button>
</div>
<div class="gallery-items grid-items">
[...Gallery Items 2...]
</div>
</div>
Note that there's no need to explicitly test if a classList.contains a particular class before adding it (though, there's no harm in doing so, it's just unnecessary).
I got the following javascript code:
function myProfileIn() {
var showme = document.getElementById("myProfileTop").fadeIn(2000);
showme.style.display = "inline";
var showme = document.getElementById("myProfileMain").fadeIn(2000);
showme.style.display = "inline";
}
This code works on my buttons as "onclick="myProfileIn();" so they get to be visible but i also want to include a fadein effect when the button is clicked.
That is why i included ".fadeIn(1000)" but this is not working.
Here is the button which activates the function:
<div id="profileButtonArea"><div id="profileButtonBox"><img onclick="myProfileIn();" id="profileButtonImg" src="<?php echo $userPath; ?>" /></div></div>
And here are the two elements(divs) which are blended in:
<div id="myProfileTop" style="display:none;"></div>
<div id="myProfileMain" style="display:none;"></div>
The issue you have is that fadeIn() is a jQuery method. It's not available on a standard HTMLElement object. Also note that if you want to toggle the element's visibility on successive clicks use fadeToggle().
You should also use unobtrusive Javascript to attach your event handlers as on* event attributes are now considered outdated. As you've tagged the question with jQuery. Here's a working example for all of the above:
$(function() {
$('#profileButtonBox img').click(function() {
$("#myProfileTop, #myProfileMain").stop(true).fadeToggle(2000);
});
});
#myProfileTop,
#myProfileMain {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="profileButtonArea">
<div id="profileButtonBox">
<img id="profileButtonImg" src="yourimage.jpg" title="click me" />
</div>
</div>
<div id="myProfileTop">myProfileTop</div>
<div id="myProfileMain">myPropfileMain</div>
How can I get the div id for a button and identify whether it's within one of two possible ids? For example, we have a call-to-action button that could be inside a div with the id="new" or id="current". Here are a few examples:
<div id="new">
Download
</div>
or
<div id="current">
Download
</div>
It's possible the id could be in a parent or parent's parent div, such as this:
<div id="new">
<div class="something">
Download
</div>
</div>
or this:
<div id="new">
<div class="row">
<div class="col-md-6">
Download
</div
</div>
</div>
We'd like our landing page developers to be able to develop the pages without having to ever touch the JavaScript for this functionality. We're ultimately trying to pass along this value in a URL string, such as this:
fileref.setAttribute("src", "https://oururl.html?cStatus=" + cStatus);
Make this slight modification: onclick="cStatus(this)" and then:
function cStatus(elem) {
var els = [];
while (elem) {
els.unshift(elem);
elem = elem.parentNode;
if (elem.id == "new") {
// has new
break;
} else if (elem.id == "current") {
// has current
break;
}
}
}
In the onclick callback you can get the parent element using $(this).parent() and then check its id.