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

Jesse Thompson
Jesse Thompson
10,684 Points

Difficult to add prevent adding same name functionality

So at form.addEventListener('submit'.... I tried to add a functionality where the for loop would go through all the list items and check if the text content was the same as the input value. To ensure that it does not add two of the same names with the same text content. Problem is, the code works the first time but the second time all it says in the console is: cannot read property 'textContent' of undefined.

I have no idea why its doing this. Im nearly positive im targeting the right elements ul.children[i].span.textContent. Thats where I see the location in the console.

document.addEventListener('DOMContentLoaded', () => {
    const form = document.getElementById('registrar');
    const input = form.querySelector('input');
    const ul = document.getElementById('invitedList');
    const mainDiv = ul.parentElement
    const div = document.createElement('div');
    const filterLabel = document.createElement('label');
    const filterCheckbox = document.createElement('input');

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

    filterLabel.textContent = "Hide those who haven't responded";
    filterCheckbox.type = 'checkbox';
    div.appendChild(filterLabel);
    div.appendChild(filterCheckbox);

    mainDiv.insertBefore(div, ul);




    //This code was originally squeezed into one function but was broken apart into several functions to make it more modular and easier to read.

    //Creates li element, label lement and checkbox element. Then it appends the checkbox and label and then returns li when called upon.
    function createLi(text) {
        //Streamlines the create element action into a function that takes 3 arguments.
        function createElement(elementName, property, value) {
            const element = document.createElement(elementName);
            element[property] = value;
            return element;
        }

        function appendTo(element, parent) {
            parent.appendChild(element);
        }
        const li = document.createElement('li');

        // Puts text into span element text content to make it an html element that can be edited easily.
        const span = createElement('span', 'textContent', text);
        appendTo(span, li);
        const label = createElement('label', 'textContent', 'Confirmed'); 
        const checkbox = createElement('input', 'type', 'checkbox');

        appendTo(checkbox, label);
        appendTo(label, li);
        //edit button.
        const editButton = createElement('button', 'textContent', 'edit');
        appendTo(editButton, li);
        const removeButton = createElement('button','textContent', 'remove');
        appendTo(removeButton, li);

        return li;
    }

    // prevents default behaviour of event target. Then turns text into input value user enters and resets input value on screen. Passes text input variable to function createLi to add textContent to new li. Then runs removeButton function with li which adds remove button then it appends li under ul.
    form.addEventListener('submit', (e) => {
        e.preventDefault();
        var sameName = false;
        if (input.value != '') { 
            for (i = 0; i < ul.children.length; i ++) {
                if (input.value == ul.children[i].span.textContent) {
                    sameName === true;
                }
            }
                if (sameName != true) {
                const text = input.value;
                input.value = '';
                const li = createLi(text);
                // pay attention here. This is creating a tree of elements ul > li > label > checkbox. 
                ul.appendChild(li);
                }
           } else {
                alert('Please enter a name');
        }
    });

    ul.addEventListener('change', (e) => {
        // console.log(e.target.checked);
        // Actual checkbox as related to the event.
        const checkbox = event.target;
        // This can also be represented by 'document.getElementsByTagName('input')[1].checked'
        const checked = checkbox.checked;
        const listItem = checkbox.parentNode.parentNode; // = li

        if (checked) {
            listItem.className = 'responded';
            } else {
            listItem.className = ''
            }
    });

    // Creates reference to target button parent node which is li and the parent node of li (which is ul) so that you can refer to the child of ul when using removeChild to remove the entire list item and also when to save the li. You can click several edit buttons and save buttons regardless of order because the first if statement always sets the appropriate constants parent elements in relation to the event target.

    //Below is a perfect example of how to put a complex line of code with contextual if statements into a variable holding several objects. Each object holds the function and you simply use nameActions.whateverfunction() in order to run that function in the same scope. This makes code more simple and more modular.

    //Practice putting your functions into objects to make them easier to reference.
    ul.addEventListener('click', (e) => {
         if (e.target.tagName === 'BUTTON') {
             const button = e.target;
             const li = button.parentNode;
             const ul = li.parentNode;
             const nameActions = {
                 remove: () => {
                 ul.removeChild(li);
                 }, 
                 edit: () => {
                  const span = li.firstElementChild;
                  const input = document.createElement('input');
                  input.type = 'text';
                  input.value = span.textContent;
                  li.insertBefore(input, span);
                  li.removeChild(span);
                  button.textContent = 'save';
                 },
                     save: () => {
                   const input = li.firstElementChild;
                   const span = document.createElement('span');
                   li.insertBefore(span, input);
                   span.textContent = input.value;
                   li.removeChild(input);
                   button.textContent = 'edit';
                 }
            }; 
             //Creates input text element and places it before span. Then removes the span element.

             const action = button.textContent;
             if (action === 'remove') {
                nameActions.remove();
              } else if (action === 'edit') {
                  console.log(event.target);
                  nameActions.edit();
              } else if(action === 'save') {
                  console.log(event.target);
                  nameActions.save();
                   }

             //Guy also replaced the above if statement with nameActions[Action](); With that case, nameActions runs and uses the action variable which will either run one of the objects remove, edit or save which essentialy does the functionality of the if statement above. This only works because the button text content names is the same as the object functions in nameActions.

           }

         });  

});```

4 Answers

Jesse Thompson
Jesse Thompson
10,684 Points

figured it out. I had to put let sameName = true; then return the variable which made it run correctly. Doesnt submit duplicates.

Jesse Thompson
Jesse Thompson
10,684 Points

Hello James,

Thanks for pointing out that syntax error for me. That should be logged in my brain for future for loops.

I added var like so "for (var i = 0; i < ul.children.length; i ++)" and it allows me to post one invitee but when I submit again it says

"app.js:79 Uncaught TypeError: Cannot read property 'textContent' of undefined at HTMLFormElement.form.addEventListener (app.js:79)" line 79 is "if (input.value == ul.children[i].span.textContent)"

Now im in the console I do document.getElementByTagName('ul') it shows the ul with id #invitedList. I do document.getElementsByTagName('ul').children; and it says undefined?

Yet in the elements part of the console it clearly shows <li></li> under the ul.

Why am I not targetting the appropriate elements right?

Jesse Thompson
Jesse Thompson
10,684 Points

Nevermind I wasnt thinking straight. I have to put getElementByTagName('span') after children[i]. Its still allowing me to submit the same name more than once but Ill figure it out now.