diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE index 09f98adf793..e18cbef176b 100644 --- a/zeppelin-distribution/src/bin_license/LICENSE +++ b/zeppelin-distribution/src/bin_license/LICENSE @@ -132,6 +132,10 @@ The text of each license is also included at licenses/LICENSE-[project]-[version (The MIT License) lodash v3.9.3 (https://lodash.com/) - https://github.com/lodash/lodash/blob/3.9.3/LICENSE.txt (The MIT License) angular-filter v0.5.4 (https://github.com/a8m/angular-filter) - https://github.com/a8m/angular-filter/blob/v0.5.4/license.md (The MIT License) ngToast v1.5.5 (http://tamerayd.in/ngToast/) - http://tameraydin.mit-license.org/ + (The MIT License) Handsontable v0.24.2 (https://github.com/handsontable/handsontable) - https://github.com/handsontable/handsontable/blob/master/LICENSE + (The MIT License) Zeroclipboard v2.2.0 (https://github.com/zeroclipboard/zeroclipboard) - https://github.com/zeroclipboard/zeroclipboard/blob/v2.2.0/LICENSE + (The MIT License) Moment v2.9.0 (https://github.com/moment/moment) - https://github.com/moment/moment/blob/2.9.0/LICENSE + (The MIT License) Pikaday v1.3.2 (https://github.com/dbushell/Pikaday) - https://github.com/dbushell/Pikaday/blob/1.3.2/LICENSE (The MIT License) slf4j v1.7.10 (org.slf4j:slf4j-api:jar:1.7.10 - http://www.slf4j.org) - http://www.slf4j.org/license.html (The MIT License) slf4j-log4j12 v1.7.10 (org.slf4j:slf4j-log4j12:jar:1.7.10 - http://www.slf4j.org) - http://www.slf4j.org/license.html (The MIT License) bcprov-jdk15on v1.51 (org.bouncycastle:bcprov-jdk15on:jar:1.51 - http://www.bouncycastle.org/java.html) - http://www.bouncycastle.org/licence.html diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java index eb09539be5e..1eadd0ecef9 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java @@ -167,9 +167,8 @@ public void testSqlSpark() throws Exception { WebElement paragraph1Result = driver.findElement(By.xpath( getParagraphXPath(1) + "//div[@class=\"tableDisplay\"]")); collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark result: ", - paragraph1Result.getText().toString(), CoreMatchers.equalTo("age job marital education balance\n" + - "30 unemployed married primary 1,787") - ); + paragraph1Result.getText().toString(), CoreMatchers.equalTo("age\njob\nmarital\neducation\nbalance\n30" + + " unemployed married primary 1,787\nage\njob\nmarital\neducation\nbalance")); } catch (Exception e) { handleException("Exception in SparkParagraphIT while testSqlSpark", e); } diff --git a/zeppelin-web/.jshintrc b/zeppelin-web/.jshintrc index d15bbb95e58..bdcd213761e 100644 --- a/zeppelin-web/.jshintrc +++ b/zeppelin-web/.jshintrc @@ -31,6 +31,7 @@ "nv": false, "ace": false, "d3": false, - "BootstrapDialog": false + "BootstrapDialog": false, + "Handsontable": false } } diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index b8773036f23..1e19060b7d1 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -30,9 +30,7 @@ "ngtoast": "~2.0.0", "ng-focus-if": "~1.0.2", "bootstrap3-dialog": "bootstrap-dialog#~1.34.7", - "floatThead": "~1.3.2", - "datatables.net-bs": "~1.10.11", - "datatables.net-buttons-bs": "~1.1.2" + "handsontable": "~0.24.2" }, "devDependencies": { "angular-mocks": "1.5.0" diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 21dd27985fc..c3feefff4e5 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -1218,110 +1218,42 @@ angular.module('zeppelinWebApp') }; var setTable = function(type, data, refresh) { - var getTableContentFormat = function(d) { - if (isNaN(d)) { - if (d.length>'%html'.length && '%html ' === d.substring(0, '%html '.length)) { - return 'html'; - } else { - return ''; - } - } else { - return ''; - } - }; - - var formatTableContent = function(d) { - if (isNaN(d)) { - var f = getTableContentFormat(d); - if (f !== '') { - return d.substring(f.length+2); - } else { - return d; - } - } else { - var dStr = d.toString(); - var splitted = dStr.split('.'); - var formatted = splitted[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); - if (splitted.length>1) { - formatted+= '.'+splitted[1]; - } - return formatted; - } - }; - - var renderTable = function() { - var html = ''; - html += ''; - html += ' '; - html += ' '; - for (var titleIndex in $scope.paragraph.result.columnNames) { - html += ''; - } - html += ' '; - html += ' '; - html += ' '; - for (var r in $scope.paragraph.result.msgTable) { - var row = $scope.paragraph.result.msgTable[r]; - html += ' '; - for (var index in row) { - var v = row[index].value; - if (getTableContentFormat(v) !== 'html') { - v = v.replace(/[\u00A0-\u9999<>\&]/gim, function(i) { - return '&#'+i.charCodeAt(0)+';'; - }); - } - html += ' '; + var height = $scope.paragraph.config.graph.height; + angular.element('#p' + $scope.paragraph.id + '_table').css('height', height); + var resultRows = $scope.paragraph.result.rows; + var columnNames = _.pluck($scope.paragraph.result.columnNames, 'name'); + var container = document.getElementById('p' + $scope.paragraph.id + '_table'); + + var handsontable = new Handsontable(container, { + data: resultRows, + colHeaders: columnNames, + rowHeaders: false, + stretchH: 'all', + sortIndicator: true, + columnSorting: true, + contextMenu: false, + manualColumnResize: true, + manualRowResize: true, + editor: false, + fillHandle: false, + disableVisualSelection: true, + cells: function (row, col, prop) { + var cellProperties = {}; + cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { + Handsontable.NumericCell.renderer.apply(this, arguments); + if (!isNaN(value)) { + cellProperties.type = 'numeric'; + cellProperties.format = '0,0'; + cellProperties.editor = false; + td.style.textAlign = 'left'; + } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) { + td.innerHTML = value.substring('%html'.length); + } + }; + return cellProperties; } - html += ' '; - } - html += ' '; - html += '
'+$scope.paragraph.result.columnNames[titleIndex].name+'
'+formatTableContent(v)+'
'; - - var tableDomEl = angular.element('#p' + $scope.paragraph.id + '_table'); - tableDomEl.html(html); - var oTable = tableDomEl.children(1).DataTable({ - paging: false, - info: false, - autoWidth: false, - lengthChange: false, - searching: false, - dom: '<>' }); - - if ($scope.paragraph.result.msgTable.length > 10000) { - tableDomEl.css({ - 'overflow': 'scroll', - 'height': $scope.paragraph.config.graph.height - }); - } else { - - var dataTable = angular.element('#p' + $scope.paragraph.id + '_table .table'); - dataTable.floatThead({ - scrollContainer: function(dataTable) { - return tableDomEl; - } - }); - - dataTable.on('remove', function () { - dataTable.floatThead('destroy'); - }); - - tableDomEl.css({ - 'position': 'relative', - 'height': '100%' - }); - tableDomEl.perfectScrollbar('destroy') - .perfectScrollbar({minScrollbarLength: 20}); - - angular.element('.ps-scrollbar-y-rail').css('z-index', '1002'); - - // set table height - var psHeight = $scope.paragraph.config.graph.height; - tableDomEl.css('height', psHeight); - tableDomEl.perfectScrollbar('update'); - } - }; var retryRenderer = function() { diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css index 60f3d7fb3cf..f0c650fc848 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css @@ -396,6 +396,29 @@ table.dataTable.table-condensed .sorting_desc:after { background: none; } +/* + Handsontable +*/ + +.handsontable th { + font-weight: bold; +} + +.handsontable th, .handsontable td { + border-right: 0px; + border-left: 0px !important; + padding: 4px; +} + +.handsontable tr:first-child th { + text-align: left; + border-top: 0px; + padding: 4px 0px 0px 0px; + border-left: 0px; + border-right: 0px; + border-bottom: 2px solid #CCC; +} + /* Pivot CSS */ diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 207320ead1c..a634fbf1821 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -44,8 +44,8 @@ - - + + @@ -132,16 +132,10 @@ - - - - - - - - - - + + + + diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js index 8049ac60304..1ec6eb62936 100644 --- a/zeppelin-web/test/karma.conf.js +++ b/zeppelin-web/test/karma.conf.js @@ -59,16 +59,10 @@ module.exports = function(config) { 'bower_components/ngtoast/dist/ngToast.js', 'bower_components/ng-focus-if/focusIf.js', 'bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js', - 'bower_components/floatThead/dist/jquery.floatThead.js', - 'bower_components/floatThead/dist/jquery.floatThead.min.js', - 'bower_components/datatables.net/js/jquery.dataTables.js', - 'bower_components/datatables.net-bs/js/dataTables.bootstrap.js', - 'bower_components/datatables.net-buttons/js/dataTables.buttons.js', - 'bower_components/datatables.net-buttons/js/buttons.colVis.js', - 'bower_components/datatables.net-buttons/js/buttons.flash.js', - 'bower_components/datatables.net-buttons/js/buttons.html5.js', - 'bower_components/datatables.net-buttons/js/buttons.print.js', - 'bower_components/datatables.net-buttons-bs/js/buttons.bootstrap.js', + 'bower_components/zeroclipboard/dist/ZeroClipboard.js', + 'bower_components/moment/moment.js', + 'bower_components/pikaday/pikaday.js', + 'bower_components/handsontable/dist/handsontable.js', 'bower_components/angular-mocks/angular-mocks.js', // endbower 'src/app/app.js',