I'm trying to use Angular-ui pagination directive for the first time and am confused why it isn't working. I can see the pagination buttons and it properly displays two pages to paginate through since there are 8 results and items-per-page="5" But all my data items are showing and not being hidden to five per page.
controller
dataService.get(uri).then(function (data) {
$scope.testimonials = data;
$scope.totalItems = $scope.testimonials.length;
$scope.currentPage = 1;
$scope.setPage = function(pageNo) {
$scope.currentPage = pageNo;
};
$scope.pageChanged = function() {
console.log('Page changed to: ' + $scope.currentPage);
}
});
view
<table class="table table-striped" ng-show="testimonials.length">
<thead>
<th>Name</th>
<th>Message</th>
</thead>
<tbody>
<tr ng-repeat="testimonial in testimonials">
<td>{{testimonial.name}}</td>
<td>{{testimonial.message}}</td>
<td>Edit</td>
<td><button class="btn btn-danger" ng-click="delete(testimonial)">Delete</button></td>
</tr>
</tbody>
<pagination total-items="totalItems" ng-model="currentPage" items-per-page="5" ng-change="pageChanged()"></pagination>
</table>
I appreciate any advice, thanks!
Yo need filter data in your ng-reapeter code below should works
<table class="table table-striped" ng-show="testimonials.length">
<thead>
<th>Name</th>
<th>Message</th>
</thead>
<tbody>
<tr ng-repeat="testimonial in testimonials | startFrom: (currentPage-1)*5| limitTo: 5">
<td>{{testimonial.name}}</td>
<td>{{testimonial.message}}</td>
<td>Edit</td>
<td><button class="btn btn-danger" ng-click="delete(testimonial)">Delete</button></td>
</tr>
</tbody>
<pagination total-items="totalItems" ng-model="currentPage" items-per-page="5" ng-change="pageChanged()"></pagination>
</table>
filter starts from:
app.filter('startFrom', function () {
return function (input, start) {
if (input === undefined || input === null || input.length === 0
|| start === undefined || start === null || start.length === 0 || start === NaN) return [];
start = +start; //parse to int
try {
var result = input.slice(start);
return result;
} catch (e) {
// alert(input);
}
}
});
I can't find the original example I used, but this is what I have in my app.
The filter part isn't important, but the filterProducts object is what gets sliced and shown in your view. Check out the $watch for how it works.
app.controller('ProductController', function($scope, $filter, $routeParams, $rootScope, $location, Products){
$scope.Products = Products;
Products.brandLimit = $routeParams.brandLimit;
Products.brand = $routeParams.brand;
// try to avoid reloading the prod data
if (!Products.products){
Products.getProducts().then(function(data){
Products.products = data.products;
Products.pagination();
});
}else{
Products.pagination();
}
// watch for when the pagination changes
$scope.$watch('Products.currentPage + Products.numPerPage', function() {
var begin = ((Products.currentPage - 1) * Products.numPerPage);
var end = begin + Products.numPerPage;
Products.pagedProducts = Products.filteredProducts.slice(begin, end);
});
});
And in the service:
app.factory('Products', function($http, $filter, $location, $routeParams){
var Products = {
search: '',
searching: false,
filteredProducts: '',
pagedProducts: '',
getProduct: function(id){
delete Products.detail;
$http.get('/product/' + id).then(function(response){
Products.detail = response.data.product;
});
},
getProducts: function(){
return $http.get('/product').then(function(response){
return response.data;
});
},
pagination: function(){
// relies on fulltext filter
this.filteredProducts = $filter('fulltext')(this.products, this.brandLimit);
// set up default values to feed to ui pagination
this.currentPage = 1;
this.numPerPage = 10;
this.maxSize = 10;
// check the length of filtered items based on search or brand clicked (in the URL)
this.totalItems = this.filteredProducts.length;
this.numPages = Math.ceil(this.totalItems / this.numPerPage);
},
brandTitle: function() {
if (this.searching === false || this.brand) {
this.search = '';
return $routeParams.brand + " Products";
} else {
return 'Searching "' + $routeParams.brandLimit + '"';
}
},
searchTerm: function(){
if(this.search){
$location.path("search/" + this.search);
this.searching = true;
}else{
$location.path("/");
this.searching = false;
}
}
};
return Products;
});
And HTML:
<pagination ng-show="Products.numPages" total-items="Products.totalItems" ng-model="Products.currentPage" max-size="Products.maxSize" class="pagination-small" boundary-links="true" rotate="false" num-pages="Products.numPages"></pagination>
<table class="table table-striped">
<tr>
<th>Maker</th>
<th>Title</th>
<th ng-bind="product.priceDealer">Dealer Price</th>
<th>MSRP</th>
</tr>
<tr ng-repeat="product in Products.pagedProducts">
<td>{{product.brand}}</td>
<td>{{product.title}}</td>
<td ng-bind="product.priceDealer | currency"></td>
<td>{{product.msrp | currency:"$"}}<td>
</tr>
</table>
No Need of all that, use attribute of angular UI Bootstrap:
HTML
<pager total-items="totalItems" ng-model="currentPage" items-per-page="itemsPerPage"></pager>
====
and add below code in your controller
===========
$scope.totalItems = $scope.testimonials.length;
$scope.itemsPerPage = 5;
$scope.currentPage = 1;
$scope.$watch('currentPage + itemsPerPage', function () {
var begin = (($scope.currentPage - 1) * $scope.itemsPerPage),
end = begin + $scope.itemsPerPage;
$scope.filteredtestimonials= $scope.alerts.slice(begin, end);
});
===========
Note that you need to mention ng-repeat="testimonial in filteredtestimonials"
and attribute should be in same scope of where you have used you used ng-repeat
Please let me know if you still face any issue and you can see more examples of this on: http://angular-ui.github.io/bootstrap/#/pagination
Do include :
in your page or layout page and items-per-page will not take direct value, it seems
Related
I am trying to load data while the user scrolls down the table on the when-scrolled event but that didn't occur during scroll
i have to implement paging mechanism in my following code
stepwise description
when first-time page load then some data will load
then when user scroll down the next batch of data will load from database and it will continue till the user scroll up
app.service("UserService", function ($http) {
this.getAllUserList = function () {
return $http.get("/FarmerRegistermaster/getAllUser");
};
});
app.directive('whenScrolled', function () {
return function (scope, elm, attr) {
var raw = elm[0];
//alert(attr);
elm.bind('scroll', function () {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
scope.$apply(attr.whenScrolled);
}
});
};
});
app.controller('UserCtrl', function ($scope, UserService) {
//GetAll();
$scope.loaderMore = false;
$scope.scrollended = false;
var IsPaging = 0;
var UserID = 1;
var page = 1;
var PageSize = 20;
var inCallback = false;
var totalData = 0;
//******=========Get All Data with Paging=========******
$scope.loadData = function (IsPaging) {
alert("When Scrolled " + IsPaging);
var geturl = '/FarmerRegistermaster/getAllUser';
if (IsPaging === 1) {
//debugger;
IsPaging = 1;
if (page > -1 && !inCallback) {
inCallback = true;
page++;
$scope.loaderMore = true;
$scope.LoadMessage = ' Loading page ' + page + ' of ' + PageSize + ' data...';
$scope.result = "color-green";
var getUser = UserService.getAllUserList();
getUser.then(function (response) {
$scope.UserList = response.data;
}, function () {
$.notify("Error to load data...", "error");
});
var getUser = UserService.getAllUserList();
getUser.then(function (response) {
$scope.UserList.push(response.data);
}, function () {
$.notify("Error to load data...", "error");
});
}
} //unlimited load data while scroll
else {
//debugger;
IsPaging = 0;
$scope.loaderMore = true;
$scope.LoadMessage = 'Loading page ' + page + ' of ' + PageSize + ' data...';
$scope.result = "color-green";
var getUser = UserService.getAllUserList();
getUser.then(function (response) {
$scope.UserList = response.data;
}, function () {
$.notify("Error to load data...", "error");
});
} //default load data while pageload
};
$scope.loadData();
});
<div ng-controller="UserCtrl" class="panel panel-headline">
<section class="panel">
<header class="panel-heading">
<center><b>User List</b></center>
</header>
<div class="panel-body">
<div class="box box-primary box-body table-responsive widget-content content_scroll"
when-scrolled="loadData(1)">
<table class=" table table-striped table-bordered " cellspacing=" 0">
<thead>
<tr>
<th>ID</th>
<th>USER NAME</th>
<th>MOBILE NO</th>
<th>EMAIL</th>
<th>REG DATE</th>
<th>STATUS</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in UserList >
<td>{{user.FARMER_ID}}</td>
<td>{{user.FARMER_NAME}}</td>
<td>{{user.MOBILE_NO}}</td>
<td>{{user.EMAIL_ID}}</td>
<td>{{user.REG_DATE | jsonDate}}</td>
<td>
{{user.STATUS}}
</td>
<td>
</td>
</tr>
</tbody>
</table>
<div class="loadmore">
<div ng-show="loaderMore" ng-class="result">
<imgsrc ="~/Areas/Crm/Content/img/ng-loader.gif" />{{LoadMessage}}
</div>
<div ng-show="scrollended" ng-class="result">
{{LoadMessage}}
</div>
</div>
</div>
</div>
</section>
</div>
<link href="//cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap.min.css" rel="stylesheet" />
#section Scripts{
<script src="//cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js"></script>
<script src="//cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js"></script>
<script src="~/AngularJS/DateFilter.js"></script>
<script src="~/AngularJS/UserCtrl.js"></script>
}
<script>
$(document).ready(function () {
$('#dataTable').DataTable({
"paging": true,
"ordering": true,
"info": true
});
});
</script>
when the user scrolled then the reloadData method should be called every time and data is to be fetched from the database
I've created a component which is being used to add "data grid" functionality to HTML tables. The headers are clickable to allow sorting of the data (ascending/descending) on that column. So far it's working great unless I have two instances of the component on the same page. When I click a header in one table, it affects both tables.
Is there a way I'm missing to isolate the component's events to only affect that instance?
Component:
angular.module('app')
.component('datagrid', {
templateUrl:'components/datagrids/datagrids.component.html',
controller:DatagridController,
})
Controller (Work in progress, I know It's a bit of a mess at the moment!):
function DatagridController($filter, datagridService){
var ctrl = this;
ctrl.today = new Date();
ctrl.sortBy = null;
ctrl.fields = [];
ctrl.data = [];
ctrl.update = function(){
var service = datagridService;
console.log(datagridService);
var updatedFields = [];
console.log(datagridService.fields);
for(var i = 0; i < datagridService.fields.length; i++){
var fieldName = datagridService.fields[i];
var fieldDirection = (ctrl.fields.length === 0) ? 'ascending' : ctrl.fields[i].direction;
updatedFields.push({name:fieldName, direction:fieldDirection});
}
ctrl.fields = updatedFields;
console.log(ctrl.fields)
if (ctrl.sortBy == null){ ctrl.sortBy = $filter('toCamelCase')(ctrl.fields[0].name); }
ctrl.data = datagridService.data.sort(ctrl.sortData(ctrl.sortBy));
ctrl.today = new Date();
};
ctrl.sortData = function(field, reverse, primer){
console.log(field + ' | ' + reverse)
var key = primer ?
function(x) {return primer(x[field])} :
function(x) {return x[field]};
reverse = !reverse ? 1 : -1;
ctrl.sortBy = field;
return function (a, b) {
return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
}
};
ctrl.toggleSortDirection = function(index){
console.log(index);
var field = ctrl.fields[index];
console.log(field);
var fieldName = field.name;
console.log(fieldName);
var direction = ctrl.fields[index].direction;
console.log(direction);
var reverse = (direction == 'ascending') ? true : false;
console.log(reverse);
var direction = (direction === 'ascending') ? 'descending' : 'ascending';
console.log(direction);
for(var i = 0; i < ctrl.fields.length; i++){
ctrl.fields[i].direction = 'ascending';
}
ctrl.fields[index].direction = direction;
ctrl.data.sort(ctrl.sortData($filter('toCamelCase')(fieldName), reverse));
};
ctrl.validDatetime = function(dt){
//this should probably be a service
console.log(dt);
var rx = /([0-9]{4})\-([0-9]{2})\-([0-9]{2})/;
if(dt.match(rx)){ console.log(dt); }
return (dt.match(rx)) ? true : false;
};
ctrl.$onInit = ctrl.update();
}
DatagridController.$inject = ['$filter', 'datagridService'];
Template:
<table ng-if="$ctrl.data.length > 0" class="datagrid">
<caption ng-if="$ctrl.caption">{{ $ctrl.caption }}</caption>
<colgroup ng-if="$ctrl.colgroup.length > 0">
<col ng-repeat="col in $ctrl.colgroup">
</colgroup>
<thead ng-if="$ctrl.hasHeader = true">
<tr>
<th ng-repeat="field in $ctrl.fields" ng-click="$ctrl.toggleSortDirection($index)" data-sortable="true">{{ field.name }}<div ng-class="field.direction"></div></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in $ctrl.data">
<td ng-repeat="field in $ctrl.fields">
<span ng-if="!$ctrl.validDatetime(record[(field.name|toCamelCase)])"><a>{{ record[(field.name|toCamelCase)] }}</a></span>
<span ng-if="$ctrl.validDatetime(record[(field.name|toCamelCase)])"><a>{{ record[(field.name|toCamelCase)] | date: 'dd-MMM-yyyy' }}</a></span>
</td>
</tr>
</tbody>
<tfoot ng-if="$ctrl.hasFooter = true">
<td colspan="{{ $ctrl.fields.length }}">Last Updated: {{ $ctrl.today | date: 'dd-MMM-yyyy' }}</td>
</tfoot>
</table>
Component Tag:
<datagrid></datagrid>
Components are isolated by default, which means there is its own $ctr for every instance.
Thing is that data is shared through service. For example you do datagridService.data.sort in first instance => it changes data in service => it gets reflected in all instances of your component (there is one data object in memory, that you are trying to access).
One fix might be, to make copies of data for every component instance.
ctrl.data = Object.assign([], datagridService.data);
Dont do any manipulation directly on datagridService.data, but use ctrl.data instead
An earlier draft of code to handle a Todo list, with fewer features, works:
{
return 0;
}
});
return (
<div id="Todo">
<h1>Todo</h1>
<form onSubmit={that.handleSubmit}>
<table>
{table_rows}
<tfoot>
<textarea name='todo-textarea' id='todo-textarea'
onChange={that.onChange}></textarea><br />
<button>{'Add activity'}</button>
</tfoot>
</table>
</form>
</div>
);
}
});
My present version is getting an InvariantViolation:
react-with-addons.js:20237 Uncaught Error: Invariant Violation: findComponentRoot(..., .0.1.1.0.0:0:0.0:1.0): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an parent. Try inspecting the child nodes of the element with React ID ``.
The present code is:
var Todo = React.createClass(
{
mixins: [React.addons.LinkedStateMixin],
getInitialState: function()
{
var result = parse(initial_todo, {
'next_todo_index': 1,
'items': [],
'text': ''
});
return result;
},
handle_change: function(event)
{
var that = this;
var address = jQuery(event.target).attr('data-index').split('.', 2);
var identifier = parseInt(address[0], 10);
for(var candidate = 0; candidate < this.state.next_todo_index;
candidate += 1)
{
if (parseInt(jQuery(this.state.items[candidate]).attr('index'), 10)
=== identifier)
{
(this.state.items[candidate][address[1]] =
!this.state.items[candidate][address[1]]);
save('Todo', this.state);
}
}
that.render();
},
handleSubmit: function(event)
{
event.preventDefault();
var new_item = get_todo_item(this);
new_item.description = this.state.text;
this.state.items.unshift(new_item);
document.getElementById('todo-textarea').value = '';
save('Todo', this.state);
if (!one_shot)
{
one_shot = true;
}
// this.forceUpdate();
// React.render(<Todo />,
// document.getElementById('Todo'));
},
onChange: function(event)
{
this.setState({text: event.target.value});
},
render: function()
{
var that = this;
var table_rows = [];
var display_item_details = function(label, item)
{
var html_id = item.index + '.' + label;
return (
<td className={label} title={label}>
<input onChange={that.handle_change} data-index={html_id}
className={label} type="checkbox"
defaultChecked={item[label]} />
</td>
);
};
var display_item = function(item)
{
var rendered_nodes = [];
if (item['Completed'] || item['Delete'] || item['Invisible'])
{
return '';
}
else
{
for(var index = 0; index < todo_item_names.length;
index += 1)
{
rendered_nodes.push(
display_item_details(todo_item_names[index], item)
);
}
return (
<tr>{rendered_nodes}
<td className="description" dangerouslySetInnerHTML={{__html:
converter.makeHtml(item.description)}} /></tr>
);
}
};
table_rows.push(that.state.items.map(display_item));
table_rows.sort(function(a, b)
{
if (a.index > b.index)
{
return 1;
}
else if (b.index > a.index)
{
return -1;
}
else
{
return 0;
}
});
return (
<div id="Todo">
<h1>Todo</h1>
<form onSubmit={that.handleSubmit}>
<table>
<tbody>
{table_rows}
<tbody>
<tfoot>
<textarea name='todo-textarea' id='todo-textarea'
onChange={that.onChange}></textarea><br />
<button>{'Add activity'}</button>
</tfoot>
</table>
</form>
</div>
);
}
});
How have I broken this?
Do check through your html table structure. Other than the <tbody> fix, the usage of <tfoot> is also invalid markup.
The <tfoot> element can only contain <tr> tags inside.
i.e.
<tfoot>
<tr>
<td>
<textarea name='todo-textarea' id='todo-textarea'
onChange={that.onChange}></textarea><br />
<button>{'Add activity'}</button>
</td>
</tr>
</tfoot>
Trying to create a Flux store. When I run gulp and check my index.html I get an error "Uncaught TypeError: listener must be a function"
var AppDispatcher = require('../dispatchers/app-dispatcher');
var AppConstants = require('../constants/app-constants');
var assign = require('object-assign');
var EventEmitterProto = require('events').EventEmitter.prototype;
var CHANGE_EVENT = 'CHANGE'; //broadcast this everytime there is a change
var _catalog = [];
var _cartItems = [];
var AppStore = assign(EventEmitterProto, {
emitChange: function(){
this.emit(CHANGE_EVENT)
},
addChangeListener: function(callback){
this.on(CHANGE_EVENT, callback); //<---if I comment this out code runs perfect
},
removeChangeListener: function(callback){
this.removeListener(CHANGE_EVENT, callback)
},
getCart: function(){
return _cartItems
},
getCatalog: function(){
return _catalog
},
getCartTotals: function(){
return _cartTotals()
}
});
module.exports = AppStore;
Below is the only component with a listener
var React = require('react');
var AppStore = require('../stores/app-store.js');
var RemoveFromCart = require('./app-removefromcart.js'); //this is a component
var Increase = require('./app-increaseitem'); //this is a component
var Decrease = require('./app-decreaseitem'); //this is a component
function cartItems(){
return {items: AppStore.getCart()}
}
var Catalog = React.createClass({
getInitialState:function(){
return cartItems();
},
componentWillMount: function(){
AppStore.addChangeListener(this.onChange)
},
_onChange: function(){
this.setState(cartItems());
},
render: function(){
var total = 0;
var items = this.state.items.map(function(item, i){
var subtotal = item.cost * item.qty;
total += subtotal;
return (
<tr key={i}>
<td><RemoveFromCart /></td>
<td>{item.title}</td>
<td>{item.qty}</td>
<td>
<Increase index={i} />
<Decrease index={i} />
</td>
<td>${subtotal}</td>
</tr>
);
})//end map
return (
<table className="table table-hover">
<thead>
<tr>
<th></th>
<th>Item</th>
<th>Qty</th>
<th></th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
{items}
</tbody>
<tfoot>
<tr>
<td colSpan="4" className="text-right">Total</td>
</tr>
</tfoot>
</table>
);
}
});
module.exports = Catalog;
Please help. This is really hurting my head
you might need to change
AppStore.addChangeListener(this._onChange)
logic to componentDidMount function like
componentDidMount:function(){
AppStore.addChangeListener(this._onChange)
}
I want to group the table header according to the same content.
<table id="tblSample" border="1" >
<tbody>
<tr><th>Group#1</th><th>Group#1</th><th>Group#1</th><th>Group#21</th></tr>
<tr><th>Sub-Group#1</th><th>Sub-Group#1</th><th>Sub-Group#2</th><th>Sub-Group#2</th></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td> </tr>
</tbody>
</table>
Here I want to merge the columns Group#1 as asingle column and Sub-Group#1 as well as. Any Idea?
I tried a code but it doesn't worked.
Here is my demo : http://jsfiddle.net/L3ab1edw/1/
expected output:
-------------------------------------------------------
Group#1 | Group#2
------------------------|---------------------------------
Sub-Group#1 | Sub-Group#2
-----------|------------|------------|---------------------
1 |2 |3 |4
$(document).ready(function () {
$('#tblSample').each(function () {
var Column_number_to_Merge = 1;
var Previous_TH = null;
var i = 1;
$("thead",this).find('tr').each(function () {
var Current_th = $(this).find('th:nth-child(' + Column_number_to_Merge + ')');
if (Previous_TH == null) {
Previous_TH = Current_th;
i = 1;
}
else if (Current_th.text() == Previous_TH.text()) {
Current_th.remove();
Previous_TH.attr('colspan', i + 1);
i = i + 1;
}
else {
Previous_TH = Current_th;
i = 1;
}
});
});
});
Use colspan:
<table id="tblSample" border="1" >
<tbody>
<tr><th colspan="3">Group#1</th><th>Group#21</th></tr>
<tr><th>Sub-Group#1</th><th>Sub-Group#1</th><th>Sub-Group#2</th><th>Sub-Group#2</th></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td> </tr>
</tbody>
</table>
I don't understand why you need a jQuery to do such a simple task, but here you go:
$("#tblSample")
.find("tbody tr:first-child")
.html('<th colspan="3">Group#1</th><th>Group#21</th>');