1

I am learning Javascript and I'm trying to clean up my code. The code is pretty simple: it simply changes the color of some text by clicking some different buttons. When you click the red button the text turns red, the blue button the text turns blue, etc. Here is the code:

HTML:

<h1 id="title">Change my color!</h1>

<button id="btn" onclick="colorRed()">Red</button>
<button id="btn" onclick="colorGreen()">Green</button>
<button id="btn" onclick="colorBlue()">Blue</button>
<button id="btn" onclick="colorBlack()">Black</button>

Javascript:

var title = document.getElementById("title");

function colorRed() {
  title.style.color = "red";
}

function colorGreen() {
  title.style.color = "green";
}

function colorBlue() {
  title.style.color = "blue";
}

function colorBlack() {
  title.style.color = "black";
}

This code works. My question is how do I clean up my Javascript; in a case where I would've had 20 buttons, coding 20 different functions would obviously not be the way to go.

I did try the following for every single color, but that didn't work:

Javascript:

var title = document.getElementById("title");

var btn = document.getElementById("btn");

function changeColor() {
  if(btn.innerHTML == "Red") {
    title.style.color = "red";
  } else if ...
}

I think it goes wrong when I try to identify which button has been clicked by seeing if their inner HTML is equal to a certain color, but I'm not sure how to fix that. Help would be much appreciated!

EDIT: My question isn't a duplicate of Change an element's background color when clicking a different element as the code I wrote works already, and I just want to learn how to clean it up.

Jona
  • 27
  • 8
  • Ya you just described the point of parameters :) – Chris W. Aug 09 '17 at 16:16
  • Possible duplicate of [Change an element's background color when clicking a different element](https://stackoverflow.com/questions/42823979/change-an-elements-background-color-when-clicking-a-different-element) – Fabian Horlacher Aug 09 '17 at 16:17

5 Answers5

4

Might be easiest to just make one changeColor function and pass it a color in the event:

var title = document.getElementById("title");

function changeColor(color) {
   console.log(color);
   title.style.color = color;
}
<h1 id="title">Change my color!</h1>

<button class="btn" onclick="changeColor('red')">Red</button>
<button class="btn" onclick="changeColor('green')">Green</button>
<button class="btn" onclick="changeColor('blue')">Blue</button>
<button class="btn" onclick="changeColor('black')">Black</button>

Side note: you really shouldn't repeat id's and should use class instead.

It is generally not advisable to use inline event handlers, use addEventListener instead. Rather than adding an event listener for each element, I would recommend adding a common parent element, attaching one event listener to that and inspect the event ("event delegation") to determine which color to apply:

var title = document.querySelector('#title');

document.querySelector('#button-container').addEventListener('click', function(event) {
   var color = event.target.getAttribute('data-color')
   title.style.color = color;
}, false)
<h1 id="title">Change my color!</h1>
<div id="button-container">
  <button data-color="red">Red</button>
  <button data-color="green">Green</button>
  <button data-color="blue">Blue</button>
  <button data-color="black">Black</button>
</div>
Rob M.
  • 35,491
  • 6
  • 51
  • 50
  • 1
    Damn, you answered while I was typing my response! – Mark Rabey Aug 09 '17 at 16:21
  • I actually wrote exactly the same code, just without the 'console.log(color);' part and it didn't work, but I am now thinking that I actually made a typo in the HTML part and didn't add parentheses to the colors there. Thanks for the quick answer anyways, it works now! – Jona Aug 09 '17 at 16:21
  • 1
    You shouldn't assign the same id to multiple controls. Each control should have its own id. The should either each get thier own eventhandler, that calls the `changeColor` function and passes in a unique color, or `changeColor` should be passed in a reference to its caller, and should decide the color based on that. – MBurnham Aug 09 '17 at 16:25
  • @MBurnham I'm aware that ids need to be unique, I just copied OP's HTML and showed them a solution to their problem. I agree that `addEventListener` is a more sustainable approach, but there is nothing inherently wrong with this approach (outside of inline/spaghetti code). – Rob M. Aug 09 '17 at 16:31
  • @Cᴏʀʏ Actually, the code does and will work. It's a horrible language design decision IMO, but you can reference elements by their `id` property in the global scope without explicit DOM lookups – Rob M. Aug 09 '17 at 16:32
  • Inline HTML event attributes are not recommended, ever, for many reasons, one of which is the redundancy they cause, which is shown in your answer. – Scott Marcus Aug 09 '17 at 16:33
  • @RobM.: Oh, yeah. Reminds me of the good old IE `document.all[]` array. – Cᴏʀʏ Aug 09 '17 at 16:33
  • @Cᴏʀʏ updated my answer anyway, definitely don't want to encourage that behavior... thanks for pointing it out – Rob M. Aug 09 '17 at 16:35
4

Don't use inline HTML event attributes, such as onclick. There are a variety of reasons why and if you are just starting with JavaScript, you don't want to pick up any bad habits. Instead, keep your JavaScript completely separate from your HTML and follow modern standards using the .addEventListener() JavaScript method for setting up event handlers.

Also, id values must be unique within a document (the whole point of them is to uniquely identify elements). To be able to group just the buttons that relate to this operation, you can give them all the same CSS class and then query on that class in JavaScript (shown below).

Next, you only need one function, but if your individual buttons were storing the color they should produce, that one function could extract it and use it without the need for any arguments to be passed to your function:

var title = document.getElementById("title");

// When you query your document for groups of matching elements (using methods
// like: getElementsByTagName or getElementsByClassName) you get back an object
// that is similar to an array, called a "node list". Although these "array-like"
// objects support some of the standard array object's features, they are not
// true arrays and don't implement many of the powerful array methods out there.

// But, we can convert the node list returned from .querySelectorAll into an array
// and then we can iterate the array with .forEach() looping method later.
var buttonArray = Array.prototype.slice.call(document.querySelectorAll(".colorBtn"));

// Loop through the button array (the function provided as an argument will be 
// executed for each element in the array)
buttonArray.forEach(function(button){
  
  // Set up click event handlers for each button
  button.addEventListener("click", function(){
    // Just set the color to the "data-color" attribute value on the element
    title.style.color = button.dataset.color;
  })

});
.colorBtn {
  box-shadow:2px 2px 1px grey;
  border-radius:4px; 
  width:100px;
  display:inline-block;
  margin:4px; 
}

.colorBtn:hover, .colorBtn:active {
  box-shadow:-2px -2px 1px #e0e0e0;
  outline:none;
}
<h1 id="title">Change my color!</h1>

<!-- Putting related elements into the same class allows you to
     not only style them identically, but also find them in 
     JavaScript more easily. -->
<button id="btn1" class="colorBtn" data-color="red">Red</button>
<button id="btn2" class="colorBtn" data-color="green">Green</button>
<button id="btn3" class="colorBtn" data-color="blue">Blue</button>
<button id="btn4" class="colorBtn" data-color="black">Black</button>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Hi @Scott Marcus, thanks for your answer! It's a little difficult for me as I yet have to study arrays and event listeners, but I might understand it better in a week or two ;-) – Jona Aug 09 '17 at 16:40
  • @JonaWolff Yes, you'll need to get up to speed on these things as they are central to so many solutions out there. But, this really is the best approach. Be very careful in web development, just because something "works" does not mean it will work in all browsers or is a "good" solution. There is a lot of "bad" code out there that proliferates and new folks like yourself don't know any better. Learn about standards and follow them. – Scott Marcus Aug 09 '17 at 16:43
1

Another way to do this is the following:

function changeColor() {
    title.style.color = btn.style.backgroundColor;
}

and set each buttons background to the appropriate color.

Apostolis I.
  • 112
  • 6
1

Rather than have the function figure out what color to change to, you could pass that color to the changeColor function. Your function would become:

function changeColor(color) {
    title.style.color = color;
}

Then in your HTML, you would change the onclick properties to pass that in:

<button id="btn" onclick="changeColor('red')">Red</button>
...

Also, I wanted to mention that your issues before weren't just related to checking the innerHTML like you suggested in your post. One issue would be that you have multiple HTML elements with the same id. That isn't going to work well when using document.getElementById().

Mark Rabey
  • 1,375
  • 9
  • 11
  • 1
    Rob M. was quicker ;-) I indeed forgot that ID's should be unique. What I'm wondering is how to select an element by it's class, but I'm sure I can find that somewhere on here. Thanks alot! – Jona Aug 09 '17 at 16:24
0

When you need a dynamic number of elements, we need an easy way to find them in the DOM. There are some helper methods out there, like getElementsByClassName() on the document object. The first thing I would do is drop the IDs on your buttons (which should be unique, by the way), in favor of a class name:

<button class="btn" onclick="colorRed()">Red</button>
<button class="btn" onclick="colorGreen()">Green</button>
<button class="btn" onclick="colorBlue()">Blue</button>
<button class="btn" onclick="colorBlack()">Black</button>

The second thing I would do is refactor this so that you can use good unobtrusive JavaScript practices (basically getting the JavaScript good out of the HTML markup and wiring it up all in a JavaScript code block). First, we need a way for the JavaScript to know which color you want to change the button text to. Let's introduce a custom HTML data- attribute in place of the hard-coded onclick handlers:

<button class="btn" data-color="red">Red</button>
<button class="btn" data-color="green">Green</button>
<button class="btn" data-color="blue">Blue</button>
<button class="btn" data-color="black">Black</button>

Now, we need a way to find these buttons so we can loop over them and apply an onclick handler. This can be done with the method I mentioned earlier:

var title = document.getElementById("title");
var buttons = document.getElementsByClassName("btn");

This will build you an array of the four buttons (or however many you have).

Then we need to loop over them and assign a click handler that retrieves the color from the data-color attribute and assigns it to the <h1> element's style.color property:

for (var btnIndex = 0; btnIndex < buttons.length; btnIndex++)
{
    buttons[btnIndex].onclick = function() {
        title.style.color = this.getAttribute('data-color');
    }
}

And that's it! We eliminated all of the duplicate and practiced some better JavaScript techniques at the same time. Try the code out below:

var title = document.getElementById("title");
var buttons = document.getElementsByClassName("btn");

for (var btnIndex = 0; btnIndex < buttons.length; btnIndex++)
{
    buttons[btnIndex].onclick = function() {
        title.style.color = this.getAttribute('data-color');
    }
}
<h1 id="title">Change my color!</h1>

<button class="btn" data-color="red">Red</button>
<button class="btn" data-color="green">Green</button>
<button class="btn" data-color="blue">Blue</button>
<button class="btn" data-color="black">Black</button>
Cᴏʀʏ
  • 105,112
  • 20
  • 162
  • 194
  • I indeed forgot that ID's should be unique. What I'm wondering is how to select an element by it's class, but I'm sure I can find that somewhere on here. Thanks alot for your answer! – Jona Aug 09 '17 at 16:24
  • @JonaWolff: I rephrased my answer to make it a little clearer. Please re-read and check out the working example code snippet. – Cᴏʀʏ Aug 09 '17 at 16:28
  • thanks for teaching me some better Javascript manners so to say! However, the code you wrote now changes the colors of the text on the buttons instead of the color of the title ;-) I will suggest an edit to fix that. – Jona Aug 09 '17 at 16:33
  • @Jona: I didn't read your question closely enough. I've modified the answer to change the color of the title. – Cᴏʀʏ Aug 09 '17 at 16:35
  • Keep in mind that `getElementsByClassName` and `getElementsByTagName` return "live node lists" and can hamper page performance. – Scott Marcus Aug 09 '17 at 16:35
  • @ScottMarcus: Agreed, and I probably should have used more modern practices, I just wanted to show OP a slightly more direct port over to plain JavaScript before introducing all of the newer things that require some studying to know how they work. – Cᴏʀʏ Aug 09 '17 at 16:39
  • Thanks Cᴏʀʏ and @ScottMarcus! Eventhough Scott's answer may be better for the long run Cory's answer was easier for me to follow :-) No offence at all Scott - but a beginner like me doesn't know a lot about live node lists ;-) @Cᴏʀʏ, could you explain what this piece of code does: for (var btnIndex = 0; btnIndex < buttons.length; btnIndex++) I know that it pretty much 'scrolls' through all the buttons looking for which button is pressed, but I don't yet understand how it actually works. – Jona Aug 09 '17 at 16:52
  • `var buttons = document.getElementsByClassName("btn");` *This will build you an array of the four buttons (or however many you have).* Well, no it actually won't. It will create an "array-like" live node list (a.k.a. live HTML Collection). – Scott Marcus Aug 09 '17 at 16:55