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.