0

I wanted to create a list of settings that a user can change in the HTML procedurally through javascript.

Much like this: Quality: - 0 +

My approach to this was making an Option class with a value property and prev() and next() methods that change the value within its range. I'm extending this class so it can be a Range, Bool, etc. This is working fine.

My issue is that I can't seem to be able to incorporate this into the HTML. My current solution works only for the last option created, the others don't trigger the onclick event functions, and even if they did I don't feel like this is the right approach to it. How can I make this work in an elegant way?

I have tried the solution shown in this question but it prevents me from accessing the class instance with this.

class UIManager {
    constructor (wrapperID, settings) {
        this.wrapper = document.getElementById(wrapperID)
        this.settings = settings
    }
    updateUI () {
        this.wrapper.innerHTML = ``
        for (let id = 0; id < this.settings.options.length; ++id) {
            let option = this.settings.options[id]

            this.wrapper.innerHTML += `
            <li>
                <div class="label">
                    ${option.name}
                </div>
                <div class="option">
                    <input id="prev${id}" class="open" type="button" value="<">
                    ${option.value}
                    <input id="next${id}" class="close" type="button" value=">">
                </div>
            </li>
            `
            let prevButton = document.getElementById(`prev${id}`)
            let nextButton = document.getElementById(`next${id}`)

            prevButton.onclick = _ => {
                this.settings.options[id].prev()
                this.updateUI()
            }
            nextButton.onclick = _ => {
                this.settings.options[id].next()
                this.updateUI()
            }
        }
    }
}
Tiago Marinho
  • 2,146
  • 17
  • 37
  • You could utilize the method that you linked to at the beginning and still reference the class instance `this` by using the [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind) method. – Tim Klein May 20 '19 at 17:42
  • Also, I would suggest using `()` as the empty argument signature for arrow functions. It is more commonly seen in code and easier to extend to include arguments without modifications. – Tim Klein May 20 '19 at 17:44
  • 1
    @TimKlein after some fiddling I figured out your method did work nicely, but since the innerHTML of the wrapper was being overwritten for each iteration only the last events were retained. I fixed it by adding the DOM elements using `document.createElement()` instead. Feel free to post your comment as an answer so I can accept it. – Tiago Marinho May 20 '19 at 18:49
  • Cheers! Will do. – Tim Klein May 20 '19 at 18:56

2 Answers2

2

The answer to the question you linked as a potential solution is usable so long as you bind the class instance member this to the anonymous function using Function.protoype.bind.

The code would look something like this (using the previously linked answer as the starting point):

for ( var i = 0; i < itemLists.length; i++ ) (function(i){ 
  itemLists[i].onclick = function() {
      // do something using `this`
  }
}).bind(this)(i);

You mentioned in the comments that this didn't work, but that it was related to overwriting innerHTML and not due to the binding.

Hope this gives a small part of the larger picture.

Tim Klein
  • 2,538
  • 15
  • 19
0

Try closure to store the values required for functions created from inside the loop.

Something like this :

prevButton.onclick = (function(settings, updateUI) {
    return function() {
        settings.options[id].prev();
        updateUI();
    };
})(this.settings, this.updateUI); 

nextButton.onclick = (function(settings, updateUI) {
    return function() {
        settings.options[id].next();
        updateUI();
    }
})(this.settings, this.updateUI);
Nils
  • 806
  • 1
  • 9
  • 24
  • This approach results in the `updateUI();` call from within the closure having the wrong `this` value, so it fails when evaluating `this.wrapper.innerHTML = ''`. How can I solve for this? – Tiago Marinho May 20 '19 at 18:04