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 DOM Scripting By Example Improving the Application Code Next Steps

Jacek Skrzypek
Jacek Skrzypek
5,040 Points

My sollution with select dropdown and local storage

looking forward for any advice on how to make it better!

document.addEventListener('DOMContentLoaded', () => {
  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 options = ["no response", "confirmed", "declined"]

  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++) {
        let li = lis[i];
        if (li.className === 'responded') {
          li.style.display = '';
        } else {
          li.style.display = 'none';
        }
      }
    } else { 
      for (let i = 0; i < lis.length; i++) {
        let li = lis[i];
        li.style.display = '';
      }
    }
  });

  function createLI(text, response) { //added response argument for management of the dropdown selection
    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;
    }

    function appendOptions(select) {
      for (let i = 0; i < options.length; i++) {
        let option = document.createElement('option');
        option.value = options[i];
        option.text = options[i];
        select.appendChild(option);
      }
      select.value = response; //sets response
    }

    const li = document.createElement('li');
    appendToLI('span', 'textContent', text);
    const select = appendToLI('select', 'id', 'select'); //added dropdown selection
    appendOptions(select);
    appendToLI('textarea', 'placeholder', 'enter your notes here');
    appendToLI('button', 'textContent', 'edit');
    appendToLI('button', 'textContent', 'remove');
    if (supportsLocalStorage()) {
      localStorage.setItem(text, response);     //saves text (name) and response to localStorage 
    }
    return li;
  }

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = input.value;
    function checkForDuplicates(text) { //Checks for duplicates
      const names = Array.from(ul.querySelectorAll('span'));
        if (names.find(name => name.textContent == text)) { 
          return true;
        } else { 
          return false;
        }
      }
    if (!text) {
      input.placeholder = "Enter invitees' name here"; //checks if the string is empty
    } else if (checkForDuplicates(text)) {
      input.value = '';
      alert(`You've already invited ${text}!`);
    } else {
      input.value = ''
      const li = createLI(text, 'no response'); // sets select.value to "no response" for new invitees
      ul.appendChild(li);
    } 
  });

  ul.addEventListener('change', (e) => {
    let select = event.target;
    let listItem  = select.parentNode;
    if (select.value === "no response") {
      listItem.className = '';
    } else {
      listItem.className = 'responded';
    }
    if (supportsLocalStorage()) {
      localStorage.setItem(listItem.firstElementChild.textContent, select.value); //updates select.value to localStorage 
    }
  });

  ul.addEventListener('click', (e) => {
    if (e.target.tagName === 'BUTTON') {
      const button = e.target;
      const li = button.parentNode;
      const ul = li.parentNode;
      const action = button.textContent;
      const select = li.querySelector('select');
      const nameActions = {
        remove: () => {
          ul.removeChild(li);
          if (supportsLocalStorage()) {
            localStorage.removeItem(li.firstElementChild.textContent); //removes from localStorage - make DRY? 
          }        
        },
        edit: () => {
          if (supportsLocalStorage()) {
            localStorage.removeItem(li.firstElementChild.textContent); //removes from localStorage - make DRY?
          }  
          const text = document.createElement('input')
          const span = li.firstElementChild;
          text.type = 'text';
          text.value = span.textContent;
          li.insertBefore(text, span);
          li.removeChild(span);
          button.textContent = 'save'
        },
        save: () => {
          const input = li.firstElementChild;
          const span = document.createElement('span');
          span.textContent = input.value;
          li.insertBefore(span, input);
          li.removeChild(input);
          button.textContent = 'edit';
          if (supportsLocalStorage()) {
            localStorage.setItem(span.textContent, select.value); //saves text (name) to localStorage with previous select.value
          } 
        }
      }
      // select & run action in button's name
      nameActions[action]();
    }
  });

  function supportsLocalStorage() {
    try {
    return 'localStorage' in window && window['localStorage'] !== null;
    } catch(e) {
      return false;
    }
  }


  window.onload = function() { //onload check supportsLocalStorage, load names and responses pairs, build lis with createLi() and ul.appendChild
    if (supportsLocalStorage()) { 
      const keys = Object.keys(localStorage);
      if (keys.length > -1) {
        for (let i = 0; i < keys.length; i++) {
          const li = createLI(keys[i], localStorage.getItem(keys[i])); 
          ul.appendChild(li);
        }
      }
    }  
  }

});

1 Answer

The only comments I have so far:

1) The location of your buttons, textarea and options changes based on the number of items in a row and looks especially odd with 3 items.

2) Checking 'Hide those who haven't responded" doesn't hide newly created items until you uncheck and check again.

Jacek Skrzypek
Jacek Skrzypek
5,040 Points

Hi Kris, thanks for your review!

ad 1. - I haven't worked on the looks yet, but I plan to:)

ad 2. should be better now:

document.addEventListener('DOMContentLoaded', () => {
  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 options = ["no response", "confirmed", "declined"]
  const setLiClassName = (li, select) => {
    if (select.value === "no response") { 
      li.className = '';
    } else {
      li.className = 'responded';
    }
  }
  const createLI = (text, response) => { //added response argument for management of the dropdown selection
    const li = document.createElement('li');
    const createElement = (elementName, property, value) => {
      const element = document.createElement(elementName);
      element[property] = value;
      return element;
    }
    const appendToLI = (elementName, property, value) => {
      const element = createElement(elementName, property, value);
      li.appendChild(element);
      return element;
    }
    const appendOptions = (select) => {
      for (let i = 0; i < options.length; i++) {
        let option = document.createElement('option');
        option.value = options[i];
        option.text = options[i];
        select.appendChild(option);
      }
      select.value = response; //sets response
      setLiClassName(li, select);
    }
    appendToLI('span', 'textContent', text);
    const select = appendToLI('select', 'id', 'select'); //added dropdown selection
    appendOptions(select);
    appendToLI('textarea', 'placeholder', 'enter your notes here');
    appendToLI('button', 'textContent', 'edit');
    appendToLI('button', 'textContent', 'remove');
    if (supportsLocalStorage()) {
      localStorage.setItem(text, response);     //saves text (name) and response to localStorage 
    }
    return li;
  }

  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++) {
        let li = lis[i];
        if (li.className === 'responded') {
          li.style.display = '';
        } else {
          li.style.display = 'none'; //hides lis if not answered
        }
      }
    } else { 
      for (let i = 0; i < lis.length; i++) {
        let li = lis[i];
        li.style.display = '';
      }
    }
  });

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = input.value;
    const checkForDuplicates = (text) => { //Checks for duplicates
      const names = Array.from(ul.querySelectorAll('span'));
        if (names.find(name => name.textContent == text)) { 
          return true;
        } else { 
          return false;
        }
      }
    if (!text) {
      input.placeholder = "Enter invitees' name here"; //checks if the string is empty
    } else if (checkForDuplicates(text)) {
      input.value = '';
      alert(`You've already invited ${text}!`);
    } else {
      input.value = ''
      const li = createLI(text, 'no response'); // sets select.value to "no response" for new invitees
      ul.appendChild(li);
      if (filterCheckbox.checked) {
        li.style.display = 'none'; //hides new lis if 'hide those who haven't responded' is checked
      }
    } 
  });

  ul.addEventListener('change', (e) => {
    let select = event.target;
    let li  = select.parentNode;
    setLiClassName(li, select);
    if (supportsLocalStorage()) {
      localStorage.setItem(li.firstElementChild.textContent, select.value); //updates select.value to localStorage 
    }
  });

  ul.addEventListener('click', (e) => {
    if (e.target.tagName === 'BUTTON') {
      const button = e.target;
      const li = button.parentNode;
      const ul = li.parentNode;
      const action = button.textContent;
      const select = li.querySelector('select');
      const nameActions = {
        remove: () => {
          ul.removeChild(li);
          if (supportsLocalStorage()) {
            localStorage.removeItem(li.firstElementChild.textContent); //removes from localStorage - make DRY? 
          }        
        },
        edit: () => {
          if (supportsLocalStorage()) {
            localStorage.removeItem(li.firstElementChild.textContent); //removes from localStorage - make DRY?
          }  
          const text = document.createElement('input')
          const span = li.firstElementChild;
          text.type = 'text';
          text.value = span.textContent;
          li.insertBefore(text, span);
          li.removeChild(span);
          button.textContent = 'save'
        },
        save: () => {
          const input = li.firstElementChild;
          const span = document.createElement('span');
          span.textContent = input.value;
          li.insertBefore(span, input);
          li.removeChild(input);
          button.textContent = 'edit';
          if (supportsLocalStorage()) {
            localStorage.setItem(span.textContent, select.value); //saves text (name) to localStorage with previous select.value
          } 
        }
      }
      // select & run action in button's name
      nameActions[action]();
    }
  });

  const supportsLocalStorage = () => {
    try {
    return 'localStorage' in window && window['localStorage'] !== null;
    } catch(e) {
      return false;
    }
  }


  window.onload = () => { //onload check supportsLocalStorage, load names and responses pairs, build lis with createLi() and ul.appendChild
    if (supportsLocalStorage()) { 
      const keys = Object.keys(localStorage);
      if (keys.length > -1) {
        for (let i = 0; i < keys.length; i++) {
          const li = createLI(keys[i], localStorage.getItem(keys[i])); 
          ul.appendChild(li);
        }
      }
    }  
  }

});