diff --git a/Gruntfile.js b/Gruntfile.js index 8c1107a09..b831d86c9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -179,8 +179,8 @@ module.exports = function (grunt) { 'node_modules/patternfly/dist/js/patternfly-settings-charts.js', 'node_modules/angular/angular.js', 'node_modules/angular-dragdrop/src/angular-dragdrop.js', - 'node_modules/angular-datatables/dist/angular-datatables.min.js', - 'node_modules/angular-datatables/dist/plugins/select/angular-datatables.select.min.js', + 'node_modules/angularjs-datatables/dist/angular-datatables.js', + 'node_modules/angularjs-datatables/dist/plugins/select/angular-datatables.select.min.js', 'node_modules/angular-sanitize/angular-sanitize.js', 'node_modules/angular-animate/angular-animate.js', 'node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js', diff --git a/README.md b/README.md index 9c0895589..2dd05d34d 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,8 @@ Note: - - + + ``` 7. (optional) The 'patternfly.canvas' module is not a dependency in the default angular 'patternfly' module. In order to use pfCanvasEditor or pfCanvas, you must add 'patternfly.canvas' as a dependency in your application: diff --git a/bower.json b/bower.json index 1a6be0ee0..a440eeca7 100644 --- a/bower.json +++ b/bower.json @@ -41,7 +41,7 @@ "angular-animate": "1.5.*", "angular-bootstrap": "2.2.x", "angular-dragdrop": "1.0.13", - "angular-datatables": "^0.5.6", + "angularjs-datatables": "^0.5.7", "angular-drag-and-drop-lists": "2.0.0", "angular-sanitize": "1.5.*", "datatables.net": "^1.10.12", diff --git a/package.json b/package.json index 912263373..eb59e0d91 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "patternfly-eng-release": "3.25.1" }, "optionalDependencies": { - "angular-datatables": "^0.5.6", + "angularjs-datatables": "^0.5.7", "angular-drag-and-drop-lists": "2.0.0", "bootstrap-select": "~1.10.0", "c3": "~0.4.11", diff --git a/src/pagination/pagination.component.js b/src/pagination/pagination.component.js index ada328e42..810b6e7ea 100644 --- a/src/pagination/pagination.component.js +++ b/src/pagination/pagination.component.js @@ -34,7 +34,7 @@ @@ -59,10 +59,12 @@ angular.module('patternfly.pagination').component('pfPagination', { transclude: true, templateUrl: 'pagination/pagination.html', bindings: { - pageNumber: '=', + pageNumber: '=?', numTotalItems: "<", pageSizeIncrements: ' ctrl.lastPageNumber) { - ctrl.pageNumber = ctrl.lastPageNumber; - } else if (ctrl.pageNumber < 1 || isNaN(ctrl.pageNumber)) { - ctrl.pageNumber = 1; + var newPageNumber = parseInt(ctrl.pageNumber, 10); + if (newPageNumber > ctrl.lastPageNumber) { + updatePageNumber(ctrl.lastPageNumber); + } else if (newPageNumber < 1 || isNaN(ctrl.pageNumber)) { + updatePageNumber(1); + } else { + updatePageNumber(newPageNumber); } }; ctrl.gotoFirstPage = function () { if (ctrl.pageNumber !== 1) { - ctrl.pageNumber = 1; + updatePageNumber(1); } }; ctrl.gotoPreviousPage = function () { if (ctrl.pageNumber !== 1) { - ctrl.pageNumber--; + updatePageNumber(ctrl.pageNumber - 1); } }; ctrl.gotoNextPage = function () { if (ctrl.pageNumber < ctrl.lastPageNumber) { - ctrl.pageNumber++; + updatePageNumber(ctrl.pageNumber + 1); } }; ctrl.gotoLastPage = function () { if (ctrl.pageNumber < ctrl.lastPageNumber) { - ctrl.pageNumber = ctrl.lastPageNumber; + updatePageNumber(ctrl.lastPageNumber); } }; ctrl.getStartIndex = function () { - return ctrl.pageSize * (ctrl.pageNumber - 1) + 1; + return ctrl.numTotalItems ? ctrl.pageSize * (ctrl.pageNumber - 1) + 1 : 0; }; ctrl.getEndIndex = function () { var numFullPages = Math.floor(ctrl.numTotalItems / ctrl.pageSize); - var numItemsOnLastPage = ctrl.numTotalItems - (numFullPages * ctrl.pageSize); + var numItemsOnLastPage = ctrl.numTotalItems - (numFullPages * ctrl.pageSize) || ctrl.pageSize; var numItemsOnPage = isLastPage() ? numItemsOnLastPage : ctrl.pageSize; - return ctrl.getStartIndex() + numItemsOnPage - 1; + return ctrl.numTotalItems ? ctrl.getStartIndex() + numItemsOnPage - 1 : 0; }; + function updatePageNumber (newPageNumber) { + ctrl.pageNumber = newPageNumber; + if (ctrl.updatePageNumber) { + ctrl.updatePageNumber({ + $event: { + pageNumber: ctrl.pageNumber + } + }); + } + } + function getLastPageNumber () { return Math.ceil(ctrl.numTotalItems / ctrl.pageSize); } diff --git a/src/table/table.less b/src/table/table.less index 56b8535a4..5fb986871 100644 --- a/src/table/table.less +++ b/src/table/table.less @@ -27,6 +27,11 @@ table.dataTable { } } tbody { + tr.selected { + span a { + color: @color-pf-white; + } + } th { padding: 2px 10px 3px; } diff --git a/src/table/table.module.js b/src/table/table.module.js index 96008466f..766218ad2 100644 --- a/src/table/table.module.js +++ b/src/table/table.module.js @@ -5,4 +5,4 @@ * Table module for patternfly. * */ -angular.module('patternfly.table', ['datatables', 'patternfly.utils', 'patternfly.filters', 'patternfly.sort']); +angular.module('patternfly.table', ['datatables', 'patternfly.pagination', 'patternfly.utils', 'patternfly.filters', 'patternfly.sort']); diff --git a/src/table/tableview/examples/table-view-basic.js b/src/table/tableview/examples/table-view-basic.js index f747ae97c..08856c7bc 100644 --- a/src/table/tableview/examples/table-view-basic.js +++ b/src/table/tableview/examples/table-view-basic.js @@ -61,6 +61,7 @@ empty-state-action-buttons="emptyStateActionButtons"> +
-
@@ -298,14 +298,14 @@ state: "New York" }, { - status: "ok", + status: "warning", name: "Mike Bird", address: "50 Forth Street", city: "New York", state: "New York" }, { - status: "ok", + status: "error", name: "Cheryl Taylor", address: "2 Main Street", city: "New York", diff --git a/src/table/tableview/examples/table-view-with-toolbar.js b/src/table/tableview/examples/table-view-with-toolbar.js index 9662e3999..7ef03f92c 100644 --- a/src/table/tableview/examples/table-view-with-toolbar.js +++ b/src/table/tableview/examples/table-view-with-toolbar.js @@ -13,7 +13,16 @@ *
  • .itemsAvailable - (boolean) If 'false', displays the {@link patternfly.views.component:pfEmptyState Empty State} component. *
  • .showCheckboxes - (boolean) Show checkboxes for row selection, default is true * - * @param {object} dtOptions Optional angular-datatables DTOptionsBuilder configuration object. See {@link http://l-lin.github.io/angular-datatables/archives/#/api angular-datatables: DTOptionsBuilder} + * @param {object} dtOptions Optional angular-datatables DTOptionsBuilder configuration object. Note: paginationType, displayLength, and dom:"p" are no longer being used for pagination, but are allowed for backward compatibility. + * Please switch to prefered 'pageConfig' settings for pf pagination controls. + * Other dtOptions can still be used, such as 'order' See {@link http://l-lin.github.io/angular-datatables/archives/#/api angular-datatables: DTOptionsBuilder} + * @param {object} pageConfig Optional pagination configuration object. Since all properties are optional it is ok to specify: 'pageConfig = {}' to indicate that you want to + * use pagination with the default parameters. + *
      + *
    • .pageNumber - (number) Optional Initial page number to display. Default is page 1. + *
    • .pageSize - (number) Optional Initial page size/display length to use. Ie. Number of "Items per Page". Default is 10 items per page + *
    • .pageSizeIncrements - (Array[Number]) Optional Page size increments for the 'per page' dropdown. If not specified, the default values are: [5, 10, 20, 40, 80, 100] + *
    * @param {array} items Array of items to display in the table view. * @param {array} columns Array of table column information to display in the table's header row and optionaly render the cells of a column. *
      @@ -86,24 +95,23 @@
  • +
    - +
    -
    @@ -154,28 +161,22 @@ { header: "BirthMonth", itemField: "birthMonth"} ]; - $scope.dtOptions = { - paginationType: 'full', - displayLength: 10, - dom: "tp" - }; - - // [WIP] attempt to dyamically change displayLength (#rows) and turn on/off pagination controls - // See: issues turning on/off pagination. see: https://datatables.net/manual/tech-notes/3 + // dtOptions paginationType, displayLength, and dom:"p" are no longer being + // used, but are allowed for backward compatibility. + // Please switch to prefered 'pageConfig' settings for pf pagination controls + // Other dtOptions can still be used, such as 'order' + // $scope.dtOptions = { + // paginationType: 'full', + // displayLength: 10, + // dom: "tp" + // } - $scope.usePagination = true; - $scope.togglePagination = function () { - $scope.usePagination = !$scope.usePagination; - console.log("---> togglePagination: " + $scope.usePagination); - if($scope.usePagination) { - $scope.dtOptions.displayLength = 3; - $scope.dtOptions.dom = "tp"; - console.log("---> use pagination: " + $scope.dtOptions.displayLength + ":" + $scope.dtOptions.dom); - } else { - $scope.dtOptions.displayLength = undefined; - $scope.dtOptions.dom = "t"; - } - }; + // New pagination config settings + $scope.pageConfig = { + pageNumber: 1, + pageSize: 10, + pageSizeIncrements: [5, 10, 15] + } $scope.allItems = [ { @@ -305,6 +306,8 @@ match = item.address.match(filter.value) !== null; } else if (filter.id === 'birthMonth') { match = item.birthMonth === filter.value; + } else if (filter.id === 'status') { + match = item.status === filter.value; } return match; }; @@ -364,6 +367,13 @@ $scope.filterConfig = { fields: [ + { + id: 'status', + title: 'Status', + placeholder: 'Filter by Status...', + filterType: 'select', + filterValues: ['error', 'warning', 'ok'] + }, { id: 'name', title: 'Name', diff --git a/src/table/tableview/table-view.component.js b/src/table/tableview/table-view.component.js index 1a87283f3..d424a6752 100644 --- a/src/table/tableview/table-view.component.js +++ b/src/table/tableview/table-view.component.js @@ -7,6 +7,7 @@ angular.module('patternfly.table').component('pfTableView', { items: '<', actionButtons: ' 0; + } + if (angular.isDefined(ctrl.pageConfig) && angular.isDefined(ctrl.pageConfig.numTotalItems)) { + ctrl.pageConfig.numTotalItems = ctrl.items.length; + } prevItems = angular.copy(ctrl.items); - //$timeout(function () { - selectRowsByChecked(); - //}); } }; @@ -198,9 +254,14 @@ angular.module('patternfly.table').component('pfTableView', { function listenForDraw () { var oTable; var dtInstance = ctrl.dtInstance; + if (dtInstance && dtInstance.dataTable) { oTable = dtInstance.dataTable; + if (angular.isDefined(ctrl.pageConfig) && angular.isDefined(ctrl.pageConfig.pageNumber)) { + oTable.fnPageChange(ctrl.pageConfig.pageNumber - 1); + } ctrl.tableId = oTable[0].id; + oTable.off('draw.dt'); oTable.on('draw.dt', function () { if (ctrl.debug) { $log.debug("--> redraw"); @@ -230,6 +291,7 @@ angular.module('patternfly.table').component('pfTableView', { } } }); + selectRowsByChecked(); }; ctrl.toggleOne = function (item) { diff --git a/src/table/tableview/table-view.html b/src/table/tableview/table-view.html index 8a41a34db..1941073e2 100644 --- a/src/table/tableview/table-view.html +++ b/src/table/tableview/table-view.html @@ -54,5 +54,11 @@ + +
    diff --git a/src/toolbars/examples/toolbar.js b/src/toolbars/examples/toolbar.js index 815b21bc9..ace2e96b4 100644 --- a/src/toolbars/examples/toolbar.js +++ b/src/toolbars/examples/toolbar.js @@ -117,13 +117,14 @@ -
    +
    +
    -
    @@ -180,7 +180,6 @@ } }; - $scope.allItems = [ { name: "Fred Flintstone", diff --git a/test/karma.conf.js b/test/karma.conf.js index 50d0689c6..4324c5aef 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -21,7 +21,7 @@ module.exports = function(config) { 'node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js', 'node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js', 'misc/angular-bootstrap-prettify.js', - 'node_modules/angular-datatables/dist/angular-datatables.min.js', + 'node_modules/angularjs-datatables/dist/angular-datatables.min.js', 'node_modules/lodash/lodash.js', 'misc/test-lib/helpers.js', 'src/**/*.module.js', diff --git a/test/pagination/pagination.spec.js b/test/pagination/pagination.spec.js index 10eeb911d..ffd841b4a 100644 --- a/test/pagination/pagination.spec.js +++ b/test/pagination/pagination.spec.js @@ -2,18 +2,15 @@ describe('Component: pfPagination', function () { var $scope; var $compile; var element; - var performedAction; - var updateCount; // load the controller's module beforeEach(function () { - module('patternfly.pagination', 'pagination/pagination.html', 'patternfly.table', 'table/tableview/table-view.html', 'views/empty-state.html'); + module('patternfly.pagination', 'patternfly.table', 'pagination/pagination.html', 'table/tableview/table-view.html', 'views/empty-state.html'); }); - beforeEach(inject(function (_$compile_, _$rootScope_, _$timeout_) { + beforeEach(inject(function (_$compile_, _$rootScope_) { $compile = _$compile_; $scope = _$rootScope_; - $timeout = _$timeout_; })); var compileHTML = function (markup, scope) { @@ -133,15 +130,47 @@ describe('Component: pfPagination', function () { var htmlTmp = ''; compileHTML(htmlTmp, $scope); + var ctrl = element.isolateScope().$ctrl; + spyOn(ctrl, 'updatePageNumber'); + // On first page, goto prev and first page buttons should be disabled expect(element.find('.disabled').length).toBe(2); angular.element(element.find('.pagination-pf-page ')).val('7').trigger('input').blur(); $scope.$digest(); + expect(ctrl.updatePageNumber).toHaveBeenCalled(); expect(angular.element(element.find('.pagination-pf-items-current')).text().trim()).toBe('61-70'); expect(angular.element(element.find('.pagination-pf-items-total')).text().trim()).toBe('126'); expect(angular.element(element.find('.pagination-pf-page')).val().trim()).toBe('7'); expect(angular.element(element.find('.pagination-pf-pages')).text().trim()).toBe('13'); }); + + it('should change the page size when selected from dropdown', function() { + $scope.pageSize = 10; + $scope.pageNumber = 1; + $scope.numTotalItems = 126; + var htmlTmp = ''; + compileHTML(htmlTmp, $scope); + + var ctrl = element.isolateScope().$ctrl; + spyOn(ctrl, 'updatePageSize'); + + //Get pageSizeDropdown + var pageSizeDropdown = element.find('div[uib-dropdown]'); + expect(pageSizeDropdown.length).toBe(1); + + //Change pageSizeDropdown to 20 + pageSizeDropdown.find('button').click(); + var pageSizeLinks = pageSizeDropdown.find('a'); + expect(pageSizeLinks.length).toBe(6); + pageSizeLinks[2].click(); // switch to 20 items per page + $scope.$digest(); + + expect(ctrl.updatePageSize).toHaveBeenCalled(); + expect(angular.element(element.find('.pagination-pf-items-current')).text().trim()).toBe('1-20'); + expect(angular.element(element.find('.pagination-pf-items-total')).text().trim()).toBe('126'); + expect(angular.element(element.find('.pagination-pf-page')).val().trim()).toBe('1'); + expect(angular.element(element.find('.pagination-pf-pages')).text().trim()).toBe('7'); + }); }); diff --git a/test/table/tableview/table-view.spec.js b/test/table/tableview/table-view.spec.js index 2f5c41801..64258c235 100644 --- a/test/table/tableview/table-view.spec.js +++ b/test/table/tableview/table-view.spec.js @@ -2,18 +2,19 @@ describe('Component: pfTableView', function () { var $scope; var $compile; var element; - var performedAction; - var updateCount; // load the controller's module beforeEach(function () { - module('patternfly.views', 'patternfly.table', 'table/tableview/table-view.html', 'views/empty-state.html'); + module('patternfly.views', 'patternfly.table', 'table/tableview/table-view.html', 'views/empty-state.html', 'pagination/pagination.html'); }); beforeEach(inject(function (_$compile_, _$rootScope_, _$timeout_) { $compile = _$compile_; $scope = _$rootScope_; $timeout = _$timeout_; + $scope.config = { + selectionMatchProp: 'uuid' + }; })); var compileHTML = function (markup, scope) { @@ -27,11 +28,7 @@ describe('Component: pfTableView', function () { $scope.result = "You clicked on " + name; } - beforeEach(function () { - $scope.config = { - selectionMatchProp: 'uuid' - }; - + function basicSetup() { $scope.columns = [ {itemField: 'uuid', header: 'ID'}, {itemField: 'name', header: 'Name', htmlTemplate: "name_template.html", colActionFn: onNameClick}, @@ -49,36 +46,70 @@ describe('Component: pfTableView', function () { var htmlTmp = '' + ''; compileHTML(htmlTmp, $scope); - }); + } + + function paginationSetup() { + $scope.columns = [ + {itemField: 'uuid', header: 'ID'}, + {itemField: 'name', header: 'Name'}, + {itemField: 'size', header: 'Size'}, + {itemField: 'capacity', header: 'Capacity'} + ]; + + $scope.items = [ + {uuid: '1', name: 'One', size: 291445030, capacity: 8200000000}, + {uuid: '2', name: 'Two', size: 1986231544, capacity: 8700000000}, + {uuid: '3', name: 'Three', size: 7864632, capacity: 7800000000}, + {uuid: '4', name: 'Four', size: 8162410, capacity: 3200000000}, + {uuid: '5', name: 'Five', size: 6781425410, capacity: 7600000000}, + {uuid: '6', name: 'Six', size: 6781425410, capacity: 7600000000}, + {uuid: '7', name: 'Seven', size: 6781425410, capacity: 7600000000} + ]; + + $scope.pageConfig = { + pageNumber: 2, + pageSize: 2, + pageSizeIncrements: [2, 10, 15] + }; + + var htmlTmp = ''; + + compileHTML(htmlTmp, $scope); + } it('should not show the empty state when items is null', function () { + basicSetup(); $scope.items = null; $scope.$digest(); expect(element.find('#title').text()).toContain(''); }); it('should show the empty state when items is empty', function () { + basicSetup(); $scope.items = []; $scope.$digest(); expect(element.find('#title').text()).toContain('No Items Available'); }); it('should show the empty state when the config property is specified', function () { + basicSetup(); $scope.config.itemsAvailable = false; $scope.$digest(); expect(element.find('#title').text()).toContain('No Items Available'); }); it('should show the correct number of items', function () { + basicSetup(); var rows = element.find('.table > tbody > tr'); expect(rows.length).toBe($scope.items.length); }); it('should populate cells with correct data', function () { + basicSetup(); var i, j, item, selector, expectedValue; for (i = 0; i < $scope.items.length; i++) { item = $scope.items[i]; @@ -92,6 +123,7 @@ describe('Component: pfTableView', function () { }); it('should show checkboxes for row selection', function () { + basicSetup(); var headerCheckbox = element.find('.table > thead > tr > th > input[type="checkbox"]'); var bodyCheckboxes = element.find('.table > tbody > tr > td > input[type="checkbox"]'); expect(headerCheckbox.length).toBe(1); @@ -99,6 +131,7 @@ describe('Component: pfTableView', function () { }); it('should not show checkboxes for row selection when "showCheckboxes" is false', function () { + basicSetup(); $scope.config.showCheckboxes = false; $scope.$digest(); var headerCheckbox = element.find('.table > thead > tr > th > input[type="checkbox"]'); @@ -108,10 +141,59 @@ describe('Component: pfTableView', function () { }); it('should use an htmlTemplate if one is configured', function () { + basicSetup(); var nameLinks = element.find('.custom-template'); expect(nameLinks.length).toBe(5); eventFire(nameLinks[0], 'click'); expect($scope.result).toBe('You clicked on One'); }); + it('should not show pagination controls by default', function () { + basicSetup(); + expect(element.find('pf-pagination').length).toBe(0); + }); + + it('should show pagination controls when configured', function () { + paginationSetup(); + expect(element.find('pf-pagination').length).toBe(1); + + expect(angular.element(element.find('.pagination-pf-items-current')).text().trim()).toBe('3-4'); // page # 2 + expect(angular.element(element.find('.pagination-pf-items-total')).text().trim()).toBe('7'); + expect(angular.element(element.find('.pagination-pf-page')).val().trim()).toBe('2'); + expect(angular.element(element.find('.pagination-pf-pages')).text().trim()).toBe('4'); + }); + + it('should goto a specific page when inputted', function () { + paginationSetup(); + + var ctrl = element.isolateScope().$ctrl; + spyOn(ctrl, 'updatePageNumber'); + + angular.element(element.find('.pagination-pf-page ')).val('3').trigger('input').blur(); + $scope.$digest(); + + expect(ctrl.updatePageNumber).toHaveBeenCalled(); + expect(angular.element(element.find('.pagination-pf-items-current')).text().trim()).toBe('5-6'); + expect(angular.element(element.find('.pagination-pf-items-total')).text().trim()).toBe('7'); + expect(angular.element(element.find('.pagination-pf-page')).val().trim()).toBe('3'); + expect(angular.element(element.find('.pagination-pf-pages')).text().trim()).toBe('4'); + }); + + it('should change the page size when selected from dropdown', function() { + var ctrl = element.isolateScope().$ctrl; + spyOn(ctrl, 'updatePageSize'); + + //Get pageSizeDropdown + var pageSizeDropdown = element.find('div[uib-dropdown]'); + expect(pageSizeDropdown.length).toBe(1); + + //Change pageSizeDropdown to 10 + pageSizeDropdown.find('button').click(); + var pageSizeLinks = pageSizeDropdown.find('a'); + expect(pageSizeLinks.length).toBe(3); + pageSizeLinks[1].click(); // switch to 10 items per page + $scope.$digest(); + + expect(ctrl.updatePageSize).toHaveBeenCalled(); + }); });