1

I'm coding a To-Do List in Vanilla Javascript and I want to make it create a new input every time someone presses return on the previous one, which for now is working. I also want to append a checkmark to it, so whenever the task has been finished you can click on it, and it will change the background-color of the input next to which the checkmark is. The only problem is I don't know how to assign some kind of value to each checkmark so the eventListener doesn't always get the first ID selected.

Tried assigning a value to each checkmark and put it into an array, but do not know how to actually assign the exact same value of each checkmark into the array.

let counter, checkmark, cross, list, newRow, addInput, addCheckmark, listid, wrapper, current1;

counter = 1;
checkmark = document.getElementById('checkmark');
cross = document.getElementById('cross');
wrapper = document.querySelector('.to-do-wrapper');
current1 = document.getElementById('current1');

let values = [];

// Event Delegation to listen to all target.matches :)
document.addEventListener('keypress', function(e) {
  if (event.target.matches('.input-new-list')) {
    let randomVal = Math.floor(Math.random() * 100000);
    list = document.querySelector('.list');
    newRow = document.createElement("li");
    addInput = document.createElement('input');
    addCheckmark = document.createElement('i');
    addCheckmark.setAttribute('class', 'fas fa-check');
    addInput.setAttribute('id', 'current-' + counter)
    addInput.setAttribute('class', 'input-new-list');
    addInput.setAttribute('type', 'text');
    newRow.setAttribute('class', 'new-list');
    let key = e.keyCode;
    if (key === 13) {
      list.appendChild(newRow);
      newRow.appendChild(addCheckmark);
      addCheckmark.setAttribute('id', 'checkmark');
      /* addCheckmark.setAttribute('value', randomVal);
      values.push(randomVal); */
      newRow.appendChild(addInput);
      document.getElementById('current-' + counter).focus();
      counter++;
      document.querySelector('#default').removeAttribute('placeholder');
    }
  }
});

// Show edit buttons on click edit list
document.addEventListener('click', function(event) {
  list = document.querySelector('.list');
  newRow = document.createElement("li");
  addInput = document.createElement('input');
  addCheckmark = document.createElement('i');
  // Ad a random value to checkmark -> Push into array. If event.target.matches checkmark value => execute if
  if (event.target.matches('#checkmark')) {
    // On click of checkmark, change input background and toggle checkmark color
    if (event.target.classList !== 'active') {
      checkmark.classList.toggle('active');
      if (document.querySelector('.input-new-list')) {
        document.querySelector('.input-new-list').classList.toggle('checked');
      } else if (document.querySelector('current' + counter)) {
        document.querySelector('#current' + counter).classList.toggle('checked')
      }
    }
  }
});

document.addEventListener('mouseover', function() {
  if (event.target.matches('input')) {
    cross.classList.add('active');
  } else if (!event.target.matches('input')) {
    cross.classList.remove('active');
  }
});
<div class="container-fluid to-do-wrapper" id="toDo">
  <ul class="list">
    <li class="new-list">
      <i class="fas fa-check" id="checkmark"></i>
      <input type="text" placeholder="Create a new list" class="input-new-list" id="default" />
      <i class="fas fa-times" id="cross"></i>
    </li>
  </ul>
</div>

Just looking for a way to assign each checkmark and input to its parent li, so doing something on it, wouldn't affect the first selected element but the one being edited.

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
mart9n
  • 11
  • 2
  • when you add the new input, don't just create it alone, add a checkbox next to it then add them both – Rainbow May 01 '19 at 12:57
  • You can accept one answer (if it helps you) by click on big gray check button on its left side. If you wish you can add +10 points to any author of any good answer by click upper gray triangle – Kamil Kiełczewski May 02 '19 at 05:46

2 Answers2

1

You can 'assign value' to checkmark by add/remove class. Don't use same id for many elements. Try to change approach and separate view and data using <template> as follows

let list= [{text:'', done: false}]; // data

function show() {
  toDo.innerHTML = list.map((x,i)=> inject(item.innerHTML,{
    check: x.done ? 'fa-check' : 'fa-uncheck',
    done: x.done ? 'done' : '',
    hide: i ? '' : 'hide',
    text: x.text,
    i,
  })).join('');
}

function inject(str, obj) { return str.replace(/\${(.*?)}/g, (x,g)=> obj[g]) }

function check(i) { list[i].done = !list[i].done; show(); }

function change(inp,i) { list[i].text = inp.value; }

function del(i) { list.splice(i,1); show(); }

function newItem(i) {
  list.splice(i+1,0,{text:'',done: false});
  show();
  this['inp'+(i+1)].focus();
}

show();
ul{ list-style-type: none; }
.fas { cursor: pointer }
.done { background: #dfd }
.input-new-list {margin: 5px}
.fa-check::after { content: '[+]'}
.fa-uncheck::after { content: '[-]'}
.hide { display: none }
Click on left '[-]' to mark task as done, type enter to add new, click 'x' to delete

<div class="container-fluid to-do-wrapper">
  <ul class="list" id="toDo"></ul>
</div>

<template id="item">
  <li class="new-list">
    <i class="fas ${check}" 
       onclick="check(${i})"></i>
    <input type="text" placeholder="Create a new list"
           class="input-new-list ${done}" 
           value="${text}" id="inp${i}"
           onchange="newItem(${i})" 
           oninput="change(this, ${i})"/>
    <i class="fas fa-times ${hide}" onclick="del(${i})">x</i>
  </li>
</template>
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
1

I kind of went a different direction, storing the data as information in the DOM, and providing a function to generate a JS object that represents todo list data on demand. I used Kamil's CSS in my answer, with some slight changes.

const list = document.querySelector('ul.list');

document.addEventListener('keypress', function(e) {
  const eventTarget = e.target;

  if (e.keyCode === 13 && eventTarget.parentElement && eventTarget.parentElement.classList.contains('new-item')) {
    const clonedListItem = eventTarget.parentElement.cloneNode(true);

    clonedListItem.classList.remove('new-item');

    const icons = clonedListItem.querySelectorAll('i');

    icons.forEach(el => el.classList.remove('hidden'));

    const [doneIcon, deleteIcon] = icons;

    doneIcon.addEventListener('click', () => toggleDone(clonedListItem));
    deleteIcon.addEventListener('click', () => deleteItem(clonedListItem));

    list.insertBefore(clonedListItem, eventTarget.parentElement);

    eventTarget.removeAttribute('placeholder');
    eventTarget.value = '';
  }
});

document.getElementById('generateJSON').addEventListener('click', () => {
  const data = [...document.querySelectorAll('ul.list li')]
    .filter(li => !li.classList.contains('new-item'))
    .map(li => ({
      text: li.querySelector('input[type="text"]').value,
      done: li.classList.contains('done')
    }));

  console.log('data', data);
});

function toggleDone(item) {
  item.classList.toggle('done');
}

function deleteItem(item) {
  item.remove();
}
ul {
  list-style-type: none;
}

.fas {
  cursor: pointer
}

li.done input[type="text"] {
  background: #dfd
}

.input-new-list {
  margin: 5px
}

li i.fa-check::after {
  content: '[+]'
}

li.done i.fa-check::after {
  content: '[-]'
}

.hidden {
  display: none;
}
<ul class="list">
  <li class="new-item">
    <i class="fas fa-check hidden"></i>
    <input type="text" placeholder="Create a new list" />
    <i class="fas hidden" id="cross">x</i>
  </li>
</ul>

<input type="button" id="generateJSON" value="Generate JSON" />
Ashley Grant
  • 10,879
  • 24
  • 36