├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── README.md ├── bower.json ├── demo ├── demo.css ├── demo.js └── index.html ├── dist ├── fixed-header.css ├── fixed-header.js └── fixed-header.min.js ├── karma.conf.js ├── package.json └── test └── fixed-header-spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | FixedHeader.iml 4 | bower_components 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - 'npm install' 6 | - 'node_modules/bower/bin/bower install' -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | uglify: { 7 | options: { 8 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 9 | }, 10 | build: { 11 | src: 'dist/<%= pkg.name %>.js', 12 | dest: 'dist/<%= pkg.name %>.min.js' 13 | } 14 | } 15 | }); 16 | 17 | // Load the plugin that provides the "uglify" task. 18 | grunt.loadNpmTasks('grunt-contrib-uglify'); 19 | 20 | // Default task(s). 21 | grunt.registerTask('default', ['uglify']); 22 | 23 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FixedHeader [![Build Status](https://secure.travis-ci.org/objectcomputing/FixedHeader.png)](http://travis-ci.org/objectcomputing/FixedHeader) 2 | 3 | AngularJS Directive and CSS for creating scrollable tables with fixed 4 | headers. 5 | 6 | Creating scrollable tables with fixed headers that do not scroll off 7 | the screen is tricky due to the way tables work in HTML. We use the 8 | the techniques described by Miriam Salzer here: 9 | [Don’t Mess With Tables – Pure CSS Fixed-Header Left-Aligned Tables](http://salzerdesign.com/blog/?p=191), 10 | and wrap them up in a reusable AngularJS directive. 11 | 12 | ## Usage 13 | 14 | Add `dist/fixed-header.js` and `dist/fixed-header.css` to your 15 | index.html. 16 | 17 | Add `oci.fixedHeader` as a module dependency on your module: 18 | 19 | ```js 20 | angular.module('app', ['oci.fixedHeader']); 21 | ``` 22 | 23 | Add `oci.fixed-header` as an attribute to your table: 24 | 25 | ```html 26 |
27 | 28 | 29 |
30 |
31 | ``` 32 | 33 | Note the wrapper div with the class `my-table`. The `oci.fixed-header` 34 | directive adds wrapper divs around the table, so we need 35 | `div.my-table` for our custom CSS below: 36 | 37 | Add custom css to control the height of the table and the height of 38 | the header row: 39 | 40 | ```css 41 | div.my-table div.fixed-table-container-inner { 42 | /* the maximum height of the table: */ 43 | max-height: 150px; 44 | 45 | border: 1px solid #aaaaaa; 46 | width: 150px; 47 | } 48 | 49 | div.my-table div.th-inner { 50 | /* the height of the header row */ 51 | line-height: 30px; 52 | } 53 | 54 | div.my-table div.fixed-table-container { 55 | /* the height of the header row - this needs to match line-height above */ 56 | padding-top: 30px; 57 | } 58 | 59 | div.my-table tr.hidden-header .th-inner { 60 | padding-right: 5px; 61 | } 62 | 63 | ``` 64 | 65 | ## Demo 66 | 67 | [oci.fixed-header demo](http://objectcomputing.github.io/FixedHeader/demo/index.html) 68 | 69 | ## Running Tests 70 | 71 | * Install global dependencies 72 | `npm install -g karma phantomjs bower` 73 | 74 | * Install local dependencies 75 | `npm install` 76 | 77 | * Install Bower dependencies 78 | `bower install` 79 | 80 | * Run tests 81 | `karma start` 82 | 83 | ## Authors 84 | 85 | - Lance Finney finneyl@ociweb.com 86 | - Steve Molitor molitors@ociweb.com 87 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oci.FixedHeader", 3 | "version": "0.1.1", 4 | "homepage": "https://github.com/objectcomputing/FixedHeader", 5 | "authors": [ 6 | "Object Computing Inc." 7 | ], 8 | "description": "AngularJS Directive and CSS for creating scrollable tables with fixed headers", 9 | "main": "dist/fixed-header.js", 10 | "keywords": [ 11 | "Angular", 12 | "AngularJS", 13 | "angular.js", 14 | "table", 15 | "header", 16 | "fixed", 17 | "static" 18 | ], 19 | "license": "MIT", 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "angular": "1.2.x" 29 | }, 30 | "devDependencies": { 31 | "jquery": "1.11.x", 32 | "angular-mocks": "1.2.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | div.my-table div.fixed-table-container-inner { 2 | /* the maximum height of the table: */ 3 | max-height: 150px; 4 | 5 | border: 1px solid #aaaaaa; 6 | width: 150px; 7 | } 8 | 9 | div.my-table div.th-inner { 10 | /* the height of the header row */ 11 | line-height: 30px; 12 | } 13 | 14 | div.my-table div.fixed-table-container { 15 | /* the height of the header row - this needs to match line-height above */ 16 | padding-top: 30px; 17 | } 18 | 19 | div.my-table tr.hidden-header .th-inner { 20 | padding-right: 5px; 21 | } 22 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | @license OCI Fixed Header version 0.1.0 3 | ⓒ 2014 OCI https://github.com/objectcomputing/FixedHeader 4 | License: MIT 5 | */ 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | var app = angular.module('app', ['oci.fixedHeader']); 11 | 12 | app.controller('ctrl', function($scope) { 13 | $scope.headers = ['First', 'Middle', 'Last']; 14 | }); 15 | })(); 16 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fixed Header Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
FirstMiddleLast
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
35 |
36 | 37 |
38 | 39 | 40 | 41 | 46 | 47 | 48 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
42 |
43 | {{header}} 44 |
45 |
49 |
50 | {{header}} 51 |
52 |
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
Bob L Smith
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /dist/fixed-header.css: -------------------------------------------------------------------------------- 1 | .fixed-table-container { 2 | position: relative; 3 | } 4 | 5 | .fixed-table-container-inner { 6 | overflow-y: auto; 7 | height: 100%; 8 | } 9 | 10 | div.fixed-table-container div table thead tr th { 11 | padding: 0; 12 | border: 0; 13 | } 14 | 15 | .th-inner { 16 | position: absolute; 17 | top: 0; 18 | text-align: left; 19 | } 20 | 21 | .hidden-header .th-inner { 22 | position: static; 23 | overflow-y: hidden; 24 | height: 0; 25 | } 26 | -------------------------------------------------------------------------------- /dist/fixed-header.js: -------------------------------------------------------------------------------- 1 | /* 2 | @license OCI Fixed Header version 0.1.0 3 | ⓒ 2014 OCI https://github.com/objectcomputing/FixedHeader 4 | License: MIT 5 | */ 6 | 7 | (function () { 8 | 'use strict'; 9 | 10 | angular.module('oci.fixedHeader', []) 11 | .directive('oci.fixedHeader', function () { 12 | return function link(scope, elem) { 13 | // Wrap the contents of every header cell with div.th-inner so that the 14 | // CSS can relocate it. 15 | var header = elem.find('thead').find('tr'); 16 | angular.forEach(header.find('th'), function (th) { 17 | angular.element(th).contents().wrap('
'); 18 | }); 19 | 20 | // Make a clone of the header that we hide using css. The purpose of this 21 | // is to allow the width of the contents of the header to be included 22 | // in the calculation of the widths of the columns 23 | var hiddenHeader = header.clone(); 24 | hiddenHeader.addClass('hidden-header'); 25 | header.after(hiddenHeader); 26 | 27 | // wrap the table in a couple of divs that bring in important css modifications 28 | elem.wrap('
'); 29 | elem.wrap('
'); 30 | }; 31 | }); 32 | })(); 33 | -------------------------------------------------------------------------------- /dist/fixed-header.min.js: -------------------------------------------------------------------------------- 1 | /*! fixed-header 2014-03-04 */ 2 | !function(){"use strict";angular.module("oci.fixedHeader",[]).directive("oci.fixedHeader",function(){return function(a,b){var c=b.find("thead").find("tr");angular.forEach(c.find("th"),function(a){angular.element(a).contents().wrap('
')});var d=c.clone();d.addClass("hidden-header"),c.after(d),b.wrap('
'),b.wrap('
')}})}(); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | "use strict"; 3 | 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '.', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'bower_components/jquery/dist/jquery.js', 17 | 'bower_components/angular/angular.js', 18 | 'bower_components/angular-mocks/angular-mocks.js', 19 | 'dist/fixed-header.js', 20 | 'test/fixed-header-spec.js' 21 | ], 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | ], 26 | 27 | singleRun: true, 28 | 29 | // test results reporter to use 30 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 31 | reporters: ['dots'], 32 | 33 | 34 | // level of logging 35 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 36 | logLevel: config.LOG_INFO, 37 | 38 | 39 | // enable / disable watching file and executing tests whenever any file changes 40 | autoWatch: false, 41 | 42 | 43 | // Start these browsers, currently available: 44 | // - Chrome 45 | // - ChromeCanary 46 | // - Firefox 47 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 48 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 49 | // - PhantomJS 50 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 51 | //browsers: ['Chrome', 'PhantomJS', 'Firefox'], 52 | browsers: ['PhantomJS'], 53 | 54 | plugins: [ 55 | //'karma-chrome-launcher', 56 | //'karma-firefox-launcher', 57 | 'karma-phantomjs-launcher', 58 | 'karma-jasmine' 59 | //'karma-junit-reporter' 60 | ], 61 | 62 | // junitReporter: { 63 | // outputFile: 'test-results.xml' 64 | // // suite: 'unit' 65 | // }, 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixed-header", 3 | "repository": "https://github.com/objectcomputing/FixedHeader", 4 | "scripts": { 5 | "test": "./node_modules/.bin/karma start" 6 | }, 7 | "devDependencies": { 8 | "bower": "^1.3.12", 9 | "grunt": "~0.4.2", 10 | "grunt-contrib-jshint": "~0.6.3", 11 | "grunt-contrib-nodeunit": "~0.2.0", 12 | "grunt-contrib-uglify": "~0.4.0", 13 | "karma": "~0.10.9", 14 | "karma-chrome-launcher": "~0.1.2", 15 | "karma-coffee-preprocessor": "~0.1.3", 16 | "karma-firefox-launcher": "~0.1.3", 17 | "karma-html2js-preprocessor": "~0.1.0", 18 | "karma-jasmine": "~0.1.5", 19 | "karma-phantomjs-launcher": "~0.1.2", 20 | "karma-requirejs": "~0.2.1", 21 | "karma-script-launcher": "~0.1.0", 22 | "phantomjs": "~1.9.7-1", 23 | "requirejs": "~2.1.11" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/fixed-header-spec.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | describe('fixed-header directive', function () { 5 | beforeEach(module('oci.fixedHeader')); 6 | 7 | var compile; 8 | var scope; 9 | 10 | beforeEach(inject(function ($compile, $rootScope) { 11 | compile = $compile; 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should render html for fixed header', function () { 16 | var html = 17 | '
onetwo
'; 18 | var elem = angular.element(html); 19 | compile(elem)(scope); 20 | scope.$digest(); 21 | 22 | expect(elem.find('>div.fixed-table-container').length).toBe(1); 23 | expect(elem.find('>div.fixed-table-container>div.fixed-table-container-inner').length).toBe(1); 24 | expect(elem.find('>div.fixed-table-container>div.fixed-table-container-inner>table').length).toBe(1); 25 | 26 | expect(elem.find('thead > tr').length).toBe(2); 27 | expect(elem.find('thead > tr').eq(0).hasClass('hidden-header')).toBeFalsy(); 28 | expect(elem.find('thead > tr').eq(1).hasClass('hidden-header')).toBeTruthy(); 29 | expect(elem.find('thead > tr').eq(0).find('th').eq(0).html()).toBe('
one
'); 30 | expect(elem.find('thead > tr').eq(0).find('th').eq(1).html()).toBe('
two
'); 31 | expect(elem.find('thead tr').eq(1).find('th').eq(0).html()).toBe('
one
'); 32 | expect(elem.find('thead > tr').eq(1).find('th').eq(1).html()).toBe('
two
'); 33 | }); 34 | }); 35 | })(); 36 | --------------------------------------------------------------------------------