Search code examples
javascripthtmlobjectstring-literals

How to pass an object to another function with an onclick in a string literal


I am kind of stumped on this one. I would like to pass an object to another function when I click on a button. That way I can work with the object in the final function.

In this example, I would like to pass the car object from the renderCar function to the makeEditable function when I click on the Edit button.

What I tried:
<button onclick="makeEditable(${car})">Edit</button>
<button onclick="makeEditable(car)">Edit</button>
<button onclick="makeEditable('${car}')">Edit</button>

I can pass in a string like
<button onclick="makeEditable('${car.type}')">Edit</button>
but no success with passing the whole object.

Here I wrote up an example of what I am looking to do.

Fiddle: https://jsfiddle.net/kyleteeter/v8gs05e1/10/

HTML

<div id="root" ></div>

JS

let cars = [
  {
    "color": "purple",
    "type": "minivan",
    "capacity": 7
  },
  {
    "color": "red",
    "type": "station wagon",
    "capacity": 5
  }
]

cars.map(car => {
    this.root.innerHTML += this.renderCar(car)
    });
    

function renderCar(car) {
    return `<div><div data-type="color">${car.color}</div><div data-type="type">${car.type}</div><div data-type="capacity">${car.capacity}</div><button onclick="makeEditable('${car}')">Edit</button></div>`
}

function makeEditable(car){
  console.log(car)
} 

Solution

  • The problem becomes pretty evident if you look at the generated HTML. In it, you'll have '[object Object]' just because that's what happens if you try to convert an object into a string, and it's kind of obvious that from this point you can't call makeEditable() and expect the object to magically appear back - you end up calling the function with '[object Object]' as an argument and, well, this isn't the object you're looking for.

    I'm seeing two possible approaches:

    1. Try to shove the entire object into the HTML like so:
    function renderCar(car) {
      return `<div><div data-type="color">${car.color}</div><div data-type="type">${car.type}</div><div data-type="capacity">${car.capacity}</div><button onclick="makeEditable('${encodeURI(JSON.stringify(car))}')">Edit</button></div>`;
    }
    

    ... then, to properly retrieve it, you'd have to reverse the process in the makeEditable() function:

    function makeEditable(data) {
      let car = JSON.parse(decodeURI(data));
      console.log(car);
    }
    

    ... but that's kind of ugly and might not always work.

    Edit: the approach above using btoa() and atob() (instead of encodeURI(JSON.stringify() and then JSON.parse(decodeURI(data)) respectively) might be better if going this route, I just hadn't thought of that at the time of writing.

    1. Store some unique IDs with the data and only pass that into the HTML, then use it to do a lookup on the array:
    let cars = [
      {
        color: "purple",
        type: "minivan",
        capacity: 7,
        id: "2a93d4e1-9ee9-4490-b4a8-da836cf81d7f",
      },
      {
        color: "red",
        type: "station wagon",
        capacity: 5,
        id: "aa207e90-08d1-434f-afeb-aed1dbd1dab9",
      }
    ];
    
    cars.map((car) => {
      this.root.innerHTML += this.renderCar(car);
    });
    
    function renderCar(car) {
      return `<div><div data-type="color">${car.color}</div><div data-type="type">${car.type}</div><div data-type="capacity">${car.capacity}</div><button onclick="makeEditable('${car.id}')">Edit</button></div>`;
    }
    
    function findById(id, array) {
      return array.find((e) => e.id === id);
    }
    
    function makeEditable(id) {
      let car = findById(id, cars);
      console.log(car);
    }