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"),
name : $dom.find('.heading').text()
}
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) {
$tbody = $table.append($('<tbody/>').addClass("totalPlayers"));
}
this.$playersContainer = $tbody;
this.$table = $table;
}
PlayersList.prototype.addRow = function(cells) {
var $tr = $('<tr/>');
for(var i = 0, clen = cells.length; i < clen; i++) {
var $td = $('<td/>');
if(cells[i].hasOwnProperty('className')) {
$td.addClass(cells[i].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) {
$row = this.addRow(
[
{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();
});
return total;
}
So we have all that we need:
var playersSchema = Player.getSchema();
var $teams = $('.teams');
var playerTable = new PlayersList($teams);
$('body').on('click', '.addToTeam', function() {
var player = Player.getPlayerFromDOM($(this).parents('[data-player-type-id]'));
playerTable.addPlayer(player);
});
$teams.on('ninja.table-updated', function() {
$('#totalvalue').text(playerTable.calcTotal(playersSchema));
});
function Player(id, name, value) {
this.id = id;
this.name = name;
this.value = value;
}
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"),
name : $dom.find('.heading').text()
}
return new Player(player.id, player.name, player.value);
}
Player.getSchema= function() {
var players = {};
$('[data-object="player"]').each(function() {
var player = Player.getPlayerFromDOM(this);
players[player.id] = player;
});
return players;
}
function PlayersList($table) {
var $tbody = $table.find("totalPlayers");
// be sure if there is the totalPlayers class
if($tbody.length == 0) {
$tbody = $table.append($('<tbody/>').addClass("totalPlayers"));
}
this.$playersContainer = $tbody;
this.$table = $table;
}
PlayersList.prototype.addPlayer = function(player) {
var $table = this.$table;
var $row = $table.find("tr[data-player-type-id='"+player.id+"']");
if($row.length === 0) {
$row = this.addRow(
[
{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')
}
PlayersList.prototype.addRow = function(cells) {
var $tr = $('<tr/>');
for(var i = 0, clen = cells.length; i < clen; i++) {
var $td = $('<td/>');
if(cells[i].hasOwnProperty('className')) {
$td.addClass(cells[i].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;
}
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();
});
return total;
}
var playersSchema = Player.getSchema();
var $teams = $('.teams');
var playerTable = new PlayersList($teams);
$('body').on('click', '.addToTeam', function() {
var player = Player.getPlayerFromDOM($(this).parents('[data-player-type-id]'));
playerTable.addPlayer(player);
});
$teams.on('ninja.table-updated', function() {
$('#totalvalue').text(playerTable.calcTotal(playersSchema));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="playerTypeBlock" data-player-type-id="1" data-player-value="10.00" data-object="player">
<div class="heading">Ninja</div>
<div class="price">$10</div> <a class="addToTeam" href="#">Add player</a>
</div>
<hr>
<div class="playerTypeBlock" data-player-type-id="2" data-player-value="20.25" data-object="player">
<div class="heading">Samurai</div>
<div class="price">$20.25</div> <a class="addToTeam" href="#">Add player</a>
</div>
<hr>
<table class="teams" border="1">
<thead>
<tr>
<th>Players</th>
<th>Quantity</th>
</tr>
</thead>
<tbody class="totalPlayers" data-object="playerList"></tbody>
</table>
<div class="total">
<span>Total: $</span>
<span class="amount" id="totalvalue">0.00</span>
</div>