
In the usual manner, first I will post the code for you to copy-paste and then if you still want to read about the explanation then you can read further.
The HTML code:
<h2>Tap the matching pairs</h2>
<div class="panels" id="panels">
<ul id="panel1">
<li data-pair="match1">we</li>
<li data-pair="match2">speak</li>
<li data-pair="match3">bread</li>
<li data-pair="match4">a</li>
<li data-pair="match5">men</li>
</ul>
<ul id="panel2">
<li data-pair="match4">ein</li>
<li data-pair="match3">Brot</li>
<li data-pair="match5">Männer</li>
<li data-pair="match1">wir</li>
<li data-pair="match2">sprecht</li>
</ul>
</div>
The Javascript code:
const Panel = class {
constructor($el, option) {
this.$el = null;
this.option = null;
}
get getElement() { return this.$el };
get getOption() { return this.option };
set setElement(el) { return this.$el = el };
set setOption(opt) { return this.option = opt };
select = () => {
this.$el.classList.add('pairing');
}
deselect = () => {
this.$el.classList.remove('pairing');
this.value = null;
this.option = null;
};
match = () => {
this.$el.classList.add('matching');
setTimeout(() => {
this.$el.classList.remove('matching');
this.$el.classList.add('matched');
matchMaker.clear();
},1000);
}
mismatch = () => {
this.$el.classList.add('mismatch');
setTimeout(() => {
this.$el.classList.remove('mismatch');
matchMaker.clear();
},1000);
}
}
const MatchMaker = class {
constructor(panel1, panel2) {
this.panel1 = panel1;
this.panel2 = panel2;
}
isMatched = () => {
return this.panel1.getOption === this.panel2.getOption;
}
isPairingComplete = () => {
return ![this.panel1.getOption, this.panel2.getOption].includes(null);
}
clear = () => {
this.panel1.deselect();
this.panel2.deselect();
}
match = () => {
this.panel1.match();
this.panel2.match();
}
mismatch = () => {
this.panel1.mismatch();
this.panel2.mismatch();
}
};
const panel1 = new Panel();
const panel2 = new Panel();
const matchMaker = new MatchMaker(panel1, panel2);
document.querySelectorAll('#panels li').forEach(item => {
item.addEventListener('click', ev => {
const data = ev.target.dataset.pair;
const $el = ev.target;
const panel = $el.closest("#panel1") ? panel1 : panel2;
panel.setElement = $el;
panel.setOption = data;
panel.select();
if(matchMaker.isPairingComplete()) {
if(matchMaker.isMatched()) {
matchMaker.match();
}
else {
matchMaker.mismatch();
}
}
});
});
The CSS code:
body {
text-align: center
}
:root {
--predominantColor: #ccc;
}
.panels {
display: flex;
justify-content: center;
color: #000
}
.panels ul {
list-style: none
}
.panels li {
border: 2px solid var(--predominantColor);
border-radius: 5px;
margin-bottom:10px;
min-height: 50px;
width: 150px;
display: flex;
flex-wrap: wrap;
place-content: center;
cursor: pointer;
box-shadow: 0 2px var(--predominantColor);
transition: opacity 1s
}
.panels li.pairing {
--predominantColor: lightblue;
}
.panels li.mismatch {
--predominantColor: red;
}
.panels li.matching {
--predominantColor: green
}
.panels li.matched {
--predominantColor: #ccc;
opacity: .3;
pointer-events: none;
}
The explanation
First we need to set up 2 columns in a div and each column will hold list items with a a data-pair attribute. In each column there are items that have an identical data-pair value in the other column. When we click on one item on the first column and then on another in the second column, with javascript we can compare those values. If the don’t match both of the items will get a red color and then just reset to default style. If there is a match, both items will get a green border and then they will fade out(to show they are not clickable anymore).

In the CSS we declared the –predominantColor for the border and box-shadow colour. Then we redefine this variable for the match and mismatch(not matched) scenarios: Red for mismatch, green for match.

Now I wrote the Javascript in an OOP fashion and it got pretty lengthy. So let’s start from the begining

First we created a class with a constructor. The $el represents the DOM element clicked(we will use it’s reference to add or remove classes from that element, instead of searching for it in the DOM). The option is the data-pair value. Both by default are null(meaning they are unset, no pairing is in progress). Then I defined getters and setters to handle the $el and the option indirectly, through methods.

I also defined some methods in the class. The select adds a ‘pairing’ class name on the element. The deselect method removes that class name and also resets the selections in $el and option to null. The match method adds a ‘matching’ class name and after 1 second it will remove that class and add the ‘matched’ class. The difference between those too in terms of visual impact is that the matching adds the green colour and the matched adds the disabled look and feel. The mismatch method adds the red colour and after 1 second it removes that class.
In both match and mismatch methods there is a matchmaker.clear() method that uses the deselect() method from our panels class.
Our panel class is used in 2 instances since we have 2 columns(If we need more columns, we create more instances of that class):

So what is this matchmaker ? It is actually a helper/utility class that has some methods for our matchmaking playground:

After we defined the MatchMaker class we create 1 instance for it:

This class mostly uses the panel class instances in order to make a comparison between both instances or check if both instances have or doesn’t have a certain value or apply some classes on both instances.
So let’s take them one by one. The isMatched method will check if on both instances the option is the same. isPairingComplete checks if the option on neither of the instances contain a null value. The clear method resets the $el and option values to null and removes the ‘pairing’ class all these operations on both instances(both column’s item). The match and mismatch methods uses the methods with the same name from panels class but it applies on both instances again.

Finally we apply our classes and iterate through all li items in both columns then we add an event listener for the click event on these items. When an item is clicked we check if this item is in column 1 or column 2 and we update that instance’s element and option with the new values. The clicket item will become the new $el value and it’s data-pair attribute the option value.
We add the pairing class on our element to have a blue highlight and now we check if the click or the next click will have 2 items selected or only 1. If 2 elements are clicked(selected) then we check if there is a match between their data-pair attribute. If yes we call the match() method to take care of the color change. If there is no match then we call the mismatch() method to add the red and then reset to normal and start the process again.
That’s it, the method names are pretty expressive, the code is readable this way and all the logic is in the class section not in the DOM manipulation section. This is a cool way to write code. Enjoy !