Testing data attributes

Why does only the top TA get gray instead of both of them when FT button is tapped?

	<h1>Sort Tools</h1>
	<p>Button shows which items are brand FT.</p>
	<button id="ft" class="btn btn__text btn--med">FT</button>
	<button id="refresh" class="btn btn__text btn--med">refresh</button>
	
	<p id="tool" data-brand="ta">TA 1495</p>
	<p id="tool" data-brand="ft">FT 1496</p>
	<p id="tool" data-brand="ta">TA 1497</p>
	<p id="tool" data-brand="ft">FT 1498</p>

<script>
	function buttonShowFt() {
		var idtool = document.getElementById("tool");
			idtool.style.color = 'black'; // reset color to black
		var databrand = idtool.dataset.brand;
		for (var i=0; i<4; i++) {
			if (databrand === 'ta') {
			idtool.style.color = '#ccc';
			}
		};
	}
	
	function buttonRefresh() {
		var idtool = document.getElementById("tool");
			idtool.style.color = '#000'; // reset color to black
	}

	document.getElementById("ft").addEventListener('click', buttonShowFt);
	document.getElementById("refresh").addEventListener('click', buttonRefresh);

</script>

You have multiple elements with the id="tool". An ID can only be used once. You’d need to change that ID stuff to a class instead and update your JS accordingly.

4 Likes

I had a quick play and came up with this.

<h1>Sort Tools</h1>
<p>Button shows which items are brand FT.</p>
<button id="ft" class="btn btn__text btn--med">FT</button>
<button id="refresh" class="btn btn__text btn--med">refresh</button>

<p id="tool1" data-brand="ta">TA 1495</p>
<p id="tool2" data-brand="ft">FT 1496</p>
<p id="tool3" data-brand="ta">TA 1497</p>
<p id="tool4" data-brand="ft">FT 1498</p>

const idtool = document.querySelectorAll("[data-brand]");
function buttonShowFt() {
  idtool.forEach(function (tool) {
    tool.style.color = "black"; // reset color to black
    if (tool.getAttribute("data-brand") === "ta") {
      tool.style.color = "#ccc";
    }
  });
}
function buttonRefresh() {
  idtool.forEach(function (tool) {
    tool.style.color = "black"; // reset color to black
  });
}
document.getElementById("ft").addEventListener("click", buttonShowFt);
document.getElementById("refresh").addEventListener("click", buttonRefresh);

However it looks like it could be simplified with css doing the heavy lifting.

e.g.

<h1>Sort Tools</h1>
<p>Button shows which items are brand FT.</p>
<button id="ft" class="btn btn__text btn--med">FT</button>
<button id="refresh" class="btn btn__text btn--med">refresh</button>

<p id="tool1" data-brand="ta">TA 1495</p>
<p id="tool2" data-brand="ft">FT 1496</p>
<p id="tool3" data-brand="ta">TA 1497</p>
<p id="tool4" data-brand="ft">FT 1498</p>
[data-brand] {
  color: black;
}
.show [data-brand="ta"] {
  color: #ccc;
}
const idtool = document.querySelectorAll("[data-brand]");
function buttonToggleOn() {
  document.querySelector("body").classList.add("show");
}
function buttonToggleOff() {
  document.querySelector("body").classList.remove("show");
}
document.getElementById("ft").addEventListener("click", buttonToggleOn);
document.getElementById("refresh").addEventListener("click", buttonToggleOff);

2 Likes

@PaulOB I don’t think you needed idtool in the last block :slight_smile:

const idtool = document.querySelectorAll("[data-brand]");

Not that you gain much in this instance, but you can also use the optional force argument on classList.toggle

function buttonToggle(force) {
  document.querySelector("body").classList.toggle("show", force)
}

document.getElementById("ft")
  .addEventListener("click", () => buttonToggle(true))

document.getElementById("refresh")
  .addEventListener("click", () => buttonToggle(false))
1 Like

Oh yes I missed that :slight_smile:

That’s neat :).

I used toggle originally but realized if the refresh button was clicked first it wouldn’t work so I changed back to the 2 functions instead.

I’ll know better next time thanks :slight_smile:

1 Like

OK, I’m back!
I don’t understand the buttonToggleOn. How is it specifying FT or TA and what to do?

However, I managed to implement this:

[data-brand] {
  color: gray;
}
.show2 [data-brand="ta"] {
  color: red;
  font-weight: bold;
  }
.show1 [data-brand="ft"] {
  color: red;
  font-weight: bold;
}
  </style>
</head>

<body>
<h1>Highlight Gas & Electric Versions</h1>
<body>
	<table>
		<tr>
			<td>
				<button id="ft" class="btn btn__text btn--med">Ford Electric</button>
				<button id="ta" class="btn btn__text btn--med">Ford Gas</button>
				<button id="refresh" class="btn btn__text btn--med">refresh</button>
			</td>

			<td>
				<p id="tool1" data-brand="ta">2022 Bronco Sport</p>
				<p id="tool2" data-brand="ft">2022 F-150 Lightning</p>
				<p id="tool3" data-brand="ta">2022 F-150</p>
				<p id="tool4" data-brand="ft">2022 Mustang Mach-E</p>
			</td>
		</tr>
	</table>

<script>
const idtool = document.querySelectorAll("[data-brand]");
function buttonToggleFt() {
  document.querySelector("body").classList.remove("show2");
  document.querySelector("body").classList.add("show1");
}
function buttonToggleTa() {
  document.querySelector("body").classList.remove("show1");
  document.querySelector("body").classList.add("show2");
}
function buttonToggleOff() {
  document.querySelector("body").classList.remove("show1");
  document.querySelector("body").classList.remove("show2");
}

document.getElementById("ft").addEventListener("click", buttonToggleFt);
document.getElementById("ta").addEventListener("click", buttonToggleTa);
document.getElementById("refresh").addEventListener("click", buttonToggleOff);
</script>

1 Like

I did make a codepen last time that might be simpler. I have just updated it to make it easier to use.

I’m sure it could be refined further by the JS gurus here :slight_smile:

(We could actually do this in CSS alone but would need to use the checkbox hack which requires a strict structure to the html.)

1 Like

That sounds like my cue :slight_smile:

Here’s a bit of a tidy up, where the brand names are retrieved from the sort buttons, and the remove just expands that array of brands.

(function (d) {
  const body = d.querySelector("body");
  const btns = d.querySelectorAll(".sortbutton");
  const allBrands = Array.from(btns).map(
    (el) => el.dataset.brand
  );

  btns.forEach(function (el) {
    el.addEventListener("click", function () {
      body.classList.remove(...allBrands);

      const brand = el.dataset.brand;
      body.classList.add(brand);
    });
  });
})(document);

3 Likes

Thanks Paul. :slight_smile:

That’s much better.

1 Like

Ha ha, it gets tidier AND further out of my comprehension. Let’s see what new thing I can learn from it…

1 Like

I added comments to show my understanding of what is happening in the script. Can you teach further?

(function (d) {
// select the body in the document (d)
  const body = d.querySelector("body");
  // obtain a list of every element matching btn in the body
  const btns = d.querySelectorAll(".btn");
  //  Make an array composed of all the btn classes. Map them as key/value pairs.
  const allBrands = Array.from(btns).map(
  // shorthand arrow func to return the data-brand. I guess the btns are paired with brands here. Brands are returned.
    (el) => el.dataset.brand
  );

	// listening for a button click for a brand
  btns.forEach(function (el) {
    el.addEventListener("click", function () {
// I don't understand the "..." Remove the css from the brands. 
      body.classList.remove(...allBrands);
	  // put the selected brand in the const
      const brand = el.dataset.brand;
	  // only the chosen brand is returned and that css is used
      body.classList.add(brand);
    });
  });
})(document); // I don't understand this
1 Like

// I don’t understand the “…” Remove the css from the brands.

If you look at classlist.remove on MDN you will see that you can pass in a number of classnames as strings and remove all of them in one go. The same applies to adding classes.

Paul has used the …spread syntax to expand the allBrands array into a sequence of arguments e.g. remove( …[‘a’, ‘b’, ‘c’]) → remove(‘a’, ‘b’, ‘c’)

})(document); // I don’t understand this

The function wrapping the code is an IIFE function, an immediately invoked function. Paul is passing in the document as an argument to that function. The parameter ‘d’ will get that value e.g. ‘d’ will now be a reference to document. You could make ‘d’ anything, for instance ‘doc’ it will do the same.

(function (doc) { // doc gets the reference to document
  doc.getElementById('myId')
})(document) // passed as an argument

Just to add by wrapping your code in an IIFE like this, it encapsulates your code so that you don’t then have conflicts with the global namespace.

1 Like

That description isn’t correct and needs some improvement too. No key/value thing occurs there.

What is happening there is that the btns array of elements, is being converted using map into a different array.

          btns    =>     map   =>  allBrands
---------------------------------------------------
<button data-brand="aa" ...>   |    "aa"
<button data-brand="bb" ...>   |    "bb" 
<button data-brand="cc" ...>   |    "cc" 
<button data-brand="xx" ...>   |    "xx" 
2 Likes