Angular is adding an empty array to a 2D array - where/why? - javascript
I'm having a bit of a weird problem and there are now 2 people in my office completely stumped by this (so I hope it's not something embarrassingly obvious!).
As an overview, we have a SQLite database that could contain any number of tables, each with any number of rows or columns. We want to display this data in a page in an Electron app.
We have an Angular page in our Electron app (with Express/Node as a backend) which makes some calls to an Express endpoint (which gets data from the SQLite db) and we end up with two 2D arrays of data - one for the table headers ($scope.tableHeaders) and one for the table content ($scope.documentsList).
The code for the calls is below:
$http.get('http://localhost:3000/tableCount').then(function (res) {
if (res.data.numberoftables) {
$scope.count = res.data.numberoftables;
var i;
$scope.documentsList = new Array();
$scope.tableHeaders = new Array();
for (i = 1; i <= $scope.count; i++) {
var tableNo = i;
//loop through tables
***$http.get('http://localhost:3000/doclist/' + tableNo).then(function (resDocs) {
//get table content
if (resDocs.data) {
***$scope.documentsList.push(resDocs.data);
}
});
$http.get('http://localhost:3000/tableColumns/' + tableNo).then(function (resHeads) {
//get table headers
if (resHeads.data) {
$scope.tableHeaders.push(resHeads.data);
}
});
}
}
});
Just for fun, here's our HTML:
<div id="documentTables">
<div ng-repeat="tableID in getNumberArray(count)">
<table st-table="documentsList[tableID]" class="table table-condensed table-hover">
<thead>
<tr st-safe-src="tableHeaders[tableID]">
<th ng-repeat="col in tableHeaders[tableID]">
{{col}}
</th>
</tr>
<tr st-safe-src="tableHeaders[tableID]" colspan="{{tableHeaders[tableID].length}}">
<th colspan="1" ng-repeat="col in tableHeaders[tableID]">
<input id="{{col + 'searchbox' + tableID}}" st-search="col" placeholder="{{col}}" class="input-sm form-control" type="search" />
</th>
</tr>
</thead>
<tbody>
<tr st-safe-src="documentsList[tableID]" ng-repeat="document in documentsList[tableID]">
<td ng-repeat="col in tableHeaders[tableID]">
{{document[col].value}}
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="{{tableHeaders[tableID].length}}" class="text-center">
<div st-pagination="" st-items-by-page="10" st-displayed-pages="7"></div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
The problem we're coming across is that no actual data is displayed in the tables. Somewhere between the starred lines (***) in the JS above, an empty array for each table is added. So if we have two tables in our database, we end up with documentsList = Array[4] with documentsList[0] and documentsList[1] being empty arrays. Why/where would this be happening, and how can I fix it?
Note - if you'd like to test this out without our endpoints, try out these variables:
$scope.count = 2;
$scope.tableHeaders = [["TabNo", "Document", "PageNumber", "Date"],["TabNo", "Document", "PageNumber"]];
$scope.documentsList = [];
$scope.documentsList.push(JSON.parse('[{"idANX":1,"pathANX":"Product Overview.pdf","isHeaderANX":1,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Product Overview.pdf","isLastCell":false,"value":"Product Overview"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1 - 2"},"Date":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":true,"value":"18 August 2015"}},{"idANX":2,"pathANX":"Spec.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"2"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Spec.pdf","isLastCell":false,"value":"Spec"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"3 - 4"},"Date":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":true,"value":"1 April 2015"}}]');
$scope.documentsList.push(JSON.parse('[{"idANX":1,"pathANX":"Product Overview.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Product Overview.pdf","isLastCell":false,"value":"Product Overview"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1 - 2"}},{"idANX":2,"pathANX":"Spec.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"2"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Spec.pdf","isLastCell":false,"value":"Spec"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"3 - 4"}}]');
$scope.getNumberArray = function (num) {
var n = new Array(num);
var i;
for (i = 0; i < num; i++) {
n[i] = i;
}
return n;
}
$scope.rowClass = function (row) {
if (row.isSubHeaderANX == 1) {
return 'subheader';
} else if (row.isHeaderANX == 1) {
return 'header';
}
else { return 'doctablerow'; }
}
Also pagination and filtering don't work but that's an issue for when our table actually contains data.
Edit: Probably should have mentioned that this is using angular-smart-table
You should take a look at how asynchronous requests and promises works. To make your code run you could do this: var promises = []; // Creates an array to store the promises
$http.get('http://localhost:3000/tableCount').then(function (res) {
if (res.data.numberoftables) {
$scope.count = res.data.numberoftables;
var i;
$scope.documentsList = new Array();
$scope.tableHeaders = new Array();
var promises = [];
for (i = 1; i <= $scope.count; i++) {
var tableNo = i;
$scope.getHttp(i).then(function(resp){
$scope.documentsList.push(resolutions[0]);
$scope.tableHeaders.push(resolutions[1]);
});
}
}
});
$scope.getHttp = function(tableNo){
var promise;
promise = $http({
url:'http://localhost:3000/doclist/' + tableNo,
method:'get'
});
promises.push(promise);
promise = $http({
url:'http://localhost:3000/tableColumns/' + tableNo,
method:'get'
});
promises.push(promise);
return $q.all(promises);
}
Make the changes accordingly. I did not test it because dont have complete controller.But for sure, if you are looping http, make sure promises are stored.
Related
How to Loop through a Table of values in AngularJS
I have a table that is being populated from database through an angularJS array. <table class="table table-striped table-bordered"> <thead> <tr> <th>Code</th> <th>Description</th> <th>Budget Amount</th> <th>Actual Amount</th> </tr> </thead> <tbody> <tr ng-repeat="lineItem in vm.budget.budgetLines"> <td> {{lineItem.code}} </td> <td>#{{lineItem.description}}</td> <td>{{lineItem.budgetAmount | currency}}</td> <td><input type="number" class="form-control" ng-model="lineItem.actualAmount" required /></td> </tr> </tbody> </table> The data population works fine. But if you look at the table, you would notice an input field in the last column. I need to be able to perform an update with changes made to the input for which I need just the 1st and last column for the operation. I used the following approach but it didn't work because it just used the original values with which the table was first populated. $scope.lineItems = []; for (var i = 0; i < vm.budget.budgetLines.length; i++) { var lineItem = vm.budget.budgetLines[i]; $scope.lineItems.push({ 'code': lineItem.code, 'actualAmount': lineItem.actualAmount }); } So I thought of using pure javascript inside my angular controller to loop through the table rows and push the columns I need in the array like so; var myTable = document.getElementById("tblValues"); var current, cell; for (var i = 0; i < myTable.rows.length ; i++) { var rowItem = myTable.rows[i]; $scope.lineItems.push({ 'code': rowItem.cells[1], 'actualAmount': rowItem.cells[4].children[0].value }); } But an error is thrown in the console: rowItem.cells[4].children[0] is undefined Please how can I get this to work just the way I want it?
I got it to work just the way I desired it to. var myTable = document.getElementById("tblValues"); var current, cell; $scope.lineItems.length = 0; for (var i = 0; i < myTable.rows.length ; i++) { var rowItem = myTable.rows[i + 1]; $scope.lineItems.push({ 'code': rowItem.cells[1].innerText, 'actualAmount': rowItem.cells[4].firstChild.value }); }
How to update data from database with api call in angular
I have a api call who give me the list of data, and I am iterating data via ng-repeat (its a list of more than 100 items) For getting list of data I have call an Api in App Controller in angularjs like this: var path = serverUrl + 'api/getAllMails'; $http.get(path).then(function (result) { $scope.mails=result }) For Iterating the mails in Html file i have use table like the below <table> <tr class="header"> <th class="center">Id</th> <th class="center">Mode of Payment</th> <th class="center">Payment Collected</th> <th class="center">Status</th> </tr> <tr ng-repeat="mail in mails"> <td>{{mail.id}}</td> <td>{{mail.paymentType}}</td> <td>Rs. {{mail.cost}} <input type="text" ng-model="mail.cost"> <button ng-click="updateCost=(mail.id, mail.cost)">Update Cost</button> </td> <td>{{mail.status}} <input type="text" ng-model="mail.status"> <button ng-click="updateStatus(mail.id, mail.status)">Update Status</button> </td> </tr> </table> Suppose in the first iterations the cost will be "100" and the status will be "pending". And I have to update this row only, change cost to "1000" and status will be "Delivered". In my App controller of Angularjs I have create methods. These two methods are calling apis and updating data in database and return the list of updated mails. $scope.updateStatus = function(mailId, mailStatus) { var path = serverUrl + 'api/updateStatus'; $http.get(path, { params: { mailId: mailId, mailStatus: mailStatus } }).then(function(result) { $scope.mails = result }) } $scope.updateCost = function(mailId, mailCost) { var path = serverUrl + 'api/updateStatus'; $http.get(path, { params: { mailId: mailId, mailCost: mailCost } }).then(function(result) { $scope.mails = result }) } These code are working fine but while it took lot of time to load a page. So what can I do to reduce the loading time or is there any better way to do the same thing. Any help will be appreciable. Thank you
You are replacing the entire dataset when there is no reason for that, you should only update the row you change. Ensure your updateStatus return the object you update and update that item in $scope.mails In example $scope.updateCost = function(mailId, mailCost) { var path = serverUrl + 'api/updateStatus'; $http.get(path, { params: { mailId: mailId, mailStatus: mailCost } }).then(function(result) { // result is the item you changed for (var i = $scope.mails.length - 1; i >= 0; i--) { if($scope.mails[i].id === mailId) { $scope.mails[i] = result; return; } }; }) }
AngularJS orderBy Function With No Sorting
I am attempting to use a custom orderBy function. Initially, I want the data to appear in the order it was added to $scope.rows, and only after clicking on a column heading should it order by a specific property. Here's my fiddle: http://jsfiddle.net/S8M4c/ Here's my view: <table ng-app ng-controller="ctrl"> <tr> <th><a ng-click="orderBy = 'id'">ID</a></th> <th><a ng-click="orderBy = 'name'">Name</a></th> </tr> <tr ng-repeat="row in rows | orderBy:mySort"> <td>{{row.object.id}}</td> <td>{{row.object.name}}</td> </tr> </table> Here's my controller: function ctrl($scope) { // Initially, we don't sort by anything $scope.orderBy = ""; $scope.rows = []; // Add some rows for(var i = 10;i < 30;i++) { $scope.rows.push({settings: {foo: true}, object: {id: i, name: "Name " + i}}) }; $scope.mySort = function(row) { if($scope.orderBy != "") { return row.object[$scope.orderBy]; } // What do I return here?? return ""; } } In the case that $scope.orderBy isn't set and I want to return $scope.rows in it's original order, what do I return in $scope.mySort? I cannot return row.object.id because the rows are not guaranteed to be added in order of their ID. Running my code as is on Chrome 32, the first row that appears has an ID of 20, which is the halfway row.
return $scope.rows.indexOf(row); (Fiddle.) You can also do this with out-of-the-box orderBy by providing a function returning that as the default predicate: Controller: $scope.mySort = $scope.unsorted = function(row) { return $scope.rows.indexOf(row); } View: <div ng-app ng-controller="ctrl"> <table> <tr> <th><a ng-click="mySort = 'object.id'">ID</a></th> <th><a ng-click="mySort = 'object.name'">Name</a></th> </tr> <tr ng-repeat="row in rows | orderBy:mySort"> <td>{{row.object.id}}</td> <td>{{row.object.name}}</td> </tr> </table> <button ng-click="mySort = unsorted;">Original Sort</button> </div> Fiddle here. (I've changed the numbers used in the objects so that sort by id, sort by name, and the original sort aren't all the same.)
I think you have to write your own sortby function. The original angulars orderBy is a regular filter that returns the sorted array. Your filter may look something like this: .filter('mySort', function(){ return function(values, param){ if(param===''){ return values; }else{ // very important! create a copy of the array - otherwise // the $wtachCollection function will fire to often! var arrayCopy = []; for ( var i = 0; i < values.length; i++) { arrayCopy.push(values[i]); } return arrayCopy.sort(function(a,b){ var v1 = a.object[param]; var v2 = b.object[param]; // you know best how to sort it! if (v1 === v2) return 0; return v1 < v2 ? -1 : 1; }); } } }) You can use this filter in this way: <table ng-app="myApp" ng-controller="ctrl"> <tr> <th><a ng-click="orderBy = 'id'">ID</a></th> <th><a ng-click="orderBy = 'name'">Name</a></th> </tr> <tr ng-repeat="row in rows | mySort:orderBy"> <td>{{row.object.id}}</td> <td>{{row.object.name}}</td> </tr> </table> here is your modified fiddle: http://jsfiddle.net/spRf6/ I have changed the names a little bit so you may see that the sorting works.
Create a copy of the objects array and the ordering then becomes trivial: Controller: $scope.objects = []; angular.forEach($scope.rows, function(row){ $scope.objects.push(row.object); }); View: <tr ng-repeat="object in objects | orderBy:orderBy"> <td>{{object.id}}</td> <td>{{object.name}}</td> </tr> No need for the mySort function.
Find table data store in within an array of arrays
I need to get data from a table and store it in an array. Each row of the table should be a new array within an array. Basically the html looks like this: <table id="contactlisttable"> <tr> <th>Name</th> <th>Title</th> <th>Phone</th> </tr> <tr> <td class="contactlist contactlistlastfirst">Joey</td> <td class="contactlist contactlisttitle">webdesigner</td> <td class="contactlist contactlistphone">5555555</td> </tr> <tr> <td class="contactlist contactlistlastfirst">Anthony</td> <td class="contactlist contactlisttitle">webdesigner</td> <td class="contactlist contactlistphone">5555555</td> </tr> </table> ect... Here is my code jQuery(document).ready(function(){ $(function(){ var $table = $("#contactlisttable"), $headerCells = $table.find("tr th"), $myrows = $table.find("tr"); // Changed this to loop through rows var headers = [], rows = []; $headerCells.each(function() { headers[headers.length] = $(this).text(); }); $myrows.each(function() { $mycells = $myrows.find( "td.contactlist" ); // loop through cells of each row cells = [] $mycells.each(function() { cells.push($(this).text()); }); if ( cells.length > 0 ) { rows.push(cells); } }); console.log(headers); console.log(rows); }); }); my current code out puts [["Waddell, Joey", "webdesigner", "", 15 more...], ["Waddell, Joey", "webdesigner", "", 15 more...], the desired output would be: ["Name","Title","Phone"] [["Joey","webdesigner","555555"] ["Anthony","webdesigner","555555"]]
I think this can be simpler: Live Demo JS $(function(){ var results = []; var row = -1; $('#contactlisttable').find('th, td').each(function(i, val){ if(i % 3 === 0){ //New Row? results.push([]); row++;//Increment the row counter } results[row].push(this.textContent || this.innerText); //Add the values (textContent is standard while innerText is not) }); console.log(results); }); EDIT Here's an even better solution (IE9+ compatible). It accounts for variable row lengths unlike my previous solution. Live Demo JS //IE9+ compatable solution $(function(){ var results = [], row; $('#contactlisttable').find('th, td').each(function(){ if(!this.previousElementSibling){ //New Row? row = []; results.push(row); } row.push(this.textContent || this.innerText); //Add the values (textContent is standard while innerText is not) }); console.log(results); });
Populate 2 tables with 2 sets of data when 1 field matches in both sets
I'm using Josh Fraser's Backwards compatible window.postMessage() (http://www.onlineaspect.com/2010/01/15/backwards-compatible-postmessage) and I'm having trouble. I have 2 iframes on a page and both iframes send different data to the same parent page. In other words, there are 2 XD.receivemessage functions getting 2 different messages from 2 different iframes. Here's a shortened version of my code: <iframe id="IFRAME1" src="https://www.DOMAIN.com/PAGENAMEFIRST.php"></iframe> <iframe id="IFRAME2" src="https://www.DOMAIN.com/PAGENAMESECOND.php"></iframe> <div id="firstTable"> <table> <tr> <th>Name</th> <th>Address</th> <th>ID</th> </tr> <tr> <td><!--this will be filled in by the data--></td> <td><!--this will be filled in by the data--></td> <td><!--this will be filled in by the data--></td> </tr> </table> </div> <div id="secondTable"> <table> <tr> <th>Email</th> <th>Twitter Handle</th> <th>ID</th> </tr> <tr> <td><!--this will be filled in by the data--></td> <td><!--this will be filled in by the data--></td> <td><!--this will be filled in by the data--></td> </tr> </table> </div> <script type="text/javascript"> $(document).ready(function() { //FIRST DATA PULL var encodeUriFirst = encodeURIComponent(document.location.href); var getMessageFirst = 'https://www.DOMAIN.com/PAGENAMEFIRST.php#' + encodeUriFirst; $("#IFRAME1").attr('src',getMessageFirst); XD.receiveMessage(function(getMessageFirst){ var getDataFirst = getMessageFirst.data; var getDataFirstEach = new Array(); for(var a=0; a<getDataFirst.length; a++){ getDataFirstEach = getDataFirst.split("~,~"); $('div#firstTable table td:nth-child(0)').text(getDataFirstEach[0].replace(/[~]/g,"")); } }, 'https://www.DOMAIN.com'); //SECOND DATA PULL var encodeUriSecond = encodeURIComponent(document.location.href); var getMessageSecond = '//www.DOMAIN.com/PAGENAMESECOND.php#' + encodeUriSecond; $("#IFRAME2").attr('src',getMessageSecond); XD.receiveMessage(function(getMessageSecond){ var getDataSecond = getMessageSecond.data; var getDataSecondEach = new Array(); for(var a=0; a<getDataFirst.length; a++){ getDataSecondEach = getDataSecond.split("~,~"); $('div#secondTable table td:nth-child(0)').text(getDataSecondEach[0].replace(/[~]/g,"")); } }, 'https://www.DOMAIN.com'); }); </script> Please keep in mind that I've scaled back the code significantly. Each getDataFirst or getDataSecond It actually works as far as getting the data from both iframes (send/receive both work). My data comes through in the format ~name~,~address~,~ID~ for the first set of data and ~email~,~twitter~,~ID~ for the second set. I'm trying to get the data to populate the 2 tables. The For loops look for the ID in the table and if the ID matches, it fills in the other fields associated with that ID. In some cases, the ID from the first data set will be the same as the ID from the second data set. When that happens, the for loops place the data in both tables instead of the appropriate one. I'm wondering why this would happen since I'm specifically targeting the table inside either $('div#firstTable') or $('div#secondTable') for the output. For example: $('div#firstTable table td:nth-child(0)').text(getDataFirstEach[0]); inside the For loop is placing getDataFirstEach[0] into both the firstTable and secondTable tables? Can anyone tell me why? AS REQUESTED, HERE IS THE FULL JS: var encodeUriGifts = encodeURIComponent(document.location.href); var giftsMessage = '//www.DOMAIN.org/firstpage.php#' + encodeUriGifts; $("#IFRAME1").attr('src',giftsMessage); XD.receiveMessage(function(giftsMessage){ var getDataGifts = giftsMessage.data; var currentRecordGifts = new Array(); var getGiftsLines = getDataGifts.split("|"); //LINES OF DATA ARE PIPE SEPARATED for(var bCnt=0;bCnt < getGiftsLines.length;bCnt++){ var getGiftsEach = getGiftsLines[bCnt].split("`,`"); //EACH ITEM IS SEPARATED BY `,` for(var cCnt=0;cCnt < getGiftsEach.length;cCnt++){ if (getGiftsEach[cCnt]!=="" && getGiftsEach[cCnt]!=="undefined"){ currentRecordGifts[bCnt] = " <td class='giftamount'>"+getGiftsEach[1]+"</td><td class='giftdate'>"+getGiftsEach[2].replace(/[`]/g,"")+"</td>"; } if (cCnt==2) { var thisGiftsID = getGiftsEach[0].replace(/[`]/g,""); $('#firstTable table td:contains("'+thisGiftsID+'")').closest('tr').find('td:nth-child(3)').after(currentRecordGifts[bCnt]); } } if (bCnt==0){ $('#firstTable table th:nth-child(3)').after('<th class="BBListingHeading DirectoryListingHeading" scope="col" style="white-space: nowrap;">Last Gift Amount</th><th class="BBListingHeading DirectoryListingHeading" scope="col" style="white-space: nowrap;">Last Gift Date</th>'); } } }, 'https://www.DOMAIN.org'); var encodeUriChanges = encodeURIComponent(document.location.href); var changesMessage = '//www.DOMAIN.org/secondpage.php#' + encodeUriChanges; $("#IFRAME2").attr('src',changesMessage); XD.receiveMessage(function(changesMessage){ var getDataChanges = changesMessage.data; var currentRecordChanges = new Array(); var getChangesLines = getDataChanges.split("|"); $('#secondTable table tr td:nth-child(3)').each( function() { $('#secondTable table tr td:nth-child(3)').after('<td class="BBListingItem DirectoryListingItem" style="white-space: nowrap;"><ul class="changes"></ul></td>'); }); for(var dCnt=0;dCnt < getChangesLines.length;dCnt++){ var getChangesEach = getChangesLines[dCnt].split("`,`"); for(var eCnt=0;eCnt < getChangesEach.length;eCnt++){ if (getChangesEach[eCnt]!=="" && getChangesEach[eCnt]!=="undefined"){ currentRecordChanges[dCnt] = "<li class='change'>"+getChangesEach[2]+" ("+getChangesEach[1].replace(/[`]/g,"")+")</li>"; } if (eCnt==2) { var thisChangesID = getChangesEach[0].replace(/[`]/g,""); $('#secondTable table td:contains("'+thisChangesID+'")').closest('tr').find('td ul.changes').append(currentRecordChanges[dCnt]); } if (dCnt==0){ $('#changesdiv .DirectoryListingTable th:nth-child(3)').after('<th class="BBListingHeading DirectoryListingHeading" scope="col" style="white-space: nowrap;">Change Details</th>'); } } } }, 'https://www.DOMAIN.org');