diff --git a/platform/features/table/bundle.js b/platform/features/table/bundle.js index 77ead67c04..15517c115c 100644 --- a/platform/features/table/bundle.js +++ b/platform/features/table/bundle.js @@ -161,6 +161,12 @@ define([ "key": "table-options-edit", "templateUrl": "templates/table-options-edit.html" } + ], + "stylesheets": [ + { + "stylesheetUrl": "css/table.css", + "priority": "mandatory" + } ] } }); diff --git a/platform/features/table/res/sass/table.scss b/platform/features/table/res/sass/table.scss new file mode 100644 index 0000000000..a79cfac4c6 --- /dev/null +++ b/platform/features/table/res/sass/table.scss @@ -0,0 +1,50 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +.sizing-table { + min-width: 100%; + z-index: -1; + visibility: hidden; + position: absolute; + + //Add some padding to allow for decorations such as limits indicator + td { + padding-right: 15px; + padding-left: 10px; + white-space: nowrap; + } +} +.mct-table { + table-layout: fixed; + th { + box-sizing: border-box; + } + tbody { + tr { + position: absolute; + } + td { + white-space: nowrap; + overflow: hidden; + box-sizing: border-box; + } + } +} \ No newline at end of file diff --git a/platform/features/table/res/templates/mct-table.html b/platform/features/table/res/templates/mct-table.html index 5997376587..7a18388455 100644 --- a/platform/features/table/res/templates/mct-table.html +++ b/platform/features/table/res/templates/mct-table.html @@ -1,22 +1,25 @@ -
- +
+ + + + + + +
{{header}}
+ {{sizingRow[header].text}} +
+ + }"> - +
@@ -41,21 +42,15 @@
{{ visibleRow.contents[header].text }} diff --git a/platform/features/table/src/controllers/MCTTableController.js b/platform/features/table/src/controllers/MCTTableController.js index ad13647766..c853f155ad 100644 --- a/platform/features/table/src/controllers/MCTTableController.js +++ b/platform/features/table/src/controllers/MCTTableController.js @@ -23,10 +23,13 @@ define( this.maxDisplayRows = 50; this.scrollable = element.find('div'); + this.thead = element.find('thead'); + this.tbody = element.find('tbody'); + this.$scope.sizingRow = {}; + this.scrollable.on('scroll', this.onScroll.bind(this)); $scope.visibleRows = []; - $scope.overrideRowPositioning = false; /** * Set default values for optional parameters on a given scope @@ -100,14 +103,31 @@ define( * @private */ MCTTableController.prototype.newRow = function (event, rowIndex) { - var row = this.$scope.rows[rowIndex]; - //Add row to the filtered, sorted list of all rows - if (this.filterRows([row]).length > 0) { - this.insertSorted(this.$scope.displayRows, row); + var self = this, + row = this.$scope.rows[rowIndex], + largestRow; + + function sizeAndScroll () { + self.setElementSizes(); + self.scrollToBottom(); } - this.$timeout(this.setElementSizes.bind(this)) - .then(this.scrollToBottom.bind(this)); + //Does the row pass the current filter? + if (this.filterRows([row]).length === 1) { + this.insertSorted(this.$scope.displayRows, row); + + //Calculate largest row + largestRow = this.buildLargestRow([this.$scope.sizingRow, row]); + + // Has it changed? If so, set the the 'sizing' row which + // determines column widths + if (JSON.stringify(largestRow) !== JSON.stringify(this.$scope.sizingRow)){ + this.$scope.sizingRow = largestRow; + this.$timeout(sizeAndScroll); + } else { + sizeAndScroll(); + } + } }; /** @@ -249,13 +269,12 @@ define( * for individual rows. */ MCTTableController.prototype.setElementSizes = function () { - var self = this, - thead = this.element.find('thead'), - tbody = this.element.find('tbody'), + var thead = this.thead, + tbody = this.tbody, firstRow = tbody.find('tr'), column = firstRow.find('td'), headerHeight = thead.prop('offsetHeight'), - rowHeight = 20, + rowHeight = firstRow.prop('offsetHeight'), columnWidth, tableWidth = 0, overallHeight = headerHeight + (rowHeight * @@ -279,8 +298,6 @@ define( } else { this.$scope.totalWidth = 'none'; } - - this.$scope.overrideRowPositioning = true; }; /** @@ -400,43 +417,32 @@ define( * pre-calculate optimal column sizes without having to render * every row. */ - MCTTableController.prototype.findLargestRow = function (rows) { - var largestRow = rows.reduce(function (largestRow, row) { + MCTTableController.prototype.buildLargestRow = function (rows) { + var largestRow = rows.reduce(function (prevLargest, row) { Object.keys(row).forEach(function (key) { - var currentColumn = row[key].text, + var currentColumn, + currentColumnLength, + largestColumn, + largestColumnLength; + if (!row[key]){ + //do nothing, no value for this column; + } else { + currentColumn = (row[key]).text; currentColumnLength = (currentColumn && currentColumn.length) ? currentColumn.length : - currentColumn, - largestColumn = largestRow[key].text, - largestColumnLength = - (largestColumn && largestColumn.length) ? - largestColumn.length : - largestColumn; + currentColumn; + largestColumn = prevLargest[key] ? prevLargest[key].text : ""; + largestColumnLength = largestColumn.length; - if (currentColumnLength > largestColumnLength) { - largestRow[key] = JSON.parse(JSON.stringify(row[key])); + if (currentColumnLength > largestColumnLength) { + prevLargest[key] = JSON.parse(JSON.stringify(row[key])); + } } + }); - return largestRow; + return prevLargest; }, JSON.parse(JSON.stringify(rows[0] || {}))); - - largestRow = JSON.parse(JSON.stringify(largestRow)); - - // Pad with characters to accomodate variable-width fonts, - // and remove characters that would allow word-wrapping. - Object.keys(largestRow).forEach(function (key) { - var padCharacters, - i; - - largestRow[key].text = String(largestRow[key].text); - padCharacters = largestRow[key].text.length / 10; - for (i = 0; i < padCharacters; i++) { - largestRow[key].text = largestRow[key].text + 'W'; - } - largestRow[key].text = largestRow[key].text - .replace(/[ \-_]/g, 'W'); - }); return largestRow; }; @@ -447,20 +453,13 @@ define( * @private */ MCTTableController.prototype.resize = function (){ - var largestRow = this.findLargestRow(this.$scope.displayRows), - self = this; - this.$scope.visibleRows = [ - { - rowIndex: 0, - offsetY: undefined, - contents: largestRow - } - ]; + var self = this; + + this.$scope.sizingRow = this.buildLargestRow(this.$scope.displayRows); //Wait a timeout to allow digest of previous change to visible // rows to happen. this.$timeout(function () { - //Remove temporary padding row used for setting column widths self.$scope.visibleRows = []; self.setElementSizes(); }); @@ -489,8 +488,6 @@ define( //Reset visible rows because new row data available. this.$scope.visibleRows = []; - this.$scope.overrideRowPositioning = false; - //Nothing to show because no columns visible if (!this.$scope.displayHeaders) { return; diff --git a/platform/features/table/test/controllers/MCTTableControllerSpec.js b/platform/features/table/test/controllers/MCTTableControllerSpec.js index 5e38c7e651..5ee9f21c41 100644 --- a/platform/features/table/test/controllers/MCTTableControllerSpec.js +++ b/platform/features/table/test/controllers/MCTTableControllerSpec.js @@ -58,15 +58,18 @@ define( mockElement = jasmine.createSpyObj('element', [ 'find', + 'prop', 'on' ]); mockElement.find.andReturn(mockElement); + mockElement.prop.andReturn(0); mockScope.displayHeaders = true; mockTimeout = jasmine.createSpy('$timeout'); mockTimeout.andReturn(promise(undefined)); controller = new MCTTableController(mockScope, mockTimeout, mockElement); + spyOn(controller, 'setVisibleRows'); }); it('Reacts to changes to filters, headers, and rows', function() { @@ -138,8 +141,6 @@ define( var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1]; controller.updateRows(testRows); expect(mockScope.displayRows.length).toBe(3); - spyOn(controller, 'setVisibleRows'); - //controller.setVisibleRows.andReturn(undefined); removeRowFunc(undefined, 2); expect(mockScope.displayRows.length).toBe(2); expect(controller.setVisibleRows).toHaveBeenCalled(); @@ -266,6 +267,25 @@ define( expect(mockScope.displayRows[4].col2.text).toEqual('ggg'); }); + it('Resizes columns if length of any columns in new' + + ' row exceeds corresponding existing column', function() { + var row7 = { + 'col1': {'text': 'row6 col1'}, + 'col2': {'text': 'some longer string'}, + 'col3': {'text': 'row6 col3'} + }; + + mockScope.sortColumn = undefined; + mockScope.sortDirection = undefined; + mockScope.filters = {}; + + mockScope.displayRows = testRows.slice(0); + + mockScope.rows.push(row7); + controller.newRow(undefined, mockScope.rows.length-1); + expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'}); + }); + }); });