javascripthtmlcssbuttongroup

Optimize & refactor differently styled button groups that swap values in concatenated string (Javascript)?


I am dipping my toe in JavaScript (and CSS). New to asking questions here; open to feedback. I'm posting in part to help others find an answer to a challenge I was facing. I couldn't find an example of what I was trying to do via Googling and head scratching.

The code does what I want it to. What are some approaches to optimize and refactor it?

Multiple button groups ...independently styled ...can each update respective parts of a concatenated string with one click.

enter image description here

https://jsfiddle.net/ncarlton/by8ragpc/1/

I am using HTML, CSS and JavaScript.

Objectives of the code included:

  1. Concatenate variables based on user selections (buttons in my case).
  2. Start with default choices.
  3. Style each button group independently. This is because the three vars will live in different areas of my final implementation's GUI ... they represent different features. The mockup has each button group styled differently.
  4. Any button click updates the concatenated string; no "Submit" button.
  5. The updates replace values in their respective positions of the concatenated string ... so each button click "swaps out" the previous value (don't re-arrange the order of first, middle, last).

JS:

//make default variables in UPPER to show that values should come from .js and not from .html
let _firstName = "MARTIN";
let _middleName = "LUTHER";
let _lastName = "KING";
//build the full Name
let fullName = _firstName + " " + _middleName + " " + _lastName;
//replace the <span>Need A. Change with the default values
//default values start as UPPERcase to show they came from variables and not innerHTML
document.getElementById("myName").innerHTML = fullName;
//swap out any of the name parts by a single button click: first, middle, last
//also style the buttons in each group to look different
function changeName() {
  //First Name Selections and Style changes
  const btnFirstSelect = function() {
    this.parentNode.getElementsByClassName("btnSelFirst")[0].classList.remove("btnSelFirst");
    this.classList.add("btnSelFirst");
    _firstName = this.innerHTML;
    fullName = _firstName + " " + _middleName + " " + _lastName;
    document.getElementById("myName").innerHTML = fullName;
  };
  document.querySelectorAll(".btnsFirstName .btnFirst").forEach(btnFirst => btnFirst.addEventListener('click', btnFirstSelect));
  //Middle Name Selections and Style changes
  const btnMiddleSelect = function() {
    this.parentNode.getElementsByClassName("btnSelMiddle")[0].classList.remove("btnSelMiddle");
    this.classList.add("btnSelMiddle");
    _middleName = this.innerHTML;
    fullName = _firstName + " " + _middleName + " " + _lastName;
    document.getElementById("myName").innerHTML = fullName;
  };
  document.querySelectorAll(".btnsMiddleName .btnMiddle").forEach(btnMiddle => btnMiddle.addEventListener('click', btnMiddleSelect));
  //Last Name Selections and Syle changes
  const btnLastSelect = function() {
    this.parentNode.getElementsByClassName("btnSelLast")[0].classList.remove("btnSelLast");
    this.classList.add("btnSelLast");
    _lastName = this.innerHTML;
    fullName = _firstName + " " + _middleName + " " + _lastName;
    document.getElementById("myName").innerHTML = fullName;
  };
  document.querySelectorAll(".btnsLastName .btnLast").forEach(btnLast => btnLast.addEventListener('click', btnLastSelect));
}
window.onload = changeName;
//click a button and then reload the console with this
//confirm your selection changed the var by grabbing the innerHTML
//confirm the full name was also rebuilt
console.log(_firstName, _middleName, _lastName, fullName);

HMTL:

<body>
  <h4>Name Builder from Button Choices</h4>
  <div>My name is: <span id="myName">Need A. Change</span>
  </div>
  <hr />
  <div class="btnsFirstName ">First name Choices:
    <button class="btnFirst btnSelFirst">Martin</button>
    <button class="btnFirst">Barack</button>
    <button class="btnFirst">Jelly</button>
  </div>
  <div class="btnsMiddleName">Middle Name Choices:
    <button class="btnMiddle btnSelMiddle">Luther</button>
    <button class="btnMiddle">Hussein</button>
    <button class="btnMiddle">Roll</button>
  </div>
  <div class="btnsLastName">Last Name Choices:
    <button class="btnLast btnSelLast">King</button>
    <button class="btnLast">Obama</button>
    <button class="btnLast">Morton</button>
  </div>
  <p>(Each button click should build the full name by swapping out the value of the clicked button in the correct position. Ideally the button groups would change styles independently too.)</p>
</body>

CSS:

.btnFirst,
.btnMiddle,
.btnLast {
  background-color: grey;
  color: green;
}

.btnSelFirst {
  background-color: black;
  color: white;
  outline: none;
}

.btnFirst:hover {
  background-color: black;
  color: white;
  outline: none;
}

.btnSelMiddle {
  background-color: white;
  color: black;
  outline: none;
}

.btnMiddle:hover {
  background-color: white;
  color: black;
  outline: none;
}

.btnSelLast {
  background-color: pink;
  color: blue;
  outline: none;
}

.btnLast:hover {
  background-color: pink;
  color: blue;
  outline: none;
}

body {
  font-family: Helvetica, Arial, Sans-Serif;
}

Using the following question, I sort of "tacked on" the additional redundant functional code to make the buttons "work" the way I wanted: How to apply javascript to multiple button groups

I've tried a couple things to reduce the code redundancy but I hope to learn various techniques.


Solution

  • Hey I come up with something.

    HTML
    Key Points:

    1. <button ... name="firstname">:
    1. <button ... onClick="changeName(this)">:
    1. Make sure the js is appended in the end of
    <body>
      <h4>Name Builder from Button Choices</h4>
      <div>My name is: <span id="myName">Need A. Change</span>
      </div>
      <hr />
      <div class="btnsFirstName ">First name Choices:
        <button class="btnFirst active" name="firstname" onClick="changeName(this)">Martin</button>
        <button class="btnFirst" name="firstname" onClick="changeName(this)">Barack</button>
        <button class="btnFirst" name="firstname" onClick="changeName(this)">Jelly</button>
      </div>
      <div class="btnsMiddleName">Middle Name Choices:
        <button class="btnMiddle active" name="middlename" onClick="changeName(this)">Luther</button>
        <button class="btnMiddle" name="middlename" onClick="changeName(this)">Hussein</button>
        <button class="btnMiddle" name="middlename" onClick="changeName(this)">Roll</button>
      </div>
      <div class="btnsLastName">Last Name Choices:
        <button class="btnLast active" name="lastname" onClick="changeName(this)">King</button>
        <button class="btnLast" name="lastname" onClick="changeName(this)">Obama</button>
        <button class="btnLast" name="lastname" onClick="changeName(this)">Morton</button>
      </div>
      <p>(Each button click should build the full name by swapping out the value of the clicked button in the correct position. Ideally the button groups would change styles independently too.)</p>
      <script src="./script.js"></script>
    </body>
    
    

    JS
    Key Points:

    1. Make a func to update fullname in dom
    2. Combine click handler for all buttons
    //make default variables in UPPER to show that values should come from .js and not from .html
    let _firstName = "MARTIN";
    let _middleName = "LUTHER";
    let _lastName = "KING";
    
    // set as a func to conveniently update fullname in the DOM
    const setFullname = () => {
      document.getElementById("myName").innerHTML = `${_firstName} ${_middleName} ${_lastName}`;
    }
    
    // refer this function in HTML, example:
    // <button class="btnFirst" onClick="changeName(this)">Jelly</button>
    const changeName = el => {
      el.parentNode.getElementsByClassName("active")[0].classList.remove("active");
      el.classList.add("active");
    
      switch(el.name) {
      // el.name is the <button ... name="firstname">
        case 'firstname':
          _firstName = el.innerHTML;
          break;
        case 'middlename':
          _middleName = el.innerHTML;
          break;
        case 'lastname':
          _lastName = el.innerHTML;
          break;
      }
    
      setFullname();
    }
    
    // call once in the beginning to set default
    setFullname()
    
    console.log("firstname: ", _firstName)
    console.log("middlename: ", _middleName)
    console.log("lastname: ", _lastName)
    
    

    CSS
    Key Points:

    1. Dont repeat, if you can, group the same styles together.
    2. CSS is cascading style sheet means inheritance plays big part, style definition from .element can cascade to .element.active, element:hover and so on
    .btnFirst,
    .btnMiddle,
    .btnLast {
      background-color: grey;
      color: green;
      /* no need to set same css on every definition
       * this will be also inherited to .btnFirst.active
       * and so on
       * */
      outline: none;
    }
    
    .btnFirst.active,
    .btnFirst:hover {
      background-color: black;
      color: white;
    }
    
    .btnMiddle.active,
    .btnMiddle:hover {
      background-color: white;
      color: black;
    }
    
    .btnLast.active,
    .btnLast:hover {
      background-color: pink;
      color: blue;
    }
    
    body {
      font-family: Helvetica, Arial, Sans-Serif;
    }