1

Some background: I've been working on a Dark Souls II statistical calculator. I have a table with buttons that will allow you to either add or subtract a value to any of the primary stats. This will then update the stats on the page, which will eventually tell you how many souls (experience points) you need to get to that soul level.

Anyway, I want to use pure Javascript to addEventListeners to each of the buttons to achieve this goal. The problem is, I have been adding them with several different calls and I was just curious if there was a more efficient way about doing this. The following code samples will show you my verbose solution, which I'm fairly certain can be trimmed down for brevity. If you can answer my question in pure Javascript it would be regarded higher than with jQuery, but if you come up with a jQuery solution I would still be very appreciative. I think an elegant solution to this problem is quite possible, but I am at a loss.

Here is a code sample with three buttons.

HTML snippet (this is obviously within the body tag):

<tr id="rowVigor">
    <td>Vigor</td>
    <td id="baseVIG">0</td>
    <td class="increased" id="increasedVIG">0</td>
    <td id="currentVIG">0</td>
    <td><input type="button" value="-" class="buttonSubtract" id="minusVIG"/></td>
    <td><input type="button" value="+" class="buttonAdd" id="addVIG"/></td>
</tr>
<tr id="rowEndurance">
    <td>Endurance</td>
    <td id="baseEND">0</td>
    <td class="increased" id="increasedEND">0</td>
    <td id="currentEND">0</td>
    <td><input type="button" value="-" class="buttonSubtract" id="minusEND"/></td>
    <td><input type="button" value="+" class="buttonAdd" id="addEND"/></td>
</tr>
<tr id="rowVitality">
    <td>Vitality</td>
    <td id="baseVIT">0</td>
    <td class="increased" id="increasedVIT">0</td>
    <td id="currentVIT">0</td>
    <td><input type="button" value="-" class="buttonSubtract" id="minusVIT"/></td>
    <td><input type="button" value="+" class="buttonAdd" id="addVIT"/></td>
</tr>

Javascript snippet:

window.onload = function() 
{
    //Global event listeners - Buttons
    //VIGOR
    document.getElementById("addVIG").addEventListener("click", function(){ addOne("increasedVIG"); }, false);
    document.getElementById("minusVIG").addEventListener("click", function(){ minusOne("increasedVIG"); }, false);

    //ENDURANCE
    document.getElementById("addEND").addEventListener("click", function() { addOne("increasedEND"); }, false);
    document.getElementById("minusEND").addEventListener("click", function() { minusOne("increasedEND"); }, false);

    //VITALITY
    document.getElementById("addVIT").addEventListener("click", function() { addOne("increasedVIT"); }, false);
    document.getElementById("minusVIT").addEventListener("click", function() { minusOne("increasedVIT"); }, false);

}

//The functions calls and objects within these two functions are for updating statistical values. 
function addOne(id)
{
        console.log(id);
    id = document.getElementById(id);
    id.innerText = parseInt(id.innerText) + 1;
    player.SoulLevel++;
    updateAll();
}

function minusOne(id)
{
        console.log(id);
    id = document.getElementById(id);
    if (parseInt(id.innerText) != 0)
    {
        id.innerText = parseInt(id.innerText) - 1;
        player.SoulLevel--;
        updateAll();
    }
}

Now, the above code works, but the problem is that there are nine statistics (I only showed three) and it's getting very wordy. Is this the proper way to do something like this? Should I even be concerned?

Here's an idea I had that did not end up working...(Same HTML as above)

Javascript snippet:

//suffix has global scope
var suffix = ["VIG", "END", "VIT"];

window.onload = function()
{
    for (var i = 0; i < suffix.length; i++)
    {
        document.getElementById("add" + suffix[i]).addEventListener("click", function(){    addOne("increased" + suffix[i]);    }, false);
        document.getElementById("minus" + suffix[i]).addEventListener("click", function(){ minusOne("increased" + suffix[i]); }, false);
    }
}

When you click the plus or minus buttons, the console.log(id) returns increasedundefined, so I'm guessing you can't create event listeners this way. Any ideas?

floatingeyecorpse
  • 213
  • 1
  • 2
  • 8
  • The fact that the function is calling the log is showing the event is connected – Brett Weber Apr 30 '14 at 15:48
  • 2
    Sounds like you should use a closure: http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – A. Wolff Apr 30 '14 at 15:48
  • and the reason it is undefined is because the variable i is undefined in the event handler. you would need to declare i globally – Brett Weber Apr 30 '14 at 15:48
  • 1
    @BrettWeber He should use a closure, not a global variable – A. Wolff Apr 30 '14 at 15:49
  • and as a side note, this is not a good way to go. Look here for some tips on creating a controller object for the page.. http://stackoverflow.com/questions/3750082/javascript-oop-best-practices/13074081#13074081 – Brett Weber Apr 30 '14 at 15:49
  • 1
    @A.Wolff - I was just about to get to that, lol – Brett Weber Apr 30 '14 at 15:50
  • @A.Wolff I did not know about the concept of closures, very useful information. I wish there was a definitive list of computer science concepts, but in this field it appears that you simply learn by doing or word of mouth from colleagues. An updated, *conceptual* book that is bereft of syntax is basically what I'm striving for. – floatingeyecorpse Apr 30 '14 at 17:10

2 Answers2

1

Here is one option that you might find useful:

Note the use of the new data-* attribute:

<div id="statsHome">
    <tr id="rowVigor">
        <td>Vigor</td>
        <td id="baseVIG">0</td>
        <td class="increased" id="increasedVIG">0</td>
        <td id="currentVIG">0</td>
        <td><input type="button" value="-" class="buttonSubtract" data-type="increasedVIG"/></td>
        <td><input type="button" value="+" class="buttonAdd" data-type="increasedVIG"/></td>
    </tr>
</div>

In this case, I'm going to loop and add to the buttons rather than using delegation. It was kind of a toss up, though, really.

function addHandler(button, mode) {
  if (mode === "add") {
    button.addEventListener("click", function() { 
      var stat = this.getAttribute("data-type");
      addOne(stat);
    }, false);
  } else { 
    button.addEventListener("click", function() {
      var stat = this.getAttribute("data-type");
      minusOne(stat);
   }, false);
  }

}

var statsHome = docuemt.getElementById("statsHome");
var buttons = statsHome.getElementsByTagName("input");
for (var i = 0; i < buttons.length; i++) {
  var button = buttons[i];
  if (button.className === "buttonSubtract") {
    addHandler(button, "subtract");
  }
  if (button.className === "buttonAdd") {
    addHandler(button, "add");
  }
}
Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
  • This is more of the answer that I was looking for, thank you for the introduction to the data-* attribute and getAttribute method and use of them in a practical situation. I will be using my knowledge of this and the use of closures as a learning exercise in an attempt to mimic both solutions. – floatingeyecorpse Apr 30 '14 at 17:13
  • This will short cut your learning -- should have linked to this earlier: [Data-* discussion](http://html5doctor.com/html5-custom-data-attributes/). This is the only time I use setAttribute vs using element properties. – Jeremy J Starcher Apr 30 '14 at 17:25
0

Nothing is wrong with your code, other than not calling a named event handler function and a scope issue on your index variable within your current event handler

Brett Weber
  • 1,839
  • 18
  • 22