Comparing and updating data from an HTML table with data returned via JSON?

Asked

Viewed 1,662 times

6

Problem: I have a table that is populated by data coming from multiple JSON files, so far no problem. The point is, I need to update the data if the values contained in JSON are different than in the table, but without changing the order of the items.

This whole process should preferably be done using only Angularjs.

If a code (Symbol) that came in JSON does not exist in the table I add it, if it already exists I update it. At the moment I have only completed the code below:

<!DOCTYPE html>
<html lang="pt-br" ng-app="money">
    <head>
        <meta charset="UTF-8"/>
        <title>BMF&Bovespa</title>
        <link rel="stylesheet" href="assets/css/bootstrap.min.css">
        <script src="assets/lib/angular.min.js"></script>
        <style>
            .green {
                color: green;
            }
            .red {
                color: red;
            }
        </style>
        <script>
            angular.module("money", []);
            angular.module("money").controller("moneyController", function ($scope, $http, $interval) {

                var papeis = ['ABEaV3.SA','BBTG12.SA', 'AGRO3.SA', 'BPAN4.SA', 'BPHA3.SA', 'BRML3.SA', 'BRSR6.SA', 'BTOW3.SA', 'CARD3.SA', 'CIEL3.SA', 'CMIG4.SA', 'CTKA4.SA', 'CTSA3.SA'];

                function consultarCotacao(papeis) {

                    var arr = [];
                    angular.forEach(papeis, function (papel) {
                        var yql = 'select * from yahoo.finance.quotes where symbol in ("' + papel + '")';
                        var api = 'https://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(yql);
                        var url = api + '&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=';
                        $http({
                            method: 'GET',
                            url: url,
                            timeout: 3000,
                            headers: {'Content-Type': 'json'}
                        }).then(function successCallback(response) {
                            resultado = response.data.query.results.quote;
                            if (resultado.Name !== null) {
                                arr.push(resultado);
                            } else {
                                console.log(resultado.Symbol + ' não foi encontrado!');
                            }
                        }, function errorCallback(response) {
                            console.log('Falha na chamada do recurso.');
                        });
                    });

                    $scope.valores = arr;
                }
                $scope.color = function (valor)
                {
                    if (valor.indexOf("+") !== -1) {
                        return "green";
                    }
                    return "red";
                }
                consultarCotacao(papeis);
                //$interval(consultarCotacao, 5000, 0);
            });
        </script>
    </head>
    <body ng-controller="moneyController">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>Código</th>
                    <th>Cotação</th>
                    <th>Variação (RS)</th>
                    <th>Variação (%)</th>
                    <th>Maior cotação</th>
                    <th>Menor cotação</th>
                    <th>Abertura</th>
                    <th>Fechamento</th>
                    <th>Volume</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="valor in valores">
                    <td>{{valor.Symbol}}</td>
                    <td>R$ {{valor.LastTradePriceOnly}}</td>
                    <td ng-class="color(valor.Change)">R$ {{valor.Change}}</td>
                    <td ng-class="color(valor.ChangeinPercent)">{{valor.ChangeinPercent}}</td>
                    <td>R$ {{valor.DaysHigh}}</td>
                    <td>R$ {{valor.DaysLow}}</td>
                    <td>R$ {{valor.Open}}</td>
                    <td>R$ {{valor.PreviousClose}}</td>
                    <td>{{valor.Volume}}</td>
                </tr>
            </tbody>
        </table>
    </body>
</html>

Mapping table columns to Json:

  • Code => Symbol
  • Quotation => Lasttradepriceonly
  • Variation (RS) => Change
  • Variation (%) => Changeinpercent
  • Highest quotation => Dayshigh
  • Lowest quotation => Dayslow
  • Opening => Open
  • Closure => Previousclose
  • Volume => Volume

JSON file populating the table:

{
    "query": {
        "count": 1,
        "created": "2017-02-22T12:29:35Z",
        "lang": "pt-br",
        "results": {
            "quote": {
                "symbol": "CTSA3.SA",
                "Ask": "2.07",
                "AverageDailyVolume": "6374",
                "Bid": "1.91",
                "AskRealtime": null,
                "BidRealtime": null,
                "BookValue": "6.24",
                "Change_PercentChange": "-0.06 - -2.90%",
                "Change": "-0.06",
                "Commission": null,
                "Currency": "BRL",
                "ChangeRealtime": null,
                "AfterHoursChangeRealtime": null,
                "DividendShare": null,
                "LastTradeDate": "2/21/2017",
                "TradeDate": null,
                "EarningsShare": "-0.37",
                "ErrorIndicationreturnedforsymbolchangedinvalid": null,
                "EPSEstimateCurrentYear": null,
                "EPSEstimateNextYear": null,
                "EPSEstimateNextQuarter": "0.00",
                "DaysLow": "2.00",
                "DaysHigh": "2.07",
                "YearLow": "1.46",
                "YearHigh": "3.29",
                "HoldingsGainPercent": null,
                "AnnualizedGain": null,
                "HoldingsGain": null,
                "HoldingsGainPercentRealtime": null,
                "HoldingsGainRealtime": null,
                "MoreInfo": null,
                "OrderBookRealtime": null,
                "MarketCapitalization": "78.99M",
                "MarketCapRealtime": null,
                "EBITDA": "136000.00",
                "ChangeFromYearLow": "0.55",
                "PercentChangeFromYearLow": "+37.67%",
                "LastTradeRealtimeWithTime": null,
                "ChangePercentRealtime": null,
                "ChangeFromYearHigh": "-1.28",
                "PercebtChangeFromYearHigh": "-38.91%",
                "LastTradeWithTime": "4:29pm - <b>2.01</b>",
                "LastTradePriceOnly": "2.01",
                "HighLimit": null,
                "LowLimit": null,
                "DaysRange": "2.00 - 2.07",
                "DaysRangeRealtime": null,
                "FiftydayMovingAverage": "2.03",
                "TwoHundreddayMovingAverage": "1.84",
                "ChangeFromTwoHundreddayMovingAverage": "0.17",
                "PercentChangeFromTwoHundreddayMovingAverage": "+9.44%",
                "ChangeFromFiftydayMovingAverage": "-0.02",
                "PercentChangeFromFiftydayMovingAverage": "-0.77%",
                "Name": "SANTANENSE  ON",
                "Notes": null,
                "Open": "2.07",
                "PreviousClose": "2.07",
                "PricePaid": null,
                "ChangeinPercent": "-2.90%",
                "PriceSales": "0.23",
                "PriceBook": "0.33",
                "ExDividendDate": "5/4/2015",
                "PERatio": null,
                "DividendPayDate": null,
                "PERatioRealtime": null,
                "PEGRatio": "0.00",
                "PriceEPSEstimateCurrentYear": null,
                "PriceEPSEstimateNextYear": null,
                "Symbol": "CTSA3.SA",
                "SharesOwned": null,
                "ShortRatio": "0.00",
                "LastTradeTime": "4:29pm",
                "TickerTrend": null,
                "OneyrTargetPrice": null,
                "Volume": "6800",
                "HoldingsValue": null,
                "HoldingsValueRealtime": null,
                "YearRange": "1.46 - 3.29",
                "DaysValueChange": null,
                "DaysValueChangeRealtime": null,
                "StockExchange": "SAO",
                "DividendYield": null,
                "PercentChange": "-2.90%"
            }
        }
    }
}

Another example of JSON:

{
    "query": {
        "count": 1,
        "created": "2017-02-22T12:29:35Z",
        "lang": "pt-br",
        "results": {
            "quote": {
                "symbol": "BPHA3.SA",
                "Ask": "7.70",
                "AverageDailyVolume": "230233",
                "Bid": "7.46",
                "AskRealtime": null,
                "BidRealtime": null,
                "BookValue": "5.54",
                "Change_PercentChange": "-0.15 - -1.96%",
                "Change": "-0.15",
                "Commission": null,
                "Currency": "BRL",
                "ChangeRealtime": null,
                "AfterHoursChangeRealtime": null,
                "DividendShare": null,
                "LastTradeDate": "2/21/2017",
                "TradeDate": null,
                "EarningsShare": "-9.42",
                "ErrorIndicationreturnedforsymbolchangedinvalid": null,
                "EPSEstimateCurrentYear": "-0.50",
                "EPSEstimateNextYear": null,
                "EPSEstimateNextQuarter": "0.00",
                "DaysLow": "7.47",
                "DaysHigh": "7.90",
                "YearLow": "3.92",
                "YearHigh": "19.20",
                "HoldingsGainPercent": null,
                "AnnualizedGain": null,
                "HoldingsGain": null,
                "HoldingsGainPercentRealtime": null,
                "HoldingsGainRealtime": null,
                "MoreInfo": null,
                "OrderBookRealtime": null,
                "MarketCapitalization": "848.11M",
                "MarketCapRealtime": null,
                "EBITDA": "-296.97M",
                "ChangeFromYearLow": "3.58",
                "PercentChangeFromYearLow": "+91.33%",
                "LastTradeRealtimeWithTime": null,
                "ChangePercentRealtime": null,
                "ChangeFromYearHigh": "-11.70",
                "PercebtChangeFromYearHigh": "-60.94%",
                "LastTradeWithTime": "6:04pm - <b>7.50</b>",
                "LastTradePriceOnly": "7.50",
                "HighLimit": null,
                "LowLimit": null,
                "DaysRange": "7.47 - 7.90",
                "DaysRangeRealtime": null,
                "FiftydayMovingAverage": "6.39",
                "TwoHundreddayMovingAverage": "8.00",
                "ChangeFromTwoHundreddayMovingAverage": "-0.50",
                "PercentChangeFromTwoHundreddayMovingAverage": "-6.30%",
                "ChangeFromFiftydayMovingAverage": "1.11",
                "PercentChangeFromFiftydayMovingAverage": "+17.40%",
                "Name": "BR PHARMA   ON      NM",
                "Notes": null,
                "Open": "7.65",
                "PreviousClose": "7.65",
                "PricePaid": null,
                "ChangeinPercent": "-1.96%",
                "PriceSales": "0.41",
                "PriceBook": "1.38",
                "ExDividendDate": "5/2/2012",
                "PERatio": null,
                "DividendPayDate": null,
                "PERatioRealtime": null,
                "PEGRatio": "0.00",
                "PriceEPSEstimateCurrentYear": null,
                "PriceEPSEstimateNextYear": null,
                "Symbol": "BPHA3.SA",
                "SharesOwned": null,
                "ShortRatio": "0.00",
                "LastTradeTime": "6:04pm",
                "TickerTrend": null,
                "OneyrTargetPrice": null,
                "Volume": "268800",
                "HoldingsValue": null,
                "HoldingsValueRealtime": null,
                "YearRange": "3.92 - 19.20",
                "DaysValueChange": null,
                "DaysValueChangeRealtime": null,
                "StockExchange": "SAO",
                "DividendYield": null,
                "PercentChange": "-1.96%"
            }
        }
    }
}

I published the code on: https://github.com/fabiojaniolima/money

  • And what you’ve done?

  • 1

    Related: http://answall.com/questions/185159/como-locator-values-attributeapplicationsusing-o-angularjs

  • 1

    @Fabio put everything you did, the ideal would be to put the angular that you’ve made so far.

  • Virgilio, the code hasn’t changed, it’s posted here: http://answall.com/questions/185159/howto locate values-values-attributeapplications-utilindo-o-angularjs I posted above a little bit so as not to pollute the page too much, since these characteristics can be considered or treated independently

  • Have you ever tried to map your JSON to a javascript object and bind to HTML with the angular?

  • Yes, but I didn’t succeed. Front-end is not my strong suit.

  • Fabio, when you get the array, is there any field that is always unique? regardless of how many times you make the request.. For example, I believe the camp symbol be unique, correct? If not, is there any other who is? like an ID, for example.

  • I would indicate using Oneway Databind and every 10 seconds as you commented, reset the array from the list used in ngRepeate and load the new items into the array. More: http://answall.com/questions/153646/para-que-serve-o-dois-pontos-duplos-angular

  • @Celsomtrindade the field Symbol will be unique

Show 4 more comments

4 answers

5


Preserve the returned values in a mapped object, whose property key is the position of the paper in the array papeis. Next, change only the value of the property according to the return of the API.

The following changes have been made:

$scope.mapa = {};

An object is created in the scope of the control to contain the returned results. Note that it is not a array, and which is not reset at each load cycle.

angular.forEach(papeis, function(papel, index) {[...]

The method forEach() provides an extra property, passed as the second parameter of the call - the item index in the source collection.

$scope.mapa[index] = resultado;

Return of query to API is stored in map according to the index.

And, in the view:

<tr ng-repeat="(k, valor) in mapa">

The format (key, value) in a ngRepeat allows an object to be used as source, properties as value keys.

The key (which is only the paper index in the original array) can be ignored, but the value is used to popular your view.

(Additionally another map object was created, $scope.prev, to help identify value differences between current and previous requests.)

Functional example to follow:

angular.module("money", []);
angular.module("money")
    .controller("moneyController", function($scope, $http, $interval) {
        var papeis = ["ABEaV3.SA", "BBTG12.SA", "AGRO3.SA", "BPAN4.SA", "BPHA3.SA", "BRML3.SA", "BRSR6.SA", "BTOW3.SA", "CARD3.SA", "CIEL3.SA", "CMIG4.SA", "CTKA4.SA", "CTSA3.SA"];
        $scope.mapa = {};
        $scope.prev = {};

        function consultarCotacao() {
            angular.forEach(papeis, function(papel, index) {
                var yql = 'select * from yahoo.finance.quotes where symbol in ("' + papel + '")';
                var api = "https://query.yahooapis.com/v1/public/yql?q=" + encodeURIComponent(yql);
                var url = api + "&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=";
                $http({
                        method: "GET",
                        url: url,
                        timeout: 3000,
                        headers: { 'Content-Type': "json" }
                    })
                    .then(function successCallback(response) {
                        resultado = response.data.query.results.quote;
                        if (resultado.Name !== null) {

                            if (angular.toJson($scope.mapa[index]) !== angular.toJson(resultado)) {
                            
                                if ($scope.mapa[index]){
                                    var clone =angular.fromJson(angular.toJson($scope.mapa[index]));
                                    $scope.prev[resultado.Symbol] = clone;
                                }
                            
                                $scope.mapa[index] = resultado;
                            }
                        } else {
                            console.log(resultado.Symbol + " não foi encontrado!");
                        }
                    }, function errorCallback(response) { console.log("Falha na chamada do recurso."); });
            });
        }

        $scope.color = function(valor) {
            if (valor.indexOf("+") !== -1) {
                return "green";
            }
            return "red";
        };
        $scope.markDiff = function(a, b) {
            if (a !== b) {
                return "bold-text";
            }
            return "normal-text";
        };
        consultarCotacao();
        $interval(consultarCotacao, 5000, 0);
    });
<html lang="pt-br" ng-app="money">
    <head>
        <meta charset="UTF-8"/>
        <title>BMF&Bovespa</title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
        <style>
            .green {
                color: green;
            }
            .red {
                color: red;
            }
            .bold-text {
                font-weight:bold;
            }
        </style>
        <script>
        </script>
    </head>
    <body ng-controller="moneyController">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>Código</th>
                    <th>Cotação</th>
                    <th>Variação (RS)</th>
                    <th>Variação (%)</th>
                    <th>Maior cotação</th>
                    <th>Menor cotação</th>
                    <th>Abertura</th>
                    <th>Fechamento</th>
                    <th>Volume</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="(k, valor) in mapa">
                    <td>{{valor.Symbol}}</td>
                    <td ng-class='markDiff(valor.LastTradePriceOnly, prev[valor.Symbol].LastTradePriceOnly)'>
                    R$ {{valor.LastTradePriceOnly}}</td>
                    <td ng-class="color(valor.Change)">R$ {{valor.Change}}</td>
                    <td ng-class="color(valor.ChangeinPercent)">{{valor.ChangeinPercent}}</td>
                    <td>R$ {{valor.DaysHigh}}</td>
                    <td>R$ {{valor.DaysLow}}</td>
                    <td>R$ {{valor.Open}}</td>
                    <td>R$ {{valor.PreviousClose}}</td>
                    <td>{{valor.Volume}}</td>
                </tr>
            </tbody>
        </table>
        
        <pre>{{mapa | json}}<pre>
        <pre>{{prev | json}}<pre>
    </body>
</html>

  • It would be possible to still flash the updated cell with background-color?

  • @Fábio does not see why not - you can store the old values and compare. I will make a small change to demonstrate the behavior.

  • @Fábio done - you can notice the bold effect on different quotes of the last request.

  • Wouldn’t you leave bold only if the next value is different from the current one? My idea is when the value is changed the table cell blinks (type an effect of 1 second)

  • @Fábio This is the behavior (bold if different) that this example is using. As for visual effects, you can use ng-Animate, for example, to define the desired behavior - however the explanation goes beyond the scope of the question.

1

Use a dictionary

To update the array doing only the update of the items, without having to clean and regenerate the array, you can use a dictionary that relates the key to the item index and update when the result brings the same key and insert when it does not find.

Suggestion based on your Github code. I used the field Symbol as key but you can put the one who best does this role, ie that can be identified solely in the records:

    angular.module("money", []);
    angular.module("money").controller("moneyController", function ($scope, $http, $interval) {

        var papeis = ['ABEaV3.SA','BBTG12.SA', 'AGRO3.SA', 'BPAN4.SA', 'BPHA3.SA', 'BRML3.SA', 'BRSR6.SA', 'BTOW3.SA', 'CARD3.SA', 'CIEL3.SA', 'CMIG4.SA', 'CTKA4.SA', 'CTSA3.SA'];
        var dict = {};
        $scope.valores = [];

        function consultarCotacao(papeis) {

            angular.forEach(papeis, function (papel) {

                var yql = 'select * from yahoo.finance.quotes where symbol in ("' + papel + '")';
                var api = 'https://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(yql);
                var url = api + '&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=';

                $http({
                    method: 'GET',
                    url: url,
                    timeout: 3000,
                    headers: {'Content-Type': 'json'}
                }).then(function successCallback(response) {
                    resultado = response.data.query.results.quote;
                    if (resultado.Name !== null) {
                        var key = resultado.Symbol; 
                        if (!dict.hasOwnProperty(key)) {
                            dict[key] = $scope.valores.push(resultado) - 1;
                            console.log('insert: ' + key);
                        } else {
                            $scope.valores[dict[key]] = resultado;
                            console.log('update: ' + key);
                        }
                    } else {
                        console.log(resultado.Symbol + ' não foi encontrado!');
                    }
                }, function errorCallback(response) {
                    console.log('Falha na chamada do recurso.');
                });
            });

        }

        $scope.color = function (valor)
        {
            if (valor.indexOf("+") !== -1) {
                return "green";
            }
            return "red";
        }

        consultarCotacao(papeis);
        $interval(consultarCotacao, 5000, 0, 0, papeis);
    });
  • Just one question, there is some way in the first load you do not need to wait for the 5 seconds set in the second to last line?

  • The $interval makes a schedule, why it waits the 5 seconds before executing the first time. If you want to run as soon as you click you can make the call before the $interval. I updated the answer.

0

As @Rafael B. Marcílio suggested, you can map json to a javascript object, for example using JSON.parse:

var json = JSON.parse('[{
"symbol": "CTSA3.SA",
"Ask": "2.07",
"AverageDailyVolume": "6374"}]')

And instead of a static table, you can use ng-repeat to populate the table dynamically, as in this example:

Documentation ng-repeat

<table>
<thead>
    <tr>
        <th>Código</th>
        <th>Descrição</th>
        <th>Categoria</th>
        <th>Preço Atual</th>
    </tr>
</thead>
<tbody>
    <tr ng-repeat="item in materialList">

        <td>{{item.codMaterial}}</td>
        <td>{{item.description}}</td>
        <td>{{item.category.description}}</td>
        <td>{{item.currentPrice | currency:"R$ "}}</td>
    </tr>
</tbody>

Where materialList is your array that has been converted from json, so any modification in the list automatically changes the whole table

  • I haven’t been able to resolve this issue yet. I updated the main post with the link to the repository on github

  • I tried using json.parse but got an error: angular.js:10837Error: JSON Parse error: Unexpected Identifier "Object"

  • Symbol is your only key, and you need to add to the items only when that Symbol is different from the others, that’s it?

0

One way that can simplify enough for you is the use of angular.merge(), that causes 2 lists to be unified into one, updating the values of the common properties.

That is, with each request you make, just update the list, so the fields update automatically, see:

$scope.valores = [];

var arr = [];

angular.forEach(papeis, function (papel) {
    $http({
        method: 'GET',
        url: url,
        timeout: 3000,
        headers: {'Content-Type': 'json'}
    }).then(function successCallback(response) {
        resultado = response.data.query.results.quote;
        if (resultado.Name !== null) {
            arr.push(resultado);
        } else {
            console.log(resultado.Symbol + ' não foi encontrado!');
        }
    }, function errorCallback(response) {
        console.log('Falha na chamada do recurso.');
    });
})

$scope.valores = angular.merge($scope.valores, arr);

Important!!

In this model, because it is well automated, you have no control of checks. For example, if you happen to receive a list and, after 10 minutes, receive another list, exactly the same (in terms of structure), HOWEVER in a different order, the angular.merge will update, changing the order as well. That is, if the object that was the first comes in 3º in the next call, it will be the third object in the new list, etc..

If that’s not a problem, there’s not much to worry about.


If you want to do a deeper analysis of the object, I believe it is best to perform a for, analyze the objects that updated and update "manually". Something like this:

for ( var i=0; i < $scope.valores.length; i++ ) {
    for ( var in=0; in < arr.length; in++ ) {
        if ( $scope.valores[i].id === arr[in].id ) {
            angular.extend( $scope.valores[i], arr[in] );
        }
    }
}

Where angular extend. will copy the properties values of an object to the source object.

This way you guarantee that you will update the correct object to which the data belongs.


I made this plunker to illustrate the scenario, already working. I used only simpler data and simulated the request through a click on the button. Just click on the buttons and see the changes.

  • I did as suggested, however, when using $Scope.values = angular.merge($Scope.values, arr); did not return data on the screen. ng-repeat needs to be changed?

  • @Fábio no. Just make sure that you are not cleaning the two arrays before doing the merge, otherwise you can replace the two arrays and try to do the merge with them empty, since you are "zeroing" to var arr within the foreach.

  • The current code is: https://github.com/fabiojaniolima/money/blob/master/index.html

  • @Fábio you can set up a plunkr (or other online service) so that I can mess with the real data you are using?

  • Oops, the Github link has the code I’m using, it calls a Yahoo API. Download the project there that it is calling the data for which I am having difficulty dealing.

  • @Fábio yes, but I am involved in a project now and where I am I will not be able to set up a site to test very well, nor do I have much time. That’s why I asked you to do it, so I see with more agility =D

  • I get it. I have made the fonts available here: https://plnkr.co/edit/Lei3esodp6YSxMjKV9AX?p=info the test (preview) is at: https://run.plnkr.co/plunks/Lei3esodp6YSxMjKV9AX/

  • @Fábio did an edition in the reply. Access the link again and see if it worked! I left running a while here on my pc and it worked. Maybe it takes a while because, from what I saw, the obtained data really take time to change right? But I believe that is ok

Show 4 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.