简体   繁体   中英

JavaScript Dynamic Button onClick event (variable scope)

Background

I am dynamically creating quite a few buttons based on a JSON object I get back from an AJAX request that queries a database.

Each button then needs to make subsequent AJAX calls to that same database, depending on information I put into each button's onClick function.

Basically, the url is "api/{foo}/{bar}/{thing}/" , And if I call just "api/" it gives me all possible options for foo . And if I call "api/book/" it'll give me all possible options for bar where foo = "book" , and so on....

The Approach I am trying.

//getJSON is a promise function that returns a JSON object
getJSON("api")
         // volumes is the JSON object that contains an array of objects
         // each object is {book:"someTitle",url:"titleShorthand"}
        .then((volumes) => {
            for (var vol in volumes) {
                //create Button returns a document.crateElement(input) 
                   //with type=button and value = the passed in param.
                var btn = createButton(volumes[vol].book)
                btn.addEventListener('click', function(){
                    getJSON("api/" + volumes[vol].url)
                    .then((books) => {
                        console.log(books)
                    })
                })
                volumeDiv.appendChild(btn)
            }
        })

The Problem

All the button.onClick events are the same, as in the console.log(books) only gives me the last set of books no matter which button I click. volumes[vol].url seems to be getting rewritten with every iteration of the for loop, for every button, not just the newly created button.

That's because you're declaring a function inside of a for loop. The value of volume[vol] is always going to be the last value by the time that the function you've declared inside the loop is run because of how closures work.

To get around this you can use a higher order function .

I had to mock some functions to get this snippet to work but you'll have to write a function like getEventListener in your code to get this working.

 var volumeDiv = document.getElementById('container') function getJSON (url) { return new Promise (resolve => { var vol1 = { book: "vol1", url: "vol1" } var vol2 = { book: "vol2", url: "vol2" } var map = { 'api': { vol1, vol2 }, 'api/vol1': vol1, 'api/vol2': vol2 } resolve(map[url]) }) } function createButton (name) { console.log(`creating button ${name}`) var btn = document.createElement('input') btn.type = 'button' btn.value = name return btn } function getEventListener (url) { return () => { getJSON(url) .then((books) => { console.log(books) }) } } getJSON("api") .then(volumes => { for (var vol in volumes) { var volumeUrl = volumes[vol].url var btn = createButton(volumes[vol].book) btn.addEventListener('click', getEventListener("api/" + volumeUrl)) volumeDiv.appendChild(btn) } })
 <div id="container" />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM