Handling data streaming using ng-repeat - angularjs

I got the following data which comes out of a EventSource interface. I'm using this data to build a simple table (i have specified the table structure below) using angular ng-repeat. The issue i have is since the data changes rapidly i.e. server would be sending the changes continuously for the requested symbol and then it would keep upon updating the values. I want to add a row when data's are requested for a symbol and then should update the same row for the new values. This is pretty easy and is working.
But the real challenge is (atleast for me) when i request another symbol i want that to be added to a new second row and from now on it should update the values in both rows. It goes on as we request new symbols. With the following data structure is this possible, if so please explain.
Data Structure:
data = [
AMZN: {
name: Amazon,
symbol: AMZN,
openPrice: $100,
latestPrice: $200,
percentage: 0.1
},
FB: {
name: Facebook,
symbol: FB,
openPrice: $150,
latestPrice: $250,
percentage: 0.2
}
]
Table Structure:
<table>
<thead>
<tr>
<th>Session ID</th>
<th>Company Name</th>
<th>Symbol</th>
<th>Open Price</th>
<th>Latest Price</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="stockVal in data">
<td>[Empty]</td>
<td>{{stockVal.companyName}}</td>
<td>{{stockVal.companySymbol}}</td>
<td>{{stockVal.openPrice}}</td>
<td>{{stockVal.latestPrice}}</td>
<td>{{stockVal.percentage}}</td>
</tr>
</tbody>
</table>
What happens now is when i do a request for AMZN the tables populates the values pertaining to Amazon and then if i request for FB it replace the values in the same row... i.e. both FB and AMZN gets replaces in the same row. Bottom line is i want FB values to be populated in new row.
Controller code:
All it does is making a request for the end point with window.EventSource and grab the data and attach it to the scope object. To avoid populating n number of rows the data array is flushed each time like making the length to 0.
Adding controller code below,
$scope.generateData = _generateData;
function generateData() {
endPoint = '/getData'
$scope.symbols = window.document.getElementById('symbols').value;
var eventSource = new window.EventSource(endPoint + '?&s=' + $scope.symbols);
eventSource.onmessage = function(e) {
var data = null;
try {
data = JSON.parse(e.data);
} catch (err){
}
if(data) {
$scope.$apply(function () {
$scope.companyName = data.name;
$scope.companySymbol = data.symbol;
$scope.openPrice = data.openPrice;
$scope.latestPrice = data.latestPrice;
$scope.percentage = data.percentage;
});
var specData = {
[$scope.companySymbol]: {
name: $scope.companyName,
symbol: $scope.companySymbol,
openPrice: $scope.openPrice,
latestPrice: $scope.latestPrice,
percentage: $scope.percentage
}
}
$scope.data[0] = specData[$scope.companySymbol];
}
}
};

Related

ServiceNow spUtil

I'm trying to make a slight improvement to an existing widget that our team created, but can't seem to get it to work correctly. We have a widget that does a RowCount of tasks and groups them by state. I want the RowCount to auto update once a task is complete without having the user press the refresh button. I've read some documentation on $rootscope, $broadcast, and $on, but can't seem to get it to work.
Below is snippet of our HTML:
<table class="table table-sm table-responsive">
<tbody>
<tr class="h3">
<td colspan=2>Complete</td>
</tr>
<tr class="h2 bg-success" ng-repeat="x in data.values track by $index">
<td><span class="glyphicon glyphicon-check"></span></td>
<td>{{x.completedCount}}</td>
</tr>
</tbody>
</table>
A snippet of our Server Script:
var values = [];
var _completedCount;
var gsCompleted = new GlideRecordSecure('sn_hr_core_task');
//CLOSED COMPLETE, CLOSED INCOMPLETE,
gsCompleted.addQuery('state', 'IN', '3,4,7');
gsCompleted.addQuery('assigned_to', gs.getUserID());
gsCompleted.addQuery("parent.state", 'NOT IN', '1,800,900');
gsCompleted.query();
if(gsCompleted){
_completedCount = gsCompleted.getRowCount();
}
else{
_completedCount = 0;
}
values.push(
{
completedCount: _completedCount
});
data.values = values;
How do I get this widget to auto update the Completed row count without refreshing the page? I've been playing around with spUtil recordWatch, but cannot get it to work correctly:
function($scope, $sce, spUtil) {
var c = this;
c.data.loading = true;
//After page initially loads re-call server script to load data
c.server.get({
action: 'retrieve_data'
}).then(function(response) {
c.data.loading = false;
console.log('Response');
console.log(response);
c.data.values = response.data.values;
spUtil.recordWatch($scope, 'sn_hr_core_task', "", function(name,data) {
spUtil.update($scope);
})
});
}
Take a look at the widget Simple List, it has an example of one that may help a bit.
You should be able to change your recordWatch to this
var filter = "stateIN3,4,7^parent.stateNOT IN1,800,900^assigned_to=" + window.NOW.user_id;
spUtil.recordWatch($scope, 'sn_hr_core_task', filter);
You generally won't need a callback function unless there is some specific action you're triggering.

Update a specific table row based on unique id

I have two components, the parent is called Layout and the child Report. In Report I have a table that loops through and renders a list of car accidents. I have a handler - handleIncidentReport in Report that calls a function in Layout to update this.state.obds (car messages) which then obviously updates the child Report.
My question is what is the proper ReactJS way to have it so only the row clicked has its {this.props.incident_report} updated, and not the other dynamically created rows with the same yield statement (not sure of the correct terminology).
I'm less than a week into ReactJS and I know I could hack it together but I want to know the proper way.
Here is a chunk of the two files -
Report
handleIncidentReport: function(e) {
var accident_id = $(e.target).closest('tr').data('accident-id')
this.props.changeIncidentReport(e, accident_id)
},
render: function() {
var self = this;
var accidents = [];
for (var i = 0; i < this.props.accidents.length; i++) {
var incident = this.props.accidents[i];
accidents.push([
<tr key={i} onClick={self.handleIncidentReport} data-accident-id={incident.id} >
<td>{incident.owner.first_name} {incident.owner.last_name}</td>
<td>{incident.car.title}</td>
<td>{moment(incident.created_at).format("MMM D, YYYY - hh:mm A")}</td>
</tr>,
<tr className="incident-report">
<td colSpan="3">
<Obds obds={this.props.incident_report} />
</td>
</tr>
]);
};
return (
<div className="report">
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Car</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{accidents}
</tbody>
</table>
</div>
);
}
Layout
changeIncidentReport: function(e, accident_id) {
var $tr = $(e.target).closest('tr');
$.ajax({
method: "GET",
data: {accident_id},
url: "/superadmin/emergency_analysis/get_incident",
datatype: 'jsonp'
}).success(function(incident){
this.setState({
incident_report: incident
});
}.bind(this));
},
You should make it so that handleIncidentReport returns a function that is configured (through closures) with the index of each of the rows, something like:
handleIncidentReport: function(accident_id) {
return function(e) {
this.props.changeIncidentReport(e, accident_id)
}
},
(If you aren't sure how closures work you should check out this excellent post: How do JavaScript closures work?)
Then update your render to use
<tr key={i} onClick={self.handleIncidentReport(i)} data-accident-id={incident.id} >
and each of the rows will have a unique handler all of their own that will be called when they're clicked on.
This should also get rid of some of that jQuery which figures out which accident_id was clicked on too.

Angular JS, bind scope property to function

I'm new to Angular JS. I am creating an app that stores data in the web storage. WHen an enquiry form is submitted, it gets the current arrays from the web storage, strigifies the new form data, adds it to an array, and posts it back to web storage. Works great and all gets saved correctly.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
}
function getStoredData(storeName) {
// get or create enquiry store
var storedData = (typeof localStorage.getItem(storeName) !== 'undefined' && localStorage.getItem(storeName)) || "[]";
// parse store into object
storedData = JSON.parse(storedData);
return storedData;
}
function addStoredData(data, storeName) {
var storedData = getStoredData(storeName);
var count = storedData.length;
// form data into next submission slot
storedData[count] = data;
// turn back into JSON
storedData = JSON.stringify(storedData);
// slap it back in web storage
localStorage.setItem(storeName, storedData);
}
On my HTML page I have this;
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Date</th>
</tr>
<tr ng-repeat="e in enquiries">
<td>{{ e.firstName }}</td>
<td>{{ e.lastName }}</td>
<td>{{ e.date }}</td>
</tr>
</table>
So I want to be able to do this...
// list of stored enquiries
$scope.enquiries = function () {
return getStoredData(ENQUIRY_STORE);
}
But it doesn't bind and there are no rows. If I do this...
$scope.enquiries = getStoredData(ENQUIRY_STORE);
it works, but then I have to keep setting it when a new enquiry is submitted. Maybe Anulgar doesn't let you have use functions to return data for binding, but I thought it did.
Any help much appreciated. Thanks.
In the first way ($scope.enquiries = function() ...) it doesn't bind because you are making an ngRepeat over a function (it doesn't throw an error because a function has actually a length).
However, even if you did <tr ng-repeat="e in enquiries()"> you would have a infinite digest error because enquiries() is returning a different object each time and angular is waiting to have the same result twice to stop the digests cycle.
The best way would be:
$scope.submit = function() {
...
setEnquiries();
}
...
function setEnquiries() {
$scope.enquiries = getStoredData(ENQUIRY_STORE);
}
And keep looping through enquiries:
<tr ng-repeat="e in enquiries">
$scope.selectedENQUIRY_STORE = null;
$scope.getStoredData= function (e) {
$scope.selectedENQUIRY_STORE = e;
};
it binds when you clicked on that enquiry form on name
You simply have to retrieve the data again, upon submitting, so move your $scope.enquiries() function into your $scope.submit() function.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
// FIRE UP THE FUNCTION
$scope.enquiries();
}
Note that you should put getStoredData() and other data related function into an angular service.
Luckily, there's already a good module for dealing with LocalStorage operations :
https://github.com/tymondesigns/angular-locker

how to fix ngtable pagination?

I have developed a ngtable with a filter. The pagination on the table does not work anymore though? When I select the show 10 rows on the bottom of the table it still shows 14 rows? How can i fix the pagination/indexing?
This is my table definition:
<table ng-table="tableParams" class="table">
<tr ng-repeat="account in $parent.filtered =(data | filter:search.accountName | filter:search.id)">
<td data-title="'id'">
{{account.account.accountId.id}}
</td>
<td data-title="'name'">
{{account.account.accountName}}
</td>
</tr>
</table>
plunkr:http://plnkr.co/edit/Rqt6px?p=preview
You need to figure pagination function by yourself. You may see ng-table's example in here.
var Api = $resource("/data");
this.tableParams = new NgTableParams({}, {
getData: function(params) {
// ajax request to api
return Api.get(params.url()).$promise.then(function(data) {
params.total(data.inlineCount); // recal. page nav controls
return data.results;
});
}
});
It first load all the data into Api. The params.url() contains current page and page count. It then use these two variable to return part of dataset. So you may need to figure out how to return this part of data.

angular js ng repeat asynchronously

I want to show a list of elements on view asynchronously.
For example i have a method which returns promise and result will look like this.
var result = {
books: [
{name: 'The Kite Runner', author: 'Khaled Hosseini'},
{name: 'The Book Thief', author: 'Markus Zusak'},
{name: 'A Thousand Splendid Suns', author: 'Khaled Hosseini'},
]
}
Here is a method:
function getBooks(userId) {
return BookProduct.getBooksByUser.query({ id: userId }).$promise;
};
And then i have a method which invokes getBooks.
$scope.showBooks = function(userId) {
//some users id
var users_id = [1,2,3,4,5];
$scope.tables = [];
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
}
Thereafter i want to show it to user on a view using angular ng-repeat.
<div data-ng-repeat="table in tables">
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="book in table.books">
<td>{{ name }}</td>
<td>{{ author }}</td>
</tr>
</tbody>
</table>
</div>
It works good but ng-repeat will work when function $scope.showBooks is executed and $scope.tables is already full. I am looking for more nice solution. I want to load books for user piecemeal on each iteration when method getBooks() invokes.
So in this part for example when the first iteration is processing and i get result of 2 elements, then i pass them to the view and user can see the first part. Next iteration i get for example other 3 elements, so i add them to previous result and pass to the view, so user can see 5 elements now, and so on. I know it will be almost insensibly for user but if my server responds slowly it will help me to be more user friendly.
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
I am not asking you HOW to implement it. I just want to know is it possible or not and where should i dig to know more, cause i tried some googling but without success.
Angular has a cycle in which it performs all scope functionality and after each cycle of "watches" it renders changes to the view. By default, Angular performs this automatically - for example simple change of variable in scope also rerenders view. But in some cases you may want to intercept because your operation didn't trigger the rerender.
For these cases, $apply and $digest are useful - more in documentation https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

Resources