Sas Sam
Sas Sam

Reputation: 691

Calculate summary with AngularJs on selected checkboxes

I'm learning the AngularJs and I've created a dynamic list with ng-repeat, but now I cannot find the solution for the next step...

I have the following code:

HTML:

<div ng-app="app">
    <div class="list" ng-controller="ListController">

        <div class="row" ng-repeat="prod in prodList">
            <div class="prod-name cell">
                <input type="checkbox" id="prod-{{ prod.id }}" data-ng-model="orders.prods.prod[prod.id]" value="{{ prod.id }}" data-cb="" /><label for="prod-{{ prod.id }}">{{ prod.name }}</label>
            </div>
            <div class="prod-size cell">
                <label for="prodColor-@{{ prod.id }}">
                    <select id="prodColor-{{ prod.id }}" data-ng-model="orders.prods.color[prod.id]" data-ng-options="color.id as color.name for color in prod.colors">
                        <option value="">Select one</option>
                    </select>
                </label>
            </div>
            <div class="prod-price cell">
                {{ prod.price }}
            </div>

        </div>

        <div class="sum">
            {{ sum }}
        </div>

    </div>
</div>

JS:

var app = angular.module("app", [], function () {
});

app.controller('ListController', function ($scope) {
    init();

    function init () {
        $scope.prodList = [{"id":"1","name":"window","colors":[{"id":"9","name":"white"},{"id":"11","name":"black"}],"price":"100"},{"id":"2","name":"door","colors":[{"id":"22","name":"blue"},{"id":"23","name":"red"}],"price":"356"},{"id":"3","name":"table","colors":[{"id":"37","name":"white"},{"id":"51","name":"black"}],"price":"505"}];
        $scope.orders = {};
        $scope.orders.prods = {};
        $scope.orders.prods.prod = {};
        $scope.orders.prods.color = {};
        $scope.sum = 0;
    }
});

Working demo:

http://jsfiddle.net/HDrzR/

Question

How can I calculate the summary of selected product's price into the $scope.sum?

Edited:

So if you select the "window" and the "table" from the list, the sum should contains 606. But if you unselect the "table" then the sum should be 100.

Thank you in advance!

Upvotes: 0

Views: 2183

Answers (5)

Quad
Quad

Reputation: 1718

Updated your fiddle FIDDLE

You can $scope.$watchCollection your scope to calcuate new sum. I chaged your prodList prices to number instead of string, and used underscore.js for easier looping in your objects/arrays.

 $scope.$watchCollection("orders.prods.prod",function(){
    var sum = 0;
    for(var i = 0;i<$scope.prodList.length;i++){
        if($scope.orders.prods.prod[$scope.prodList[i].id]){
            sum += $scope.prodList[i].price;
        }
    }
    $scope.sum = sum; 
});

watchCollection docs are here

edit: changed with regular js answer

Upvotes: 1

Dalorzo
Dalorzo

Reputation: 20014

From my POV it looks more appropriate to use is ngChange from the input[checkbox] itself. Like:

ng-change="addMe(prod.price);"

We need a way to differentiate checked from not checked and in order to change less your app we will use the existence of the object by it doing this way:

ng-change="addMe(orders.prods.prod[prod.id],prod.price);"
ng-true-value="{{ prod.id }}" 
ng-false-value=""

The full HTML will look like this:

<input type="checkbox" id="prod-{{ prod.id }}" data-ng-model="orders.prods.prod[prod.id]" ng-change="addMe(orders.prods.prod[prod.id],prod.price);" ng-true-value="{{ prod.id }}" ng-false-value="" />

From your Controller all you need is to add or substract when necessary:

$scope.addMe=function(checked,val){
  val = 1*val
  if (!checked){ $scope.sum = $scope.sum-val;}
  else { $scope.sum =  $scope.sum +val; }
}

Here is the Online Demo

Upvotes: 1

AAhad
AAhad

Reputation: 2845

Register an ng-click with checkbox and pass $event & prod.id references to a custom function that will be called on checkbox click event.

 <input type="checkbox" id="prod-{{ prod.id }}" data-ng-model="orders.prods.prod[prod.id]" value="{{ prod.price }}" ng-click="calcSum($event, prod.id)" data-cb="" />

Within controller define custom function as:

$scope.calcSum = function($event, id) {
         var checkbox = $event.target;

        if (checkbox.checked  ) {
            $scope.sum += parseInt(checkbox.value);   
         } else {
            $scope.sum -= parseInt(checkbox.value);    
         }  
}

Change the Checkbox value attribute to store {{ prod.price }} instead of {{ prod.id }}, it makes more sense.

Upvotes: 1

gkalpak
gkalpak

Reputation: 48211

You could either $watch over $scope.orders.prod and re-calculate the sum or (if performance is not a major concern and the list of objects is not huge) you could use a sum() function and reference it in the view:

Total: {{sum()}}

$scope.sum = function () {
    return $scope.prodList.filter(function (prod) {
        return $scope.orders.prods.prod[prod.id];
    }).reduce(function (subtotal, selectedProd) {
        return subtotal + parseInt(selectedProd.price);
    }, 0);
};

/* If the above looks complicated, it is the equivalent of: */
$scope.sum = function () {
    var sum = 0;
    for (var i = 0; i < $scope.prodList.length, i++) {
        var prod = $scope.prodList[i];
        if ($scope.orders.prods.prod[prod.id]) {
            sum += parseInt(prod.price);
        }
    }
    return sum;
}

If you decide to go down the watching path, this is how the code should look like:

Total: {{watchedSum}}

$scope.sum = ...;   // same as above
$scope.$watchCollection('orders.prods.prod', function (newValue, oldValue) {
    if (newValue !== undefined) {
        $scope.watchedSum = $scope.sum();
    }
});

UPDATE:
"Stole" Quad's $watchCollection as this is more "cheap" than $watch(..., true).
As long as $scope.order.prods.prod contains only rimitive values (i.e. no objects), $watchCollection is enough to detect the changes.


See, also, this short demo illustrating both approaches.

Upvotes: 2

Nikola Yovchev
Nikola Yovchev

Reputation: 10226

here is an updated fiddle. http://jsfiddle.net/HRQ9u/

what I did is add a $scope.$watch on the orders:

         $scope.$watch('orders', function (newValue, oldValue) {
            var findProdById = function (products, id) {
                for (var i = 0; i < products.length; i++) {
                    if (parseInt(products[i].id, 10) === parseInt(id, 10)) {
                        return products[i];
                    }
                }
                return null;
            };
            var orders = newValue;

            var usedProducts = newValue.prods.prod;
            var sum = 0;
            for ( var prop in usedProducts ){
               var prodList = $scope.prodList;
               var id = prop;
               if ( usedProducts[id] ){
                   var product = findProdById(prodList, id);
                   if ( product !== null ){
                       sum += parseInt(product.price, 10);
                   }
               }
            }
            $scope.sum = sum;
        }, true);

Edit:

With that said, you can probably simplify your model a lot. And I mean A LOT.

Also, I have used only regular js as opposed to lodash or underscore.

Upvotes: 1

Related Questions