├── .eslintignore ├── .bowerrc ├── docs ├── font │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── partials │ └── api │ │ ├── mdtHeaderRow.html │ │ ├── mdtRow.html │ │ ├── mdtCell.html │ │ └── mdtColumn.html ├── css │ ├── animations.css │ ├── prettify.css │ ├── doc_widgets.css │ └── docs.css └── js │ └── docs-setup.js ├── .csslintrc ├── .gitignore ├── index.js ├── app ├── modules │ ├── main │ │ ├── templates │ │ │ ├── cells │ │ │ │ ├── generateCheckboxCell.html │ │ │ │ ├── generateCellValue.html │ │ │ │ ├── generateSortingIcons.html │ │ │ │ └── generateCell.html │ │ │ ├── rows │ │ │ │ ├── errorIndicator.html │ │ │ │ ├── noResultIndicator.html │ │ │ │ ├── generateRowsVirtualRepeat.html │ │ │ │ └── generateRows.html │ │ │ ├── generateTable.html │ │ │ ├── mdtAlternateHeaders.html │ │ │ ├── smallEditDialog.html │ │ │ ├── mdtGeneratedHeaderRow.html │ │ │ ├── mdtCardHeader.html │ │ │ ├── largeEditDialog.html │ │ │ ├── mdtTable.html │ │ │ ├── mdtDropdownColumnFilter.html │ │ │ ├── mdtCardFooter.html │ │ │ ├── mdtColumnSelector.html │ │ │ ├── mdtCheckboxColumnFilter.html │ │ │ ├── mdtChipsColumnFilter.html │ │ │ └── mdtGeneratedHeaderCellContent.html │ │ ├── factories │ │ │ ├── mdtLodashFactory.js │ │ │ ├── mdtPaginationHelperFactory.js │ │ │ ├── TableDataStorageFactory.js │ │ │ └── mdtAjaxPaginationHelperFactory.js │ │ ├── directives │ │ │ ├── header │ │ │ │ ├── mdtGeneratedHeaderRowDirective.js │ │ │ │ ├── mdtGeneratedHeaderCellContentDirective.js │ │ │ │ ├── mdtHeaderRowDirective.js │ │ │ │ └── mdtColumnDirective.js │ │ │ ├── helpers │ │ │ │ ├── mdtAnimateSortIconHandlerDirective.js │ │ │ │ ├── mdtAddAlignClass.js │ │ │ │ ├── mdtSelectAllRowsHandlerDirective.js │ │ │ │ ├── mdtCustomCellDirective.js │ │ │ │ └── mdtAddHtmlContentToCellDirective.js │ │ │ ├── tablecard │ │ │ │ ├── mdtCardFooterDirective.js │ │ │ │ └── mdtCardHeaderDirective.js │ │ │ ├── mdtAlternateHeadersDirective.js │ │ │ └── body │ │ │ │ ├── mdtRowDirective.js │ │ │ │ └── mdtCellDirective.js │ │ ├── providers │ │ │ ├── PaginatorTypeProvider.js │ │ │ └── ColumnOptionsProvider.js │ │ ├── features │ │ │ ├── ColumnSortFeature │ │ │ │ ├── providers │ │ │ │ │ └── ColumnSortDirection.js │ │ │ │ ├── directive │ │ │ │ │ └── mdtSortingIconsDirective.js │ │ │ │ └── ColumnSortFeature.js │ │ │ ├── SelectableRowsFeature.js │ │ │ ├── EditCellFeature │ │ │ │ └── EditCellFeature.js │ │ │ ├── PaginationFeature.js │ │ │ ├── ColumnFilterFeature │ │ │ │ ├── directives │ │ │ │ │ ├── mdtChipsColumnFilterDirective.js │ │ │ │ │ ├── mdtDropdownColumnFilterDirective.js │ │ │ │ │ └── mdtCheckboxColumnFilterDirective.js │ │ │ │ └── ColumnFilterFeature.js │ │ │ └── ColumnSelectorFeature │ │ │ │ ├── ColumnSelectorFeature.js │ │ │ │ └── directives │ │ │ │ └── mdtColumnSelectorDirective.js │ │ ├── helpers │ │ │ └── ColumnAlignmentHelper.js │ │ └── controllers │ │ │ └── InlineEditModalCtrl.js │ └── app.js └── scss │ └── main.scss ├── .travis.yml ├── test ├── helpers │ └── environment.js ├── karma.conf.js ├── karma.unit.conf.js └── unit │ └── modules │ ├── features │ ├── PaginationFeatureTest.js │ ├── EditCellFeature │ │ └── EditCellFeatureTest.js │ ├── ColumnSortFeature │ │ └── ColumnSortFeatureTest.js │ └── ColumnFilterFeature │ │ └── directives │ │ ├── mdtChipsColumnFilterDirectiveTest.js │ │ └── mdtDropdownColumnFilterDirectiveTest.js │ └── directives │ ├── body │ └── mdDataTableCellDirectiveTest.js │ ├── mdDataTableDirectiveTest.js │ └── header │ └── mdDataTableColumnDirectiveTest.js ├── gulp-tasks ├── lint.js ├── test.js ├── gulp-utils.js ├── templates.js ├── compass.js ├── dist.js ├── index.js └── copy.js ├── jsdocConf.json ├── .codeclimate.yml ├── .jshintrc ├── LICENSE ├── bower.json ├── gulpfile.js ├── wallaby.js ├── package.json ├── demo ├── developmentArea.html ├── index.html └── demoApp.js └── .eslintrc /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } -------------------------------------------------------------------------------- /docs/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamisti/mdDataTable/HEAD/docs/font/FontAwesome.otf -------------------------------------------------------------------------------- /docs/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamisti/mdDataTable/HEAD/docs/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamisti/mdDataTable/HEAD/docs/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamisti/mdDataTable/HEAD/docs/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | --exclude-exts=.min.css 2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | app/bower_components 3 | dist/main.css 4 | build 5 | node_modules 6 | reports 7 | typings 8 | .idea 9 | coverage 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('angular-material-icons'); 2 | require('./dist/md-data-table-templates.js'); 3 | require('./dist/md-data-table.js'); 4 | 5 | module.exports = 'mdDataTable'; 6 | -------------------------------------------------------------------------------- /app/modules/main/templates/cells/generateCheckboxCell.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/modules/main/templates/rows/errorIndicator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/modules/main/templates/rows/noResultIndicator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.12 5 | 6 | before_install: 7 | - npm install -g gulp 8 | - npm install -g bower 9 | - npm install -g codeclimate-test-reporter 10 | 11 | script: 12 | - bower install 13 | - gulp ci 14 | 15 | after_script: 16 | - codeclimate-test-reporter < coverage/lcov.info 17 | -------------------------------------------------------------------------------- /test/helpers/environment.js: -------------------------------------------------------------------------------- 1 | module.exports.getUnitReportersForCurrentRun = function() { 2 | var reporters = ['coverage', 'progress']; 3 | 4 | return reporters; 5 | }; 6 | 7 | module.exports.getCoverageReportersForCurrentRun = function() { 8 | var reporters = [ 9 | { type: 'lcov', subdir: '.' } 10 | ]; 11 | 12 | return reporters; 13 | }; 14 | -------------------------------------------------------------------------------- /gulp-tasks/lint.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | cached = require('gulp-cached'), 3 | jshint = require('gulp-jshint'); 4 | 5 | gulp.task('lint',function() { 6 | var applicationSources = ['app/modules/**/*.js']; 7 | 8 | return gulp.src(applicationSources) 9 | .pipe(cached('lint')) 10 | .pipe(jshint()) 11 | .pipe(jshint.reporter('default')); 12 | }); -------------------------------------------------------------------------------- /app/modules/main/templates/cells/generateCellValue.html: -------------------------------------------------------------------------------- 1 | 2 | {{headerRowData.columnName}} 3 | 4 | 5 | 8 | {{headerRowData.columnName}} 9 | 10 | -------------------------------------------------------------------------------- /app/modules/main/factories/mdtLodashFactory.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtLodashFactory($window){ 5 | if(!$window._){ 6 | throw Error('Lodash does not found. Please make sure you load Lodash before any source for mdDataTable'); 7 | } 8 | 9 | return $window._; 10 | } 11 | 12 | angular 13 | .module('mdDataTable') 14 | .factory('_', mdtLodashFactory); 15 | }()); -------------------------------------------------------------------------------- /app/modules/main/templates/cells/generateSortingIcons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/modules/main/directives/header/mdtGeneratedHeaderRowDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtGeneratedHeaderRowDirective(){ 5 | return { 6 | restrict: 'A', 7 | templateUrl: '/main/templates/mdtGeneratedHeaderRow.html' 8 | }; 9 | } 10 | 11 | angular 12 | .module('mdDataTable') 13 | .directive('mdtGeneratedHeaderRow', mdtGeneratedHeaderRowDirective); 14 | }()); -------------------------------------------------------------------------------- /jsdocConf.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": { 3 | "applicationName": "mdDataTable - Angular material data table", 4 | "disqus": "", 5 | "googleAnalytics": "", 6 | "openGraph": { 7 | "title": "", 8 | "type": "website", 9 | "image": "", 10 | "site_name": "", 11 | "url": "" 12 | }, 13 | "meta": { 14 | "title": "", 15 | "description": "", 16 | "keyword": "" 17 | }, 18 | "linenums": true 19 | } 20 | } -------------------------------------------------------------------------------- /app/modules/main/providers/PaginatorTypeProvider.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @name PaginatorTypeProvider 6 | * @returns possible values for different type of paginators 7 | * 8 | * @describe Representing the possible paginator types. 9 | */ 10 | var PaginatorTypeProvider = { 11 | AJAX : 'ajax', 12 | ARRAY : 'array' 13 | }; 14 | 15 | angular 16 | .module('mdDataTable') 17 | .value('PaginatorTypeProvider', PaginatorTypeProvider); 18 | })(); 19 | -------------------------------------------------------------------------------- /app/modules/main/features/ColumnSortFeature/providers/ColumnSortDirection.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @name ColumnSortDirectionProvider 6 | * @returns possible values for different type of paginators 7 | * 8 | * @describe Representing the possible paginator types. 9 | */ 10 | var ColumnSortDirectionProvider = { 11 | ASC : 'asc', 12 | DESC : 'desc' 13 | }; 14 | 15 | angular 16 | .module('mdDataTable') 17 | .value('ColumnSortDirectionProvider', ColumnSortDirectionProvider); 18 | })(); 19 | -------------------------------------------------------------------------------- /app/modules/main/templates/generateTable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /app/modules/main/templates/mdtAlternateHeaders.html: -------------------------------------------------------------------------------- 1 |
2 | {{getNumberOfSelectedRows()}} item selected 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
-------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | csslint: 4 | enabled: true 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - ruby 10 | - javascript 11 | - python 12 | - php 13 | eslint: 14 | enabled: true 15 | fixme: 16 | enabled: true 17 | ratings: 18 | paths: 19 | - "**.css" 20 | - "**.inc" 21 | - "**.js" 22 | - "**.jsx" 23 | - "**.module" 24 | - "**.php" 25 | - "**.py" 26 | - "**.rb" 27 | exclude_paths: 28 | - "demo/*" 29 | - "docs/*" 30 | - "gulp-tasks/*" 31 | - "dist/*" 32 | - "test/*" 33 | - "gulpfile.js" 34 | -------------------------------------------------------------------------------- /gulp-tasks/test.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | karma = require('gulp-karma'), 3 | KarmaServer = require('karma').Server; 4 | 5 | gulp.task('unit', function(done) { 6 | var config = { 7 | configFile: __dirname + '/../test/karma.unit.conf.js', 8 | singleRun: true 9 | }; 10 | 11 | new KarmaServer(config, done).start(); 12 | }); 13 | 14 | gulp.task('integration', function() { 15 | var config = { 16 | configFile: 'test/karma.integration.conf.js', 17 | action: 'run' 18 | }; 19 | 20 | return gulp.src([]).pipe(karma(config)); 21 | }); 22 | -------------------------------------------------------------------------------- /app/modules/main/providers/ColumnOptionsProvider.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @name ColumnOptionProvider 6 | * @returns possible assignable column options you can give 7 | * 8 | * @describe Representing the assignable properties to the columns you can give. 9 | */ 10 | var ColumnOptionProvider = { 11 | ALIGN_RULE : { 12 | ALIGN_LEFT: 'left', 13 | ALIGN_RIGHT: 'right' 14 | } 15 | }; 16 | 17 | angular 18 | .module('mdDataTable') 19 | .value('ColumnOptionProvider', ColumnOptionProvider); 20 | })(); 21 | -------------------------------------------------------------------------------- /app/modules/main/directives/helpers/mdtAnimateSortIconHandlerDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtAnimateSortIconHandlerDirective(){ 5 | return { 6 | restrict: 'A', 7 | scope: false, 8 | link: function($scope, element){ 9 | if($scope.animateSortIcon){ 10 | element.addClass('animate-sort-icon'); 11 | } 12 | } 13 | }; 14 | } 15 | 16 | angular 17 | .module('mdDataTable') 18 | .directive('mdtAnimateSortIconHandler', mdtAnimateSortIconHandlerDirective); 19 | }()); -------------------------------------------------------------------------------- /app/modules/main/directives/helpers/mdtAddAlignClass.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtAddAlignClass(ColumnAlignmentHelper){ 5 | return { 6 | restrict: 'A', 7 | scope: { 8 | mdtAddAlignClass: '=' 9 | }, 10 | link: function($scope, element){ 11 | var classToAdd = ColumnAlignmentHelper.getColumnAlignClass($scope.mdtAddAlignClass); 12 | 13 | element.addClass(classToAdd); 14 | } 15 | }; 16 | } 17 | 18 | angular 19 | .module('mdDataTable') 20 | .directive('mdtAddAlignClass', mdtAddAlignClass); 21 | }()); -------------------------------------------------------------------------------- /gulp-tasks/gulp-utils.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | connect = require('gulp-connect'); 3 | 4 | module.exports.not = function(sources) { 5 | if(typeof sources === 'string') { 6 | sources = [sources]; 7 | } 8 | 9 | return sources.map(function(path) { 10 | return '!' + path; 11 | }); 12 | }; 13 | 14 | module.exports.combine = function() { 15 | var result = []; 16 | return result.concat.apply(result, arguments); 17 | }; 18 | 19 | module.exports.livereload = function() { 20 | gulp.src('gulpfile.js').pipe(connect.reload()); 21 | }; 22 | 23 | module.exports.isNeedToWatch = function() { 24 | return !!process.isLongRunning; 25 | }; -------------------------------------------------------------------------------- /app/modules/main/helpers/ColumnAlignmentHelper.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function ColumnAlignmentHelper(ColumnOptionProvider){ 5 | var service = this; 6 | service.getColumnAlignClass = getColumnAlignClass; 7 | 8 | function getColumnAlignClass(alignRule) { 9 | if (alignRule === ColumnOptionProvider.ALIGN_RULE.ALIGN_RIGHT) { 10 | return 'rightAlignedColumn'; 11 | } else { 12 | return 'leftAlignedColumn'; 13 | } 14 | } 15 | } 16 | 17 | angular 18 | .module('mdDataTable') 19 | .service('ColumnAlignmentHelper', ColumnAlignmentHelper); 20 | }()); -------------------------------------------------------------------------------- /app/modules/main/templates/smallEditDialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 11 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": false, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "multistr": true, 21 | "validthis": true, 22 | "globals":{ 23 | "angular" : false, 24 | "_":false, 25 | "$" : false, 26 | "uuid" : false, 27 | "Raphael" : false, 28 | "$$UMFP" : false, 29 | "loadJSON" : false, 30 | "Detectizr" : false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/modules/main/templates/cells/generateCell.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/modules/main/features/ColumnSortFeature/directive/mdtSortingIconsDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtSortingIconsDirective(ColumnSortDirectionProvider){ 5 | return { 6 | restrict: 'E', 7 | templateUrl: '/main/templates/cells/generateSortingIcons.html', 8 | scope: { 9 | data: '=', 10 | size: '@' 11 | }, 12 | link: function($scope){ 13 | $scope.ColumnSortDirectionProvider = ColumnSortDirectionProvider; 14 | } 15 | }; 16 | } 17 | 18 | angular 19 | .module('mdDataTable') 20 | .directive('mdtSortingIcons', mdtSortingIconsDirective); 21 | }()); -------------------------------------------------------------------------------- /app/modules/main/directives/helpers/mdtSelectAllRowsHandlerDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtSelectAllRowsHandlerDirective(){ 5 | return { 6 | restrict: 'A', 7 | scope: false, 8 | require: '^mdtTable', 9 | link: function($scope, element, attrs, ctrl){ 10 | $scope.selectAllRows = false; 11 | 12 | $scope.$watch('selectAllRows', function(val){ 13 | ctrl.dataStorage.setAllRowsSelected(val, $scope.isPaginationEnabled()); 14 | }); 15 | } 16 | }; 17 | } 18 | 19 | angular 20 | .module('mdDataTable') 21 | .directive('mdtSelectAllRowsHandler', mdtSelectAllRowsHandlerDirective); 22 | }()); 23 | -------------------------------------------------------------------------------- /gulp-tasks/templates.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | utils = require('./gulp-utils.js'), 3 | watch = require('gulp-watch'), 4 | templateCache = require('gulp-angular-templatecache'), 5 | runSequence = require('run-sequence'); 6 | 7 | var htmlFiles = ['./app/modules/**/*.html']; 8 | 9 | gulp.task('cache templates', function() { 10 | return gulp 11 | .src(htmlFiles) 12 | .pipe(templateCache('templates.js', { module:'mdtTemplates', root:'/', standalone:true })) 13 | .pipe(gulp.dest('build')); 14 | }); 15 | 16 | gulp.task('templates', ['cache templates'], function () { 17 | if(utils.isNeedToWatch()) { 18 | watch(htmlFiles, { verbose: true }, function() { 19 | runSequence('cache templates', utils.livereload); 20 | }); 21 | } 22 | }); -------------------------------------------------------------------------------- /docs/partials/api/mdtHeaderRow.html: -------------------------------------------------------------------------------- 1 |

mdtHeaderRow 2 |
3 |
4 |

5 |

Description

6 |

Representing a header row which should be placed inside mdt-table element directive. 7 | The main responsibility of this directive is to execute all the transcluded mdt-column element directives.

8 |
9 |

Dependencies

10 | 13 |

Usage

14 |
as element:
<mdt-header-row>
15 | </mdt-header-row>
16 |
17 |
18 | -------------------------------------------------------------------------------- /gulp-tasks/compass.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | utils = require('./gulp-utils.js'), 3 | watch = require('gulp-watch'), 4 | compass = require('gulp-compass'), 5 | runSequence = require('run-sequence'); 6 | 7 | gulp.task('run compass for main scss', function() { 8 | var runCompass = compass({ css: 'build', sass: 'app/scss', image: 'app/img' }); 9 | 10 | return gulp 11 | .src('app/scss/main.scss') 12 | .pipe(runCompass) 13 | .pipe(gulp.dest('build')); 14 | }); 15 | 16 | gulp.task('compass', ['run compass for main scss'], function () { 17 | if(utils.isNeedToWatch()) { 18 | var sassFiles = ['app/assets/**/*.scss', 'app/scss/**/*.scss']; 19 | 20 | watch(sassFiles, { base: 'app/scss', verbose: true }, function() { 21 | runSequence('run compass for main scss', utils.livereload); 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | var bowerFiles = require('main-bower-files'); 2 | 3 | var dependencies = bowerFiles({ debug: false }); 4 | 5 | module.exports = function() { 6 | return { 7 | basePath: '..', 8 | 9 | files: dependencies.concat([ 10 | //required for html2js 11 | 'app/modules/**/*.html', 12 | 'app/modules/**/*.js', 13 | { 14 | pattern: 'app/assets/**', 15 | watched: false, 16 | served: true, 17 | included: false 18 | } 19 | ]), 20 | 21 | preprocessors: { 22 | 'app/modules/**/*.html': ['ng-html2js'] 23 | }, 24 | 25 | ngHtml2JsPreprocessor: { 26 | stripPrefix: 'app/modules', 27 | moduleName: 'mdtTemplates' 28 | }, 29 | 30 | frameworks: ['jasmine-jquery', 'jasmine'], 31 | browsers: ['PhantomJS'] 32 | }; 33 | }; -------------------------------------------------------------------------------- /app/modules/main/directives/tablecard/mdtCardFooterDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtCardFooterDirective(){ 5 | return { 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtCardFooter.html', 8 | transclude: true, 9 | replace: true, 10 | scope: true, 11 | require: ['^mdtTable'], 12 | link: function($scope){ 13 | $scope.rowsPerPage = $scope.mdtPaginationHelper.rowsPerPage; 14 | 15 | $scope.$watch('rowsPerPage', function(newVal, oldVal){ 16 | if(newVal !== oldVal){ 17 | $scope.mdtPaginationHelper.setRowsPerPage(newVal); 18 | } 19 | }); 20 | } 21 | }; 22 | } 23 | 24 | angular 25 | .module('mdDataTable') 26 | .directive('mdtCardFooter', mdtCardFooterDirective); 27 | }()); 28 | -------------------------------------------------------------------------------- /test/karma.unit.conf.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | environment = require('./helpers/environment.js'), 3 | baseConfig = require('./karma.conf.js'); 4 | 5 | function mergeTopLevel(lhs, rhs) { 6 | if (_.isArray(lhs)) { 7 | return lhs.concat(rhs); 8 | } 9 | } 10 | 11 | var unitTestingConfig = _.merge(baseConfig(), { 12 | files: [ 13 | //extra testing code 14 | 'app/bower_components/angular-mocks/angular-mocks.js', 15 | 16 | //unit tests 17 | 'test/unit/**/*.js' 18 | ], 19 | 20 | preprocessors: { 21 | 'app/modules/**/*.js': ['coverage'] 22 | }, 23 | 24 | reporters: environment.getUnitReportersForCurrentRun(), 25 | coverageReporter: { 26 | reporters: environment.getCoverageReportersForCurrentRun(), 27 | dir: 'coverage', 28 | subdir: '.' 29 | } 30 | }, mergeTopLevel); 31 | 32 | module.exports = function(config) { 33 | config.set(unitTestingConfig); 34 | }; 35 | -------------------------------------------------------------------------------- /app/modules/main/directives/helpers/mdtCustomCellDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtCustomCellDirective(){ 5 | return { 6 | restrict: 'E', 7 | transclude: true, 8 | template: '', 9 | require: '^mdtTable', 10 | link: { 11 | pre: function($scope, element, attrs, ctrl, transclude){ 12 | transclude(function (clone) { 13 | var columnKey = attrs.columnKey; 14 | 15 | ctrl.dataStorage.customCells[columnKey] = { 16 | scope: $scope, 17 | htmlContent: clone.clone() 18 | }; 19 | }); 20 | } 21 | } 22 | }; 23 | } 24 | 25 | angular 26 | .module('mdDataTable') 27 | .directive('mdtCustomCell', mdtCustomCellDirective); 28 | }()); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtGeneratedHeaderRow.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/modules/main/templates/mdtCardHeader.html: -------------------------------------------------------------------------------- 1 |
2 | {{tableCard.title}} 3 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 |
-------------------------------------------------------------------------------- /app/modules/main/controllers/InlineEditModalCtrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function InlineEditModalCtrl($scope, position, cellData, mdtTranslations, $timeout, $mdDialog){ 5 | 6 | $timeout(function() { 7 | var el = $('md-dialog'); 8 | el.css('position', 'fixed'); 9 | el.css('top', position['top']); 10 | el.css('left', position['left']); 11 | 12 | el.find('input[type="text"]').focus(); 13 | }); 14 | 15 | $scope.cellData = cellData; 16 | $scope.mdtTranslations = mdtTranslations; 17 | 18 | $scope.saveRow = saveRow; 19 | $scope.cancel = cancel; 20 | 21 | function saveRow(){ 22 | if($scope.editFieldForm.$valid){ 23 | $mdDialog.hide(cellData.value); 24 | } 25 | } 26 | 27 | function cancel(){ 28 | $mdDialog.cancel(); 29 | } 30 | } 31 | 32 | angular 33 | .module('mdDataTable') 34 | .controller('InlineEditModalCtrl', InlineEditModalCtrl); 35 | }()); -------------------------------------------------------------------------------- /app/modules/main/directives/mdtAlternateHeadersDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtAlternateHeadersDirective(_){ 5 | return { 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtAlternateHeaders.html', 8 | transclude: true, 9 | replace: true, 10 | scope: true, 11 | require: '^mdtTable', 12 | link: function($scope, element, attrs, ctrl){ 13 | $scope.deleteSelectedRows = deleteSelectedRows; 14 | $scope.getNumberOfSelectedRows = _.bind(ctrl.dataStorage.getNumberOfSelectedRows, ctrl.dataStorage); 15 | 16 | function deleteSelectedRows(){ 17 | var deletedRows = ctrl.dataStorage.deleteSelectedRows(); 18 | 19 | $scope.deleteRowCallback({rows: deletedRows}); 20 | } 21 | } 22 | }; 23 | } 24 | 25 | angular 26 | .module('mdDataTable') 27 | .directive('mdtAlternateHeaders', mdtAlternateHeadersDirective); 28 | }()); -------------------------------------------------------------------------------- /app/modules/main/templates/rows/generateRowsVirtualRepeat.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 23 | -------------------------------------------------------------------------------- /app/modules/main/templates/largeEditDialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

{{cellData.attributes.editableFieldTitle}}

5 | 6 | 7 | 8 | 12 | 13 |
14 |
15 | 16 | 17 | {{mdtTranslations.largeEditDialog.saveButtonLabel}} 18 | {{mdtTranslations.largeEditDialog.cancelButtonLabel}} 19 | 20 |
21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Istvan Fodor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app/modules/main/directives/tablecard/mdtCardHeaderDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtCardHeaderDirective(){ 5 | return { 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtCardHeader.html', 8 | transclude: true, 9 | replace: true, 10 | scope: true, 11 | require: ['^mdtTable'], 12 | link: function($scope){ 13 | $scope.isTableCardEnabled = false; 14 | 15 | //TODO: move it to the feature file 16 | $scope.handleColumnChooserButtonClick = function(){ 17 | if($scope.columnSelectorFeature.isEnabled){ 18 | $scope.columnSelectorFeature.isActive = !$scope.columnSelectorFeature.isActive 19 | } 20 | }; 21 | 22 | if($scope.tableCard && $scope.tableCard.visible !== false){ 23 | $scope.isTableCardEnabled = true; 24 | } 25 | } 26 | }; 27 | } 28 | 29 | angular 30 | .module('mdDataTable') 31 | .directive('mdtCardHeader', mdtCardHeaderDirective); 32 | }()); -------------------------------------------------------------------------------- /app/modules/main/directives/header/mdtGeneratedHeaderCellContentDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtGeneratedHeaderCellContentDirective(ColumnFilterFeature, ColumnSortFeature){ 5 | return { 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtGeneratedHeaderCellContent.html', 8 | replace: true, 9 | scope: false, 10 | require: '^mdtTable', 11 | link: function($scope, element, attrs, ctrl){ 12 | ColumnFilterFeature.initGeneratedHeaderCellContent($scope, $scope.headerRowData, ctrl.mdtPaginationHelper, ctrl.dataStorage); 13 | 14 | $scope.columnClickHandler = function(){ 15 | ColumnFilterFeature.generatedHeaderCellClickHandler($scope, $scope.headerRowData, element); 16 | ColumnSortFeature.columnClickHandler($scope.headerRowData, ctrl.dataStorage, ctrl.mdtPaginationHelper, attrs.index); 17 | }; 18 | } 19 | }; 20 | } 21 | 22 | angular 23 | .module('mdDataTable') 24 | .directive('mdtGeneratedHeaderCellContent', mdtGeneratedHeaderCellContentDirective); 25 | }()); 26 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-data-table", 3 | "version": "2.2.0", 4 | "homepage": "https://github.com/iamisti/mdDataTable", 5 | "author":"Istvan Fodor ", 6 | "main": [ 7 | "dist/md-data-table.js", 8 | "dist/md-data-table-templates.js", 9 | "dist/md-data-table-style.css" 10 | ], 11 | "description": "Angular material table. Complete implementation of google material design based on angular material components.", 12 | "keywords": [ 13 | "table", 14 | "angular", 15 | "material", 16 | "angular material", 17 | "angular table" 18 | ], 19 | "license": "MIT", 20 | "ignore": [ 21 | "test", 22 | "demo", 23 | "docs", 24 | "gulp-tasks", 25 | "gulpfile.js", 26 | "package.json", 27 | "README.md" 28 | ], 29 | "dependencies": { 30 | "jquery": "~2.1.4", 31 | "lodash": "~3.10.1", 32 | "angular": "~1.5.8", 33 | "angular-sanitize": "~1.5.8", 34 | "angular-animate": "~1.5.8", 35 | "angular-material": "1.1.1", 36 | "angular-material-icons": "~v0.6.0" 37 | }, 38 | "devDependencies": { 39 | "angular-mocks": "~1.5.8" 40 | }, 41 | "resolutions": { 42 | "angular": "~1.5.8" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/modules/main/directives/header/mdtHeaderRowDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @ngdoc directive 6 | * @name mdtHeaderRow 7 | * @restrict E 8 | * @requires mdtTable 9 | * 10 | * @description 11 | * Representing a header row which should be placed inside `mdt-table` element directive. 12 | * The main responsibility of this directive is to execute all the transcluded `mdt-column` element directives. 13 | * 14 | */ 15 | function mdtHeaderRowDirective(){ 16 | return { 17 | restrict: 'E', 18 | replace: true, 19 | transclude: true, 20 | require: '^mdtTable', 21 | scope: true, 22 | link: function($scope, element, attrs, mdtCtrl, transclude){ 23 | appendColumns(); 24 | 25 | function appendColumns(){ 26 | transclude(function (clone) { 27 | element.append(clone); 28 | }); 29 | } 30 | } 31 | }; 32 | } 33 | 34 | angular 35 | .module('mdDataTable') 36 | .directive('mdtHeaderRow', mdtHeaderRowDirective); 37 | }()); -------------------------------------------------------------------------------- /app/modules/main/features/SelectableRowsFeature.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function SelectableRowsFeatureFactory($timeout){ 5 | 6 | function SelectableRowsFeature(params){ 7 | this.$scope = params.$scope; 8 | this.ctrl = params.ctrl; 9 | 10 | this.$scope.onCheckboxChange = _.bind(this.onCheckboxChange, this); 11 | } 12 | 13 | SelectableRowsFeature.prototype.onCheckboxChange = function(){ 14 | var that = this; 15 | // we need to push it to the event loop to make it happen last 16 | // (e.g.: all the elements can be selected before we call the callback) 17 | $timeout(function(){ 18 | that.$scope.selectedRowCallback({ 19 | rows: that.ctrl.dataStorage.getSelectedRows() 20 | }); 21 | },0); 22 | }; 23 | 24 | return { 25 | getInstance: function(params){ 26 | return new SelectableRowsFeature(params); 27 | } 28 | }; 29 | } 30 | 31 | angular 32 | .module('mdDataTable') 33 | .service('SelectableRowsFeature', SelectableRowsFeatureFactory); 34 | }()); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtTable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gulp-tasks/dist.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | concat = require('gulp-concat'), 3 | eventStream = require('event-stream'), 4 | compass = require('gulp-compass'), 5 | rename = require('gulp-rename'), 6 | ngAnnotate = require('gulp-ng-annotate'), 7 | templateCache = require('gulp-angular-templatecache'); 8 | 9 | var jsAssets = ['app/modules/**/*.js']; 10 | var htmlFiles = ['app/modules/**/*.html']; 11 | 12 | gulp.task('dist', function() { 13 | var jsStream = gulp.src(jsAssets) 14 | .pipe(concat('md-data-table.js')) 15 | .pipe(ngAnnotate({ remove: false, add: true, single_quotes: true })) 16 | .pipe(gulp.dest('dist')); 17 | 18 | var cssStream = gulp 19 | .src('app/scss/main.scss') 20 | .pipe(compass({ css: 'dist', sass: 'app/scss', image: 'app/img' })) 21 | .pipe(rename('md-data-table-style.css')) 22 | .pipe(gulp.dest('dist')); 23 | 24 | var templatesStream = gulp 25 | .src(htmlFiles) 26 | .pipe(templateCache('md-data-table-templates.js', { root:'/', module:'mdtTemplates', standalone:true })) 27 | .pipe(gulp.dest('dist')); 28 | 29 | return eventStream.merge([jsStream, templatesStream, cssStream]); 30 | }); -------------------------------------------------------------------------------- /docs/css/animations.css: -------------------------------------------------------------------------------- 1 | .slide-reveal.ng-enter { 2 | -webkit-transition:0.5s linear all; 3 | -moz-transition:0.5s linear all; 4 | -o-transition:0.5s linear all; 5 | transition:0.5s linear all; 6 | 7 | opacity:0.5; 8 | position:relative; 9 | opacity:0; 10 | top:10px; 11 | } 12 | .slide-reveal.ng-enter-active { 13 | top:0; 14 | opacity:1; 15 | } 16 | 17 | .expand.ng-enter { 18 | -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 19 | -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 20 | -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 21 | transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 22 | 23 | opacity:0; 24 | max-height:0; 25 | overflow:hidden; 26 | } 27 | .expand.ng-enter-active { 28 | opacity:1; 29 | max-height:40px; 30 | } 31 | 32 | .expand.ng-leave { 33 | -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 34 | -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 35 | -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 36 | transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; 37 | 38 | opacity:1; 39 | max-height:40px; 40 | overflow:hidden; 41 | } 42 | .expand.ng-leave-active { 43 | opacity:0; 44 | max-height:0; 45 | } 46 | -------------------------------------------------------------------------------- /app/modules/main/templates/rows/generateRows.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | connect = require('gulp-connect'), 3 | runSequence = require('run-sequence'); 4 | 5 | require('./gulp-tasks/compass'); 6 | require('./gulp-tasks/copy'); 7 | require('./gulp-tasks/index'); 8 | require('./gulp-tasks/templates'); 9 | require('./gulp-tasks/lint'); 10 | require('./gulp-tasks/test'); 11 | require('./gulp-tasks/dist'); 12 | 13 | gulp.task('start development webserver', function() { 14 | connect.server({ 15 | root: [ 'build' ], 16 | port: 9000, 17 | livereload: true 18 | }); 19 | }); 20 | 21 | gulp.task('default', function(next) { 22 | process.isLongRunning = true; 23 | runSequence('start development webserver', 'build', next); 24 | }); 25 | 26 | gulp.task('build', function(next) { 27 | runSequence('test', 'copy', 'templates', 'compass', 'create index.html', 'ngdocs', next); 28 | }); 29 | 30 | gulp.task('ci', function(next) { 31 | runSequence('lint', 'test', next); 32 | }); 33 | 34 | gulp.task('test', function(next) { 35 | runSequence('unit', /*'integration', */next); 36 | }); 37 | 38 | gulp.task('release', function(next) { 39 | runSequence('build', 'dist', next); 40 | }); 41 | 42 | gulp.task('ngdocs', [], function () { 43 | var gulpDocs = require('gulp-ngdocs'); 44 | return gulp.src('app/modules/**/*.js') 45 | .pipe(gulpDocs.process()) 46 | .pipe(gulp.dest('./docs')); 47 | }); 48 | -------------------------------------------------------------------------------- /app/modules/main/templates/mdtDropdownColumnFilter.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | Sort A-Z 6 |
7 | 8 |
9 | 10 | 11 | 12 | {{transformChip(item)}} 13 | 14 | 15 | 16 |
17 | 18 |
19 | Ok 20 | Cancel 21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /gulp-tasks/index.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | utils = require('./gulp-utils.js'), 3 | watch = require('gulp-watch'), 4 | inject = require('gulp-inject'), 5 | bowerFiles = require('main-bower-files'), 6 | runSequence = require('run-sequence'); 7 | 8 | var jsAssets = ['app/modules/**/*.js'], 9 | demoAssets = ['demo/**/*.js'], 10 | bowerAssets = bowerFiles({ debug: true }); 11 | 12 | gulp.task('inject assets into index.html', function() { 13 | return gulp.src('demo/index.html') 14 | .pipe(inject(gulp.src(bowerAssets, { read: false }), { name: 'bower' })) 15 | .pipe(inject(gulp.src(jsAssets, { read: false }))) 16 | .pipe(inject(gulp.src(demoAssets, { read: false }), { name: 'demo'})) 17 | .pipe(gulp.dest('build')); 18 | }); 19 | 20 | gulp.task('create index.html', ['inject assets into index.html'], function() { 21 | if(utils.isNeedToWatch()) { 22 | var files = utils.combine(jsAssets, bowerAssets); 23 | 24 | watch(files, { verbose: true, events: [ 'add', 'unlink' ] }, function() { 25 | runSequence('inject assets into index.html', utils.livereload); 26 | }); 27 | 28 | watch('app/index.html', { verbose: true }, function() { 29 | runSequence('inject assets into index.html', utils.livereload); 30 | }); 31 | 32 | watch('demo/*', { verbose: true }, function() { 33 | runSequence('inject assets into index.html', utils.livereload); 34 | }); 35 | } 36 | }); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtCardFooter.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | //var wallabyAngularFilesort = require('wallaby-angular-filesort'); 2 | //var wallabyPostprocessor = wallabyAngularFilesort.create({ 3 | // whitelist: ['app/modules/**/*.js'] 4 | //}); 5 | 6 | var angularTemplatePreprocessor = require('wallaby-ng-html2js-preprocessor'); 7 | 8 | module.exports = function () { 9 | return { 10 | files: [ 11 | '/app/bower_components/jquery/dist/jquery.js', 12 | '/app/bower_components/lodash/lodash.js', 13 | '/app/bower_components/angular/angular.js', 14 | '/app/bower_components/angular-sanitize/angular-sanitize.js', 15 | '/app/bower_components/angular-animate/angular-animate.js', 16 | '/app/bower_components/angular-aria/angular-aria.js', 17 | '/app/bower_components/angular-messages/angular-messages.js', 18 | '/app/bower_components/angular-material-icons/angular-material-icons.min.js', 19 | '/app/bower_components/angular-material/angular-material.js', 20 | 21 | 'app/**/*.html', 22 | 23 | '/app/bower_components/angular-mocks/angular-mocks.js', 24 | '/app/modules/**/*.js' 25 | ], 26 | tests: [ 27 | 'test/unit/**/*.js' 28 | ], 29 | //postprocessor: wallabyPostprocessor, 30 | preprocessors: { 31 | 'app/modules/main/templates/**/*.html': function (file) { 32 | return angularTemplatePreprocessor.transform(file, { 33 | stripPrefix: 'app/modules', 34 | moduleName: 'mdtTemplates' 35 | }) 36 | } 37 | }, 38 | testFramework: 'jasmine', 39 | debug: true 40 | } 41 | }; -------------------------------------------------------------------------------- /test/unit/modules/features/PaginationFeatureTest.js: -------------------------------------------------------------------------------- 1 | describe('PaginationFeature', function(){ 2 | 3 | beforeEach(module('mdtTemplates')); 4 | beforeEach(module('mdDataTable')); 5 | 6 | describe('WHEN calling `initFeature`', function(){ 7 | var scope; 8 | var ctrl; 9 | 10 | beforeEach(inject(function($rootScope){ 11 | scope = $rootScope.$new(); 12 | ctrl = { 13 | dataStore: {} 14 | }; 15 | })); 16 | 17 | it('AND feature is used THEN required functions should be added to the scope and to the ctrl', inject(function(PaginationFeature){ 18 | //given/when 19 | PaginationFeature.initFeature(scope, ctrl); 20 | 21 | //then 22 | expect(scope.isPaginationEnabled).toBeDefined(); 23 | expect(ctrl.paginationFeature).toBeDefined(); 24 | expect(ctrl.mdtPaginationHelper).toBeDefined(); 25 | expect(ctrl.paginationFeature).toBeDefined(); 26 | expect(ctrl.paginationFeature.startPaginationFeature).toBeDefined(); 27 | })); 28 | 29 | it('AND `startPaginationFeature` is called THEN it should call `mdtPaginationHelper.fetchPage(1)`', inject(function(PaginationFeature){ 30 | //given/when 31 | PaginationFeature.initFeature(scope, ctrl); 32 | 33 | //then 34 | expect(scope.isPaginationEnabled).toBeDefined(); 35 | expect(ctrl.paginationFeature).toBeDefined(); 36 | expect(ctrl.mdtPaginationHelper).toBeDefined(); 37 | expect(ctrl.paginationFeature).toBeDefined(); 38 | expect(ctrl.paginationFeature.startPaginationFeature).toBeDefined(); 39 | })); 40 | }); 41 | }); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtColumnSelector.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | Columns 7 |
8 |
9 | Select all - Clear 10 | 11 |
{{selectedItems.length}} Selected
12 |
13 | 14 |
15 | 20 | {{item.columnName}} 21 | 22 |
23 | 24 |
25 | Ok 26 | Cancel 27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /gulp-tasks/copy.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | utils = require('./gulp-utils.js'), 3 | watch = require('gulp-watch'), 4 | eventStream = require('event-stream'), 5 | ngAnnotate = require('gulp-ng-annotate'); 6 | 7 | gulp.task('copy',function() { 8 | var applicationSources = ['app/modules/**/*.js'], 9 | demoSources = ['demo/**/*.js', 'demo/developmentArea.html'], 10 | applicationAssets = ['app/assets/**/*.js'], 11 | bowerComponents = ['app/bower_components/**']; 12 | 13 | var sourcesStream = gulp 14 | .src(applicationSources, { base: '.' }) 15 | .pipe(ngAnnotate({ remove: false, add: true, single_quotes: true })) 16 | .pipe(gulp.dest('build')); 17 | 18 | var demoStream = gulp 19 | .src(demoSources, { base: '.' }) 20 | .pipe(gulp.dest('build')); 21 | 22 | var assetsStream = gulp 23 | .src(applicationAssets,{ base: 'app' }) 24 | .pipe(gulp.dest('build')); 25 | 26 | var bowerStream = gulp 27 | .src(bowerComponents, { base: '.' }) 28 | .pipe(gulp.dest('build')); 29 | 30 | if(utils.isNeedToWatch()) { 31 | watch(applicationSources, { base: '.', verbose: true }, utils.livereload) 32 | .pipe(ngAnnotate({ remove: false, add: true, single_quotes: true })) 33 | .pipe(gulp.dest('build')); 34 | 35 | watch(demoSources, { base: '.', verbose: true }, utils.livereload) 36 | .pipe(ngAnnotate({ remove: false, add: true, single_quotes: true })) 37 | .pipe(gulp.dest('build')); 38 | 39 | watch(applicationAssets, { base: 'app', verbose: true }, utils.livereload) 40 | .pipe(gulp.dest('build')); 41 | } 42 | 43 | return eventStream.merge([sourcesStream, bowerStream, assetsStream, demoStream]); 44 | }); 45 | -------------------------------------------------------------------------------- /docs/css/prettify.css: -------------------------------------------------------------------------------- 1 | .pln { color: #000 } /* plain text */ 2 | 3 | @media screen { 4 | .str { color: #080 } /* string content */ 5 | .kwd { color: #008 } /* a keyword */ 6 | .com { color: #800 } /* a comment */ 7 | .typ { color: #606 } /* a type name */ 8 | .lit { color: #066 } /* a literal value */ 9 | /* punctuation, lisp open bracket, lisp close bracket */ 10 | .pun, .opn, .clo { color: #660 } 11 | .tag { color: #008 } /* a markup tag name */ 12 | .atn { color: #606 } /* a markup attribute name */ 13 | .atv { color: #080 } /* a markup attribute value */ 14 | .dec, .var { color: #606 } /* a declaration; a variable name */ 15 | .fun { color: red } /* a function name */ 16 | } 17 | 18 | /* Use higher contrast and text-weight for printable form. */ 19 | @media print, projection { 20 | .str { color: #060 } 21 | .kwd { color: #006; font-weight: bold } 22 | .com { color: #600; font-style: italic } 23 | .typ { color: #404; font-weight: bold } 24 | .lit { color: #044 } 25 | .pun, .opn, .clo { color: #440 } 26 | .tag { color: #006; font-weight: bold } 27 | .atn { color: #404 } 28 | .atv { color: #060 } 29 | } 30 | 31 | pre.prettyprint { 32 | padding: 8px; 33 | border: 1px solid #e1e1e8; 34 | } 35 | pre.prettyprint:not(.alert) { 36 | background-color: #f7f7f9; 37 | } 38 | 39 | pre.prettyprint.linenums { 40 | -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 41 | -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 42 | box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 43 | } 44 | ol.linenums { 45 | margin: 0 0 0 33px; /* IE indents via margin-left */ 46 | } 47 | ol.linenums li { 48 | padding-left: 12px; 49 | font-size:12px; 50 | color: #bebec5; 51 | line-height: 18px; 52 | text-shadow: 0 1px 0 #fff; 53 | list-style-type:decimal!important; 54 | } 55 | -------------------------------------------------------------------------------- /test/unit/modules/directives/body/mdDataTableCellDirectiveTest.js: -------------------------------------------------------------------------------- 1 | xdescribe('mdtColumnDirective', function(){ 2 | var $compile, 3 | $rootScope, 4 | $scope, 5 | element, 6 | elementScope; 7 | 8 | var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE'; 9 | 10 | beforeEach(module('mdtTemplates')); 11 | beforeEach(module('mdDataTable')); 12 | 13 | beforeEach(inject(function($injector){ 14 | $compile = $injector.get('$compile'); 15 | $rootScope = $injector.get('$rootScope'); 16 | 17 | $scope = $rootScope.$new(); 18 | })); 19 | 20 | describe('WHEN created', function(){ 21 | beforeEach(function(){ 22 | //given/when 23 | compileDirective(); 24 | }); 25 | 26 | xit('THEN it should have the required methods', function(){ 27 | 28 | //then 29 | expect(elementScope.getColumnAlignClass).toBeDefined(); 30 | expect(elementScope.getCellValue).toBeDefined(); 31 | }); 32 | }); 33 | 34 | function compileDirective(status){ 35 | var mainElement; 36 | 37 | switch(status){ 38 | case DIRECTIVE_DEFAULT_CASE: 39 | default: 40 | mainElement = $compile('' + 41 | '' + 42 | ' ' + 43 | ' A Column' + 44 | ' ' + 45 | ' ' + 46 | ' cell' + 47 | ' ' + 48 | '')($scope); 49 | } 50 | 51 | $scope.$digest(); 52 | 53 | elementScope = mainElement.find('tr td.column .ng-scope').scope(); 54 | element = mainElement.find('tr.column'); 55 | } 56 | }); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtCheckboxColumnFilter.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | Sort A-Z 6 |
7 | 8 |
9 | Select all - Clear 10 | 11 |
{{selectedItems.length}} Selected
12 |
13 | 14 |
15 | 19 | {{ transformChip(item) }} 20 | 21 |
22 | 23 |
24 | Ok 25 | Cancel 26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-data-table", 3 | "version": "2.2.0", 4 | "author": "Istvan Fodor ", 5 | "registry": "github", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/iamisti/mdDataTable" 9 | }, 10 | "license": "MIT", 11 | "devDependencies": { 12 | "codeclimate-test-reporter": "~0.1.1", 13 | "event-stream": "~3.3.4", 14 | "fstream": "^1.0.10", 15 | "glob": "~6.0.2", 16 | "gulp": "~3.9.0", 17 | "gulp-angular-filesort": "~1.1.1", 18 | "gulp-angular-templatecache": "~1.8.0", 19 | "gulp-cached": "~1.1.0", 20 | "gulp-compass": "~2.1.0", 21 | "gulp-concat": "~2.6.0", 22 | "gulp-connect": "~2.3.1", 23 | "gulp-inject": "~3.0.0", 24 | "gulp-jshint": "~2.0.0", 25 | "gulp-karma": "~0.0.5", 26 | "gulp-ng-annotate": "~1.1.0", 27 | "gulp-ngdocs": "^0.2.13", 28 | "gulp-plumber": "~1.0.1", 29 | "gulp-rename": "~1.2.2", 30 | "gulp-rimraf": "~0.2.0", 31 | "gulp-uglify": "~1.5.1", 32 | "gulp-watch": "~4.3.5", 33 | "jasmine-core": "^2.4.1", 34 | "jasmine-reporters": "~2.1.0", 35 | "jshint": "^2.9.3", 36 | "karma": "~0.13.15", 37 | "karma-coverage": "~0.5.3", 38 | "karma-jasmine": "~0.3.6", 39 | "karma-jasmine-jquery": "~0.1.1", 40 | "karma-ng-html2js-preprocessor": "~0.2.0", 41 | "karma-ng-scenario": "~0.1.0", 42 | "karma-phantomjs-launcher": "~0.2.1", 43 | "karma-traceur-preprocessor": "~0.4.0", 44 | "lodash": "~3.10.1", 45 | "main-bower-files": "~2.11.0", 46 | "phantomjs": "^2.1.7", 47 | "run-sequence": "~1.1.5", 48 | "traceur": "~0.0.95", 49 | "wallaby-ng-html2js-preprocessor": "^0.1.0" 50 | }, 51 | "dependencies": { 52 | "angular": "~1.5.8", 53 | "angular-animate": "~1.5.8", 54 | "angular-aria": "~1.5.8", 55 | "angular-material": "1.1.1", 56 | "angular-material-icons": "~v0.6.0", 57 | "angular-sanitize": "~1.5.8", 58 | "jquery": "~2.1.4", 59 | "lodash": "~3.10.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/modules/main/features/EditCellFeature/EditCellFeature.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function EditCellFeature($mdDialog){ 5 | 6 | var service = this; 7 | 8 | service.addRequiredFunctions = function($scope, ctrl){ 9 | 10 | $scope.saveRow = function(rowData){ 11 | var rawRowData = ctrl.dataStorage.getSavedRowData(rowData); 12 | 13 | $scope.saveRowCallback({row: rawRowData}); 14 | }; 15 | 16 | $scope.showEditDialog = function(ev, cellData, rowData){ 17 | var rect = ev.currentTarget.closest('td').getBoundingClientRect(); 18 | var position = { 19 | top: rect.top, 20 | left: rect.left 21 | }; 22 | 23 | var ops = { 24 | controller: 'InlineEditModalCtrl', 25 | targetEvent: ev, 26 | clickOutsideToClose: true, 27 | escapeToClose: true, 28 | focusOnOpen: false, 29 | locals: { 30 | position: position, 31 | cellData: JSON.parse(JSON.stringify(cellData)), 32 | mdtTranslations: $scope.mdtTranslations 33 | } 34 | }; 35 | 36 | if(cellData.attributes.editableField === 'smallEditDialog'){ 37 | ops.templateUrl = '/main/templates/smallEditDialog.html'; 38 | }else{ 39 | ops.templateUrl = '/main/templates/largeEditDialog.html'; 40 | } 41 | 42 | var that = this; 43 | $mdDialog.show(ops).then(function(cellValue){ 44 | cellData.value = cellValue; 45 | 46 | that.saveRow(rowData); 47 | }); 48 | }; 49 | } 50 | } 51 | 52 | angular 53 | .module('mdDataTable') 54 | .service('EditCellFeature', EditCellFeature); 55 | }()); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtChipsColumnFilter.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | Sort A-Z 6 |
7 | 8 |
9 | 11 | 12 | 18 | 19 | {{transformChip(item)}} 20 | 21 | 22 | No results found. 23 | 24 | 25 | 26 | 27 | 28 | {{transformChip($chip)}} 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 | Ok 37 | Cancel 38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /app/modules/app.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @description 6 | * Component resolution and creation flow: 7 | * 8 | * Because directives are not containing each other in their templates (e.g. not a general parent-child 9 | * relationship), that's why the resolution of different components are not obvious. They are working with 10 | * transclusion and it's rule will apply to the process flow. 11 | * Here is an overview on what directives and which part of that will execute in which order. 12 | * 13 | * 1. `mdtTable` controller 14 | * - basic services initialized for future usage by other directives 15 | * 16 | * 2. `mdtTable` link 17 | * - transclude `mdtHeaderRow` and all `mdtRow` elements generated contents (however it's not relevant, 18 | * the real generated content is generated with the help of the collected data by `TableStorageService`. 19 | * - bind some helper functions for real the generated content 20 | * 21 | * 3. Header resolution 22 | * 23 | * 3.1. `mdtHeaderRow` link 24 | * - transclude all `mdtColumn` directive's generated contents 25 | * 26 | * 3.2. `mdtColumn` link(s) 27 | * - add columns by the help of the `mdtTable` public API 28 | * - contents generated but not yet transcluded 29 | * 30 | * 4. Rows resolution 31 | * 32 | * 4.1. `mdtRow` controller(s) 33 | * - public function created which able to add a cell data to the locally stored array 34 | * 35 | * 4.2. `mdtRow` link(s) 36 | * - transclude all `mdtCell` directive's generated contents 37 | * - add the collected row data to the service by the help of `mdtTable` public API 38 | * 39 | * 4.3. `mdtCell` link(s) 40 | * - add cells data by the help of `mdtRow` public API 41 | * - contents generated but not yet transcluded 42 | * 43 | */ 44 | angular.module('mdDataTable', ['mdtTemplates', 'ngMaterial', 'ngMdIcons', 'ngSanitize']); 45 | }()); -------------------------------------------------------------------------------- /app/modules/main/features/PaginationFeature.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function PaginationFeature(mdtPaginationHelperFactory, mdtAjaxPaginationHelperFactory){ 5 | var service = this; 6 | 7 | service.initFeature = initFeature; 8 | service.startFeature = startFeature; 9 | 10 | function initFeature(scope, ctrl){ 11 | if(!scope.mdtRowPaginator){ 12 | ctrl.mdtPaginationHelper = scope.mdtPaginationHelper = mdtPaginationHelperFactory 13 | .getInstance(ctrl.dataStorage, scope.paginatedRows, scope.mdtRow); 14 | }else{ 15 | ctrl.mdtPaginationHelper = scope.mdtPaginationHelper = mdtAjaxPaginationHelperFactory.getInstance({ 16 | dataStorage: ctrl.dataStorage, 17 | paginationSetting: scope.paginatedRows, 18 | mdtRowOptions: scope.mdtRow, 19 | mdtRowPaginatorFunction: scope.mdtRowPaginator, 20 | mdtRowPaginatorErrorMessage: scope.mdtRowPaginatorErrorMessage, 21 | mdtRowPaginatorNoResultsMessage: scope.mdtRowPaginatorNoResultsMessage, 22 | mdtTriggerRequest: scope.mdtTriggerRequest 23 | }); 24 | } 25 | 26 | scope.isPaginationEnabled = function(){ 27 | if(scope.paginatedRows === true || 28 | (scope.paginatedRows && scope.paginatedRows.hasOwnProperty('isEnabled') && scope.paginatedRows.isEnabled === true)){ 29 | return true; 30 | } 31 | 32 | return false; 33 | }; 34 | 35 | ctrl.paginationFeature = { 36 | startPaginationFeature: function() { 37 | if (scope.mdtRowPaginator) { 38 | scope.mdtPaginationHelper.fetchPage(1); 39 | } 40 | } 41 | }; 42 | } 43 | 44 | function startFeature(ctrl){ 45 | ctrl.paginationFeature.startPaginationFeature(); 46 | } 47 | } 48 | 49 | angular 50 | .module('mdDataTable') 51 | .service('PaginationFeature', PaginationFeature); 52 | }()); -------------------------------------------------------------------------------- /app/modules/main/features/ColumnFilterFeature/directives/mdtChipsColumnFilterDirective.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function mdtChipsColumnFilterDirective(_, $timeout, ColumnFilterFeature, ColumnSortFeature){ 5 | return{ 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtChipsColumnFilter.html', 8 | scope: { 9 | confirmCallback: '=', 10 | cancelCallback: '&', 11 | headerRowData: '=' 12 | }, 13 | link: function($scope, element){ 14 | ColumnFilterFeature.positionColumnFilterBox(element); 15 | 16 | $scope.transformChip = transformChip; 17 | 18 | $scope.availableItems = []; 19 | $scope.selectedItems = _.map($scope.headerRowData.columnFilter.filtersApplied, _.clone); 20 | $scope.placeholderText = $scope.headerRowData.columnFilter.placeholderText || 'Filter column...'; 21 | 22 | //TODO: simplify structure 23 | $scope.sortingData = { 24 | columnSort: { 25 | sort: $scope.headerRowData.columnSort.sort 26 | } 27 | }; 28 | $scope.sortingCallback = ColumnSortFeature.sortingCallback; 29 | 30 | //destroying scope doesn't remove element, since it belongs to the body directly 31 | $scope.$on('$destroy', function(){ 32 | element.remove(); 33 | }); 34 | 35 | //focus input immediately 36 | $timeout(function(){ 37 | element.find('input').focus(); 38 | },0); 39 | 40 | function transformChip(chip) { 41 | if($scope.headerRowData.columnFilter.valuesTransformerCallback){ 42 | return $scope.headerRowData.columnFilter.valuesTransformerCallback(chip); 43 | } 44 | 45 | return chip; 46 | } 47 | } 48 | } 49 | } 50 | 51 | angular 52 | .module('mdDataTable') 53 | .directive('mdtChipsColumnFilter', mdtChipsColumnFilterDirective); 54 | })(); -------------------------------------------------------------------------------- /app/modules/main/templates/mdtGeneratedHeaderCellContent.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 22 | 23 | 24 |
25 | {{headerRowData.columnDefinition}} 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | {{headerRowData.columnDefinition}} 35 | 36 | 37 |
38 |
-------------------------------------------------------------------------------- /docs/partials/api/mdtRow.html: -------------------------------------------------------------------------------- 1 |

mdtRow 2 |
3 |
4 |

5 |

Description

6 |

Representing a row which should be placed inside mdt-table element directive.

7 |

Please note the following: This element has limited functionality. It cannot listen on data changes that happens outside of the 8 | component. E.g.: if you provide an ng-repeat to generate your data rows for the table, using this directive, 9 | it won't work well if this data will change. Since the way how transclusions work, it's (with my best 10 | knowledge) an impossible task to solve at the moment. If you intend to use dynamic data rows, it's still 11 | possible with using mdtRow attribute of mdtTable.

12 |
13 |

Dependencies

14 | 17 |

Usage

18 |
as element:
<mdt-row
19 |        [table-row-id="{string|integer}"]>
20 | </mdt-row>
21 |

Parameters

ParamTypeDetails
tableRowId
(optional)
stringinteger

when set table will have a uniqe id. In case of deleting a row will give 22 | back this id.

23 |
24 |

Example

25 |
26 | <mdt-table>
27 |     <mdt-header-row>
28 |         <mdt-column>Product name</mdt-column>
29 |         <mdt-column>Price</mdt-column>
30 |     </mdt-header-row>
31 | 
32 |     <mdt-row
33 |         ng-repeat="product in products"
34 |         table-row-id="{{product.id}}">
35 |         <mdt-cell>{{product.name}}</mdt-cell>
36 |         <mdt-cell>{{product.price}}</mdt-cell>
37 |     </mdt-row>
38 | </mdt-table>
39 | 
40 |
41 |
42 | -------------------------------------------------------------------------------- /test/unit/modules/features/EditCellFeature/EditCellFeatureTest.js: -------------------------------------------------------------------------------- 1 | describe('EditCellFeature', function(){ 2 | 3 | beforeEach(module('mdtTemplates')); 4 | beforeEach(module('mdDataTable')); 5 | 6 | describe('WHEN calling `addRequiredFunctions`', function(){ 7 | var scope; 8 | 9 | beforeEach(inject(function($rootScope){ 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('AND feature is used THEN required functions should be added to the scope', inject(function($rootScope, EditCellFeature){ 14 | //given/when 15 | EditCellFeature.addRequiredFunctions(scope); 16 | 17 | //then 18 | expect(scope.saveRow).toBeDefined(); 19 | expect(scope.showEditDialog).toBeDefined(); 20 | })); 21 | 22 | describe('WHEN `saveRow` is called', function(){ 23 | var rowDataToSave; 24 | var rawRowData; 25 | var ctrl; 26 | 27 | beforeEach(function(){ 28 | scope.saveRowCallback = angular.noop; 29 | 30 | rowDataToSave = { 31 | 'some': 'data' 32 | }; 33 | 34 | rawRowData = { 35 | 'other': 'thing here' 36 | }; 37 | 38 | ctrl = { 39 | dataStorage: { 40 | getSavedRowData: angular.noop 41 | } 42 | }; 43 | 44 | spyOn(ctrl.dataStorage, 'getSavedRowData').and.returnValue(rawRowData); 45 | spyOn(scope, 'saveRowCallback'); 46 | }); 47 | 48 | it('THEN it should save the result into the data storage', inject(function(EditCellFeature){ 49 | //given 50 | EditCellFeature.addRequiredFunctions(scope, ctrl); 51 | 52 | //when 53 | scope.saveRow(rowDataToSave); 54 | 55 | //then 56 | expect(ctrl.dataStorage.getSavedRowData).toHaveBeenCalledWith(rowDataToSave); 57 | })); 58 | 59 | it('THEN it should publish the result by calling the callback', inject(function(EditCellFeature){ 60 | //given 61 | EditCellFeature.addRequiredFunctions(scope, ctrl); 62 | 63 | //when 64 | scope.saveRow(rowDataToSave); 65 | 66 | //then 67 | expect(scope.saveRowCallback).toHaveBeenCalledWith({row: rawRowData}); 68 | })); 69 | }); 70 | }); 71 | }); -------------------------------------------------------------------------------- /test/unit/modules/features/ColumnSortFeature/ColumnSortFeatureTest.js: -------------------------------------------------------------------------------- 1 | describe('ColumnSortFeature', function(){ 2 | 3 | beforeEach(module('mdtTemplates')); 4 | beforeEach(module('mdDataTable')); 5 | 6 | describe('WHEN calling `appendHeaderCellData`', function(){ 7 | var scope; 8 | 9 | beforeEach(inject(function($rootScope){ 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('AND feature is used THEN it needs to add the feature related default variables to the passed objects', inject(function(ColumnSortFeature){ 14 | //given 15 | var cellDataToStore = {}; 16 | var columnSortOptions = true; 17 | 18 | //when 19 | ColumnSortFeature.appendHeaderCellData(cellDataToStore, columnSortOptions); 20 | 21 | //then 22 | expect(cellDataToStore.columnSort).toBeDefined(); 23 | expect(cellDataToStore.columnSort.isEnabled).toBe(true); 24 | expect(cellDataToStore.columnSort.sort).toBe(false); 25 | expect(cellDataToStore.columnSort.comparator).toBe(false); 26 | })); 27 | 28 | it('AND feature is used AND comparator is defined THEN it needs to add the feature related default variables to the passed objects', inject(function(ColumnSortFeature){ 29 | //given 30 | var cellDataToStore = {}; 31 | var columnSortOptions = { 32 | comparator: function (){} 33 | }; 34 | 35 | //when 36 | ColumnSortFeature.appendHeaderCellData(cellDataToStore, columnSortOptions); 37 | 38 | //then 39 | expect(cellDataToStore.columnSort).toBeDefined(); 40 | expect(cellDataToStore.columnSort.isEnabled).toBe(true); 41 | expect(cellDataToStore.columnSort.sort).toBe(false); 42 | expect(cellDataToStore.columnSort.comparator).toEqual(columnSortOptions.comparator); 43 | })); 44 | 45 | it('AND feature is not used THEN it must not add the feature related default variables to the passed objects', inject(function(ColumnSortFeature){ 46 | //given 47 | var cellDataToStore = {}; 48 | 49 | //when 50 | ColumnSortFeature.appendHeaderCellData(cellDataToStore); 51 | 52 | //then 53 | expect(cellDataToStore.columnSort).toBeDefined(); 54 | expect(cellDataToStore.columnSort.isEnabled).toBe(false); 55 | expect(cellDataToStore.columnSort.sort).not.toBeDefined(); 56 | })); 57 | }); 58 | }); -------------------------------------------------------------------------------- /app/modules/main/directives/helpers/mdtAddHtmlContentToCellDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtAddHtmlContentToCellDirective($parse, $compile, $rootScope){ 5 | return { 6 | restrict: 'A', 7 | require: '^?mdtTable', 8 | link: function($scope, element, attr, ctrl){ 9 | 10 | //for performance reasons we keep the parsedValue over here, since we need to reuse it twice. 11 | var parsedValue; 12 | 13 | $scope.$watch(function(){ 14 | //this needs to be like that. Passing only `attr.mdtAddHtmlContentToCell` will cause digest to go crazy 10 times. 15 | // so we has to say explicitly that we only want to watch the content and nor the attributes, or the additional metadata. 16 | parsedValue = $parse(attr.mdtAddHtmlContentToCell)($scope); 17 | 18 | return parsedValue.value; 19 | 20 | }, function(val){ 21 | element.empty(); 22 | 23 | // ctrl doesn't exist on the first row, making html content impossible to show up. 24 | // TODO: make it as a global service .... I know but any better idea? 25 | if(parsedValue.columnKey && ctrl && ctrl.dataStorage.customCells[parsedValue.columnKey]){ 26 | var customCellData = ctrl.dataStorage.customCells[parsedValue.columnKey]; 27 | 28 | var clonedHtml = customCellData.htmlContent; 29 | 30 | //append value to the scope 31 | var localScope = $rootScope.$new(); 32 | 33 | if(parsedValue.rowId){ 34 | localScope.rowId = parsedValue.rowId; 35 | } 36 | 37 | localScope.clientScope = customCellData.scope; 38 | localScope.value = val; 39 | 40 | $compile(clonedHtml)(localScope, function(cloned){ 41 | element.append(cloned); 42 | }); 43 | 44 | }else{ 45 | element.append(val); 46 | } 47 | 48 | }, false); 49 | // issue with false value. If fields are editable then it won't reflect the change. 50 | } 51 | }; 52 | } 53 | 54 | angular 55 | .module('mdDataTable') 56 | .directive('mdtAddHtmlContentToCell', mdtAddHtmlContentToCellDirective); 57 | }()); -------------------------------------------------------------------------------- /app/modules/main/directives/body/mdtRowDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @ngdoc directive 6 | * @name mdtRow 7 | * @restrict E 8 | * @requires mdtTable 9 | * 10 | * @description 11 | * Representing a row which should be placed inside `mdt-table` element directive. 12 | * 13 | * Please note the following: This element has limited functionality. It cannot listen on data changes that happens outside of the 14 | * component. E.g.: if you provide an ng-repeat to generate your data rows for the table, using this directive, 15 | * it won't work well if this data will change. Since the way how transclusions work, it's (with my best 16 | * knowledge) an impossible task to solve at the moment. If you intend to use dynamic data rows, it's still 17 | * possible with using mdtRow attribute of mdtTable. 18 | * 19 | * @param {string|integer=} tableRowId when set table will have a uniqe id. In case of deleting a row will give 20 | * back this id. 21 | * 22 | * @example 23 | *
24 |      *  
25 |      *      
26 |      *          Product name
27 |      *          Price
28 |      *      
29 |      *
30 |      *      
33 |      *          {{product.name}}
34 |      *          {{product.price}}
35 |      *      
36 |      *  
37 |      * 
38 | */ 39 | function mdtRowDirective(){ 40 | return { 41 | restrict: 'E', 42 | transclude: true, 43 | require: '^mdtTable', 44 | scope: { 45 | tableRowId: '=' 46 | }, 47 | controller: function($scope){ 48 | var vm = this; 49 | 50 | vm.addToRowDataStorage = addToRowDataStorage; 51 | $scope.rowDataStorage = []; 52 | 53 | function addToRowDataStorage(value, attributes){ 54 | $scope.rowDataStorage.push({value: value, attributes: attributes}); 55 | } 56 | }, 57 | link: function($scope, element, attrs, ctrl, transclude){ 58 | appendColumns(); 59 | 60 | ctrl.dataStorage.addRowData($scope.tableRowId, $scope.rowDataStorage); 61 | 62 | function appendColumns(){ 63 | transclude(function (clone) { 64 | element.append(clone); 65 | }); 66 | } 67 | } 68 | }; 69 | } 70 | 71 | angular 72 | .module('mdDataTable') 73 | .directive('mdtRow', mdtRowDirective); 74 | }()); -------------------------------------------------------------------------------- /app/modules/main/features/ColumnFilterFeature/directives/mdtDropdownColumnFilterDirective.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function mdtDropdownColumnFilterDirective(ColumnFilterFeature, ColumnSortFeature){ 5 | return{ 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtDropdownColumnFilter.html', 8 | scope: { 9 | confirmCallback: '=', 10 | cancelCallback: '&', 11 | headerRowData: '=' 12 | }, 13 | link: function($scope, element){ 14 | ColumnFilterFeature.positionColumnFilterBox(element); 15 | 16 | $scope.transformChip = transformChip; 17 | $scope.selectedItem = selectedItem; 18 | 19 | $scope.selectableItems = []; 20 | $scope.selectedItems = _.map($scope.headerRowData.columnFilter.filtersApplied, _.clone); 21 | $scope.oneSelectedItem = $scope.selectedItems.length ? transformChip($scope.selectedItems[0]) : undefined; 22 | $scope.placeholderText = $scope.headerRowData.columnFilter.placeholderText || 'Choose a value'; 23 | 24 | //TODO: simplify structure 25 | $scope.sortingData = { 26 | columnSort: { 27 | sort: $scope.headerRowData.columnSort.sort 28 | } 29 | }; 30 | $scope.sortingCallback = ColumnSortFeature.sortingCallback; 31 | 32 | //destroying scope doesn't remove element, since it belongs to the body directly 33 | $scope.$on('$destroy', function(){ 34 | element.remove(); 35 | }); 36 | 37 | //populating choosable values 38 | $scope.headerRowData.columnFilter.valuesProviderCallback().then(function(values){ 39 | if(values){ 40 | $scope.selectableItems = values; 41 | } 42 | }); 43 | 44 | function transformChip(chip) { 45 | if($scope.headerRowData.columnFilter.valuesTransformerCallback){ 46 | return $scope.headerRowData.columnFilter.valuesTransformerCallback(chip); 47 | } 48 | 49 | return chip; 50 | } 51 | 52 | function selectedItem(){ 53 | if(typeof $scope.oneSelectedItem !== 'undefined'){ 54 | var result = _.find($scope.selectableItems, function(anItem){ 55 | return transformChip(anItem) === $scope.oneSelectedItem 56 | }); 57 | 58 | if(result){ 59 | $scope.selectedItems = [result]; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | angular 68 | .module('mdDataTable') 69 | .directive('mdtDropdownColumnFilter', mdtDropdownColumnFilterDirective); 70 | })(); -------------------------------------------------------------------------------- /docs/partials/api/mdtCell.html: -------------------------------------------------------------------------------- 1 |

mdtCell 2 |
3 |
4 |

5 |

Description

6 |

Representing a cell which should be placed inside mdt-row element directive.

7 |
8 |

Dependencies

9 | 14 |

Usage

15 |
as element:
<mdt-cell
16 |        [html-content="{boolean}"]
17 |        [editable-field="{string}"]
18 |        [editable-field-title="{string}"]
19 |        [editable-field-max-length="{number}"]>
20 | </mdt-cell>
21 |

Parameters

ParamTypeDetails
htmlContent
(optional)
boolean

if set to true, then html content can be placed into the content of the directive.

22 |
editableField
(optional)
string

if set, then content can be editable.

23 |
 Available modes are:
24 | 
25 |  - "smallEditDialog" - A simple, one-field edit dialog on click
26 |  - "largeEditDialog" - A complex, flexible edit edit dialog on click
27 | 
editableFieldTitle
(optional)
string

if set, then it sets the title of the dialog. (only for largeEditDialog)

28 |
editableFieldMaxLength
(optional)
number

if set, then it sets the maximum length of the field.

29 |
30 |

Example

31 |
32 | <mdt-table>
33 |     <mdt-header-row>
34 |         <mdt-column>Product name</mdt-column>
35 |         <mdt-column>Price</mdt-column>
36 |         <mdt-column>Details</mdt-column>
37 |     </mdt-header-row>
38 | 
39 |     <mdt-row ng-repeat="product in ctrl.products">
40 |         <mdt-cell>{{product.name}}</mdt-cell>
41 |         <mdt-cell>{{product.price}}</mdt-cell>
42 |         <mdt-cell html-content="true">
43 |             <a href="productdetails/{{product.id}}">more details</a>
44 |         </mdt-cell>
45 |     </mdt-row>
46 | </mdt-table>
47 | 
48 |
49 |
50 | -------------------------------------------------------------------------------- /app/modules/main/directives/body/mdtCellDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @ngdoc directive 6 | * @name mdtCell 7 | * @restrict E 8 | * @requires mdtTable 9 | * @requires mdtRow 10 | * 11 | * @description 12 | * Representing a cell which should be placed inside `mdt-row` element directive. 13 | * 14 | * @param {boolean=} htmlContent if set to true, then html content can be placed into the content of the directive. 15 | * @param {string=} editableField if set, then content can be editable. 16 | * 17 | * Available modes are: 18 | * 19 | * - "smallEditDialog" - A simple, one-field edit dialog on click 20 | * - "largeEditDialog" - A complex, flexible edit edit dialog on click 21 | * 22 | * @param {string=} editableFieldTitle if set, then it sets the title of the dialog. (only for `largeEditDialog`) 23 | * @param {number=} editableFieldMaxLength if set, then it sets the maximum length of the field. 24 | * 25 | * 26 | * @example 27 | *
28 |      *  
29 |      *      
30 |      *          Product name
31 |      *          Price
32 |      *          Details
33 |      *      
34 |      *
35 |      *      
36 |      *          {{product.name}}
37 |      *          {{product.price}}
38 |      *          
39 |      *              more details
40 |      *          
41 |      *      
42 |      *  
43 |      * 
44 | */ 45 | function mdtCellDirective($interpolate){ 46 | return { 47 | restrict: 'E', 48 | replace: true, 49 | transclude: true, 50 | require: '^mdtRow', 51 | link: function($scope, element, attr, mdtRowCtrl, transclude){ 52 | 53 | var attributes = { 54 | htmlContent: attr.htmlContent ? attr.htmlContent : false, 55 | editableField: attr.editableField ? attr.editableField : false, 56 | editableFieldTitle: attr.editableFieldTitle ? attr.editableFieldTitle : false, 57 | editableFieldMaxLength: attr.editableFieldMaxLength ? attr.editableFieldMaxLength : false 58 | }; 59 | 60 | transclude(function (clone) { 61 | 62 | if(attr.htmlContent){ 63 | mdtRowCtrl.addToRowDataStorage(clone, attributes); 64 | }else{ 65 | //TODO: better idea? 66 | var cellValue = $interpolate(clone.html())($scope.$parent); 67 | 68 | mdtRowCtrl.addToRowDataStorage(cellValue, attributes); 69 | } 70 | }); 71 | } 72 | }; 73 | } 74 | 75 | angular 76 | .module('mdDataTable') 77 | .directive('mdtCell', mdtCellDirective); 78 | }()); -------------------------------------------------------------------------------- /app/modules/main/factories/mdtPaginationHelperFactory.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtPaginationHelperFactory(PaginatorTypeProvider, _){ 5 | 6 | function mdtPaginationHelper(dataStorage, paginationSetting){ 7 | this.paginatorType = PaginatorTypeProvider.ARRAY; 8 | 9 | this.dataStorage = dataStorage; 10 | 11 | if(paginationSetting && 12 | paginationSetting.hasOwnProperty('rowsPerPageValues') && 13 | paginationSetting.rowsPerPageValues.length > 0){ 14 | 15 | this.rowsPerPageValues = paginationSetting.rowsPerPageValues; 16 | }else{ 17 | this.rowsPerPageValues = [10,20,30,50,100]; 18 | } 19 | 20 | this.rowsPerPage = this.rowsPerPageValues[0]; 21 | this.page = 1; 22 | } 23 | 24 | mdtPaginationHelper.prototype.calculateVisibleRows = function (){ 25 | var that = this; 26 | 27 | _.each(this.dataStorage.storage, function (rowData, index) { 28 | if(index >= that.getStartRowIndex() && index <= that.getEndRowIndex()) { 29 | rowData.optionList.visible = true; 30 | } else { 31 | rowData.optionList.visible = false; 32 | } 33 | }); 34 | }; 35 | 36 | mdtPaginationHelper.prototype.getStartRowIndex = function(){ 37 | return (this.page-1) * this.rowsPerPage; 38 | }; 39 | 40 | mdtPaginationHelper.prototype.getEndRowIndex = function(){ 41 | var lastItem = this.getStartRowIndex() + this.rowsPerPage-1; 42 | 43 | if(this.dataStorage.storage.length < lastItem){ 44 | return this.dataStorage.storage.length - 1; 45 | } 46 | 47 | return lastItem; 48 | }; 49 | 50 | mdtPaginationHelper.prototype.getTotalRowsCount = function(){ 51 | return this.dataStorage.storage.length; 52 | }; 53 | 54 | mdtPaginationHelper.prototype.getRows = function(){ 55 | this.calculateVisibleRows(); 56 | 57 | return this.dataStorage.storage; 58 | }; 59 | 60 | mdtPaginationHelper.prototype.previousPage = function(){ 61 | if(this.hasPreviousPage()){ 62 | this.page--; 63 | } 64 | }; 65 | 66 | mdtPaginationHelper.prototype.nextPage = function(){ 67 | if(this.hasNextPage()){ 68 | this.page++; 69 | } 70 | }; 71 | 72 | mdtPaginationHelper.prototype.hasNextPage = function(){ 73 | var totalPages = Math.ceil(this.getTotalRowsCount() / this.rowsPerPage); 74 | 75 | return this.page < totalPages; 76 | }; 77 | 78 | mdtPaginationHelper.prototype.hasPreviousPage = function(){ 79 | return this.page > 1; 80 | }; 81 | 82 | mdtPaginationHelper.prototype.setRowsPerPage = function(rowsPerPage){ 83 | this.rowsPerPage = rowsPerPage; 84 | this.page = 1; 85 | }; 86 | 87 | return { 88 | getInstance: function(dataStorage, isEnabled){ 89 | return new mdtPaginationHelper(dataStorage, isEnabled); 90 | } 91 | }; 92 | } 93 | 94 | angular 95 | .module('mdDataTable') 96 | .service('mdtPaginationHelperFactory', mdtPaginationHelperFactory); 97 | }()); -------------------------------------------------------------------------------- /docs/css/doc_widgets.css: -------------------------------------------------------------------------------- 1 | ul.doc-example { 2 | list-style-type: none; 3 | position: relative; 4 | font-size: 14px; 5 | } 6 | 7 | ul.doc-example > li { 8 | border: 2px solid gray; 9 | border-radius: 5px; 10 | -moz-border-radius: 5px; 11 | -webkit-border-radius: 5px; 12 | background-color: white; 13 | margin-bottom: 20px; 14 | } 15 | 16 | ul.doc-example > li.doc-example-heading { 17 | border: none; 18 | border-radius: 0; 19 | margin-bottom: -10px; 20 | } 21 | 22 | span.nojsfiddle { 23 | float: right; 24 | font-size: 14px; 25 | margin-right:10px; 26 | margin-top: 10px; 27 | } 28 | 29 | form.jsfiddle { 30 | position: absolute; 31 | right: 0; 32 | z-index: 1; 33 | height: 14px; 34 | } 35 | 36 | form.jsfiddle button { 37 | cursor: pointer; 38 | padding: 4px 10px; 39 | margin: 10px; 40 | background-color: #FFF; 41 | font-weight: bold; 42 | color: #7989D6; 43 | border-color: #7989D6; 44 | -moz-border-radius: 8px; 45 | -webkit-border-radius:8px; 46 | border-radius: 8px; 47 | } 48 | 49 | form.jsfiddle textarea, form.jsfiddle input { 50 | display: none; 51 | } 52 | 53 | li.doc-example-live { 54 | padding: 10px; 55 | font-size: 1.2em; 56 | } 57 | 58 | div.syntaxhighlighter { 59 | padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */ 60 | } 61 | 62 | /* TABS - tutorial environment navigation */ 63 | 64 | div.tabs-nav { 65 | height: 25px; 66 | position: relative; 67 | } 68 | 69 | div.tabs-nav ul li { 70 | list-style: none; 71 | display: inline-block; 72 | padding: 5px 10px; 73 | } 74 | 75 | div.tabs-nav ul li.current a { 76 | color: white; 77 | text-decoration: none; 78 | } 79 | 80 | div.tabs-nav ul li.current { 81 | background: #7989D6; 82 | -moz-box-shadow: 4px 4px 6px #48577D; 83 | -moz-border-radius-topright: 8px; 84 | -moz-border-radius-topleft: 8px; 85 | box-shadow: 4px 4px 6px #48577D; 86 | border-radius-topright: 8px; 87 | border-radius-topleft: 8px; 88 | -webkit-box-shadow: 4px 4px 6px #48577D; 89 | -webkit-border-top-right-radius: 8px; 90 | -webkit-border-top-left-radius: 8px; 91 | border-top-right-radius: 8px; 92 | border-top-left-radius: 8px; 93 | } 94 | 95 | div.tabs-content { 96 | padding: 4px; 97 | position: relative; 98 | background: #7989D6; 99 | -moz-border-radius: 8px; 100 | border-radius: 8px; 101 | -webkit-border-radius: 8px; 102 | } 103 | 104 | div.tabs-content-inner { 105 | margin: 1px; 106 | padding: 10px; 107 | background: white; 108 | border-radius: 6px; 109 | -moz-border-radius: 6px; 110 | -webkit-border-radius: 6px; 111 | } 112 | 113 | 114 | /* Tutorial Nav Bar */ 115 | 116 | #tutorial-nav { 117 | margin: 0.5em 0 1em 0; 118 | padding: 0; 119 | list-style-type: none; 120 | background: #7989D6; 121 | 122 | -moz-border-radius: 15px; 123 | -webkit-border-radius: 15px; 124 | border-radius: 15px; 125 | 126 | -moz-box-shadow: 4px 4px 6px #48577D; 127 | -webkit-box-shadow: 4px 4px 6px #48577D; 128 | box-shadow: 4px 4px 6px #48577D; 129 | } 130 | 131 | 132 | #tutorial-nav li { 133 | display: inline; 134 | } 135 | 136 | 137 | #tutorial-nav a:link, #tutorial-nav a:visited { 138 | font-size: 1.2em; 139 | color: #FFF; 140 | text-decoration: none; 141 | text-align: center; 142 | display: inline-block; 143 | width: 11em; 144 | padding: 0.2em 0; 145 | } 146 | 147 | 148 | #tutorial-nav a:hover { 149 | color: #000; 150 | } 151 | -------------------------------------------------------------------------------- /demo/developmentArea.html: -------------------------------------------------------------------------------- 1 |
2 | 21 | 22 | 23 | Dessert (100g serving) 33 | 34 | Service Size Units 42 | 43 | Fat (g) 52 | 53 | Carbs (g) 58 | Protein (g) 59 | Sodium (mg) 60 | Calcium (%) 61 | Iron (%) 62 | 63 | 64 | 65 | {{value}}, -{{rowId}}- 66 | 67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /app/modules/main/features/ColumnFilterFeature/directives/mdtCheckboxColumnFilterDirective.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function mdtCheckboxColumnFilterDirective(_, ColumnFilterFeature, ColumnSortFeature, ColumnSortDirectionProvider){ 5 | return{ 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtCheckboxColumnFilter.html', 8 | scope: { 9 | confirmCallback: '=', 10 | cancelCallback: '&', 11 | headerRowData: '=' 12 | }, 13 | link: function($scope, element){ 14 | ColumnFilterFeature.positionColumnFilterBox(element); 15 | 16 | $scope.transformChip = transformChip; 17 | $scope.selectableItems = []; 18 | $scope.selectedItems = _.map($scope.headerRowData.columnFilter.filtersApplied, _.clone); 19 | 20 | //TODO: simplify structure 21 | $scope.sortingData = { 22 | columnSort: { 23 | sort: $scope.headerRowData.columnSort.sort 24 | } 25 | }; 26 | $scope.sortingCallback = ColumnSortFeature.sortingCallback; 27 | 28 | //destroying scope doesn't remove element, since it belongs to the body directly 29 | $scope.$on('$destroy', function(){ 30 | element.remove(); 31 | }); 32 | 33 | //populating choosable values 34 | $scope.headerRowData.columnFilter.valuesProviderCallback().then(function(values){ 35 | if(values){ 36 | $scope.selectableItems = values 37 | } 38 | }); 39 | 40 | $scope.exists = function (item) { 41 | var result = _.findIndex($scope.selectedItems, function(arrayItem){ 42 | return transformChip(arrayItem) === transformChip(item); 43 | }); 44 | 45 | return result != -1; 46 | }; 47 | 48 | $scope.toggle = function (item) { 49 | var idx = _.findIndex($scope.selectedItems, function(arrayItem){ 50 | return transformChip(arrayItem) === transformChip(item); 51 | }); 52 | 53 | if (idx > -1) { 54 | $scope.selectedItems.splice(idx, 1); 55 | } 56 | else { 57 | $scope.selectedItems.push(item); 58 | } 59 | }; 60 | 61 | $scope.selectAll = function($event){ 62 | $event.preventDefault(); 63 | 64 | $scope.selectedItems = $scope.selectableItems.slice(0, $scope.selectableItems.length); 65 | }; 66 | 67 | $scope.clearAll = function($event){ 68 | $event.preventDefault(); 69 | 70 | $scope.selectedItems = []; 71 | }; 72 | 73 | function transformChip(chip) { 74 | if($scope.headerRowData.columnFilter.valuesTransformerCallback){ 75 | return $scope.headerRowData.columnFilter.valuesTransformerCallback(chip); 76 | } 77 | 78 | return chip; 79 | } 80 | } 81 | } 82 | } 83 | 84 | angular 85 | .module('mdDataTable') 86 | .directive('mdtCheckboxColumnFilter', mdtCheckboxColumnFilterDirective); 87 | })(); -------------------------------------------------------------------------------- /app/modules/main/features/ColumnSelectorFeature/ColumnSelectorFeature.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function ColumnSelectorFeature() { 5 | 6 | var service = this; 7 | 8 | /** 9 | * This is the first entry point when we initialize the feature. 10 | * 11 | * The method adds feature-related variable to the passed object. 12 | * 13 | * @param cellDataToStore 14 | */ 15 | service.appendHeaderCellData = function(cellDataToStore, columnSelectorFeature, isColumnExcludedFromColumnSelector, hideColumnByDefault) { 16 | if(!columnSelectorFeature.isEnabled){ 17 | return; 18 | } 19 | 20 | cellDataToStore.columnSelectorFeature = {}; 21 | 22 | if(isColumnExcludedFromColumnSelector){ 23 | cellDataToStore.columnSelectorFeature.isExcluded = true; 24 | }else{ 25 | cellDataToStore.columnSelectorFeature.isExcluded = false; 26 | } 27 | 28 | if(hideColumnByDefault){ 29 | cellDataToStore.columnSelectorFeature.isHidden = true; 30 | }else{ 31 | cellDataToStore.columnSelectorFeature.isHidden = false; 32 | } 33 | }; 34 | 35 | /** 36 | * This is the first entry point when we initialize the feature. 37 | * 38 | * The method adds feature-related variable to the passed object. 39 | * 40 | * @param cellDataToStore 41 | */ 42 | service.initFeature = function(scope, vm) { 43 | //TODO: backward compatible when there is only a string input 44 | scope.columnSelectorFeature = {}; 45 | 46 | if(scope.tableCard && scope.tableCard.columnSelector){ 47 | scope.columnSelectorFeature.isEnabled = true; 48 | }else{ 49 | scope.columnSelectorFeature.isEnabled = false; 50 | } 51 | 52 | vm.columnSelectorFeature = scope.columnSelectorFeature; 53 | }; 54 | 55 | /** 56 | * This is the second entry point when we initialize the feature. 57 | * 58 | * The method adds feature-related variable to the passed header rows array. 59 | * 60 | * @param headerRowsData 61 | */ 62 | service.initFeatureHeaderValues = function(headerRowsData, columnSelectorFeature){ 63 | if(columnSelectorFeature && columnSelectorFeature.isEnabled){ 64 | _.each(headerRowsData, function(item){ 65 | item.columnSelectorFeature.isVisible = !item.columnSelectorFeature.isHidden; 66 | }); 67 | } 68 | }; 69 | 70 | /** 71 | * Set the position of the panel. It's required to attach it to the outer container 72 | * of the component because otherwise some parts of the panel can became partially or fully hidden 73 | * (e.g.: when table has only one row to show) 74 | */ 75 | service.positionElement = function(element){ 76 | var elementToPosition = element.parent().find('.mdt-column-chooser-button'); 77 | var elementPosition = elementToPosition.offset(); 78 | var rt = ($(window).width() - (elementPosition.left + elementToPosition.outerWidth())); 79 | 80 | var targetMetrics = { 81 | top: elementPosition.top + 55, 82 | right: rt 83 | }; 84 | 85 | element.css('position', 'absolute'); 86 | element.detach().appendTo('body'); 87 | 88 | element.css({ 89 | top: targetMetrics.top + 'px', 90 | right: targetMetrics.right + 'px', 91 | position:'absolute' 92 | }); 93 | } 94 | } 95 | 96 | angular 97 | .module('mdDataTable') 98 | .service('ColumnSelectorFeature', ColumnSelectorFeature); 99 | }()); -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | mdDataTable demo 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 |

Material Design Data Table - DEVELOPMENT

37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | {{aPage.name}} 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /demo/demoApp.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function DemoController($scope, PageService, $http, $compile){ 5 | $scope.isDevelopmentAreaActive = false; 6 | $scope.pages = PageService.getAllPages(); 7 | $scope.selectPage = selectPage; 8 | 9 | //init 10 | selectPage($scope.pages[0]); 11 | 12 | function selectPage(aPage){ 13 | $scope.selectedPage = aPage; 14 | 15 | if(aPage.name === 'Development area' || aPage.codepen === '-'){ 16 | $scope.isDevelopmentAreaActive = true; 17 | }else{ 18 | $scope.isDevelopmentAreaActive = false; 19 | 20 | $http.get('http://codepen.io/iamisti/pen/'+aPage.codepen+'.html').then(function(content){ 21 | angular.element('#myDiv').empty().append($compile(content.data)($scope)); 22 | }); 23 | } 24 | } 25 | } 26 | 27 | function PageService(){ 28 | var service = this; 29 | 30 | service.getAllPages = getAllPages; 31 | 32 | var pages = [ 33 | { 34 | name: 'Development area', 35 | codepen: '-' 36 | },{ 37 | name: 'Basic', 38 | codepen: 'mVOKEw' 39 | },{ 40 | name: 'Table card', 41 | codepen: 'bEBKgK' 42 | },{ 43 | name: 'Selectable Columns', 44 | codepen: 'RGvvWL' 45 | },{ 46 | name: 'Selectable rows', 47 | codepen: 'bEBKRj' 48 | },{ 49 | name: 'Selected rows callback', 50 | codepen: 'OMvbMj' 51 | },{ 52 | name: 'Sortable columns', 53 | codepen: 'dGOKzN' 54 | },{ 55 | name: 'Virtual Repeat', 56 | codepen: 'qbxLEQ' 57 | },{ 58 | name: 'Animated sort icon', 59 | codepen: 'MKbXOM' 60 | },{ 61 | name: 'Pagination', 62 | codepen: 'GoNGMy' 63 | },{ 64 | name: 'Ajax support', 65 | codepen: 'BjpNow' 66 | },{ 67 | name: 'Ajax search support', 68 | codepen: 'RRrjLk' 69 | },{ 70 | name: 'Ajax column filter support (NEW!)', 71 | codepen: 'kkvBxd' 72 | },{ 73 | name: 'Ajax html cell support', 74 | codepen: 'pbLYwq' 75 | },{ 76 | name: 'Search', 77 | codepen: 'bEBKYx' 78 | },{ 79 | name: 'Html support', 80 | codepen: 'LGLYjZ' 81 | },{ 82 | name: 'Editable fields - small dialog', 83 | codepen: 'LNYBZX' 84 | },{ 85 | name: 'Editable fields - large dialog', 86 | codepen: 'zqYLNj' 87 | },{ 88 | name: 'Vertical Scroll', 89 | codepen: 'mAKWLb' 90 | }/*,{ 91 | name: 'Ripple effect', 92 | codepen: 'xZRzpV' 93 | }*/ 94 | ]; 95 | 96 | function getAllPages(){ 97 | return pages; 98 | } 99 | } 100 | 101 | //already defined in external resources 102 | angular.module('demo', ['ngMaterial', 103 | 'developmentAreaApp', 104 | 'exampleApp', 105 | 'exampleApp2', 106 | 'exampleApp3', 107 | 'exampleApp4', 108 | 'exampleApp5', 109 | 'exampleApp6', 110 | 'exampleApp7', 111 | 'exampleApp8', 112 | 'exampleApp9', 113 | 'exampleApp10', 114 | 'exampleApp11', 115 | 'exampleApp12', 116 | 'exampleApp13', 117 | 'exampleApp14', 118 | 'exampleApp15', 119 | 'exampleApp16', 120 | 'exampleApp17', 121 | 'exampleApp18', 122 | 'exampleApp19' 123 | ]); 124 | 125 | angular.module('demo').service('PageService', PageService); 126 | angular.module('demo').controller('DemoController', DemoController); 127 | }()); -------------------------------------------------------------------------------- /test/unit/modules/features/ColumnFilterFeature/directives/mdtChipsColumnFilterDirectiveTest.js: -------------------------------------------------------------------------------- 1 | describe('ChipsColumnFilterDirective', function(){ 2 | 3 | var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE'; 4 | var _$compile; 5 | 6 | beforeEach(module('mdtTemplates')); 7 | beforeEach(module('mdDataTable')); 8 | 9 | beforeEach(inject(function($compile, ColumnFilterFeature){ 10 | _$compile = $compile; 11 | 12 | spyOn(ColumnFilterFeature, 'positionColumnFilterBox'); 13 | })); 14 | 15 | describe('WHEN initializing', function(){ 16 | var scope; 17 | 18 | beforeEach(inject(function($q, $rootScope){ 19 | scope = $rootScope.$new(); 20 | 21 | scope.confirmCallback = function(){}; 22 | scope.cancelCallback = function(){}; 23 | scope.headerRowData = { 24 | columnFilter: { 25 | filtersApplied: [], 26 | valuesProviderCallback: function(){ return $q.resolve(); } 27 | }, 28 | columnSort: {} 29 | }; 30 | })); 31 | 32 | it('THEN default values must be set', inject(function(){ 33 | //given/when 34 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 35 | 36 | //then 37 | expect(element.isolateScope().availableItems).toEqual([]); 38 | expect(element.isolateScope().selectedItems).toEqual([]); 39 | expect(element.isolateScope().placeholderText).toEqual('Filter column...'); 40 | })); 41 | 42 | it('AND custom placeholder must be set', inject(function(){ 43 | //given 44 | scope.headerRowData.columnFilter.placeholderText = 'Search please...'; 45 | 46 | //when 47 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 48 | 49 | //then 50 | expect(element.isolateScope().placeholderText).toEqual('Search please...'); 51 | })); 52 | }); 53 | 54 | describe('WHEN transforming items', function(){ 55 | var scope; 56 | 57 | beforeEach(inject(function($q, $rootScope){ 58 | scope = $rootScope.$new(); 59 | 60 | scope.confirmCallback = function(){}; 61 | scope.cancelCallback = function(){}; 62 | scope.headerRowData = { 63 | columnFilter: { 64 | filtersApplied: [], 65 | valuesProviderCallback: function(){ return $q.resolve();} 66 | }, 67 | columnSort: {} 68 | }; 69 | })); 70 | 71 | it('AND transform function were not provided THEN it needs to return with the item itself', inject(function(){ 72 | //given 73 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 74 | 75 | //when 76 | var result = element.isolateScope().transformChip('Ice Cream Sandwitch'); 77 | 78 | //then 79 | expect(result).toEqual('Ice Cream Sandwitch'); 80 | })); 81 | 82 | it('AND transform function were provided THEN it needs to return with the transformed value of the item', inject(function(){ 83 | //given 84 | scope.headerRowData.columnFilter.valuesTransformerCallback = function(item){ 85 | return item.name; 86 | }; 87 | 88 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 89 | 90 | //when 91 | var result = element.isolateScope().transformChip({name: 'Ice Cream Sandwitch'}); 92 | 93 | //then 94 | expect(result).toEqual('Ice Cream Sandwitch'); 95 | })); 96 | }); 97 | 98 | function compileDirective(scope, status){ 99 | var mainElement; 100 | 101 | switch(status){ 102 | case DIRECTIVE_DEFAULT_CASE: 103 | mainElement = _$compile('' + 104 | '')(scope); 109 | break; 110 | default: 111 | throw Error('Not implemented case'); 112 | } 113 | 114 | scope.$digest(); 115 | 116 | return mainElement; 117 | } 118 | }); -------------------------------------------------------------------------------- /docs/partials/api/mdtColumn.html: -------------------------------------------------------------------------------- 1 |

mdtColumn 2 |
3 |
4 |

5 |

Description

6 |

Representing a header column cell which should be placed inside mdt-header-row element directive.

7 |
8 |

Dependencies

9 | 12 |

Usage

13 |
as element:
<mdt-column
14 |        [align-rule="{string}"]
15 |        [column-sort="{boolean=|object}"]
16 |        [column-filter="{object}"]
17 |        [column-definition="{string}"]
18 |        [exclude-from-column-selector="{boolean}"]
19 |        [hide-column-by-default="{boolean}"]>
20 | </mdt-column>
21 |

Parameters

ParamTypeDetails
alignRule
(optional)
string

align cell content. This settings will have affect on each data cells in the same 22 | column (e.g. every x.th cell in every row).

23 |

Assignable values:

24 |
    25 |
  • 'left'
  • 26 |
  • 'right'
  • 27 |
28 |
columnSort
(optional)
boolean=object

sort data and display a sorted state in the header. Clicking on a column 29 | which is already sorted will reverse the sort order and rotate the sort icon.

30 |

When object is passed, then compare function can be passed for sorting the column data's. As every compare 31 | function, it gets two parameters and return with the compared result (-1,1,0)

32 |

Assignable values:

33 |
    34 |
  • true or false
  • 35 |
  • { comparator: function(a,b)}
  • 36 |
37 |
columnFilter
(optional)
object

if provided, user can activate column filter feature on the selected column

38 |

Assignable properties:

39 |
- {Function=} valuesProviderCallback required, function which provides the values into the column filter. It must return with a promise which resolves an array of strings/objects
40 | - {Function=} valuesTransformerCallback optional, function which transforms the provided objects into strings to be able to show it visually in the column filter
41 | - {string=} placeholderText optional, placeholder which will show up as a default text (available only for `chips` and `dropdown` filter types
42 | - {string=} filterType defines the type of the filter you want to use. Available options are: `chips`, `checkbox`, `dropdown`. If you don't specify it, the default will be `chips`
43 | 
columnDefinition
(optional)
string

displays a tooltip on hover.

44 |
excludeFromColumnSelector
(optional)
boolean

disables the column selection for the applied column for the column select feature.

45 |
hideColumnByDefault
(optional)
boolean

sets the target column as unselected for the column select feature.

46 |
47 |

Example

48 |
49 | <mdt-table>
50 |     <mdt-header-row>
51 |         <mdt-column align-rule="left">Product name</mdt-column>
52 |         <mdt-column
53 |             align-rule="right"
54 |             column-definition="The price of the product in gross.">Price</mdt-column>
55 |     </mdt-header-row>
56 | 
57 |     <mdt-row ng-repeat="product in ctrl.products">
58 |         <mdt-cell>{{product.name}}</mdt-cell>
59 |         <mdt-cell>{{product.price}}</mdt-cell>
60 |     </mdt-row>
61 | </mdt-table>
62 | 
63 |
64 |
65 | -------------------------------------------------------------------------------- /app/modules/main/features/ColumnSelectorFeature/directives/mdtColumnSelectorDirective.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function mdtColumnSelectorDirective(ColumnSelectorFeature, ColumnFilterFeature, PaginatorTypeProvider){ 5 | return{ 6 | restrict: 'E', 7 | templateUrl: '/main/templates/mdtColumnSelector.html', 8 | scope: true, 9 | link: function($scope, element){ 10 | ColumnSelectorFeature.positionElement(element); 11 | 12 | $scope.headerRowsData = _.map($scope.dataStorage.header, function(item){ 13 | //excluded content should also be in, since we use the index of the array to apply the changes. Do not exclude them. 14 | 15 | return { 16 | columnName: item.columnName, 17 | isVisible: item.columnSelectorFeature.isVisible, 18 | isExcluded: item.columnSelectorFeature.isExcluded 19 | }; 20 | }); 21 | 22 | //destroying scope doesn't remove element, since it belongs to the body directly 23 | $scope.$on('$destroy', function(){ 24 | element.remove(); 25 | }); 26 | 27 | $scope.checked = function (item) { 28 | return item.isVisible; 29 | }; 30 | 31 | $scope.toggle = function (item) { 32 | item.isVisible = !item.isVisible; 33 | }; 34 | 35 | $scope.selectAll = function($event){ 36 | $event.preventDefault(); 37 | 38 | _.each($scope.headerRowsData, function(item){ 39 | if(item.isExcluded){ 40 | return; 41 | } 42 | 43 | item.isVisible = true; 44 | }); 45 | }; 46 | 47 | $scope.clearAll = function($event){ 48 | $event.preventDefault(); 49 | 50 | _.each($scope.headerRowsData, function(item){ 51 | if(item.isExcluded){ 52 | return; 53 | } 54 | 55 | item.isVisible = false; 56 | }); 57 | }; 58 | 59 | $scope.isAllSelected = function(){ 60 | var result = _.find($scope.headerRowsData, function(item){ 61 | if(item.isExcluded){ 62 | return false; 63 | } 64 | 65 | return item.isVisible === false; 66 | }); 67 | 68 | return result ? false : true; 69 | }; 70 | 71 | $scope.isNothingSelected = function(){ 72 | var result = _.find($scope.headerRowsData, function(item){ 73 | if(item.isExcluded){ 74 | return false; 75 | } 76 | 77 | return item.isVisible === true; 78 | }); 79 | 80 | return result ? false : true; 81 | }; 82 | 83 | $scope.confirmCallback = function(params){ 84 | var paginator = params.paginator; 85 | var isAnyResetHappened = false; 86 | 87 | _.each($scope.dataStorage.header, function(item, index){ 88 | item.columnSelectorFeature.isVisible = $scope.headerRowsData[index].isVisible; 89 | 90 | if(!item.columnSelectorFeature.isVisible){ 91 | var result = ColumnFilterFeature.resetFiltersForColumn($scope.dataStorage, index); 92 | 93 | if(result){ 94 | isAnyResetHappened = true; 95 | } 96 | } 97 | }); 98 | 99 | $scope.columnSelectorFeature.isActive = false; 100 | 101 | if(isAnyResetHappened){ 102 | if(paginator.paginatorType === PaginatorTypeProvider.AJAX){ 103 | paginator.getFirstPage(); 104 | }else{ 105 | // no support for non-ajax yet 106 | } 107 | } 108 | }; 109 | 110 | $scope.cancelCallback = function(){ 111 | $scope.columnSelectorFeature.isActive = false; 112 | }; 113 | } 114 | } 115 | } 116 | 117 | angular 118 | .module('mdDataTable') 119 | .directive('mdtColumnSelector', mdtColumnSelectorDirective); 120 | })(); -------------------------------------------------------------------------------- /app/modules/main/directives/header/mdtColumnDirective.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | /** 5 | * @ngdoc directive 6 | * @name mdtColumn 7 | * @restrict E 8 | * @requires mdtTable 9 | * 10 | * @description 11 | * Representing a header column cell which should be placed inside `mdt-header-row` element directive. 12 | * 13 | * @param {string=} alignRule align cell content. This settings will have affect on each data cells in the same 14 | * column (e.g. every x.th cell in every row). 15 | * 16 | * Assignable values: 17 | * - 'left' 18 | * - 'right' 19 | * 20 | * @param {boolean=|object=} columnSort sort data and display a sorted state in the header. Clicking on a column 21 | * which is already sorted will reverse the sort order and rotate the sort icon. 22 | * 23 | * When object is passed, then compare function can be passed for sorting the column data's. As every compare 24 | * function, it gets two parameters and return with the compared result (-1,1,0) 25 | * 26 | * Assignable values: 27 | * - true or false 28 | * - { comparator: function(a,b)} 29 | * 30 | * @param {object=} columnFilter if provided, user can activate column filter feature on the selected column 31 | * 32 | * Assignable properties: 33 | * - {Function=} valuesProviderCallback required, function which provides the values into the column filter. It must return with a promise which resolves an array of strings/objects 34 | * - {Function=} valuesTransformerCallback optional, function which transforms the provided objects into strings to be able to show it visually in the column filter 35 | * - {string=} placeholderText optional, placeholder which will show up as a default text (available only for `chips` and `dropdown` filter types 36 | * - {string=} filterType defines the type of the filter you want to use. Available options are: `chips`, `checkbox`, `dropdown`. If you don't specify it, the default will be `chips` 37 | * 38 | * @param {string=} columnDefinition displays a tooltip on hover. 39 | * 40 | * @param {boolean=} excludeFromColumnSelector disables the column selection for the applied column for the column select feature. 41 | * 42 | * @param {boolean=} hideColumnByDefault sets the target column as unselected for the column select feature. 43 | * 44 | * @example 45 | *
 46 |      *  
 47 |      *      
 48 |      *          Product name
 49 |      *          Price
 52 |      *      
 53 |      *
 54 |      *      
 55 |      *          {{product.name}}
 56 |      *          {{product.price}}
 57 |      *      
 58 |      *  
 59 |      * 
60 | */ 61 | function mdtColumnDirective($interpolate, ColumnFilterFeature, ColumnSortFeature, ColumnSelectorFeature){ 62 | return { 63 | restrict: 'E', 64 | transclude: true, 65 | replace: true, 66 | scope: { 67 | alignRule: '@', 68 | columnDefinition: '@', 69 | columnSort: '=?', 70 | columnFilter: '=?', 71 | excludeFromColumnSelector: '=?', 72 | hideColumnByDefault: '=?' 73 | }, 74 | require: ['^mdtTable'], 75 | link: function ($scope, element, attrs, ctrl, transclude) { 76 | var mdtTableCtrl = ctrl[0]; 77 | 78 | transclude(function (clone) { 79 | // directive creates an isolate scope so use parent scope to resolve variables. 80 | var cellValue = $interpolate(clone.html())($scope.$parent); 81 | var cellDataToStore = { 82 | alignRule: $scope.alignRule, 83 | columnDefinition: $scope.columnDefinition, 84 | columnName: cellValue 85 | }; 86 | 87 | ColumnFilterFeature.appendHeaderCellData($scope, cellDataToStore, mdtTableCtrl.dataStorage); 88 | ColumnSortFeature.appendHeaderCellData(cellDataToStore, $scope.columnSort); 89 | ColumnSelectorFeature.appendHeaderCellData(cellDataToStore, mdtTableCtrl.columnSelectorFeature, $scope.excludeFromColumnSelector, $scope.hideColumnByDefault); 90 | 91 | mdtTableCtrl.dataStorage.addHeaderCellData(cellDataToStore); 92 | }); 93 | } 94 | }; 95 | } 96 | 97 | angular 98 | .module('mdDataTable') 99 | .directive('mdtColumn', mdtColumnDirective); 100 | }()); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | jsx: true 4 | 5 | env: 6 | amd: true 7 | browser: true 8 | es6: true 9 | jquery: true 10 | node: true 11 | 12 | # http://eslint.org/docs/rules/ 13 | rules: 14 | # Possible Errors 15 | comma-dangle: [2, never] 16 | no-cond-assign: 2 17 | no-console: 0 18 | no-constant-condition: 2 19 | no-control-regex: 2 20 | no-debugger: 2 21 | no-dupe-args: 2 22 | no-dupe-keys: 2 23 | no-duplicate-case: 2 24 | no-empty: 2 25 | no-empty-character-class: 2 26 | no-ex-assign: 2 27 | no-extra-boolean-cast: 2 28 | no-extra-parens: 0 29 | no-extra-semi: 2 30 | no-func-assign: 2 31 | no-inner-declarations: [2, functions] 32 | no-invalid-regexp: 2 33 | no-irregular-whitespace: 2 34 | no-negated-in-lhs: 2 35 | no-obj-calls: 2 36 | no-regex-spaces: 2 37 | no-sparse-arrays: 2 38 | no-unexpected-multiline: 2 39 | no-unreachable: 2 40 | use-isnan: 2 41 | valid-jsdoc: 0 42 | valid-typeof: 2 43 | 44 | # Best Practices 45 | accessor-pairs: 2 46 | block-scoped-var: 0 47 | complexity: [2, 6] 48 | consistent-return: 0 49 | curly: 0 50 | default-case: 0 51 | dot-location: 0 52 | dot-notation: 0 53 | eqeqeq: 2 54 | guard-for-in: 2 55 | no-alert: 2 56 | no-caller: 2 57 | no-case-declarations: 2 58 | no-div-regex: 2 59 | no-else-return: 0 60 | no-empty-label: 2 61 | no-empty-pattern: 2 62 | no-eq-null: 2 63 | no-eval: 2 64 | no-extend-native: 2 65 | no-extra-bind: 2 66 | no-fallthrough: 2 67 | no-floating-decimal: 0 68 | no-implicit-coercion: 0 69 | no-implied-eval: 2 70 | no-invalid-this: 0 71 | no-iterator: 2 72 | no-labels: 0 73 | no-lone-blocks: 2 74 | no-loop-func: 2 75 | no-magic-number: 0 76 | no-multi-spaces: 0 77 | no-multi-str: 0 78 | no-native-reassign: 2 79 | no-new-func: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-octal-escape: 2 83 | no-octal: 2 84 | no-proto: 2 85 | no-redeclare: 2 86 | no-return-assign: 2 87 | no-script-url: 2 88 | no-self-compare: 2 89 | no-sequences: 0 90 | no-throw-literal: 0 91 | no-unused-expressions: 2 92 | no-useless-call: 2 93 | no-useless-concat: 2 94 | no-void: 2 95 | no-warning-comments: 0 96 | no-with: 2 97 | radix: 2 98 | vars-on-top: 0 99 | wrap-iife: 2 100 | yoda: 0 101 | 102 | # Strict 103 | strict: 0 104 | 105 | # Variables 106 | init-declarations: 0 107 | no-catch-shadow: 2 108 | no-delete-var: 2 109 | no-label-var: 2 110 | no-shadow-restricted-names: 2 111 | no-shadow: 0 112 | no-undef-init: 2 113 | no-undef: 0 114 | no-undefined: 0 115 | no-unused-vars: 0 116 | no-use-before-define: 0 117 | 118 | # Node.js and CommonJS 119 | callback-return: 2 120 | global-require: 2 121 | handle-callback-err: 2 122 | no-mixed-requires: 0 123 | no-new-require: 0 124 | no-path-concat: 2 125 | no-process-exit: 2 126 | no-restricted-modules: 0 127 | no-sync: 0 128 | 129 | # Stylistic Issues 130 | array-bracket-spacing: 0 131 | block-spacing: 0 132 | brace-style: 0 133 | camelcase: 0 134 | comma-spacing: 0 135 | comma-style: 0 136 | computed-property-spacing: 0 137 | consistent-this: 0 138 | eol-last: 0 139 | func-names: 0 140 | func-style: 0 141 | id-length: 0 142 | id-match: 0 143 | indent: 0 144 | jsx-quotes: 0 145 | key-spacing: 0 146 | linebreak-style: 0 147 | lines-around-comment: 0 148 | max-depth: 0 149 | max-len: 0 150 | max-nested-callbacks: 0 151 | max-params: 0 152 | max-statements: [2, 30] 153 | new-cap: 0 154 | new-parens: 0 155 | newline-after-var: 0 156 | no-array-constructor: 0 157 | no-bitwise: 0 158 | no-continue: 0 159 | no-inline-comments: 0 160 | no-lonely-if: 0 161 | no-mixed-spaces-and-tabs: 0 162 | no-multiple-empty-lines: 0 163 | no-negated-condition: 0 164 | no-nested-ternary: 0 165 | no-new-object: 0 166 | no-plusplus: 0 167 | no-restricted-syntax: 0 168 | no-spaced-func: 0 169 | no-ternary: 0 170 | no-trailing-spaces: 0 171 | no-underscore-dangle: 0 172 | no-unneeded-ternary: 0 173 | object-curly-spacing: 0 174 | one-var: 0 175 | operator-assignment: 0 176 | operator-linebreak: 0 177 | padded-blocks: 0 178 | quote-props: 0 179 | quotes: 0 180 | require-jsdoc: 0 181 | semi-spacing: 0 182 | semi: 0 183 | sort-vars: 0 184 | space-after-keywords: 0 185 | space-before-blocks: 0 186 | space-before-function-paren: 0 187 | space-before-keywords: 0 188 | space-in-parens: 0 189 | space-infix-ops: 0 190 | space-return-throw-case: 0 191 | space-unary-ops: 0 192 | spaced-comment: 0 193 | wrap-regex: 0 194 | 195 | # ECMAScript 6 196 | arrow-body-style: 0 197 | arrow-parens: 0 198 | arrow-spacing: 0 199 | constructor-super: 0 200 | generator-star-spacing: 0 201 | no-arrow-condition: 0 202 | no-class-assign: 0 203 | no-const-assign: 0 204 | no-dupe-class-members: 0 205 | no-this-before-super: 0 206 | no-var: 0 207 | object-shorthand: 0 208 | prefer-arrow-callback: 0 209 | prefer-const: 0 210 | prefer-reflect: 0 211 | prefer-spread: 0 212 | prefer-template: 0 213 | require-yield: 0 214 | -------------------------------------------------------------------------------- /app/modules/main/factories/TableDataStorageFactory.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function TableDataStorageFactory($log, _){ 5 | 6 | function TableDataStorageService(){ 7 | this.storage = []; 8 | this.header = []; 9 | this.customCells = {}; 10 | } 11 | 12 | TableDataStorageService.prototype.addHeaderCellData = function(ops){ 13 | this.header.push(ops); 14 | }; 15 | 16 | TableDataStorageService.prototype.addRowData = function(explicitRowId, rowArray, className){ 17 | if(!(rowArray instanceof Array)){ 18 | $log.error('`rowArray` parameter should be array'); 19 | return; 20 | } 21 | 22 | this.storage.push({ 23 | rowId: explicitRowId, 24 | optionList: { 25 | selected: false, 26 | deleted: false, 27 | visible: true, 28 | className: className || false 29 | }, 30 | data: rowArray 31 | }); 32 | }; 33 | 34 | TableDataStorageService.prototype.getRowData = function(index){ 35 | if(!this.storage[index]){ 36 | $log.error('row is not exists at index: '+index); 37 | return; 38 | } 39 | 40 | return this.storage[index].data; 41 | }; 42 | 43 | TableDataStorageService.prototype.getRowOptions = function(index){ 44 | if(!this.storage[index]){ 45 | $log.error('row is not exists at index: '+index); 46 | return; 47 | } 48 | 49 | return this.storage[index].optionList; 50 | }; 51 | 52 | TableDataStorageService.prototype.setAllRowsSelected = function(isSelected, isPaginationEnabled){ 53 | if(typeof isSelected === 'undefined'){ 54 | $log.error('`isSelected` parameter is required'); 55 | return; 56 | } 57 | 58 | _.each(this.storage, function(rowData){ 59 | if(isPaginationEnabled) { 60 | if (rowData.optionList.visible) { 61 | rowData.optionList.selected = isSelected ? true : false; 62 | } 63 | }else{ 64 | rowData.optionList.selected = isSelected ? true : false; 65 | } 66 | }); 67 | }; 68 | 69 | TableDataStorageService.prototype.isAnyRowSelected = function(){ 70 | return _.some(this.storage, function(rowData){ 71 | return rowData.optionList.selected === true && rowData.optionList.deleted === false; 72 | }); 73 | }; 74 | 75 | TableDataStorageService.prototype.getNumberOfSelectedRows = function(){ 76 | var res = _.countBy(this.storage, function(rowData){ 77 | return rowData.optionList.selected === true && rowData.optionList.deleted === false ? 'selected' : 'unselected'; 78 | }); 79 | 80 | return res.selected ? res.selected : 0; 81 | }; 82 | 83 | TableDataStorageService.prototype.deleteSelectedRows = function(){ 84 | var deletedRows = []; 85 | 86 | _.each(this.storage, function(rowData){ 87 | if(rowData.optionList.selected && rowData.optionList.deleted === false){ 88 | 89 | if(rowData.rowId){ 90 | deletedRows.push(rowData.rowId); 91 | 92 | //Fallback when no id was specified 93 | } else{ 94 | deletedRows.push(rowData.data); 95 | } 96 | 97 | rowData.optionList.deleted = true; 98 | } 99 | }); 100 | 101 | return deletedRows; 102 | }; 103 | 104 | TableDataStorageService.prototype.getSelectedRows = function(){ 105 | var selectedRows = []; 106 | 107 | _.each(this.storage, function(rowData){ 108 | if(rowData.optionList.selected && rowData.optionList.deleted === false){ 109 | 110 | if(rowData.rowId){ 111 | selectedRows.push(rowData.rowId); 112 | 113 | //Fallback when no id was specified 114 | } else{ 115 | selectedRows.push(rowData.data); 116 | } 117 | } 118 | }); 119 | 120 | return selectedRows; 121 | }; 122 | 123 | TableDataStorageService.prototype.getSavedRowData = function(rowData){ 124 | var rawRowData = []; 125 | 126 | _.each(rowData.data, function(aCell){ 127 | rawRowData.push(aCell.value); 128 | }); 129 | 130 | return rawRowData; 131 | }; 132 | 133 | return { 134 | getInstance: function(){ 135 | return new TableDataStorageService(); 136 | } 137 | }; 138 | } 139 | 140 | angular 141 | .module('mdDataTable') 142 | .factory('TableDataStorageFactory', TableDataStorageFactory); 143 | }()); -------------------------------------------------------------------------------- /docs/js/docs-setup.js: -------------------------------------------------------------------------------- 1 | NG_DOCS={ 2 | "sections": { 3 | "api": "API Documentation" 4 | }, 5 | "pages": [ 6 | { 7 | "section": "api", 8 | "id": "mdtCell", 9 | "shortName": "mdtCell", 10 | "type": "directive", 11 | "moduleName": "mdtCell", 12 | "shortDescription": "Representing a cell which should be placed inside mdt-row element directive.", 13 | "keywords": "api cell click complex content ctrl details dialog directive edit editable editablefield editablefieldmaxlength editablefieldtitle element field flexible href html html-content htmlcontent inside largeeditdialog length maximum mdt-row mdtcell mdtrow mdttable modes ng-repeat one-field price product productdetails products representing set sets simple smalleditdialog title true" 14 | }, 15 | { 16 | "section": "api", 17 | "id": "mdtColumn", 18 | "shortName": "mdtColumn", 19 | "type": "directive", 20 | "moduleName": "mdtColumn", 21 | "shortDescription": "Representing a header column cell which should be placed inside mdt-header-row element directive.", 22 | "keywords": "activate affect align align-rule alignrule api applied array assignable cell cells checkbox chips clicking column column-definition columndefinition columnfilter columnsort comparator compare compared content ctrl data default defines directive disables display displays don dropdown element excludefromcolumnselector false feature filter filtertype function gross header hidecolumnbydefault hover icon inside left mdt-header-row mdtcolumn mdttable ng-repeat object objects optional options order parameters passed placeholder placeholdertext price product products promise properties provided representing required resolves result return reverse rotate row select selected selection sets settings sort sorted sorting strings target text tooltip transforms true type types unselected user values valuesprovidercallback valuestransformercallback visually" 23 | }, 24 | { 25 | "section": "api", 26 | "id": "mdtHeaderRow", 27 | "shortName": "mdtHeaderRow", 28 | "type": "directive", 29 | "moduleName": "mdtHeaderRow", 30 | "shortDescription": "Representing a header row which should be placed inside mdt-table element directive.", 31 | "keywords": "api directive directives element execute header inside main mdt-column mdt-table mdtheaderrow mdttable representing responsibility row transcluded" 32 | }, 33 | { 34 | "section": "api", 35 | "id": "mdtRow", 36 | "shortName": "mdtRow", 37 | "type": "directive", 38 | "moduleName": "mdtRow", 39 | "shortDescription": "Representing a row which should be placed inside mdt-table element directive.", 40 | "keywords": "api attribute best case change changes component data deleting directive dynamic element functionality generate impossible inside intend knowledge limited listen mdt-table mdtrow mdttable moment ng-repeat note price product products provide representing row rows set solve table table-row-id tablerowid task transclusions uniqe won work" 41 | }, 42 | { 43 | "section": "api", 44 | "id": "mdtTable", 45 | "shortName": "mdtTable", 46 | "type": "directive", 47 | "moduleName": "mdtTable", 48 | "shortDescription": "The base HTML tag for the component.", 49 | "keywords": "accepts actionicons actions activates additional ajax ajax-based alternateheaders animated animatesorticon api applied approaches argument array asc assignable assigned attribute base based basic benefit bottom callback called card cards change changes checkbox class clicked color column column-keys columnfilter columns columnselector columnsort compatible component configure contextual controller create created_by creator css currently custom customised data default delete deleted deleterowcallback deleting demo desc directive disable display docs dynamic element embedded enable enables error example exclude-from-column-selector explicitly field filter filtered fixed format function header headers height help html icon identifier implemented implementing indicator initialize input inside inspect isenabled item_name items key-value kind list listen ll loading manipulation manually md-virtual-repeat-container mdt-header-row mdt-row mdtloadingindicator mdtrow mdtrowpaginator mdtrowpaginatorerrormessage mdtrowpaginatornoresultsmessage mdttable mdttranslations mdttriggerrequest message names number options order overrides pagesize paginatedrows pagination paginator paging pairs parameters parse passed passing persistent persistentactions populate product promise properly properties property provide provided providing rejected request require returns ripple rippleeffect row rows rowsperpagevalues scrolling search selectablerows selected selectedrowcallback selecting selection set sets setup sizes sort sorted structure table table-row-class-name table-row-id table-row-id-key tablecard tag title titles tools top translations trigger triggering uniq update update_date values virtual virtualrepeat visible work working" 50 | } 51 | ], 52 | "apis": { 53 | "api": true 54 | }, 55 | "__file": "_FAKE_DEST_/js/docs-setup.js", 56 | "__options": { 57 | "startPage": "/api", 58 | "scripts": [ 59 | "js/angular.min.js", 60 | "js/angular-animate.min.js", 61 | "js/marked.js" 62 | ], 63 | "styles": [], 64 | "title": "API Documentation", 65 | "html5Mode": true, 66 | "editExample": true, 67 | "navTemplate": false, 68 | "navContent": "", 69 | "navTemplateData": {}, 70 | "loadDefaults": { 71 | "angular": true, 72 | "angularAnimate": true, 73 | "marked": true 74 | } 75 | }, 76 | "html5Mode": true, 77 | "editExample": true, 78 | "startPage": "/api", 79 | "scripts": [ 80 | "js/angular.min.js", 81 | "js/angular-animate.min.js", 82 | "js/marked.js" 83 | ] 84 | }; -------------------------------------------------------------------------------- /docs/css/docs.css: -------------------------------------------------------------------------------- 1 | img.AngularJS-small { 2 | width: 95px; 3 | height: 25px; 4 | } 5 | 6 | /* this is here to avoid the display=block shuffling of ngShow */ 7 | .breadcrumb li > * { 8 | float:left; 9 | margin:0 2px 0 0; 10 | } 11 | 12 | .breadcrumb { 13 | padding-top: 6px; 14 | padding-bottom: 0; 15 | line-height: 18px 16 | } 17 | 18 | .navbar img { 19 | max-height: 40px; 20 | padding-right: 20px; 21 | } 22 | 23 | .navbar img+.brand { 24 | padding: 10px 20px 10px 10px; 25 | } 26 | 27 | .clear-navbar { 28 | margin-top: 60px; 29 | } 30 | 31 | .footer { 32 | padding-top: 2em; 33 | background-color: #333; 34 | color: white; 35 | padding-bottom: 2em; 36 | } 37 | 38 | .spacer { 39 | height: 1em; 40 | } 41 | 42 | 43 | .icon-cog { 44 | line-height: 13px; 45 | } 46 | 47 | /* =============================== */ 48 | 49 | .form-search { 50 | margin-right: 10px; 51 | } 52 | 53 | .form-search .search-query { 54 | width: 180px; 55 | width: 200px \9; 56 | } 57 | 58 | .form-search .dropdown-menu { 59 | margin-left: 10px; 60 | } 61 | 62 | .form-search .code { 63 | font-family: monospace; 64 | font-weight: bold; 65 | font-size: 13px; 66 | color: black; 67 | } 68 | 69 | .form-search > ul.nav > li > a { 70 | margin: 0; 71 | overflow: auto; 72 | } 73 | 74 | .form-search > ul.nav > li.module { 75 | background-color: #d3d3d3; 76 | } 77 | 78 | .form-search > ul.nav > li.section { 79 | background-color: #ebebeb; 80 | min-height: 14px; 81 | } 82 | 83 | .form-search > ul.nav > li.first { 84 | padding-top: 6px; 85 | } 86 | 87 | .form-search > ul.nav > li.last { 88 | padding-bottom: 6px; 89 | } 90 | 91 | .form-search > ul.nav > li.last + li.api-list-item { 92 | margin-top: -6px; 93 | padding-bottom: 6px; 94 | } 95 | 96 | .form-search .well { 97 | border-color: #d3d3d3; 98 | padding: 0; 99 | margin-bottom: 15px; 100 | } 101 | 102 | .form-search .well .nav-header { 103 | text-transform: none; 104 | padding: 3px 1px; 105 | margin: 0; 106 | } 107 | 108 | .form-search .well .nav-header a { 109 | text-transform: none; 110 | color: black; 111 | } 112 | .form-search .well .nav-header a:hover { 113 | background-color: inherit; 114 | } 115 | 116 | .form-search .well li { 117 | line-height: 14px; 118 | } 119 | 120 | .form-search .well li a:focus { 121 | outline: none; 122 | } 123 | 124 | .form-search .well .guide { 125 | float: right; 126 | padding-top: 0; 127 | color: gray; 128 | } 129 | 130 | .form-search .module .guide { 131 | line-height: 20px; 132 | } 133 | 134 | .match > a, .nav > .match > a:hover { 135 | background-color: #dbeaf4; 136 | } 137 | 138 | /* =============================== */ 139 | /* Content */ 140 | /* =============================== */ 141 | 142 | .improve-docs { 143 | float: right; 144 | } 145 | 146 | .hint { 147 | font-size: .6em; 148 | color: #c0c0c0; 149 | display: block; 150 | } 151 | 152 | .content code { 153 | background-color: inherit; 154 | color: inherit; 155 | border: none; 156 | padding: 0; 157 | font-size: inherit; 158 | font-family: monospace; 159 | } 160 | 161 | .content h2, 162 | .content h3, 163 | .content h4, 164 | .content h5 { 165 | margin: 1em 0 5px; 166 | } 167 | 168 | .content h1 { 169 | font-size: 30px; 170 | line-height: 36px; 171 | } 172 | 173 | .content h2 { 174 | font-size: 24px; 175 | line-height: 36px; 176 | } 177 | 178 | .content h3 { 179 | line-height: 27px; 180 | font-size: 18px; 181 | } 182 | 183 | .content h4 { 184 | font-size: 15px; 185 | } 186 | 187 | ul.parameters > li > p, 188 | .returns > p { 189 | display: inline; 190 | } 191 | 192 | ul.methods > li, 193 | ul.properties > li, 194 | ul.events > li { 195 | list-style: none; 196 | min-height: 20px; 197 | padding: 19px; 198 | margin-bottom: 20px; 199 | background-color: #f5f5f5; 200 | border: 1px solid #eee; 201 | border: 1px solid rgba(0, 0, 0, 0.05); 202 | -webkit-border-radius: 4px; 203 | -moz-border-radius: 4px; 204 | border-radius: 4px; 205 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 206 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 207 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 208 | } 209 | 210 | .member.method > h2, 211 | .member.property > h2, 212 | .member.event > h2 { 213 | margin-bottom: .5em; 214 | } 215 | 216 | ul.methods > li > h3, 217 | ul.properties > li > h3, 218 | ul.events > li > h3 { 219 | margin: -19px -19px 1em -19px; 220 | padding: .25em 19px; 221 | background-color: #d3d3d3; 222 | font-family: monospace; 223 | } 224 | 225 | .center { 226 | display: block; 227 | margin: 2em auto; 228 | } 229 | 230 | .diagram { 231 | display: block; 232 | margin: 2em auto; 233 | padding: 1em; 234 | border: 1px solid black; 235 | 236 | -moz-box-shadow: 4px 4px 6px #48577D; 237 | -webkit-box-shadow: 4px 4px 6px #48577D; 238 | box-shadow: 4px 4px 6px #48577D; 239 | 240 | -moz-border-radius: 15px; 241 | -webkit-border-radius: 15px; 242 | border-radius: 15px; 243 | } 244 | 245 | .tutorial-nav { 246 | margin-left: 175px; 247 | color: black; 248 | margin-top: 2em; 249 | margin-bottom: 2em; 250 | } 251 | 252 | .tutorial-nav a { 253 | color: white; 254 | } 255 | 256 | .tutorial-nav a:hover { 257 | color: white; 258 | text-decoration: none; 259 | } 260 | 261 | .clear { 262 | clear: both; 263 | } 264 | 265 | .variables-matrix td { 266 | vertical-align:top; 267 | padding:5px; 268 | } 269 | 270 | .type-hint { 271 | display:inline-block; 272 | } 273 | 274 | .variables-matrix .type-hint { 275 | text-align:center; 276 | display:block; 277 | min-width:60px; 278 | } 279 | 280 | .type-hint + .type-hint { 281 | margin-top:5px; 282 | } 283 | 284 | .type-hint-string { 285 | background:#3a87ad; 286 | } 287 | 288 | .type-hint-object { 289 | background:#999; 290 | } 291 | 292 | .type-hint-array { 293 | background:#F90;; 294 | } 295 | 296 | .type-hint-boolean { 297 | background:rgb(18, 131, 39); 298 | } 299 | 300 | .type-hint-number { 301 | background:rgb(189, 63, 66); 302 | } 303 | -------------------------------------------------------------------------------- /test/unit/modules/features/ColumnFilterFeature/directives/mdtDropdownColumnFilterDirectiveTest.js: -------------------------------------------------------------------------------- 1 | describe('DropdownColumnFilterDirective', function(){ 2 | 3 | var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE'; 4 | var _$compile; 5 | 6 | beforeEach(module('mdtTemplates')); 7 | beforeEach(module('mdDataTable')); 8 | 9 | beforeEach(inject(function($compile, ColumnFilterFeature){ 10 | _$compile = $compile; 11 | 12 | spyOn(ColumnFilterFeature, 'positionColumnFilterBox'); 13 | })); 14 | 15 | describe('WHEN initializing', function(){ 16 | var scope; 17 | 18 | beforeEach(inject(function($q, $rootScope){ 19 | scope = $rootScope.$new(); 20 | 21 | scope.confirmCallback = function(){}; 22 | scope.cancelCallback = function(){}; 23 | scope.headerRowData = { 24 | columnFilter: { 25 | filtersApplied: [], 26 | valuesProviderCallback: function(){ return $q.resolve();} 27 | }, 28 | columnSort: {} 29 | }; 30 | })); 31 | 32 | it('THEN default values must be set', inject(function(){ 33 | //given/when 34 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 35 | 36 | //then 37 | expect(element.isolateScope().selectableItems).toEqual([]); 38 | expect(element.isolateScope().selectedItems).toEqual([]); 39 | })); 40 | 41 | it('AND selectable items must be filled', inject(function($q){ 42 | //given 43 | scope.headerRowData.columnFilter = { 44 | filtersApplied: [], 45 | valuesProviderCallback: function(){ return $q.resolve(['one', 'two', 'three']); } 46 | }; 47 | 48 | //when 49 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 50 | 51 | //then 52 | expect(element.isolateScope().selectableItems).toEqual(['one', 'two', 'three']); 53 | })); 54 | 55 | it('WHEN selected items are defined THEN it must be set', inject(function($q){ 56 | //given 57 | scope.headerRowData.columnFilter = { 58 | filtersApplied: ['two'], 59 | valuesProviderCallback: function(){ return $q.resolve(['one', 'two', 'three']); } 60 | }; 61 | 62 | //when 63 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 64 | 65 | //then 66 | expect(element.isolateScope().oneSelectedItem).toEqual('two'); 67 | })); 68 | }); 69 | 70 | describe('WHEN transforming items', function(){ 71 | var scope; 72 | 73 | beforeEach(inject(function($q, $rootScope){ 74 | scope = $rootScope.$new(); 75 | 76 | scope.confirmCallback = function(){}; 77 | scope.cancelCallback = function(){}; 78 | scope.headerRowData = { 79 | columnFilter: { 80 | filtersApplied: [], 81 | valuesProviderCallback: function(){ return $q.resolve();} 82 | }, 83 | columnSort: {} 84 | }; 85 | })); 86 | 87 | it('AND transform function were not provided THEN it needs to return with the item itself', inject(function(){ 88 | //given 89 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 90 | 91 | //when 92 | var result = element.isolateScope().transformChip('Ice Cream Sandwitch'); 93 | 94 | //then 95 | expect(result).toEqual('Ice Cream Sandwitch'); 96 | })); 97 | 98 | it('AND transform function were provided THEN it needs to return with the transformed value of the item', inject(function(){ 99 | //given 100 | scope.headerRowData.columnFilter.valuesTransformerCallback = function(item){ 101 | return item.name; 102 | }; 103 | 104 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 105 | 106 | //when 107 | var result = element.isolateScope().transformChip({name: 'Ice Cream Sandwitch'}); 108 | 109 | //then 110 | expect(result).toEqual('Ice Cream Sandwitch'); 111 | })); 112 | }); 113 | 114 | describe('WHEN selecting an item', function(){ 115 | var scope; 116 | 117 | beforeEach(inject(function($q, $rootScope){ 118 | scope = $rootScope.$new(); 119 | 120 | scope.confirmCallback = function(){}; 121 | scope.cancelCallback = function(){}; 122 | scope.headerRowData = { 123 | columnFilter: { 124 | filtersApplied: [], 125 | valuesProviderCallback: function(){ return $q.resolve(['one', 'two', 'three']);} 126 | }, 127 | columnSort: {} 128 | }; 129 | })); 130 | 131 | it('THEN if value is undefined THEN it should not set', inject(function(){ 132 | //given 133 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 134 | 135 | //when 136 | element.isolateScope().selectedItem(); 137 | 138 | //then 139 | expect(element.isolateScope().selectedItems).toEqual([]); 140 | })); 141 | 142 | it('THEN if value is not undefined THEN it should set the selected items', inject(function(){ 143 | //given 144 | var element = compileDirective(scope, DIRECTIVE_DEFAULT_CASE); 145 | 146 | element.isolateScope().oneSelectedItem = 'two'; 147 | 148 | //when 149 | element.isolateScope().selectedItem(); 150 | 151 | //then 152 | expect(element.isolateScope().selectedItems).toEqual(['two']); 153 | })); 154 | }); 155 | 156 | function compileDirective(scope, status){ 157 | var mainElement; 158 | 159 | switch(status){ 160 | case DIRECTIVE_DEFAULT_CASE: 161 | mainElement = _$compile('' + 162 | '')(scope); 167 | break; 168 | default: 169 | throw Error('Not implemented case'); 170 | } 171 | 172 | scope.$digest(); 173 | 174 | return mainElement; 175 | } 176 | }); -------------------------------------------------------------------------------- /app/modules/main/features/ColumnFilterFeature/ColumnFilterFeature.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function ColumnFilterFeature(ColumnSortFeature, PaginatorTypeProvider){ 5 | 6 | var service = this; 7 | 8 | /** 9 | * This is the first entry point when we initialize the feature. 10 | * 11 | * The method adds feature-related variable to the passed object. 12 | * The variables gets stored afterwards in the dataStorage for the header cell 13 | * 14 | * @param $scope 15 | * @param cellDataToStore 16 | */ 17 | service.appendHeaderCellData = function($scope, cellDataToStore, dataStorage){ 18 | cellDataToStore.columnFilter = {}; 19 | 20 | if($scope.columnFilter && $scope.columnFilter.valuesProviderCallback){ 21 | cellDataToStore.columnFilter.isEnabled = true; 22 | cellDataToStore.columnFilter.filtersApplied = []; 23 | cellDataToStore.columnFilter.valuesProviderCallback = $scope.columnFilter.valuesProviderCallback; 24 | cellDataToStore.columnFilter.valuesTransformerCallback = $scope.columnFilter.valuesTransformerCallback; 25 | cellDataToStore.columnFilter.placeholderText = $scope.columnFilter.placeholderText; 26 | cellDataToStore.columnFilter.type = $scope.columnFilter.filterType || 'chips'; 27 | cellDataToStore.columnFilter.type = $scope.columnFilter.filterType || 'chips'; 28 | cellDataToStore.columnFilter.isActive = false; 29 | 30 | cellDataToStore.columnFilter.setColumnActive = function(bool){ 31 | //first we disable every column filter if any is active 32 | _.each(dataStorage.header, function(headerData){ 33 | if(headerData.columnFilter.isEnabled){ 34 | headerData.columnFilter.isActive = false; 35 | } 36 | }); 37 | 38 | //then we activate ours 39 | cellDataToStore.columnFilter.isActive = bool ? true : false; 40 | } 41 | }else{ 42 | cellDataToStore.columnFilter.isEnabled = false; 43 | } 44 | }; 45 | 46 | /** 47 | * Generating the needed functions and variables for the header cell which will 48 | * handle the actions of the column filter component. 49 | * 50 | * @param $scope 51 | * @param headerData 52 | * @param paginator 53 | */ 54 | service.initGeneratedHeaderCellContent = function($scope, headerData, paginator, dataStorage){ 55 | if(!headerData.columnFilter.isEnabled){ 56 | return; 57 | } 58 | 59 | $scope.columnFilterFeature = {}; 60 | 61 | $scope.columnFilterFeature.cancelFilterDialog = function(event){ 62 | if(event){ 63 | event.stopPropagation(); 64 | } 65 | 66 | headerData.columnFilter.setColumnActive(false); 67 | }; 68 | 69 | $scope.columnFilterFeature.confirmFilterDialog = function(params){ 70 | params.event.stopPropagation(); 71 | 72 | headerData.columnFilter.setColumnActive(false); 73 | 74 | headerData.columnFilter.filtersApplied = params.selectedItems; 75 | 76 | //applying changes to sort feature 77 | ColumnSortFeature.setHeaderSort(headerData, params.sortingData, dataStorage); 78 | 79 | if(paginator.paginatorType === PaginatorTypeProvider.AJAX){ 80 | paginator.getFirstPage(); 81 | }else{ 82 | // no support for non-ajax yet 83 | } 84 | } 85 | }; 86 | 87 | /** 88 | * Click handler for the feature when header cell gets clicked 89 | * @param $scope 90 | * @param headerRowData 91 | */ 92 | service.generatedHeaderCellClickHandler = function($scope, headerRowData, element){ 93 | if(!headerRowData.columnFilter.isEnabled) { 94 | return; 95 | } 96 | 97 | headerRowData.columnFilter.setColumnActive(!headerRowData.columnFilter.isActive); 98 | }; 99 | 100 | /** 101 | * Returns with an array of currently applied filters on the columns. 102 | * @param dataStorage 103 | * @param callbackArguments 104 | */ 105 | service.appendAppliedFiltersToCallbackArgument = function(dataStorage, callbackArguments){ 106 | var columnFilters = []; 107 | var isEnabled = false; 108 | 109 | _.each(dataStorage.header, function(headerData){ 110 | var filters = headerData.columnFilter.filtersApplied || []; 111 | 112 | if(headerData.columnFilter.isEnabled){ 113 | isEnabled = true; 114 | } 115 | 116 | columnFilters.push(filters); 117 | }); 118 | 119 | if(isEnabled){ 120 | callbackArguments.options.columnFilter = columnFilters; 121 | } 122 | }; 123 | 124 | service.resetFiltersForColumn = function(dataStorage, index){ 125 | if(dataStorage.header[index].columnFilter 126 | && dataStorage.header[index].columnFilter.isEnabled 127 | && dataStorage.header[index].columnFilter.filtersApplied.length){ 128 | 129 | dataStorage.header[index].columnFilter.filtersApplied = []; 130 | 131 | return true; 132 | } 133 | 134 | return false; 135 | }; 136 | 137 | /** 138 | * Set the position of the column filter panel. It's required to attach it to the outer container 139 | * of the component because otherwise some parts of the panel can became partially or fully hidden 140 | * (e.g.: when table has only one row to show) 141 | */ 142 | service.positionColumnFilterBox = function(element){ 143 | var elementPosition = element.closest('th').offset(); 144 | 145 | var targetMetrics = { 146 | top: elementPosition.top + 60, 147 | left: elementPosition.left 148 | }; 149 | 150 | element.css('position', 'absolute'); 151 | element.detach().appendTo('body'); 152 | 153 | element.css({ 154 | top: targetMetrics.top + 'px', 155 | left: targetMetrics.left + 'px', 156 | position:'absolute' 157 | }); 158 | } 159 | } 160 | 161 | angular 162 | .module('mdDataTable') 163 | .service('ColumnFilterFeature', ColumnFilterFeature); 164 | }()); -------------------------------------------------------------------------------- /app/modules/main/factories/mdtAjaxPaginationHelperFactory.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function mdtAjaxPaginationHelperFactory(ColumnFilterFeature, ColumnSortFeature, PaginatorTypeProvider, _){ 5 | 6 | function mdtAjaxPaginationHelper(params){ 7 | this.paginatorType = PaginatorTypeProvider.AJAX; 8 | 9 | this.dataStorage = params.dataStorage; 10 | this.rowOptions = params.mdtRowOptions; 11 | this.paginatorFunction = params.mdtRowPaginatorFunction; 12 | this.mdtRowPaginatorErrorMessage = params.mdtRowPaginatorErrorMessage || 'Ajax error during loading contents.'; 13 | this.mdtRowPaginatorNoResultsMessage = params.mdtRowPaginatorNoResultsMessage || 'No results.'; 14 | this.mdtTriggerRequest = params.mdtTriggerRequest; 15 | 16 | if(params.paginationSetting && 17 | params.paginationSetting.hasOwnProperty('rowsPerPageValues') && 18 | params.paginationSetting.rowsPerPageValues.length > 0){ 19 | 20 | this.rowsPerPageValues = params.paginationSetting.rowsPerPageValues; 21 | }else{ 22 | this.rowsPerPageValues = [10,20,30,50,100]; 23 | } 24 | 25 | this.rowsPerPage = this.rowsPerPageValues[0]; 26 | this.page = 1; 27 | this.totalResultCount = 0; 28 | this.totalPages = 0; 29 | 30 | this.isLoading = false; 31 | 32 | //fetching the 1st page 33 | //this.fetchPage(this.page); 34 | 35 | //triggering ajax call manually 36 | if(this.mdtTriggerRequest) { 37 | params.mdtTriggerRequest({ 38 | loadPageCallback: this.fetchPage.bind(this) 39 | }); 40 | } 41 | } 42 | 43 | mdtAjaxPaginationHelper.prototype.getStartRowIndex = function(){ 44 | return (this.page-1) * this.rowsPerPage; 45 | }; 46 | 47 | mdtAjaxPaginationHelper.prototype.getEndRowIndex = function(){ 48 | var lastItem = this.getStartRowIndex() + this.rowsPerPage - 1; 49 | 50 | if(this.totalResultCount < lastItem){ 51 | return this.totalResultCount - 1; 52 | } 53 | 54 | return lastItem; 55 | }; 56 | 57 | mdtAjaxPaginationHelper.prototype.getTotalRowsCount = function(){ 58 | return this.totalResultCount; 59 | }; 60 | 61 | mdtAjaxPaginationHelper.prototype.getRows = function(){ 62 | return this.dataStorage.storage; 63 | }; 64 | 65 | mdtAjaxPaginationHelper.prototype.previousPage = function(){ 66 | var that = this; 67 | if(this.hasPreviousPage()){ 68 | this.fetchPage(this.page-1).then(function(){ 69 | that.page--; 70 | }); 71 | } 72 | }; 73 | 74 | mdtAjaxPaginationHelper.prototype.nextPage = function(){ 75 | var that = this; 76 | if(this.hasNextPage()){ 77 | this.fetchPage(this.page+1).then(function(){ 78 | that.page++; 79 | }); 80 | } 81 | }; 82 | 83 | mdtAjaxPaginationHelper.prototype.getFirstPage = function(){ 84 | this.page = 1; 85 | 86 | this.fetchPage(this.page); 87 | }; 88 | 89 | mdtAjaxPaginationHelper.prototype.hasNextPage = function(){ 90 | return this.page < this.totalPages; 91 | }; 92 | 93 | mdtAjaxPaginationHelper.prototype.hasPreviousPage = function(){ 94 | return this.page > 1; 95 | }; 96 | 97 | mdtAjaxPaginationHelper.prototype.fetchPage = function(page){ 98 | this.isLoading = true; 99 | 100 | var that = this; 101 | 102 | var callbackArguments = {page: page, pageSize: this.rowsPerPage, options: {}}; 103 | 104 | ColumnFilterFeature.appendAppliedFiltersToCallbackArgument(this.dataStorage, callbackArguments); 105 | ColumnSortFeature.appendSortedColumnToCallbackArgument(this.dataStorage, callbackArguments); 106 | 107 | return this.paginatorFunction(callbackArguments) 108 | .then(function(data){ 109 | that.dataStorage.storage = []; 110 | that.setRawDataToStorage(that, data.results, that.rowOptions['table-row-id-key'], that.rowOptions['column-keys'], that.rowOptions); 111 | that.totalResultCount = data.totalResultCount; 112 | that.totalPages = Math.ceil(data.totalResultCount / that.rowsPerPage); 113 | 114 | if(that.totalResultCount == 0){ 115 | that.isNoResults = true; 116 | }else{ 117 | that.isNoResults = false; 118 | } 119 | 120 | that.isLoadError = false; 121 | that.isLoading = false; 122 | 123 | }, function(){ 124 | that.dataStorage.storage = []; 125 | 126 | that.isLoadError = true; 127 | that.isLoading = false; 128 | that.isNoResults = true; 129 | }); 130 | }; 131 | 132 | mdtAjaxPaginationHelper.prototype.setRawDataToStorage = function(that, data, tableRowIdKey, columnKeys, rowOptions){ 133 | var rowId; 134 | var columnValues = []; 135 | _.each(data, function(row){ 136 | rowId = _.get(row, tableRowIdKey); 137 | columnValues = []; 138 | 139 | _.each(columnKeys, function(columnKey){ 140 | //TODO: centralize adding column values into one place. 141 | // Duplication occurs at mdtCellDirective's link function. 142 | // Duplication in mdtTableDirective `_addRawDataToStorage` method! 143 | columnValues.push({ 144 | attributes: { 145 | editableField: false 146 | }, 147 | rowId: rowId, 148 | columnKey: columnKey, 149 | value: _.get(row, columnKey) 150 | }); 151 | }); 152 | 153 | var className = rowOptions['table-row-class-name'] ? rowOptions['table-row-class-name'](row) : false; 154 | 155 | that.dataStorage.addRowData(rowId, columnValues, className); 156 | }); 157 | }; 158 | 159 | mdtAjaxPaginationHelper.prototype.setRowsPerPage = function(rowsPerPage){ 160 | this.rowsPerPage = rowsPerPage; 161 | 162 | this.getFirstPage(); 163 | }; 164 | 165 | return { 166 | getInstance: function(dataStorage, isEnabled, paginatorFunction, rowOptions){ 167 | return new mdtAjaxPaginationHelper(dataStorage, isEnabled, paginatorFunction, rowOptions); 168 | } 169 | }; 170 | } 171 | 172 | angular 173 | .module('mdDataTable') 174 | .service('mdtAjaxPaginationHelperFactory', mdtAjaxPaginationHelperFactory); 175 | }()); 176 | -------------------------------------------------------------------------------- /app/modules/main/features/ColumnSortFeature/ColumnSortFeature.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | function ColumnSortFeature(ColumnSortDirectionProvider) { 5 | 6 | var service = this; 7 | 8 | /** 9 | * This is the first entry point when we initialize the feature. 10 | * 11 | * The method adds feature-related variable to the passed object. 12 | * 13 | * @param cellDataToStore 14 | */ 15 | service.appendHeaderCellData = function(cellDataToStore, columnSortOptions) { 16 | cellDataToStore.columnSort = {}; 17 | 18 | if(columnSortOptions){ 19 | cellDataToStore.columnSort.isEnabled = true; 20 | cellDataToStore.columnSort.sort = false; 21 | cellDataToStore.columnSort.comparator = columnSortOptions.comparator ? columnSortOptions.comparator : false; 22 | }else{ 23 | cellDataToStore.columnSort.isEnabled = false; 24 | } 25 | }; 26 | 27 | /** 28 | * Sets the sorting direction for the passed header 29 | * 30 | * @param headerRowData 31 | * @param valueToSet 32 | * @param dataStorage 33 | */ 34 | service.setHeaderSort = function(headerRowData, valueToSet, dataStorage){ 35 | if(!valueToSet){ 36 | return; 37 | } 38 | 39 | headerRowData.columnSort.sort = (valueToSet.columnSort && valueToSet.columnSort.sort === ColumnSortDirectionProvider.ASC) ? ColumnSortDirectionProvider.ASC : ColumnSortDirectionProvider.DESC; 40 | 41 | //set other columns isSorted flag to false 42 | resetColumnDirections(headerRowData, dataStorage); 43 | }; 44 | 45 | /** 46 | * Perform sorting for the passed column. 47 | * 48 | * @param headerRowData 49 | * @param dataStorage 50 | * @param paginator 51 | * @param columnIndex 52 | */ 53 | service.columnClickHandler = function(headerRowData, dataStorage, paginator, columnIndex){ 54 | // if feature is not set for the column 55 | if(!headerRowData.columnSort.isEnabled){ 56 | return; 57 | } 58 | 59 | // if column filter feature is enabled, it must be disabled by clicking on the column, we handle ordering there 60 | if(headerRowData.columnFilter.isEnabled){ 61 | return; 62 | } 63 | 64 | //set other columns isSorted flag to false 65 | resetColumnDirections(headerRowData, dataStorage); 66 | 67 | //calculate next sorting direction 68 | setNextSortingDirection(headerRowData); 69 | 70 | // if ajax paginator is the current paginator 71 | if(paginator.getFirstPage){ 72 | paginator.getFirstPage(); 73 | // or it's just a simple data paginator 74 | }else{ 75 | //todo: making it nicer 76 | //adding the column index information to the header cell data 77 | headerRowData.columnSort.columnIndex = columnIndex; 78 | 79 | //sortSimpleDataByColumn(columnIndex, dataStorage); 80 | sortByColumn(headerRowData, dataStorage); 81 | } 82 | }; 83 | 84 | /** 85 | * Add the appropriate values to the paginator callback 86 | * @param dataStorage 87 | * @param callbackArguments 88 | */ 89 | service.appendSortedColumnToCallbackArgument = function(dataStorage, callbackArguments){ 90 | var columnsSortInformation = []; 91 | var isEnabled = false; 92 | 93 | _.each(dataStorage.header, function(headerData){ 94 | var sortValue = headerData.columnSort.sort ? headerData.columnSort.sort : false; 95 | 96 | columnsSortInformation.push({ 97 | sort: sortValue 98 | }); 99 | 100 | if(headerData.columnSort.isEnabled){ 101 | isEnabled = true; 102 | } 103 | }); 104 | 105 | if(isEnabled){ 106 | callbackArguments.options.columnSort = columnsSortInformation; 107 | } 108 | }; 109 | 110 | /*** 111 | * Helper function for handling the sorting states in the column filter panels 112 | * @param event 113 | * @param sortingData 114 | */ 115 | service.sortingCallback = function(event, sortingData){ 116 | event.preventDefault(); 117 | 118 | if(sortingData.columnSort.sort == false){ 119 | sortingData.columnSort.sort = ColumnSortDirectionProvider.ASC; 120 | }else if(sortingData.columnSort.sort === ColumnSortDirectionProvider.ASC){ 121 | sortingData.columnSort.sort = ColumnSortDirectionProvider.DESC; 122 | }else{ 123 | sortingData.columnSort.sort = false; 124 | } 125 | }; 126 | 127 | function resetColumnDirections(headerRowData, dataStorage){ 128 | var lastDirectionValue = headerRowData.columnSort.sort; 129 | _.each(dataStorage.header, function(headerData){ 130 | headerData.columnSort.sort = false; 131 | }); 132 | 133 | headerRowData.columnSort.sort = lastDirectionValue; 134 | } 135 | 136 | function setNextSortingDirection(headerRowData){ 137 | if(headerRowData.columnSort.sort === false){ 138 | headerRowData.columnSort.sort = ColumnSortDirectionProvider.ASC; 139 | }else if(headerRowData.columnSort.sort === ColumnSortDirectionProvider.ASC){ 140 | headerRowData.columnSort.sort = ColumnSortDirectionProvider.DESC; 141 | }else{ 142 | headerRowData.columnSort.sort = false; 143 | } 144 | } 145 | 146 | function sortByColumn(headerRowData, dataStorage){ 147 | var sortFunction; 148 | var index = headerRowData.columnSort.columnIndex; 149 | 150 | if (typeof headerRowData.columnSort.comparator === 'function') { 151 | sortFunction = function(a, b) { 152 | return headerRowData.columnSort.comparator(a.data[index].value, b.data[index].value); 153 | }; 154 | } else { 155 | // basic comparator function on basic values 156 | sortFunction = function (a, b) { 157 | if(typeof a.data[index].value === 'string' && typeof b.data[index].value === 'string'){ 158 | 159 | if(a.data[index].value > b.data[index].value){ 160 | return 1; 161 | }else if(a.data[index].value < b.data[index].value){ 162 | return -1; 163 | }else{ 164 | return 0; 165 | } 166 | } 167 | 168 | return a.data[index].value - b.data[index].value; 169 | }; 170 | } 171 | 172 | dataStorage.storage.sort(sortFunction); 173 | 174 | if(headerRowData.columnSort.sort === ColumnSortDirectionProvider.DESC){ 175 | dataStorage.storage.reverse(); 176 | } 177 | } 178 | } 179 | 180 | angular 181 | .module('mdDataTable') 182 | .service('ColumnSortFeature', ColumnSortFeature); 183 | }()); -------------------------------------------------------------------------------- /test/unit/modules/directives/mdDataTableDirectiveTest.js: -------------------------------------------------------------------------------- 1 | xdescribe('mdtTableDirective', function(){ 2 | var $compile, 3 | $rootScope, 4 | $scope, 5 | element, 6 | elementController, 7 | elementIsolatedScope; 8 | 9 | var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE'; 10 | var DIRECTIVE_SELECTABLE_ROWS_TRUE = 'DIRECTIVE_SELECTABLE_ROWS_TRUE'; 11 | var DIRECTIVE_SELECTABLE_ROWS_FALSE = 'DIRECTIVE_SELECTABLE_ROWS_FALSE'; 12 | var DIRECTIVE_SORTABLE_COLUMNS_TRUE = 'DIRECTIVE_SORTABLE_COLUMNS_TRUE'; 13 | var DIRECTIVE_SORTABLE_COLUMNS_FALSE = 'DIRECTIVE_SORTABLE_COLUMNS_FALSE'; 14 | var DIRECTIVE_WITH_COMPILED_CONTENT = 'DIRECTIVE_WITH_COMPILED_CONTENT'; 15 | 16 | beforeEach(module('mdtTemplates')); 17 | beforeEach(module('mdDataTable')); 18 | 19 | beforeEach(inject(function($injector){ 20 | $compile = $injector.get('$compile'); 21 | $rootScope = $injector.get('$rootScope'); 22 | 23 | $scope = $rootScope.$new(); 24 | })); 25 | 26 | describe('WHEN created', function(){ 27 | beforeEach(function(){ 28 | //given/when 29 | compileDirective(); 30 | }); 31 | 32 | it('THEN it should have the shared required methods', function(){ 33 | //then 34 | expect(elementController.isSelectableRows).toBeDefined(); 35 | expect(elementController.isSortingEnabled).toBeDefined(); 36 | expect(elementController.sortByColumn).toBeDefined(); 37 | expect(elementController.getSortedColumnIndex).toBeDefined(); 38 | 39 | expect(elementController.addRowData).toBeDefined(); 40 | expect(elementController.getRowData).toBeDefined(); 41 | expect(elementController.getRowOptions).toBeDefined(); 42 | expect(elementController.setAllRowsSelected).toBeDefined(); 43 | 44 | expect(elementController.increaseIndex).toBeDefined(); 45 | expect(elementController.getIndex).toBeDefined(); 46 | 47 | expect(elementController.addColumnOptions).toBeDefined(); 48 | expect(elementController.getColumnOptions).toBeDefined(); 49 | }); 50 | 51 | it('AND it should have the required methods', function(){ 52 | expect(elementIsolatedScope.isAnyRowSelected).toBeDefined(); 53 | }); 54 | }); 55 | 56 | describe('WHEN calling `isSelectableRows`', function(){ 57 | it('THEN depending the attribute it should return true', function(){ 58 | //given/when 59 | compileDirective(DIRECTIVE_SELECTABLE_ROWS_TRUE); 60 | 61 | //then 62 | expect(elementController.isSelectableRows()).toBe(true); 63 | }); 64 | 65 | it('AND depending the attribute it should return false', function(){ 66 | //given/when 67 | compileDirective(DIRECTIVE_SELECTABLE_ROWS_FALSE); 68 | 69 | //then 70 | expect(elementController.isSelectableRows()).toBe(false); 71 | }); 72 | }); 73 | 74 | describe('WHEN calling `isSortingEnabled`', function(){ 75 | it('THEN depending the attribute it should be true', function(){ 76 | //given/when 77 | compileDirective(DIRECTIVE_SORTABLE_COLUMNS_TRUE); 78 | 79 | //then 80 | expect(elementController.isSortingEnabled()).toBe(true); 81 | }); 82 | 83 | it('AND depending the attribute it should be false', function(){ 84 | //given/when 85 | compileDirective(DIRECTIVE_SORTABLE_COLUMNS_FALSE); 86 | 87 | //then 88 | expect(elementController.isSortingEnabled()).toBe(false); 89 | }); 90 | }); 91 | 92 | describe('WHEN calling `sortByColumn`', function(){ 93 | it('THEN `tableDataStorageService.sortByColumnIndex` should be called', function(){ 94 | //given 95 | compileDirective(); 96 | 97 | //when 98 | elementController.sortByColumn(0); 99 | 100 | //then 101 | expect(elementIsolatedScope.tableDataStorageService.sortByColumnIndex).toHaveBeenCalledWith(0, undefined); 102 | }); 103 | }); 104 | 105 | describe('WHEN calling `sortByColumn` on the same column index twice', function(){ 106 | beforeEach(function(){ 107 | //given 108 | compileDirective(); 109 | 110 | elementController.sortByColumn(3); 111 | }); 112 | 113 | it('THEN `tableDataStorageService.sortByColumnIndex` should be called only once', function(){ 114 | //when 115 | elementController.sortByColumn(3); 116 | 117 | //then 118 | expect(elementIsolatedScope.tableDataStorageService.sortByColumnIndex.calls.count()).toEqual(1); 119 | }); 120 | 121 | it('THEN `tableDataStorageService.reverseRows` should be called', function(){ 122 | //when 123 | elementController.sortByColumn(3); 124 | 125 | //then 126 | expect(elementIsolatedScope.tableDataStorageService.reverseRows).toHaveBeenCalled(); 127 | }); 128 | }); 129 | 130 | describe('WHEN calling `getSortedColumnByIndex`', function(){ 131 | beforeEach(function(){ 132 | //given 133 | compileDirective(); 134 | 135 | elementController.sortByColumn(4); 136 | }); 137 | 138 | it('THEN `tableDataStorageService.getSortedColumnByIndex` should return the previously sortedd column index', function(){ 139 | //when 140 | var sortedcolumnIndex = elementController.getSortedColumnIndex(); 141 | 142 | //then 143 | expect(sortedcolumnIndex).toEqual(4); 144 | }); 145 | }); 146 | 147 | describe('WHEN created AND transcluded content has compiled', function(){ 148 | beforeEach(function(){ 149 | //given/when 150 | compileDirective(DIRECTIVE_WITH_COMPILED_CONTENT); 151 | }); 152 | 153 | //TOOD: do not parse in the right way atm 154 | xit('THEN it should parse the content into the table header', function(){ 155 | //then 156 | expect(element.find('table thead').length).toEqual(1); 157 | }); 158 | 159 | //TOOD: do not parse in the right way atm 160 | it('THEN it should parse the content into the table body', function(){ 161 | //then 162 | expect(element.find('table tbody').length).toEqual(1); 163 | }); 164 | }); 165 | 166 | function compileDirective(status){ 167 | switch(status){ 168 | case DIRECTIVE_SELECTABLE_ROWS_TRUE: 169 | element = $compile('')($scope); 170 | break; 171 | case DIRECTIVE_SELECTABLE_ROWS_FALSE: 172 | element = $compile('')($scope); 173 | break; 174 | case DIRECTIVE_SORTABLE_COLUMNS_TRUE: 175 | element = $compile('')($scope); 176 | break; 177 | case DIRECTIVE_SORTABLE_COLUMNS_FALSE: 178 | element = $compile('')($scope); 179 | break; 180 | case DIRECTIVE_WITH_COMPILED_CONTENT: 181 | element = $compile('' + 182 | '' + 183 | ' ' + 184 | ' headrow' + 185 | ' ' + 186 | ' ' + 187 | ' bodyrow' + 188 | ' ' + 189 | '')($scope); 190 | break; 191 | case DIRECTIVE_DEFAULT_CASE: 192 | default: 193 | element = $compile('')($scope); 194 | } 195 | 196 | $scope.$digest(); 197 | 198 | elementController = element.controller('mdtTable'); 199 | elementIsolatedScope = element.isolateScope(); 200 | 201 | spyOn(elementIsolatedScope.tableDataStorageService, 'sortByColumnIndex'); 202 | spyOn(elementIsolatedScope.tableDataStorageService, 'reverseRows'); 203 | } 204 | }); -------------------------------------------------------------------------------- /test/unit/modules/directives/header/mdDataTableColumnDirectiveTest.js: -------------------------------------------------------------------------------- 1 | xdescribe('mdtColumnDirective', function(){ 2 | var $compile, 3 | $rootScope, 4 | $scope, 5 | element, 6 | elementScope; 7 | 8 | var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE'; 9 | var DIRECTIVE_LEFT_ALIGNED = 'DIRECTIVE_LEFT_ALIGNED'; 10 | var DIRECTIVE_RIGHT_ALIGNED = 'DIRECTIVE_RIGHT_ALIGNED'; 11 | var DIRECTIVE_MULTI_COLUMN = 'DIRECTIVE_MULTI_COLUMN'; 12 | 13 | beforeEach(module('mdtTemplates')); 14 | beforeEach(module('mdDataTable')); 15 | 16 | beforeEach(inject(function($injector){ 17 | $compile = $injector.get('$compile'); 18 | $rootScope = $injector.get('$rootScope'); 19 | 20 | $scope = $rootScope.$new(); 21 | })); 22 | 23 | describe('WHEN created', function(){ 24 | beforeEach(function(){ 25 | //given/when 26 | compileDirective(); 27 | }); 28 | 29 | it('THEN it should have the required methods', function(){ 30 | //then 31 | expect(elementScope.direction).not.toBeDefined(); 32 | expect(elementScope.isSorted).toBeDefined(); 33 | expect(elementScope.clickHandler).toBeDefined(); 34 | expect(elementScope.isColumnLeftAligned).toBeDefined(); 35 | expect(elementScope.isColumnRightAligned).toBeDefined(); 36 | expect(elementScope.isSortingEnabled).toBeDefined(); 37 | expect(elementScope.columnAlignClass).toBeDefined(); 38 | }); 39 | }); 40 | 41 | describe('WHEN `isSorted` called', function(){ 42 | beforeEach(function(){ 43 | compileDirective(); 44 | }); 45 | 46 | it('THEN it should return false', function(){ 47 | //given/when 48 | var isSortedResult = elementScope.isSorted(); 49 | 50 | //then 51 | expect(isSortedResult).toBe(false); 52 | }); 53 | 54 | it('AND sorting is disabled but somehow the column was sorted THEN it should return false', function(){ 55 | //given 56 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 57 | return false; 58 | }); 59 | 60 | //when 61 | element.click(); 62 | 63 | //then; 64 | expect(elementScope.isSorted()).toBe(false); 65 | }); 66 | 67 | it('AND the column was sorted THEN it should return true', function(){ 68 | //given 69 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 70 | return true; 71 | }); 72 | 73 | //when 74 | element.click(); 75 | 76 | //then; 77 | expect(elementScope.isSorted()).toBe(true); 78 | }); 79 | }); 80 | 81 | describe('WHEN `clickHandler` called', function(){ 82 | beforeEach(function(){ 83 | compileDirective(DIRECTIVE_MULTI_COLUMN); 84 | }); 85 | 86 | it('AND sorting is disabled THEN it should not set the direction', function(){ 87 | //given 88 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 89 | return false; 90 | }); 91 | 92 | //when 93 | element.first().click(); 94 | 95 | //then; 96 | expect(elementScope.direction).not.toBeDefined(); 97 | }); 98 | 99 | it('AND sorting is enabled THEN it should set the direction', function(){ 100 | //given 101 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 102 | return true; 103 | }); 104 | 105 | //when 106 | element.first().click(); 107 | 108 | //then; 109 | expect(elementScope.direction).toBeDefined(); 110 | }); 111 | 112 | it('AND sorting is enabled THEN it should set the direction to ascending (-1) on first click', function(){ 113 | //given 114 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 115 | return true; 116 | }); 117 | 118 | //when 119 | element.first().click(); 120 | 121 | //then; 122 | expect(elementScope.direction).toBe(-1); 123 | }); 124 | 125 | it('AND sorting is enabled THEN it should set the direction to descending (1) on second click', function(){ 126 | //given 127 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 128 | return true; 129 | }); 130 | 131 | //when 132 | element.first().click(); 133 | element.first().click(); 134 | 135 | //then; 136 | expect(elementScope.direction).toBe(1); 137 | }); 138 | 139 | it('AND sorting is enabled THEN it should set the direction to ascending (-1) when switching sort column after second click', function(){ 140 | 141 | var lastElementScope = element.last().find('.ng-scope').scope().$parent; 142 | //given 143 | spyOn(elementScope, 'isSortingEnabled').and.callFake(function(){ 144 | return true; 145 | }); 146 | 147 | spyOn(lastElementScope, 'isSortingEnabled').and.callFake(function(){ 148 | return true; 149 | }); 150 | 151 | 152 | //when 153 | element.first().click(); 154 | element.first().click(); 155 | element.last().click(); 156 | 157 | //then; 158 | expect(lastElementScope.direction).toBe(-1); 159 | }); 160 | }); 161 | 162 | describe('WHEN directive is left aligned', function(){ 163 | beforeEach(function(){ 164 | compileDirective(DIRECTIVE_LEFT_ALIGNED); 165 | }); 166 | 167 | it('THEN `isColumnLeftAligned` should return true', function(){ 168 | expect(elementScope.isColumnLeftAligned()).toBe(true); 169 | }); 170 | 171 | it('THEN `isColumnRightAligned` should return false', function(){ 172 | expect(elementScope.isColumnRightAligned()).toBe(false); 173 | }); 174 | }); 175 | 176 | describe('WHEN directive is right aligned', function(){ 177 | beforeEach(function(){ 178 | compileDirective(DIRECTIVE_RIGHT_ALIGNED); 179 | }); 180 | 181 | it('THEN `isColumnLeftAligned` should return false', function(){ 182 | expect(elementScope.isColumnLeftAligned()).toBe(false); 183 | }); 184 | 185 | it('THEN `isColumnRightAligned` should return true', function(){ 186 | expect(elementScope.isColumnRightAligned()).toBe(true); 187 | }); 188 | }); 189 | 190 | function compileDirective(status){ 191 | var mainElement; 192 | 193 | switch(status){ 194 | case DIRECTIVE_LEFT_ALIGNED: 195 | mainElement = $compile('' + 196 | '' + 197 | ' ' + 198 | ' A Column' + 199 | ' ' + 200 | '')($scope); 201 | break; 202 | 203 | case DIRECTIVE_RIGHT_ALIGNED: 204 | mainElement = $compile('' + 205 | '' + 206 | ' ' + 207 | ' A Column' + 208 | ' ' + 209 | '')($scope); 210 | break; 211 | 212 | case DIRECTIVE_MULTI_COLUMN: 213 | mainElement = $compile('' + 214 | '' + 215 | ' ' + 216 | ' A Column' + 217 | ' Another Column' + 218 | ' ' + 219 | '')($scope); 220 | break; 221 | 222 | case DIRECTIVE_DEFAULT_CASE: 223 | default: 224 | mainElement = $compile('' + 225 | '' + 226 | ' ' + 227 | ' A Column' + 228 | ' ' + 229 | '')($scope); 230 | } 231 | 232 | $scope.$digest(); 233 | 234 | elementScope = mainElement.find('.ng-scope').scope().$parent; 235 | element = mainElement.find('th.column'); 236 | } 237 | }); -------------------------------------------------------------------------------- /app/scss/main.scss: -------------------------------------------------------------------------------- 1 | $whiteColor: #ffffff; 2 | $primaryColorPalette: #0D47A1; 3 | $disabled: #9E9E9E; 4 | $grey: #757575; 5 | 6 | @mixin hoverSortIcons($size){ 7 | ng-md-icon{ 8 | visibility: hidden; 9 | width: $size; 10 | height: $size; 11 | fill: darken($whiteColor, 30%); 12 | 13 | svg{ 14 | -webkit-transform:rotate(90deg); 15 | transform:rotate(90deg); 16 | } 17 | } 18 | } 19 | 20 | @mixin hoverSortIconOnHover(){ 21 | ng-md-icon{ 22 | visibility: visible; 23 | } 24 | } 25 | 26 | @mixin sortedTarget($size){ 27 | /* when hoverSortIcons on a sorted column*/ 28 | .hoverSortIcons ng-md-icon{ 29 | display:none; 30 | } 31 | 32 | ng-md-icon{ 33 | /* specified 16px is a fix now cause angular materia generates a 24x24 icon even the passed value is 16 */ 34 | width: $size; 35 | height: $size; 36 | 37 | fill: darken($whiteColor, 87%); 38 | 39 | /* sort icon rotated 90 degrees for ascending sort (default) */ 40 | svg{ 41 | -webkit-transform:rotate(90deg); 42 | transform:rotate(90deg); 43 | } 44 | 45 | } 46 | 47 | /* sort icon rotated -90 degrees for descending sort */ 48 | &.descending ng-md-icon>svg{ 49 | -webkit-transform:rotate(-90deg); 50 | transform:rotate(-90deg); 51 | } 52 | } 53 | 54 | .mdtTableContainer{ 55 | 56 | position:relative; 57 | overflow: visible; 58 | 59 | .mdtTable{ 60 | position:relative; 61 | } 62 | 63 | .md-chips {font-size: 13px;} 64 | .p-r {position: relative;} 65 | .no-outline {outline: none;} 66 | 67 | *, 68 | *:before, 69 | *:after { 70 | -webkit-box-sizing: inherit; 71 | -moz-box-sizing: inherit; 72 | box-sizing: inherit; 73 | } 74 | 75 | table{ 76 | width:100%; 77 | display: table; 78 | 79 | &:focus{ 80 | outline: none; 81 | } 82 | } 83 | 84 | td, th{ padding: 0; margin: 0; } 85 | font-size: 15px; 86 | 87 | /* 12sp Roboto Medium, 54% black */ 88 | th{ 89 | font-size: 12px; 90 | font-weight: 500; 91 | color: darken($whiteColor, 54%); 92 | white-space: nowrap; 93 | /* prevent ink ripple bleeding */ 94 | position: relative; 95 | 96 | /* no pointer cursor when disabled, could be not-allowed but i think that indication is to strong */ 97 | &[disabled]{ 98 | cursor: auto; 99 | } 100 | 101 | /* remove the default blue outline in Chrome for consistent button-like behaviour */ 102 | &:focus, *:focus{ 103 | outline: none; 104 | } 105 | 106 | .column-header-content{ 107 | cursor: default; 108 | } 109 | 110 | .column-header-content.clickable{ 111 | cursor: pointer; 112 | } 113 | 114 | /* when hoverSortIcons on a non-sorted column*/ 115 | .hoverSortIcons{ 116 | @include hoverSortIcons(16px); 117 | } 118 | 119 | &:hover .hoverSortIcons{ 120 | @include hoverSortIconOnHover(); 121 | } 122 | 123 | .sortedColumn{ 124 | @include sortedTarget(16px); 125 | } 126 | } 127 | 128 | /* optional animation of sort icons: add class 'animate-sort-icon' to mdt-header-row to activate animation */ 129 | tr.animate-sort-icon .sortedColumn ng-md-icon svg{ 130 | -webkit-transition: 0.3s linear all; 131 | transition: 0.3s linear all; 132 | } 133 | 134 | 135 | /* 64dp card header height */ 136 | .mdt-header{ 137 | height: 64px; 138 | padding-left: 24px; 139 | padding-right: 14px; 140 | 141 | md-button{ 142 | margin-left: 24px; 143 | } 144 | 145 | ng-md-icon{ 146 | fill: darken($whiteColor, 54%); 147 | } 148 | } 149 | 150 | .mdt-header-alternate{ 151 | @extend .mdt-header; 152 | 153 | background-color: lighten($primaryColorPalette, 60%); 154 | 155 | .alternate-text{ 156 | color: $primaryColorPalette; 157 | } 158 | } 159 | 160 | /* 56do for last row */ 161 | .mdt-footer, tr th{ 162 | height: 56px; 163 | line-height: 56px; 164 | 165 | .mdt-pagination{ 166 | font-size: 12px; 167 | color: darken($whiteColor, 54%); 168 | 169 | md-input-container{ 170 | margin-top: 0px; 171 | margin-bottom: 0px; 172 | } 173 | } 174 | } 175 | 176 | .mdt-footer{ 177 | overflow: hidden; 178 | } 179 | 180 | /* column padding */ 181 | .checkboxCell{ 182 | width: 18px; 183 | 184 | & md-checkbox{ 185 | margin: 0; 186 | padding: 0; 187 | } 188 | 189 | /*the next cell should not have just 24px padding */ 190 | & + td, & + th { 191 | padding-left: 24px; 192 | } 193 | } 194 | 195 | /* 48dp row height */ 196 | /* 13sp Roboto Regular, 87% black */ 197 | tr td{ 198 | padding: 0; 199 | height: 48px; 200 | font-size: 13px; 201 | color: darken($whiteColor, 87%); 202 | } 203 | 204 | td:first-child, th:first-child{ 205 | padding: 0 0 0 24px; 206 | } 207 | 208 | td:last-child, th:last-child{ 209 | padding-right: 24px; 210 | } 211 | 212 | .column{ 213 | padding-left: 56px; 214 | } 215 | 216 | .leftAlignedColumn{ 217 | text-align: left; 218 | } 219 | 220 | .rightAlignedColumn{ 221 | text-align: right; 222 | } 223 | 224 | /* border separation color */ 225 | tr th{ 226 | border-bottom: solid 1px #DDDDDD; 227 | } 228 | 229 | tr td{ 230 | border-bottom: solid 1px #DDDDDD; 231 | } 232 | 233 | 234 | /* INTERACTION */ 235 | tr:hover td{ 236 | background: #EEEEEE; 237 | } 238 | 239 | .selectedRow td{ 240 | background: #F5F5F5; 241 | } 242 | 243 | /* default icon color */ 244 | ng-md-icon{ 245 | fill: darken($whiteColor, 54%); 246 | } 247 | 248 | .md-inactive ng-md-icon { fill: rgba(0, 0, 0, 0.26); } 249 | 250 | .md-virtual-repeat-container{ 251 | min-height: 106px; 252 | } 253 | 254 | /* filter drop down */ 255 | /* TH Select outline color & caret */ 256 | .filter-select { 257 | border: 1px solid transparent; padding: 11px 10px; 258 | border-radius: 2px; cursor: pointer; margin-left: -10px; 259 | } 260 | .filter-select:hover { 261 | border-color: #dedede; 262 | & ng-md-icon {visibility: visible;} 263 | } 264 | .filter-select.is-active { 265 | color: #fff; 266 | background-color: #0096D6; 267 | border-color: #0096D6; 268 | } 269 | .filter-select ng-md-icon {visibility: hidden;} 270 | .filter-select.is-active ng-md-icon { 271 | visibility: visible; 272 | fill: rgba(255,255,255,0.87); fill: #fff; 273 | } 274 | 275 | md-progress-linear{ 276 | .md-container{ 277 | height: 3px; 278 | } 279 | 280 | .md-bar { 281 | background: #2A87E3; 282 | } 283 | } 284 | 285 | .loading-indicator{ 286 | position:absolute; 287 | top:0; 288 | left:0; 289 | 290 | visibility: hidden; 291 | } 292 | 293 | .loading-is-active{ 294 | visibility: visible; 295 | } 296 | } 297 | 298 | 299 | /* filter drop down AND column selector */ 300 | .filter-dropdown, .mdt-column-selector{ 301 | position: absolute; 302 | z-index: 1; 303 | width: 250px; 304 | text-align: initial; 305 | 306 | a{ 307 | text-decoration: none; 308 | } 309 | 310 | .selectall_clearall{ 311 | font-size: 12px; 312 | 313 | span{ 314 | margin: 0 5px 0 5px; 315 | } 316 | 317 | .selected_items{ 318 | text-align: right; 319 | color: darkgray; 320 | } 321 | 322 | .disabled{ 323 | color: $disabled; 324 | cursor: default; 325 | } 326 | } 327 | 328 | .hoverSortIcons{ 329 | @include hoverSortIcons(20px); 330 | @include hoverSortIconOnHover(); 331 | } 332 | 333 | .sortedColumn{ 334 | @include sortedTarget(20px); 335 | } 336 | 337 | .b-b {border-bottom: 1px solid #dedede;} 338 | .p-md {padding: 16px;} 339 | .p-l-md {padding-left: 16px;} 340 | .p-r-md {padding-right: 16px;} 341 | .p-t-md {padding-top: 16px;} 342 | .p-r-md {padding-right: 16px;} 343 | .p-smd {padding: 12px;} 344 | .p-sm {padding: 8px;} 345 | .p-l-sm {padding-left: 8px;} 346 | .p-t-sm {padding-top: 8px;} 347 | .p-b-sm {padding-bottom: 8px;} 348 | .p-r-sm {padding-right: 8px;} 349 | .p-b-n {padding-bottom: 0;} 350 | 351 | .md-chips{ 352 | font-size: 12px; 353 | } 354 | 355 | .filter__scroll {max-height: 200px; overflow-y: auto; border-bottom: solid 1px #DDDDDD;} 356 | } 357 | 358 | .mdt-column-selector{ 359 | right: 0px; /* <-- this is a fix for positioning an element with the 'right' value using jquery. To make it work right:0px should be set in css before */ 360 | 361 | .mdt-column-selector-title{ 362 | color: darken($whiteColor, 54%); 363 | } 364 | 365 | .mdt-checbox-column-items span{ 366 | width: 180px; 367 | display:block; 368 | white-space: nowrap; 369 | overflow: hidden; 370 | -ms-text-overflow: ellipsis; 371 | text-overflow: ellipsis; 372 | } 373 | } --------------------------------------------------------------------------------