2

I'm trying to initialize a remove button in order to remove an element from the DOM and its related data-item from its locally stored list. Below is the code which is supposed to display the properties of each data-item.

let deltioItems = localStorage.getItem('assoiChoices');
deltioItems = JSON.parse(deltioItems);

let deltioContainer = document.querySelector('.bet_slip_matches');
deltioContainer.innerHTML = '';
  
Object.values(deltioItems).map(item => {
    deltioContainer.innerHTML += `
        <div class="betslip_top_container">
            <div style="padding-left:5px;">
                <i class="close_but far fa-times-circle"></i>
            </div>
            <div class="b_s_matches">
                <p class="paroli_items">${item.teams}</p>
                <p class="paroli_items">${item.name}</p>
                <p class="paroli_items">${item.location} </p>
            </div>
        </div>
    `;
});

In addition to the above code I want to add an event listener to the .close_but classified elements that calls e.g a removeFromLocalStorage function.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Bakaru
  • 21
  • 2
  • well you could use inline-event-attributes, or you initialize the handler after dom-insert – john Smith Oct 01 '21 at 10:10
  • 3
    `.map` is the wrong method to use, because you are not interested in what it returns (it returns nothing). You want to use `.forEach`. – Jeremy Thille Oct 01 '21 at 10:15
  • `I want to add an event listener to close_but` --> then why don't you do just that? :) – Jeremy Thille Oct 01 '21 at 10:16
  • @PeterSeliger yes so? What's your point? I don't get it – Jeremy Thille Oct 01 '21 at 10:17
  • I want to display my localstorage and then i want to add a remove button, thats why i am using map. I updated my code above so you can see the whole function – Bakaru Oct 01 '21 at 10:20
  • No, that is not why you used `map` :) there is absolutely no relation between using `map` (instead of `forEach`) and adding an event listener or accessing the localStorage. `map` is used to transform every item in an array and expects a return value. It has nothing to do with anything else. – Jeremy Thille Oct 01 '21 at 10:26
  • I maded it with a `foreach` and i added `
    ` but i get `Uncaught SyntaxError: Unexpected identifier` on console
    – Bakaru Oct 01 '21 at 10:28
  • Yep, sounds good to me, did it work as intended? – Jeremy Thille Oct 01 '21 at 10:28
  • on console i see `removeItem([object Object]);` @JeremyThille – Bakaru Oct 01 '21 at 10:29
  • Ah yes of course, because everything is stringified. Try with an ID, like `onclick="removeItem(${item.id});"` – Jeremy Thille Oct 01 '21 at 10:35
  • with this i get undefined on console..... @JeremyThille – Bakaru Oct 01 '21 at 10:42
  • I dont know if it helps i didn a `let finalDeltio = Object.values(deltioItems); finalDeltio.forEach(item => {console.log(typeof item);` and it says it is an object – Bakaru Oct 01 '21 at 10:45
  • @Bakaru ... please make use of [document.createElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement), [appendChild](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) and [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) instead of the `innerHTML` approach. Working with element nodes makes things easier even though string concatenation of HTML markup at first sight looks more tempting/promising. – Peter Seliger Oct 01 '21 at 11:02
  • 1
    Use event delegation: `$(document).on("click", ".close_but", function() { removeItem($(this).data("id")) })` and add `data-id=` to your markup as you build it. – freedomn-m Oct 01 '21 at 11:41
  • Should be the answer ^^^ – epascarello Oct 01 '21 at 12:42
  • @Bakaru ... From all the provided comments/solutions/approaches are there any questions left? – Peter Seliger Oct 28 '21 at 11:15
  • @Bakaru ... At SO it is considered to be a nice gesture from the one who got help, to provide some feedback and/or vote on answers and/or accept the answer which was the most helpful in solving the OP's problem. – Peter Seliger Nov 11 '21 at 08:36

1 Answers1

0

Any implementation should break down the requirements into dedicated tasks (hence functions) e.g. for ...

  • retrieving the items from local storage,
  • rendering all storage items,
  • creating an item's element node representation,
  • the event subscription for an item's removal,
  • an item's remove handling for both removing the element node and the storage update of the original item list.

The next provided approach adds for each created item-specific element-node an individual event-listener in a way that all item-related data like the item itself, the item-list and the element-node are bound to a function; thus each deltio item gets created its own item-specific handler.

function handleDeltioItemRemoveWithBoundData(/* evt */) {
  const { node, item, itemList } = this;
  const removeAtIndex = itemList.findIndex(({ teams, name, location }) =>
    ((item.teams === teams) && (item.name === name) && (item.location === location))
  );
  itemList.splice(removeAtIndex, 1);

  node.remove();

  // localStorage.setItem('assoiChoices', JSON.stringify(itemList));

  console.log('remove deltio item :: item ...', item);
  console.log('remove deltio item :: itemList ...', itemList);
}

function subscribeToDeltioItemRemove(node, item, itemList) {
  node
    .querySelector('.close_but')
    .addEventListener('click', handleDeltioItemRemoveWithBoundData.bind({
      node,
      item,
      itemList,
    }));
}

function createDeltioItem(item) {
  const renderBox = document.createElement('div');
  renderBox.innerHTML = `
    <div class="betslip_top_container">
      <div style="padding-left:5px;">
          <i class="close_but far fa-times-circle"></i>
      </div>
      <div class="b_s_matches">
          <p class="paroli_items">${ item.teams }</p>
          <p class="paroli_items">${ item.name }</p>
          <p class="paroli_items">${ item.location } </p>
      </div>
    </div>`;
  return renderBox.firstElementChild;  
}

function renderDeltioItems(rootNode, itemList) {
  itemList.forEach(item => {

    const elmNode = createDeltioItem(item);

    subscribeToDeltioItemRemove(elmNode, item, itemList);

    rootNode.appendChild(elmNode);
  });
}

function main() {
  const deltioItems = [
    { teams: 'The Foos', name: 'Foo Fighter', location: 'foo' },
    { teams: 'The Bars', name: 'Bar Keeper', location: 'bar' },
    { teams: 'The Bazzos', name: 'Bazooka', location: 'baz' },
  ];
  // let deltioItems = localStorage.getItem('assoiChoices');
  // deltioItems = JSON.parse(deltioItems);

  console.log('main() :: deltioItems ...', deltioItems);

  renderDeltioItems(
    document.querySelector('.bet_slip_matches'),
    deltioItems
  );
}

main();
body { margin: 0; font-size: 86%; }
p { margin: 0; padding: 0; }
.betslip_top_container { position: relative; width: 30%; }
.betslip_top_container > [style] { position: absolute; top: 0; right: 0; }
.close_but { cursor: pointer; }
.b_s_matches > p { display: inline-block; }
.b_s_matches > p:not(:last-child)::after { content: ', '; }
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" rel="stylesheet"/>

<div class="bet_slip_matches"></div>

An event delegation based approach can subscribe the event listener at the single root node of all rendered deltio-items but needs to establish the relationship in between data-item and element-node by e.g. a unique ID.

function handleDeltioItemRemoveWithBoundItemList(evt) {
  // make sure the event was triggered via the "close button".
  // if (evt.target?.classList?.contains('close_but')) {
  if (evt.target?.matches('.close_but')) {

    const deltioNode = evt.target.closest('[data-uid^="deltio_item"]');
    // make sure the data-item related elemen-node exists.
    if (deltioNode) {

      const { uid } = deltioNode.dataset;
      const itemList = this;

      // confirm/ensure data-item + element-node relationship via unique ID.
      const removeAtIndex = itemList.findIndex(item => item.uid === uid);
      itemList.splice(removeAtIndex, 1);

      deltioNode.remove();

      // localStorage.setItem('assoiChoices', JSON.stringify(itemList));

      console.log('remove deltio item :: itemList ...', itemList);
    }
  }
}

function subscribeToDeltioItemRemove(rootNode, itemList) {
  rootNode
    .addEventListener(
      'click',
      handleDeltioItemRemoveWithBoundItemList.bind(itemList)
    );
}

function createDeltioItem(item) {
  const renderBox = document.createElement('div');
  renderBox.innerHTML = `
    <div class="betslip_top_container">
      <div style="padding-left:5px;">
          <i class="close_but far fa-times-circle"></i>
      </div>
      <div class="b_s_matches">
          <p class="paroli_items">${ item.teams }</p>
          <p class="paroli_items">${ item.name }</p>
          <p class="paroli_items">${ item.location } </p>
      </div>
    </div>`;

  const elmNode = renderBox.firstElementChild;
  // assign the unique ID via the dataset-API.
  elmNode.dataset.uid = item.uid;

  return elmNode;
}

function renderDeltioItems(rootNode, itemList) {
  itemList.forEach(item =>
    rootNode.appendChild(
      createDeltioItem(item)
    )
  );
}

function main() {
  // let deltioItems = localStorage.getItem('assoiChoices');
  // deltioItems = JSON.parse(deltioItems);
  const deltioItems = [
    { teams: 'The Foos', name: 'Foo Fighter', location: 'foo' },
    { teams: 'The Bars', name: 'Bar Keeper', location: 'bar' },
    { teams: 'The Bazzos', name: 'Bazooka', location: 'baz' },
  ];
  const itemList = deltioItems.map((item, idx) =>
    // create a new data-item with an enriched unique ID.
    Object.assign({}, item, { uid: `deltio_item_${ idx }` })
  );
  const rootNode = document.querySelector('.bet_slip_matches');

  subscribeToDeltioItemRemove(rootNode, itemList);
  renderDeltioItems(rootNode, itemList);

  console.log('main() :: deltioItems ...', deltioItems);
  console.log('main() :: itemList ...', itemList);
}

main();
body { margin: 0; font-size: 86%; }
p { margin: 0; padding: 0; }
.betslip_top_container { position: relative; width: 30%; }
.betslip_top_container > [style] { position: absolute; top: 0; right: 0; }
.close_but { cursor: pointer; }
.b_s_matches > p { display: inline-block; }
.b_s_matches > p:not(:last-child)::after { content: ', '; }
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" rel="stylesheet"/>

<div class="bet_slip_matches"></div>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37