I have created the following website:
http://expenseappusingangular.azurewebsites.net/
I have the following code for the spending details table at the very bottom of the page:
<section id="Details">
<h2>Spending Details</h2>
<br />
<table class="table table-hover">
<thead>
<tr>
<th>Date</th>
<th>Expense</th>
<th>Category</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="expense in expenses | orderBy: 'date'">
<td>{{ expense.date }}</td>
<td>{{ expense.text }}</td>
<td>{{ expense.category }}</td>
<td>{{ expense.amount > 0 ? '$' : ''}}{{ expense.amount }}</td>
</tr>
</tbody>
</table>
</section>
function ExpenseController($scope) {
$scope.appTitle = "Expense App using Angular";
$scope.saved = localStorage.getItem('expenses');
$scope.expenses = (localStorage.getItem('expenses') !== null) ? JSON.parse($scope.saved) : [];
localStorage.setItem('expenses', JSON.stringify($scope.expenses));
$scope.addExpense = function () {
$scope.expenses.push({
text: $scope.expenseText,
category: $scope.categoryText,
amount: $scope.amountText,
date: $scope.dateText
});
$scope.expenseText = '';
$scope.categoryText = '';
$scope.amountText = '';
$scope.dateText = '';
localStorage.setItem('expenses', JSON.stringify($scope.expenses));
};
$scope.countExpenses = function () {
var count = 0;
angular.forEach($scope.expenses, function (expense) {
count += 1;
});
return count;
};
$scope.totalAmountByCategory = function (category) {
var sum = 0;
angular.forEach($scope.expenses, function (expense) {
sum += expense.category == category ? parseFloat(expense.amount) : 0;
});
return sum;
};
$scope.totalAmount = function () {
var sum = 0;
angular.forEach($scope.expenses, function (expense) {
sum += expense.amount > 0 ? parseFloat(expense.amount) : 0;
});
return sum;
};}
After I enter in an expense or two, wait a few minutes, and then refresh the page, the data in the table at the bottom disappears but is still reflected in the category table and the chart. I checked the local storage in the browser, and the expenses object is still there.
Do you have any idea why the values are disappearing from this bottom table?
Thanks!
Related
I have a table in html, I have set for each td an id that I will need to sort the table with a Jquery code.
Sorting works with the FireFox browser, but with Chrome it does not work ... do you know how to help me?
$(function() {
$(".table-user-th").click(function() {
var o = $(this).hasClass('asc') ? 'desc' : 'asc';
$('.table-user-th').removeClass('asc').removeClass('desc');
$(this).addClass(o);
var colIndex = $(this).prevAll().length;
var tbod = $(this).closest("table").find("tbody");
var rows = tbod.find("tr");
rows.sort(function(a, b) {
var A = $(a).find("td").eq(colIndex).attr('id');;
var B = $(b).find("td").eq(colIndex).attr('id');;
if (!isNaN(A)) A = Number(A);
if (!isNaN(B)) B = Number(B);
return o == 'asc' ? A > B : B > A;
});
$.each(rows, function(index, ele) {
tbod.append(ele);
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table border="1">
<thead>
<tr>
<th class="table-user-th">Firstname</th>
<th class="table-user-th">Lastname</th>
</tr>
</thead>
<tbody>
<tr>
<td id="Mark">Mark</td>
<td id="Red">Red</td>
</tr>
<tr>
<td id="Nick">Nick</td>
<td id="Sid">Sid</td>
</tr>
<tr>
<td id="Alex">Alex</td>
<td id="Nirv">Nirv</td>
</tr>
</tbody>
</table>
It seems like there is no purpose of using ids here.
Actually the problem was in you sort function. It should return not just true/false but the numeric difference between two values. As usual it is return -1/0/1
So here I wrote comparator func that does just that. And depending on sort type I just multiply it on -1 or 1.
I've also refactored a little bit your code not to use classes or ids. Using jquery you can use data method that stores data on element by key/value.
$(function() {
function cmp(a,b) {return a < b ? 1 : a > b ? -1 : 0}
$(".sortable-table").on('click', 'th', function() {
var th = $(this);
var colIndex = th.data('column');
if(typeof colIndex === 'undefined') {
return;
}
var sortType = th.data('sort') === 'asc' ? 'desc' : 'asc';
th.data('sort', sortType);
var table = $(this).closest("table");
table.find('thead th').removeClass('asc desc');
th.addClass(sortType);
var tbody = table.find("tbody");
var rows = tbody.find("tr");
rows.sort(function(a, b) {
var A = $(a).find("td").eq(colIndex).text();
var B = $(b).find("td").eq(colIndex).text();
return cmp(A,B) * (sortType === 'asc' ? -1 : 1);
});
$.each(rows, function(index, ele) {
tbody.append(ele);
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table border="1" class="sortable-table">
<thead>
<tr>
<th data-column="0">Firstname</th>
<th data-column="1">Lastname</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mark</td>
<td>Red</td>
</tr>
<tr>
<td>Nick</td>
<td>Sid</td>
</tr>
<tr>
<td>Alex</td>
<td>Nirv</td>
</tr>
</tbody>
</table>
Update
Added
var table = $(this).closest("table");
table.find('thead th').removeClass('asc desc');
th.addClass(sortType);
Issue
For example: totalImprCount returns the same value on all the pages since it returns the sum from the last page only.
Expected result
I would like to display the correct sum for the following for each page:
totalImprCount, totalClickCount, totalMediaSpend, and totalAdvertiserSpend
Pagination used
Angular Utils Pagination:
https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination
In Controller
$scope.pageSize = 20;
// campaignstats is an array of objects
function sum(campaignstats){
var imprCount, clickCount, mediaSpend, advertiserSpend;
imprCount = clickCount = mediaSpend = advertiserSpend = 0;
var statsPerPage = $scope.pageSize;
if (campaignstats) {
for (var stats = 0; stats < campaignstats.length; stats++){
imprCount += parseInt(campaignstats[stats].impr);
clickCount += parseInt(campaignstats[stats].click);
mediaSpend += parseFloat(campaignstats[stats].media_spend);
advertiserSpend += parseFloat(campaignstats[stats].advertiser_spend);
$scope.calcCtr = clickCount/imprCount * 100;
if (stats == statsPerPage - 1) {
$scope.totalImprCount = imprCount;
$scope.totalClickCount = clickCount;
$scope.totalMediaSpend = mediaSpend;
$scope.totalAdvertiserSpend = advertiserSpend;
imprCount = clickCount = mediaSpend = advertiserSpend = 0;
statsPerPage += $scope.pageSize;
}
}
}
}
In View
<tr class="campaign-stats-total-tr">
<td class="campaign-stats-total-td">Total</td>
<td> - </td>
<td>{{ totalImprCount | number }}</td>
<td>{{ totalClickCount | number }}</td>
<td>{{ calcCtr | number:2}}%</td>
<td> - </td>
<td>{{ totalMediaSpend | currency }}</td>
<td>{{ totalAdvertiserSpend | currency }}</td>
</tr>
How about adding ng-init="sumFunction(impr, clickCount, mediaSpend, advertiserSpend)" to your repeat and creating a resetData function on page changes?
I might have understand you, you would like to keep the additions between pages not only from the last page.
If it is that you might have some luck using $rootScope. This will share between your pages a common scope.
I was able to purely implement a grid from a JSON object - AngularJS ng-repeat to populate grid from array. However, due to the nature of the added indices, being able to create a search bar with ng-model and filter:search does not work - it only can search for the first in each table row.
var test= angular.module("app", []);
test.controller("appControl", function($scope, $http) {
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function (response) {
$scope.data = response.records;
}
);
$scope.getFiltered= function(obj, idx){
//Set a property on the item being repeated with its actual index
//return true only for every 1st item in 5 items
return !((obj._index = idx) % 5);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<body ng-app='app' ng-controller='appControl'>
<input type='text' ng-model='search.Country' />
<table>
<tr ng-repeat="work in data | filter:getFiltered | filter:search">
<td>{{work.Country}}</td>
<td>{{data[work._index+1].Country}}</td>
<td>{{data[work._index+2].Country}}</td>
<td>{{data[work._index+3].Country}}</td>
<td>{{data[work._index+4].Country}}</td>
</tr>
</table>
</body>
The length of data may or not cause the table to look like a perfect rectangle.
I'm working on making a function to split up the array and create the grid in JavaScript itself, but I'm still not sure how to filter it via search input.
Second try (with the mentioned function, but no filters at all yet...):
var test= angular.module("app", []);
function createGrid(arr, width) {
newArr = [];
reps = Math.ceil(arr.length/width) * width;
k = 0;
for (var i = 0; i < reps/width; i++) {
newArr[i] = [];
}
for (var i = 0; i < reps/width; i++) {
for (var j = 0; j < width; j++) {
(arr[k]) ? newArr[i][j] = arr[k] : newArr[i][j] = "";
//console.log(i, j, arr[k]);
k++;
}
}
return newArr;
}
test.controller("appControl", function($scope, $http) {
$scope.gridWidth = 4;
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function (response) {
$scope.data = createGrid(Object.keys(response.records).map(function(k) { return response.records[k] }), $scope.gridWidth);
}
);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<body ng-app='app' ng-controller='appControl'>
<input type='text' ng-model='search.Country' />
<table>
<tr ng-repeat="row in data">
<td ng-repeat='work in row'>
{{ work.Country }}
</td>
</tr>
</table>
</body>
You could try something like this:
var test= angular.module("app", []);
test.controller("appControl", function($scope, $http) {
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function (response) {
$scope.data = response.records;
$scope.filteredData= response.records;
}
);
$scope.$watch('search', function () {
var array=[];
for(var i in $scope.data)
{
if($scope.search==undefined || $scope.search.length == 0 || ($scope.data[i].Country!=undefined&&$scope.data[i].Country.toUpperCase().startsWith($scope.search.toUpperCase()))){
array.push($scope.data[i]);
}
}
$scope.filteredData=array;
});
$scope.getFiltered= function(obj, idx){
//Set a property on the item being repeated with its actual index
//return true only for every 1st item in 3 items
return !((obj._index = idx) % 5);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<body ng-app='app' ng-controller='appControl'>
<input type='text' ng-model='search' />
<table>
<tr ng-repeat="work in filteredData | filter:getFiltered | filter:search">
<td>{{work.Country}}</td>
<td ng-show="filteredData[work._index+1]">{{filteredData[work._index+1].Country}}</td>
<td ng-show="filteredData[work._index+2]">{{filteredData[work._index+2].Country}}</td>
<td ng-show="filteredData[work._index+3]">{{filteredData[work._index+3].Country}}</td>
<td ng-show="filteredData[work._index+4]">{{filteredData[work._index+4].Country}}</td>
</tr>
</table>
</body>
You could prefilter the items after a successful Ajax call and every time your model search changes.
Save the previously filtered items into $scope.workers.
Use $scope.$watch to watch for changes in the model search
Use the function searched(data) to filter for the entries that have characters given in the search field using the indexOf method. If the filter is empty, also show every item (typeof $scope.search == 'undefined').
If you want the search be case insensitive, transform searchand the Country .toLowerCase(), when using .indexOf()
Then you will only need one Angular filter $scope.getFiltered(), which makes sure, that the entries are in rows of five.
var test= angular.module("app", []);
test.controller("appControl", function($scope, $http) {
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function (response) {
$scope.data = response.records;
$scope.workers = $scope.searched($scope.data);
}
);
$scope.getFiltered= function(obj, idx){
//Set a property on the item being repeated with its actual index
//return true only for every 1st item in 5 items
return !((obj._index = idx) % 5);
};
$scope.searched = function (data) {
var array = [];
var max = 0;
if (typeof data === 'object') {
max = data.length;
}
for (var i = 0; i < max; i += 1) {
if (typeof $scope.search == 'undefined' || data[i].Country.toLowerCase().indexOf($scope.search.toLowerCase()) != -1) {
array.push(data[i]);
}
}
return array;
};
$scope.$watch('search', function () {
$scope.workers = $scope.searched($scope.data);
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<body ng-app='app' ng-controller='appControl'>
<input type='text' ng-model='search' />
<table>
<tr ng-repeat="work in workers | filter:getFiltered">
<td>{{ work.Country }}</td>
<td>{{ workers[$index+1].Country }}</td>
<td>{{ workers[$index+2].Country }}</td>
<td>{{ workers[$index+3].Country }}</td>
<td>{{ workers[$index+4].Country }}</td>
</tr>
</table>
</body>
I have this angular app, where I display a table based on the records, with filters, but the problem is when I eneter a value in filter the records get filter but after removing filter value, it doesnt get updated, and also pagination keeps when we click next numbers keep reducing, I am new to angularjs any help would be greatly appreciated.
This is my html code:
<input type="search" placeholder="Search By Any..." ng-model="search.$" />
table-striped table-bordered">
<thead>
<tr>
<th>User</th>
<th>Content Type</th>
<th>Content Name</th>
<th>Start Time</th>
<th>End Time</th>
<th>Duration(In Secs)</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in filteredRecords
| filter: search
| offset: currentPage*itemsPerPage
| limitTo: itemsPerPage
| orderBy:sort:reverse track by $index">
<td>{{record.user}}</td>
<td>{{record.contentType}}</td>
<td>{{record.contentName}}</td>
<td>{{record.startTime}}</td>
<td>{{record.endTime}}</td>
<td>{{record.duration}}</td>
</tr>
</tbody>
<tfoot>
<td colspan="6">
<div class="pagination pull-left">
<ul>
<li ng-class="prevPageDisabled()"><a href
ng-click="prevPage()">« Prev</a></li>
<li ng-repeat="n in range()"
ng-class="{active: n == currentPage}" ng-click="setPage(n)">
{{n+1}}
</li>
<li ng-class="nextPageDisabled()"><a href
ng-click="nextPage()">Next »</a></li>
</ul>
</div>
</td>
</tfoot>
This is angular code:
angular.module("contentViewStatusApp")
.controller("contentViewStatusController", function($scope,
$filter,contentViewStatusService)
{
var records = contentViewStatusService.list();
$scope.changeSort = function(value)
{
if ($scope.sort == value)
{
$scope.reverse = !$scope.reverse;
return;
}
$scope.sort = value;
$scope.reverse = false;
}
$scope.itemsPerPage = 8;
$scope.currentPage = 0;
$scope.filteredRecords = [];
$scope.items = [];
$scope.range = function()
{
var rangeSize = 5;
var ret = [];
var start;
start = $scope.currentPage;
if (start > $scope.pageCount() - rangeSize && $scope.pageCount() > rangeSize)
{
start = $scope.pageCount() - rangeSize + 1;
}
if($scope.pageCount() > rangeSize)
for (var i = start; i < start + rangeSize; i++)
{
ret.push(i);
}
else
for (var i = start; i < $scope.pageCount()+1; i++)
{
ret.push(i);
}
return ret;
};
var filterBy = $filter('filter');
$scope.$watch('search', function(newValue)
{
$scope.filteredRecords = filterBy(records, newValue);
}, true);
$scope.prevPage = function()
{
if ($scope.currentPage > 0)
{
$scope.currentPage--;
}
};
$scope.prevPageDisabled = function()
{
return $scope.currentPage === 0 ? "disabled" : "";
};
$scope.pageCount = function()
{
return Math.ceil($scope.filteredRecords.length / $scope.itemsPerPage) - 1;
};
$scope.nextPage = function()
{
if ($scope.currentPage < $scope.pageCount())
{
$scope.currentPage++;
}
};
$scope.nextPageDisabled = function()
{
return $scope.currentPage === $scope.pageCount() ? "disabled" : "";
};
$scope.setPage = function(n)
{
$scope.currentPage = n;
};
});
#Mckenzie, for your scenario already plugin available where you can get more out of it.
Check out this plugin: ng-table ( https://github.com/esvit/ng-table )
See this example it does all your job that you require.
ng-table combining sorting and filtering
I have knockout binding on table with columns. I was trying to achieve table sorting for each column.
The view looks like:
<table id="notes" class="notes_table">
<tr class="head">
<th data-bind='click: function() { SortItems("CreatedDate")}'>
<span>Date</span>
</th>
<th data-bind='click: function() { SortItems("Type")}'>
<span>Type</span>
</th>
<th data-bind='click: function() { SortItems("Category")}'>
<span>Category</span>
</th>
<th data-bind='click: function() {SortItems("AddedBy")}'>
<span>Added by</span>
</th>
</tr>
<tbody data-bind="template: { name: 'StudentNote', foreach: notes }"></tbody>
</table>
<script type="text/html" id="StudentNote">
<tr class="even">
<td><span data-bind="text: CreatedDate"></span></td>
<td><span data-bind="text: Type"></span></td>
<td><span data-bind="text: Category"></span></td>
<td><span data-bind="text: AddedBy"></span></td>
</tr>
</script>
and the javascript is like:
function notesViewModel() {
var _this = {};
_this.colName = "CreatedDate";
_this.sortOrder = "desc";
_this.notes = ko.observableArray();
_this.SortItems = function (ColumnName) {
var newNotes = _this.notes();
if (_this.sortOrder === "desc") {
this.notes(newNotes.sort(notesViewModel._getSortFunction = function (a, b) {
_this.sortOrder = "asc";
return a[ColumnName] < b[ColumnName] ? -1 : 1;
}));
} else {
this.notes(newNotes.sort(notesViewModel._getSortFunction = function (a, b) {
_this.sortOrder = "desc";
return a[ColumnName] > b[ColumnName] ? -1 : 1;
}));
}
};
ko.applyBindings(_this, $("body").get(0));
return _this;
Even though it does sorting, it just switches between ascending and descending sort on each of the column, but not recognises which column it is sorting.. How to do sorting by each column..
Try this:
function notesViewModel() {
var _this = {};
_this.colName = "CreatedDate";
_this.sortOrder = 1;
_this.notes = ko.observableArray();
_this.SortItems = function (ColumnName) {
if(ColumnName == _this.colName)
_this.sortOrder = _this.sortOrder * -1;
else
_this.colName = ColumnName;
_this.notes.sort(function (a, b) {
return (a[ColumnName] < b[ColumnName] ? -1 : 1) * _this.sortOrder;
});
};
ko.applyBindings(_this, $("body").get(0));
return _this;
}