Reputation: 65
I'm a novice with JavaScript so bear with me.
I'm trying to use JavaScript to generate tabbed windows for specific bits of content. The HTML looks like this:
<div class="tab-win" id="tablinks-d38e44">
<button class="tablinks" data-target="tab-bash-d38e44">bash</button>
<button class="tablinks" data-target="tab-gui-d38e44">gui</button>
</div>
<div class="tabcontents" id="tabcontents-d38e44">
<div class="tabcontent" id="tab-bash-d38e44">This is bash.</div>
<div class="tabcontent" id="tab-gui-d38e44">This is plain old GUI...</div>
</div>
<p class="p">Here is another one.</p>
<div class="tab-win" id="tablinks-d38e56">
<button class="tablinks" data-target="tab-bash-d38e56">bash</button>
<button class="tablinks" data-target="tab-gui-d38e56">gui</button>
</div>
<div class="tabcontents" id="tabcontents-d38e56">
<div class="tabcontent" id="tab-bash-d38e56">This is bash #2.</div>
<div class="tabcontent" id="tab-gui-d38e56">This is plain old GUI..#2.</div>
</div>
</div>
I've cobbled together this function.
function tabbedWindows(evt, env) {
// Declare all variables
var i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(env).style.display = "block";
evt.currentTarget.className += " active";
}
If I add an onclick attribute directly to the button elements like so:
onclick="tabbedWindows(event,'content-id')"
it works great. However, I'm trying to mind separation of concerns and want to add the event dynamically. I'm trying to do this with the following:
const tab = document.querySelectorAll(".tablinks");
const content = document.querySelectorAll(".tabcontent");
for (let i = 0; i < tab.length; i++) {
let conId = tab[i].getAttribute('data-target');
let conArray = Array.from(content);
let con = conArray.find((c) => c.getAttribute('id') === conId);
let c = con.getAttribute('id');
tab[i].addEventListener('click', tabbedWindows(event,c), false);
}
This doesn't work due to the following error: main.js:23 Uncaught TypeError: Cannot read property 'currentTarget' of undefined. The line it's failing on is:
evt.currentTarget.className += " active";
I think the reason for this is by the time the function attempts to attach the listener, event variable is already out of scope. That is just what I've been able to glean from research. The long and the short of it is I'm stumped and not even sure if this is the correct approach. I guess some questions I have are:
Any help is appreciated.
Upvotes: 0
Views: 5155
Reputation: 191986
When you declare the event handler like this - tab[i].addEventListener('click', tabbedWindows(event,c), false);
, you call the tabbedWindows
with undefined
values. Since evt
is undefined
this statement - evt.currentTarget
- throws the error.
You need to pass a 2nd param to the event handler, because you want the content id that you take from the tab `data-target'. However, the tab is the event's target, and you can get the information when you click it. I've refactored the code, and removed redundancies.
const tabLinks = document.querySelectorAll(".tablinks");
const tabContents = document.querySelectorAll(".tabcontent");
const activateTab = target => {
const contentId = target.getAttribute('data-target');
// Get all elements with class="tabcontent" and hide them, and show the current tab
tabContents.forEach(tabContent => {
tabContent.style.display = tabContent.id === contentId ? 'block' : 'none';
});
// Get all elements with class="tablinks" and remove the class "active"
tabLinks.forEach(tablink => {
tablink.classList.remove('active');
});
// Add an "active" class to the button that opened the tab
target.classList.add('active');
};
const tabbedWindows = evt => {
const target = evt.currentTarget;
activateTab(target);
}
document.querySelectorAll(".tablinks").forEach(tab => {
tab.addEventListener('click', tabbedWindows, false);
});
activateTab(tabLinks[0]);
<div class="tab-win" id="tablinks-d38e44">
<button class="tablinks" data-target="tab-bash-d38e44">bash</button>
<button class="tablinks" data-target="tab-gui-d38e44">gui</button>
</div>
<div class="tabcontents" id="tabcontents-d38e44">
<div class="tabcontent" id="tab-bash-d38e44">This is bash.</div>
<div class="tabcontent" id="tab-gui-d38e44">This is plain old GUI...</div>
</div>
<p class="p">Here is another one.</p>
<div class="tab-win" id="tablinks-d38e56">
<button class="tablinks" data-target="tab-bash-d38e56">bash</button>
<button class="tablinks" data-target="tab-gui-d38e56">gui</button>
</div>
<div class="tabcontents" id="tabcontents-d38e56">
<div class="tabcontent" id="tab-bash-d38e56">This is bash #2.</div>
<div class="tabcontent" id="tab-gui-d38e56">This is plain old GUI..#2.</div>
</div>
</div>
Previous answer:
You can use currying to get the env
, and return a new function that waits for the event object:
/** get env and return a new function that will be called with the event object **/
const tabbedWindows = env => evt => {
// Declare all variables
var i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(env).style.display = "block";
evt.currentTarget.className += " active";
}
const tab = document.querySelectorAll(".tablinks");
const content = document.querySelectorAll(".tabcontent");
for (let i = 0; i < tab.length; i++) {
let conId = tab[i].getAttribute('data-target');
let conArray = Array.from(content);
let con = conArray.find((c) => c.getAttribute('id') === conId);
let c = con.getAttribute('id');
tab[i].addEventListener('click', tabbedWindows(c), false); // call tabbedWindows with c
}
<div class="tab-win" id="tablinks-d38e44">
<button class="tablinks" data-target="tab-bash-d38e44">bash</button>
<button class="tablinks" data-target="tab-gui-d38e44">gui</button>
</div>
<div class="tabcontents" id="tabcontents-d38e44">
<div class="tabcontent" id="tab-bash-d38e44">This is bash.</div>
<div class="tabcontent" id="tab-gui-d38e44">This is plain old GUI...</div>
</div>
<p class="p">Here is another one.</p>
<div class="tab-win" id="tablinks-d38e56">
<button class="tablinks" data-target="tab-bash-d38e56">bash</button>
<button class="tablinks" data-target="tab-gui-d38e56">gui</button>
</div>
<div class="tabcontents" id="tabcontents-d38e56">
<div class="tabcontent" id="tab-bash-d38e56">This is bash #2.</div>
<div class="tabcontent" id="tab-gui-d38e56">This is plain old GUI..#2.</div>
</div>
</div>
Upvotes: 2
Reputation: 163
Maybe you should just try doing this first:
for (let i = 0; i < tab.length; i++) {
tab[i].addEventListener('click', tabbedWindows, false);
}
and in your tabbedWindows function you can add these lines
function tabbedWindows(event) {
let conId = event.currentTarget.getAttribute('data-target');
let conArray = Array.from(content);
let con = conArray.find((c) => c.getAttribute('id') === conId);
let c = con.getAttribute('id');
// and then rest of your code goes here
}
Upvotes: 0