├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── Brocfile.js ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── column-definition.js ├── components │ └── ember-table.js ├── controllers │ └── row.js ├── mixins │ ├── mouse-wheel-handler-mixin.js │ ├── scroll-handler-mixin.js │ ├── show-horizontal-scroll-mixin.js │ ├── style-bindings-mixin.js │ └── touch-move-handler-mixin.js ├── resize-detection.js ├── row-definition.js └── views │ ├── body-table-container.js │ ├── column-sortable-indicator.js │ ├── footer-table-container.js │ ├── header-block.js │ ├── header-cell.js │ ├── header-row.js │ ├── header-table-container.js │ ├── lazy-container.js │ ├── lazy-item.js │ ├── lazy-table-block.js │ ├── multi-item-view-collection.js │ ├── scroll-container.js │ ├── scroll-panel.js │ ├── table-block.js │ ├── table-cell.js │ ├── table-container.js │ └── table-row.js ├── app ├── .gitkeep ├── column-definition.js ├── components │ └── ember-table.js ├── controllers │ └── row.js ├── initializers │ └── ember-cli-ember-table.js ├── row-definition.js ├── templates │ ├── body-table-container.hbs │ ├── components │ │ └── ember-table.hbs │ ├── footer-container.hbs │ ├── header-cell.hbs │ ├── header-row.hbs │ ├── header-table-container.hbs │ ├── scroll-container.hbs │ ├── table-cell.hbs │ └── table-row.hbs └── views │ ├── body-table-container.js │ ├── column-sortable-indicator.js │ ├── footer-table-container.js │ ├── header-block.js │ ├── header-cell.js │ ├── header-row.js │ ├── header-table-container.js │ ├── lazy-container.js │ ├── lazy-item.js │ ├── lazy-table-block.js │ ├── multi-item-view-collection.js │ ├── scroll-container.js │ ├── scroll-panel.js │ ├── table-block.js │ ├── table-cell.js │ ├── table-container.js │ └── table-row.js ├── blueprints └── ember-cli-ember-table │ └── index.js ├── bower.json ├── config ├── ember-try.js └── environment.js ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── acceptance │ ├── fixed-test.js │ ├── removable-columns-test.js │ ├── simple-test.js │ ├── sort-array-controller-test.js │ └── sort-computed-test.js ├── dummy │ ├── .jshintrc │ ├── app │ │ ├── app.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── fixed.js │ │ │ ├── removable-columns.js │ │ │ ├── simple.js │ │ │ ├── sort-array-controller.js │ │ │ └── sort-computed.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ ├── fixed.js │ │ │ ├── removable-columns.js │ │ │ ├── simple.js │ │ │ ├── sort-array-controller.js │ │ │ └── sort-computed.js │ │ ├── styles │ │ │ ├── .gitkeep │ │ │ └── dummy.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── fixed.hbs │ │ │ ├── index.hbs │ │ │ ├── removable-columns.hbs │ │ │ ├── simple.hbs │ │ │ ├── sort-array-controller.hbs │ │ │ └── sort-computed.hbs │ ├── config │ │ └── environment.js │ └── public │ │ ├── .gitkeep │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── resolver.js │ ├── start-app.js │ └── text.js ├── index.html ├── test-helper.js └── unit │ └── .gitkeep └── vendor ├── .gitkeep └── ember-table.css /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | dist/ 5 | 6 | .bowerrc 7 | .editorconfig 8 | .ember-cli 9 | .travis.yml 10 | .npmignore 11 | **/.gitkeep 12 | bower.json 13 | Brocfile.js 14 | testem.json 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | env: 13 | - EMBER_TRY_SCENARIO=default 14 | - EMBER_TRY_SCENARIO=ember-release 15 | - EMBER_TRY_SCENARIO=ember-beta 16 | - EMBER_TRY_SCENARIO=ember-canary 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | - env: EMBER_TRY_SCENARIO=ember-canary 22 | 23 | before_install: 24 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 25 | - "npm config set spin false" 26 | - "npm install -g npm@^2" 27 | 28 | install: 29 | - npm install -g bower 30 | - npm install 31 | - bower install 32 | 33 | script: 34 | - ember try $EMBER_TRY_SCENARIO test 35 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* global require, module */ 3 | 4 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 5 | 6 | /* 7 | This Brocfile specifes the options for the dummy test app of this 8 | addon, located in `/tests/dummy` 9 | 10 | This Brocfile does *not* influence how the addon or the app using it 11 | behave. You most likely want to be modifying `./index.js` or app's Brocfile 12 | */ 13 | 14 | var app = new EmberAddon(); 15 | 16 | module.exports = app.toTree(); 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-cli-ember-table 2 | 3 | **NOTE**: This addon is a fork from [http://addepar.github.io/ember-table](http://addepar.github.io/ember-table) into vanilla JS and ES6 style modules. The fork was made out of the master branch @ea0983f. 4 | 5 | ## Test in dummy app locally 6 | 7 | * `git clone` this repository 8 | * `cd ember-cli-ember-table` 9 | * `npm install` 10 | * `bower install` 11 | * `ember server` 12 | * Visit http://localhost:4200 13 | 14 | ## Usage (in consuming ember-cli project) 15 | 16 | * `npm install ember-cli-ember-table --save` 17 | * `ember g ember-cli-ember-table` 18 | 19 | At this point you can use the component in the templates of your consuming app. 20 | 21 | {{ember-table 22 | height=400 23 | hasFooter=false 24 | enableContentSelection=true 25 | columnsBinding="columns" 26 | contentBinding="content" 27 | }} 28 | 29 | You will also need to setup your controller to provide content for the ember-table. 30 | 31 | Example controller 32 | 33 | import ColumnDefinition from '../column-definition'; 34 | 35 | export default Ember.Controller.extend({ 36 | numRows: 10000, 37 | 38 | columns: function() { 39 | var dateColumn, openColumn, highColumn, lowColumn, closeColumn; 40 | dateColumn = ColumnDefinition.create({ 41 | columnWidth: 150, 42 | textAlign: 'text-align-left', 43 | headerCellName: 'Date', 44 | getCellContent: function(row) { 45 | return row.get('date').toDateString(); 46 | } 47 | }); 48 | openColumn = ColumnDefinition.create({ 49 | columnWidth: 100, 50 | headerCellName: 'Open', 51 | getCellContent: function(row) { 52 | return row.get('open').toFixed(2); 53 | } 54 | }); 55 | highColumn = ColumnDefinition.create({ 56 | columnWidth: 100, 57 | headerCellName: 'High', 58 | getCellContent: function(row) { 59 | return row.get('high').toFixed(2); 60 | } 61 | }); 62 | lowColumn = ColumnDefinition.create({ 63 | columnWidth: 100, 64 | headerCellName: 'Low', 65 | getCellContent: function(row) { 66 | return row.get('low').toFixed(2); 67 | } 68 | }); 69 | closeColumn = ColumnDefinition.create({ 70 | columnWidth: 100, 71 | headerCellName: 'Close', 72 | getCellContent: function(row) { 73 | return row.get('close').toFixed(2); 74 | } 75 | }); 76 | return [dateColumn, openColumn, highColumn, lowColumn, closeColumn]; 77 | }.property(), 78 | 79 | content: function() { 80 | var generatedContent = []; 81 | for (var i = 0; i < this.get('numRows'); i++) { 82 | var date = new Date(); 83 | date.setDate(date.getDate() + i); 84 | generatedContent.push(Ember.Object.create({ 85 | date: date, 86 | open: Math.random() * 100, 87 | high: Math.random() * 100 + 50, 88 | low: Math.random() * 100 - 50, 89 | close: Math.random() * 100 90 | })); 91 | } 92 | return generatedContent; 93 | }.property('numRows'), 94 | ... 95 | 96 | 97 | For more information on how to setup `ember-table`, please visit [http://addepar.github.io/ember-table](http://addepar.github.io/ember-table). 98 | 99 | For more information on using `ember-cli`, please visit [http://www.ember-cli.com/](http://www.ember-cli.com/). 100 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intuitivepixel/ember-cli-ember-table/15fa20cd8177965c5a78fa61f7b99afdd29e4007/addon/.gitkeep -------------------------------------------------------------------------------- /addon/column-definition.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var ColumnDefinition = Ember.Object.extend({ 4 | 5 | /* 6 | * --------------------------------------------------------------------------- 7 | * API - Inputs 8 | * --------------------------------------------------------------------------- 9 | */ 10 | 11 | /* 12 | Name of the column, to be displayed in the header. 13 | TODO(new-api): Change to `columnName` 14 | */ 15 | headerCellName: null, 16 | 17 | /* 18 | Path of the content for this cell. If the row object is a hash of keys 19 | and values to specify data for each column, `contentPath` corresponds to 20 | the key. 21 | */ 22 | contentPath: null, 23 | 24 | /* 25 | Minimum column width. Affects both manual resizing and automatic resizing 26 | (in `forceFillColumns` mode). 27 | */ 28 | minWidth: null, 29 | 30 | /* 31 | Maximum column width. Affects both manual resizing and automatic resizing 32 | (in `forceFillColumns` mode). 33 | */ 34 | maxWidth: null, 35 | 36 | /* 37 | Default column width. Specifies the initial width of the column; if the 38 | column is later resized automatically, it will be proportional to this. 39 | */ 40 | defaultColumnWidth: 150, 41 | 42 | /* Whether the column can be manually resized. */ 43 | isResizable: true, 44 | 45 | /* 46 | Whether the column can be rearranged with other columns. Only matters if 47 | the table's `enableColumnReorder` property is set to true (the default). 48 | TODO(new-api): Rename to `isReorderable` 49 | */ 50 | isSortable: true, 51 | 52 | /* 53 | Alignment of the text in the cell. Possible values are "left", "center", 54 | and "right". 55 | */ 56 | textAlign: 'text-align-right', 57 | 58 | /* 59 | Whether the column can automatically resize to fill space in the table. 60 | Only matters if the table is in `forceFillColumns` mode. 61 | */ 62 | canAutoResize: true, 63 | 64 | /* 65 | TODO(new-api): Remove `headerCellViewClass` 66 | Override to specify a custom view to use for the header cell. 67 | */ 68 | headerCellView: 'header-cell', 69 | headerCellViewClass: Ember.computed.alias('headerCellView'), 70 | 71 | /* 72 | TODO(new-api): Remove `tableCellViewClass` 73 | Override to specify a custom view to use for table cells. 74 | */ 75 | tableCellView: 'table-cell', 76 | tableCellViewClass: Ember.computed.alias('tableCellView'), 77 | 78 | /* 79 | Override to customize how the column gets data from each row object. 80 | Given a row, should return a formatted cell value, e.g. $20,000,000. 81 | */ 82 | getCellContent: function(row) { 83 | var path; 84 | path = this.get('contentPath'); 85 | Ember.assert("You must either provide a contentPath or override " + "getCellContent in your column definition", path != null); 86 | return Ember.get(row, path); 87 | }, 88 | 89 | /* 90 | Override to maintain a consistent path to update cell values. 91 | Recommended to make this a function which takes (row, value) and updates 92 | the row value. 93 | */ 94 | setCellContent: Ember.K, 95 | 96 | /* 97 | * --------------------------------------------------------------------------- 98 | * Internal properties 99 | * --------------------------------------------------------------------------- 100 | */ 101 | 102 | /* 103 | Internal: width of the column. 104 | TODO: Rename to `width` 105 | */ 106 | columnWidth: Ember.computed.oneWay('defaultColumnWidth'), 107 | resize: function(width) { 108 | return this.set('columnWidth', width); 109 | } 110 | }); 111 | 112 | export default ColumnDefinition; -------------------------------------------------------------------------------- /addon/components/ember-table.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | import RowArrayController from '../controllers/row'; 4 | import RowDefinition from '../row-definition'; 5 | import { addResizeListener } from '../resize-detection'; 6 | 7 | /* global $ */ 8 | const {on, set, get} = Ember; 9 | 10 | let EmberTableComponent = Ember.Component.extend(StyleBindingsMixin, { 11 | classNames: ['ember-table-tables-container'], 12 | classNameBindings: ['enableContentSelection:ember-table-content-selectable'], 13 | 14 | /* 15 | * --------------------------------------------------------------------------- 16 | * API - Inputs 17 | * --------------------------------------------------------------------------- 18 | */ 19 | 20 | /* 21 | Values which are bound to the table's style attr. See 22 | `Ember.StyleBindingsMixin` documentation for more details. 23 | */ 24 | styleBindings: ['height'], 25 | 26 | /* 27 | Attributes on the element which are bound the component values 28 | */ 29 | attributeBindings: ['tabIndex'], 30 | 31 | 32 | /* 33 | Setting tab index to -1 removes the element from being tab to. This is 34 | required to allow keyboard navigation. 35 | */ 36 | tabIndex: -1, 37 | 38 | /* 39 | An array of row objects. Usually a hash where the keys are column names and 40 | the values are the rows's values. However, could be any object, since each 41 | column can define a function to return the column value given the row 42 | object. See `Ember.Table.ColumnDefinition.getCellContent`. 43 | */ 44 | content: null, 45 | 46 | /* 47 | An array of column definitions: see `Ember.Table.ColumnDefinition`. Allows 48 | each column to have its own configuration. 49 | */ 50 | columns: null, 51 | 52 | /* 53 | The number of fixed columns on the left side of the table. Fixed columns 54 | are always visible, even when the table is scrolled horizontally. 55 | */ 56 | numFixedColumns: 0, 57 | 58 | /* 59 | The number of footer rows in the table. Footer rows appear at the bottom of 60 | the table and are always visible. 61 | TODO(new-api): Rename to `numFooterRows` 62 | */ 63 | numFooterRow: 0, 64 | 65 | /* 66 | The row height in pixels. A consistent row height is necessary to calculate 67 | which rows are being shown, to enable lazy rendering. 68 | TODO: Currently must be kept in sync with the LESS file. 69 | */ 70 | rowHeight: 30, 71 | 72 | /* 73 | The minimum header height in pixels. Headers will grow in height if given 74 | more content than they can display. 75 | TODO: Currently must be kept in sync with the LESS file. 76 | */ 77 | minHeaderHeight: 30, 78 | 79 | /* 80 | The footer height in pixels. 81 | TODO: Currently must be kept in sync with the LESS file. 82 | */ 83 | footerHeight: 30, 84 | 85 | /* Enables or disables the header block. */ 86 | hasHeader: true, 87 | 88 | /* 89 | Enables or disables the footer block. 90 | TODO(new-api): Default to no 91 | */ 92 | hasFooter: true, 93 | 94 | /* 95 | If true, columns with `canAutoResize=true` (the default setting) will 96 | attempt to fill the width of the table when possible. After a column is 97 | manually resized, any other columns with `canAutoResize=true` will 98 | distribute the change in width between them. Once manually resized, a 99 | column will no longer automatically resize. 100 | */ 101 | forceFillColumns: false, 102 | 103 | /* 104 | Allow the columns to be rearranged by drag-and-drop. Only columns with 105 | `isSortable=true` (the default setting) will be affected. 106 | */ 107 | enableColumnReorder: true, 108 | 109 | /* Allow users to select the content of table cells. */ 110 | enableContentSelection: false, 111 | 112 | /* 113 | Sets which row selection behavior to follow. Possible values are 'none' 114 | (clicking on a row does nothing), 'single' (clicking on a row selects it 115 | and deselects other rows), and 'multiple' (multiple rows can be selected 116 | through ctrl/cmd-click or shift-click). 117 | */ 118 | selectionMode: 'single', 119 | 120 | /** 121 | * Overwrite this property to set fixed height on the table. 122 | * If you do not overwrite this property, the table will automatically 123 | * adjust to the height of the container. 124 | */ 125 | height: Ember.computed.alias('_tablesContainerHeight'), 126 | 127 | /** 128 | * This property controlls how tall the table can get before showing scrollbars. 129 | */ 130 | maxHeight: null, 131 | 132 | /* 133 | * --------------------------------------------------------------------------- 134 | * API - Outputs 135 | * --------------------------------------------------------------------------- 136 | */ 137 | 138 | /* 139 | An array of the rows currently selected. If `selectionMode` is set to 140 | 'single', the array will contain either one or zero elements. 141 | */ 142 | selection: Ember.computed('_selection.[]', 'selectionMode', function(key, val) { 143 | var content, _i, _len, _ref, _ref1; 144 | if (arguments.length > 1 && val) { 145 | if (this.get('selectionMode') === 'single') { 146 | this.get('persistedSelection').clear(); 147 | this.get('persistedSelection').addObject(this.findRow(val)); 148 | } else { 149 | this.get('persistedSelection').clear(); 150 | for (_i = 0, _len = val.length; _i < _len; _i++) { 151 | content = val[_i]; 152 | this.get('persistedSelection').addObject(this.findRow(content)); 153 | } 154 | } 155 | this.get('rangeSelection').clear(); 156 | } 157 | if (this.get('selectionMode') === 'single') { 158 | return (_ref = this.get('_selection')) != null ? (_ref1 = _ref[0]) != null ? _ref1.get('content') : void 0 : void 0; 159 | } else { 160 | return this.get('_selection').toArray().map(function(row) { 161 | return row.get('content'); 162 | }); 163 | } 164 | }), 165 | 166 | /* 167 | * --------------------------------------------------------------------------- 168 | * Internal properties 169 | * --------------------------------------------------------------------------- 170 | */ 171 | init: function() { 172 | this._super(); 173 | if (!$.ui) { 174 | throw 'Missing dependency: jquery-ui'; 175 | } 176 | if (!$().mousewheel) { 177 | throw 'Missing dependency: jquery-mousewheel'; 178 | } 179 | if (!$().antiscroll) { 180 | throw 'Missing dependency: antiscroll.js'; 181 | } 182 | }, 183 | 184 | /* TODO: Document */ 185 | actions: { 186 | addColumn: Ember.K, 187 | sortByColumn: function(column) { 188 | this.sendAction('sortByColumn', column); 189 | } 190 | }, 191 | 192 | /* 193 | TODO(new-api): eliminate view alias 194 | specify the view class to use for rendering the table rows 195 | */ 196 | tableRowView: 'table-row', 197 | tableRowViewClass: Ember.computed.alias('tableRowView'), 198 | 199 | onColumnSort: function(column, newIndex) { 200 | var columns; 201 | columns = this.get('tableColumns'); 202 | columns.removeObject(column); 203 | return columns.insertAt(newIndex, column); 204 | }, 205 | 206 | /* An array of Ember.Table.Row computed based on `content` */ 207 | bodyContent: Ember.computed('content', function() { 208 | return RowArrayController.create({ 209 | target: this, 210 | parentController: this, 211 | container: this.get('container'), 212 | itemController: RowDefinition, 213 | content: this.get('content') 214 | }); 215 | }), 216 | 217 | /* An array of Ember.Table.Row */ 218 | footerContent: Ember.computed(function(key, value) { 219 | if (value) { 220 | return value; 221 | } else { 222 | return Ember.A(); 223 | } 224 | }), 225 | 226 | fixedColumns: Ember.computed('columns.@each', 'numFixedColumns', function() { 227 | var columns, numFixedColumns; 228 | columns = this.get('columns'); 229 | if (!columns) { 230 | return Ember.A(); 231 | } 232 | numFixedColumns = this.get('numFixedColumns') || 0; 233 | columns = columns.slice(0, numFixedColumns) || []; 234 | this.prepareTableColumns(columns); 235 | return columns; 236 | }), 237 | 238 | tableColumns: Ember.computed('columns.@each', 'numFixedColumns', function() { 239 | var columns, numFixedColumns; 240 | columns = this.get('columns'); 241 | if (!columns) { 242 | return Ember.A(); 243 | } 244 | numFixedColumns = this.get('numFixedColumns') || 0; 245 | columns = columns.slice(numFixedColumns, columns.get('length')) || []; 246 | this.prepareTableColumns(columns); 247 | return columns; 248 | }), 249 | 250 | prepareTableColumns: function(columns) { 251 | columns.forEach((column)=>{ 252 | set(column, 'controller', this); 253 | }); 254 | }, 255 | 256 | /* 257 | * --------------------------------------------------------------------------- 258 | * View concerns 259 | * --------------------------------------------------------------------------- 260 | */ 261 | didInsertElement: function() { 262 | this._super(); 263 | this.set('_tableScrollTop', 0); 264 | this.updateLayout(); 265 | }, 266 | 267 | addResizeListener: on('didInsertElement', function() { 268 | addResizeListener(this.get('element'), Ember.run.bind(this, this.elementSizeDidChange)); 269 | }), 270 | 271 | elementSizeDidChange: function() { 272 | if ((this.get('_state') || this.get('state')) !== 'inDOM') { 273 | return; 274 | } 275 | this.updateLayout(); 276 | }, 277 | 278 | updateLayout() { 279 | Ember.run.scheduleOnce('afterRender', this, function updateWidthAfterRender(){ 280 | // setting the height on the table might cause the scrollbar to become visible 281 | // this will effect table width if parent's width is set to 100% 282 | // to ensure that we get parent's width after scrollbar, we need to wait for 283 | // the height to be set to the DOM element before taking parent's width 284 | this.updateWidth(); 285 | if (this.get('forceFillColumns')) { 286 | this.doForceFillColumns(); 287 | } 288 | }); 289 | // this needs to be called after updateWidthAfterRender is schedule to make 290 | // sure that updateWidthAfterRender is enqueued before updateHeight's 291 | // antiscroll rebuild 292 | this.updateHeight(); 293 | }, 294 | 295 | updateHeight() { 296 | let maxHeight = this.get('maxHeight'); 297 | if (maxHeight == null) { 298 | this.set('_height', this.$().parent().outerHeight()); 299 | } 300 | this.scheduleAntiscrollRebuild(); 301 | }, 302 | 303 | updateWidth() { 304 | this.set('_width', this.$().parent().outerWidth()); 305 | this.scheduleAntiscrollRebuild(); 306 | }, 307 | 308 | scheduleAntiscrollRebuild() { 309 | Ember.run.scheduleOnce('afterRender', this, this.rebuildAntiscroll); 310 | }, 311 | 312 | rebuildAntiscroll() { 313 | if (this._state !== 'inDOM'){ return; } 314 | 315 | this.$('.antiscroll-wrap').antiscroll(); 316 | }, 317 | 318 | doForceFillColumns: function() { 319 | /* Expand the columns if there's extra space */ 320 | var additionWidthPerColumn, availableContentWidth, columnsToResize, contentWidth, fixedColumnsWidth, remainingWidth, tableColumns, totalWidth; 321 | totalWidth = this.get('_width'); 322 | fixedColumnsWidth = this.get('_fixedColumnsWidth'); 323 | tableColumns = this.get('tableColumns'); 324 | contentWidth = this._getTotalWidth(tableColumns); 325 | availableContentWidth = totalWidth - fixedColumnsWidth; 326 | remainingWidth = availableContentWidth - contentWidth; 327 | columnsToResize = tableColumns.filterProperty('canAutoResize'); 328 | additionWidthPerColumn = Math.floor(remainingWidth / columnsToResize.length); 329 | return columnsToResize.forEach(function(column) { 330 | var columnWidth = column.get('columnWidth') + additionWidthPerColumn; 331 | return column.set('columnWidth', columnWidth); 332 | }); 333 | }, 334 | 335 | onBodyContentLengthDidChange: Ember.observer('bodyContent.[]', function() { 336 | this.updateHeight(); 337 | }), 338 | 339 | /* 340 | * --------------------------------------------------------------------------- 341 | * Private variables 342 | * --------------------------------------------------------------------------- 343 | */ 344 | _tableScrollTop: 0, 345 | _tableScrollLeft: 0, 346 | _width: null, 347 | _height: null, 348 | _contentHeaderHeight: null, 349 | 350 | _hasVerticalScrollbar: Ember.computed( 351 | '_height', 352 | 'contentHeight', function() { 353 | let height = this.get('_height'); 354 | let contentHeight = this.get('contentHeight'); 355 | if (height < contentHeight) { 356 | return true; 357 | } else { 358 | return false; 359 | } 360 | }), 361 | 362 | contentHeight: Ember.computed( 363 | '_headerHeight', 364 | '_tableContentHeight', 365 | '_footerHeight', function() { 366 | return this.get('_headerHeight') + this.get('_tableContentHeight') + this.get('_footerHeight'); 367 | }), 368 | 369 | _hasHorizontalScrollbar: Ember.computed( 370 | '_tableColumnsWidth', 371 | '_width', 372 | '_fixedColumnsWidth', function() { 373 | let contentWidth = this.get('_tableColumnsWidth'); 374 | let tableWidth = this.get('_width') - this.get('_fixedColumnsWidth'); 375 | if (contentWidth > tableWidth) { 376 | return true; 377 | } else { 378 | return false; 379 | } 380 | }), 381 | 382 | /* tables-container height adjusts to the content height */ 383 | _tablesContainerHeight: Ember.computed( 384 | 'maxHeight', 385 | '_height', 386 | 'contentHeight', function() { 387 | const maxHeight = this.get('maxHeight'); 388 | const parentHeight = this.get('_height'); 389 | const contentHeight = this.get('contentHeight'); 390 | 391 | let limit = parentHeight; 392 | if (parentHeight == null) { 393 | limit = maxHeight; 394 | } 395 | 396 | if (contentHeight < limit) { 397 | return contentHeight; 398 | } else { 399 | return limit; 400 | } 401 | }), 402 | 403 | /* Actual width of the fixed columns */ 404 | _fixedColumnsWidth: Ember.computed('fixedColumns.@each.columnWidth', function() { 405 | return this._getTotalWidth(this.get('fixedColumns')); 406 | }), 407 | 408 | /* Actual width of the (non-fixed) columns */ 409 | _tableColumnsWidth: Ember.computed( 410 | 'tableColumns.@each.columnWidth', 411 | '_width', 412 | '_fixedColumnsWidth', function() { 413 | /* 414 | Hack: We add 3px padding to the right of the table content so that we can 415 | reorder into the last column. 416 | */ 417 | let contentWidth = (this._getTotalWidth(this.get('tableColumns'))) + 3; 418 | let availableWidth = this.get('_width') - this.get('_fixedColumnsWidth'); 419 | if (contentWidth > availableWidth) { 420 | return contentWidth; 421 | } else { 422 | return availableWidth; 423 | } 424 | }), 425 | 426 | _rowWidth: Ember.computed( 427 | '_fixedColumnsWidth', 428 | '_tableColumnsWidth', 429 | '_tableContainerWidth', function() { 430 | let columnsWidth = this.get('_tableColumnsWidth'); 431 | let nonFixedTableWidth = this.get('_tableContainerWidth') - this.get('_fixedColumnsWidth'); 432 | if (columnsWidth < nonFixedTableWidth) { 433 | return nonFixedTableWidth; 434 | } 435 | return columnsWidth; 436 | }), 437 | 438 | /* Dynamic header height that adjusts according to the header content height */ 439 | _headerHeight: Ember.computed('_contentHeaderHeight', 'minHeaderHeight', function() { 440 | let minHeight = this.get('minHeaderHeight'); 441 | let contentHeaderHeight = this.get('_contentHeaderHeight'); 442 | if (contentHeaderHeight < minHeight) { 443 | return minHeight; 444 | } else { 445 | return contentHeaderHeight; 446 | } 447 | }), 448 | 449 | /* Dynamic footer height that adjusts according to the footer content height */ 450 | _footerHeight: Ember.computed('footerHeight', 'hasFooter', function() { 451 | if (this.get('hasFooter')) { 452 | return this.get('footerHeight'); 453 | } else { 454 | return 0; 455 | } 456 | }), 457 | 458 | _bodyHeight: Ember.computed( 459 | '_tablesContainerHeight', 460 | '_hasHorizontalScrollbar', 461 | '_headerHeight', 462 | 'footerHeight', 463 | 'hasHeader', 464 | 'hasFooter', function() { 465 | let bodyHeight = this.get('_tablesContainerHeight'); 466 | if (this.get('hasHeader')) { 467 | bodyHeight -= this.get('_headerHeight'); 468 | } 469 | if (this.get('hasFooter')) { 470 | bodyHeight -= this.get('footerHeight'); 471 | } 472 | return bodyHeight; 473 | }), 474 | 475 | _tableBlockWidth: Ember.computed('_width', '_fixedColumnsWidth', function() { 476 | return this.get('_width') - this.get('_fixedColumnsWidth'); 477 | }), 478 | 479 | _fixedBlockWidth: Ember.computed.alias('_fixedColumnsWidth'), 480 | 481 | _tableContentHeight: Ember.computed('rowHeight', 'bodyContent.[]', function() { 482 | return this.get('rowHeight') * this.get('bodyContent.length'); 483 | }), 484 | 485 | _tableContainerWidth: Ember.computed('_width', function() { 486 | return this.get('_width'); 487 | }), 488 | 489 | _scrollContainerWidth: Ember.computed( 490 | '_width', 491 | '_fixedColumnsWidth', function() { 492 | return this.get('_width') - this.get('_fixedColumnsWidth'); 493 | }), 494 | 495 | _numItemsShowing: Ember.computed( 496 | '_bodyHeight', 497 | 'rowHeight', function() { 498 | return Math.floor(this.get('_bodyHeight') / this.get('rowHeight')); 499 | }), 500 | 501 | _startIndex: Ember.computed( 502 | 'bodyContent.[]', 503 | '_numItemsShowing', 504 | 'rowHeight', 505 | '_tableScrollTop', function() { 506 | let numContent = this.get('bodyContent.length'); 507 | let numViews = this.get('_numItemsShowing'); 508 | let rowHeight = this.get('rowHeight'); 509 | let scrollTop = this.get('_tableScrollTop'); 510 | let index = Math.floor(scrollTop / rowHeight); 511 | 512 | /* adjust start index so that end index doesn't exceed content length */ 513 | if (index + numViews >= numContent) { 514 | index = numContent - numViews; 515 | } 516 | if (index < 0) { 517 | return 0; 518 | } else { 519 | return index; 520 | } 521 | }), 522 | 523 | _getTotalWidth: function(columns, columnWidthPath) { 524 | if (columnWidthPath == null) { 525 | columnWidthPath = 'columnWidth'; 526 | } 527 | if (!columns) { 528 | return 0; 529 | } 530 | let widths = columns.map((column)=>{ 531 | return get(column, columnWidthPath); 532 | }); 533 | return widths.reduce((function(total, w) { 534 | return total + w; 535 | }), 0); 536 | }, 537 | 538 | /* 539 | * --------------------------------------------------------------------------- 540 | * Selection 541 | * TODO: Make private or reorganize into a new section 542 | * --------------------------------------------------------------------------- 543 | */ 544 | isSelected: function(row) { 545 | return this.get('_selection').contains(row); 546 | }, 547 | 548 | setSelected: function(row, val) { 549 | this.persistSelection(); 550 | if (val) { 551 | return this.get('persistedSelection').addObject(row); 552 | } else { 553 | return this.get('persistedSelection').removeObject(row); 554 | } 555 | }, 556 | 557 | /* 558 | rows that were selected directly or as part of a previous 559 | range selection (shift-click) 560 | */ 561 | persistedSelection: Ember.computed(function() { 562 | return Ember.ArrayProxy.createWithMixins(Ember.MutableArray, {content:[]}); 563 | }), 564 | 565 | /* rows that are part of the currently editable range selection */ 566 | rangeSelection: Ember.computed(function() { 567 | return Ember.ArrayProxy.createWithMixins(Ember.MutableArray, {content:[]}); 568 | }), 569 | 570 | _selection: Ember.computed('persistedSelection.[]', 'rangeSelection.[]', function() { 571 | return this.get('persistedSelection').toArray().copy().addObjects(this.get('rangeSelection')); 572 | }), 573 | 574 | click: function(event) { 575 | var curIndex, lastIndex, maxIndex, minIndex, row; 576 | row = this.getRowForEvent(event); 577 | if (!row) { 578 | return; 579 | } 580 | if (this.get('selectionMode') === 'none') { 581 | return; 582 | } 583 | if (this.get('selectionMode') === 'single') { 584 | this.get('persistedSelection').clear(); 585 | this.get('persistedSelection').addObject(row); 586 | } else { 587 | if (event.shiftKey) { 588 | this.get('rangeSelection').clear(); 589 | lastIndex = this.rowIndex(this.get('lastSelected')); 590 | curIndex = this.rowIndex(this.getRowForEvent(event)); 591 | minIndex = Math.min(lastIndex, curIndex); 592 | maxIndex = Math.max(lastIndex, curIndex); 593 | this.get('rangeSelection').addObjects(this.get('bodyContent').slice(minIndex, maxIndex + 1)); 594 | } else { 595 | if (!event.ctrlKey && !event.metaKey) { 596 | this.get('persistedSelection').clear(); 597 | this.get('rangeSelection').clear(); 598 | } else { 599 | this.persistSelection(); 600 | } 601 | if (this.get('persistedSelection').contains(row)) { 602 | this.get('persistedSelection').removeObject(row); 603 | } else { 604 | this.get('persistedSelection').addObject(row); 605 | } 606 | this.set('lastSelected', row); 607 | } 608 | } 609 | }, 610 | 611 | 612 | keyDown: function (e) { 613 | if (e.keyCode === 38) { 614 | // arrow up 615 | e.preventDefault(); 616 | this.selectWithArrow('up', e.shiftKey); 617 | } else if (e.keyCode === 40) { 618 | // arrow down 619 | e.preventDefault(); 620 | this.selectWithArrow('down', e.shiftKey); 621 | } else if (e.keyCode === 65) { 622 | // 65 is a 623 | if (e.ctrlKey || e.metaKey) { 624 | e.preventDefault(); 625 | this.selectAll(); 626 | } 627 | } 628 | }, 629 | 630 | selectWithArrow: function (direction) { 631 | var rowIndex, futureRowIndex, futureSelection; 632 | if (this.get('selectionMode') === 'none') { 633 | return; 634 | } 635 | 636 | // Get the appropriate row index 637 | if (this.get('selectionMode') === 'single') { 638 | rowIndex = this.rowIndex(this.get('persistedSelection.lastObject')); 639 | } else { 640 | rowIndex = this.rowIndex(this.get('lastSelected')); 641 | } 642 | 643 | // Calculate new index, defaulting to current index for edge values 644 | if (direction === 'up' && rowIndex !== 0) { 645 | futureRowIndex = rowIndex - 1; 646 | } else if (direction === 'down' && rowIndex !== this.get('bodyContent.length') - 1) { 647 | futureRowIndex = rowIndex + 1; 648 | } else { 649 | futureRowIndex = rowIndex; 650 | } 651 | 652 | // Clear current selection 653 | this.get('persistedSelection').clear(); 654 | this.get('rangeSelection').clear(); 655 | 656 | // Get new row and persist it. Set lastSelected for book-keeping 657 | futureSelection = this.get('bodyContent').objectAt(futureRowIndex); 658 | this.get('persistedSelection').addObject(futureSelection); 659 | this.set('lastSelected', futureSelection); 660 | }, 661 | 662 | selectAll: function() { 663 | if (this.get('selectionMode') !== 'multiple') { 664 | return; 665 | } 666 | 667 | // Clear current selection 668 | this.get('persistedSelection').clear(); 669 | this.get('rangeSelection').clear(); 670 | this.set('lastSelected', null); 671 | 672 | // Set new selection 673 | this.get('rangeSelection').addObjects(this.get('bodyContent')); 674 | this.set('lastSelected', this.get('bodyContent.lastObject')); 675 | }, 676 | 677 | findRow: function(content) { 678 | var row, _i, _len, _ref; 679 | _ref = this.get('bodyContent'); 680 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 681 | row = _ref[_i]; 682 | if (row.get('content') === content) { 683 | return row; 684 | } 685 | } 686 | }, 687 | 688 | rowIndex: function(row) { 689 | var _ref; 690 | return (_ref = this.get('bodyContent')) != null ? _ref.indexOf(row) : void 0; 691 | }, 692 | 693 | persistSelection: function() { 694 | this.get('persistedSelection').addObjects(this.get('rangeSelection')); 695 | return this.get('rangeSelection').clear(); 696 | }, 697 | 698 | getRowForEvent: function(event) { 699 | var $rowView, view; 700 | $rowView = $(event.target).parents('.ember-table-table-row'); 701 | view = Ember.View.views[$rowView.attr('id')]; 702 | if (view) { 703 | return view.get('row'); 704 | } 705 | } 706 | }); 707 | 708 | 709 | /* Ember.Handlebars.helper('ember-table', EmberTableComponent) */ 710 | 711 | export default EmberTableComponent; 712 | -------------------------------------------------------------------------------- /addon/controllers/row.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | /* jshint unused:false */ 4 | 5 | var RowArrayController; 6 | 7 | RowArrayController = Ember.ArrayController.extend({ 8 | itemController: null, 9 | content: null, 10 | rowContent: Ember.computed(function() { 11 | return []; 12 | }), 13 | controllerAt: function(idx, object, controllerClass) { 14 | var container, subController, subControllers; 15 | container = this.get('container'); 16 | subControllers = this.get('_subControllers'); 17 | subController = subControllers[idx]; 18 | if (subController) { 19 | return subController; 20 | } 21 | subController = this.get('itemController').create({ 22 | target: this, 23 | parentController: this.get('parentController') || this, 24 | content: object 25 | }); 26 | subControllers[idx] = subController; 27 | return subController; 28 | } 29 | }); 30 | 31 | export default RowArrayController; 32 | -------------------------------------------------------------------------------- /addon/mixins/mouse-wheel-handler-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var MouseWheelHandlerMixin; 4 | 5 | MouseWheelHandlerMixin = Ember.Mixin.create({ 6 | onMouseWheel: Ember.K, 7 | didInsertElement: function() { 8 | this._super(); 9 | return this.$().bind('mousewheel', (function(_this) { 10 | return function(event, delta, deltaX, deltaY) { 11 | return Ember.run(_this, _this.onMouseWheel, event, delta, deltaX, deltaY); 12 | }; 13 | })(this)); 14 | }, 15 | willDestroyElement: function() { 16 | var _ref; 17 | if ((_ref = this.$()) != null) { 18 | _ref.unbind('mousewheel'); 19 | } 20 | return this._super(); 21 | } 22 | }); 23 | 24 | export default MouseWheelHandlerMixin; -------------------------------------------------------------------------------- /addon/mixins/scroll-handler-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var ScrollHandlerMixin; 4 | 5 | ScrollHandlerMixin = Ember.Mixin.create({ 6 | onScroll: Ember.K, 7 | scrollElementSelector: '', 8 | didInsertElement: function() { 9 | this._super(); 10 | return this.$(this.get('scrollElementSelector')).bind('scroll', (function(_this) { 11 | return function(event) { 12 | return Ember.run(_this, _this.onScroll, event); 13 | }; 14 | })(this)); 15 | }, 16 | willDestroyElement: function() { 17 | var _ref; 18 | if ((_ref = this.$(this.get('scrollElementSelector'))) != null) { 19 | _ref.unbind('scroll'); 20 | } 21 | return this._super(); 22 | } 23 | }); 24 | 25 | export default ScrollHandlerMixin; -------------------------------------------------------------------------------- /addon/mixins/show-horizontal-scroll-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | /* global $ */ 4 | 5 | /* HACK: We want the horizontal scroll to show on mouse enter and leave. */ 6 | var ShowHorizontalScrollMixin; 7 | 8 | ShowHorizontalScrollMixin = Ember.Mixin.create({ 9 | mouseEnter: function(event) { 10 | var $horizontalScroll, $tablesContainer; 11 | $tablesContainer = $(event.target).parents('.ember-table-tables-container'); 12 | $horizontalScroll = $tablesContainer.find('.antiscroll-scrollbar-horizontal'); 13 | return $horizontalScroll.addClass('antiscroll-scrollbar-shown'); 14 | }, 15 | mouseLeave: function(event) { 16 | var $horizontalScroll, $tablesContainer; 17 | $tablesContainer = $(event.target).parents('.ember-table-tables-container'); 18 | $horizontalScroll = $tablesContainer.find('.antiscroll-scrollbar-horizontal'); 19 | return $horizontalScroll.removeClass('antiscroll-scrollbar-shown'); 20 | } 21 | }); 22 | 23 | export default ShowHorizontalScrollMixin; -------------------------------------------------------------------------------- /addon/mixins/style-bindings-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Mixin.create({ 4 | concatenatedProperties: ['styleBindings'], 5 | attributeBindings: ['style'], 6 | unitType: 'px', 7 | createStyleString: function(styleName, property) { 8 | var value; 9 | value = this.get(property); 10 | if (Ember.isNone(value)) { 11 | return; 12 | } 13 | if (Ember.typeOf(value) === 'number') { 14 | value = value + this.get('unitType'); 15 | } 16 | return Ember.String.dasherize("" + styleName) + ":" + value + ";"; 17 | }, 18 | applyStyleBindings: function() { 19 | var lookup, properties, styleBindings, styleComputed, styles, 20 | _this = this; 21 | styleBindings = this.styleBindings; 22 | if (!styleBindings) { 23 | return; 24 | } 25 | lookup = {}; 26 | styleBindings.forEach(function(binding) { 27 | var property, style, tmp; 28 | tmp = binding.split(':'); 29 | property = tmp[0]; 30 | style = tmp[1]; 31 | lookup[style || property] = property; 32 | }); 33 | styles = Ember.keys(lookup); 34 | properties = styles.map(function(style) { 35 | return lookup[style]; 36 | }); 37 | styleComputed = Ember.computed(function() { 38 | var styleString, styleTokens; 39 | styleTokens = styles.map(function(style) { 40 | return _this.createStyleString(style, lookup[style]); 41 | }); 42 | styleString = styleTokens.join(''); 43 | if (styleString.length !== 0) { 44 | return styleString.htmlSafe(); 45 | } 46 | }); 47 | styleComputed.property.apply(styleComputed, properties); 48 | return Ember.defineProperty(this, 'style', styleComputed); 49 | }, 50 | init: function() { 51 | this.applyStyleBindings(); 52 | return this._super(); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /addon/mixins/touch-move-handler-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var TouchMoveHandlerMixin; 4 | 5 | TouchMoveHandlerMixin = Ember.Mixin.create({ 6 | onTouchMove: Ember.K, 7 | didInsertElement: function() { 8 | var startX, startY; 9 | this._super(); 10 | startX = startY = 0; 11 | this.$().bind('touchstart', function(event) { 12 | startX = event.originalEvent.targetTouches[0].pageX; 13 | startY = event.originalEvent.targetTouches[0].pageY; 14 | }); 15 | return this.$().bind('touchmove', (function(_this) { 16 | return function(event) { 17 | var deltaX, deltaY, newX, newY; 18 | newX = event.originalEvent.targetTouches[0].pageX; 19 | newY = event.originalEvent.targetTouches[0].pageY; 20 | deltaX = -(newX - startX); 21 | deltaY = -(newY - startY); 22 | Ember.run(_this, _this.onTouchMove, event, deltaX, deltaY); 23 | startX = newX; 24 | startY = newY; 25 | }; 26 | })(this)); 27 | }, 28 | willDestroy: function() { 29 | var _ref; 30 | if ((_ref = this.$()) != null) { 31 | _ref.unbind('touchmove'); 32 | } 33 | return this._super(); 34 | } 35 | }); 36 | 37 | export default TouchMoveHandlerMixin; -------------------------------------------------------------------------------- /addon/resize-detection.js: -------------------------------------------------------------------------------- 1 | // Private / Helper Methods 2 | var attachEvent = document.attachEvent; 3 | var isIE = navigator.userAgent.match(/Trident/); 4 | var requestFrame = (function(){ 5 | var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || 6 | function(fn){ return window.setTimeout(fn, 20); }; 7 | return function(fn){ return raf(fn); }; 8 | })(); 9 | 10 | var cancelFrame = (function() { 11 | var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || 12 | window.clearTimeout; 13 | return function(id){ return cancel(id); }; 14 | })(); 15 | 16 | function resizeListener(e) { 17 | var win = e.target || e.srcElement; 18 | if (win.__resizeRAF__) { 19 | cancelFrame(win.__resizeRAF__); 20 | } 21 | win.__resizeRAF__ = requestFrame(function(){ 22 | var trigger = win.__resizeTrigger__; 23 | if (!trigger) { return; } 24 | (trigger.__resizeListeners__ || []).forEach(function(fn){ 25 | fn.call(trigger, e); 26 | }); 27 | }); 28 | } 29 | 30 | function objectLoad() { 31 | this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__; 32 | this.contentDocument.defaultView.addEventListener('resize', resizeListener); 33 | } 34 | 35 | // Public methods 36 | 37 | var addResizeListener = function(element, fn) { 38 | if (!element.__resizeListeners__) { 39 | element.__resizeListeners__ = []; 40 | if (attachEvent) { 41 | element.__resizeTrigger__ = element; 42 | element.attachEvent('onresize', resizeListener); 43 | } 44 | else { 45 | if (getComputedStyle(element).position === 'static') { 46 | element.style.position = 'relative'; 47 | } 48 | var obj = element.__resizeTrigger__ = document.createElement('object'); 49 | obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;'); 50 | obj.__resizeElement__ = element; 51 | obj.onload = objectLoad; 52 | obj.type = 'text/html'; 53 | if (isIE) { 54 | element.appendChild(obj); 55 | } 56 | obj.data = 'about:blank'; 57 | if (!isIE) { 58 | element.appendChild(obj); 59 | } 60 | } 61 | } 62 | element.__resizeListeners__.push(fn); 63 | }; 64 | 65 | var removeResizeListener = function(element, fn) { 66 | element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); 67 | if (!element.__resizeListeners__.length) { 68 | if (attachEvent) { 69 | element.detachEvent('onresize', resizeListener); 70 | } else { 71 | element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener); 72 | element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__); 73 | } 74 | } 75 | }; 76 | 77 | export { addResizeListener, removeResizeListener }; 78 | -------------------------------------------------------------------------------- /addon/row-definition.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var RowDefinition = Ember.ObjectProxy.extend({ 4 | content: null, 5 | isShowing: true, 6 | isHovered: false, 7 | isSelected: Ember.computed('parentController._selection.[]', function(key, val) { 8 | if (arguments.length > 1) { 9 | this.get('parentController').setSelected(this, val); 10 | } 11 | return this.get('parentController').isSelected(this); 12 | }) 13 | }); 14 | 15 | export default RowDefinition; 16 | -------------------------------------------------------------------------------- /addon/views/body-table-container.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import MouseWheelHandlerMixin from '../mixins/mouse-wheel-handler-mixin'; 3 | import TouchMoveHandlerMixin from '../mixins/touch-move-handler-mixin'; 4 | import ScrollHandlerMixin from '../mixins/scroll-handler-mixin'; 5 | import ShowHorizontalScrollMixin from '../mixins/show-horizontal-scroll-mixin'; 6 | import TableContainer from '../views/table-container'; 7 | 8 | export default TableContainer.extend(MouseWheelHandlerMixin, TouchMoveHandlerMixin, ScrollHandlerMixin, ShowHorizontalScrollMixin, { 9 | templateName: 'body-table-container', 10 | classNames: ['ember-table-table-container', 'ember-table-body-container', 'antiscroll-wrap'], 11 | height: Ember.computed.alias('controller._bodyHeight'), 12 | width: Ember.computed.alias('controller._width'), 13 | scrollTop: Ember.computed.alias('controller._tableScrollTop'), 14 | scrollLeft: Ember.computed.alias('controller._tableScrollLeft'), 15 | scrollElementSelector: '.antiscroll-inner', 16 | 17 | /* `event` here is a jQuery event */ 18 | onScroll: function(event) { 19 | this.set('scrollTop', event.target.scrollTop); 20 | return event.preventDefault(); 21 | }, 22 | 23 | /* `event` here is a jQuery event */ 24 | onMouseWheel: function(event, delta, deltaX, deltaY) { 25 | var scrollLeft; 26 | if ((Math.abs(deltaX) > Math.abs(deltaY)) === false) { 27 | return; 28 | } 29 | scrollLeft = this.$('.ember-table-right-table-block').scrollLeft() + deltaX; 30 | this.set('scrollLeft', scrollLeft); 31 | return event.preventDefault(); 32 | }, 33 | onTouchMove: function(event, deltaX, deltaY) { 34 | var scrollLeft; 35 | if ((Math.abs(deltaX) > Math.abs(deltaY)) === false) { 36 | return; 37 | } 38 | scrollLeft = this.$('.ember-table-right-table-block').scrollLeft() + deltaX; 39 | this.set('scrollLeft', scrollLeft); 40 | return event.preventDefault(); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /addon/views/column-sortable-indicator.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | var ColumnSortableIndicator; 5 | 6 | ColumnSortableIndicator = Ember.View.extend(StyleBindingsMixin, { 7 | classNames: 'ember-table-column-sortable-indicator', 8 | classNameBindings: 'controller._isShowingSortableIndicator:active', 9 | styleBindings: ['left', 'height'], 10 | left: Ember.computed.alias('controller._sortableIndicatorLeft'), 11 | height: Ember.computed.alias('controller._height') 12 | }); 13 | 14 | export default ColumnSortableIndicator; -------------------------------------------------------------------------------- /addon/views/footer-table-container.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import MouseWheelHandlerMixin from '../mixins/mouse-wheel-handler-mixin'; 3 | import TouchMoveHandlerMixin from '../mixins/touch-move-handler-mixin'; 4 | import ShowHorizontalScrollMixin from '../mixins/show-horizontal-scroll-mixin'; 5 | import TableContainer from '../views/table-container'; 6 | 7 | /* jshint unused:false */ 8 | const {computed} = Ember; 9 | 10 | export default TableContainer.extend(MouseWheelHandlerMixin, TouchMoveHandlerMixin, ShowHorizontalScrollMixin, { 11 | templateName: 'footer-container', 12 | classNames: ['ember-table-table-container', 'ember-table-fixed-table-container', 'ember-table-footer-container'], 13 | styleBindings: 'top', 14 | height: Ember.computed.alias('controller.footerHeight'), 15 | width: Ember.computed.alias('controller._tableContainerWidth'), 16 | scrollLeft: Ember.computed.alias('controller._tableScrollLeft'), 17 | top: computed( 18 | 'controller._bodyHeight', 19 | 'controller._headerHeight', 20 | 'controller._tableContentHeight', function() { 21 | let headerHeight = this.get('controller._headerHeight'); 22 | let contentHeight = this.get('controller._tableContentHeight') + headerHeight; 23 | let bodyHeight = this.get('controller._bodyHeight') + headerHeight; 24 | if (contentHeight < bodyHeight) { 25 | return contentHeight; 26 | } else { 27 | return bodyHeight; 28 | } 29 | }), 30 | onMouseWheel: function(event, delta, deltaX, deltaY) { 31 | var scrollLeft; 32 | scrollLeft = this.$('.ember-table-right-table-block').scrollLeft() + deltaX; 33 | this.set('scrollLeft', scrollLeft); 34 | return event.preventDefault(); 35 | }, 36 | onTouchMove: function(event, deltaX, deltaY) { 37 | var scrollLeft; 38 | scrollLeft = this.$('.ember-table-right-table-block').scrollLeft() + deltaX; 39 | this.set('scrollLeft', scrollLeft); 40 | return event.preventDefault(); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /addon/views/header-block.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import TableBlock from '../views/table-block'; 3 | 4 | const {computed} = Ember; 5 | 6 | export default TableBlock.extend({ 7 | classNames: ['ember-table-header-block'], 8 | itemViewClass: 'header-row', 9 | // TODO(taras): this should probably be computed.oneWay 10 | content: computed('columns', function() { 11 | return [this.get('columns')]; 12 | }) 13 | }); 14 | -------------------------------------------------------------------------------- /addon/views/header-cell.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | /* global jQuery, $ */ 5 | 6 | const {computed} = Ember; 7 | 8 | let HeaderCell = Ember.View.extend(StyleBindingsMixin, { 9 | 10 | /* 11 | * --------------------------------------------------------------------------- 12 | * API - Inputs 13 | * --------------------------------------------------------------------------- 14 | */ 15 | 16 | /* 17 | TODO: Doc 18 | */ 19 | templateName: 'header-cell', 20 | classNames: ['ember-table-cell', 'ember-table-header-cell'], 21 | classNameBindings: ['column.isSortable:sortable', 'column.textAlign'], 22 | styleBindings: ['width', 'height'], 23 | 24 | /* 25 | * --------------------------------------------------------------------------- 26 | * Internal properties 27 | * --------------------------------------------------------------------------- 28 | */ 29 | column: Ember.computed.alias('content'), 30 | width: Ember.computed.alias('column.columnWidth'), 31 | height: computed('controller._headerHeight', function() { 32 | return this.get('controller._headerHeight'); 33 | }), 34 | 35 | /* jQuery UI resizable option */ 36 | resizableOption: Ember.computed(function() { 37 | return { 38 | handles: 'e', 39 | minHeight: 40, 40 | minWidth: this.get('column.minWidth') || 100, 41 | maxWidth: this.get('column.maxWidth') || 500, 42 | grid: this.get('column.snapGrid'), 43 | resize: jQuery.proxy(this.onColumnResize, this), 44 | stop: jQuery.proxy(this.onColumnResize, this) 45 | }; 46 | }), 47 | didInsertElement: function() { 48 | this.elementSizeDidChange(); 49 | if (this.get('column.isResizable')) { 50 | this.$().resizable(this.get('resizableOption')); 51 | this._resizableWidget = this.$().resizable('widget'); 52 | } 53 | }, 54 | 55 | /* `event` here is a jQuery event */ 56 | onColumnResize: function(event, ui) { 57 | this.elementSizeDidChange(); 58 | 59 | /* 60 | Special case for force-filled columns: if this is the last column you 61 | resize (or the only column), then it will be reset to before the resize 62 | to preserve the table's force-fill property. 63 | */ 64 | if (this.get('controller.forceFillColumns') && this.get('controller.columns').filterProperty('canAutoResize').length > 1) { 65 | this.set('column.canAutoResize', false); 66 | } 67 | return this.get("column").resize(ui.size.width); 68 | }, 69 | elementSizeDidChange: function() { 70 | var maxHeight; 71 | maxHeight = 0; 72 | 73 | /* TODO(Louis): This seems bad... */ 74 | $('.ember-table-header-block .ember-table-content').each(function() { 75 | var thisHeight; 76 | thisHeight = $(this).outerHeight(); 77 | if (thisHeight > maxHeight) { 78 | return maxHeight = thisHeight; 79 | } 80 | }); 81 | this.set('controller._contentHeaderHeight', maxHeight); 82 | } 83 | }); 84 | 85 | export default HeaderCell; 86 | -------------------------------------------------------------------------------- /addon/views/header-row.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | /* global jQuery */ 5 | 6 | /* jshint unused:false */ 7 | 8 | /* 9 | We hacked this. There is an inconsistency at the level in which we are 10 | handling scroll event... 11 | */ 12 | export default Ember.View.extend(StyleBindingsMixin, { 13 | templateName: 'header-row', 14 | classNames: ['ember-table-table-row', 'ember-table-header-row'], 15 | styleBindings: ['width'], 16 | columns: Ember.computed.alias('content'), 17 | width: Ember.computed.alias('controller._rowWidth'), 18 | scrollLeft: Ember.computed.alias('controller._tableScrollLeft'), 19 | 20 | /* Options for jQuery UI sortable */ 21 | sortableOption: Ember.computed(function() { 22 | return { 23 | axis: 'x', 24 | containment: 'parent', 25 | cursor: 'move', 26 | helper: 'clone', 27 | items: ".ember-table-header-cell.sortable", 28 | opacity: 0.9, 29 | placeholder: 'ui-state-highlight', 30 | scroll: true, 31 | tolerance: 'intersect', 32 | update: jQuery.proxy(this.onColumnSortDone, this), 33 | stop: jQuery.proxy(this.onColumnSortStop, this), 34 | sort: jQuery.proxy(this.onColumnSortChange, this) 35 | }; 36 | }), 37 | onScrollLeftDidChange: Ember.observer(function() { 38 | return this.$().scrollLeft(this.get('scrollLeft')); 39 | }, 'scrollLeft'), 40 | didInsertElement: function() { 41 | this._super(); 42 | if (this.get('controller.enableColumnReorder')) { 43 | return this.$('> div').sortable(this.get('sortableOption')); 44 | } 45 | }, 46 | onScroll: function(event) { 47 | this.set('scrollLeft', event.target.scrollLeft); 48 | return event.preventDefault(); 49 | }, 50 | onColumnSortStop: function(event, ui) { 51 | return this.set('controller._isShowingSortableIndicator', false); 52 | }, 53 | onColumnSortChange: function(event, ui) { 54 | var left; 55 | left = this.$('.ui-state-highlight').offset().left - this.$().closest('.ember-table-tables-container').offset().left; 56 | this.set('controller._isShowingSortableIndicator', true); 57 | return this.set('controller._sortableIndicatorLeft', left); 58 | }, 59 | onColumnSortDone: function(event, ui) { 60 | var column, newIndex, view; 61 | newIndex = ui.item.index(); 62 | view = Ember.View.views[ui.item.attr('id')]; 63 | column = view.get('column'); 64 | this.get('controller').onColumnSort(column, newIndex); 65 | return this.set('controller._isShowingSortableIndicator', false); 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /addon/views/header-table-container.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ShowHorizontalScrollMixin from '../mixins/show-horizontal-scroll-mixin'; 3 | import TableContainer from '../views/table-container'; 4 | 5 | export default TableContainer.extend(ShowHorizontalScrollMixin, { 6 | templateName: 'header-table-container', 7 | classNames: ['ember-table-table-container', 'ember-table-fixed-table-container', 'ember-table-header-container'], 8 | height: Ember.computed.alias('controller._headerHeight'), 9 | width: Ember.computed.alias('controller._tableContainerWidth') 10 | }); 11 | -------------------------------------------------------------------------------- /addon/views/lazy-container.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | const {computed} = Ember; 5 | 6 | /* jshint unused:false */ 7 | export default Ember.ContainerView.extend(StyleBindingsMixin, { 8 | classNames: 'lazy-list-container', 9 | styleBindings: ['height'], 10 | content: null, 11 | itemViewClass: null, 12 | rowHeight: null, 13 | scrollTop: null, 14 | startIndex: null, 15 | 16 | init: function() { 17 | this._super(); 18 | Ember.run.scheduleOnce('afterRender', this, this.onNumChildViewsDidChange); 19 | }, 20 | 21 | height: Ember.computed('content.[]', 'rowHeight', function() { 22 | return this.get('content.length') * this.get('rowHeight'); 23 | }), 24 | 25 | numChildViews: computed('numItemsShowing', function() { 26 | return this.get('numItemsShowing') + 2; 27 | }), 28 | 29 | onNumChildViewsDidChange: Ember.observer('numChildViews', 'itemViewClass', function() { 30 | var itemViewClass, newNumViews, numViewsToInsert, oldNumViews, view, viewsToAdd, viewsToRemove, _i, _results; 31 | view = this; 32 | 33 | /* We are getting the class from a string e.g. "Ember.Table.Row" */ 34 | itemViewClass = this.get('itemViewClass'); 35 | if (typeof itemViewClass === 'string') { 36 | if (/[A-Z]+/.exec(itemViewClass)) { 37 | 38 | /* Global var lookup - 'App.MessagePreviewView' */ 39 | itemViewClass = Ember.get(Ember.lookup, itemViewClass); 40 | } else { 41 | 42 | /* Ember CLI Style lookup - 'message/preview' */ 43 | itemViewClass = this.container.lookupFactory("view:" + itemViewClass); 44 | } 45 | } 46 | newNumViews = this.get('numChildViews'); 47 | if (!(itemViewClass && newNumViews)) { 48 | return; 49 | } 50 | oldNumViews = this.get('length'); 51 | numViewsToInsert = newNumViews - oldNumViews; 52 | 53 | /* if newNumViews < oldNumViews we need to remove some views */ 54 | if (numViewsToInsert < 0) { 55 | viewsToRemove = this.slice(newNumViews, oldNumViews); 56 | return this.removeObjects(viewsToRemove); 57 | } else if (numViewsToInsert > 0) { 58 | 59 | /* if oldNumViews < newNumViews we need to add more views */ 60 | viewsToAdd = []; 61 | for (var i = numViewsToInsert - 1; i >= 0; i--) { 62 | viewsToAdd.push(view.createChildView(itemViewClass)); 63 | } 64 | 65 | return this.pushObjects(viewsToAdd); 66 | } 67 | }), 68 | 69 | /* 70 | TODO(Peter): Consider making this a computed... binding logic will go 71 | into the LazyItemMixin 72 | */ 73 | viewportDidChange: Ember.observer('content.[]', 'length', 'startIndex', function() { 74 | var content = this.get('content') || []; 75 | var clength = content.get('length'); 76 | var startIndex = this.get('startIndex'); 77 | 78 | /* 79 | this is a necessary check otherwise we are trying to access an object 80 | that doesn't exists 81 | */ 82 | if (startIndex + numShownViews >= clength) { 83 | startIndex = clength - numShownViews; 84 | } 85 | if (startIndex < 0) { 86 | startIndex = 0; 87 | } 88 | 89 | var childViews = this.filter(function(view){ 90 | return !view.isAttrNode; 91 | }); 92 | var numShownViews = Math.min(childViews.length, clength); 93 | 94 | return childViews.forEach(function(childView, i) { 95 | 96 | /* 97 | for all views that we are not using... just remove content 98 | this makes them invisble 99 | */ 100 | var item, itemIndex; 101 | if (i >= numShownViews) { 102 | childView = childViews.objectAt(i); 103 | childView.set('content', null); 104 | return; 105 | } 106 | itemIndex = startIndex + i; 107 | childView = childViews.objectAt(itemIndex % numShownViews); 108 | item = content.objectAt(itemIndex); 109 | if (item !== childView.get('content')) { 110 | childView.teardownContent(); 111 | childView.set('itemIndex', itemIndex); 112 | childView.set('content', item); 113 | return childView.prepareContent(); 114 | } 115 | }, this); 116 | }) 117 | }); 118 | -------------------------------------------------------------------------------- /addon/views/lazy-item.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | export default Ember.View.extend(StyleBindingsMixin, { 5 | itemIndex: null, 6 | prepareContent: Ember.K, 7 | teardownContent: Ember.K, 8 | rowHeightBinding: 'parentView.rowHeight', 9 | styleBindings: ['width', 'top', 'display'], 10 | top: Ember.computed('itemIndex', 'rowHeight', function() { 11 | return this.get('itemIndex') * this.get('rowHeight'); 12 | }), 13 | display: Ember.computed('content', function() { 14 | if (!this.get('content')) { 15 | return 'none'; 16 | } 17 | }) 18 | }); 19 | -------------------------------------------------------------------------------- /addon/views/lazy-table-block.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import LazyContainerView from '../views/lazy-container'; 3 | 4 | export default LazyContainerView.extend({ 5 | classNames: ['ember-table-table-block'], 6 | styleBindings: ['width'], 7 | itemViewClass: Ember.computed.alias('controller.tableRowViewClass'), 8 | rowHeight: Ember.computed.alias('controller.rowHeight'), 9 | columns: null, 10 | content: null, 11 | scrollLeft: null, 12 | scrollTop: null, 13 | onScrollLeftDidChange: Ember.observer('scrollLeft', function() { 14 | return this.$().scrollLeft(this.get('scrollLeft')); 15 | }) 16 | }); 17 | -------------------------------------------------------------------------------- /addon/views/multi-item-view-collection.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | var MultiItemViewCollectionView; 5 | 6 | MultiItemViewCollectionView = Ember.CollectionView.extend(StyleBindingsMixin, { 7 | styleBindings: 'width', 8 | itemViewClassField: null, 9 | createChildView: function(view, attrs) { 10 | var itemViewClass, itemViewClassField; 11 | itemViewClassField = this.get('itemViewClassField'); 12 | itemViewClass = attrs.content.get(itemViewClassField); 13 | if (typeof itemViewClass === 'string') { 14 | if (/[A-Z]+/.exec(itemViewClass)) { 15 | 16 | /* Global var lookup - 'App.MessagePreviewView' */ 17 | itemViewClass = Ember.get(Ember.lookup, itemViewClass); 18 | } else { 19 | 20 | /* Ember CLI Style lookup - 'message/preview' */ 21 | itemViewClass = this.container.lookupFactory("view:" + itemViewClass); 22 | } 23 | } 24 | return this._super(itemViewClass, attrs); 25 | } 26 | }); 27 | 28 | export default MultiItemViewCollectionView; -------------------------------------------------------------------------------- /addon/views/scroll-container.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ScrollHandlerMixin from '../mixins/scroll-handler-mixin'; 3 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 4 | 5 | export default Ember.View.extend(StyleBindingsMixin, ScrollHandlerMixin, { 6 | templateName: 'scroll-container', 7 | classNames: ['ember-table-scroll-container'], 8 | styleBindings: ['left', 'width', 'height'], 9 | scrollElementSelector: '.antiscroll-inner', 10 | width: Ember.computed.alias('controller._scrollContainerWidth'), 11 | 12 | /* 10 is the height of the horizontal scrollbar */ 13 | height: 10, 14 | left: Ember.computed.alias('controller._fixedColumnsWidth'), 15 | scrollTop: Ember.computed.alias('controller._tableScrollTop'), 16 | scrollLeft: Ember.computed.alias('controller._tableScrollLeft'), 17 | 18 | /* 19 | HACK: onScrollLeftDidChange will not fire unless scrollLeft has been get 20 | at least once. Therefore, we want to call onScrollLeftDidChange in 21 | didInsertElement 22 | */ 23 | didInsertElement: function() { 24 | this._super(); 25 | return this.onScrollLeftDidChange(); 26 | }, 27 | 28 | /* `event` here is a jQuery event */ 29 | onScroll: function(event) { 30 | this.set('scrollLeft', event.target.scrollLeft); 31 | return event.preventDefault(); 32 | }, 33 | onScrollLeftDidChange: Ember.observer(function() { 34 | var selector; 35 | selector = this.get('scrollElementSelector'); 36 | return this.$(selector).scrollLeft(this.get('scrollLeft')); 37 | }, 'scrollLeft', 'scrollElementSelector') 38 | }); 39 | -------------------------------------------------------------------------------- /addon/views/scroll-panel.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | export default Ember.View.extend(StyleBindingsMixin, { 5 | classNames: ['ember-table-scroll-panel'], 6 | styleBindings: ['width', 'height'], 7 | width: Ember.computed.alias('controller._tableColumnsWidth'), 8 | height: Ember.computed.alias('controller._tableContentHeight') 9 | }); 10 | -------------------------------------------------------------------------------- /addon/views/table-block.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | /* TODO: This should be a mixin */ 5 | export default Ember.CollectionView.extend(StyleBindingsMixin, { 6 | classNames: ['ember-table-table-block'], 7 | styleBindings: ['width', 'height'], 8 | itemViewClass: Ember.computed.alias('controller.tableRowViewClass'), 9 | columns: null, 10 | content: null, 11 | scrollLeft: null, 12 | onScrollLeftDidChange: Ember.observer(function() { 13 | return this.$().scrollLeft(this.get('scrollLeft')); 14 | }, 'scrollLeft'), 15 | height: Ember.computed('controller._headerHeight', function() { 16 | return this.get('controller._headerHeight'); 17 | }) 18 | }); 19 | -------------------------------------------------------------------------------- /addon/views/table-cell.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | export default Ember.View.extend(StyleBindingsMixin, { 5 | 6 | /* 7 | * --------------------------------------------------------------------------- 8 | * API - Inputs 9 | * --------------------------------------------------------------------------- 10 | */ 11 | 12 | /* 13 | TODO: Doc 14 | */ 15 | templateName: 'table-cell', 16 | classNames: ['ember-table-cell'], 17 | classNameBindings: 'column.textAlign', 18 | styleBindings: 'width', 19 | 20 | /* 21 | * --------------------------------------------------------------------------- 22 | * Internal properties 23 | * --------------------------------------------------------------------------- 24 | */ 25 | init: function() { 26 | this._super(); 27 | this.contentPathDidChange(); 28 | return this.contentDidChange(); 29 | }, 30 | row: Ember.computed.alias('parentView.row'), 31 | column: Ember.computed.alias('content'), 32 | width: Ember.computed.alias('column.columnWidth'), 33 | contentDidChange: function() { 34 | return this.notifyPropertyChange('cellContent'); 35 | }, 36 | contentPathWillChange: function() { 37 | let contentPath = this.get('column.contentPath'); 38 | if (contentPath) { 39 | return this.removeObserver("row." + contentPath, this, this.contentDidChange); 40 | } 41 | }.observesBefore('column.contentPath'), 42 | contentPathDidChange: function() { 43 | var contentPath = this.get('column.contentPath'); 44 | if (contentPath) { 45 | return this.addObserver("row." + contentPath, this, this.contentDidChange); 46 | } 47 | }.observesBefore('column.contentPath'), 48 | cellContent: Ember.computed('row.isLoaded', 'column', function(key, value) { 49 | var column, row; 50 | row = this.get('row'); 51 | column = this.get('column'); 52 | if (!(row && column)) { 53 | return; 54 | } 55 | if (arguments.length === 1) { 56 | value = column.getCellContent(row); 57 | } else { 58 | column.setCellContent(row, value); 59 | } 60 | return value; 61 | }) 62 | }); 63 | -------------------------------------------------------------------------------- /addon/views/table-container.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import StyleBindingsMixin from '../mixins/style-bindings-mixin'; 3 | 4 | export default Ember.View.extend(StyleBindingsMixin, { 5 | classNames: ['ember-table-table-container'], 6 | styleBindings: ['height', 'width'] 7 | }); 8 | -------------------------------------------------------------------------------- /addon/views/table-row.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import LazyItemView from '../views/lazy-item'; 3 | 4 | /* jshint unused:false */ 5 | export default LazyItemView.extend({ 6 | templateName: 'table-row', 7 | classNames: 'ember-table-table-row', 8 | classNameBindings: ['row.isHovered:ember-table-hover', 'row.isSelected:ember-table-selected', 'row.rowStyle', 'isLastRow:ember-table-last-row'], 9 | styleBindings: ['width', 'height'], 10 | row: Ember.computed.alias('content'), 11 | columns: Ember.computed.alias('parentView.columns'), 12 | width: Ember.computed.alias('controller._rowWidth'), 13 | height: Ember.computed.alias('controller.rowHeight'), 14 | isLastRow: Ember.computed('controller.bodyContent.lastObject', 'row', function() { 15 | return this.get('row') === this.get('controller.bodyContent.lastObject'); 16 | }), 17 | mouseEnter: function(event) { 18 | var row; 19 | row = this.get('row'); 20 | if (row) { 21 | return row.set('isHovered', true); 22 | } 23 | }, 24 | mouseLeave: function(event) { 25 | var row; 26 | row = this.get('row'); 27 | if (row) { 28 | return row.set('isHovered', false); 29 | } 30 | }, 31 | teardownContent: function() { 32 | var row; 33 | row = this.get('row'); 34 | if (row) { 35 | return row.set('isHovered', false); 36 | } 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intuitivepixel/ember-cli-ember-table/15fa20cd8177965c5a78fa61f7b99afdd29e4007/app/.gitkeep -------------------------------------------------------------------------------- /app/column-definition.js: -------------------------------------------------------------------------------- 1 | import ColumnDefinition from 'ember-cli-ember-table/column-definition'; 2 | export default ColumnDefinition; -------------------------------------------------------------------------------- /app/components/ember-table.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is just a proxy file requiring the component from the /addon folder and 3 | making it available to the dummy application! 4 | */ 5 | import EmberTable from 'ember-cli-ember-table/components/ember-table'; 6 | export default EmberTable; -------------------------------------------------------------------------------- /app/controllers/row.js: -------------------------------------------------------------------------------- 1 | import RowArrayController from 'ember-cli-ember-table/controllers/row'; 2 | export default RowArrayController; -------------------------------------------------------------------------------- /app/initializers/ember-cli-ember-table.js: -------------------------------------------------------------------------------- 1 | /* global jQuery */ 2 | 3 | import Ember from 'ember'; 4 | var registered = false; 5 | 6 | export default { 7 | name: 'ember-cli-ember-table', 8 | initialize: function() { 9 | 10 | var VERSION = '0.2.2'; 11 | if (!registered) { 12 | Ember.libraries.register('Ember Table', VERSION); 13 | registered = true; 14 | } 15 | 16 | /* 17 | jQuery.browser shim that makes HT working with jQuery 1.8+ 18 | */ 19 | if (!jQuery.browser) { 20 | (function() { 21 | var browser, matched, res; 22 | matched = void 0; 23 | browser = void 0; 24 | jQuery.uaMatch = function(ua) { 25 | var match; 26 | ua = ua.toLowerCase(); 27 | match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; 28 | return { 29 | browser: match[1] || "", 30 | version: match[2] || "0" 31 | }; 32 | }; 33 | matched = jQuery.uaMatch(navigator.userAgent); 34 | browser = {}; 35 | if (matched.browser) { 36 | browser[matched.browser] = true; 37 | browser.version = matched.version; 38 | } 39 | if (browser.chrome) { 40 | browser.webkit = true; 41 | } else { 42 | if (browser.webkit) { 43 | browser.safari = true; 44 | } 45 | } 46 | res = jQuery.browser = browser; 47 | return res; 48 | })(); 49 | } 50 | 51 | } 52 | }; -------------------------------------------------------------------------------- /app/row-definition.js: -------------------------------------------------------------------------------- 1 | import RowDefinition from 'ember-cli-ember-table/row-definition'; 2 | export default RowDefinition; -------------------------------------------------------------------------------- /app/templates/body-table-container.hbs: -------------------------------------------------------------------------------- 1 |