# Calculator for the price of a team of ninjas and samurai

Posted on

Problem

``````var addToTeam = document.getElementsByClassName('addToTeam'),
parentElement = document.getElementsByClassName('playerTypeBlock'),
totalVal = document.getElementById('totalvalue'),
tableBody = document.querySelector('.totalPlayers');

var updatePlayers = function (evt) {

evt.preventDefault();

var amount = totalVal.innerHTML,
productId = this.parentElement.getAttribute('data-player-type-id'),
productPrice = this.parentElement.getAttribute('data-player-value'),
playerId = \$(this).parent().data('playerTypeId'),
updateTotal = parseFloat(amount) + parseFloat(productPrice);

var quantity = parseInt(\$(this).data('click'), 10) || 0;
quantity++;
\$(this).data('click', quantity);

totalVal.innerHTML = Math.round(updateTotal * 100) / 100;

if (quantity > 1) {
\$('table .totalPlayers').find('#player-' + playerId + ' .quantity').text(quantity);

}
else {
\$('table .totalPlayers').append('<tr id="player-' + playerId + '"><td>' + playerName + '</td><td class="quantity">' + quantity + ' </td></tr>');
}

};

for (var i = 0; i < addToTeam.length; i++) {
}``````
``````<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div class="playerTypeBlock" data-player-type-id="1" data-player-value="10.00">

</div>
<hr>
<div class="playerTypeBlock" data-player-type-id="2" data-player-value="20.25">

</div>
<hr>
<table class="teams" border="1">
<tr>
<th>Players</th>
<th>Quantity</th>
</tr>
<tbody class="totalPlayers"></tbody>
</table>
<div class="total">
<span>Total: \$</span>
<span class="amount" id="totalvalue">0.00</span>
</div>``````

It looks like any additional functionality I want to add would have to go through `updatePlayers` function like for instance if I wanted to remove a player that `removePlayer` function would need to be inside the `updatePlayers` function too and If I had a hundred players on the page I would be attaching event listeners to each of them.

Although this works fine, I want to know if there are any OOP techniques I can use to make my code more scalable and extensible?

SEE DEMO

Solution

The main problem is to mix javascript objects with DOM elements. It’s conceptual wrong to do this. However this has some advantages, first of all the execution speed.

The alternative, indeed, is to calculate everything with pure javascript and, when something change, rewrite all the DOM elements. This solution, when the data are very large and the elements change too frequently, could be not optimal.

Said this, according with your approach, I try to separate as much possible the DOM with the Javascript with some class/method bridge between them.

First rule: works always with data-* when search values on DOM. Because the css class could be change during a refactory by other developers (after several years maybe). It’s also possibile that what you want see don’t match what you want manipulate. For example: the quantity of Ninjas could be “1000” and you would write “1k”. So it’s better to store this value in a data-attribute.

### Premise: This is only a point to start.

Here we go.

For link a DOM element with a Javascript Object I’ve added a data attribute `data-object`

``````<div class="playerTypeBlock" data-player-type-id="1" data-player-value="10.00" data-object="player">
``````

and of course here

``````<div class="playerTypeBlock" data-player-type-id="2" data-player-value="20.25" data-object="player">
``````

and here

``````<tbody class="totalPlayers" data-object="playerList"></tbody>
``````

Now we have the object `Player`

``````function Player(id, name, value) {
this.id = id;
this.name = name;
this.value = value;
}
``````

We need a Factory of players starting from DOM. Because, if something change on your HTML you should modify only this method:

``````Player.getPlayerFromDOM = function (domElement) {
var \$dom = \$(domElement);
var playerType = \$dom.attr("data-player-type-id");
var player = {
id    : +\$dom.attr("data-player-type-id"),
value : +\$dom.attr("data-player-value"),
}
return new Player(player.id, player.name, player.value);
}
``````

Another thing to do is to write a function that fetch the schema of players (how much cost a ninja? How much a samurai?) I think that the better solution is to inject a json with this values, but I don’t know if you want/can. So fetching it by DOM.

``````Player.getSchema= function() {
var players = {};
\$('[data-object="player"]').each(function() {
var player =  Player.getPlayerFromDOM(this);
players[player.id] = player;
});
return players;
}
``````

For players we have all.
There is now the player list:

``````function PlayersList(\$table) {
var \$tbody = \$table.find("totalPlayers");
// be sure if there is the totalPlayers class
if(\$tbody.length == 0) {
}
this.\$playersContainer = \$tbody;
this.\$table = \$table;
}

var \$tr = \$('<tr/>');
for(var i = 0, clen = cells.length; i < clen; i++) {
var \$td = \$('<td/>');
if(cells[i].hasOwnProperty('className')) {
}
if(cells[i].hasOwnProperty('data')) {
for(var dataAttr in cells[i].data) {
\$td.attr("data-"+dataAttr, cells[i].data[dataAttr]);
}
}
\$tr.append(\$td.html(cells[i].value))
}
this.\$playersContainer.append(\$tr);
return \$tr;
}
``````

It’s for a simple table managment. Method `AddRow` add a row with some class or some data-attribute. It could be a simple method of a superclass more generic for table management.
So, when a player is added, the table must be updated with new values.

``````PlayersList.prototype.addPlayer = function(player) {
var \$table = this.\$table;
var \$row = \$table.find("tr[data-player-type-id='"+player.id+"']");
if(\$row.length === 0) {
[
{value: player.name},
{value: 1, className: 'quantity', data:{quantity:1}}
]
).attr("data-player-type-id", player.id);
}else {
var newValue = +\$row.find("[data-quantity]").attr("data-quantity")+1;
\$row.find("[data-quantity]").attr("data-quantity", newValue).text(newValue);
}
this.\$table.trigger('ninja.table-updated')
}
``````

Nothing of special. Just note the trigger of custom event `ninja.table-updated` that notice the listeners that something is changed (in this case, we will add a listener for total calculation).

For totalcalculation, let’s create a method with compute the total with a given schema (maybe the schema could change, or could depends by other things)

``````PlayersList.prototype.calcTotal = function(schema) {
var total = 0;
this.\$table.find('tbody').find("tr").each(function() {
var playerId = \$(this).attr("data-player-type-id")
total += schema[playerId].value*\$(this).find("[data-quantity]").text();
});
}
``````

So we have all that we need:

``````var playersSchema = Player.getSchema();
var \$teams = \$('.teams');
var playerTable = new PlayersList(\$teams);