2

I'm trying to create a basic inventory in JavaScript. I would like to add items to an array which would be the player inventory. What's inside the inventory array would be displayed on multiple divs (one per item). I've already starting experiencing and getting the basics to work, but I wanted to elaborate a bit and create buttons per each item in the item list so as to later use the buttons to add the items to the inventory.

I started by selecting my HTML divs and creating a basic itemList array and a playerInv empty array:

const container = document.querySelector('.container');
const inventory = document.querySelector('.inventory');
const invItem = document.querySelectorAll('.grid-item');

const itemList = [{
    id: 1,
    name: "Coke Bottle",
    quality: "usable"
  },
  {
    id: 6,
    name: "Pair of Jeans",
    quality: "pants"
  }
]

// Player inventory, is by default empty.
const playerInv = []

const itemBtnContainer = document.createElement("div");
itemBtnContainer.classList.add("item-add-btn-ctn");
container.appendChild(itemBtnContainer);

let itemBtn;

function addItemBtn() {
  itemList.map((item) => {
    itemBtn = document.createElement("button");
    itemBtn.classList.add("item-add-btn");
    itemBtnContainer.appendChild(itemBtn);
    itemBtn.innerText += item.name;
  })
}
addItemBtn();
<div class="container">
  CONT
  <div class="inventory">
    <div class="inventory-left">
      <div class="inventory-left-title">Your inventory</div>
      <div class="inventory-left-grid">
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
      </div>
    </div>
    <div class="inventory-right"></div>
  </div>
</div>

So, in order to create my buttons my idea was to loop through my itemList array with map. Each itemList item would create a button with a class of "item-add-btn" and inside the button would be displayed the name of the item.

Visually, it works. I have 6 buttons because of the 6 itemList items, but because I created the buttons in my loop I can't access them outside of the loop. Indeed, if I console.log(itemBtn) it returns 'undefined', so I can't create an onClick function with addEventListener in order to later write my addItemToInv() function. I understand why it doesn't work, but I really don't know what else I can do to get to the same results AND be able to use my buttons.

I don't even know what else I can try since it's a very specific need.

j08691
  • 204,283
  • 31
  • 260
  • 272
axl.dev
  • 23
  • 3
  • Why do you need to access the buttons outside the loop? – epascarello Jan 11 '22 at 15:18
  • If you're not using the return value of `.map()` then `.map()` is the wrong tool. – Andreas Jan 11 '22 at 15:19
  • 1
    `map` is not purposed to just iterate an array, use `forEach` instead. For the actual issue, take a look at [this post](https://stackoverflow.com/q/1687296/1169519). – Teemu Jan 11 '22 at 15:20
  • I don't know why everyone uses `map` instead of `forEach`, and at this point I'm too afraid to ask – Jeremy Thille Jan 11 '22 at 15:21
  • in your loop (preferably forEach one) you can add your listener : (...) itemBtn.addEventListener("click", myFunctionOutOfTheLoop) ; – TomVerdier Jan 11 '22 at 15:21
  • Should I use .forEach() then? I first tried with a basic for loop but didn't get what I wanted. I need to access the buttons outside of the loop so as to use addEventListener on them. – axl.dev Jan 11 '22 at 15:22
  • I don't understand what you mean by "Accessing the buttons outside the loop". The loop creates the buttons in the DOM. They appear, then you can click them. But "outside the loop"? I don't get it – Jeremy Thille Jan 11 '22 at 15:23
  • @JeremyThille OP states "_so I can't create an onClick function with addEventListener_". Maybe `map` used just because it's shorter to write than `forEach` ..? – Teemu Jan 11 '22 at 15:24
  • Thanks for your help everyone, from what you've told me I didn't understand what .map() was for. I use it for React props and thought I could use it for this type of problem. I now know and understand I should use a for of loop or even a forEach. Thank you! – axl.dev Jan 11 '22 at 15:37

4 Answers4

4

First you should be using for .. of or Array.prototype.forEach because .map builds a new array which you are discarding.

Second, make a createButton function that returns the button so you can store it to a value -

function createButton(item) {
  const itemBtn = document.createElement("button")
  itemBtn.type = "button"
  itemBtn.classList.add("item-add-btn")
  itemBtn.innerText += item.name
  return itemBtn
}

Now you can loop -

for (const item of itemList) {
  // create button reference
  const button = createButton(item)

  // add click handler
  button.addEventListener("click", ...)

  // append button to container
  itemBtnContainer.appendChild(button)
}

Here's a full working demo -

const inventory = []

const itemList = [{id:1,name: "Coke Bottle",quality: "usable"},{id: 6,name: "Pair of Jeans",quality: "pants"}]

function renderInventory() {
  document
  .querySelector("#inventory")
  .textContent = `inventory: ${JSON.stringify(inventory, null, 2)}`
}

function createButton(text, onClick) {
  const e = document.createElement("button")
  e.type = "button"
  e.textContent = text
  e.addEventListener("click", onClick)
  return e
}

function addToInv(item) {
  return event => {
    inventory.push(item)
    renderInventory()
  }
}

renderInventory()

for (const item of itemList)
  document.body.appendChild(createButton(item.name, addToInv(item)))
#inventory {
  padding: 1rem;
  background-color: #eee;
  font-family: monospace;
}
<pre id="inventory"></pre>
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    Oh thanks! It's exactly what I needed, from what you and everyone else said it's clear that I completely misunderstood what .map() was for. Thanks a lot for your help, I really appreciate it :) – axl.dev Jan 11 '22 at 15:33
  • you're welcome ^_^ i added a complete working example at the bottom of the answer – Mulan Jan 11 '22 at 15:37
1

In your map, you never return the itemBtn that you create. If you modify your code just a little as in the following, you should get a list of button elements you can use.

function addItemBtn() {
    return itemList.map((item) => {
        const itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText += item.name;
        return itemBtn;
    });
}

const buttons = addItemBtn(); // List of button elements to do with what you will

I do want to mention that once you define your addItemToInv function, you could do something like the following:

function addItemToInv(item) {
  // Logic to add item to inventory here
}

function addItemBtn() {
    return itemList.map((item) => {
        const itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText += item.name;
        itemBtn.addEventListener("click", () => addItemToInv(item));
        return itemBtn;
    });
}
1

Just add your listener in your loop no ?

itemBtn.addEventListener('click', function(event){
  const elem = event.target;
});

Why global itemBtn ?

Mino mnz
  • 181
  • 1
  • 4
0

It is unclear why you would need to access the buttons outside the loop. You are clearly using map, so return the button and you now have an array of buttons.

let itemBtns;

function addItemBtn() {
    itemBtns = itemList.map((item) => {
        itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText = item.name;
        return itemBtn;
    });
}

But you probably just want to know when a button is clicked, so you should just add the event listener to the button when you create it.


function addItemBtn() {
    itemList.forEach((item) => {
        itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText = item.name;
        itemBtn.type = "button";
        itemBtn.addEventListener("click", () => { 
          console.log(item); 
        });
    });
}
epascarello
  • 204,599
  • 20
  • 195
  • 236