Traverse through div elements using tab index - javascript

So, I'm creating a Memory Game and I want to be able to select and flip the different cards using the keyboard. Right now I'm using tab index on the cards (which are DIV elements), but the problem is that when traversing over the cards using TAB I'm not selecting the cards in the correct order, rather it's jumping around when selecting. I have tried setting the tab index value to different values, such as the first card having the lowest value and the last card having the highest value.
So, my question is: How can I traverse the cards with the tab key in the correct order?
Note, I'm only using vanilla javascript.

Next time, try to provide a minimal reproducable example.
Here's one: sort the elements on their tabindex property value:
document.querySelector(`[tabindex="1"]`).focus();
const sortedDivsOnTabIndex = [...document.querySelectorAll(`[tabindex]`)]
.sort((divA, divB) =>
+divA.getAttribute(`tabindex`) - +divB.getAttribute(`tabindex`));
// result
document.querySelector(`pre`).textContent = sortedDivsOnTabIndex
.map(div =>
`tabindex ${div.getAttribute(`tabindex`)}, content "${div.textContent}"`)
.join(`\n`);
#tiContainer {
display: inline-block;
width: 12rem;
}
pre {
display: inline-block;
position: absolute;
top: 0;
left: 13rem;
padding-left: 1rem;
border-left: 1px solid #999;
}
<div id=tiContainer>
<div tabindex=3>t3</div>
<div tabindex=4>t4</div>
<div tabindex=8>t8</div>
<div tabindex=6>t6</div>
<div tabindex=1>t1</div>
<div tabindex=9>t9</div>
<div tabindex=2>t2</div>
<div tabindex=10>t10</div>
<div tabindex=7>t7</div>
<div tabindex=5>t5</div>
</div>
<pre></pre>

Related

Toggle or Show/Hide

I need help toggling overlays with multiple divs. I don't want to have a separate function for each one (there's 6 with 6 different overlay popups). The onclick div will reveal the overlay popup. Help is appreciated!
I need help toggling overlays with multiple divs. I don't want to have a separate function for each one (there's 6 with 6 different overlay popups). The onclick div will reveal the overlay popup. Help is appreciated!
function on() {
document.getElementById("overlay").style.display = "block";
}
function off() {
document.getElementById("overlay").style.display = "none";
}
#overlay {
position: fixed;
display: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.8);
z-index: 2;
cursor: pointer;
}
#text{
position: absolute;
top: 50%;
left: 50%;
font-size: 1rem;
color: white;
transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
}
<!-- //DIV -->
<div class="row ">
<div class="col-md-6 col-lg-4 d-flex align-items-stretch" onclick="on()">
<div class="card mb-3">
<img src="img/ballet.jpg" class="embed-responsive w-100 classpic" alt="...">
<div class="card-body">
<h5 class="card-title">BALLET</h5>
</div>
</div>
</div>
<!-- //POPUP -->
<div id="overlay" onclick="off()">
<div id="text">
<h3>Ballet</h3>
<p>Ballet is an artistic dance form performed to music using precise and highly formalized set steps and gestures.
Classical ballet, which originated in Renaissance Italy and established its present form during the 19th century,
is characterized by light, graceful, fluid movements and the use of pointe shoes.
</p>
<h4>Shedule:</h4>
<p>Ages 4-8: Thursdays • 4PM<br>
Ages 9-14: Fridays • 7PM</p>
</div>
</div>
There's a problem with your approach, namely, when an element has display:none it is removed from the html tree and cannot receive a click event. Also, no two elements can share the same id attribute and so your function cannot be applied by reference to an id directly.
I've made a working snippet that achieves what I think you are after. There are undoubtedly others that would work but it's quite straight forward and works.
Firstly, arrange each of your alternative div pairs (one hidden, one visible) inside a parent div and give it a class name. This has the advantage that, if you size the container div appropriately, the content will not jump about when you swap the hidden div for visible and vice versa. Next, give classes to distinguish the (initially) hidden content from the visible div. Your markup pattern then will be repeats of:
<div class='container'>
<div class='main'>my first main content</div>
<div class='hidden'>my first hidden content </div>
</div>
In the style sheet, set the class display properties:
.hidden {
display: none;
}
.main {
display: block;
}
Then, set up a click event listener in javascript. This will take a click event from anywhere on the page.
document.addEventListener('click', event => {
})
inside the event listener, place an if block to test whether the click event was received by an element that was inside a div of .container class:
if (event.target.parentElement.className=='container') {
}
I slightly modified this, see edit note and bottom.
If the click event got that far, the click must have been recieved by the visible div inside that container (since the hidden one cannot receive click events and they are the only two elements present.
So you can go ahead and swap the classes applied to the visible div that received the click:
event.target.classList.add('hidden');
event.target.classList.remove('main');
You now have to do the opposite to the other div in the container class to make that sibling visible. The problem is, you don't know whether the hidden class was the first child, or the second child of the container div. What you do know for sure, is that the other div is a sibling of the div you just made invisible.
So we can test to see if there is a next sibling using a conditional:
if (event.target.nextElementSibling) {
event.target.nextElementSibling.classList.add('main');
event.target.nextElementSibling.classList.remove('hidden');
}
If the hidden div followed the visible one, a nextElementSibling will be found and the classes swapped. If no nextElementSibling was found, we know the other div had to come before the one we already hid.
so, an else extension of that if block can be added to switch the classes on the previousElementSibling:
...} else {
event.target.previousElementSibling.classList.add('main');
event.target.previousElementSibling.classList.remove('hidden');
} // end else;
And you're done!
I wanted to explain the logic in detail to make sure you know what's going on, but it's not that complicated.
The advantage of an approach like this is that the single event listener will cope with 1, 2, or 1,000 pairs of divs and none need any special IDs or anything other than an initial class of .main or .hidden (and that they be grouped inside a .container div.
document.addEventListener('click', event => {
if (event.target.parentElement && event.target.parentElement.className=='container') {
event.target.classList.add('hidden');
event.target.classList.remove('main');
if(event.target.nextElementSibling) {
event.target.nextElementSibling.classList.add('main');
event.target.nextElementSibling.classList.remove('hidden');
} else {
event.target.previousElementSibling.classList.add('main');
event.target.previousElementSibling.classList.remove('hidden');
} // end else;
} // end parentElement if;
}) // end click listener;
.hidden {
display: none;
border: 1px solid red;
margin: 5px;
}
.main {
display: block;
border: 1px solid black;
margin: 5px;
}
<div class='container'>
<div class='main'>my first main content</div>
<div class='hidden'>my first hidden content </div>
</div>
<div class='container'>
<div class='main'>my second main content</div>
<div class='hidden'>my second hidden content </div>
</div>
Edit the conditional to detect whether the parent element of the click event was a .container div was modified to check that the event target has a parent AND that the parent is a .container div. This prevents an error if a click is received anywhere outside of the container div.
** Displaying an Opaque Overlay in Response to Click **
Again, this solution allows the functionality to be applied to limitless div elements without the need for independent ids. Again, two classed .main and .hidden are used to decide which div has been clicked from a single event listener applied to the document rather than to multiple divs.
The basic process of displaying, and then re-hiding the (originally hidden) .overlay div is very simple:
if (element.className == 'main') {
element.parentElement.getElementsByClassName('overlay')[0].classList.remove('hidden');
}
if (element.className == 'overlay') {
element.classList.add('hidden');
}
However, a problem arises because of the use of class names, rather than ids. Namely, when the overlay is displayed, a click on it may be received by a descendent element that does not have the class name .hidden. To work properly, every descendent of the overlay div would have to be given the .hidden class and the class swapped applied for ever element inside the .hidden div. This could get very complicated if the div had many child elements (perhaps with their own descendents).
Instead, when a click is received, the target element is inspected to see if it has a relevant class (main or hidden). If it does, the script flows to the simple class switching blocks. If it has no, or a different class name however, a do-while loop examined the parent element of the click to see if it was contained in a relevant (main or hidden) class. The loop continues searching up the document tree until either a relevant element is found, or there are no more parent elements to examine.
If a parent is found to have the required class name, a reference to the element is passed onto the class switching block.
do {
if (element && (element.className == 'overlay' || element.className == 'main')) {
// foundElementClassName = element.className;
break;
} // end if;
if (element.parentElement) {
element = element.parentElement;
} else {
break;
}
} while (element.className != "overlay" || element.className != "main");
The following working snippet demonstrates the functionality. In it, three divs (coloured pink) have an associated (initially) hidden overlay div, while a fourth div has no associated overlay and should ignore clicks.
If a click is made on a pink div, it's specific overlay appears. A click anywhere on the overlay dismisses it, regardless of whether the click was received by the overlay div itself, or by a child element or deeper descendent (e.g. clicking on the text of the overlay (which is in a child h2 element still allows the correct .overlay div to have its styles switched to hide it again.
document.addEventListener('click', (event) => {
let element = event.target;
do {
if (element && (element.className == 'overlay' || element.className == 'main')) {
// foundElementClassName = element.className;
break;
} // end if;
if (element.parentElement) {
element = element.parentElement;
} else {
break;
}
} while (element.className != "overlay" || element.className != "main");
// end do-while loop;
// if a relevant element was found, the element object is stored in element variable;
if (element.className == 'main') {
element.parentElement.getElementsByClassName('overlay')[0].classList.remove('hidden');
}
if (element.className == 'overlay') {
element.classList.add('hidden');
}
}) // end click event listener;
.main {
display: block;
width: 50%;
margin: 10px;
border: 1px solid black;
background: pink;
}
.overlay {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0px;
left: 0px;
width: 100%;
min-height: 100%;
bottom: auto;
z-index: 1;
background: rgba(255,255,0,0.7);
padding: 20px;
}
.hidden {
display: none;
}
.other {
display: block;
width: 50%;
margin: 10px;
border: 1px solid black;
background: yellow;
}
<div class="container">
<div class="main">Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1 </div>
<div class="overlay hidden"><h1>overlay for first pink div</h1> </div>
</div>
<div class="other">
some other content that doesn't have an associated overlay and that should ignore clicks.
</div>
<div class="container">
<div class="main">Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2.</div>
<div class="overlay hidden"><h1>overlay for SECOND pink div</h1> </div>
</div>
<div class="container">
<div class="main">Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. </div>
<div class="overlay hidden"><h1>overlay for Third pink div</h1> </div>
</div>

How could I make this tab controller code cleaner and simpler?

How would I be able to simplify this jquery code. I feel like I am repeating myself and just wondering if there is a shorter way to write this which I'm sure there is. I am a bit new to javascript and jquery. I have created a two tabs with their own containers with miscellaneous information in them. Basically I want the container to open when it's related tab is clicked on. I also would like the tab to be highlighted when it's active. Also, how would I be able to write code to make all tab containers disappear when you click off from the tab containers.
<!-- HTML Code -->
<div class="sort-filters">
<span class="sort-by active">SORT BY</span>
<span class="filter">FILTER</span>
</div>
<div class="sort-containers">
<div class="sort-by-container">Sort by click me here</div>
<div class="filter-container">Filter click me here</div>
</div>
/* CSS */
.sort-filters {
display: flex;
width: 500px;
height: 30px;
}
.sort-by,
.filter {
background: #CCC;
color: #756661;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Arial', sans-serif;
cursor: pointer;
}
.sort-by-container,
.filter-container {
width: 500px;
background: #756661;
color: #FFF;
height: 100px;
display: none;
}
.active {
background: #756661;
color: #FFF;
transition: 0.2s;
}
// Jquery Code
js = $.noConflict();
var sort = js('.sort-by');
var filter = js('.filter');
var sortContainer = js('.sort-by-container');
var filterContainer = js('.filter-container');
js(sort).click(function() {
js(filterContainer).hide();
js(sortContainer).show();
js(sort).addClass('active');
js(filter).removeClass('active');
});
js(filter).click(function() {
js(sortContainer).hide();
js(filterContainer).show();
js(filter).addClass('active');
js(sort).removeClass('active');
});
In order to avoid such repetitive actions I like to stick to naming conventions, so that I can apply the ID's, classes or attributes from one element to select other elements, for instance:
<div id="tabs">
<span class="active" data-type="sort-by">SORT BY</span>
<span data-type="filter">FILTER</span>
</div>
Now, all you need is one click handler on #tabs span, and get the data-type of the span you clicked on. You can use that to filter on the classes of the other container elements.
The second thing is that you can attach handler to more than 1 element at the same time. So in your example, js('#sort-containers div').hide(); will hide all the div's that match the selector at once.
results
I changed some classes to ID's, and some classes to data attributes. Here's a fiddle: https://jsfiddle.net/mq9xk29y/
HTML:
<div id="tabs">
<span data-type="sort-by">SORT BY</span>
<span data-type="filter">FILTER</span>
</div>
<div id="sort-containers">
<div class="sort-by-container">Sort by click me here</div>
<div class="filter-container">Filter click me here</div>
</div>
JS:
js = $.noConflict();
var $tabs = js('#tabs span');
$tabs.click(function() {
var $clicked = js(this); //get the element thats clicked on
var type = $clicked.data('type'); //get the data-type value
$tabs.removeClass('active'); //remove active from all tabs
$clicked.addClass('active'); //add active to the current tab
js('#sort-containers div').hide(); //hide all containers
js('.' + type + '-container').show().addClass('active'); //add active to current container
});
As long as you follow the naming convention of data-type: bla in the tabs, and bla-container on the classes in sort-container, you never have to worry about coding for additional tabs.
There might still be things that could be further optimised, but at least it'll take care of the repetition.

CSS and JavaScript click on a button to create card elements

Say I have a blank page and a button (somewhere in the top right corner). When I click that button I want to be able to create a square on the page (A contact card). And when I click it again I want to be able to create another card next to it with the same dimensions and so on (i.e every click adds a card till theres 4 in a roll then starts on the bottom of the card untill whole page is filled).
I am unsure on how I can accomplish this. I know how to insert a button and a click event just not sure how I can structure this. Would I need to use flex?
Thanks in advance,
I am trying to visualize how I can tackle this problem.
Would I need to use flex?
In 2016, flex would be the best way to approach creating horizontal rows, each containing 4 equal-width elements, yes.
But if you want a legacy-browser solution, you can also use
display: inline-block;
float: left;
width --px;
and a container with an explicitly specified width which means that every :nth-of-type(4n+1) element will start on a new row.
For instance:
.card {
display: inline-block;
float: left;
width: 100px;
margin: 12px;
}
means each card requires 124px of space (12px + 100px + 12px).
So if you give the .card-container an explicit width of 4 x 124:
.card-container {
width: 496px;
}
then after every 4 cards, the next card will begin on a new row.
Here's a quick prototype using jQuery and Twitter Bootstrap.
When pressing the button, the first card with class card-hidden is shown and has it's card-hidden class removed. The next button press will show the next card until there's no cards left.
HTML
<html>
<body>
<button id="button">Add</button>
<div class="container">
<div class="row">
<div class="card card-hidden col-xs-3">1</div>
<div class="card card-hidden col-xs-3">2</div>
<div class="card card-hidden col-xs-3">3</div>
<div class="card card-hidden col-xs-3">4</div>
</div>
</div>
</body>
</html>
CSS
.card {
height: 200px;
}
.card-hidden {
display: none;
}
JS
$("#button").on("click", function(e) {
if ($(".card-hidden").length > 0) {
$(".card-hidden").first().slideToggle(function() {
$(this).removeClass("card-hidden");
});
} else {
console.log("No more cards to show.");
}
});

How to use JavaScript to Alter CSS for Multiple Elements

I am trying to use JavaScript to change the background color of an element after being selected, and also to make sure that only one element at a time has the particular background color. Once the user selects on a different element I would like the previous element that was selected to be replaced by a different background color. Currently I am only able to toggle individual elements by selecting on EACH element. I need to be able to select on an element and apply the new background color, then have JavaScript change the background color of the previously active element to a different color (one less click).
What I am trying to do is very similar to modern navbars or list items where only one element at a time is “active” and has a background color that is different than the other elements in the same div, row, etc.
Notes about my work I am utilizing bootstrap and have no desire to use jQuery for this particular project.
CSS:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
h4 {
border: 1px solid black;
border-radius: 8px;
padding: 10px 2px 10px 2px;
margin: 20px 20px 0px 20px;
background-color: #F0F0F0;
border-color: #F8F8F8;
color: #505050;
cursor: pointer;
}
.active {
background-color: #99E6FF;
}
</style>
</head>
</html>
HTML:
<div id="pTwoRowOne">
<div class="row">
<div class="col-md-4 row row-centered">
<h4 id="techBio" class="test">Biology</h4>
</div>
<div class="col-md-4 row row-centered">
<h4 id="techCart" class="test">Cartography</h4>
</div>
<div class="col-md-4 row row-centered">
<h4 id="techChem" class="test">Chemistry</h4>
</div>
</div>
</div>
JavaScript:
document.getElementById("techBio").onclick=function() {
document.getElementById("techBio").classList.toggle('active');
}
document.getElementById("techCart").onclick=function() {
document.getElementById("techCart").classList.toggle('active');
}
document.getElementById("techChem").onclick=function() {
document.getElementById("techChem").classList.toggle('active');
}
An example can be seen here: http://jsbin.com/fugogarove/1/edit?html,css,js,output
If clarification is needed let me know.
Yup, pretty straightforward.
Assumptions
You're not trying to support IE8, since you're using classList
You're okay with housing your elements as variables as opposed to repeatedly querying the DOM.
Example
JSBin
Code
I rewrote your JavaScript to make it a little bit cleaner and to DRY it up a bit:
var techs = [].slice.call(document.querySelectorAll('#pTwoRowOne h4'));
function set_active(event) {
techs.forEach(function(tech){
if (event.target == tech) { return; }
tech.classList.remove('active');
});
event.target.classList.toggle('active');
}
techs.forEach(function(item) {
item.addEventListener('click', set_active);
});
Some explanation
[].slice.call(document.querySelectorAll('#pTwoRowOne h4')); – We're using this to change the output from a NodeList to an Array. This allows us to use forEach later. querySelectorAll returns a NodeList that contains all elements matching the CSS selector. You can probably replace that with a better CSS selector depending on your environment.
addEventListener is a much nicer way than the iterative add via onclick += to bind an event listener. It's also the recommended way (as far as I know) in ECMA5 and later.
By setting the element queries as variables, you'll be able to keep the reference in memory instead of polling the DOM every time to alter elements. That'll make your JavaScript marginally faster, and it's again just a nicer, cleaner version of the code which it produces.
updates
I reworked the JS to make more sense.
Assuming you only ever have one active element, you can find it using document.querySelector() - if you can have multiples you can use document.querySelectorAll() and iterate through them.
Simple case:
function activate(event) {
var active=document.querySelector('.active');
// activate the clicked element (even if it was already active)
event.target.classList.add('active');
// deactivate the previously-active element (even if it was the clicked one => toggle)
if (active) active.classList.remove('active');
}
document.getElementById("techBio").addEventListener("click",activate);
document.getElementById("techCart").addEventListener("click",activate);
document.getElementById("techChem").addEventListener("click",activate);
h4 {
border: 1px solid black;
border-radius: 8px;
padding: 10px 2px 10px 2px;
margin: 20px 20px 0px 20px;
background-color: #F0F0F0;
border-color: #F8F8F8;
color: #505050;
cursor: pointer;
}
.active {
background-color: #99E6FF;
}
<div id="pTwoRowOne">
<div class="row">
<div class="col-md-4 row row-centered">
<h4 id="techBio" class="test">Biology</h4>
</div>
<div class="col-md-4 row row-centered">
<h4 id="techCart" class="test">Cartography</h4>
</div>
<div class="col-md-4 row row-centered">
<h4 id="techChem" class="test">Chemistry</h4>
</div>
</div>
</div>
Another similar yet simpler way to do it: jsBin ;)
var H4 = document.getElementsByClassName("test"), act;
[].forEach.call(H4, function(el){
el.addEventListener("click", function(){
if(act) act.classList.remove("active");
return (this.classList.toggle("active"), act=this);
});
});
You can do something like this:
[].slice.call(document.querySelectorAll(".test")).forEach(function(element) {
element.addEventListener('click', function(event) {
if (activeElement = document.querySelector(".test.active")) {
activeElement.classList.remove("active");
};
event.target.classList.add('active');
});
});
Basically, first we remove the active class from the active element, then we add it to the target.
JSBin

Add to Favorites Array

What I want to do in Javascript/Jquery is be able to click a button (a button on each item), that adds it to an array. This array will then be posted in order when you click on a favorites page.
I'm just having a hard time wrapping my head around how this would work. Because I may want each item in the array to contain a few things, such as a picture and text describing the item.
In general terms/examples, how would this be set up?
There are a number of ways to do this. But, I'll go with one that's a bit more general - which you can extend for yourself:
http://jsfiddle.net/TEELr/11/
HTML:
This simply creates different elements with the favorite class - which will be the selector by which we check if an element has been clicked.
<div class="favorite"><p>Add to favorites</p></div>
<div class="favorite type2"><p>Just another favorite type</p></div>
<button id="reveal">
Reveal Favorites
</button>
JS:
Every time an element with the "favorite" CSS class is clicked, it is added to the array - this also works for elements with more than one class (that have the "favorite" CSS class).
Now, when the "Reveal Favorites" button is clicked, it will alert what's in the array - which is in the order clicked (as asked).
$(document).ready(function() {
var favorites = [];
var counter = 0;
$('.favorite').click(function() {
++counter;
favorites.push("\"" + $(this).text() + " " + counter + "\"");
});
$('#reveal').click(function() {
alert(favorites);
});
});
CSS:
Simple CSS that only exist for demonstration purposes to prove previous point with multiple CSS class selectors:
.favorite {
width: 400px;
height: 50px;
line-height: 50px;
text-align: center;
display: block;
background-color: #f3f3f3;
border-bottom: 1px solid #ccc;
}
.favorite.type2 {
background-color: #ff3;
}
.favorite:hover {
cursor:hand;
cursor: pointer;
}

Categories

Resources