From b92e92ff8821a58c47d7510818ec518414499c83 Mon Sep 17 00:00:00 2001 From: Peilin Yang Date: Thu, 25 Aug 2016 10:06:23 -0700 Subject: [PATCH 1/8] add text/numeric conversion support to table display --- zeppelin-web/src/app/notebook/notebook.css | 49 +++++++ .../paragraph/paragraph.controller.js | 121 +++++++++++++++++- 2 files changed, 166 insertions(+), 4 deletions(-) diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css index c11544fd0d2..ea494a785d6 100644 --- a/zeppelin-web/src/app/notebook/notebook.css +++ b/zeppelin-web/src/app/notebook/notebook.css @@ -312,3 +312,52 @@ min-width: 150px; max-width: 50%; } + + +.changeType { + border: 1px solid #bbb; + color: #bbb; + background: #eee; + border-radius: 2px; + padding: 2px; + font-size: 9px; + float: right; + line-height: 9px; + margin: 3px 3px 0 0; +} +.changeType:hover { + border: 1px solid #777; + color: #777; + cursor: pointer; +} +.changeType.pressed { + background-color: #999; +} +.changeTypeMenu { + position: absolute; + border: 1px solid #ccc; + margin-top: 18px; + box-shadow: 0 1px 3px -1px #323232; + background: white; + padding: 0; + font-size: 13px; + display: none; + z-index: 10; +} +.changeTypeMenu li { + text-align: left; + list-style: none; + padding: 2px 20px; + cursor: pointer; + margin-bottom: 0; +} +.changeTypeMenu li.active:before { + font-size: 12px; + content: "\2714"; + margin-left: -15px; + margin-right: 3px; +} +.changeTypeMenu li:hover { + background: #eee; +} + diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index c2bc8e64cb6..529985ef680 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -949,6 +949,85 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r }; var setTable = function(data, refresh) { + function addButtonMenuEvent(button, menu) { + Handsontable.Dom.addEvent(button, 'click', function(event) { + var changeTypeMenu; + var position; + var removeMenu; + + document.body.appendChild(menu); + + event.preventDefault(); + event.stopImmediatePropagation(); + + changeTypeMenu = document.querySelectorAll('.changeTypeMenu'); + + for (var i = 0, len = changeTypeMenu.length; i < len; i++) { + changeTypeMenu[i].style.display = 'none'; + } + menu.style.display = 'block'; + position = button.getBoundingClientRect(); + + menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px'; + menu.style.left = (position.left) + 'px'; + + removeMenu = function(event) { + if (menu.parentNode) { + menu.parentNode.removeChild(menu); + } + if (!(event.target.nodeName === 'LI' && event.target.parentNode.className.indexOf('changeTypeMenu') !== -1)) { + $scope.curSelectCol = -1; // set this variable to not affect other renderTable event! + } + }; + Handsontable.Dom.removeEvent(document, 'click', removeMenu); + Handsontable.Dom.addEvent(document, 'click', removeMenu); + }); + } + + function buildMenu(activeCellType) { + var menu = document.createElement('UL'); + var types = ['text', 'numeric']; + var item; + + menu.className = 'changeTypeMenu'; + + for (var i = 0, len = types.length; i < len; i++) { + item = document.createElement('LI'); + if ('innerText' in item) { + item.innerText = types[i]; + } else { + item.textContent = types[i]; + } + + item.data = {'colType': types[i]}; + + if (activeCellType === types[i]) { + item.className = 'active'; + } + menu.appendChild(item); + } + + return menu; + } + + function buildButton() { + var button = document.createElement('BUTTON'); + + button.innerHTML = '\u25BC'; + button.className = 'changeType'; + + return button; + } + + function setColumnType(i, type, instance) { + $scope.isChangingColType = true; + $scope.curSelectCol = i; + $scope.columnFormat[i] = type; + instance.validateCells(function() { + instance.render(); + }); + } + var renderTable = function() { var height = $scope.paragraph.config.graph.height; var container = angular.element('#p' + $scope.paragraph.id + '_table').css('height', height).get(0); @@ -958,7 +1037,9 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if ($scope.hot) { $scope.hot.destroy(); } - + if (!$scope.columnFormat) { + $scope.columnFormat = Array.apply(null, Array(data.columnNames.length)).map(function() { return 'text'; }); + } $scope.hot = new Handsontable(container, { colHeaders: columnNames, data: resultRows, @@ -978,9 +1059,20 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r var cellProperties = {}; cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { if (!isNaN(value)) { - cellProperties.format = '0,0.[00000]'; - td.style.textAlign = 'left'; - Handsontable.renderers.NumericRenderer.apply(this, arguments); + if (typeof $scope.isChangingColType !== 'undefined') { + if ($scope.columnFormat[col] === 'numeric') { + cellProperties.format = '0,0.[00000]'; + td.style.textAlign = 'left'; + Handsontable.renderers.NumericRenderer.apply(this, arguments); + } else { + Handsontable.renderers.TextRenderer.apply(this, arguments); + } + } else { + cellProperties.format = '0,0.[00000]'; + td.style.textAlign = 'left'; + Handsontable.renderers.NumericRenderer.apply(this, arguments); + $scope.columnFormat[col] = 'numeric'; + } } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) { td.innerHTML = value.substring('%html'.length); } else { @@ -988,6 +1080,27 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } }; return cellProperties; + }, + afterRender: function(isForced) { + $scope.isChangingColType = false; + }, + afterGetColHeader: function(col, TH) { + var instance = this; + var menu = buildMenu($scope.columnFormat[col]); + var button = buildButton(); + + addButtonMenuEvent(button, menu); + + Handsontable.Dom.addEvent(menu, 'click', function(event) { + if (event.target.nodeName === 'LI') { + setColumnType(col, event.target.data.colType, instance); + } + }); + if (TH.firstChild.lastChild.nodeName === 'BUTTON') { + TH.firstChild.removeChild(TH.firstChild.lastChild); + } + TH.firstChild.appendChild(button); + TH.style['white-space'] = 'normal'; } }); }; From 972cbe2304306e0f844107373ce38b5f90d33c30 Mon Sep 17 00:00:00 2001 From: Peilin Yang Date: Sat, 10 Sep 2016 16:58:55 -0400 Subject: [PATCH 2/8] rebase merge --- zeppelin-web/src/app/notebook/notebook.css | 49 +++++++ .../paragraph/paragraph.controller.js | 120 +++++++++++++++++- 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css index c11544fd0d2..ea494a785d6 100644 --- a/zeppelin-web/src/app/notebook/notebook.css +++ b/zeppelin-web/src/app/notebook/notebook.css @@ -312,3 +312,52 @@ min-width: 150px; max-width: 50%; } + + +.changeType { + border: 1px solid #bbb; + color: #bbb; + background: #eee; + border-radius: 2px; + padding: 2px; + font-size: 9px; + float: right; + line-height: 9px; + margin: 3px 3px 0 0; +} +.changeType:hover { + border: 1px solid #777; + color: #777; + cursor: pointer; +} +.changeType.pressed { + background-color: #999; +} +.changeTypeMenu { + position: absolute; + border: 1px solid #ccc; + margin-top: 18px; + box-shadow: 0 1px 3px -1px #323232; + background: white; + padding: 0; + font-size: 13px; + display: none; + z-index: 10; +} +.changeTypeMenu li { + text-align: left; + list-style: none; + padding: 2px 20px; + cursor: pointer; + margin-bottom: 0; +} +.changeTypeMenu li.active:before { + font-size: 12px; + content: "\2714"; + margin-left: -15px; + margin-right: 3px; +} +.changeTypeMenu li:hover { + background: #eee; +} + diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index bd3b6b3c945..7857ae7f55e 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -966,6 +966,85 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r }; var setTable = function(data, refresh) { + function addButtonMenuEvent(button, menu) { + Handsontable.Dom.addEvent(button, 'click', function(event) { + var changeTypeMenu; + var position; + var removeMenu; + + document.body.appendChild(menu); + + event.preventDefault(); + event.stopImmediatePropagation(); + + changeTypeMenu = document.querySelectorAll('.changeTypeMenu'); + + for (var i = 0, len = changeTypeMenu.length; i < len; i++) { + changeTypeMenu[i].style.display = 'none'; + } + menu.style.display = 'block'; + position = button.getBoundingClientRect(); + + menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px'; + menu.style.left = (position.left) + 'px'; + + removeMenu = function(event) { + if (menu.parentNode) { + menu.parentNode.removeChild(menu); + } + if (!(event.target.nodeName === 'LI' && event.target.parentNode.className.indexOf('changeTypeMenu') !== -1)) { + $scope.curSelectCol = -1; // set this variable to not affect other renderTable event! + } + }; + Handsontable.Dom.removeEvent(document, 'click', removeMenu); + Handsontable.Dom.addEvent(document, 'click', removeMenu); + }); + } + + function buildMenu(activeCellType) { + var menu = document.createElement('UL'); + var types = ['text', 'numeric']; + var item; + + menu.className = 'changeTypeMenu'; + + for (var i = 0, len = types.length; i < len; i++) { + item = document.createElement('LI'); + if ('innerText' in item) { + item.innerText = types[i]; + } else { + item.textContent = types[i]; + } + + item.data = {'colType': types[i]}; + + if (activeCellType === types[i]) { + item.className = 'active'; + } + menu.appendChild(item); + } + + return menu; + } + + function buildButton() { + var button = document.createElement('BUTTON'); + + button.innerHTML = '\u25BC'; + button.className = 'changeType'; + + return button; + } + + function setColumnType(i, type, instance) { + $scope.isChangingColType = true; + $scope.curSelectCol = i; + $scope.columnFormat[i] = type; + instance.validateCells(function() { + instance.render(); + }); + } + var renderTable = function() { var height = $scope.paragraph.config.graph.height; var container = angular.element('#p' + $scope.paragraph.id + '_table').css('height', height).get(0); @@ -975,7 +1054,9 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if ($scope.hot) { $scope.hot.destroy(); } - + if (!$scope.columnFormat) { + $scope.columnFormat = Array.apply(null, Array(data.columnNames.length)).map(function() { return 'text'; }); + } $scope.hot = new Handsontable(container, { colHeaders: columnNames, data: resultRows, @@ -1000,6 +1081,22 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r cellProperties.format = '0,0.[00000]'; td.style.textAlign = 'left'; Handsontable.renderers.NumericRenderer.apply(this, arguments); + + // if (!isNaN(value)) { + // if (typeof $scope.isChangingColType !== 'undefined') { + // if ($scope.columnFormat[col] === 'numeric') { + // cellProperties.format = '0,0.[00000]'; + // td.style.textAlign = 'left'; + // Handsontable.renderers.NumericRenderer.apply(this, arguments); + // } else { + // Handsontable.renderers.TextRenderer.apply(this, arguments); + // } + // } else { + // cellProperties.format = '0,0.[00000]'; + // td.style.textAlign = 'left'; + // Handsontable.renderers.NumericRenderer.apply(this, arguments); + // $scope.columnFormat[col] = 'numeric'; + // } } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) { td.innerHTML = value.substring('%html'.length); } else { @@ -1007,6 +1104,27 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } }; return cellProperties; + }, + afterRender: function(isForced) { + $scope.isChangingColType = false; + }, + afterGetColHeader: function(col, TH) { + var instance = this; + var menu = buildMenu($scope.columnFormat[col]); + var button = buildButton(); + + addButtonMenuEvent(button, menu); + + Handsontable.Dom.addEvent(menu, 'click', function(event) { + if (event.target.nodeName === 'LI') { + setColumnType(col, event.target.data.colType, instance); + } + }); + if (TH.firstChild.lastChild.nodeName === 'BUTTON') { + TH.firstChild.removeChild(TH.firstChild.lastChild); + } + TH.firstChild.appendChild(button); + TH.style['white-space'] = 'normal'; } }); }; From 7976585849fb09d7a5de66c9b2074f8fc3aa7b4b Mon Sep 17 00:00:00 2001 From: Peilin Yang Date: Mon, 12 Sep 2016 12:41:20 -0400 Subject: [PATCH 3/8] add validator for the columns --- .../paragraph/paragraph.controller.js | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 7857ae7f55e..aa1e26001d8 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -913,9 +913,9 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if (i === 0) { columnNames.push({name: col, index: j, aggr: 'sum'}); } else { - var parsedCol = $scope.parseTableCell(col); - cols.push(parsedCol); - cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: parsedCol}); + //var parsedCol = $scope.parseTableCell(col); + cols.push(col); + cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: col}); } } if (i !== 0) { @@ -1003,7 +1003,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r function buildMenu(activeCellType) { var menu = document.createElement('UL'); - var types = ['text', 'numeric']; + var types = ['text', 'numeric', 'date']; var item; menu.className = 'changeTypeMenu'; @@ -1036,10 +1036,36 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r return button; } + function numericValidator(value, callback) { + if (!isNaN(value)) { + if (value.length !== 0 && Number(value) <= Number.MAX_SAFE_INTEGER && Number(value) >= Number.MIN_SAFE_INTEGER) { + return callback(true); + } + } + return callback(false); + } + + function dateValidator(value, callback) { + var d = moment(value); + if (d.isValid()) { + return callback(true); + } else { + return callback(false); + } + } + function setColumnType(i, type, instance) { - $scope.isChangingColType = true; + $scope.colTypeFlag[i] = true; $scope.curSelectCol = i; - $scope.columnFormat[i] = type; + $scope.columns[i].type = type; + if ($scope.columns[i].type === 'numeric') { + $scope.columns[i].validator = numericValidator; + } else if ($scope.columns[i].type === 'date') { + $scope.columns[i].validator = dateValidator; + } else { + $scope.columns[i].validator = null; + } + instance.updateSettings({columns: $scope.columns}); instance.validateCells(function() { instance.render(); }); @@ -1054,8 +1080,13 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if ($scope.hot) { $scope.hot.destroy(); } - if (!$scope.columnFormat) { - $scope.columnFormat = Array.apply(null, Array(data.columnNames.length)).map(function() { return 'text'; }); + if (!$scope.columns) { + $scope.columns = Array.apply(null, Array(data.columnNames.length)).map(function() { + return {type: 'text'}; + }); + } + if (!$scope.colTypeFlag) { + $scope.colTypeFlag = Array.apply(null, Array(data.columnNames.length)).map(function() { return null; }); } $scope.hot = new Handsontable(container, { colHeaders: columnNames, @@ -1075,28 +1106,10 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r cells: function(row, col, prop) { var cellProperties = {}; cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { - if (value instanceof moment) { - td.innerHTML = value._i; - } else if (!isNaN(value)) { + if (!isNaN(value)) { cellProperties.format = '0,0.[00000]'; td.style.textAlign = 'left'; Handsontable.renderers.NumericRenderer.apply(this, arguments); - - // if (!isNaN(value)) { - // if (typeof $scope.isChangingColType !== 'undefined') { - // if ($scope.columnFormat[col] === 'numeric') { - // cellProperties.format = '0,0.[00000]'; - // td.style.textAlign = 'left'; - // Handsontable.renderers.NumericRenderer.apply(this, arguments); - // } else { - // Handsontable.renderers.TextRenderer.apply(this, arguments); - // } - // } else { - // cellProperties.format = '0,0.[00000]'; - // td.style.textAlign = 'left'; - // Handsontable.renderers.NumericRenderer.apply(this, arguments); - // $scope.columnFormat[col] = 'numeric'; - // } } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) { td.innerHTML = value.substring('%html'.length); } else { @@ -1106,11 +1119,13 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r return cellProperties; }, afterRender: function(isForced) { - $scope.isChangingColType = false; + if ($scope.colTypeFlag[$scope.curSelectCol]) { + $scope.colTypeFlag[$scope.curSelectCol] = false; + } }, afterGetColHeader: function(col, TH) { var instance = this; - var menu = buildMenu($scope.columnFormat[col]); + var menu = buildMenu($scope.columns[col].type); var button = buildButton(); addButtonMenuEvent(button, menu); From d088fc2e22c217d06fd93ef85c90d60ff482ff80 Mon Sep 17 00:00:00 2001 From: Peilin Yang Date: Mon, 12 Sep 2016 13:23:50 -0400 Subject: [PATCH 4/8] change the format of numeric column --- .../notebook/paragraph/paragraph.controller.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index aa1e26001d8..f22eb0e8b02 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -1036,13 +1036,19 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r return button; } - function numericValidator(value, callback) { + function isNumeric(value) { if (!isNaN(value)) { - if (value.length !== 0 && Number(value) <= Number.MAX_SAFE_INTEGER && Number(value) >= Number.MIN_SAFE_INTEGER) { - return callback(true); + if (value.length !== 0) { + if (Number(value) <= Number.MAX_SAFE_INTEGER && Number(value) >= Number.MIN_SAFE_INTEGER) { + return true; + } } } - return callback(false); + return false; + } + + function numericValidator(value, callback) { + return callback(isNumeric(value)); } function dateValidator(value, callback) { @@ -1106,7 +1112,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r cells: function(row, col, prop) { var cellProperties = {}; cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { - if (!isNaN(value)) { + if ($scope.columns[col].type === 'numeric' && isNumeric(value)) { cellProperties.format = '0,0.[00000]'; td.style.textAlign = 'left'; Handsontable.renderers.NumericRenderer.apply(this, arguments); From 40f0a276fb346f81b9cc7d75a4e5ee974e938c03 Mon Sep 17 00:00:00 2001 From: Peilin Yang Date: Thu, 22 Sep 2016 11:44:21 -0400 Subject: [PATCH 5/8] fix the bug: data type lost when switching between tables and charts. --- .../paragraph/paragraph.controller.js | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index f22eb0e8b02..3a0a49c0a00 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -992,9 +992,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if (menu.parentNode) { menu.parentNode.removeChild(menu); } - if (!(event.target.nodeName === 'LI' && event.target.parentNode.className.indexOf('changeTypeMenu') !== -1)) { - $scope.curSelectCol = -1; // set this variable to not affect other renderTable event! - } }; Handsontable.Dom.removeEvent(document, 'click', removeMenu); Handsontable.Dom.addEvent(document, 'click', removeMenu); @@ -1060,21 +1057,23 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } } + function setColumnValidator() { + for (var i = 0; i < $scope.columns.length; ++i) { + if ($scope.columns[i].type === 'numeric') { + $scope.columns[i].validator = numericValidator; + } else if ($scope.columns[i].type === 'date') { + $scope.columns[i].validator = dateValidator; + } else { + $scope.columns[i].validator = null; + } + } + } + function setColumnType(i, type, instance) { - $scope.colTypeFlag[i] = true; - $scope.curSelectCol = i; $scope.columns[i].type = type; - if ($scope.columns[i].type === 'numeric') { - $scope.columns[i].validator = numericValidator; - } else if ($scope.columns[i].type === 'date') { - $scope.columns[i].validator = dateValidator; - } else { - $scope.columns[i].validator = null; - } + setColumnValidator(); instance.updateSettings({columns: $scope.columns}); - instance.validateCells(function() { - instance.render(); - }); + instance.validateCells(null); } var renderTable = function() { @@ -1091,15 +1090,13 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r return {type: 'text'}; }); } - if (!$scope.colTypeFlag) { - $scope.colTypeFlag = Array.apply(null, Array(data.columnNames.length)).map(function() { return null; }); - } $scope.hot = new Handsontable(container, { colHeaders: columnNames, data: resultRows, rowHeaders: false, stretchH: 'all', sortIndicator: true, + columns: $scope.columns, columnSorting: true, contextMenu: false, manualColumnResize: true, @@ -1124,11 +1121,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r }; return cellProperties; }, - afterRender: function(isForced) { - if ($scope.colTypeFlag[$scope.curSelectCol]) { - $scope.colTypeFlag[$scope.curSelectCol] = false; - } - }, afterGetColHeader: function(col, TH) { var instance = this; var menu = buildMenu($scope.columns[col].type); @@ -1148,6 +1140,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r TH.style['white-space'] = 'normal'; } }); + $scope.hot.validateCells(null); }; var retryRenderer = function() { From caeffa9268af6a30d754336b372690444e698850 Mon Sep 17 00:00:00 2001 From: Damien CORNEAU Date: Tue, 4 Oct 2016 18:01:21 +0900 Subject: [PATCH 6/8] Move HandsonTable functions to a Service --- .../paragraph/paragraph.controller.js | 164 +-------------- .../dataTypeService/dataType.service.js | 36 ++++ .../handsonHelper.service.js | 195 ++++++++++++++++++ zeppelin-web/src/index.html | 2 + 4 files changed, 237 insertions(+), 160 deletions(-) create mode 100644 zeppelin-web/src/components/dataTypeService/dataType.service.js create mode 100644 zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 3a0a49c0a00..163709bc4f5 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -16,7 +16,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $rootScope, $route, $window, $routeParams, $location, $timeout, $compile, $http, websocketMsgSrv, baseUrlSrv, ngToast, - saveAsService, esriLoader) { + saveAsService, esriLoader, handsonHelperService, + dataTypeService) { var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_'; $scope.parentNote = null; $scope.paragraph = null; @@ -966,115 +967,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r }; var setTable = function(data, refresh) { - function addButtonMenuEvent(button, menu) { - Handsontable.Dom.addEvent(button, 'click', function(event) { - var changeTypeMenu; - var position; - var removeMenu; - - document.body.appendChild(menu); - - event.preventDefault(); - event.stopImmediatePropagation(); - - changeTypeMenu = document.querySelectorAll('.changeTypeMenu'); - - for (var i = 0, len = changeTypeMenu.length; i < len; i++) { - changeTypeMenu[i].style.display = 'none'; - } - menu.style.display = 'block'; - position = button.getBoundingClientRect(); - - menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px'; - menu.style.left = (position.left) + 'px'; - - removeMenu = function(event) { - if (menu.parentNode) { - menu.parentNode.removeChild(menu); - } - }; - Handsontable.Dom.removeEvent(document, 'click', removeMenu); - Handsontable.Dom.addEvent(document, 'click', removeMenu); - }); - } - - function buildMenu(activeCellType) { - var menu = document.createElement('UL'); - var types = ['text', 'numeric', 'date']; - var item; - - menu.className = 'changeTypeMenu'; - - for (var i = 0, len = types.length; i < len; i++) { - item = document.createElement('LI'); - if ('innerText' in item) { - item.innerText = types[i]; - } else { - item.textContent = types[i]; - } - - item.data = {'colType': types[i]}; - - if (activeCellType === types[i]) { - item.className = 'active'; - } - menu.appendChild(item); - } - - return menu; - } - - function buildButton() { - var button = document.createElement('BUTTON'); - - button.innerHTML = '\u25BC'; - button.className = 'changeType'; - - return button; - } - - function isNumeric(value) { - if (!isNaN(value)) { - if (value.length !== 0) { - if (Number(value) <= Number.MAX_SAFE_INTEGER && Number(value) >= Number.MIN_SAFE_INTEGER) { - return true; - } - } - } - return false; - } - - function numericValidator(value, callback) { - return callback(isNumeric(value)); - } - - function dateValidator(value, callback) { - var d = moment(value); - if (d.isValid()) { - return callback(true); - } else { - return callback(false); - } - } - - function setColumnValidator() { - for (var i = 0; i < $scope.columns.length; ++i) { - if ($scope.columns[i].type === 'numeric') { - $scope.columns[i].validator = numericValidator; - } else if ($scope.columns[i].type === 'date') { - $scope.columns[i].validator = dateValidator; - } else { - $scope.columns[i].validator = null; - } - } - } - - function setColumnType(i, type, instance) { - $scope.columns[i].type = type; - setColumnValidator(); - instance.updateSettings({columns: $scope.columns}); - instance.validateCells(null); - } var renderTable = function() { var height = $scope.paragraph.config.graph.height; @@ -1090,56 +982,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r return {type: 'text'}; }); } - $scope.hot = new Handsontable(container, { - colHeaders: columnNames, - data: resultRows, - rowHeaders: false, - stretchH: 'all', - sortIndicator: true, - columns: $scope.columns, - columnSorting: true, - contextMenu: false, - manualColumnResize: true, - manualRowResize: true, - readOnly: true, - readOnlyCellClassName: '', // don't apply any special class so we can retain current styling - fillHandle: false, - fragmentSelection: true, - disableVisualSelection: true, - cells: function(row, col, prop) { - var cellProperties = {}; - cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { - if ($scope.columns[col].type === 'numeric' && isNumeric(value)) { - cellProperties.format = '0,0.[00000]'; - td.style.textAlign = 'left'; - Handsontable.renderers.NumericRenderer.apply(this, arguments); - } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) { - td.innerHTML = value.substring('%html'.length); - } else { - Handsontable.renderers.TextRenderer.apply(this, arguments); - } - }; - return cellProperties; - }, - afterGetColHeader: function(col, TH) { - var instance = this; - var menu = buildMenu($scope.columns[col].type); - var button = buildButton(); - - addButtonMenuEvent(button, menu); - - Handsontable.Dom.addEvent(menu, 'click', function(event) { - if (event.target.nodeName === 'LI') { - setColumnType(col, event.target.data.colType, instance); - } - }); - if (TH.firstChild.lastChild.nodeName === 'BUTTON') { - TH.firstChild.removeChild(TH.firstChild.lastChild); - } - TH.firstChild.appendChild(button); - TH.style['white-space'] = 'normal'; - } - }); + $scope.hot = new Handsontable(container, handsonHelperService.getHandsonTableConfig( + $scope.columns, columnNames, resultRows)); $scope.hot.validateCells(null); }; diff --git a/zeppelin-web/src/components/dataTypeService/dataType.service.js b/zeppelin-web/src/components/dataTypeService/dataType.service.js new file mode 100644 index 00000000000..b57b6b91310 --- /dev/null +++ b/zeppelin-web/src/components/dataTypeService/dataType.service.js @@ -0,0 +1,36 @@ +/* + * 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. + */ +'use strict'; +(function() { + + angular.module('zeppelinWebApp').service('dataTypeService', dataTypeService); + + dataTypeService.$inject = []; + + function dataTypeService() { + + this.isNumeric = function(value) { + if (!isNaN(value)) { + if (value.length !== 0) { + if (Number(value) <= Number.MAX_SAFE_INTEGER && Number(value) >= Number.MIN_SAFE_INTEGER) { + return true; + } + } + } + return false; + }; + + } + +})(); diff --git a/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js b/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js new file mode 100644 index 00000000000..272723d0e0a --- /dev/null +++ b/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js @@ -0,0 +1,195 @@ +/* + * 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. + */ +'use strict'; +(function() { + + angular.module('zeppelinWebApp').factory('handsonHelperService', handsonHelperService); + + handsonHelperService.$inject = ['dataTypeService']; + + function handsonHelperService(dataTypeService) { + + var service = { + getHandsonTableConfig: getHandsonTableConfig + }; + + /* + ** Public Service Functions + */ + + function getHandsonTableConfig(columns, columnNames, resultRows) { + return { + colHeaders: columnNames, + data: resultRows, + rowHeaders: false, + stretchH: 'all', + sortIndicator: true, + columns: columns, + columnSorting: true, + contextMenu: false, + manualColumnResize: true, + manualRowResize: true, + readOnly: true, + readOnlyCellClassName: '', + fillHandle: false, + fragmentSelection: true, + disableVisualSelection: true, + cells: function(ro, co, pro) { + var cellProperties = {}; + var colType = columns[co].type; + cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { + _cellRenderer(instance, td, row, col, prop, value, cellProperties, colType); + }; + return cellProperties; + }, + afterGetColHeader: function(col, TH) { + var instance = this; + var menu = _buildDropDownMenu(columns[col].type); + var button = _buildTypeSwitchButton(); + + _addButtonMenuEvent(button, menu); + + Handsontable.Dom.addEvent(menu, 'click', function(event) { + if (event.target.nodeName === 'LI') { + _setColumnType(columns, event.target.data.colType, instance, col); + } + }); + if (TH.firstChild.lastChild.nodeName === 'BUTTON') { + TH.firstChild.removeChild(TH.firstChild.lastChild); + } + TH.firstChild.appendChild(button); + TH.style['white-space'] = 'normal'; + } + }; + } + + /* + ** Private Service Functions + */ + + function _addButtonMenuEvent(button, menu) { + Handsontable.Dom.addEvent(button, 'click', function(event) { + var changeTypeMenu; + var position; + var removeMenu; + + document.body.appendChild(menu); + + event.preventDefault(); + event.stopImmediatePropagation(); + + changeTypeMenu = document.querySelectorAll('.changeTypeMenu'); + + for (var i = 0, len = changeTypeMenu.length; i < len; i++) { + changeTypeMenu[i].style.display = 'none'; + } + menu.style.display = 'block'; + position = button.getBoundingClientRect(); + + menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px'; + menu.style.left = (position.left) + 'px'; + + removeMenu = function(event) { + if (menu.parentNode) { + menu.parentNode.removeChild(menu); + } + }; + Handsontable.Dom.removeEvent(document, 'click', removeMenu); + Handsontable.Dom.addEvent(document, 'click', removeMenu); + }); + } + + function _buildDropDownMenu(activeCellType) { + var menu = document.createElement('UL'); + var types = ['text', 'numeric', 'date']; + var item; + + menu.className = 'changeTypeMenu'; + + for (var i = 0, len = types.length; i < len; i++) { + item = document.createElement('LI'); + if ('innerText' in item) { + item.innerText = types[i]; + } else { + item.textContent = types[i]; + } + + item.data = {'colType': types[i]}; + + if (activeCellType === types[i]) { + item.className = 'active'; + } + menu.appendChild(item); + } + + return menu; + } + + function _buildTypeSwitchButton() { + var button = document.createElement('BUTTON'); + + button.innerHTML = '\u25BC'; + button.className = 'changeType'; + + return button; + } + + function _cellRenderer(instance, td, row, col, prop, value, cellProperties, colType) { + if (colType === 'numeric' && dataTypeService.isNumeric(value)) { + cellProperties.format = '0,0.[00000]'; + td.style.textAlign = 'left'; + Handsontable.renderers.NumericRenderer.apply(this, arguments); + } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) { + td.innerHTML = value.substring('%html'.length); + } else { + Handsontable.renderers.TextRenderer.apply(this, arguments); + } + } + + function _dateValidator(value, callback) { + var d = moment(value); + if (d.isValid()) { + return callback(true); + } else { + return callback(false); + } + } + + function _numericValidator(value, callback) { + return callback(dataTypeService.isNumeric(value)); + } + + function _setColumnType(columns, type, instance, col) { + columns[col].type = type; + _setColumnValidator(columns); + instance.updateSettings({columns: columns}); + instance.validateCells(null); + } + + function _setColumnValidator(columns) { + for (var i = 0; i < columns.length; ++i) { + if (columns[i].type === 'numeric') { + columns[i].validator = _numericValidator; + } else if (columns[i].type === 'date') { + columns[i].validator = _dateValidator; + } else { + columns[i].validator = null; + } + } + } + + return service; + } + +})(); diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 9b049552d59..50e81b69587 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -184,6 +184,8 @@ + + From 4e9b1f306a7ac71d12cb70a78c4ace7bf29ad101 Mon Sep 17 00:00:00 2001 From: Damien CORNEAU Date: Wed, 5 Oct 2016 16:02:34 +0900 Subject: [PATCH 7/8] Resort column after type change --- .../handsonHelperService/handsonHelper.service.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js b/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js index 272723d0e0a..c3214656686 100644 --- a/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js +++ b/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js @@ -175,10 +175,17 @@ _setColumnValidator(columns); instance.updateSettings({columns: columns}); instance.validateCells(null); + if (_isColumnSorted(instance, col)) { + instance.sort(col, instance.sortOrder); + } + } + + function _isColumnSorted(instance, col) { + return instance.sortingEnabled && instance.sortColumn === col; } function _setColumnValidator(columns) { - for (var i = 0; i < columns.length; ++i) { + for (var i = 0; i < columns.length; i++) { if (columns[i].type === 'numeric') { columns[i].validator = _numericValidator; } else if (columns[i].type === 'date') { From 82770b20dc3783564f1bfbc02e4e904032ae0ca7 Mon Sep 17 00:00:00 2001 From: Damien CORNEAU Date: Wed, 5 Oct 2016 16:50:57 +0900 Subject: [PATCH 8/8] Small refactoring --- .../src/app/notebook/paragraph/paragraph.css | 2 +- .../handsonHelper.service.js | 24 +++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css index aa9856fb0cf..03e167cc8ba 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css @@ -552,7 +552,7 @@ table.table-striped { .changeTypeMenu { position: absolute; border: 1px solid #ccc; - margin-top: 18px; + margin-top: 22px; box-shadow: 0 1px 3px -1px #323232; background: white; padding: 0; diff --git a/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js b/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js index c3214656686..b896e6dd41d 100644 --- a/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js +++ b/zeppelin-web/src/components/handsonHelperService/handsonHelper.service.js @@ -159,11 +159,7 @@ function _dateValidator(value, callback) { var d = moment(value); - if (d.isValid()) { - return callback(true); - } else { - return callback(false); - } + return callback(d.isValid() ? true : false); } function _numericValidator(value, callback) { @@ -172,7 +168,7 @@ function _setColumnType(columns, type, instance, col) { columns[col].type = type; - _setColumnValidator(columns); + _setColumnValidator(columns, col); instance.updateSettings({columns: columns}); instance.validateCells(null); if (_isColumnSorted(instance, col)) { @@ -184,15 +180,13 @@ return instance.sortingEnabled && instance.sortColumn === col; } - function _setColumnValidator(columns) { - for (var i = 0; i < columns.length; i++) { - if (columns[i].type === 'numeric') { - columns[i].validator = _numericValidator; - } else if (columns[i].type === 'date') { - columns[i].validator = _dateValidator; - } else { - columns[i].validator = null; - } + function _setColumnValidator(columns, col) { + if (columns[col].type === 'numeric') { + columns[col].validator = _numericValidator; + } else if (columns[col].type === 'date') { + columns[col].validator = _dateValidator; + } else { + columns[col].validator = null; } }