Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

paulodiogo
paulodiogo
15,120 Points

My solution to (almost) all improvements RSVP App

Hi community,

I made the following improvements to the RSVP App, as suggested by Guil at the end of the DOM by Scripting By Example course:

  • Form validation for both empty string and duplicate
  • Reject duplicates when editing an existing name
  • Label toggle confirm/confirmed
  • Hide label confirmed when filterCheckBox checked
  • Insert textarea
  • Local storage of names with edits and deletes

I had a some problems and I'm pretty sure I didn't implement the best techniques. There's also a lot of repetition.

Can you help with some hints regarding optimization and best approaches to the solutions implemented?

Thank you so much!

Codepen: https://codepen.io/pndiogo/pen/bMGZQa

document.addEventListener('DOMContentLoaded', () => {
  const header = document.getElementById('header');
  const form = document.getElementById('registrar');
  const input = form.querySelector('input');

  const mainDiv = document.querySelector('.main');
  const ul = document.getElementById('invitedList');

  const div = document.createElement('div');
  const filterLabel = document.createElement('label');
  const filterCheckBox = document.createElement('input');

  const namesArray = [];

  let newText = '';

  filterLabel.textContent = "Hide those who haven't responded";
  filterCheckBox.type = 'checkbox';
  div.appendChild(filterLabel);
  div.appendChild(filterCheckBox);
  mainDiv.insertBefore(div, ul);

  filterCheckBox.addEventListener('change', (e) => {
    const isChecked = e.target.checked;
    const lis = ul.children;
    if(isChecked) {
      for (let i = 0; i < lis.length; i += 1) {
        let li = lis[i];
        if (li.className === 'responded') {
          li.style.display = '';
          li.querySelector('label').style.display = 'none';  
        } else {
          li.style.display = 'none';                    
        }
      }
    } else {
      for (let i = 0; i < lis.length; i += 1) {
        let li = lis[i];
        li.style.display = '';
        li.querySelector('label').style.display = '';
      }                                 
    }
  });

  function createLI(text) {
    function createElement(elementName, property, value) {
      const element = document.createElement(elementName);  
      element[property] = value; 
      return element;
    }

    function appendToLI(elementName, property, value) {
      const element = createElement(elementName, property, value);     
      li.appendChild(element); 
      return element;
    }

    const li = document.createElement('li');

    appendToLI('span', 'textContent', text);     
    appendToLI('label', 'textContent', 'Confirm')
      .appendChild(createElement('input', 'type', 'checkbox'));
    appendToLI('textarea', 'placeholder', 'Notes');
    appendToLI('button', 'textContent', 'edit');
    appendToLI('button', 'textContent', 'remove');
    return li;
  }

  function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
  }  

  function errorEmpty() {
    const errorEmpty = document.createElement('p');
    errorEmpty.className = 'errorEmpty';
    errorEmpty.textContent = ('You have not entered a name!');
    header.appendChild(errorEmpty);
  }

  function errorDuplicate() {
    const errorDuplicate = document.createElement('p');
    errorDuplicate.className = 'errorDuplicate';
    errorDuplicate.textContent = ('You have already entered this name!');
    header.appendChild(errorDuplicate);
  }

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = capitalizeFirstLetter(input.value.toLowerCase());

    const errorEmpty = document.createElement('p');
    errorEmpty.className = 'errorEmpty';
    errorEmpty.textContent = ('You have not entered a name!');
    const errorCountEmpty = document.getElementsByClassName('errorEmpty').length;

    const errorDuplicate = document.createElement('p');
    errorDuplicate.className = 'errorDuplicate';
    errorDuplicate.textContent = ('You have already entered this name!');
    const errorCountDuplicate = document.getElementsByClassName('errorDuplicate').length;

    if (text === '') {
      header.appendChild(errorEmpty);
      const pEmpty = document.querySelector('.errorEmpty'); //
      const pDuplicate = document.querySelector('.errorDuplicate'); //
        if (errorCountEmpty > 0) {
          pEmpty.remove();
        }
        if (errorCountDuplicate > 0) {          
          pDuplicate.remove();
        }

    } else {
      const isDuplicate = namesArray.indexOf(text);

      if (isDuplicate > -1) {
        header.appendChild(errorDuplicate);
        if (errorCountDuplicate > 0) {
          header.removeChild(errorDuplicate);
        }
        if (errorCountEmpty > 0) {
          const pEmpty = document.querySelector('.errorEmpty'); //
          pEmpty.remove();
        }
      } else {
        namesArray.push(text);
          localStorage.setItem('storage', JSON.stringify(namesArray));
        input.value = '';
        const li = createLI(text);
        ul.appendChild(li);
        const pEmpty = document.querySelector('.errorEmpty'); //
        const pDuplicate = document.querySelector('.errorDuplicate'); //
          if (errorCountEmpty > 0) {
            pEmpty.remove();
          }
          if (errorCountDuplicate > 0) {          
            pDuplicate.remove();
          }
        }
      }  
  });

  window.onload = function() {
    function getItems() {
      const items = localStorage.getItem('storage');
      if (items) {
        const startItems = JSON.parse(items);
        for(let i = 0; i < startItems.length; i++) {
          namesArray.push(startItems[i]);
          const li = createLI(startItems[i]);
          ul.appendChild(li);
        }
      }
    }
    getItems();
  }


  ul.addEventListener('change', (e) => {
    const checkbox = event.target;
    const checked = checkbox.checked;
    const listItem = checkbox.parentNode.parentNode;
    const label = checkbox.parentNode;

    if (checked) {
      listItem.className = 'responded';
      label.childNodes[0].textContent = 'Confirmed';
    } else {
      listItem.className = '';
      label.childNodes[0].textContent = 'Confirm';
    }
  });


  ul.addEventListener('click', (e) => {
    const spanOldText = '';
    if (e.target.tagName === 'BUTTON') {
      const button = e.target;
      const li = button.parentNode;
      const ul = li.parentNode;
      const action = button.textContent;
      const nameActions = {
        remove: () => {
          const span = li.firstElementChild;
          newText = span.textContent;
          const indexnewText = namesArray.indexOf(newText);
          if (indexnewText > -1) {
            namesArray.splice(indexnewText, 1);
          }
          localStorage.setItem('storage', JSON.stringify(namesArray));
          ul.removeChild(li);
        },
        edit: () => {
          const span = li.firstElementChild;
          newText = span.textContent;
          console.log(newText);
          const input = document.createElement('input');
          input.type = 'text';
          input.value = newText;
          li.insertBefore(input, span);
          li.removeChild(span);
          button.textContent = 'save';
        },
        save: () => {
          const input = li.firstElementChild;
          const span = document.createElement('span');
          const text = capitalizeFirstLetter(input.value.toLowerCase());
          const pEmpty = document.querySelector('.errorEmpty');
          const errorCountEmpty = document.getElementsByClassName('errorEmpty').length;
          const pDuplicate = document.querySelector('.errorDuplicate');
          const errorCountDuplicate = document.getElementsByClassName('errorDuplicate').length;
          span.textContent = text;
          if (text === '') {
            errorEmpty();
            if (errorCountEmpty > 0) {
              pEmpty.remove();
            }
            if (errorCountDuplicate > 0) {          
              pDuplicate.remove();
            }
          } else {
            const isDuplicate = namesArray.indexOf(text);
            if (isDuplicate > -1) {
              errorDuplicate();
              if (errorCountEmpty > 0) {
                pEmpty.remove();
              }
              if (errorCountDuplicate > 0) {          
                pDuplicate.remove();
              }
            } else {
              namesArray.push(text);
              console.log(newText);
              const indexnewText = namesArray.indexOf(newText);
              console.log(indexnewText);

              if (indexnewText > -1) {
                namesArray.splice(indexnewText, 1);
              }

              localStorage.setItem('storage', JSON.stringify(namesArray));
              li.insertBefore(span, input);
              li.removeChild(input);
              button.textContent = 'edit';
              if (errorCountEmpty > 0) {
                pEmpty.remove();
              }
              if (errorCountDuplicate > 0) {          
                pDuplicate.remove();
              }
            }
          }        
        }
      };

      // select and run action in button's name
      nameActions[action]();
    }
  });  
});
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>RSVP App</title>
  <link href="https://fonts.googleapis.com/css?family=Courgette" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Lato:400,700" rel="stylesheet">
  <link href="css/style.css" rel="stylesheet">
</head>
<body>
  <div class="wrapper">
    <header id="header">
      <h1>RSVP</h1>
      <p>Are you coming?</p>
      <form autocomplete="off" id="registrar">
        <input type="text" name="name" placeholder="Invite Someone">
        <button type="submit" name="submit" value="submit">Submit</button>
      </form>
    </header>

    <div class="main">  
      <h2>Invitees</h2>
      <ul id="invitedList"></ul>    
    </div>
  </div>
  <script type="text/javascript" src="app.js"></script>
</body>
</html>
/* ================================= 
  Element Styles
==================================== */

* {
  box-sizing: border-box;
}
body {
  font-family: 'Lato', sans-serif;
  color: #4c4c4c;
  background: #f8fdf3;
}
h1,
p,
form button {
  color: white;  
}
h1 {
  font-family: 'Courgette', cursive;
  text-shadow: 0 1px 0 rgba(0,0,0, .4);
  line-height: .65;
  margin-top: .5em;
  margin-bottom: 0;
}
h2 {
  margin-top: 0;
}
p {
  font-size: 1.1em;
  text-shadow: 0 1px 0 rgba(0,0,0, .25);
}
form {
  width: 60%;
  background: white;
  display: inline-block;
  overflow: hidden;
  display: -webkit-flex;
  display: flex;
  border-radius: .2em;
  border: solid 4px white;
  box-shadow: 0 1px 14px rgba(0,0,0, .12);
}
form button {
  padding: 0 1em;
  font-size: 1em;
  background: #7bcbc4;
  border-radius: .2em;
}
ul {
  list-style: none;
  padding: 0;
  margin: 2em 0 1em;
}
ul li {
  padding: 1em;
  background: #fff;
  border-radius: .2em;
  border: solid 1px rgba(88, 183, 205, .2);
  border-bottom: solid 2px rgba(88, 183, 205, .2);
  position: relative;
}
button {
  cursor: pointer;
}
input, 
button {
  border: none;
  outline: none;
}
header {
  text-align: center;
  background: linear-gradient(90deg, #d4eece, #55b3d0, #1e7eb7),
              url('../images/header-bg.jpg') no-repeat;
              background-blend-mode: multiply;    
              background-size: cover;   
}
header input {
  padding: 12px;
  font-size: 1.15em;
  width: 100%;
}
li span, 
li input[type=text] {
  color: #707070;
  font-size: 1.3em;
  margin-bottom: .3em;
}
li input[type=text] {
  padding: .2em;
  width: 95%;
  border: 1px dotted rgba(0,0,0, .2);
}
li label {
  font-size: .9em;
  display: block;
  color: rgba(112, 112, 112, .55);
}
li span, 
li input[type=text]:first-child {
  display: block;
}
li button {
  font-size: .78em;
  margin-top: 1.65em;
  margin-right: .4em;
  border-radius: .3em;
  padding: .4em .6em;
  color: white;
  background: #58b7cd;
}
li button:last-child {
  background: rgba(88, 183, 205, .5);
}
.wrapper {
  width: 100%;
  max-width: 900px;
  margin: 2.5em auto;
  border-radius: .35em;
  background: #fcfcfc;
  overflow: hidden;
  box-shadow: 0 0 26px rgba(0,0,0, .13);
}
div > input:last-child {
  font-size: 1em;
  margin-left: 6px;
}
div > label {
  color: #767676;
}

textarea {
  display: block;
  padding-bottom: 1rem;
  margin-top: 1rem;
  border-color: #58b7cd;
}

/* responded */
.responded {
  transition: 0.4s;
  border-color: rgba(88, 183, 205, .9);
}
.responded label {
  transition: 0.4s;
  color: rgba(88, 183, 205, 1);
}

header p.errorEmpty,
header p.errorDuplicate {
  color: red;
} 

/* ================================= 
  Media Queries
==================================== */

@media (min-width: 0) and (max-width: 768px) {
  header {
    padding: 1.25em;
  }
  h1 {
    font-size: 3.6em;
    margin: .3em 0 0;
  }
  ul li {
    margin-bottom: 1em;
  }
  form {
    width: 95%;
    -webkit-flex-direction: column;
    flex-direction: column;
    margin: auto;
    margin-top: 2.5em;
  }
  form button {
    padding: 12px 0;
    margin-top: .5em;
  }
  form input {
    font-size: 1em;
    text-align: center;
  }
  .wrapper {
    margin: 0;
  }
  .main {
    padding: 2em 1em .75em;
  }
}

@media (min-width: 769px) {
  header {
    height: 280px;
    padding: 2.5em 1em 1em;  
    background-position: 0 -95px; 
  }
  .main > div {
    float: right;
    display: inline-block;
    margin-top: 12px;
    margin-right: 1.25%;
  }
  div > label {
    margin-top: 12px;
  }
  h1 {
    font-size: 5.8em;
  }
  h2 {
    float: left;
    font-size: 1.9em;
    margin-left: 1.25%;
  }
  form {
    margin: 4.15em auto 0;
    z-index: 3000;
    position: relative;
  }
  .wrapper {
    width: 90%;
  }
  .main {
    padding: 3.8em 1.5em .75em;
    position: relative;
    z-index: 10;
  }
  ul {
    display: -webkit-flex;
    display: flex;
    clear: both;

    -webkit-justify-content: space-between;
    justify-content: space-between;
    -webkit-flex-wrap: wrap;    
    flex-wrap: wrap;
    padding-top: 1.25em;
  }
  ul li {
    -webkit-flex-grow: 1;
    flex-grow: 1;
    -webkit-flex-basis: 47.5%;
    flex-basis: 47.5%;
    margin: 0 1.25% 1em;
  }
}
@media (min-width: 880px) { 
  ul li {
    -webkit-flex-basis: 20%;
    flex-basis: 20%;
  }
}