├── .gitattributes ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── MIT-LICENCE.txt ├── README.md ├── app.js ├── bower.json ├── dist ├── scrollgrid.latest.js ├── scrollgrid.latest.min.js ├── scrollgrid.v0.1.0.js ├── scrollgrid.v0.1.0.min.js ├── scrollgrid.v0.1.1.js ├── scrollgrid.v0.1.1.min.js ├── scrollgrid.v0.1.10.js ├── scrollgrid.v0.1.10.min.js ├── scrollgrid.v0.1.11.js ├── scrollgrid.v0.1.11.min.js ├── scrollgrid.v0.1.12.js ├── scrollgrid.v0.1.12.min.js ├── scrollgrid.v0.1.13.js ├── scrollgrid.v0.1.13.min.js ├── scrollgrid.v0.1.14.js ├── scrollgrid.v0.1.14.min.js ├── scrollgrid.v0.1.15.js ├── scrollgrid.v0.1.15.min.js ├── scrollgrid.v0.1.2.js ├── scrollgrid.v0.1.2.min.js ├── scrollgrid.v0.1.3.js ├── scrollgrid.v0.1.3.min.js ├── scrollgrid.v0.1.4.js ├── scrollgrid.v0.1.4.min.js ├── scrollgrid.v0.1.5.js ├── scrollgrid.v0.1.5.min.js ├── scrollgrid.v0.1.6.js ├── scrollgrid.v0.1.6.min.js ├── scrollgrid.v0.1.7.js ├── scrollgrid.v0.1.7.min.js ├── scrollgrid.v0.1.8.js ├── scrollgrid.v0.1.8.min.js ├── scrollgrid.v0.1.9.js ├── scrollgrid.v0.1.9.min.js ├── scrollgrid.v0.2.0.js ├── scrollgrid.v0.2.0.min.js ├── scrollgrid.v0.2.1.js ├── scrollgrid.v0.2.1.min.js ├── scrollgrid.v0.2.2.js ├── scrollgrid.v0.2.2.min.js ├── scrollgrid.v0.3.0.js └── scrollgrid.v0.3.0.min.js ├── docs ├── advancedStyling.md ├── contents.md ├── gettingStarted.md └── gridOptions.md ├── examples ├── async.html ├── data │ └── example_data.tsv ├── scratchpad.html ├── test.html └── tsv_file_viewer.html ├── karma.config.js ├── lib ├── LICENSE ├── d3.v3.4.8.js └── d3.v3.4.8.min.js ├── package.json ├── src ├── external │ ├── adapters │ │ ├── json.js │ │ └── simple.js │ ├── addFormatRules.js │ ├── allowColumnResizing.js │ ├── allowSorting.js │ ├── cellPadding.js │ ├── data.js │ ├── dragHandleWidth.js │ ├── footerColumns.js │ ├── footerRowHeight.js │ ├── footerRows.js │ ├── formatRules.js │ ├── headerColumns.js │ ├── headerRowHeight.js │ ├── headerRows.js │ ├── on.js │ ├── refresh.js │ ├── rowHeight.js │ └── sortIconSize.js ├── init.js └── internal │ ├── dom │ ├── getTopMargin.js │ ├── layoutDOM.js │ ├── populateDOM.js │ ├── populatePanel.js │ ├── redirectViewportEvents.js │ ├── setAbsolutePosition.js │ ├── setAutoResize.js │ ├── setRelativePosition.js │ ├── setScrollerSize.js │ └── stylePanels.js │ ├── events │ └── addEventHandlers.js │ ├── interaction │ ├── addResizeHandles.js │ ├── addSortButtons.js │ ├── autoResizeColumn.js │ ├── columnResizeEnd.js │ ├── columnResizeStart.js │ ├── columnResizing.js │ ├── defaultComparer.js │ ├── getColumnResizer.js │ └── sortColumn.js │ ├── raise.js │ ├── render │ ├── applyRules.js │ ├── calculateCellAdjustments.js │ ├── cropText.js │ ├── draw.js │ ├── getClipPath.js │ ├── getDataBounds.js │ ├── getDataInBounds.js │ ├── getTextAnchor.js │ ├── getTextPosition.js │ ├── getVisibleRegion.js │ ├── matchRule.js │ ├── renderBackground.js │ ├── renderForeground.js │ ├── renderRegion.js │ ├── renderRegionForeground.js │ ├── renderSortIcon.js │ ├── setDefaultStyles.js │ └── sortIcon.js │ └── sizes │ ├── calculatePhysicalBounds.js │ ├── calculateTextBound.js │ ├── getExistingTextBound.js │ ├── getRowHeight.js │ ├── initialiseColumns.js │ └── pushTextBound.js └── test ├── bootstrap.js ├── mock ├── d3.js └── scrollgrid.js └── spec ├── external ├── adapters │ ├── json.spec.js │ └── simple.spec.js ├── addFormatRules.spec.js ├── allowColumnResizing.spec.js ├── allowSorting.spec.js ├── cellPadding.spec.js ├── data.spec.js ├── dragHandleWidth.spec.js ├── footerColumns.spec.js ├── footerRowHeight.spec.js ├── footerRows.spec.js ├── formatRules.spec.js ├── headerColumns.spec.js ├── headerRowHeight.spec.js ├── headerRows.spec.js ├── on.spec.js ├── refresh.spec.js ├── rowHeight.spec.js └── sortIconSize.spec.js └── internal ├── dom ├── getTopMargin.spec.js ├── layoutDOM.spec.js ├── populateDOM.spec.js ├── populatePanel.spec.js ├── setAbsolutePosition.spec.js ├── setAutoResize.spec.js ├── setRelativePosition.spec.js ├── setScrollerSize.spec.js └── stylePanels.spec.js ├── events └── addEventHandlers.spec.js ├── interaction ├── addResizeHandles.spec.js ├── addSortButtons.spec.js ├── autoResizeColumn.spec.js ├── columnResizeEnd.spec.js ├── columnResizeStart.spec.js ├── columnResizing.spec.js ├── defaultComparer.spec.js ├── getColumnResizer.spec.js └── sortColumn.spec.js ├── raise.spec.js ├── render ├── applyRules.spec.js ├── calculateCellAdjustments.spec.js ├── cropText.spec.js ├── draw.spec.js ├── getClipPath.spec.js ├── getDataBounds.spec.js ├── getDataInBounds.spec.js ├── getTextAnchor.spec.js ├── getTextPosition.spec.js ├── getVisibleRegion.spec.js ├── matchRule.spec.js ├── rendeRegionForeground.spec.js ├── renderBackground.spec.js ├── renderForeground.spec.js ├── renderRegion.spec.js ├── renderSortIcon.spec.js ├── setDefaultStyles.spec.js └── sortIcon.spec.js └── sizes ├── calculatePhysicalBounds.spec.js ├── calculateTextBound.spec.js ├── getExistingTextBound.spec.js ├── getRowHeight.spec.js ├── initialiseColumns.spec.js └── pushTextBound.spec.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | tmp 3 | node_modules 4 | coverage -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | install: 5 | - npm -d install 6 | - npm install -g grunt-cli 7 | - npm install -g grunt 8 | script: 9 | - grunt travis 10 | after_script: 11 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | "use strict"; 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | concat: { 7 | dist: { 8 | src: [ 9 | "src/init.js", 10 | "src/internal/**/*.js", 11 | "src/external/**/*.js" 12 | ], 13 | dest: 'dist/<%= pkg.name %>.v<%= pkg.version %>.js' 14 | }, 15 | test: { 16 | src: '<%= concat.dist.src %>', 17 | dest: 'tmp/<%= pkg.name %>.js' 18 | } 19 | }, 20 | uglify: { 21 | dist: { 22 | files: { 23 | 'dist/<%= pkg.name %>.v<%= pkg.version %>.min.js': ['<%= concat.dist.dest %>'] 24 | } 25 | } 26 | }, 27 | copy: { 28 | main: { 29 | files: [ 30 | { src: 'dist/<%= pkg.name %>.v<%= pkg.version %>.min.js', dest: 'dist/<%= pkg.name %>.latest.min.js'}, 31 | { src: 'dist/<%= pkg.name %>.v<%= pkg.version %>.js', dest: 'dist/<%= pkg.name %>.latest.js'} 32 | ] 33 | } 34 | }, 35 | connect: { 36 | server: { 37 | options: { 38 | port: 4001, 39 | base: '.' 40 | } 41 | } 42 | }, 43 | jslint: { 44 | client: { 45 | src: [ 46 | 'Gruntfile.js', 47 | 'test/spec/**/*.spec.js', 48 | 'src/**/*.js' 49 | ], 50 | directives: { 51 | browser: true, 52 | predef: [ 53 | 'd3', 54 | 'scrollgrid', 55 | 'Scrollgrid', 56 | 'module', 57 | 'console', 58 | 'jasmine', 59 | 'define', 60 | 'require', 61 | 'exports', 62 | 'describe', 63 | 'spyOn', 64 | 'expect', 65 | 'it', 66 | 'xdescribe', 67 | 'xit', 68 | 'beforeEach', 69 | 'afterEach' 70 | ] 71 | } 72 | } 73 | }, 74 | karma: { 75 | unit: { 76 | configFile: 'karma.config.js', 77 | singleRun: true 78 | }, 79 | continuous: { 80 | configFile: 'karma.config.js', 81 | background: true 82 | } 83 | }, 84 | watch: { 85 | src: { 86 | files: [ 87 | '<%= concat.test.src %>' 88 | ], 89 | tasks: ['concat:test', 'karma:continuous:run'] 90 | }, 91 | test: { 92 | files: [ 93 | 'test/spec/**/*.spec.js', 94 | 'test/spec/*.spec.js' 95 | ], 96 | tasks: ['karma:continuous:run'] 97 | } 98 | } 99 | }); 100 | 101 | grunt.loadNpmTasks('grunt-contrib-concat'); 102 | grunt.loadNpmTasks('grunt-contrib-uglify'); 103 | grunt.loadNpmTasks('grunt-contrib-connect'); 104 | grunt.loadNpmTasks('grunt-contrib-copy'); 105 | grunt.loadNpmTasks('grunt-jslint'); 106 | grunt.loadNpmTasks('grunt-contrib-watch'); 107 | grunt.loadNpmTasks('grunt-karma'); 108 | 109 | // Default tasks 110 | grunt.registerTask('default', ['concat', 'jslint', 'concat:test', 'karma:unit', 'uglify', 'copy', 'connect']); 111 | grunt.registerTask('travis', ['concat', 'jslint', 'concat:test', 'karma:unit']); 112 | grunt.registerTask('test:unit', ['concat:test', 'karma:unit']); 113 | grunt.registerTask('test', ['karma:continuous:start', 'watch']); 114 | 115 | }; -------------------------------------------------------------------------------- /MIT-LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 AlignAlytics 2 | www.align-alytics.com 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Scrollgrid ### 2 | [![Build Status](https://travis-ci.org/PMSI-AlignAlytics/scrollgrid.svg?branch=master)](https://travis-ci.org/PMSI-AlignAlytics/scrollgrid) [![Coverage Status](https://coveralls.io/repos/PMSI-AlignAlytics/scrollgrid/badge.svg?branch=master&service=github)](https://coveralls.io/github/PMSI-AlignAlytics/scrollgrid?branch=master) 3 | 4 | Scrollgrid is the first fully featured grid control for web built in SVG using [d3](http://d3js.org). As well as the core behaviours you would expect of any grid control, Scrollgrid also offers: 5 | 6 | * Dynamic viewport - meaning only the portion of the table you can see is rendered in the DOM. This gives really high performance on large data sets. 7 | * Multiple sticky and fixed headers and/or footers on rows and/or columns. 8 | * Support for custom renderers - Scrollgrid is built in d3 (the language of charting on the web) so it has simple built in support for any d3 based cell graphics (sparklines etc). It's dynamic viewport means that only a handful of cells will be rendered at a time, meaning graphics can be drawn for large datasets without any slow down. 9 | * Custom number/date formatters - Provide a d3 formatter or write your own to manipulate number display however you like. 10 | * Column sorting - Built in sorting is overridable by passing custom predicates. This means you can even sort columns with row graphics. 11 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use('/', express.static(__dirname)); 5 | 6 | app.listen(process.env.PORT || 4000); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrollgrid", 3 | "description": "A dynamic scrollable table built using d3", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/PMSI-AlignAlytics/scrollgrid.git" 8 | }, 9 | "dependencies": { 10 | "d3": ">=3.5.2" 11 | }, 12 | "version": "0.3.0", 13 | "homepage": "https://github.com/PMSI-AlignAlytics/scrollgrid", 14 | "authors": [ 15 | "johnkiernander " 16 | ], 17 | "main": "dist/scrollgrid.latest.min.js", 18 | "moduleType": [ 19 | "amd" 20 | ], 21 | "keywords": [ 22 | "grid", 23 | "pivot", 24 | "table", 25 | "d3" 26 | ], 27 | "ignore": [ 28 | "**/.*", 29 | "app.js", 30 | "Gruntfile.js", 31 | "package.json", 32 | "node_modules", 33 | "bower_components", 34 | "test", 35 | "spec", 36 | "data", 37 | "examples", 38 | "lib", 39 | "src", 40 | "tmp" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /examples/async.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 33 | 34 | 35 | 70 |
71 | -------------------------------------------------------------------------------- /examples/scratchpad.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 32 | 33 | 34 | 54 |
55 | -------------------------------------------------------------------------------- /examples/test.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 41 | 42 | 43 | 77 |
78 | -------------------------------------------------------------------------------- /examples/tsv_file_viewer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 24 | 25 | 26 | 47 |
-------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basepath: '', 4 | frameworks: ['jasmine', 'requirejs'], 5 | files: [ 6 | 'test/bootstrap.js', 7 | { pattern: 'test/mock/*.js', included: false }, 8 | { pattern: 'test/spec/**/*.spec.js', included: false }, 9 | { pattern: 'src/*.js', included: false }, 10 | { pattern: 'src/**/*.js', included: false } 11 | ], 12 | reporters: ['progress','coverage'], 13 | preprocessors: { 14 | 'src/internal/**/*.js': ['coverage'], 15 | 'src/external/**/*.js': ['coverage'] 16 | }, 17 | port: 9876, 18 | colors: true, 19 | browsers: ['PhantomJS'], 20 | coverageReporter: { 21 | reporters: [ 22 | { 23 | type: 'lcov', 24 | dir: 'coverage/', 25 | subdir: '.', 26 | instrumenter: { 27 | '**/*.js': 'istanbul' 28 | } 29 | }, 30 | { 31 | type: 'text-summary', 32 | instrumenter: { 33 | '**/*.js': 'istanbul' 34 | } 35 | } 36 | ] 37 | } 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /lib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014, Michael Bostock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Michael Bostock may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrollgrid", 3 | "version": "0.3.0", 4 | "private": true, 5 | "devDependencies": { 6 | "coveralls": "*", 7 | "express": "*", 8 | "grunt": "^0.4.5", 9 | "grunt-contrib-concat": "^0.5.1", 10 | "grunt-contrib-connect": "^0.11.2", 11 | "grunt-contrib-copy": "^0.8.2", 12 | "grunt-contrib-uglify": "^0.11.0", 13 | "grunt-contrib-watch": "^0.6.1", 14 | "grunt-jslint": "^1.1.14", 15 | "grunt-karma": "^0.12.1", 16 | "istanbul": "*", 17 | "jasmine-core": "^2.4.1", 18 | "karma": "^0.13.15", 19 | "karma-chrome-launcher": "*", 20 | "karma-coffee-preprocessor": "*", 21 | "karma-coverage": "^0.5.3", 22 | "karma-firefox-launcher": "*", 23 | "karma-html2js-preprocessor": "*", 24 | "karma-jasmine": "^0.3.6", 25 | "karma-phantomjs-launcher": "^0.2.1", 26 | "karma-requirejs": "*", 27 | "karma-script-launcher": "*", 28 | "requirejs": "*" 29 | }, 30 | "buildDependencies": { 31 | "d3": "*" 32 | }, 33 | "scripts": { 34 | "test": "" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/external/adapters/json.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/adapters/json.js 5 | Scrollgrid.adapters.json = function (data, columns, options) { 6 | "use strict"; 7 | 8 | options = options || {}; 9 | 10 | var columnLookup = {}, 11 | headRow = {}, 12 | cols = columns || [], 13 | table = data, 14 | key, 15 | sampleSize = options.rowSampleSize || 100, 16 | i; 17 | 18 | // If columns aren't provided find them from the data 19 | if (cols.length === 0) { 20 | for (i = 0; i < Math.min(table.length, sampleSize); i += 1) { 21 | for (key in table[i]) { 22 | if (table[i].hasOwnProperty(key)) { 23 | if (columnLookup[key] === undefined) { 24 | columnLookup[key] = cols.length; 25 | cols.push(key); 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | for (i = 0; i < cols.length; i += 1) { 33 | headRow[cols[i]] = cols[i]; 34 | } 35 | 36 | table.splice(0, 0, headRow); 37 | 38 | return { 39 | rowCount: function () { return table.length; }, 40 | columnCount: function () { return cols.length; }, 41 | sort: function (column, headers, footers, descending, compareFunction) { 42 | var heads = table.splice(0, headers), 43 | foots = table.splice(table.length - footers), 44 | j; 45 | table.sort(function (a, b) { 46 | return compareFunction(a[cols[column]], b[cols[column]]) * (descending ? -1 : 1); 47 | }); 48 | for (j = heads.length - 1; j >= 0; j -= 1) { 49 | table.splice(0, 0, heads[j]); 50 | } 51 | for (j = 0; j < foots.length; j += 1) { 52 | table.push(foots[j]); 53 | } 54 | }, 55 | loadDataRange: function (viewArea, callback) { 56 | var r, c, row, d = []; 57 | for (r = viewArea.top; r < viewArea.bottom; r += 1) { 58 | row = []; 59 | for (c = viewArea.left; c < viewArea.right; c += 1) { 60 | row.push(table[r][cols[c]]); 61 | } 62 | d.push(row); 63 | } 64 | callback(d); 65 | } 66 | }; 67 | }; -------------------------------------------------------------------------------- /src/external/adapters/simple.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/adapters/simple.js 5 | Scrollgrid.adapters.simple = function (data, options) { 6 | "use strict"; 7 | 8 | options = options || {}; 9 | 10 | var columnCount = 0, 11 | table = data, 12 | sampleSize = options.rowSampleSize || 100, 13 | i; 14 | 15 | // Find the longest length of the data sub arrays 16 | for (i = 0; i < Math.min(table.length, sampleSize); i += 1) { 17 | if (table[i] && Object.prototype.toString.call(table[i]) === '[object Array]') { 18 | if (table[i].length > columnCount) { 19 | columnCount = table[i].length; 20 | } 21 | } 22 | } 23 | 24 | return { 25 | rowCount: function () { return table.length; }, 26 | columnCount: function () { return columnCount; }, 27 | sort: function (column, headers, footers, descending, compareFunction) { 28 | var heads = table.splice(0, headers), 29 | foots = table.splice(table.length - footers), 30 | j; 31 | table.sort(function (a, b) { 32 | return compareFunction(a[column], b[column]) * (descending ? -1 : 1); 33 | }); 34 | for (j = heads.length - 1; j >= 0; j -= 1) { 35 | table.splice(0, 0, heads[j]); 36 | } 37 | for (j = 0; j < foots.length; j += 1) { 38 | table.push(foots[j]); 39 | } 40 | }, 41 | loadDataRange: function (viewArea, callback) { 42 | var r, c, row, d = []; 43 | for (r = viewArea.top; r < viewArea.bottom; r += 1) { 44 | row = []; 45 | for (c = viewArea.left; c < viewArea.right; c += 1) { 46 | row.push(table[r][c]); 47 | } 48 | d.push(row); 49 | } 50 | callback(d); 51 | } 52 | }; 53 | 54 | }; -------------------------------------------------------------------------------- /src/external/addFormatRules.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/addFormatRules.js 5 | Scrollgrid.prototype.addFormatRules = function (rules, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | int = this.internal; 10 | if (rules) { 11 | // Set the value and redraw but return self for chaining 12 | props.formatRules = props.formatRules.concat(rules); 13 | int.sizes.initialiseColumns.call(this); 14 | if (!silent) { 15 | this.refresh(); 16 | } 17 | } 18 | 19 | return props.formatRules; 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/external/allowColumnResizing.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/allowColumnResizing.js 5 | Scrollgrid.prototype.allowColumnResizing = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.allowColumnResizing; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.allowColumnResizing = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/allowSorting.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/allowSorting.js 5 | Scrollgrid.prototype.allowSorting = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.allowSorting; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.allowSorting = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/cellPadding.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/cellPadding.js 5 | Scrollgrid.prototype.cellPadding = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.cellPadding; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.cellPadding = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/data.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/data.js 5 | Scrollgrid.prototype.data = function (data, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | int = this.internal, 10 | c; 11 | 12 | if (data) { 13 | 14 | // If the dataAdapter is an array, treat it as the data itself and instantiate with the default adapter 15 | if (Object.prototype.toString.call(data) === '[object Array]') { 16 | this.adapter = Scrollgrid.adapters.simple(data); 17 | } else { 18 | this.adapter = data; 19 | } 20 | props.virtualOuterHeight = this.adapter.rowCount(); 21 | props.virtualOuterWidth = this.adapter.columnCount(); 22 | 23 | // Set up the columns 24 | int.sizes.initialiseColumns.call(this); 25 | 26 | // If any of the columns have a sort it should be applied 27 | for (c = 0; c < this.columns.length; c += 1) { 28 | if (this.columns[c].sort === 'asc' || this.columns[c].sort === 'desc') { 29 | int.interaction.sortColumn.call(this, c, false); 30 | } 31 | } 32 | 33 | // Calculate the bounds of the data displayable in the main grid 34 | props.virtualInnerWidth = props.virtualOuterWidth - props.virtualLeft - props.virtualRight; 35 | props.virtualInnerHeight = props.virtualOuterHeight - props.virtualTop - props.virtualBottom; 36 | 37 | // Render the control 38 | if (!silent) { 39 | this.refresh(false); 40 | } 41 | 42 | } 43 | 44 | return this.adapter; 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /src/external/dragHandleWidth.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/dragHandleWidth.js 5 | Scrollgrid.prototype.dragHandleWidth = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.dragHandleWidth; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.dragHandleWidth = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/footerColumns.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/footerColumns.js 5 | Scrollgrid.prototype.footerColumns = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.virtualRight; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.virtualRight = value; 16 | props.virtualInnerWidth = props.virtualOuterWidth - props.virtualLeft - props.virtualRight; 17 | result = this; 18 | if (!silent) { 19 | this.refresh(); 20 | } 21 | } 22 | 23 | return result; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/external/footerRowHeight.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/footerRowHeight.js 5 | Scrollgrid.prototype.footerRowHeight = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.footerRowHeight; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.footerRowHeight = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/footerRows.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/footerRows.js 5 | Scrollgrid.prototype.footerRows = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.virtualBottom; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.virtualBottom = value; 16 | props.virtualInnerHeight = props.virtualOuterHeight - props.virtualTop - props.virtualBottom; 17 | result = this; 18 | if (!silent) { 19 | this.refresh(); 20 | } 21 | } 22 | 23 | return result; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/external/formatRules.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/formatRules.js 5 | Scrollgrid.prototype.formatRules = function (value, silent) { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | props = this.properties, 10 | result; 11 | 12 | if (value === undefined) { 13 | result = props.formatRules; 14 | } else { 15 | // Set the value and redraw but return self for chaining 16 | props.formatRules = value; 17 | int.sizes.initialiseColumns.call(this); 18 | result = this; 19 | if (!silent) { 20 | this.refresh(); 21 | } 22 | } 23 | 24 | return result; 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /src/external/headerColumns.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/headerColumns.js 5 | Scrollgrid.prototype.headerColumns = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.virtualLeft; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.virtualLeft = value; 16 | props.virtualInnerWidth = props.virtualOuterWidth - props.virtualLeft - props.virtualRight; 17 | result = this; 18 | if (!silent) { 19 | this.refresh(); 20 | } 21 | } 22 | 23 | return result; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/external/headerRowHeight.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/headerRowHeight.js 5 | Scrollgrid.prototype.headerRowHeight = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.headerRowHeight; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.headerRowHeight = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/headerRows.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/headerRows.js 5 | Scrollgrid.prototype.headerRows = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.virtualTop; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.virtualTop = value; 16 | props.virtualInnerHeight = props.virtualOuterHeight - props.virtualTop - props.virtualBottom; 17 | result = this; 18 | if (!silent) { 19 | this.refresh(); 20 | } 21 | } 22 | 23 | return result; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/external/on.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/on.js 5 | Scrollgrid.prototype.on = function (type, listener, capture) { 6 | "use strict"; 7 | 8 | this.eventHandlers.push({ 9 | type: type, 10 | listener: listener, 11 | capture: capture 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/external/refresh.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/refresh.js 5 | Scrollgrid.prototype.refresh = function (maintainCache) { 6 | "use strict"; 7 | 8 | var int = this.internal; 9 | 10 | // Call the instantiated layout refresh 11 | int.dom.layoutDOM.call(this); 12 | int.render.draw.call(this, !maintainCache, true); 13 | int.dom.setScrollerSize.call(this); 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /src/external/rowHeight.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/rowHeight.js 5 | Scrollgrid.prototype.rowHeight = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.rowHeight; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.rowHeight = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/external/sortIconSize.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/external/sortIconSize.js 5 | Scrollgrid.prototype.sortIconSize = function (value, silent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | result; 10 | 11 | if (value === undefined) { 12 | result = props.sortIconSize; 13 | } else { 14 | // Set the value and redraw but return self for chaining 15 | props.sortIconSize = value; 16 | result = this; 17 | if (!silent) { 18 | this.refresh(); 19 | } 20 | } 21 | 22 | return result; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/internal/dom/getTopMargin.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/getTopMargin.js 5 | Scrollgrid.prototype.internal.dom.getTopMargin = function (containerSize, parent) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | topMargin = 0, 10 | parentHeight; 11 | 12 | if (containerSize && containerSize.height && parent) { 13 | if (props.verticalAlignment === 'middle') { 14 | // This is duplicated in the conditions rather than outside the if because 15 | // it is costly and will not be used for most tables 16 | parentHeight = parent.node().offsetHeight; 17 | topMargin = ((parentHeight - containerSize.height) / 2); 18 | } else if (props.verticalAlignment === 'bottom') { 19 | // This is duplicated in the conditions rather than outside the if because 20 | // it is costly and will not be used for most tables 21 | parentHeight = parent.node().offsetHeight; 22 | topMargin = parentHeight - containerSize.height - 1; 23 | } 24 | } 25 | 26 | return topMargin; 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /src/internal/dom/layoutDOM.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/layoutDOM.js 5 | Scrollgrid.prototype.internal.dom.layoutDOM = function (fixedSize) { 6 | "use strict"; 7 | 8 | var self = this, 9 | int = self.internal, 10 | props = self.properties, 11 | elems = self.elements, 12 | topMargin; 13 | 14 | // This is required so content can size relative to it 15 | elems.parent 16 | .style('position', 'relative'); 17 | 18 | topMargin = int.dom.getTopMargin.call(self, fixedSize, elems.parent); 19 | 20 | elems.container 21 | .style('position', 'relative') 22 | .style('width', (fixedSize && fixedSize.width ? fixedSize.width + 'px' : '100%')) 23 | .style('height', (fixedSize && fixedSize.height ? (fixedSize.height) + 'px' : '100%')) 24 | .style('padding-top', topMargin + 'px') 25 | .style('font-size', 0); 26 | 27 | // If the fixed size is too great, reset to 100%, this gives the effect of 28 | // pinning the edges when they reach the limit of available space 29 | if (elems.container.node().offsetWidth > elems.parent.node().offsetWidth) { 30 | elems.container.style('width', '100%'); 31 | } 32 | if (elems.container.node().offsetHeight > elems.parent.node().offsetHeight) { 33 | elems.container 34 | .style('margin-top', '0px') 35 | .style('height', '100%'); 36 | } 37 | 38 | // Set the physical dimensions of the various data elements in memory 39 | int.sizes.calculatePhysicalBounds.call(self, topMargin); 40 | 41 | // Set all panels 42 | int.dom.setAbsolutePosition.call(self, elems.left.svg, 0, props.physicalTop + topMargin, props.physicalLeft, props.physicalVisibleInnerHeight); 43 | int.dom.setRelativePosition.call(self, elems.top.svg, props.physicalLeft, props.physicalVisibleInnerWidth, props.physicalTop, 'hidden'); 44 | int.dom.setRelativePosition.call(self, elems.main.viewport, props.physicalLeft, props.physicalVisibleInnerWidth, props.physicalVisibleInnerHeight, 'auto'); 45 | int.dom.setAbsolutePosition.call(self, elems.right.svg, props.physicalLeft + props.physicalVisibleInnerWidth, props.physicalTop + topMargin, props.physicalRight, props.physicalVisibleInnerHeight); 46 | int.dom.setRelativePosition.call(self, elems.bottom.svg, props.physicalLeft, props.physicalVisibleInnerWidth, props.physicalBottom, 'hidden'); 47 | int.dom.setAbsolutePosition.call(self, elems.top.left.svg, 0, topMargin, props.physicalLeft + props.dragHandleWidth / 2, props.physicalTop); 48 | int.dom.setAbsolutePosition.call(self, elems.top.right.svg, props.physicalLeft + props.physicalVisibleInnerWidth - props.dragHandleWidth / 2, topMargin, props.physicalRight + props.dragHandleWidth / 2, props.physicalTop); 49 | int.dom.setAbsolutePosition.call(self, elems.bottom.left.svg, 0, props.physicalTop + props.physicalVisibleInnerHeight + topMargin, props.physicalLeft, props.physicalBottom); 50 | int.dom.setAbsolutePosition.call(self, elems.bottom.right.svg, props.physicalLeft + props.physicalVisibleInnerWidth, props.physicalTop + props.physicalVisibleInnerHeight + topMargin, props.physicalRight, props.physicalBottom); 51 | int.dom.setAbsolutePosition.call(self, elems.main.svg, props.physicalLeft, props.physicalTop + topMargin, props.physicalVisibleInnerWidth, props.physicalVisibleInnerHeight); 52 | 53 | // Style all panels 54 | int.dom.stylePanels.call(this, this.style); 55 | 56 | // Top right panel needs a small offset for the handle 57 | elems.top.right.transform.attr('transform', 'translate(' + props.dragHandleWidth / 2 + ', 0)'); 58 | 59 | // Invoke draw on scroll 60 | elems.main.viewport.on('scroll', function () { int.render.draw.call(self, false, false); }); 61 | 62 | // Invoke eventHandlers of the target group behind the main viewport 63 | int.dom.redirectViewportEvents.call(self); 64 | 65 | // Set the scrollable area 66 | int.dom.setScrollerSize.call(self); 67 | 68 | // Get the scroll bar bounds 69 | props.verticalScrollbarWidth = elems.main.viewport.node().offsetWidth - elems.main.viewport.node().clientWidth; 70 | props.horizontalScrollbarHeight = elems.main.viewport.node().offsetHeight - elems.main.viewport.node().clientHeight; 71 | 72 | }; 73 | -------------------------------------------------------------------------------- /src/internal/dom/populateDOM.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/populateDOM.js 5 | Scrollgrid.prototype.internal.dom.populateDOM = function () { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | elems = this.elements; 10 | 11 | // Get the parent container 12 | elems.parent = d3.select(this.target); 13 | // Add a container to the target which will house everything 14 | elems.container = elems.parent.append('div'); 15 | 16 | // Populate the 5 regions of the control 17 | elems.left = int.dom.populatePanel.call(this); 18 | elems.top = int.dom.populatePanel.call(this); 19 | elems.top.left = int.dom.populatePanel.call(this); 20 | elems.top.right = int.dom.populatePanel.call(this); 21 | elems.main = int.dom.populatePanel.call(this); 22 | 23 | // Add the viewport which is the fixed area with scroll bars 24 | elems.main.viewport = elems.container.append('div'); 25 | 26 | elems.right = int.dom.populatePanel.call(this); 27 | elems.bottom = int.dom.populatePanel.call(this); 28 | elems.bottom.left = int.dom.populatePanel.call(this); 29 | elems.bottom.right = int.dom.populatePanel.call(this); 30 | 31 | // The scroller is going to be as large as the virtual size of 32 | // the data (as if it had all been rendered) this is so that 33 | // the scroll bars behave as expected 34 | elems.main.scroller = elems.main.viewport.append('div'); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /src/internal/dom/populatePanel.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/populatePanel.js 5 | Scrollgrid.prototype.internal.dom.populatePanel = function () { 6 | "use strict"; 7 | 8 | var elems = this.elements, 9 | panel = {}; 10 | 11 | panel.svg = elems.container.append('svg'); 12 | panel.transform = panel.svg.append('g'); 13 | panel.content = panel.transform.append('g'); 14 | 15 | return panel; 16 | }; 17 | -------------------------------------------------------------------------------- /src/internal/dom/redirectViewportEvents.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/redirectViewportEvents.js 5 | Scrollgrid.prototype.internal.dom.redirectViewportEvents = function () { 6 | "use strict"; 7 | 8 | var self = this, 9 | elems = self.elements, 10 | j, 11 | getRedirectHandler; 12 | 13 | getRedirectHandler = function (elems, eventType) { 14 | return function () { 15 | var mouse, 16 | svg, 17 | rpos, 18 | list, 19 | target, 20 | g, 21 | targetEventHandler; 22 | 23 | mouse = d3.mouse(this); 24 | 25 | svg = elems.main.svg.node(); 26 | rpos = svg.createSVGRect(); 27 | rpos.x = mouse[0]; 28 | rpos.y = mouse[1]; 29 | rpos.width = 1; 30 | rpos.height = 1; 31 | 32 | list = svg.getIntersectionList(rpos, null); 33 | if (list.length) { 34 | target = list[0].parentNode; 35 | g = d3.select(target); 36 | targetEventHandler = g.on(eventType); 37 | if (targetEventHandler) { 38 | targetEventHandler.call(target, g.datum()); 39 | } 40 | } 41 | }; 42 | }; 43 | 44 | for (j = 0; j < this.eventHandlers.length; j += 1) { 45 | elems.main.viewport.on(this.eventHandlers[j].type, getRedirectHandler(elems, this.eventHandlers[j].type)); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/internal/dom/setAbsolutePosition.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/setAbsolutePosition.js 5 | Scrollgrid.prototype.internal.dom.setAbsolutePosition = function (element, x, y, width, height) { 6 | "use strict"; 7 | 8 | return element 9 | .style('position', 'absolute') 10 | .style('overflow', 'hidden') 11 | .style('left', x + 'px') 12 | .style('top', y + 'px') 13 | .style('width', width + 'px') 14 | .style('height', height + 'px'); 15 | }; -------------------------------------------------------------------------------- /src/internal/dom/setAutoResize.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/setAutoResize.js 5 | Scrollgrid.prototype.internal.dom.setAutoResize = function () { 6 | "use strict"; 7 | 8 | // Pick up any existing resize handlers 9 | var self = this, 10 | existingHandler = window.onresize; 11 | 12 | // Add a new handler 13 | window.onresize = function () { 14 | // Invoke the non-scrollgrid resize handlers 15 | if (existingHandler) { 16 | existingHandler(); 17 | } 18 | // Call the instantiated layout refresh 19 | self.refresh(true); 20 | }; 21 | 22 | }; -------------------------------------------------------------------------------- /src/internal/dom/setRelativePosition.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/setRelativePosition.js 5 | Scrollgrid.prototype.internal.dom.setRelativePosition = function (element, x, width, height, overflow) { 6 | "use strict"; 7 | 8 | return element 9 | .style('overflow', overflow) 10 | .style('position', 'relative') 11 | .style('margin-left', x + 'px') 12 | .style('width', width + 'px') 13 | .style('height', height + 'px'); 14 | }; -------------------------------------------------------------------------------- /src/internal/dom/setScrollerSize.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/setScrollerSize.js 5 | Scrollgrid.prototype.internal.dom.setScrollerSize = function () { 6 | "use strict"; 7 | 8 | var elems = this.elements, 9 | props = this.properties; 10 | 11 | elems.main.scroller 12 | .style('width', props.physicalTotalInnerWidth + 'px') 13 | .style('height', props.physicalTotalInnerHeight + 'px'); 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /src/internal/dom/stylePanels.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/dom/stylePanels.js 5 | Scrollgrid.prototype.internal.dom.stylePanels = function (style) { 6 | "use strict"; 7 | 8 | var elems = this.elements; 9 | 10 | this.style = style || this.style; 11 | 12 | elems.left.svg.attr('class', this.style.left.panel); 13 | elems.top.svg.attr('class', this.style.top.panel); 14 | elems.right.svg.attr('class', this.style.right.panel); 15 | elems.bottom.svg.attr('class', this.style.bottom.panel); 16 | elems.top.left.svg.attr('class', this.style.top.left.panel); 17 | elems.top.right.svg.attr('class', this.style.top.right.panel); 18 | elems.bottom.left.svg.attr('class', this.style.bottom.left.panel); 19 | elems.bottom.right.svg.attr('class', this.style.bottom.right.panel); 20 | elems.main.svg.attr('class', this.style.main.panel); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /src/internal/events/addEventHandlers.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/renderBackground.js 5 | Scrollgrid.prototype.internal.events.addEventHandlers = function (g, viewData) { 6 | "use strict"; 7 | 8 | var i; 9 | 10 | g.attr("data-row", viewData.rowIndex) 11 | .attr("data-col", viewData.columnIndex); 12 | 13 | for (i = 0; i < this.eventHandlers.length; i += 1) { 14 | g.on(this.eventHandlers[i].type, this.eventHandlers[i].listener, this.eventHandlers[i].capture); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/internal/interaction/addResizeHandles.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/addResizeHandles.js 5 | Scrollgrid.prototype.internal.interaction.addResizeHandles = function (target, bounds, startX) { 6 | "use strict"; 7 | 8 | var self = this, 9 | props = this.properties, 10 | style = this.style, 11 | int = this.internal, 12 | runningTotal = startX || 0; 13 | 14 | target.content 15 | .selectAll(".sg-no-style--handle-selector") 16 | .remove(); 17 | 18 | target.content 19 | .selectAll(".sg-no-style--handle-selector") 20 | .data(this.columns.slice(bounds.left, bounds.right)) 21 | .enter() 22 | .append("rect") 23 | .attr("class", "sg-no-style--handle-selector " + style.resizeHandle) 24 | .attr("transform", "translate(" + (-1 * props.dragHandleWidth / 2) + ", 0)") 25 | .attr("x", function (c) { 26 | runningTotal += c.width; 27 | c.x = runningTotal; 28 | return c.x; 29 | }) 30 | .attr("y", 0) 31 | .attr("width", props.dragHandleWidth) 32 | .attr("height", props.physicalTop) 33 | .on("dblclick", function (c) { int.interaction.autoResizeColumn.call(self, c); }) 34 | .call(int.interaction.getColumnResizer.call(self)); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /src/internal/interaction/addSortButtons.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/addSortButtons.js 5 | Scrollgrid.prototype.internal.interaction.addSortButtons = function (g, viewData) { 6 | "use strict"; 7 | 8 | var self = this, 9 | int = this.internal; 10 | 11 | g.append("rect") 12 | .attr("width", viewData.boxWidth) 13 | .attr("height", viewData.boxHeight) 14 | .style("opacity", 0) 15 | .style("cursor", "pointer") 16 | .on("click", function () { return int.interaction.sortColumn.call(self, viewData.columnIndex, true); }); 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /src/internal/interaction/autoResizeColumn.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/autoResizeColumn.js 5 | Scrollgrid.prototype.internal.interaction.autoResizeColumn = function (column) { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | elems = this.elements, 10 | panels = [elems.top.left, elems.top, elems.top.right, elems.left, elems.main, elems.right, elems.bottom.left, elems.bottom, elems.bottom.right], 11 | i; 12 | 13 | // Do not allow the width to be less than 0 14 | column.width = 0; 15 | 16 | // Get the widest from the various panels (some panels may not apply to the given cell but those panels will return zero anyway) 17 | for (i = 0; i < panels.length; i += 1) { 18 | column.width = Math.max(column.width, int.sizes.getExistingTextBound.call(this, panels[i].svg, column.index).width); 19 | } 20 | 21 | // Update the container size because the width will have changed 22 | this.refresh(true); 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/internal/interaction/columnResizeEnd.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/columnResizeEnd.js 5 | Scrollgrid.prototype.internal.interaction.columnResizeEnd = function (shape) { 6 | "use strict"; 7 | 8 | shape.classed('dragging', false); 9 | 10 | }; -------------------------------------------------------------------------------- /src/internal/interaction/columnResizeStart.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/columnResizeStart.js 5 | Scrollgrid.prototype.internal.interaction.columnResizeStart = function (shape) { 6 | "use strict"; 7 | 8 | d3.event.sourceEvent.stopPropagation(); 9 | shape.classed('dragging', true); 10 | 11 | }; -------------------------------------------------------------------------------- /src/internal/interaction/columnResizing.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/columnResizing.js 5 | Scrollgrid.prototype.internal.interaction.columnResizing = function (shape, column) { 6 | "use strict"; 7 | 8 | // Some resize handle should be inverted 9 | column.width -= column.x - d3.event.x; 10 | 11 | // Update the x coordinate for the drag 12 | column.x = d3.event.x; 13 | 14 | // If the column width is below 0 reset it, negative widths cause problems 15 | if (column.width < 0) { 16 | column.width = 0; 17 | } 18 | 19 | // Move the drag handle itself 20 | shape.attr('x', column.x); 21 | 22 | // Redraw 23 | this.refresh(true); 24 | 25 | }; -------------------------------------------------------------------------------- /src/internal/interaction/defaultComparer.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/defaultComparer.js 5 | Scrollgrid.prototype.internal.interaction.defaultComparer = function (a, b) { 6 | "use strict"; 7 | 8 | var order; 9 | 10 | if (isNaN(a) || isNaN(b)) { 11 | order = (new Date(a)) - (new Date(b)); 12 | } else { 13 | order = parseFloat(a) - parseFloat(b); 14 | } 15 | if (isNaN(order)) { 16 | order = (a < b ? -1 : (a > b ? 1 : 0)); 17 | } 18 | 19 | return order; 20 | 21 | }; -------------------------------------------------------------------------------- /src/internal/interaction/getColumnResizer.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/getColumnResizer.js 5 | Scrollgrid.prototype.internal.interaction.getColumnResizer = function () { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | self = this; 10 | 11 | return d3.behavior.drag() 12 | .origin(function (c) { return c; }) 13 | .on('dragstart', function () { int.interaction.columnResizeStart.call(self, d3.select(this)); }) 14 | .on('drag', function (c) { int.interaction.columnResizing.call(self, d3.select(this), c); }) 15 | .on('dragend', function () { int.interaction.columnResizeEnd.call(self, d3.select(this)); }); 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /src/internal/interaction/sortColumn.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/interaction/sortColumn.js 5 | Scrollgrid.prototype.internal.interaction.sortColumn = function (index, toggle) { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | props = this.properties, 10 | c; 11 | 12 | // Clear existing sorts and set the new one 13 | for (c = 0; c < this.columns.length; c += 1) { 14 | if (c !== index) { 15 | delete this.columns[c].sort; 16 | } else if (toggle) { 17 | this.columns[c].sort = (this.columns[c].sort === 'desc' ? 'asc' : 'desc'); 18 | } 19 | } 20 | 21 | // Instruct the adapter to perform a sort 22 | this.adapter.sort(index, props.virtualTop, props.virtualBottom, this.columns[index].sort === 'desc', this.columns[index].compareFunction || int.interaction.defaultComparer); 23 | this.refresh(false); 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/internal/raise.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/raise.js 5 | Scrollgrid.prototype.internal.raise = function (err) { 6 | "use strict"; 7 | 8 | var log = this.reporter || console; 9 | if (log && log.error) { 10 | log.error(err); 11 | } else { 12 | throw err; 13 | } 14 | 15 | }; -------------------------------------------------------------------------------- /src/internal/render/applyRules.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/applyRules.js 5 | Scrollgrid.prototype.internal.render.applyRules = function (data) { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | props = this.properties, 10 | rule, 11 | key, 12 | ruleDefinition = {}, 13 | i, 14 | k, 15 | r, 16 | c; 17 | 18 | if (props.formatRules) { 19 | 20 | // Iterate the focus data 21 | for (i = 0; i < data.length; i += 1) { 22 | 23 | ruleDefinition = {}; 24 | 25 | // Rules use 1 based indices for rows and columns, this is because they use negative 26 | // notation to refer to elements from the end e.g. row: -1 = last row. This would be 27 | // inconsistent if 0 was the first row. 28 | r = data[i].rowIndex + 1; 29 | c = data[i].columnIndex + 1; 30 | 31 | for (k = 0; k < props.formatRules.length; k += 1) { 32 | rule = props.formatRules[k]; 33 | if (int.render.matchRule.call(this, rule.row, r, props.virtualOuterHeight) && int.render.matchRule.call(this, rule.column, c, props.virtualOuterWidth)) { 34 | // Iterate the rule properties and apply them to the object 35 | for (key in rule) { 36 | if (rule.hasOwnProperty(key) && key !== "row" && key !== "column") { 37 | ruleDefinition[key] = rule[key]; 38 | } 39 | } 40 | } 41 | } 42 | 43 | // Apply the combined rules 44 | if (ruleDefinition.formatter) { 45 | data[i].formatter = ruleDefinition.formatter; 46 | } 47 | if (ruleDefinition.alignment) { 48 | data[i].alignment = ruleDefinition.alignment; 49 | } 50 | if (ruleDefinition.cellPadding) { 51 | data[i].cellPadding = ruleDefinition.cellPadding; 52 | } 53 | if (ruleDefinition.backgroundStyle) { 54 | data[i].backgroundStyle += " " + ruleDefinition.backgroundStyle; 55 | } 56 | if (ruleDefinition.foregroundStyle) { 57 | data[i].foregroundStyle += " " + ruleDefinition.foregroundStyle; 58 | } 59 | if (ruleDefinition.renderBackground) { 60 | data[i].renderBackground = ruleDefinition.renderBackground; 61 | } 62 | if (ruleDefinition.renderBetween) { 63 | data[i].renderBetween = ruleDefinition.renderBetween; 64 | } 65 | if (ruleDefinition.renderForeground) { 66 | data[i].renderForeground = ruleDefinition.renderForeground; 67 | } 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/internal/render/calculateCellAdjustments.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/calculateCellAdjustments.js 5 | Scrollgrid.prototype.internal.render.calculateCellAdjustments = function (row, column) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | extension = { 10 | x: 0, 11 | y: 0, 12 | boxHeight: 0, 13 | boxWidth: 0, 14 | textHeight: 0, 15 | textWidth: 0 16 | }; 17 | 18 | // If the cell is a columns header or footer and the column is the last in the dataset we need to extend the width 19 | // to remove the gap for the scrollbar 20 | if ((row < props.virtualTop || row >= props.virtualOuterHeight - props.virtualBottom) && column === props.virtualOuterWidth - props.virtualRight - 1) { 21 | extension.boxWidth += props.verticalScrollbarWidth; 22 | } 23 | // If the cell is a row header or footer and the row is the last in the dataset we need to extend the height to 24 | // remove the gap for the scrollbar 25 | if ((column < props.virtualLeft || column >= props.virtualOuterWidth - props.virtualRight) && row === props.virtualOuterHeight - props.virtualBottom - 1) { 26 | extension.boxHeight += props.horizontalScrollbarHeight; 27 | } 28 | // If the cell is the last column header reduce height by 1 to show the bottom gridline 29 | if (row === props.virtualTop - 1) { 30 | extension.boxHeight -= 1; 31 | } 32 | // If the cell is the first row after a column header and there is a column header extend it up to hide the top line 33 | if (row === props.virtualTop && row > 0) { 34 | extension.boxHeight += 1; 35 | extension.y -= 1; 36 | } 37 | // If the cell is the last row header reduce width by 1 to show the right gridline 38 | if (column === props.virtualLeft - 1) { 39 | extension.boxWidth -= 1; 40 | } 41 | // If the cell is the first column after a row header and there is a row header extend it left to hide the top line 42 | if (column === props.virtualLeft && column > 0) { 43 | extension.boxWidth += 1; 44 | extension.x -= 1; 45 | } 46 | // If the cell is in the last row shrink it to show the bottom line 47 | if (row === props.virtualOuterHeight - 1) { 48 | extension.boxHeight -= 1; 49 | } 50 | // If the cell is in the last column shrink it to show the right line 51 | if (column === props.virtualOuterWidth - 1) { 52 | extension.boxWidth -= 1; 53 | } 54 | // If the cell is in the last row of the column headers and the column is being sorted 55 | if (row === props.virtualTop - 1) { 56 | // Set the sort icon to that of the column 57 | extension.sortIcon = (this.columns[column] ? this.columns[column].sort : undefined); 58 | } 59 | 60 | return extension; 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /src/internal/render/cropText.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/cropText.js 5 | Scrollgrid.prototype.internal.render.cropText = function (textShape, width) { 6 | "use strict"; 7 | 8 | var textWidth = textShape.node().getBBox().width, 9 | text = textShape.text(), 10 | avgChar = textWidth / text.length, 11 | // Deliberately overestimate to start with and reduce toward target 12 | chars = Math.ceil(width / avgChar) + 1; 13 | 14 | // Store the unabbreviated text 15 | textShape.datum().originalText = text; 16 | 17 | // Handle cases where chars is < 0 (negative width) so it never enters while loop 18 | if (chars <= 0) { 19 | textShape.text(""); 20 | } 21 | 22 | while (textWidth > width && chars >= 0) { 23 | if (chars === 0) { 24 | textShape.text(""); 25 | } else { 26 | textShape.text(text.substring(0, chars)); 27 | } 28 | textWidth = textShape.node().getBBox().width; 29 | chars -= 1; 30 | } 31 | 32 | }; -------------------------------------------------------------------------------- /src/internal/render/draw.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/draw.js 5 | Scrollgrid.prototype.internal.render.draw = function (clearCache, reviewSize) { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | props = this.properties, 10 | elems = this.elements, 11 | physicalViewArea = int.render.getVisibleRegion.call(this), 12 | viewArea = int.render.getDataBounds.call(this, physicalViewArea), 13 | totalWidth, 14 | totalHeight, 15 | fixedSize = {}, 16 | p = viewArea.physical, 17 | v = viewArea.virtual, 18 | y = { 19 | top: { top: 0, bottom: props.virtualTop }, 20 | middle: { top: props.virtualTop + v.top, bottom: props.virtualTop + v.bottom }, 21 | bottom: { top: props.virtualOuterHeight - props.virtualBottom, bottom: props.virtualOuterHeight } 22 | }, 23 | x = { 24 | left: { left: 0, right: props.virtualLeft }, 25 | middle: { left: props.virtualLeft + v.left, right: props.virtualLeft + v.right }, 26 | right: { left: props.virtualOuterWidth - props.virtualRight, right: props.virtualOuterWidth } 27 | }; 28 | 29 | // Draw the separate regions 30 | int.render.renderRegion.call(this, elems.top.left, {}, x.left, y.top, clearCache); 31 | int.render.renderRegion.call(this, elems.top, { x: p.x }, x.middle, y.top, clearCache); 32 | int.render.renderRegion.call(this, elems.top.right, {}, x.right, y.top, clearCache); 33 | int.render.renderRegion.call(this, elems.left, { y: p.y }, x.left, y.middle, clearCache); 34 | int.render.renderRegion.call(this, elems.main, { x: p.x, y: p.y }, x.middle, y.middle, clearCache); 35 | int.render.renderRegion.call(this, elems.right, { y: p.y }, x.right, y.middle, clearCache); 36 | int.render.renderRegion.call(this, elems.bottom.left, {}, x.left, y.bottom, clearCache); 37 | int.render.renderRegion.call(this, elems.bottom, { x: p.x }, x.middle, y.bottom, clearCache); 38 | int.render.renderRegion.call(this, elems.bottom.right, {}, x.right, y.bottom, clearCache); 39 | 40 | // Add resize handles 41 | if (props.allowColumnResizing) { 42 | int.interaction.addResizeHandles.call(this, elems.top.left, x.left); 43 | int.interaction.addResizeHandles.call(this, elems.top, x.middle, p.x); 44 | int.interaction.addResizeHandles.call(this, elems.top.right, x.right); 45 | } 46 | 47 | // Calculate if the rendering means that the width of the 48 | // whole table should change and layout accordingly, this only runs if the flag is set 49 | // this is because it can be slow to run when scrolling 50 | if (reviewSize) { 51 | totalWidth = (props.physicalLeft + props.physicalTotalInnerWidth + props.physicalRight + props.verticalScrollbarWidth); 52 | fixedSize.width = (totalWidth < elems.parent.node().offsetWidth ? totalWidth : null); 53 | totalHeight = (props.physicalTop + props.physicalTotalInnerHeight + props.physicalBottom + props.horizontalScrollbarHeight); 54 | fixedSize.height = (totalHeight < elems.parent.node().offsetHeight ? totalHeight : null); 55 | int.dom.layoutDOM.call(this, fixedSize); 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /src/internal/render/getClipPath.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/getClipPath.js 5 | Scrollgrid.prototype.internal.render.getClipPath = function (viewData) { 6 | "use strict"; 7 | var right = (viewData.textWidth - viewData.cellPadding - (!(!viewData.sortIcon || viewData.sortIcon === 'none') ? this.properties.sortIconSize + viewData.cellPadding : 0)) + "px", 8 | bottom = (viewData.textHeight - viewData.cellPadding) + "px"; 9 | return "polygon(0px 0px, " + right + " 0px, " + right + " " + bottom + ", 0px " + bottom + ")"; 10 | }; 11 | -------------------------------------------------------------------------------- /src/internal/render/getDataBounds.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/getDataBounds.js 5 | Scrollgrid.prototype.internal.render.getDataBounds = function (physicalViewArea) { 6 | "use strict"; 7 | 8 | var i, 9 | props = this.properties, 10 | cols = this.columns, 11 | runningX = 0, 12 | columnWidth, 13 | left, 14 | right, 15 | bounds = { 16 | physical: { 17 | x: 0, 18 | y: 0 19 | }, 20 | virtual: { 21 | top: Math.max(Math.floor(props.virtualInnerHeight * (physicalViewArea.top / props.physicalTotalInnerHeight) - 1), 0), 22 | bottom: Math.min(Math.ceil(props.virtualInnerHeight * (physicalViewArea.bottom / props.physicalTotalInnerHeight) + 1), props.virtualInnerHeight) 23 | } 24 | }; 25 | 26 | bounds.physical.y = bounds.virtual.top * props.rowHeight - physicalViewArea.top; 27 | for (i = 0; i < props.virtualInnerWidth; i += 1) { 28 | columnWidth = cols[i + props.virtualLeft].width; 29 | if (left === undefined && (i === props.virtualInnerWidth - 1 || runningX + columnWidth > physicalViewArea.left)) { 30 | left = i; 31 | bounds.physical.x = runningX - physicalViewArea.left; 32 | } 33 | if (right === undefined && (i === props.virtualInnerWidth - 1 || runningX + columnWidth > physicalViewArea.right)) { 34 | right = i + 1; 35 | break; 36 | } 37 | runningX += columnWidth; 38 | } 39 | 40 | bounds.virtual.left = Math.max(Math.floor(left), 0); 41 | bounds.virtual.right = Math.min(Math.ceil(right + 1), props.virtualInnerWidth); 42 | 43 | return bounds; 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /src/internal/render/getDataInBounds.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/getDataInBounds.js 5 | Scrollgrid.prototype.internal.render.getDataInBounds = function (viewArea) { 6 | "use strict"; 7 | 8 | var i, r, c, x, vc, vr = 0, 9 | int = this.internal, 10 | props = this.properties, 11 | cols = this.columns, 12 | column, 13 | runningX, 14 | runningY, 15 | rowHeight = 0, 16 | visibleData = [], 17 | adjustments; 18 | 19 | runningY = viewArea.startY; 20 | 21 | for (r = viewArea.top || 0, i = 0; r < viewArea.bottom || 0; r += 1) { 22 | rowHeight = int.sizes.getRowHeight.call(this, r); 23 | runningX = viewArea.startX || 0; 24 | vc = 0; 25 | for (c = viewArea.left || 0; c < viewArea.right || 0; c += 1, i += 1) { 26 | // Get any measurement modifiers based on cell position 27 | adjustments = int.render.calculateCellAdjustments.call(this, r, c); 28 | // Get the column definition 29 | column = cols[c]; 30 | // Get the x position of the cell 31 | x = Math.floor(runningX) + adjustments.x + 0.5; 32 | // Using direct assignment for speed 33 | visibleData[i] = { 34 | x: x, 35 | y: Math.floor(runningY) + adjustments.y + 0.5, 36 | visibleRow: vr, 37 | visibleColumn: vc, 38 | boxWidth: Math.ceil(column.width) + adjustments.boxWidth, 39 | boxHeight: Math.ceil(rowHeight) + adjustments.boxHeight, 40 | textWidth: Math.ceil(column.width) + adjustments.textWidth, 41 | textHeight: Math.ceil(rowHeight) + adjustments.textHeight, 42 | backgroundStyle: this.style.cellBackgroundPrefix + 'r' + (r + 1) + ' ' + this.style.cellBackgroundPrefix + 'c' + (c + 1), 43 | foregroundStyle: this.style.cellForegroundPrefix + 'r' + (r + 1) + ' ' + this.style.cellForegroundPrefix + 'c' + (c + 1), 44 | sortIcon: adjustments.sortIcon || 'none', 45 | cellPadding: props.cellPadding, 46 | alignment: 'left', 47 | rowIndex: r, 48 | columnIndex: c, 49 | column: column, 50 | formatter: null, 51 | renderForeground: int.render.renderForeground, 52 | renderBetween: null, 53 | renderBackground: int.render.renderBackground 54 | }; 55 | // We abuse the key here, cells will be rendered on enter only, we therefore 56 | // want to key by any value which should result in a redraw of a particular cell, 57 | // this has huge performance benefits. The 58 | visibleData[i].key = visibleData[i].columnIndex + '_' + visibleData[i].rowIndex + "_" + visibleData[i].boxHeight + "_" + visibleData[i].boxWidth + "_" + visibleData[i].sortIcon; 59 | vc += 1; 60 | runningX += column.width; 61 | } 62 | vr += 1; 63 | runningY += rowHeight; 64 | } 65 | 66 | // Modify the data based on the user rules 67 | int.render.applyRules.call(this, visibleData); 68 | 69 | return visibleData; 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /src/internal/render/getTextAnchor.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/getTextAnchor.js 5 | Scrollgrid.prototype.internal.render.getTextAnchor = function (d) { 6 | "use strict"; 7 | 8 | var anchor = 'start'; 9 | 10 | if (d.alignment === 'center') { 11 | anchor = 'middle'; 12 | } else if (d.alignment === 'right') { 13 | anchor = 'end'; 14 | } 15 | 16 | return anchor; 17 | 18 | }; -------------------------------------------------------------------------------- /src/internal/render/getTextPosition.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/getTextPosition.js 5 | Scrollgrid.prototype.internal.render.getTextPosition = function (d) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | x = 0; 10 | 11 | if (d.alignment === 'center') { 12 | x += d.textWidth / 2; 13 | } else if (d.alignment === 'right') { 14 | x += d.textWidth - d.cellPadding; 15 | } else { 16 | x += d.cellPadding; 17 | if (d.sortIcon && d.sortIcon !== 'none') { 18 | x += props.sortIconSize + d.cellPadding; 19 | } 20 | } 21 | 22 | return x; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/internal/render/getVisibleRegion.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/getVisibleRegion.js 5 | Scrollgrid.prototype.internal.render.getVisibleRegion = function () { 6 | "use strict"; 7 | 8 | var elems = this.elements, 9 | visibleRegion; 10 | 11 | visibleRegion = {}; 12 | 13 | visibleRegion.left = elems.main.viewport.node().scrollLeft; 14 | visibleRegion.top = elems.main.viewport.node().scrollTop; 15 | visibleRegion.right = visibleRegion.left + elems.main.viewport.node().clientWidth; 16 | visibleRegion.bottom = visibleRegion.top + elems.main.viewport.node().clientHeight; 17 | 18 | return visibleRegion; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /src/internal/render/matchRule.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/matchRule.js 5 | Scrollgrid.prototype.internal.render.matchRule = function (ruleSelector, toCompare, extremity) { 6 | "use strict"; 7 | 8 | // Default for selection is to match. So no row or column definition will match all 9 | var match = false, 10 | defs, 11 | rangeMarker, 12 | skipRange, 13 | skip, 14 | lhs, 15 | rhs, 16 | min, 17 | max, 18 | i; 19 | 20 | // Valid rule selectors are: 21 | // "12" Match 12th element of dimension 22 | // "12,14" Match 12th and 14th element of dimension 23 | // "12:14" Match 12th, 13th and 14th element of dimension 24 | // "12:14,16" Match 12th, 13th, 14th and 16th element of dimension 25 | // "-1" Match last element of dimension 26 | // "-2:-1" Match last two elements of dimension 27 | // "2:-2" Match all but first and last element 28 | // "12(2)16" Match every 2nd element from the 12th to the 16th. i.e. 12th,14th,16th 29 | // "*" Match all elements (same as no selector 30 | if (!ruleSelector || ruleSelector === "*") { 31 | match = true; 32 | } else { 33 | // Split comma separated into an array, each sub-element can be processed as a rule on its own 34 | defs = ruleSelector.toString().replace(/\s/g, '').split(","); 35 | for (i = 0; i < defs.length; i += 1) { 36 | rangeMarker = defs[i].indexOf(":"); 37 | // Find the pattern a(n)b where we want every nth cell 38 | skipRange = defs[i].match(/(\-{0,1}[0-9]+)\(([0-9]+)\)(\-{0,1}[0-9]+)/); 39 | if (rangeMarker !== -1) { 40 | // Handle ranges a:b 41 | lhs = parseFloat(defs[i].substring(0, rangeMarker)); 42 | skip = 1; 43 | rhs = parseFloat(defs[i].substring(rangeMarker + 1)); 44 | } else if (skipRange && skipRange.length === 4) { 45 | // Handle skip ranges a(n)b 46 | lhs = parseFloat(skipRange[1]); 47 | skip = Math.max(parseFloat(skipRange[2]), 1); 48 | rhs = parseFloat(skipRange[3]); 49 | } else { 50 | // Handle single values by creating as a range where both ends match 51 | lhs = parseFloat(defs[i]); 52 | skip = 1; 53 | rhs = lhs; 54 | } 55 | // Handle the requirement for negatives to come from the end of the set 56 | lhs = (lhs < 0 ? extremity + lhs + 1 : lhs); 57 | rhs = (rhs < 0 ? extremity + rhs + 1 : rhs); 58 | // Match them from min to max regardless of the way they are defined 59 | min = Math.min(lhs, rhs); 60 | max = Math.max(lhs, rhs); 61 | // Check that the cell is in the range and not skipped 62 | match = (min <= toCompare && max >= toCompare) && ((toCompare - min) % skip === 0); 63 | // If any match the rule passes 64 | if (match) { 65 | break; 66 | } 67 | } 68 | } 69 | 70 | return match; 71 | 72 | }; 73 | -------------------------------------------------------------------------------- /src/internal/render/renderBackground.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/renderBackground.js 5 | Scrollgrid.prototype.internal.render.renderBackground = function (g, viewData) { 6 | "use strict"; 7 | 8 | g.append("rect") 9 | .attr("class", viewData.backgroundStyle) 10 | .attr("width", viewData.boxWidth) 11 | .attr("height", viewData.boxHeight); 12 | 13 | }; -------------------------------------------------------------------------------- /src/internal/render/renderForeground.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/renderForeground.js 5 | Scrollgrid.prototype.internal.render.renderForeground = function (g, viewData) { 6 | "use strict"; 7 | 8 | var self = this, 9 | int = this.internal, 10 | props = this.properties, 11 | path, 12 | text; 13 | 14 | // Clear any existing text 15 | g.selectAll(".sg-no-style--text-selector").remove(); 16 | 17 | text = g.append("text") 18 | .attr("class", "sg-no-style--text-selector " + viewData.foregroundStyle) 19 | .attr("dy", "0.35em") 20 | .attr("x", int.render.getTextPosition.call(self, viewData)) 21 | .attr("y", viewData.textHeight / 2) 22 | .style("text-anchor", int.render.getTextAnchor.call(self, viewData)) 23 | .style("clip-path", int.render.getClipPath.call(self, viewData)); 24 | 25 | if (viewData.formatter) { 26 | text.text(viewData.formatter(viewData.value)); 27 | } else { 28 | text.text(viewData.value); 29 | } 30 | 31 | path = text.node().style.clipPath; 32 | // If the new clip path css doesn't work (I'm looking at you IE and Firefox) revert to the slower method 33 | if (!path || path === "") { 34 | int.render.cropText.call(self, text, viewData.textWidth - viewData.cellPadding - (!(!viewData.sortIcon || viewData.sortIcon === 'none') ? props.sortIconSize + viewData.cellPadding : 0)); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/internal/render/renderRegion.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/renderRegion.js 5 | Scrollgrid.prototype.internal.render.renderRegion = function (target, physicalOffset, xVirtual, yVirtual, clearCache) { 6 | "use strict"; 7 | 8 | var self = this, 9 | int = this.internal, 10 | props = this.properties, 11 | elems = this.elements, 12 | cells, 13 | metadata, 14 | bounds; 15 | 16 | if ((xVirtual.left || 0) !== (xVirtual.right || 0) && (yVirtual.top || 0) !== (yVirtual.bottom || 0)) { 17 | 18 | bounds = { 19 | startX: physicalOffset.x || 0, 20 | startY: physicalOffset.y || 0, 21 | top: yVirtual.top || 0, 22 | bottom: yVirtual.bottom || 0, 23 | left: xVirtual.left || 0, 24 | right: xVirtual.right || 0 25 | }; 26 | 27 | metadata = int.render.getDataInBounds.call(self, bounds); 28 | 29 | // On refresh we will clear and redraw everything. This can 30 | // be invoked externally or internally on full grid changes. On scroll or resize 31 | // we don't want to clear the cache because affected cells will be redrawn anyway 32 | if (clearCache) { 33 | target.content 34 | .selectAll(".sg-no-style--cell-selector") 35 | .remove(); 36 | } 37 | 38 | cells = target.content 39 | .selectAll(".sg-no-style--cell-selector") 40 | .data(metadata, function (d) { 41 | return d.key; 42 | }); 43 | 44 | // We use the cell key to invoke an enter if the cell needs a render 45 | // for any reason, this means everything here happens on enter. 46 | cells.enter() 47 | .append("g") 48 | .attr("class", "sg-no-style--cell-selector") 49 | .each(function (d) { 50 | var group = d3.select(this); 51 | if (d.renderBackground) { 52 | d.renderBackground.call(self, group, d); 53 | } 54 | int.render.renderSortIcon.call(self, d, group, !(!d.sortIcon || d.sortIcon === 'none')); 55 | // Add some interaction to the headers 56 | if (props.allowSorting && (target === elems.top || target === elems.top.left || target === elems.top.right)) { 57 | int.interaction.addSortButtons.call(self, group, d); 58 | } 59 | 60 | // Register events 61 | int.events.addEventHandlers.call(self, group, d); 62 | }); 63 | 64 | // Draw the foreground separately to allow for asynchronous adapters 65 | int.render.renderRegionForeground.call(this, bounds, cells); 66 | 67 | cells.attr("transform", function (d) { 68 | return "translate(" + d.x + "," + d.y + ")"; 69 | }); 70 | 71 | cells.exit() 72 | .remove(); 73 | 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/internal/render/renderRegionForeground.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/renderRegionForeground.js 5 | Scrollgrid.prototype.internal.render.renderRegionForeground = function (bounds, cells) { 6 | "use strict"; 7 | 8 | var self = this; 9 | 10 | this.adapter.loadDataRange.call(this, bounds, function (data) { 11 | cells.each(function (d) { 12 | var group = d3.select(this); 13 | 14 | // Get the value from the visible range and set it in the data object 15 | d.value = data[d.visibleRow][d.visibleColumn]; 16 | 17 | if (d.renderBetween) { 18 | d.renderBetween.call(self, group, d); 19 | } 20 | if (d.renderForeground) { 21 | d.renderForeground.call(self, group, d); 22 | } 23 | }); 24 | }); 25 | }; -------------------------------------------------------------------------------- /src/internal/render/renderSortIcon.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/renderSortIcon.js 5 | Scrollgrid.prototype.internal.render.renderSortIcon = function (d, target, sorted) { 6 | "use strict"; 7 | 8 | var self = this, 9 | int = this.internal, 10 | props = this.properties; 11 | 12 | if (sorted && d.textWidth > d.cellPadding + props.sortIconSize) { 13 | target.append("g") 14 | .datum(d.sortIcon) 15 | .attr("class", "sg-no-style--sort-icon-selector") 16 | .attr("transform", "translate(" + (d.cellPadding + props.sortIconSize / 2) + "," + (d.textHeight / 2) + ")") 17 | .call(function (d) { return int.render.sortIcon.call(self, d); }); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /src/internal/render/setDefaultStyles.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/setDefaultStyles.js 5 | Scrollgrid.prototype.internal.render.setDefaultStyles = function () { 6 | "use strict"; 7 | 8 | // Define default classes, these are kept external as users might want to use their own 9 | this.style = { 10 | left: { 11 | panel: 'sg-grid sg-fixed sg-left' 12 | }, 13 | top: { 14 | panel: 'sg-grid sg-fixed sg-top', 15 | left: { 16 | panel: 'sg-grid sg-fixed sg-top sg-left' 17 | }, 18 | right: { 19 | panel: 'sg-grid sg-fixed sg-top sg-right' 20 | } 21 | }, 22 | right: { 23 | panel: 'sg-grid sg-fixed sg-right' 24 | }, 25 | bottom: { 26 | panel: 'sg-grid sg-fixed sg-bottom', 27 | left: { 28 | panel: 'sg-grid sg-fixed sg-bottom sg-left' 29 | }, 30 | right: { 31 | panel: 'sg-grid sg-fixed sg-bottom sg-right' 32 | } 33 | }, 34 | main: { 35 | panel: 'sg-grid' 36 | }, 37 | resizeHandle: 'sg-resize-handle', 38 | cellBackgroundPrefix: 'sg-cell-background-', 39 | cellForegroundPrefix: 'sg-cell-foreground-', 40 | sortIcon: 'sg-sort-icon' 41 | }; 42 | 43 | }; -------------------------------------------------------------------------------- /src/internal/render/sortIcon.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/render/sortIcon.js 5 | Scrollgrid.prototype.internal.render.sortIcon = function (group) { 6 | "use strict"; 7 | 8 | var icon = group.append("path").attr("class", this.style.sortIcon), 9 | props = this.properties, 10 | size = props.sortIconSize; 11 | 12 | if (group.datum() === 'asc') { 13 | icon.attr("d", "M " + (size / 2) + " 0 L " + size + " " + size + " L 0 " + size + " z"); 14 | } else if (group.datum() === 'desc') { 15 | icon.attr("d", "M 0 0 L " + size + " 0 L " + (size / 2) + " " + size + " z"); 16 | } 17 | 18 | // Center it around zero 19 | icon.attr("transform", "translate(" + icon.node().getBBox().width / -2 + "," + icon.node().getBBox().height / -2 + ")"); 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/internal/sizes/calculatePhysicalBounds.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/sizes/calculatePhysicalBounds.js 5 | Scrollgrid.prototype.internal.sizes.calculatePhysicalBounds = function (topMargin) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | elems = this.elements, 10 | i; 11 | 12 | // Variable column widths mean horizontal sizes cost O(n) to calculate 13 | props.physicalLeft = 0; 14 | for (i = 0; i < props.virtualLeft; i += 1) { 15 | props.physicalLeft += this.columns[i].width; 16 | } 17 | props.physicalTotalInnerWidth = 0; 18 | for (i = props.virtualLeft; i < props.virtualOuterWidth - props.virtualRight; i += 1) { 19 | props.physicalTotalInnerWidth += this.columns[i].width; 20 | } 21 | props.physicalRight = 0; 22 | for (i = props.virtualOuterWidth - props.virtualRight; i < props.virtualOuterWidth; i += 1) { 23 | props.physicalRight += this.columns[i].width; 24 | } 25 | 26 | // Keeping static row height means vertical position calculations can stay O(1) 27 | props.physicalTop = props.virtualTop * props.headerRowHeight; 28 | props.physicalBottom = props.virtualBottom * props.footerRowHeight; 29 | props.physicalVisibleInnerWidth = elems.container.node().offsetWidth - props.physicalLeft - props.physicalRight; 30 | props.physicalVisibleInnerHeight = elems.container.node().offsetHeight - props.physicalTop - props.physicalBottom - topMargin; 31 | props.physicalTotalInnerHeight = props.virtualInnerHeight * props.rowHeight; 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /src/internal/sizes/calculateTextBound.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/sizes/calculateTextBound.js 5 | Scrollgrid.prototype.internal.sizes.calculateTextBound = function (surface, text) { 6 | "use strict"; 7 | 8 | var toMeasure, 9 | bounds, 10 | returnBounds = { width: 0, height: 0 }; 11 | 12 | // Append some text for measuring 13 | toMeasure = surface.append('text').text(text); 14 | // Get the bounds 15 | bounds = toMeasure.node().getBBox(); 16 | // Parse into a simpler object because the BBox object 17 | // has some IE restrictions 18 | returnBounds.width = bounds.width; 19 | returnBounds.height = bounds.height; 20 | // Remove from the dom 21 | toMeasure.remove(); 22 | 23 | return returnBounds; 24 | 25 | }; -------------------------------------------------------------------------------- /src/internal/sizes/getExistingTextBound.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/sizes/getExistingTextBound.js 5 | Scrollgrid.prototype.internal.sizes.getExistingTextBound = function (surface, column, row) { 6 | "use strict"; 7 | 8 | var int = this.internal, 9 | props = this.properties, 10 | returnBounds = { width: 0, height: 0 }; 11 | 12 | surface.selectAll("text") 13 | .filter(function (d) { 14 | return (column === undefined || d.columnIndex === column) && (row === undefined || d.rowIndex === row); 15 | }) 16 | .each(function (d) { 17 | var sortIconSize = (d.sortIcon && d.sortIcon !== 'none' ? props.sortIconSize + d.cellPadding : 0); 18 | returnBounds = int.sizes.pushTextBound(returnBounds, d3.select(this), d.cellPadding, sortIconSize); 19 | }); 20 | 21 | return returnBounds; 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /src/internal/sizes/getRowHeight.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/sizes/getRowHeight.js 5 | Scrollgrid.prototype.internal.sizes.getRowHeight = function (row) { 6 | "use strict"; 7 | 8 | var props = this.properties, 9 | rowHeight = 0; 10 | 11 | if (row < props.virtualTop) { 12 | rowHeight = props.headerRowHeight; 13 | } else if (row < props.virtualOuterHeight - props.virtualBottom) { 14 | rowHeight = props.rowHeight; 15 | } else { 16 | rowHeight = props.footerRowHeight; 17 | } 18 | 19 | return rowHeight; 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/internal/sizes/initialiseColumns.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/sizes/initialiseColumns.js 5 | Scrollgrid.prototype.internal.sizes.initialiseColumns = function () { 6 | "use strict"; 7 | 8 | var i, 9 | int = this.internal, 10 | props = this.properties, 11 | rule; 12 | 13 | // Initialise the columns if required 14 | this.columns = this.columns || []; 15 | 16 | for (i = 0; i < props.virtualOuterWidth; i += 1) { 17 | // Initialise with a default to ensure we always have a width 18 | this.columns[i] = this.columns[i] || {}; 19 | this.columns[i].width = this.columns[i].width || props.defaultColumnWidth; 20 | 21 | if (props.formatRules && props.formatRules.length > 0) { 22 | for (rule = 0; rule < props.formatRules.length; rule += 1) { 23 | if (int.render.matchRule.call(this, props.formatRules[rule].column, i + 1, props.virtualOuterWidth)) { 24 | this.columns[i] = { 25 | width: props.formatRules[rule].columnWidth || this.columns[i].width, 26 | index: i, 27 | sort: props.formatRules[rule].sort || this.columns[i].sort, 28 | compareFunction: props.formatRules[rule].compareFunction || this.columns[i].compareFunction 29 | }; 30 | } 31 | } 32 | } 33 | } 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /src/internal/sizes/pushTextBound.js: -------------------------------------------------------------------------------- 1 | 2 | // Copyright: 2017 AlignAlytics 3 | // License: "https://github.com/PMSI-AlignAlytics/scrollgrid/blob/master/MIT-LICENSE.txt" 4 | // Source: /src/internal/sizes/pushTextBound.js 5 | Scrollgrid.prototype.internal.sizes.pushTextBound = function (currentBounds, shape, cellPadding, sortIconSize) { 6 | "use strict"; 7 | 8 | var cellText = shape.text(), 9 | b; 10 | 11 | // Remove any abbreviation 12 | shape.text(shape.datum().originalText || shape.text()); 13 | 14 | // Get the bounds 15 | b = shape.node().getBBox(); 16 | if (b.width + 2 * cellPadding + sortIconSize > currentBounds.width) { 17 | currentBounds.width = b.width + 2 * cellPadding + sortIconSize; 18 | } 19 | if (b.height > currentBounds.height) { 20 | currentBounds.height = b.height; 21 | } 22 | // Reapply abbreviation 23 | shape.text(cellText); 24 | 25 | // Return the newly stretched bounds 26 | return currentBounds; 27 | 28 | }; -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | // Call init to build the name spaces. Code under test is loaded for each test in turn 3 | var tests = ["/base/src/init.js"], 4 | file; 5 | 6 | for (file in window.__karma__.files) { 7 | if (window.__karma__.files.hasOwnProperty(file)) { 8 | if (/\.spec\.js$/.test(file)) { 9 | tests.push(file); 10 | } 11 | } 12 | } 13 | 14 | requirejs.config({ 15 | 16 | // Karma serves files from '/base' 17 | baseUrl: '/base/src', 18 | 19 | paths: { 20 | 'mock': '../test/mock/scrollgrid', 21 | 'd3': '../test/mock/d3', 22 | 'init': '../src/init', 23 | 'internal': '../src/internal', 24 | 'dom': '../src/internal/dom', 25 | 'interaction': '../src/internal/interaction', 26 | 'render': '../src/internal/render', 27 | 'sizes': '../src/internal/sizes', 28 | 'events': '../src/internal/events', 29 | 'external': '../src/external', 30 | 'adapters': '../src/external/adapters' 31 | }, 32 | 33 | // ask Require.js to load these files (all our tests) 34 | deps: tests, 35 | 36 | // start test run, once Require.js is done 37 | callback: window.__karma__.start 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/spec/external/adapters/simple.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'adapters/simple'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("simple", function () { 5 | 6 | var underTest = Scrollgrid.adapters.simple; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not error if options are not passed", function () { 14 | expect(underTest.call(mock, [], [])).toBeDefined(); 15 | }); 16 | 17 | it("should not error if columns are not passed", function () { 18 | expect(underTest.call(mock, [])).toBeDefined(); 19 | }); 20 | 21 | it("should return the number of rows", function () { 22 | expect(underTest.call(mock, [ 23 | ["A", "B", "C"], 24 | [1, 2, 3, 4], 25 | [5, 6, 7, 8, 9, 10] 26 | ]).rowCount()).toEqual(3); 27 | }); 28 | 29 | it("should infer the number of columns from the passed data if columns are not passed", function () { 30 | expect(underTest.call(mock, [ 31 | ["A", "B", "C"], 32 | [1, 2, 3, 4], 33 | [5, 6, 7, 8, 9, 10] 34 | ]).columnCount()).toEqual(6); 35 | }); 36 | 37 | it("should infer the number of columns from the sample rows specified if columns is not passed", function () { 38 | expect(underTest.call(mock, [ 39 | ["A", "B", "C"], 40 | [1, 2, 3, 4], 41 | [5, 6, 7, 8, 9, 10] 42 | ], { rowSampleSize: 2 }).columnCount()).toEqual(4); 43 | }); 44 | 45 | it("should ignore null rows", function () { 46 | expect(underTest.call(mock, [ 47 | ["A", "B", "C"], 48 | [1, 2, 3, 4], 49 | null, 50 | [5, 6, 7, 8, 9, 10] 51 | ]).columnCount()).toEqual(6); 52 | }); 53 | 54 | it("should ignore non-array rows", function () { 55 | expect(underTest.call(mock, [ 56 | ["A", "B", "C"], 57 | [1, 2, 3, 4], 58 | {}, 59 | [5, 6, 7, 8, 9, 10] 60 | ]).columnCount()).toEqual(6); 61 | }); 62 | 63 | describe("return object", function () { 64 | var data, 65 | result; 66 | 67 | beforeEach(function () { 68 | data = [ 69 | ["A", "B", "C"], 70 | [1, 40, 300], 71 | [3, 50, 700], 72 | [5, 20, 400], 73 | [4, 70, 200], 74 | [2, 10, 100], 75 | [7, 60, 600], 76 | [6, 30, 500] 77 | ]; 78 | result = underTest.call(mock, data); 79 | }); 80 | 81 | it("should sort ignoring the header rows", function () { 82 | result.sort.call(mock, 1, 2, 1, false, function (a, b) { return a - b; }); 83 | expect(data).toEqual([ 84 | ["A", "B", "C"], 85 | [1, 40, 300], 86 | [2, 10, 100], 87 | [5, 20, 400], 88 | [3, 50, 700], 89 | [7, 60, 600], 90 | [4, 70, 200], 91 | [6, 30, 500] 92 | ]); 93 | }); 94 | 95 | it("should sort descending ignoring the header rows", function () { 96 | result.sort.call(mock, 1, 2, 1, true, function (a, b) { return a - b; }); 97 | expect(data).toEqual([ 98 | ["A", "B", "C"], 99 | [1, 40, 300], 100 | [4, 70, 200], 101 | [7, 60, 600], 102 | [3, 50, 700], 103 | [5, 20, 400], 104 | [2, 10, 100], 105 | [6, 30, 500] 106 | ]); 107 | }); 108 | 109 | it("should invoke the callback with the correct subset of data", function () { 110 | var cb = jasmine.createSpy("callback"); 111 | data = [ 112 | ["A", "B", "C"], 113 | [1, 40, 300], 114 | [4, 70, 200], 115 | [7, 60, 600], 116 | [3, 50, 700], 117 | [5, 20, 400], 118 | [2, 10, 100], 119 | [6, 30, 500] 120 | ]; 121 | result.loadDataRange({top: 0, bottom: 2, left: 0, right: 2}, cb); 122 | expect(cb).toHaveBeenCalledWith([["A", "B"], [1, 40]]); 123 | }); 124 | 125 | }); 126 | }); 127 | }); -------------------------------------------------------------------------------- /test/spec/external/addFormatRules.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/addFormatRules'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("addFormatRules", function () { 5 | 6 | var underTest = Scrollgrid.prototype.addFormatRules; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, {}, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the format rules if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(['A', 'B', 'C']); 20 | }); 21 | 22 | it("should append to the format rules if a single value is passed", function () { 23 | expect(underTest.call(mock, 'D')).toEqual(['A', 'B', 'C', 'D']); 24 | }); 25 | 26 | it("should append to the format rules if an array is passed", function () { 27 | expect(underTest.call(mock, ['D', 'E'])).toEqual(['A', 'B', 'C', 'D', 'E']); 28 | }); 29 | 30 | it("should call the initialise columns method", function () { 31 | underTest.call(mock, ['D', 'E']); 32 | expect(mock.internal.sizes.initialiseColumns).toHaveBeenCalled(); 33 | }); 34 | 35 | it("should call refresh when a parameter is passed", function () { 36 | underTest.call(mock, ['D', 'E']); 37 | expect(mock.refresh).toHaveBeenCalled(); 38 | }); 39 | 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/external/allowColumnResizing.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/allowColumnResizing'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("allowColumnResizing", function () { 5 | 6 | var underTest = Scrollgrid.prototype.allowColumnResizing; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, true, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(true); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, false); 24 | expect(mock.properties.allowColumnResizing).toEqual(false); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, false)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, false); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/external/allowSorting.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/allowSorting'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("allowSorting", function () { 5 | 6 | var underTest = Scrollgrid.prototype.allowSorting; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, true, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(true); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, false); 24 | expect(mock.properties.allowSorting).toEqual(false); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, false)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, false); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/external/cellPadding.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/cellPadding'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("cellPadding", function () { 5 | 6 | var underTest = Scrollgrid.prototype.cellPadding; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 19, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.cellPadding); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.cellPadding).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/external/data.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/data'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("data", function () { 5 | 6 | var underTest = Scrollgrid.prototype.data, 7 | result, 8 | simpleAdapter = Scrollgrid.adapters.simple, 9 | mockSimple; 10 | 11 | beforeEach(function () { 12 | mock.init(); 13 | d3.init(); 14 | // Reset the adapter 15 | Scrollgrid.adapters.simple = simpleAdapter; 16 | // Adapter fixture 17 | mockSimple = { 18 | rowCount: jasmine.createSpy("rowCount").and.returnValue(71), 19 | columnCount: jasmine.createSpy("columnCount").and.returnValue(73) 20 | }; 21 | }); 22 | 23 | it("should not refresh if no parameter is passed", function () { 24 | underTest.call(mock); 25 | expect(mock.refresh).not.toHaveBeenCalled(); 26 | }); 27 | 28 | it("should not refresh if true is passed to the 'silent' argument", function () { 29 | underTest.call(mock, [1, 2, 3], true); 30 | expect(mock.refresh).not.toHaveBeenCalled(); 31 | }); 32 | 33 | it("should use the simple adapter if an array is passed", function () { 34 | Scrollgrid.adapters.simple = jasmine.createSpy("Simple").and.returnValue(mockSimple); 35 | result = underTest.call(mock, [1, 2, 3]); 36 | expect(Scrollgrid.adapters.simple).toHaveBeenCalledWith([1, 2, 3]); 37 | expect(result).toEqual(mockSimple); 38 | }); 39 | 40 | it("should return the passed adapter if passed", function () { 41 | Scrollgrid.adapters.simple = jasmine.createSpy("Simple").and.returnValue(mockSimple); 42 | result = underTest.call(mock, mockSimple); 43 | expect(Scrollgrid.adapters.simple).not.toHaveBeenCalledWith(mockSimple); 44 | expect(result).toEqual(mockSimple); 45 | }); 46 | 47 | it("should set the outer height from the adapter", function () { 48 | result = underTest.call(mock, mockSimple); 49 | expect(mock.properties.virtualOuterHeight).toEqual(71); 50 | }); 51 | 52 | it("should set the outer width from the adapter", function () { 53 | result = underTest.call(mock, mockSimple); 54 | expect(mock.properties.virtualOuterWidth).toEqual(73); 55 | }); 56 | 57 | it("should initialise columns", function () { 58 | result = underTest.call(mock, mockSimple); 59 | expect(mock.internal.sizes.initialiseColumns).toHaveBeenCalled(); 60 | }); 61 | 62 | it("should sort any columns with sort defined", function () { 63 | mock.columns = [ 64 | { sort: 'none'}, 65 | { sort: 'asc'}, 66 | { sort: null}, 67 | {}, 68 | { sort: 'desc'}, 69 | { sort: 'incorrect string' } 70 | ]; 71 | result = underTest.call(mock, mockSimple); 72 | expect(mock.internal.interaction.sortColumn.calls.count()).toEqual(2); 73 | expect(mock.internal.interaction.sortColumn.calls.argsFor(0)).toEqual([1, false]); 74 | expect(mock.internal.interaction.sortColumn.calls.argsFor(1)).toEqual([4, false]); 75 | }); 76 | 77 | it("should calculate the inner width", function () { 78 | mock.properties.virtualLeft = 5; 79 | mock.properties.virtualRight = 7; 80 | result = underTest.call(mock, mockSimple); 81 | expect(mock.properties.virtualInnerWidth).toEqual(73 - 5 - 7); 82 | }); 83 | 84 | it("should calculate the inner height", function () { 85 | mock.properties.virtualTop = 11; 86 | mock.properties.virtualBottom = 13; 87 | result = underTest.call(mock, mockSimple); 88 | expect(mock.properties.virtualInnerHeight).toEqual(71 - 11 - 13); 89 | }); 90 | 91 | 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /test/spec/external/dragHandleWidth.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/dragHandleWidth'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("dragHandleWidth", function () { 5 | 6 | var underTest = Scrollgrid.prototype.dragHandleWidth; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 7, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.dragHandleWidth); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.dragHandleWidth).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/external/footerColumns.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/footerColumns'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("footerColumns", function () { 5 | 6 | var underTest = Scrollgrid.prototype.footerColumns; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 2, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.virtRight); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.virtualRight).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/external/footerRowHeight.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/footerRowHeight'], function (d3, mock) { 2 | "use strict"; 3 | 4 | 5 | describe("footerRowHeight", function () { 6 | 7 | var underTest = Scrollgrid.prototype.footerRowHeight; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | }); 13 | 14 | it("should not refresh if true is passed to the 'silent' argument", function () { 15 | underTest.call(mock, 13, true); 16 | expect(mock.refresh).not.toHaveBeenCalled(); 17 | }); 18 | 19 | it("should return the column resizing value if no parameter is passed", function () { 20 | expect(underTest.call(mock)).toEqual(mock.vals.footerRowHeight); 21 | }); 22 | 23 | it("should update the column resizing value to a passed parameter", function () { 24 | underTest.call(mock, 13); 25 | expect(mock.properties.footerRowHeight).toEqual(13); 26 | }); 27 | 28 | it("should return the context when a parameter is passed", function () { 29 | expect(underTest.call(mock, 13)).toEqual(mock); 30 | }); 31 | 32 | it("should call refresh when a parameter is passed", function () { 33 | underTest.call(mock, 13); 34 | expect(mock.refresh).toHaveBeenCalled(); 35 | }); 36 | 37 | it("should not call refresh when a parameter is not passed", function () { 38 | underTest.call(mock); 39 | expect(mock.refresh).not.toHaveBeenCalled(); 40 | }); 41 | 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /test/spec/external/footerRows.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/footerRows'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("footerRows", function () { 5 | 6 | var underTest = Scrollgrid.prototype.footerRows; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 7, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.virtBottom); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.virtualBottom).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /test/spec/external/formatRules.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/formatRules'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("formatRules", function () { 5 | 6 | var underTest = Scrollgrid.prototype.formatRules; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, [], true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(['A', 'B', 'C']); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, ['X', 'Y', 'Z']); 24 | expect(mock.properties.formatRules).toEqual(['X', 'Y', 'Z']); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, ['X', 'Y', 'Z'])).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, ['X', 'Y', 'Z']); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/external/headerColumns.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/headerColumns'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("headerColumns", function () { 5 | 6 | var underTest = Scrollgrid.prototype.headerColumns; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 3, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.virtLeft); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.virtualLeft).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/external/headerRowHeight.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/headerRowHeight'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("headerRowHeight", function () { 5 | 6 | var underTest = Scrollgrid.prototype.headerRowHeight; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 17, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.headerRowHeight); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.headerRowHeight).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/external/headerRows.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/headerRows'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("headerRows", function () { 5 | 6 | var underTest = Scrollgrid.prototype.headerRows; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 3, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.virtTop); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.virtualTop).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/external/on.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/on'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("on", function () { 5 | 6 | var underTest = Scrollgrid.prototype.on; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should append to internal eventHandlers array", function () { 14 | var type = "click", 15 | listener = function () { 16 | return "clicked"; 17 | }, 18 | capture = false; 19 | 20 | underTest.call(mock, type, listener, capture); 21 | expect(mock.eventHandlers[0].type).toEqual(type); 22 | }); 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/spec/external/refresh.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/refresh'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("data", function () { 5 | 6 | var underTest = Scrollgrid.prototype.refresh; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should call layoutDOM", function () { 14 | underTest.call(mock); 15 | expect(mock.internal.dom.layoutDOM).toHaveBeenCalled(); 16 | }); 17 | 18 | it("should call draw with no clear cache if maintain cache is passed true", function () { 19 | underTest.call(mock, true); 20 | expect(mock.internal.render.draw).toHaveBeenCalledWith(false, true); 21 | }); 22 | 23 | it("should call draw with clear cache if maintain cache is passed false", function () { 24 | underTest.call(mock, false); 25 | expect(mock.internal.render.draw).toHaveBeenCalledWith(true, true); 26 | }); 27 | 28 | it("should call setScrollerSize", function () { 29 | underTest.call(mock); 30 | expect(mock.internal.dom.setScrollerSize).toHaveBeenCalled(); 31 | }); 32 | 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /test/spec/external/rowHeight.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/rowHeight'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("rowHeight", function () { 5 | 6 | var underTest = Scrollgrid.prototype.rowHeight; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 23, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the column resizing value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.physRowHeight); 20 | }); 21 | 22 | it("should update the column resizing value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.rowHeight).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/external/sortIconSize.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'external/sortIconSize'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("sortIconSize", function () { 5 | 6 | var underTest = Scrollgrid.prototype.sortIconSize; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should not refresh if true is passed to the 'silent' argument", function () { 14 | underTest.call(mock, 3, true); 15 | expect(mock.refresh).not.toHaveBeenCalled(); 16 | }); 17 | 18 | it("should return the private value if no parameter is passed", function () { 19 | expect(underTest.call(mock)).toEqual(mock.vals.sortIconSize); 20 | }); 21 | 22 | it("should update the private value to a passed parameter", function () { 23 | underTest.call(mock, 13); 24 | expect(mock.properties.sortIconSize).toEqual(13); 25 | }); 26 | 27 | it("should return the context when a parameter is passed", function () { 28 | expect(underTest.call(mock, 13)).toEqual(mock); 29 | }); 30 | 31 | it("should call refresh when a parameter is passed", function () { 32 | underTest.call(mock, 13); 33 | expect(mock.refresh).toHaveBeenCalled(); 34 | }); 35 | 36 | it("should not call refresh when a parameter is not passed", function () { 37 | underTest.call(mock); 38 | expect(mock.refresh).not.toHaveBeenCalled(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/internal/dom/getTopMargin.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'dom/getTopMargin'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getTopMargin", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.dom.getTopMargin, 7 | props, 8 | result; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | props = mock.properties; 13 | }); 14 | 15 | it("should return 0 if containerSize is not passed", function () { 16 | result = underTest.call(mock); 17 | expect(result).toEqual(0); 18 | }); 19 | 20 | it("should return 0 if containerSize doesn't have a height", function () { 21 | result = underTest.call(mock, {}); 22 | expect(result).toEqual(0); 23 | }); 24 | 25 | it("should return 0 if parent is not passed", function () { 26 | result = underTest.call(mock, { height: mock.vals.containerHeight }); 27 | expect(result).toEqual(0); 28 | }); 29 | 30 | it("should default to 0 if alignment is not set", function () { 31 | props.verticalAlignment = undefined; 32 | result = underTest.call(mock, { height: mock.vals.containerHeight }, new d3.shape(mock.vals)); 33 | expect(result).toEqual(0); 34 | }); 35 | 36 | it("should return 0 if alignment is top", function () { 37 | props.verticalAlignment = "top"; 38 | result = underTest.call(mock, { height: mock.vals.containerHeight }, new d3.shape(mock.vals)); 39 | expect(result).toEqual(0); 40 | }); 41 | 42 | it("should return half parent height minus half container height if alignment is middle", function () { 43 | props.verticalAlignment = "middle"; 44 | result = underTest.call(mock, { height: mock.vals.containerHeight }, new d3.shape(mock.vals)); 45 | expect(result).toEqual(mock.vals.nodeOffsetHeight / 2 - mock.vals.containerHeight / 2); 46 | }); 47 | 48 | it("should return parent height minus container height with 1 pixel margin if alignment is bottom", function () { 49 | props.verticalAlignment = "bottom"; 50 | result = underTest.call(mock, { height: mock.vals.containerHeight }, new d3.shape(mock.vals)); 51 | expect(result).toEqual(mock.vals.nodeOffsetHeight - mock.vals.containerHeight - 1); 52 | }); 53 | 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/spec/internal/dom/populateDOM.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'dom/populateDOM'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("populateDOM", function () { 5 | 6 | var underTest, 7 | elems, 8 | int; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | 14 | underTest = Scrollgrid.prototype.internal.dom.populateDOM; 15 | int = mock.internal; 16 | elems = mock.elements; 17 | }); 18 | 19 | it("should select the target and set as parent", function () { 20 | underTest.call(mock); 21 | expect(d3.select).toHaveBeenCalledWith(mock.vals.target); 22 | expect(elems.parent).toBe(d3.returnValues.select); 23 | }); 24 | 25 | it("should add a div to the parent and store it in container", function () { 26 | underTest.call(mock); 27 | expect(elems.parent.children.div[0]).toBeDefined(); 28 | expect(elems.container).toEqual(elems.parent.children.div[0]); 29 | }); 30 | 31 | it("should add a div to the container and store it in main viewport", function () { 32 | underTest.call(mock); 33 | expect(elems.container.children.div[0]).toBeDefined(); 34 | expect(elems.main.viewport).toEqual(elems.container.children.div[0]); 35 | }); 36 | 37 | it("should add a div to the viewport and store it in scroller", function () { 38 | underTest.call(mock); 39 | expect(elems.main.viewport.children.div[0]).toBeDefined(); 40 | expect(elems.main.scroller).toEqual(elems.main.viewport.children.div[0]); 41 | }); 42 | 43 | it("should use populate panel for each panel in the dom", function () { 44 | underTest.call(mock); 45 | expect(int.dom.populatePanel).toHaveBeenCalled(); 46 | expect(int.dom.populatePanel.calls.count()).toEqual(9); 47 | }); 48 | 49 | it("should populate the top left panel and store in the dom", function () { 50 | underTest.call(mock); 51 | expect(elems.top.left).toBeDefined(); 52 | }); 53 | 54 | it("should populate the top panel and store in the dom", function () { 55 | underTest.call(mock); 56 | expect(elems.top).toBeDefined(); 57 | }); 58 | 59 | it("should populate the top right panel and store in the dom", function () { 60 | underTest.call(mock); 61 | expect(elems.top.right).toBeDefined(); 62 | }); 63 | 64 | it("should populate the left panel and store in the dom", function () { 65 | underTest.call(mock); 66 | expect(elems.left).toBeDefined(); 67 | }); 68 | 69 | it("should populate the main panel and store in the dom", function () { 70 | underTest.call(mock); 71 | expect(elems.main).toBeDefined(); 72 | }); 73 | 74 | it("should populate the right panel and store in the dom", function () { 75 | underTest.call(mock); 76 | expect(elems.right).toBeDefined(); 77 | }); 78 | 79 | it("should populate the bottom left panel and store in the dom", function () { 80 | underTest.call(mock); 81 | expect(elems.bottom.left).toBeDefined(); 82 | }); 83 | 84 | it("should populate the bottom panel and store in the dom", function () { 85 | underTest.call(mock); 86 | expect(elems.bottom).toBeDefined(); 87 | }); 88 | 89 | it("should populate the bottom right panel and store in the dom", function () { 90 | underTest.call(mock); 91 | expect(elems.bottom.right).toBeDefined(); 92 | }); 93 | }); 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /test/spec/internal/dom/populatePanel.spec.js: -------------------------------------------------------------------------------- 1 | define(['mock', 'dom/populatePanel'], function (mock) { 2 | "use strict"; 3 | 4 | describe("populatePanel", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.dom.populatePanel, 7 | elems; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | elems = mock.elements; 12 | }); 13 | 14 | it("should add an svg to the container and store it in svg", function () { 15 | var panel = underTest.call(mock); 16 | expect(elems.container.children.svg[0]).toBeDefined(); 17 | expect(panel.svg).toEqual(elems.container.children.svg[0]); 18 | }); 19 | 20 | it("should add a group to the panel svg and store it as transform", function () { 21 | var panel = underTest.call(mock); 22 | expect(panel.svg.children.g[0]).toBeDefined(); 23 | expect(panel.transform).toEqual(panel.svg.children.g[0]); 24 | }); 25 | 26 | it("should add a group to the transform group and store it as content", function () { 27 | var panel = underTest.call(mock); 28 | expect(panel.transform.children.g[0]).toBeDefined(); 29 | expect(panel.content).toEqual(panel.transform.children.g[0]); 30 | }); 31 | 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/spec/internal/dom/setAbsolutePosition.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'dom/setAbsolutePosition'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("setAbsolutePosition", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.dom.setAbsolutePosition, 7 | inShape, 8 | outShape, 9 | vals = { 10 | x: 1, 11 | y: 2, 12 | width: 3, 13 | height: 4 14 | }; 15 | 16 | beforeEach(function () { 17 | mock.init(); 18 | inShape = new d3.shape(mock.vals); 19 | outShape = underTest.call(mock, inShape, vals.x, vals.y, vals.width, vals.height); 20 | }); 21 | 22 | it("should return the passed object for chaining", function () { 23 | expect(outShape).toEqual(inShape); 24 | }); 25 | 26 | it("should set position to absolute", function () { 27 | expect(outShape.styles.position).toEqual("absolute"); 28 | }); 29 | 30 | it("should set overflow to hidden", function () { 31 | expect(outShape.styles.overflow).toEqual("hidden"); 32 | }); 33 | 34 | it("should set left to x value and add px for cross-browser compatibility", function () { 35 | expect(outShape.styles.left).toEqual(vals.x + "px"); 36 | }); 37 | 38 | it("should set top to y value and add px for cross-browser compatibility", function () { 39 | expect(outShape.styles.top).toEqual(vals.y + "px"); 40 | }); 41 | 42 | it("should set width to width value and add px for cross-browser compatibility", function () { 43 | expect(outShape.styles.width).toEqual(vals.width + "px"); 44 | }); 45 | 46 | it("should set height to height value and add px for cross-browser compatibility", function () { 47 | expect(outShape.styles.height).toEqual(vals.height + "px"); 48 | }); 49 | 50 | }); 51 | }); -------------------------------------------------------------------------------- /test/spec/internal/dom/setAutoResize.spec.js: -------------------------------------------------------------------------------- 1 | define(['mock', 'dom/setAutoResize'], function (mock) { 2 | "use strict"; 3 | 4 | describe("setAutoResize", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.dom.setAutoResize; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | window.onresize = null; 11 | }); 12 | 13 | it("should add a function to the onresize of the window", function () { 14 | expect(window.onresize).toBeNull(); 15 | underTest.call(mock); 16 | expect(window.onresize).toBeDefined(); 17 | }); 18 | 19 | it("should invoke the refresh method on resize", function () { 20 | underTest.call(mock); 21 | window.onresize(); 22 | expect(mock.refresh).toHaveBeenCalled(); 23 | }); 24 | 25 | it("should invoke the existing resize method as well if one exists", function () { 26 | var existingHandler = jasmine.createSpy("existing resize handler"); 27 | window.onresize = existingHandler; 28 | underTest.call(mock); 29 | window.onresize(); 30 | expect(existingHandler).toHaveBeenCalled(); 31 | expect(mock.refresh).toHaveBeenCalled(); 32 | }); 33 | 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /test/spec/internal/dom/setRelativePosition.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'dom/setRelativePosition'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("setRelativePosition", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.dom.setRelativePosition, 7 | inShape, 8 | outShape, 9 | vals = { 10 | x: 1, 11 | width: 2, 12 | height: 3, 13 | overflow: "test overflow setting" 14 | }; 15 | 16 | beforeEach(function () { 17 | mock.init(); 18 | inShape = new d3.shape(mock.vals); 19 | outShape = underTest.call(mock, inShape, vals.x, vals.width, vals.height, vals.overflow); 20 | }); 21 | 22 | it("should return the passed object for chaining", function () { 23 | expect(outShape).toEqual(inShape); 24 | }); 25 | 26 | it("should set position to relative", function () { 27 | expect(outShape.styles.position).toEqual("relative"); 28 | }); 29 | 30 | it("should set overflow to passed value", function () { 31 | expect(outShape.styles.overflow).toEqual(vals.overflow); 32 | }); 33 | 34 | it("should set left margin to x value and add px for cross-browser compatibility", function () { 35 | expect(outShape.styles["margin-left"]).toEqual(vals.x + "px"); 36 | }); 37 | 38 | it("should set width to width value and add px for cross-browser compatibility", function () { 39 | expect(outShape.styles.width).toEqual(vals.width + "px"); 40 | }); 41 | 42 | it("should set height to height value and add px for cross-browser compatibility", function () { 43 | expect(outShape.styles.height).toEqual(vals.height + "px"); 44 | }); 45 | 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/spec/internal/dom/setScrollerSize.spec.js: -------------------------------------------------------------------------------- 1 | define(['mock', 'dom/setScrollerSize'], function (mock) { 2 | "use strict"; 3 | 4 | describe("setScrollerSize", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.dom.setScrollerSize, 7 | elems; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | elems = mock.elements; 12 | }); 13 | 14 | it("should set width to total inner width and add px for cross-browser compatibility", function () { 15 | underTest.call(mock); 16 | expect(elems.main.scroller.styles.width).toEqual(mock.vals.totalInnerWidth + "px"); 17 | }); 18 | 19 | it("should set height to total inner height and add px for cross-browser compatibility", function () { 20 | underTest.call(mock); 21 | expect(elems.main.scroller.styles.height).toEqual(mock.vals.totalInnerHeight + "px"); 22 | }); 23 | 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/spec/internal/events/addEventHandlers.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'events/addEventHandlers'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("addDataAttrs", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.events.addEventHandlers, 7 | target, 8 | data; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | target = new d3.shape(mock.vals); 14 | data = { 15 | rowIndex: "Row Index", 16 | columnIndex: "Column Index" 17 | }; 18 | underTest.call(mock, target, data); 19 | }); 20 | 21 | it("should append data-row attribute to the target", function () { 22 | expect(target.attributes['data-row']).toEqual(data.rowIndex); 23 | }); 24 | 25 | it("should append data-col attribute to the target", function () { 26 | expect(target.attributes['data-col']).toEqual(data.columnIndex); 27 | }); 28 | 29 | it("should add event handlers to the target", function () { 30 | expect(target.on('click')).toBeDefined(); 31 | }); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/spec/internal/interaction/addSortButtons.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'interaction/addSortButtons'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("addSortButtons", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.addSortButtons, 7 | target, 8 | viewData; 9 | 10 | beforeEach(function () { 11 | d3.init(); 12 | mock.init(); 13 | target = new d3.shape(mock.vals); 14 | viewData = { 15 | boxWidth: 7, 16 | boxHeight: 9, 17 | columnIndex: 11 18 | }; 19 | underTest.call(mock, target, viewData); 20 | }); 21 | 22 | it("should append a rectangle to the target panel", function () { 23 | expect(target.children.rect).toBeDefined(); 24 | expect(target.children.rect.length).toEqual(1); 25 | }); 26 | 27 | it("should set the width using the boxWidth of the viewData", function () { 28 | expect(target.children.rect[0].attributes.width).toEqual(7); 29 | }); 30 | 31 | it("should set the height using the boxHeight of the viewData", function () { 32 | expect(target.children.rect[0].attributes.height).toEqual(9); 33 | }); 34 | 35 | it("should set the opacity to 0 as this is only for handling the click", function () { 36 | expect(target.children.rect[0].styles.opacity).toEqual(0); 37 | }); 38 | 39 | it("should set the opacity to 0 as this is only for handling the click", function () { 40 | expect(target.children.rect[0].styles.cursor).toEqual("pointer"); 41 | }); 42 | 43 | it("should sort the column on click", function () { 44 | expect(target.children.rect[0].eventHandlers.click).toBeDefined(); 45 | target.children.rect[0].eventHandlers.click(); 46 | expect(mock.internal.interaction.sortColumn).toHaveBeenCalledWith(11, true); 47 | }); 48 | 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /test/spec/internal/interaction/columnResizeEnd.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'interaction/columnResizeEnd'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("columnResizeEnd", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.columnResizeEnd, 7 | handle; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | handle = new d3.shape(mock.vals); 12 | }); 13 | 14 | it("should remove the 'dragging' class", function () { 15 | underTest.call(mock, handle); 16 | expect(handle.classed).toHaveBeenCalledWith("dragging", false); 17 | }); 18 | 19 | }); 20 | 21 | }); -------------------------------------------------------------------------------- /test/spec/internal/interaction/columnResizeStart.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'interaction/columnResizeStart'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("columnResizeStart", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.columnResizeStart, 7 | handle; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | handle = new d3.shape(mock.vals); 12 | }); 13 | 14 | it("should add the 'dragging' class", function () { 15 | underTest.call(mock, handle); 16 | expect(handle.classed).toHaveBeenCalledWith("dragging", true); 17 | }); 18 | 19 | it("should stop event propagation", function () { 20 | underTest.call(mock, handle); 21 | expect(d3.event.sourceEvent.stopPropagation).toHaveBeenCalled(); 22 | }); 23 | 24 | }); 25 | 26 | }); -------------------------------------------------------------------------------- /test/spec/internal/interaction/columnResizing.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'interaction/columnResizing'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("columnResizing", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.columnResizing, 7 | handle, 8 | column, 9 | vals; 10 | 11 | beforeEach(function () { 12 | mock.init(); 13 | d3.init(); 14 | handle = new d3.shape(mock.vals); 15 | vals = { 16 | eventX: 7, 17 | colWidth: 13, 18 | dataX: 17 19 | }; 20 | column = { 21 | x: vals.dataX, 22 | width: vals.colWidth 23 | }; 24 | d3.event.x = vals.eventX; 25 | }); 26 | 27 | it("should subtract the difference between the event x and datum x", function () { 28 | underTest.call(mock, handle, column); 29 | expect(column.width).toEqual(vals.colWidth - (vals.dataX - vals.eventX)); 30 | }); 31 | 32 | it("if the column width is less than zero it should be set to zero", function () { 33 | // As proven in the test above width normally equals width - (dataX - eventX) which in this case 34 | // will be 13 - (999 - 7) = -986 but we expect this to be set to zero 35 | column.x = 999; 36 | underTest.call(mock, handle, column); 37 | expect(column.width).toEqual(0); 38 | }); 39 | 40 | it("should set the x value of the datum to the event x", function () { 41 | underTest.call(mock, handle, column); 42 | expect(column.x).toEqual(vals.eventX); 43 | }); 44 | 45 | it("should update the handle x position to the new datum x value", function () { 46 | underTest.call(mock, handle, column); 47 | expect(handle.attributes.x).toBeDefined(); 48 | expect(handle.attributes.x).toEqual(column.x); 49 | }); 50 | 51 | it("should call the refresh method", function () { 52 | underTest.call(mock, handle, column); 53 | expect(mock.refresh).toHaveBeenCalled(); 54 | }); 55 | 56 | }); 57 | 58 | }); -------------------------------------------------------------------------------- /test/spec/internal/interaction/defaultComparer.spec.js: -------------------------------------------------------------------------------- 1 | define(['interaction/defaultComparer'], function () { 2 | "use strict"; 3 | 4 | describe("defaultComparer", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.defaultComparer; 7 | 8 | it("should return zero if two matching dates are passed", function () { 9 | var result = underTest(new Date("2012-06-01"), new Date("2012-06-01")); 10 | expect(result).toEqual(0); 11 | }); 12 | 13 | it("should return a negative number if 'a' is a date earlier than 'b'", function () { 14 | var result = underTest(new Date("2012-05-01"), new Date("2012-06-01")); 15 | expect(result).toBeLessThan(0); 16 | }); 17 | 18 | it("should return a positive number if 'a' is a date later than 'b'", function () { 19 | var result = underTest(new Date("2012-06-01"), new Date("2012-05-01")); 20 | expect(result).toBeGreaterThan(0); 21 | }); 22 | 23 | it("should return zero if two matching date strings are passed", function () { 24 | var result = underTest("2012-06-01", "2012-06-01"); 25 | expect(result).toEqual(0); 26 | }); 27 | 28 | it("should return a negative number if 'a' is a date string earlier than 'b'", function () { 29 | var result = underTest("2012-05-01", "2012-06-01"); 30 | expect(result).toBeLessThan(0); 31 | }); 32 | 33 | it("should return a positive number if 'a' is a date string later than 'b'", function () { 34 | var result = underTest("2012-06-01", "2012-05-01"); 35 | expect(result).toBeGreaterThan(0); 36 | }); 37 | 38 | it("should return zero if two matching numbers are passed", function () { 39 | var result = underTest(123.456, 123.456); 40 | expect(result).toEqual(0); 41 | }); 42 | 43 | it("should return a negative number if 'a' is a number less than 'b'", function () { 44 | var result = underTest(-34.56, 1.234); 45 | expect(result).toBeLessThan(0); 46 | }); 47 | 48 | it("should return a positive number if 'a' is a number greater than 'b'", function () { 49 | var result = underTest(1.234, -34.56); 50 | expect(result).toBeGreaterThan(0); 51 | }); 52 | 53 | it("should return zero if two matching number strings are passed", function () { 54 | var result = underTest("123.456", "123.456"); 55 | expect(result).toEqual(0); 56 | }); 57 | 58 | it("should return a negative number if 'a' is a number string less than 'b'", function () { 59 | var result = underTest("-34.56", "1.234"); 60 | expect(result).toBeLessThan(0); 61 | }); 62 | 63 | it("should return a positive number if 'a' is a number string greater than 'b'", function () { 64 | var result = underTest("1.234", "-34.56"); 65 | expect(result).toBeGreaterThan(0); 66 | }); 67 | 68 | it("should return zero if two matching strings are passed", function () { 69 | var result = underTest("Apples", "Apples"); 70 | expect(result).toEqual(0); 71 | }); 72 | 73 | it("should return a negative number if 'a' is a string alphabetically less than 'b'", function () { 74 | var result = underTest("Apples", "Bananas"); 75 | expect(result).toBeLessThan(0); 76 | }); 77 | 78 | it("should return a positive number if 'a' is a string alphabetically greater than 'b'", function () { 79 | var result = underTest("Bananas", "Apples"); 80 | expect(result).toBeGreaterThan(0); 81 | }); 82 | 83 | }); 84 | 85 | }); -------------------------------------------------------------------------------- /test/spec/internal/interaction/getColumnResizer.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'interaction/getColumnResizer'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getColumnResizer", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.getColumnResizer, 7 | interaction; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | interaction = mock.internal.interaction; 13 | }); 14 | 15 | it("should call the drag method of the d3 behaviour object", function () { 16 | underTest.call(mock); 17 | expect(d3.behavior.drag).toHaveBeenCalled(); 18 | }); 19 | 20 | it("should return a drag object", function () { 21 | var result = underTest.call(mock); 22 | expect(result).toBeDefined(); 23 | }); 24 | 25 | it("should invoke origin on the drag instance returned", function () { 26 | var result = underTest.call(mock); 27 | expect(result.origin).toHaveBeenCalled(); 28 | }); 29 | 30 | it("should pass the datum back to the origin property", function () { 31 | var result = underTest.call(mock), 32 | fn = result.origin.calls.argsFor(0)[0]; 33 | expect(fn("passed in value")).toEqual("passed in value"); 34 | }); 35 | 36 | it("should call the resize start function on drag start", function () { 37 | var result = underTest.call(mock); 38 | expect(result.eventHandlers.dragstart).toBeDefined(); 39 | expect(interaction.columnResizeStart).not.toHaveBeenCalled(); 40 | result.eventHandlers.dragstart('datum value'); 41 | expect(d3.select).toHaveBeenCalled(); 42 | expect(interaction.columnResizeStart).toHaveBeenCalledWith(d3.returnValues.select); 43 | }); 44 | 45 | it("should call the resizing function on drag", function () { 46 | var result = underTest.call(mock, 'invert value'); 47 | expect(result.eventHandlers.drag).toBeDefined(); 48 | expect(interaction.columnResizing).not.toHaveBeenCalled(); 49 | result.eventHandlers.drag('datum value'); 50 | expect(d3.select).toHaveBeenCalled(); 51 | expect(interaction.columnResizing).toHaveBeenCalledWith(d3.returnValues.select, 'datum value'); 52 | }); 53 | 54 | it("should call the resize end function on drag end", function () { 55 | var result = underTest.call(mock); 56 | expect(result.eventHandlers.dragend).toBeDefined(); 57 | expect(interaction.columnResizeEnd).not.toHaveBeenCalled(); 58 | result.eventHandlers.dragend('datum value'); 59 | expect(d3.select).toHaveBeenCalled(); 60 | expect(interaction.columnResizeEnd).toHaveBeenCalledWith(d3.returnValues.select); 61 | }); 62 | 63 | }); 64 | 65 | }); -------------------------------------------------------------------------------- /test/spec/internal/interaction/sortColumn.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'interaction/sortColumn'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("sortColumn", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.interaction.sortColumn; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should remove the sort property from every column except the passed index", function () { 14 | mock.columns[0].sort = 'asc'; 15 | mock.columns[1].sort = 'asc'; 16 | mock.columns[2].sort = 'asc'; 17 | underTest.call(mock, 1); 18 | expect(mock.columns[0].sort).toBeUndefined(); 19 | expect(mock.columns[1].sort).toBeDefined(); 20 | expect(mock.columns[2].sort).toBeUndefined(); 21 | }); 22 | 23 | it("if toggle is false the sort value should remain", function () { 24 | mock.columns[1].sort = 'asc'; 25 | underTest.call(mock, 1, false); 26 | expect(mock.columns[1].sort).toEqual('asc'); 27 | }); 28 | 29 | it("if toggle is true and the sort value was asc it should be changed to desc", function () { 30 | mock.columns[1].sort = 'asc'; 31 | underTest.call(mock, 1, true); 32 | expect(mock.columns[1].sort).toEqual('desc'); 33 | }); 34 | 35 | it("if toggle is true and the sort value was desc it should be changed to asc", function () { 36 | mock.columns[1].sort = 'desc'; 37 | underTest.call(mock, 1, true); 38 | expect(mock.columns[1].sort).toEqual('asc'); 39 | }); 40 | 41 | it("it should call the sort method of the adapter ascending", function () { 42 | mock.columns[1] = { sort: 'asc', compareFunction: 'comparer' }; 43 | underTest.call(mock, 1, false); 44 | expect(mock.adapter.sort).toHaveBeenCalledWith(1, mock.vals.virtTop, mock.vals.virtBottom, false, 'comparer'); 45 | }); 46 | 47 | it("it should call the sort method of the adapter descending", function () { 48 | mock.columns[1] = { sort: 'desc', compareFunction: 'comparer' }; 49 | underTest.call(mock, 1, false); 50 | expect(mock.adapter.sort).toHaveBeenCalledWith(1, mock.vals.virtTop, mock.vals.virtBottom, true, 'comparer'); 51 | }); 52 | 53 | it("it should fall back to the default comparer if none is specified", function () { 54 | mock.columns[1] = { sort: 'desc' }; 55 | underTest.call(mock, 1, false); 56 | expect(mock.adapter.sort).toHaveBeenCalledWith(1, mock.vals.virtTop, mock.vals.virtBottom, true, mock.internal.interaction.defaultComparer); 57 | }); 58 | 59 | }); 60 | 61 | }); -------------------------------------------------------------------------------- /test/spec/internal/raise.spec.js: -------------------------------------------------------------------------------- 1 | define(['mock', 'internal/raise'], function (mock) { 2 | "use strict"; 3 | 4 | describe("raise", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.raise; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | }); 11 | 12 | it("should use the reporter on the context if provided", function () { 13 | underTest.call(mock, "Example Message"); 14 | expect(mock.reporter.error).toHaveBeenCalledWith("Example Message"); 15 | }); 16 | 17 | it("should use the console if no reporter is provided", function () { 18 | var errSpy = spyOn(console, 'error'); 19 | delete mock.reporter; 20 | underTest.call(mock, "Example Message"); 21 | expect(errSpy).toHaveBeenCalledWith("Example Message"); 22 | }); 23 | 24 | it("should throw an exception if console is not supported in the current environment", function () { 25 | expect(function () { 26 | delete mock.reporter.error; 27 | underTest.call(mock, "Example Message"); 28 | }).toThrow("Example Message"); 29 | }); 30 | 31 | }); 32 | 33 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/calculateCellAdjustments.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/calculateCellAdjustments'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("calculateCellAdjustments", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.calculateCellAdjustments, 7 | props; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | props = mock.properties; 13 | // Describe a 10x10 grid with a single row and column header or footer 14 | props.virtualOuterHeight = 10; 15 | props.virtualOuterWidth = 10; 16 | props.virtualTop = 1; 17 | props.virtualLeft = 1; 18 | props.virtualRight = 1; 19 | props.virtualBottom = 1; 20 | }); 21 | 22 | it("should not apply cell adjustments to any non special cells", function () { 23 | var result = underTest.call(mock, 5, 5); 24 | expect(result.x).toEqual(0); 25 | expect(result.y).toEqual(0); 26 | expect(result.boxHeight).toEqual(0); 27 | expect(result.boxWidth).toEqual(0); 28 | expect(result.textHeight).toEqual(0); 29 | expect(result.textWidth).toEqual(0); 30 | }); 31 | 32 | it("should add scrollbar width to the last column header before the row footer", function () { 33 | var result = underTest.call(mock, 0, 8); 34 | expect(result.boxWidth).toEqual(mock.vals.verticalScrollbarWidth); 35 | }); 36 | 37 | it("should add scrollbar width to the last column footer before the row footer", function () { 38 | var result = underTest.call(mock, 9, 8); 39 | expect(result.boxWidth).toEqual(mock.vals.verticalScrollbarWidth); 40 | }); 41 | 42 | it("should add scrollbar height to the last row header before the column footer", function () { 43 | var result = underTest.call(mock, 8, 0); 44 | expect(result.boxHeight).toEqual(mock.vals.horizontalScrollbarHeight); 45 | }); 46 | 47 | it("should add scrollbar height to the last row footer before the column footer", function () { 48 | var result = underTest.call(mock, 8, 9); 49 | expect(result.boxHeight).toEqual(mock.vals.horizontalScrollbarHeight); 50 | }); 51 | 52 | it("should decrease the row height for the last header row so that its line is visible above the fixed area", function () { 53 | var result = underTest.call(mock, 0, 5); 54 | expect(result.boxHeight).toEqual(-1); 55 | }); 56 | 57 | it("should decrease the column width for the last header column so that its line is visible before the fixed area", function () { 58 | var result = underTest.call(mock, 5, 0); 59 | expect(result.boxWidth).toEqual(-1); 60 | }); 61 | 62 | it("should decrease the column width for the last column so that its line is visible", function () { 63 | var result = underTest.call(mock, 5, 9); 64 | expect(result.boxWidth).toEqual(-1); 65 | }); 66 | 67 | it("should shift the first row after the header so that the top line is not visible (because the header line is already visible", function () { 68 | var result = underTest.call(mock, 1, 5); 69 | expect(result.boxHeight).toEqual(1); 70 | expect(result.y).toEqual(-1); 71 | }); 72 | 73 | it("should shift the first column after the header so that the left line is not visible (because the header line is already visible", function () { 74 | var result = underTest.call(mock, 5, 1); 75 | expect(result.boxWidth).toEqual(1); 76 | expect(result.x).toEqual(-1); 77 | }); 78 | 79 | it("should add the sort icon for the last column header if one is defined", function () { 80 | mock.columns[5] = { sort: "Mock Sort Icon" }; 81 | var result = underTest.call(mock, 0, 5); 82 | expect(result.sortIcon).toEqual("Mock Sort Icon"); 83 | }); 84 | 85 | it("should not add the sort icon for the column if one is defined but cell is not last header", function () { 86 | mock.columns[5] = { sort: "Mock Sort Icon" }; 87 | var result = underTest.call(mock, 5, 5); 88 | expect(result.sortIcon).not.toBeDefined(); 89 | }); 90 | }); 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /test/spec/internal/render/cropText.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/cropText'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("cropText", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.cropText, 7 | textShape, 8 | exampleText; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | textShape = new d3.shape(mock.vals); 14 | exampleText = "Example Text Value"; 15 | textShape.text(exampleText); 16 | }); 17 | 18 | it("should store the original text in the datum of the shape", function () { 19 | underTest.call(mock, textShape, 100); 20 | expect(textShape.dataPoint.originalText).toEqual(exampleText); 21 | }); 22 | 23 | it("should not crop the text if the size exceeds the width of the text", function () { 24 | textShape.bounds.width = 99; 25 | underTest.call(mock, textShape, 100); 26 | expect(textShape.text()).toEqual(exampleText); 27 | }); 28 | 29 | it("should crop the text if the size is smaller than the width of the text", function () { 30 | textShape.characterWidth = 10; 31 | underTest.call(mock, textShape, 100); 32 | expect(textShape.text()).toEqual(exampleText.substring(0, 10)); 33 | }); 34 | 35 | it("should return an empty string for zero width", function () { 36 | underTest.call(mock, textShape, 0); 37 | expect(textShape.text()).toEqual(""); 38 | }); 39 | 40 | it("should return an empty string for negative width", function () { 41 | underTest.call(mock, textShape, -100); 42 | expect(textShape.text()).toEqual(""); 43 | }); 44 | 45 | it("should return an empty string if no characters fit", function () { 46 | textShape.characterWidth = 101; 47 | underTest.call(mock, textShape, 100); 48 | expect(textShape.text()).toEqual(""); 49 | }); 50 | 51 | }); 52 | 53 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/getClipPath.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/getClipPath'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getClipPath", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.getClipPath, 7 | viewData; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | viewData = { 13 | cellPadding: 31, 14 | textHeight: 230, 15 | textWidth: 270 16 | }; 17 | }); 18 | 19 | it("Should account for sort icon size if one is present in the view data", function () { 20 | viewData.sortIcon = "Icon Specified"; 21 | expect(underTest.call(mock, viewData)).toEqual("polygon(0px 0px, 15px 0px, 15px 199px, 0px 199px)"); 22 | }); 23 | 24 | it("Should not account for sort icon size if set to none in the view data", function () { 25 | viewData.sortIcon = "none"; 26 | expect(underTest.call(mock, viewData)).toEqual("polygon(0px 0px, 239px 0px, 239px 199px, 0px 199px)"); 27 | }); 28 | 29 | it("Should not account for sort icon size if set to null in the view data", function () { 30 | viewData.sortIcon = null; 31 | expect(underTest.call(mock, viewData)).toEqual("polygon(0px 0px, 239px 0px, 239px 199px, 0px 199px)"); 32 | }); 33 | 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /test/spec/internal/render/getDataBounds.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/getDataBounds'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getDataBounds", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.getDataBounds, 7 | props; 8 | 9 | beforeEach(function () { 10 | var i; 11 | mock.init(); 12 | d3.init(); 13 | props = mock.properties; 14 | // Define a 100x200px physical grid for testing 15 | props.physicalTotalInnerWidth = 100; 16 | props.physicalTotalInnerHeight = 200; 17 | props.rowHeight = 10; 18 | // Define a 10 column by 20 row virtual grid for testing 19 | props.virtualInnerWidth = 10; 20 | props.virtualInnerHeight = 20; 21 | props.virtualLeft = 1; 22 | props.virtualRight = 1; 23 | props.virtualTop = 1; 24 | props.virtualBottom = 1; 25 | mock.columns = []; 26 | for (i = 0; i < 12; i += 1) { 27 | mock.columns.push({ width: 10 }); 28 | } 29 | }); 30 | 31 | it("should select the first visible row from the set", function () { 32 | // Call the test method with a visible range 33 | var result = underTest.call(mock, { top: 45 }); 34 | expect(result.virtual.top).toEqual(3); 35 | expect(result.physical.y).toEqual(3 * 10 - 45); 36 | }); 37 | 38 | it("should not be able to select a negative row", function () { 39 | // Call the test method with a visible range 40 | var result = underTest.call(mock, { top: -40 }); 41 | expect(result.virtual.top).toEqual(0); 42 | }); 43 | 44 | it("should select the last visible row from the set", function () { 45 | // Call the test method with a visible range 46 | var result = underTest.call(mock, { bottom: 175 }); 47 | expect(result.virtual.bottom).toEqual(19); 48 | }); 49 | 50 | it("should not be able to select a row outside the bounds", function () { 51 | // Call the test method with a visible range 52 | var result = underTest.call(mock, { bottom: 300 }); 53 | expect(result.virtual.bottom).toEqual(20); 54 | }); 55 | 56 | it("should select the first visible column from the set", function () { 57 | // Call the test method with a visible range 58 | var result = underTest.call(mock, { left: 45 }); 59 | expect(result.virtual.left).toEqual(4); 60 | expect(result.physical.x).toEqual(4 * 10 - 45); 61 | }); 62 | 63 | it("should not be able to select a negative column", function () { 64 | // Call the test method with a visible range 65 | var result = underTest.call(mock, { left: -40 }); 66 | expect(result.virtual.left).toEqual(0); 67 | }); 68 | 69 | it("should select the last visible row from the set", function () { 70 | // Call the test method with a visible range 71 | var result = underTest.call(mock, { right: 75 }); 72 | expect(result.virtual.right).toEqual(9); 73 | }); 74 | 75 | it("should not be able to select a row outside the bounds", function () { 76 | // Call the test method with a visible range 77 | var result = underTest.call(mock, { right: 300 }); 78 | expect(result.virtual.right).toEqual(10); 79 | }); 80 | 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/spec/internal/render/getTextAnchor.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/getTextAnchor'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getTextAnchor", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.getTextAnchor, 7 | result; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | }); 13 | 14 | it("should return start for left alignment", function () { 15 | result = underTest.call(mock, { alignment: "left" }); 16 | expect(result).toEqual("start"); 17 | }); 18 | 19 | it("should return middle for center alignment", function () { 20 | result = underTest.call(mock, { alignment: "center" }); 21 | expect(result).toEqual("middle"); 22 | }); 23 | 24 | it("should return end for right alignment", function () { 25 | result = underTest.call(mock, { alignment: "right" }); 26 | expect(result).toEqual("end"); 27 | }); 28 | 29 | it("should default to start", function () { 30 | result = underTest.call(mock, {}); 31 | expect(result).toEqual("start"); 32 | }); 33 | 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/getTextPosition.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/getTextPosition'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getTextPosition", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.getTextPosition, 7 | result, 8 | datum; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | datum = { 14 | alignment: "left", 15 | cellPadding: 13, 16 | textWidth: 17 17 | }; 18 | }); 19 | 20 | it("should left align text plus padding for left alignment", function () { 21 | result = underTest.call(mock, datum); 22 | expect(result).toEqual(datum.cellPadding); 23 | }); 24 | 25 | it("should default to left alignment", function () { 26 | delete datum.alignment; 27 | result = underTest.call(mock, datum); 28 | expect(result).toEqual(datum.cellPadding); 29 | }); 30 | 31 | it("should include the sort icon size and another cell padding for left alignment", function () { 32 | datum.sortIcon = "sort icon"; 33 | result = underTest.call(mock, datum); 34 | expect(result).toEqual(mock.vals.sortIconSize + 2 * datum.cellPadding); 35 | }); 36 | 37 | it("should set the x position to the center of the cell for center alignment", function () { 38 | datum.alignment = "center"; 39 | result = underTest.call(mock, datum); 40 | expect(result).toEqual(datum.textWidth / 2); 41 | }); 42 | 43 | it("should set the x position to the right of the cell minus the padding", function () { 44 | datum.alignment = "right"; 45 | result = underTest.call(mock, datum); 46 | expect(result).toEqual(datum.textWidth - datum.cellPadding); 47 | }); 48 | 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/getVisibleRegion.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/getVisibleRegion'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getTextPosition", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.getVisibleRegion, 7 | result; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | result = underTest.call(mock); 13 | }); 14 | 15 | it("should use the scroll left for the left of the visible region", function () { 16 | expect(result.left).toEqual(mock.vals.nodeScrollLeft); 17 | }); 18 | 19 | it("should use the scroll top for the top of the visible region", function () { 20 | expect(result.top).toEqual(mock.vals.nodeScrollTop); 21 | }); 22 | 23 | it("should use the scroll left plus the visible region width for the right of the visible region", function () { 24 | expect(result.right).toEqual(mock.vals.nodeScrollLeft + mock.vals.nodeClientWidth); 25 | }); 26 | 27 | it("should use the scroll top plus the visible region height for the bottom of the visible region", function () { 28 | expect(result.right).toEqual(mock.vals.nodeScrollTop + mock.vals.nodeClientHeight); 29 | }); 30 | 31 | }); 32 | 33 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/matchRule.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/matchRule'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("matchRule", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.matchRule; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | }); 12 | 13 | it("should always match the * selector", function () { 14 | expect(underTest.call(mock, "*", 11, 100)).toEqual(true); 15 | }); 16 | 17 | it("should match a single value for a direct value", function () { 18 | expect(underTest.call(mock, "11", 11, 100)).toEqual(true); 19 | }); 20 | 21 | it("should not match a different single value for a direct value", function () { 22 | expect(underTest.call(mock, "12", 11, 100)).toEqual(false); 23 | }); 24 | 25 | it("should match -1 with the extreme value", function () { 26 | expect(underTest.call(mock, "-1", 100, 100)).toEqual(true); 27 | }); 28 | 29 | it("should match -1 with the extreme value minus 1", function () { 30 | expect(underTest.call(mock, "-1", 99, 100)).toEqual(false); 31 | }); 32 | 33 | it("should match -1 with literal minus 1", function () { 34 | expect(underTest.call(mock, "-1", -1, 100)).toEqual(false); 35 | }); 36 | 37 | it("should match first element in a comma separated array of values", function () { 38 | expect(underTest.call(mock, "1,3,-2", 1, 100)).toEqual(true); 39 | }); 40 | 41 | it("should match second element in a comma separated array of values", function () { 42 | expect(underTest.call(mock, "1,3,-2", 3, 100)).toEqual(true); 43 | }); 44 | 45 | it("should match negative element in a comma separated array of values", function () { 46 | expect(underTest.call(mock, "1,3,-2", 99, 100)).toEqual(true); 47 | }); 48 | 49 | it("should not match non-matching element in a comma separated array of values", function () { 50 | expect(underTest.call(mock, "1,3,-2", 2, 100)).toEqual(false); 51 | }); 52 | 53 | it("should match first element of an array of elements", function () { 54 | expect(underTest.call(mock, "2:4", 2, 100)).toEqual(true); 55 | }); 56 | 57 | it("should match middle element of an array of elements", function () { 58 | expect(underTest.call(mock, "2:4", 3, 100)).toEqual(true); 59 | }); 60 | 61 | it("should match last element of an array of elements", function () { 62 | expect(underTest.call(mock, "2:4", 4, 100)).toEqual(true); 63 | }); 64 | 65 | it("should not match outside element of an array of elements", function () { 66 | expect(underTest.call(mock, "2:4", 5, 100)).toEqual(false); 67 | }); 68 | 69 | it("should match first element in a negative array of elements", function () { 70 | expect(underTest.call(mock, "-92:-90", 9, 100)).toEqual(true); 71 | }); 72 | 73 | it("should match middle element in a negative array of elements", function () { 74 | expect(underTest.call(mock, "-92:-90", 10, 100)).toEqual(true); 75 | }); 76 | 77 | it("should match last element in a negative array of elements", function () { 78 | expect(underTest.call(mock, "-92:-90", 11, 100)).toEqual(true); 79 | }); 80 | 81 | it("should not match external element in a negative array of elements", function () { 82 | expect(underTest.call(mock, "-92:-90", 12, 100)).toEqual(false); 83 | }); 84 | 85 | it("should match array of elements as part of a list", function () { 86 | expect(underTest.call(mock, "1,5:6,9", 5, 100)).toEqual(true); 87 | }); 88 | 89 | it("should not match elements outside array of elements as part of a list", function () { 90 | expect(underTest.call(mock, "1,5:6,9", 4, 100)).toEqual(false); 91 | }); 92 | 93 | it("should match the first element in a skipped array", function () { 94 | expect(underTest.call(mock, "1(2)10", 1, 100)).toEqual(true); 95 | }); 96 | 97 | it("should not match a skipped element in a skipped array", function () { 98 | expect(underTest.call(mock, "1(2)10", 2, 100)).toEqual(false); 99 | }); 100 | 101 | it("should match an unskipped element in a skipped array", function () { 102 | expect(underTest.call(mock, "1(2)10", 3, 100)).toEqual(true); 103 | }); 104 | 105 | it("should match whole array when skip value is less than 1", function () { 106 | expect(underTest.call(mock, "1(0)10", 2, 100)).toEqual(true); 107 | }); 108 | 109 | }); 110 | 111 | }); 112 | -------------------------------------------------------------------------------- /test/spec/internal/render/rendeRegionForeground.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/renderRegionForeground'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("renderRegionForeground", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.renderRegionForeground, 7 | target; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | target = new d3.shape(mock.vals); 13 | }); 14 | 15 | it("should call loadDataRange on the adapter", function () { 16 | underTest.call(mock, "Bounds", target); 17 | expect(mock.adapter.loadDataRange).toHaveBeenCalled(); 18 | }); 19 | 20 | it("should pass bounds to loadDataRange", function () { 21 | underTest.call(mock, "Bounds", target); 22 | expect(mock.adapter.loadDataRange.calls.argsFor(0)[0]).toEqual("Bounds"); 23 | }); 24 | 25 | describe("loadDataRange callback", function () { 26 | 27 | var data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; 28 | 29 | beforeEach(function () { 30 | underTest.call(mock, "Bounds", target); 31 | mock.adapter.loadDataRange.calls.argsFor(0)[1](data); 32 | }); 33 | 34 | it("should call each on the cells", function () { 35 | expect(target.each).toHaveBeenCalled(); 36 | }); 37 | 38 | describe("each iterator", function () { 39 | 40 | var each; 41 | 42 | beforeEach(function () { 43 | each = target.each.calls.argsFor(0)[0]; 44 | }); 45 | 46 | it("should set the value", function () { 47 | var params = { visibleRow: 1, visibleColumn: 2, value: null}; 48 | each(params); 49 | expect(params.value).toEqual(6); 50 | }); 51 | 52 | it("should call renderBetween if set", function () { 53 | var params = { visibleRow: 1, visibleColumn: 2, value: null, renderBetween: jasmine.createSpy("renderBetween")}; 54 | each(params); 55 | expect(params.renderBetween).toHaveBeenCalled(); 56 | }); 57 | 58 | it("should call renderForeground if set", function () { 59 | var params = { visibleRow: 1, visibleColumn: 2, value: null, renderForeground: jasmine.createSpy("renderForeground")}; 60 | each(params); 61 | expect(params.renderForeground).toHaveBeenCalled(); 62 | }); 63 | 64 | }); 65 | 66 | }); 67 | 68 | }); 69 | 70 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/renderBackground.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/renderBackground'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("cropText", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.renderBackground, 7 | target, 8 | data; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | target = new d3.shape(mock.vals); 14 | data = { 15 | backgroundStyle: "Background Style", 16 | boxWidth: "Box Width", 17 | boxHeight: "Box Height" 18 | }; 19 | underTest.call(mock, target, data); 20 | }); 21 | 22 | it("should append a rectangle to the target", function () { 23 | expect(target.children.rect).toBeDefined(); 24 | }); 25 | 26 | it("should apply the background style class", function () { 27 | expect(target.children.rect[0].attributes['class']).toEqual(data.backgroundStyle); 28 | }); 29 | 30 | it("should set the box width", function () { 31 | expect(target.children.rect[0].attributes.width).toEqual(data.boxWidth); 32 | }); 33 | 34 | it("should set the box height", function () { 35 | expect(target.children.rect[0].attributes.height).toEqual(data.boxHeight); 36 | }); 37 | 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/renderForeground.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/renderForeground'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("renderForeground", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.renderForeground, 7 | target, 8 | data; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | target = new d3.shape(mock.vals); 14 | data = { 15 | foregroundStyle: "Foreground Style", 16 | textHeight: 7, 17 | textWidth: 13, 18 | cellPadding: 3, 19 | rowIndex: 11, 20 | columnIndex: 13, 21 | value: 123 22 | }; 23 | }); 24 | 25 | 26 | it("should append a text to the target", function () { 27 | underTest.call(mock, target, data); 28 | expect(target.children.text).toBeDefined(); 29 | }); 30 | 31 | it("should call the get text anchor method", function () { 32 | underTest.call(mock, target, data); 33 | expect(mock.internal.render.getTextAnchor).toHaveBeenCalledWith(data); 34 | }); 35 | 36 | it("should apply the text anchor", function () { 37 | underTest.call(mock, target, data); 38 | expect(target.children.text[0].styles["text-anchor"]).toEqual("Text Anchor"); 39 | }); 40 | 41 | it("should set the y offset", function () { 42 | underTest.call(mock, target, data); 43 | expect(target.children.text[0].attributes.dy).toEqual("0.35em"); 44 | }); 45 | 46 | it("should call the get text position method", function () { 47 | underTest.call(mock, target, data); 48 | expect(mock.internal.render.getTextPosition).toHaveBeenCalledWith(data); 49 | }); 50 | 51 | it("should apply the text position", function () { 52 | underTest.call(mock, target, data); 53 | expect(target.children.text[0].attributes.x).toEqual("Text Position"); 54 | }); 55 | 56 | it("should set the text to value if no formatter is defined", function () { 57 | underTest.call(mock, target, data); 58 | expect(target.children.text[0].textValue).toEqual(123); 59 | }); 60 | 61 | it("should set the text to the formatted value if formatter is defined", function () { 62 | data.formatter = function (v) { return v * 2; }; 63 | underTest.call(mock, target, data); 64 | expect(target.children.text[0].textValue).toEqual(246); 65 | }); 66 | 67 | it("should set y to half the height", function () { 68 | underTest.call(mock, target, data); 69 | expect(target.children.text[0].attributes.y).toEqual(3.5); 70 | }); 71 | 72 | it("should not call crop text if clip path is functional", function () { 73 | underTest.call(mock, target, data); 74 | expect(mock.internal.render.cropText).not.toHaveBeenCalled(); 75 | }); 76 | 77 | describe("browsers which don't support clip-path", function () { 78 | 79 | beforeEach(function () { 80 | mock.internal.render.getClipPath = function () { 81 | return ""; 82 | }; 83 | }); 84 | 85 | it("should call crop text if clip path is not functional", function () { 86 | underTest.call(mock, target, data); 87 | expect(mock.internal.render.cropText).toHaveBeenCalled(); 88 | }); 89 | 90 | it("should call crop text with the width minus cell padding if no sort icon is specified", function () { 91 | underTest.call(mock, target, data); 92 | expect(mock.internal.render.cropText).toHaveBeenCalledWith(target.children.text[0], data.textWidth - data.cellPadding); 93 | }); 94 | 95 | it("should call crop text with the width minus cell padding if no sort icon is set to none", function () { 96 | data.sortIcon = 'none'; 97 | underTest.call(mock, target, data); 98 | expect(mock.internal.render.cropText).toHaveBeenCalledWith(target.children.text[0], data.textWidth - data.cellPadding); 99 | }); 100 | 101 | it("should should take off sort icon size and an additional padding if sort icon is defined", function () { 102 | data.sortIcon = 'A defined icon'; 103 | underTest.call(mock, target, data); 104 | expect(mock.internal.render.cropText).toHaveBeenCalledWith(target.children.text[0], data.textWidth - 2 * data.cellPadding - mock.vals.sortIconSize); 105 | }); 106 | }); 107 | 108 | }); 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /test/spec/internal/render/renderSortIcon.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/renderSortIcon'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("renderSortIcon", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.renderSortIcon, 7 | target, 8 | datum; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | datum = { 14 | sortIcon: "Sort Icon", 15 | textWidth: 3, 16 | textHeight: 5, 17 | cellPadding: 7 18 | }; 19 | target = new d3.shape(mock.vals); 20 | }); 21 | 22 | it("should not append a group if sorted is false", function () { 23 | underTest.call(mock, datum, target, false); 24 | expect(target.children.g).toBeUndefined(); 25 | }); 26 | 27 | it("should not append a group if text width is narrower than sort icon size and cell padding", function () { 28 | datum.textWidth = mock.vals.sortIconSize + datum.cellPadding; 29 | underTest.call(mock, datum, target, true); 30 | expect(target.children.g).toBeUndefined(); 31 | }); 32 | 33 | describe("text width is wider than the sort icon", function () { 34 | 35 | beforeEach(function () { 36 | datum.textWidth = mock.vals.sortIconSize + datum.cellPadding + 1; 37 | underTest.call(mock, datum, target, true); 38 | }); 39 | 40 | it("should append a group", function () { 41 | expect(target.children.g).toBeDefined(); 42 | expect(target.children.g.length).toEqual(1); 43 | }); 44 | 45 | it("should pass the sort icon as a datum", function () { 46 | expect(target.children.g[0].dataPoint).toEqual("Sort Icon"); 47 | }); 48 | 49 | it("should class it with a selector", function () { 50 | expect(target.children.g[0].attributes["class"]).toEqual("sg-no-style--sort-icon-selector"); 51 | }); 52 | 53 | it("should position it with a transform", function () { 54 | expect(target.children.g[0].attributes.transform).toEqual("translate(" + (datum.cellPadding + mock.vals.sortIconSize / 2) + "," + (datum.textHeight / 2) + ")"); 55 | }); 56 | 57 | it("should call the sort icon method", function () { 58 | expect(target.children.g[0].call).toHaveBeenCalled(); 59 | target.children.g[0].call.calls.argsFor(0)[0]("Data Point"); 60 | expect(mock.internal.render.sortIcon).toHaveBeenCalledWith("Data Point"); 61 | }); 62 | 63 | }); 64 | 65 | 66 | }); 67 | 68 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/setDefaultStyles.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/setDefaultStyles'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("setDefaultStyles", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.setDefaultStyles; 7 | 8 | beforeEach(function () { 9 | mock.init(); 10 | d3.init(); 11 | underTest.call(mock); 12 | }); 13 | 14 | it("should set a style for the left panel", function () { 15 | expect(mock.style.left.panel).toEqual("sg-grid sg-fixed sg-left"); 16 | }); 17 | it("should set a style for the top panel", function () { 18 | expect(mock.style.top.panel).toEqual("sg-grid sg-fixed sg-top"); 19 | }); 20 | it("should set a style for the top left panel", function () { 21 | expect(mock.style.top.left.panel).toEqual("sg-grid sg-fixed sg-top sg-left"); 22 | }); 23 | it("should set a style for the top right panel", function () { 24 | expect(mock.style.top.right.panel).toEqual("sg-grid sg-fixed sg-top sg-right"); 25 | }); 26 | it("should set a style for the right panel", function () { 27 | expect(mock.style.right.panel).toEqual("sg-grid sg-fixed sg-right"); 28 | }); 29 | it("should set a style for the bottom panel", function () { 30 | expect(mock.style.bottom.panel).toEqual("sg-grid sg-fixed sg-bottom"); 31 | }); 32 | it("should set a style for the bottom left panel", function () { 33 | expect(mock.style.bottom.left.panel).toEqual("sg-grid sg-fixed sg-bottom sg-left"); 34 | }); 35 | it("should set a style for the bottom right panel", function () { 36 | expect(mock.style.bottom.right.panel).toEqual("sg-grid sg-fixed sg-bottom sg-right"); 37 | }); 38 | it("should set a style for the main panel", function () { 39 | expect(mock.style.main.panel).toEqual("sg-grid"); 40 | }); 41 | it("should set a style for the resize handle", function () { 42 | expect(mock.style.resizeHandle).toEqual("sg-resize-handle"); 43 | }); 44 | it("should set a prefix for cell background classes", function () { 45 | expect(mock.style.cellBackgroundPrefix).toEqual("sg-cell-background-"); 46 | }); 47 | it("should set a prefix for cell foreground classes", function () { 48 | expect(mock.style.cellForegroundPrefix).toEqual("sg-cell-foreground-"); 49 | }); 50 | it("should set a style for the sort icon", function () { 51 | expect(mock.style.resizeHandle).toEqual("sg-resize-handle"); 52 | }); 53 | 54 | 55 | }); 56 | 57 | }); -------------------------------------------------------------------------------- /test/spec/internal/render/sortIcon.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'render/sortIcon'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("sortIcon", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.render.sortIcon, 7 | target; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | target = new d3.shape(mock.vals); 13 | mock.properties.sortIconSize = 2; 14 | }); 15 | 16 | it("should append a path", function () { 17 | underTest.call(mock, target); 18 | expect(target.children.path).toBeDefined(); 19 | }); 20 | 21 | it("should apply the sort icon class", function () { 22 | underTest.call(mock, target); 23 | expect(target.children.path[0].attributes["class"]).toEqual(mock.style.sortIcon); 24 | }); 25 | 26 | it("should draw an up arrow if sort is ascending", function () { 27 | target.dataPoint = "asc"; 28 | underTest.call(mock, target); 29 | expect(target.children.path[0].attributes.d).toEqual("M 1 0 L 2 2 L 0 2 z"); 30 | }); 31 | 32 | it("should draw a down arrow if sort is descending", function () { 33 | target.dataPoint = "desc"; 34 | underTest.call(mock, target); 35 | expect(target.children.path[0].attributes.d).toEqual("M 0 0 L 2 0 L 1 2 z"); 36 | }); 37 | 38 | it("should not set the data element is direction is not set", function () { 39 | target.dataPoint = null; 40 | underTest.call(mock, target); 41 | expect(target.children.path[0].attributes.d).toBeUndefined(); 42 | }); 43 | 44 | it("should center the icon around (0, 0)", function () { 45 | underTest.call(mock, target); 46 | expect(target.children.path[0].attributes.transform).toEqual("translate(" + (mock.vals.boundingBoxWidth / -2) + "," + (mock.vals.boundingBoxHeight / -2) + ")"); 47 | }); 48 | 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /test/spec/internal/sizes/calculatePhysicalBounds.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'sizes/calculatePhysicalBounds'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("calculatePhysicalBounds", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.sizes.calculatePhysicalBounds, 7 | elems, 8 | props, 9 | topMargin; 10 | 11 | beforeEach(function () { 12 | 13 | mock.init(); 14 | d3.init(); 15 | 16 | elems = mock.elements; 17 | props = mock.properties; 18 | 19 | mock.columns = [ 20 | { width: 1 }, { width: 2 }, { width: 3 }, { width: 5 }, { width: 7 }, 21 | { width: 11 }, { width: 13 }, { width: 17 }, { width: 23 }, { width: 37 } 22 | ]; 23 | 24 | props.virtualLeft = 4; 25 | props.virtualRight = 2; 26 | props.virtualOuterWidth = 10; 27 | props.virtualTop = 2; 28 | props.virtualBottom = 3; 29 | props.virtualInnerHeight = 5; 30 | props.headerRowHeight = 41; 31 | props.footerRowHeight = 43; 32 | props.rowHeight = 47; 33 | elems.container.nodeObject.offsetHeight = 49; 34 | elems.container.nodeObject.offsetWidth = 51; 35 | topMargin = 53; 36 | 37 | underTest.call(mock, topMargin); 38 | 39 | }); 40 | 41 | it("should calculate physical left as the sum of column widths before the left most cell", function () { 42 | expect(props.physicalLeft).toEqual(1 + 2 + 3 + 5); 43 | }); 44 | 45 | it("should calculate physical inner width as the sum of column widths between left and right most cells", function () { 46 | expect(props.physicalTotalInnerWidth).toEqual(7 + 11 + 13 + 17); 47 | }); 48 | 49 | it("should calculate the physical right bound as the column widths after the right most cell", function () { 50 | expect(props.physicalRight).toEqual(23 + 37); 51 | }); 52 | 53 | it("should calculate the physical height of the column header", function () { 54 | expect(props.physicalTop).toEqual(2 * 41); 55 | }); 56 | 57 | it("should calculate the physical height of column footer", function () { 58 | expect(props.physicalBottom).toEqual(3 * 43); 59 | }); 60 | 61 | it("should calculate the width of the visible area", function () { 62 | expect(props.physicalVisibleInnerWidth).toEqual(51 - props.physicalLeft - props.physicalRight); 63 | }); 64 | 65 | it("should calculate the height of the visible area", function () { 66 | expect(props.physicalVisibleInnerHeight).toEqual(49 - 53 - props.physicalTop - props.physicalBottom); 67 | }); 68 | 69 | it("should calculate the height of the visible area", function () { 70 | expect(props.physicalTotalInnerHeight).toEqual(5 * 47); 71 | }); 72 | 73 | }); 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /test/spec/internal/sizes/calculateTextBound.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'sizes/calculateTextBound'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("calculateTextBound", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.sizes.calculateTextBound, 7 | surface, 8 | result; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | 14 | surface = new d3.shape(mock.vals); 15 | 16 | result = underTest(surface, "My Text To Measure"); 17 | }); 18 | 19 | it("should append the text to the passed surface to measure", function () { 20 | expect(surface.children.text[0].textValue).toEqual("My Text To Measure"); 21 | }); 22 | 23 | it("should remove the shape from the surface afterwards", function () { 24 | expect(surface.children.text[0].remove).toHaveBeenCalled(); 25 | }); 26 | 27 | it("should return the measured width of the text", function () { 28 | expect(result.width).toEqual(surface.children.text[0].bounds.width); 29 | }); 30 | 31 | it("should return the measured height of the text", function () { 32 | expect(result.height).toEqual(surface.children.text[0].bounds.height); 33 | }); 34 | 35 | }); 36 | 37 | }); -------------------------------------------------------------------------------- /test/spec/internal/sizes/getExistingTextBound.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'sizes/getExistingTextBound'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getExistingTextBound", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.sizes.getExistingTextBound, 7 | surface, 8 | sub; 9 | 10 | beforeEach(function () { 11 | mock.init(); 12 | d3.init(); 13 | 14 | surface = new d3.shape(mock.vals); 15 | 16 | }); 17 | 18 | it("should select all text elements on the surface", function () { 19 | underTest.call(mock, surface); 20 | expect(surface.selections.text.length).toEqual(1); 21 | }); 22 | 23 | it("should handle any row if row and column are not passed", function () { 24 | underTest.call(mock, surface); 25 | sub = surface.selections.text[0].filter.calls.argsFor(0)[0]; 26 | expect(sub({ rowIndex: 123, columnIndex: 456 })).toEqual(true); 27 | expect(sub({ rowIndex: 321, columnIndex: 456 })).toEqual(true); 28 | expect(sub({ rowIndex: 123, columnIndex: 654 })).toEqual(true); 29 | expect(sub({ rowIndex: 321, columnIndex: 654 })).toEqual(true); 30 | }); 31 | 32 | it("should handle any row if columns match", function () { 33 | underTest.call(mock, surface, 456); 34 | sub = surface.selections.text[0].filter.calls.argsFor(0)[0]; 35 | expect(sub({ rowIndex: 123, columnIndex: 456 })).toEqual(true); 36 | expect(sub({ rowIndex: 321, columnIndex: 456 })).toEqual(true); 37 | expect(sub({ rowIndex: 123, columnIndex: 654 })).toEqual(false); 38 | expect(sub({ rowIndex: 321, columnIndex: 654 })).toEqual(false); 39 | }); 40 | 41 | it("should handle any column if rows match", function () { 42 | underTest.call(mock, surface, undefined, 123); 43 | sub = surface.selections.text[0].filter.calls.argsFor(0)[0]; 44 | expect(sub({ rowIndex: 123, columnIndex: 456 })).toEqual(true); 45 | expect(sub({ rowIndex: 321, columnIndex: 456 })).toEqual(false); 46 | expect(sub({ rowIndex: 123, columnIndex: 654 })).toEqual(true); 47 | expect(sub({ rowIndex: 321, columnIndex: 654 })).toEqual(false); 48 | }); 49 | 50 | it("should handle only row and column which are passed", function () { 51 | underTest.call(mock, surface, 456, 123); 52 | sub = surface.selections.text[0].filter.calls.argsFor(0)[0]; 53 | expect(sub({ rowIndex: 123, columnIndex: 456 })).toEqual(true); 54 | expect(sub({ rowIndex: 321, columnIndex: 456 })).toEqual(false); 55 | expect(sub({ rowIndex: 123, columnIndex: 654 })).toEqual(false); 56 | expect(sub({ rowIndex: 321, columnIndex: 654 })).toEqual(false); 57 | }); 58 | 59 | it("should return the pushed bounds", function () { 60 | expect(underTest.call(mock, surface, 456, 123)).toEqual({ width: 0, height: 0 }); 61 | }); 62 | 63 | describe("each operation", function () { 64 | 65 | beforeEach(function () { 66 | underTest.call(mock, surface); 67 | sub = surface.selections.text[0].each.calls.argsFor(0)[0]; 68 | }); 69 | 70 | it("should pass zero if no sort icon is set", function () { 71 | sub({ cellPadding: 13 }); 72 | expect(mock.internal.sizes.pushTextBound).toHaveBeenCalledWith({ width: 0, height: 0 }, d3.returnValues.select, 13, 0); 73 | }); 74 | 75 | it("should pass zero if sort icon is set to none", function () { 76 | sub({ cellPadding: 13, sortIcon: "none" }); 77 | expect(mock.internal.sizes.pushTextBound).toHaveBeenCalledWith({ width: 0, height: 0 }, d3.returnValues.select, 13, 0); 78 | }); 79 | 80 | it("should pass size if sort icon is set", function () { 81 | sub({ cellPadding: 13, sortIcon: "asc" }); 82 | expect(mock.internal.sizes.pushTextBound).toHaveBeenCalledWith({ width: 0, height: 0 }, d3.returnValues.select, 13, mock.properties.sortIconSize + 13); 83 | }); 84 | 85 | }); 86 | 87 | }); 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /test/spec/internal/sizes/getRowHeight.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'sizes/getRowHeight'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("getRowHeight", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.sizes.getRowHeight, 7 | props; 8 | 9 | beforeEach(function () { 10 | mock.init(); 11 | d3.init(); 12 | 13 | props = mock.properties; 14 | 15 | props.virtualOuterHeight = 10; 16 | props.virtualTop = 2; 17 | props.virtualBottom = 3; 18 | props.headerRowHeight = 17; 19 | props.rowHeight = 23; 20 | props.footerRowHeight = 29; 21 | }); 22 | 23 | it("should return the header row height for a header row", function () { 24 | expect(underTest.call(mock, 1)).toEqual(17); 25 | }); 26 | 27 | it("should return the standard row height for a body row", function () { 28 | expect(underTest.call(mock, 4)).toEqual(23); 29 | }); 30 | 31 | it("should return the standard row height for a body row", function () { 32 | expect(underTest.call(mock, 8)).toEqual(29); 33 | }); 34 | 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /test/spec/internal/sizes/pushTextBound.spec.js: -------------------------------------------------------------------------------- 1 | define(['d3', 'mock', 'sizes/pushTextBound'], function (d3, mock) { 2 | "use strict"; 3 | 4 | describe("pushTextBound", function () { 5 | 6 | var underTest = Scrollgrid.prototype.internal.sizes.pushTextBound, 7 | currentBounds, 8 | shape, 9 | cellPadding, 10 | sortIconSize; 11 | 12 | beforeEach(function () { 13 | mock.init(); 14 | d3.init(); 15 | 16 | sortIconSize = 3; 17 | cellPadding = 7; 18 | currentBounds = { 19 | height: 17, 20 | width: 19 21 | }; 22 | shape = new d3.shape(mock.vals); 23 | 24 | }); 25 | 26 | it("should use the original text if defined", function () { 27 | shape.dataPoint = { 28 | originalText: "My Original Text" 29 | }; 30 | shape.textValue = "My Abbr..."; 31 | underTest.call(mock, currentBounds, shape, cellPadding, sortIconSize); 32 | expect(shape.text.calls.argsFor(0)).toEqual([]); 33 | expect(shape.text.calls.argsFor(1)).toEqual(["My Original Text"]); 34 | expect(shape.text.calls.argsFor(2)).toEqual(["My Abbr..."]); 35 | }); 36 | 37 | it("should use the cell text if original text is not defined", function () { 38 | shape.dataPoint = {}; 39 | shape.textValue = "My Abbr..."; 40 | underTest.call(mock, currentBounds, shape, cellPadding, sortIconSize); 41 | expect(shape.text.calls.argsFor(0)).toEqual([]); 42 | expect(shape.text.calls.argsFor(1)).toEqual([]); 43 | expect(shape.text.calls.argsFor(2)).toEqual(["My Abbr..."]); 44 | expect(shape.text.calls.argsFor(3)).toEqual(["My Abbr..."]); 45 | }); 46 | 47 | it("should return 2 * padding + bounds width + sort icon size if greater than current width", function () { 48 | var text = "Text"; 49 | shape.dataPoint = { originalText: text }; 50 | shape.textValue = text; 51 | shape.characterWidth = 1; 52 | expect(underTest.call(mock, currentBounds, shape, cellPadding, sortIconSize).width).toEqual(2 * 7 + 4 + 3); 53 | }); 54 | 55 | it("should return current width if wider than 2 * padding + bounds width + sort icon size", function () { 56 | var text = "T"; 57 | shape.dataPoint = { originalText: text }; 58 | shape.textValue = text; 59 | shape.characterWidth = 1; 60 | expect(underTest.call(mock, currentBounds, shape, cellPadding, sortIconSize).width).toEqual(19); 61 | }); 62 | 63 | it("should return bounds height if greater than current height", function () { 64 | shape.bounds.height = 18; 65 | expect(underTest.call(mock, currentBounds, shape, cellPadding, sortIconSize).height).toEqual(18); 66 | }); 67 | 68 | it("should return current height if greater than bounds height", function () { 69 | shape.bounds.height = 16; 70 | expect(underTest.call(mock, currentBounds, shape, cellPadding, sortIconSize).height).toEqual(17); 71 | }); 72 | }); 73 | 74 | }); --------------------------------------------------------------------------------