Repository: zeppelin
Updated Branches:
  refs/heads/master c7c9aa1cc -> bed82eb4b


[ZEPPELIN-2411] Improve Table

### What is this PR for?

**Improve Table**

- column filter
- persist column state: type, order, hide/show, sorting, pinning
- pagination
- `setting` menu to configure table UI
- group by + aggregation

And **all these things are persisted and synchronized among web socket clients**

See the screenshot section for more detail.

### What type of PR is it?
[Improvement]

### Todos
* [x] - Remove handsontable dependencies
* [x] - Use npm packaged moment* packages.
* [x] - Apply ui-grid
* [x] - Add setting menu
* [x] - Fix some issues
* [x] - Persist column type

### What is the Jira issue?

[ZEPPELIN-2411](https://issues.apache.org/jira/browse/ZEPPELIN-2411)

### How should this be tested?

1. Build: `mvn clean package -DskipTests; ./bin/zeppelin-daemon.sh restart`
2. Open a note and create tables. If you don't have proper paragraphs, use this 
snippet.

```scala
%spark

import org.apache.commons.io.IOUtils
import java.net.URL
import java.nio.charset.Charset

val bankText = sc.parallelize(
    IOUtils.toString(
        new 
URL("https://s3.amazonaws.com/apache-zeppelin/tutorial/bank/bank.csv";),
        Charset.forName("utf8")).split("\n"))

case class Bank(
    age: Integer,
    job: String,
    marital: String,
    education: String,
    balance: Integer,
    housing: Boolean,
    loan: Boolean,
    contact: String,
    day: Int,
    month: String,
    duration: Int,
    y: Boolean
)

val bank = bankText.map(s => s.split(";")).filter(s => s(0) != "\"age\"").map(
    s => Bank(s(0).toInt,
            s(1).replaceAll("\"", ""),
            s(2).replaceAll("\"", ""),
            s(3).replaceAll("\"", ""),
            s(5).replaceAll("\"", "").toInt,
            if (s(6).replaceAll("\"", "") == "yes") true else false,
            if (s(7).replaceAll("\"", "") == "yes") true else false,
            s(8).replaceAll("\"", ""),
            s(9).replaceAll("\"", "").toInt,
            s(10).replaceAll("\"", ""),
            s(11).replaceAll("\"", "").toInt,
            if (s(16).replaceAll("\"", "") == "yes") true else false
        )
).toDF()
bank.registerTempTable("bank")
```

```sql
select age, education, job, balance from bank limit 1000
```

### Screenshots (if appropriate)

#### Before

![image](https://cloud.githubusercontent.com/assets/4968473/25803644/d0d81524-3432-11e7-8cf6-dde16465a447.png)

#### After: Filter

![2411_new_filter](https://cloud.githubusercontent.com/assets/4968473/25880089/4e1e6a5e-3570-11e7-8339-36e712a170cc.gif)

#### After: Column related features

![2411_new_column](https://cloud.githubusercontent.com/assets/4968473/25880690/d8a4da02-3573-11e7-9851-cc359fd6075c.gif)

#### After: Pagination

![2411_new_pagination](https://cloud.githubusercontent.com/assets/4968473/25880646/ad3b5ea4-3573-11e7-8e03-e56af683eb9b.gif)

#### After: Group, Aggregation

![2411_new_group_aggr](https://cloud.githubusercontent.com/assets/4968473/25880819/97ba7a82-3574-11e7-8dc4-8d39df6a65a0.gif)

#### After: Synchronized

![2411_new_sync](https://cloud.githubusercontent.com/assets/4968473/25880669/c1784bb6-3573-11e7-966d-cb21d222d683.gif)

### Questions:
* Does the licenses files need update? - YES, updated
* Is there breaking changes for older versions? - NO
* Does this needs documentation? - NO

Author: 1ambda <[email protected]>

Closes #2323 from 1ambda/ZEPPELIN-2411/prettify-table and squashes the 
following commits:

c56edca [1ambda] fix: Change table cell color to white
2047c5f [1ambda] feat: Render HTML display in cell
2a79915 [1ambda] fix: Add tooltip and change icon for restore
39d64c1 [1ambda] fixup: tooltip
2b40978 [1ambda] fix: Remove save btn in table setting panel
bd2b4c8 [1ambda] fix: Add tooltip for description
3a87718 [1ambda] fix: DON'T display type of table options
1885172 [1ambda] fix: Remove pagination related options from table spec
4878614 [1ambda] fix: order for table options
e4d2c37 [1ambda] fix: Disable selection
06e7841 [1ambda] feat: Add option for selection
a9d313d [1ambda] fix: Remove desc in opt specs
cf597bf [1ambda] fix: Reset tableColumnTypeState
399116b [1ambda] fix: SparkParagraphIT for table
84c04ac [1ambda] fix: Remove duplicated console
fdcfe7f [1ambda] chore: Remove unused license
fc2f6d0 [1ambda] fix: css loader for karma test
4742a28 [1ambda] fix: RAT issue
8ea8ae5 [1ambda] fix: DON'T debounce for emit
a1ae980 [1ambda] feat: Persist type
d928290 [1ambda] fix: Use isRestoring flag to avoid triggering event when 
initializing
a262b79 [1ambda] fix: Persist tableOption immediately
f83c070 [1ambda] fix: Commit graph config when closing
6db38ae [1ambda] feat: Add missing change events
911c0e7 [1ambda] fix: Prevent recursive emitting
d47ccc8 [1ambda] feat: Persist grid state
b433d7f [1ambda] refactor: Add getGrid* funcs
1480841 [1ambda] fix: Remove refresh in menu actions
35d99e9 [1ambda] fix: enable scroll in col menus
25a72fe [1ambda] feat: Add types to menu
acc38cf [1ambda] feat: Add paginiation table opts
8793d8f [1ambda] fix: set valid pagination opts
744dc66 [1ambda] docs: Update desc for table option
8bd8256 [1ambda] fix: persist initial config
78cec42 [1ambda] feat: resetTableOption
475bc31 [1ambda] feat: Add table option
fc0abd4 [1ambda] feat: Use tabledata
85cdd8e [1ambda] feat: render setting for table
5ee6a2e [1ambda] fix: Remove handsonhelper while using moment form npm
2444855 [1ambda] refactor: variable name
ed17862 [1ambda] fix: Remove unused css
1f61260 [1ambda] refactor: extract table related css to display-table.css


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/bed82eb4
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/bed82eb4
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/bed82eb4

Branch: refs/heads/master
Commit: bed82eb4bdf20db5c894444ccaed8a8631846997
Parents: c7c9aa1
Author: 1ambda <[email protected]>
Authored: Fri May 19 00:43:23 2017 +0900
Committer: Lee moon soo <[email protected]>
Committed: Sat May 20 15:10:20 2017 -0400

----------------------------------------------------------------------
 zeppelin-distribution/src/bin_license/LICENSE   |   6 +-
 .../zeppelin/integration/SparkParagraphIT.java  |  14 +-
 zeppelin-web/bower.json                         |   2 -
 zeppelin-web/karma.conf.js                      |   5 -
 zeppelin-web/package.json                       |   7 +-
 zeppelin-web/src/app/app.js                     |  13 +
 .../src/app/handsontable/handsonHelper.js       | 201 ----------
 .../src/app/jobmanager/jobs/job.controller.js   |   2 +
 .../src/app/notebook/notebook-actionBar.html    |   2 +-
 .../src/app/notebook/notebook.controller.js     |   9 +-
 .../notebook/paragraph/paragraph.controller.js  |  20 +-
 .../src/app/notebook/paragraph/paragraph.css    | 103 -----
 .../notebook/paragraph/result/display-table.css |  49 +++
 .../paragraph/result/result-chart-selector.html |   2 +-
 .../paragraph/result/result.controller.js       |  21 +-
 .../app/notebook/paragraph/result/result.html   |   4 +-
 .../visualization-table-grid-filter.html        |  27 ++
 .../builtins/visualization-table-setting.html   |  84 ++++
 .../builtins/visualization-table.js             | 385 +++++++++++++++++--
 .../builtins/visualization-util.js              | 172 +++++++++
 .../components/editor/codeEditor.directive.js   |   2 +-
 .../websocketEvents/websocketEvents.factory.js  |   3 +-
 zeppelin-web/src/index.html                     |   8 +-
 zeppelin-web/src/index.js                       |   1 -
 zeppelin-web/webpack.config.js                  |   1 -
 25 files changed, 774 insertions(+), 369 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-distribution/src/bin_license/LICENSE
----------------------------------------------------------------------
diff --git a/zeppelin-distribution/src/bin_license/LICENSE 
b/zeppelin-distribution/src/bin_license/LICENSE
index 60fe5c0..7dd1fb3 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -244,9 +244,9 @@ 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 v2.0.0 (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) moment v2.18.1 (https://github.com/moment/moment) - 
https://github.com/moment/moment/blob/2.18.1/LICENSE
+    (The MIT License) moment-duration-format v1.3.0 
(https://github.com/jsmreese/moment-duration-format) - 
https://github.com/jsmreese/moment-duration-format/blob/1.3.0/LICENSE
+    (The MIT License) angular-ui-grid v4.0.4 
(https://github.com/angular-ui/ui-grid) - 
https://github.com/angular-ui/ui-grid/blob/v4.0.4/LICENSE.md
     (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 v1.7.21 (org.slf4j:slf4j-simple:1.7.21 - 
http://www.slf4j.org) - http://www.slf4j.org/license.html

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java
----------------------------------------------------------------------
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 2cc6a6c..0aa0354 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
@@ -32,6 +32,8 @@ import org.openqa.selenium.WebElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 public class SparkParagraphIT extends AbstractZeppelinIT {
   private static final Logger LOG = 
LoggerFactory.getLogger(SparkParagraphIT.class);
 
@@ -182,10 +184,16 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
         );
       }
 
-      WebElement paragraph1Result = driver.findElement(By.xpath(
-          getParagraphXPath(1) + 
"//div[contains(@id,\"_graph\")]/div/div/div/div/div[1]"));
+      // Age, Job, Marital, Education, Balance
+      List<WebElement> tableHeaders = 
driver.findElements(By.cssSelector("span.ui-grid-header-cell-label"));
+      String headerNames = "";
+
+      for(WebElement header : tableHeaders) {
+        headerNames += header.getText().toString() + "|";
+      }
+
       collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark 
result: ",
-          paragraph1Result.getText().toString(), 
CoreMatchers.equalTo("age\n▼\njob\n▼\nmarital\n▼\neducation\n▼\nbalance\n▼\n30
 unemployed married primary 1787"));
+          headerNames, 
CoreMatchers.equalTo("Age|Job|Marital|Education|Balance|"));
     } catch (Exception e) {
       handleException("Exception in SparkParagraphIT while testSqlSpark", e);
     }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/bower.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index 30fc5f0..8e9a1e5 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -30,8 +30,6 @@
     "ngtoast": "~2.0.0",
     "ng-focus-if": "~1.0.2",
     "bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
-    "handsontable": "~0.24.2",
-    "moment-duration-format": "^1.3.0",
     "select2": "^4.0.3",
     "MathJax": "2.7.0",
     "ngclipboard": "^1.1.1"

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/karma.conf.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/karma.conf.js b/zeppelin-web/karma.conf.js
index f47741a..b163b56 100644
--- a/zeppelin-web/karma.conf.js
+++ b/zeppelin-web/karma.conf.js
@@ -81,11 +81,6 @@ 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/zeroclipboard/dist/ZeroClipboard.js',
-      'bower_components/moment/moment.js',
-      'bower_components/pikaday/pikaday.js',
-      'bower_components/handsontable/dist/handsontable.js',
-      'bower_components/moment-duration-format/lib/moment-duration-format.js',
       'bower_components/select2/dist/js/select2.js',
       'bower_components/MathJax/MathJax.js',
       'bower_components/clipboard/dist/clipboard.js',

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/package.json b/zeppelin-web/package.json
index a992a0f..39b230a 100644
--- a/zeppelin-web/package.json
+++ b/zeppelin-web/package.json
@@ -18,14 +18,16 @@
     "dev:helium": "HELIUM_BUNDLE_DEV=true webpack-dev-server --hot",
     "dev:watch": "grunt watch-webpack-dev",
     "dev": "npm-run-all --parallel dev:server lint:watch dev:watch",
-    "pretest": "npm install karma-phantomjs-launcher babel-polyfill",
     "test": "karma start karma.conf.js"
   },
   "dependencies": {
     "github-markdown-css": "2.6.0",
+    "angular-ui-grid": "^4.0.4",
     "grunt-angular-templates": "^0.5.7",
     "grunt-dom-munger": "^3.4.0",
-    "headroom.js": "^0.9.3"
+    "headroom.js": "^0.9.3",
+    "moment": "^2.18.1",
+    "moment-duration-format": "^1.3.0"
   },
   "devDependencies": {
     "autoprefixer": "^6.5.4",
@@ -73,6 +75,7 @@
     "karma-coverage": "^1.1.1",
     "karma-jasmine": "~1.0.2",
     "karma-sourcemap-loader": "^0.3.7",
+    "karma-phantomjs-launcher": "^1.0.4",
     "karma-webpack": "^1.8.1",
     "load-grunt-tasks": "^0.4.0",
     "ng-annotate-loader": "^0.2.0",

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/app.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js
index fc5ffbd..427d340 100644
--- a/zeppelin-web/src/app/app.js
+++ b/zeppelin-web/src/app/app.js
@@ -18,6 +18,9 @@
 import 'headroom.js'
 import 'headroom.js/dist/angular.headroom'
 
+import 'angular-ui-grid/ui-grid.css'
+import 'angular-ui-grid'
+
 const requiredModules = [
   'ngCookies',
   'ngAnimate',
@@ -37,6 +40,16 @@ const requiredModules = [
   'focus-if',
   'ngResource',
   'ngclipboard',
+  'ui.grid',
+  'ui.grid.exporter',
+  'ui.grid.edit', 'ui.grid.rowEdit',
+  'ui.grid.selection',
+  'ui.grid.cellNav', 'ui.grid.pinning',
+  'ui.grid.grouping',
+  'ui.grid.emptyBaseLayer',
+  'ui.grid.resizeColumns', 'ui.grid.moveColumns',
+  'ui.grid.pagination',
+  'ui.grid.saveState',
 ]
 
 // headroom should not be used for CI, since we have to execute some 
integration tests.

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/handsontable/handsonHelper.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/handsontable/handsonHelper.js 
b/zeppelin-web/src/app/handsontable/handsonHelper.js
deleted file mode 100644
index 8d724c0..0000000
--- a/zeppelin-web/src/app/handsontable/handsonHelper.js
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * HandsonHelper class
- */
-export default class HandsonHelper {
-  constructor (columns, rows, comment) {
-    this.columns = columns || []
-    this.rows = rows || []
-    this.comment = comment || ''
-    this._numericValidator = this._numericValidator.bind(this)
-  }
-
-  getHandsonTableConfig (columns, columnNames, resultRows) {
-    let self = this
-    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) {
-        let cellProperties = {}
-        let colType = columns[co].type
-        cellProperties.renderer = function (instance, td, row, col, prop, 
value, cellProperties) {
-          self._cellRenderer(instance, td, row, col, prop, value, 
cellProperties, colType)
-        }
-        return cellProperties
-      },
-      afterGetColHeader: function (col, TH) {
-        let instance = this
-        let menu = self._buildDropDownMenu(columns[col].type)
-        let button = self._buildTypeSwitchButton()
-
-        self._addButtonMenuEvent(button, menu)
-
-        Handsontable.Dom.addEvent(menu, 'click', function (event) {
-          if (event.target.nodeName === 'LI') {
-            self._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
-  */
-
-  _addButtonMenuEvent (button, menu) {
-    Handsontable.Dom.addEvent(button, 'click', function (event) {
-      let changeTypeMenu
-      let position
-      let removeMenu
-
-      document.body.appendChild(menu)
-
-      event.preventDefault()
-      event.stopImmediatePropagation()
-
-      changeTypeMenu = document.querySelectorAll('.changeTypeMenu')
-
-      for (let 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)
-    })
-  }
-
-  _buildDropDownMenu (activeCellType) {
-    let menu = document.createElement('UL')
-    let types = ['text', 'numeric', 'date']
-    let item
-
-    menu.className = 'changeTypeMenu'
-
-    for (let 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
-  }
-
-  _buildTypeSwitchButton () {
-    let button = document.createElement('BUTTON')
-
-    button.innerHTML = '\u25BC'
-    button.className = 'changeType'
-
-    return button
-  }
-
-  _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
-  }
-
-  _cellRenderer (instance, td, row, col, prop, value, cellProperties, colType) 
{
-    if (colType === 'numeric' && this._isNumeric(value)) {
-      cellProperties.format = '0,0.[00000]'
-      td.style.textAlign = 'left'
-      // eslint-disable-next-line prefer-rest-params
-      Handsontable.renderers.NumericRenderer.apply(this, arguments)
-    } else if (value.length > '%html'.length && value.substring(0, '%html 
'.length) === '%html ') {
-      td.innerHTML = value.substring('%html'.length)
-    } else {
-      // eslint-disable-next-line prefer-rest-params
-      Handsontable.renderers.TextRenderer.apply(this, arguments)
-    }
-  }
-
-  _dateValidator (value, callback) {
-    let d = moment(value)
-    return callback(d.isValid())
-  }
-
-  _numericValidator (value, callback) {
-    return callback(this._isNumeric(value))
-  }
-
-  _setColumnType (columns, type, instance, col) {
-    columns[col].type = type
-    this._setColumnValidator(columns, col)
-    instance.updateSettings({columns: columns})
-    instance.validateCells(null)
-    if (this._isColumnSorted(instance, col)) {
-      instance.sort(col, instance.sortOrder)
-    }
-  }
-
-  _isColumnSorted (instance, col) {
-    return instance.sortingEnabled && instance.sortColumn === col
-  }
-
-  _setColumnValidator (columns, col) {
-    if (columns[col].type === 'numeric') {
-      columns[col].validator = this._numericValidator
-    } else if (columns[col].type === 'date') {
-      columns[col].validator = this._dateValidator
-    } else {
-      columns[col].validator = null
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/jobmanager/jobs/job.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/jobmanager/jobs/job.controller.js 
b/zeppelin-web/src/app/jobmanager/jobs/job.controller.js
index e1ce02d..e811f7b 100644
--- a/zeppelin-web/src/app/jobmanager/jobs/job.controller.js
+++ b/zeppelin-web/src/app/jobmanager/jobs/job.controller.js
@@ -12,6 +12,8 @@
  * limitations under the License.
  */
 
+import moment from 'moment'
+
 import { ParagraphStatus, } from '../../notebook/paragraph/paragraph.status'
 
 angular.module('zeppelinWebApp').controller('JobCtrl', JobCtrl)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/notebook-actionBar.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook-actionBar.html 
b/zeppelin-web/src/app/notebook/notebook-actionBar.html
index e83e05e..5b600f2 100644
--- a/zeppelin-web/src/app/notebook/notebook-actionBar.html
+++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html
@@ -156,7 +156,7 @@ limitations under the License.
                 <strong>{{revision.message}}</strong>
               </span>
               <span class="revisionDate">
-                <em>{{moment.unix(revision.time).format('MMMM Do YYYY, h:mm 
a')}}</em>
+                <em>{{formatRevisionDate(revision.time)}}</em>
               </span>
             </a>
           </li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/notebook.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js 
b/zeppelin-web/src/app/notebook/notebook.controller.js
index 4b02f1a..3944326 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -12,6 +12,8 @@
  * limitations under the License.
  */
 
+import moment from 'moment'
+
 import { isParagraphRunning, } from './paragraph/paragraph.status'
 
 angular.module('zeppelinWebApp').controller('NotebookCtrl', NotebookCtrl)
@@ -25,7 +27,6 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
   ngToast.dismiss()
 
   $scope.note = null
-  $scope.moment = moment
   $scope.editorToggled = false
   $scope.tableToggled = false
   $scope.viewOnly = false
@@ -42,6 +43,10 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
     {name: '1d', value: '0 0 0 * * ?'}
   ]
 
+  $scope.formatRevisionDate = function(date) {
+    return moment.unix(date).format('MMMM Do YYYY, h:mm a')
+  }
+
   $scope.interpreterSettings = []
   $scope.interpreterBindings = []
   $scope.isNoteDirty = null
@@ -223,7 +228,7 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
   }
 
   $scope.$on('listRevisionHistory', function (event, data) {
-    console.log('received list of revisions %o', data)
+    console.debug('received list of revisions %o', data)
     $scope.noteRevisions = data.revisionList
     $scope.noteRevisions.splice(0, 0, {
       id: 'Head',

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js 
b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index a6564d4..3d75676 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -17,6 +17,9 @@ import {
   ParagraphStatus, isParagraphRunning,
 } from './paragraph.status'
 
+import moment from 'moment'
+require('moment-duration-format')
+
 const ParagraphExecutor = {
   SPELL: 'SPELL',
   INTERPRETER: 'INTERPRETER',
@@ -983,19 +986,24 @@ function ParagraphCtrl ($scope, $rootScope, $route, 
$window, $routeParams, $loca
   }
 
   $scope.getExecutionTime = function (pdata) {
-    let timeMs = Date.parse(pdata.dateFinished) - Date.parse(pdata.dateStarted)
+    const end = pdata.dateFinished
+    const start = pdata.dateStarted
+    let timeMs = Date.parse(end) - Date.parse(start)
     if (isNaN(timeMs) || timeMs < 0) {
       if ($scope.isResultOutdated(pdata)) {
         return 'outdated'
       }
       return ''
     }
+
+    const durationFormat = moment.duration((timeMs / 1000), 
'seconds').format('h [hrs] m [min] s [sec]')
+    const endFormat = moment(pdata.dateFinished).format('MMMM DD YYYY, h:mm:ss 
A')
+
     let user = (pdata.user === undefined || pdata.user === null) ? 'anonymous' 
: pdata.user
-    let desc = 'Took ' + moment.duration((timeMs / 1000), 'seconds').format('h 
[hrs] m [min] s [sec]') +
-      '. Last updated by ' + user + ' at ' + 
moment(pdata.dateFinished).format('MMMM DD YYYY, h:mm:ss A') + '.'
-    if ($scope.isResultOutdated(pdata)) {
-      desc += ' (outdated)'
-    }
+    let desc = `Took ${durationFormat}. Last updated by ${user} at 
${endFormat}.`
+
+    if ($scope.isResultOutdated(pdata)) { desc += ' (outdated)' }
+
     return desc
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/paragraph/paragraph.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css 
b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
index e517be4..b17acf7 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
@@ -52,21 +52,6 @@
   width: 100%;
 }
 
-table.dataTable {
-  margin-top: 0px !important;
-  margin-bottom: 6px !important;
-}
-
-table.dataTable.table-condensed > thead > tr > th {
-  padding-right: 28px;
-}
-
-table.dataTable.table-condensed .sorting:after,
-table.dataTable.table-condensed .sorting_asc:after,
-table.dataTable.table-condensed .sorting_desc:after {
-  right: 12px;
-}
-
 .plainTextContainer {
   font-family: "Monaco","Menlo","Ubuntu 
Mono","Consolas","source-code-pro",monospace;
   font-size: 12px !important;
@@ -488,46 +473,6 @@ table.table-shortcut {
 }
 
 /*
-  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;
-}
-
-.handsontable .columnSorting.ascending::after {
-  content: '\f160';
-  margin-left: 3px;
-  font-size: 12px;
-  font-family: FontAwesome;
-  line-height: 2;
-}
-
-.handsontable .columnSorting.descending::after {
-  content: '\f161';
-  margin-left: 3px;
-  font-size: 12px;
-  margin-left: 3px;
-  font-family: FontAwesome;
-  line-height: 2;
-}
-
-/*
   Pivot CSS
 */
 
@@ -583,54 +528,6 @@ table.table-striped {
   width: 20px;
 }
 
-
-.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: 22px;
-  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;
-}
-
 /*
   Overwrite github-markdown-css for Markdown interpreter
 */

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/paragraph/result/display-table.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/display-table.css 
b/zeppelin-web/src/app/notebook/paragraph/result/display-table.css
new file mode 100644
index 0000000..9462109
--- /dev/null
+++ b/zeppelin-web/src/app/notebook/paragraph/result/display-table.css
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+.ui-grid-pager-panel {
+  vertical-align: middle;
+  background-color: #f3f3f3;
+}
+
+.ui-grid-footer-info {
+  padding: 5px;
+  background-color: #f3f3f3;
+}
+
+.ui-grid-menu {
+  overflow: auto; /** enable scrollbar in column menu */
+}
+
+/** disable alternative color for even lows in a table */
+.ui-grid-row:nth-child(even) .ui-grid-cell {
+  background-color: white !important;
+}
+
+/** support `height: auto` for cells */
+.ui-grid-viewport .ui-grid-cell-contents {
+  word-wrap: break-word;
+  white-space: normal !important;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.ui-grid-row, .ui-grid-cell {
+  height: auto !important;
+}
+
+.ui-grid-row div[role=row] {
+  display: flex ;
+  align-content: stretch;
+}
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html 
b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
index d8a316b..4becdc6 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
@@ -93,7 +93,7 @@ limitations under the License.
 </div>
 
 <span
-   ng-if="type=='TABLE' && !config.helium.activeApp && graphMode!='table' && 
!asIframe && !viewOnly"
+   ng-if="type=='TABLE' && !config.helium.activeApp && !asIframe && !viewOnly"
    style="margin-left:10px; cursor:pointer; display: inline-block; 
vertical-align:top; position: relative; line-height:30px;">
   <a class="btnText" ng-click="toggleGraphSetting()">
     settings <span ng-class="config.graph.optionOpen ? 'fa fa-caret-up' : 'fa 
fa-caret-down'"></span>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js 
b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
index 4c9ad84..8237c78 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
@@ -12,6 +12,8 @@
  * limitations under the License.
  */
 
+import moment from 'moment'
+
 import TableData from '../../../tabledata/tabledata'
 import TableVisualization from 
'../../../visualization/builtins/visualization-table'
 import BarchartVisualization from 
'../../../visualization/builtins/visualization-barchart'
@@ -25,11 +27,14 @@ import {
 } from '../../../spell'
 import { ParagraphStatus, } from '../paragraph.status'
 
+const TableGridFilterTemplate = 
require('../../../visualization/builtins/visualization-table-grid-filter.html')
+
 angular.module('zeppelinWebApp').controller('ResultCtrl', ResultCtrl)
 
 function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, 
$location,
-                    $timeout, $compile, $http, $q, $templateRequest, $sce, 
websocketMsgSrv,
-                    baseUrlSrv, ngToast, saveAsService, noteVarShareService, 
heliumService) {
+                    $timeout, $compile, $http, $q, $templateCache, 
$templateRequest, $sce, websocketMsgSrv,
+                    baseUrlSrv, ngToast, saveAsService, noteVarShareService, 
heliumService,
+                    uiGridConstants) {
   'ngInject'
 
   /**
@@ -527,6 +532,12 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
           }
           builtInViz.instance._emitter = emitter
           builtInViz.instance._compile = $compile
+
+          // ui-grid related
+          $templateCache.put('ui-grid/ui-grid-filter', TableGridFilterTemplate)
+          builtInViz.instance._uiGridConstants = uiGridConstants
+          builtInViz.instance._timeout = $timeout
+
           builtInViz.instance._createNewScope = createNewScope
           builtInViz.instance._templateRequest = $templateRequest
           const transformation = builtInViz.instance.getTransformation()
@@ -616,8 +627,8 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
     } else {
       newConfig.graph.optionOpen = true
     }
-    let newParams = angular.copy(paragraph.settings.params)
 
+    let newParams = angular.copy(paragraph.settings.params)
     commitParagraphResult(paragraph.title, paragraph.text, newConfig, 
newParams)
   }
 
@@ -646,7 +657,7 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
         }
       }
     }
-    console.log('getVizConfig', config)
+    console.debug('getVizConfig', config)
     return config
   }
 
@@ -675,7 +686,7 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
       newConfig.graph.values = newConfig.graph.commonSetting.pivot.values
       delete newConfig.graph.commonSetting.pivot
     }
-    console.log('committVizConfig', newConfig)
+    console.debug('committVizConfig', newConfig)
     let newParams = angular.copy(paragraph.settings.params)
     commitParagraphResult(paragraph.title, paragraph.text, newConfig, 
newParams)
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/notebook/paragraph/result/result.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.html 
b/zeppelin-web/src/app/notebook/paragraph/result/result.html
index 5a523de..faac9cd 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.html
@@ -25,9 +25,9 @@ limitations under the License.
 
     <div ng-if="type=='TABLE'"
          ng-style="getPointerEvent()">
-      <!-- graph setting -->
+      <!-- setting -->
       <div class="option lightBold" style="overflow: visible;"
-           ng-show="graphMode!='table' && config.graph.optionOpen && !asIframe 
&& !viewOnly">
+           ng-show="config.graph.optionOpen && !asIframe && !viewOnly">
         <div ng-repeat="viz in builtInTableDataVisualizationList track by 
$index"
              id="trsetting{{id}}_{{viz.id}}"
              ng-show="graphMode == viz.id"></div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/visualization/builtins/visualization-table-grid-filter.html
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/visualization/builtins/visualization-table-grid-filter.html
 
b/zeppelin-web/src/app/visualization/builtins/visualization-table-grid-filter.html
new file mode 100644
index 0000000..d4b7e98
--- /dev/null
+++ 
b/zeppelin-web/src/app/visualization/builtins/visualization-table-grid-filter.html
@@ -0,0 +1,27 @@
+<!--
+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.
+-->
+
+<div class="ui-grid-filter-container"
+     ng-repeat="colFilter in col.filters"
+     ng-class="{'ui-grid-filter-cancel-button-hidden' : 
colFilter.disableCancelFilterButton === true }">
+  <div ng-if="colFilter.type !== 'select'">
+    <input type="text" class="input-sm form-control"
+           style="font-size: 14px; font-weight: 500"
+
+           ng-model="colFilter.term"
+           ng-model-options="{ debounce : { 'default' : 300, 'blur' : 0 }}"
+           ng-attr-placeholder="{{colFilter.placeholder || ''}}"
+           aria-label="{{colFilter.ariaLabel || aria.defaultFilterLabel}}" />
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/visualization/builtins/visualization-table-setting.html
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/visualization/builtins/visualization-table-setting.html 
b/zeppelin-web/src/app/visualization/builtins/visualization-table-setting.html
new file mode 100644
index 0000000..d01fd1b
--- /dev/null
+++ 
b/zeppelin-web/src/app/visualization/builtins/visualization-table-setting.html
@@ -0,0 +1,84 @@
+<!--
+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.
+-->
+
+<div class="panel panel-default" style="margin-top: 10px; margin-bottom: 
11px;">
+  <div class="panel-heading" style="padding: 6px 12px 6px 12px; font-size: 
13px;">
+    <span style="vertical-align: middle; display: inline-block; margin-top: 
3px;">Table Options</span>
+    <span style="float: right;">
+       <div class="btn-group" role="group" aria-label="...">
+         <div type="button" ng-click="resetTableOption()"
+              uib-tooltip="Restore the default setting" 
tooltip-placement="left"
+              class="btn btn-default" style="font-size: 11px; padding: 2px 5px 
2px 5px;">
+           <i class="fa fa-undo" aria-hidden="true"></i>
+         </div>
+       </div>
+    </span>
+    <div style="clear: both;"></div> <!-- to fix previous span which has 
float: right -->
+  </div>
+
+  <div class="panel-body" style="padding: 8px 12px; margin-top: 3px;">
+    <table class="table table-striped">
+      <tr>
+        <th style="font-size: 12px; font-style: italic">Name</th>
+        <th style="font-size: 12px; font-style: italic">Value</th>
+      </tr>
+      <tr>
+      </tr>
+
+      <tr data-ng-repeat="optSpec in tableOptionSpecs">
+        <td style="font-weight: 400; vertical-align: middle;">
+          <span uib-tooltip="{{optSpec.description}}" 
tooltip-placement="right">
+            {{optSpec.name}}
+            <i class="fa fa-info-circle" style="margin-top: 2px; margin-left: 
3px; color: #7b7bbd;" aria-hidden="true"></i>
+          </span>
+        </td>
+        <td>
+          <div ng-if="isInputWidget(optSpec)"
+               class="input-group">
+            <input type="text" class="form-control input-sm"
+                   style="font-weight: 400; font-size: 12px; 
vertical-align:middle; border-radius: 5px;"
+                   ng-keydown="tableWidgetOnKeyDown($event, optSpec)"
+                   data-ng-model="config.tableOptionValue[optSpec.name]" />
+          </div>
+          <div class="btn-group"
+               ng-if="isOptionWidget(optSpec)">
+            <select class="form-control input-sm"
+                    ng-change="tableOptionValueChanged(optSpec)"
+                    data-ng-model="config.tableOptionValue[optSpec.name]"
+                    data-ng-options="optionValue for optionValue in 
optSpec.optionValues"
+                    style="font-weight: 400; font-size: 12px;">
+            </select>
+          </div>
+
+          <div ng-if="isCheckboxWidget(optSpec)">
+            <input type="checkbox"
+                   ng-keydown="parameterOnKeyDown($event, optSpec)"
+                   ng-click="tableOptionValueChanged(optSpec)"
+                   data-ng-model="config.tableOptionValue[optSpec.name]"
+                   data-ng-checked="config.tableOptionValue[optSpec.name]" />
+          </div>
+
+          <div ng-if="isTextareaWidget(optSpec)">
+            <textarea class="form-control" rows="3"
+                      ng-keydown="tableWidgetOnKeyDown($event, optSpec)"
+                      data-ng-model="config.tableOptionValue[optSpec.name]"
+                      style="font-weight: 400; font-size: 12px;">
+            </textarea>
+          </div>
+
+        </td>
+      </tr>
+    </table>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/visualization/builtins/visualization-table.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-table.js 
b/zeppelin-web/src/app/visualization/builtins/visualization-table.js
index 3192ee6..f8c280a 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-table.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-table.js
@@ -14,7 +14,41 @@
 
 import Visualization from '../visualization'
 import PassthroughTransformation from '../../tabledata/passthrough'
-import HandsonHelper from '../../handsontable/handsonHelper'
+
+import {
+  Widget, ValueType,
+  isInputWidget, isOptionWidget, isCheckboxWidget,
+  isTextareaWidget, isBtnGroupWidget,
+  initializeTableConfig, resetTableOptionConfig,
+  DefaultTableColumnType, TableColumnType, updateColumnTypeState,
+  parseTableOption,
+} from './visualization-util'
+
+const SETTING_TEMPLATE = require('./visualization-table-setting.html')
+
+const TABLE_OPTION_SPECS = [
+  {
+    name: 'useFilter',
+    valueType: ValueType.BOOLEAN,
+    defaultValue: false,
+    widget: Widget.CHECKBOX,
+    description: 'Enable filter for columns',
+  },
+  {
+    name: 'showPagination',
+    valueType: ValueType.BOOLEAN,
+    defaultValue: false,
+    widget: Widget.CHECKBOX,
+    description: 'Enable pagination for better navigation',
+  },
+  {
+    name: 'showAggregationFooter',
+    valueType: ValueType.BOOLEAN,
+    defaultValue: false,
+    widget: Widget.CHECKBOX,
+    description: 'Enable a footer for displaying aggregated values',
+  },
+]
 
 /**
  * Visualize data in table format
@@ -22,42 +56,349 @@ import HandsonHelper from 
'../../handsontable/handsonHelper'
 export default class TableVisualization extends Visualization {
   constructor (targetEl, config) {
     super(targetEl, config)
-    console.log('Init table viz')
-    targetEl.addClass('table')
     this.passthrough = new PassthroughTransformation(config)
+    this.emitTimeout = null
+    this.isRestoring = false
+
+    initializeTableConfig(config, TABLE_OPTION_SPECS)
+  }
+
+  createGridOptions(tableData, onRegisterApiCallback, config) {
+    const rows = tableData.rows
+    const columnNames = tableData.columns.map(c => c.name)
+
+    const gridData = rows.map(r => {
+      return columnNames.reduce((acc, colName, index) => {
+        acc[colName] = r[index]
+        return acc
+      }, {})
+    })
+
+    const gridOptions = {
+      data: gridData,
+      enableGridMenu: true,
+      modifierKeysToMultiSelectCells: true,
+      exporterMenuCsv: true,
+      exporterMenuPdf: false,
+      flatEntityAccess: true,
+      fastWatch: false,
+      treeRowHeaderAlwaysVisible: false,
+
+      columnDefs: columnNames.map(colName => {
+        return {
+          name: colName,
+          type: DefaultTableColumnType,
+          cellTemplate: `
+            <div ng-if="!grid.getCellValue(row, col).startsWith('%html')"
+                 class="ui-grid-cell-contents">
+              {{grid.getCellValue(row, col)}}
+            </div>
+            <div ng-if="grid.getCellValue(row, col).startsWith('%html')"
+                 ng-bind-html="grid.getCellValue(row, col).split('%html')[1]"
+                 class="ui-grid-cell-contents">
+            </div>
+          `,
+        }
+      }),
+      rowEditWaitInterval: -1, /** disable saveRow event */
+      enableRowHashing: true,
+      saveFocus: false,
+      saveScroll: false,
+      saveSort: true,
+      savePinning: true,
+      saveGrouping: true,
+      saveGroupingExpandedStates: true,
+      saveOrder: true, // column order
+      saveVisible: true, // column visibility
+      saveTreeView: true,
+      saveFilter: true,
+      saveSelection: false,
+    }
+
+    return gridOptions
   }
 
-  refresh () {
-    this.hot.render()
+  getGridElemId() {
+    // angular doesn't allow `-` in scope variable name
+    const gridElemId = `${this.targetEl[0].id}_grid`.replace('-', '_')
+    return gridElemId
   }
 
-  render (tableData) {
-    let height = this.targetEl.height()
-    let container = this.targetEl.css('height', height).get(0)
-    let resultRows = tableData.rows
-    let columnNames = _.pluck(tableData.columns, 'name')
-    // eslint-disable-next-line prefer-spread
-    let columns = Array.apply(null, 
Array(tableData.columns.length)).map(function () {
-      return {type: 'text'}
+  getGridApiId() {
+    // angular doesn't allow `-` in scope variable name
+    const gridApiId = `${this.targetEl[0].id}_gridApi`.replace('-', '_')
+    return gridApiId
+  }
+
+  refresh() {
+    const gridElemId = this.getGridElemId()
+    const gridElem = angular.element(`#${gridElemId}`)
+
+    if (gridElem) {
+      gridElem.css('height', this.targetEl.height() - 10)
+    }
+  }
+
+  refreshGrid() {
+    const gridElemId = this.getGridElemId()
+    const gridElem = angular.element(`#${gridElemId}`)
+
+    if (gridElem) {
+      const scope = this.getScope()
+      const gridApiId = this.getGridApiId()
+      
scope[gridApiId].core.notifyDataChange(this._uiGridConstants.dataChange.ALL)
+    }
+  }
+
+  updateColDefType(colDef, type) {
+    if (type === colDef.type) { return }
+
+    colDef.type = type
+    const colName = colDef.name
+    const config = this.config
+    if (config.tableColumnTypeState.names && 
config.tableColumnTypeState.names[colName]) {
+      config.tableColumnTypeState.names[colName] = type
+      this.persistConfigWithGridState(this.config)
+    }
+  }
+
+  addColumnMenus(gridOptions) {
+    if (!gridOptions || !gridOptions.columnDefs) { return }
+
+    const self = this // for closure
+
+    // SHOULD use `function() { ... }` syntax for each action to get `this`
+    gridOptions.columnDefs.map(colDef => {
+      colDef.menuItems = [
+        {
+          title: 'Type: String',
+          action: function() {
+            self.updateColDefType(this.context.col.colDef, 
TableColumnType.STRING)
+          },
+          active: function() {
+            return this.context.col.colDef.type === TableColumnType.STRING
+          },
+        },
+        {
+          title: 'Type: Number',
+          action: function() {
+            self.updateColDefType(this.context.col.colDef, 
TableColumnType.NUMBER)
+          },
+          active: function() {
+            return this.context.col.colDef.type === TableColumnType.NUMBER
+          },
+        },
+        {
+          title: 'Type: Date',
+          action: function() {
+            self.updateColDefType(this.context.col.colDef, 
TableColumnType.DATE)
+          },
+          active: function() {
+            return this.context.col.colDef.type === TableColumnType.DATE
+          },
+        },
+      ]
     })
+  }
+
+  setDynamicGridOptions(gridOptions, config) {
+    // parse based on their type definitions
+    const parsed = parseTableOption(TABLE_OPTION_SPECS, 
config.tableOptionValue)
+
+    const { showAggregationFooter, useFilter, showPagination, } = parsed
 
-    if (this.hot) {
-      this.hot.destroy()
+    gridOptions.showGridFooter = false
+    gridOptions.showColumnFooter = showAggregationFooter
+    gridOptions.enableFiltering = useFilter
+
+    gridOptions.enablePagination = showPagination
+    gridOptions.enablePaginationControls = showPagination
+
+    if (showPagination) {
+      gridOptions.paginationPageSize = 50
+      gridOptions.paginationPageSizes = [25, 50, 100, 250, 1000]
     }
 
-    let handsonHelper = new HandsonHelper()
-    this.hot = new Handsontable(container, handsonHelper.getHandsonTableConfig(
-      columns, columnNames, resultRows))
-    this.hot.validateCells(null)
+    // selection can't be rendered dynamically in ui-grid 4.0.4
+    gridOptions.enableRowSelection = false
+    gridOptions.enableRowHeaderSelection = false
+    gridOptions.enableFullRowSelection = false
+    gridOptions.enableSelectAll = false
+    gridOptions.enableGroupHeaderSelection = false
+    gridOptions.enableSelectionBatchEvent = false
   }
 
-  destroy () {
-    if (this.hot) {
-      this.hot.destroy()
+  render (tableData) {
+    const gridElemId = this.getGridElemId()
+    let gridElem = document.getElementById(gridElemId)
+
+    const config = this.config
+    const self = this // for closure
+
+    if (!gridElem) {
+      // create, compile and append grid elem
+      gridElem = angular.element(
+        `<div id="${gridElemId}" ui-grid="${gridElemId}"
+              ui-grid-edit ui-grid-row-edit 
+              ui-grid-pagination 
+              ui-grid-selection
+              ui-grid-cellNav ui-grid-pinning
+              ui-grid-empty-base-layer
+              ui-grid-resize-columns ui-grid-move-columns
+              ui-grid-grouping
+              ui-grid-save-state
+              ui-grid-exporter></div>`)
+
+      gridElem.css('height', this.targetEl.height() - 10)
+      const scope = this.getScope()
+      gridElem = this._compile(gridElem)(scope)
+      this.targetEl.append(gridElem)
+
+      // set gridOptions for this elem
+      const gridOptions = this.createGridOptions(tableData, 
onRegisterApiCallback, config)
+      this.setDynamicGridOptions(gridOptions, config)
+      this.addColumnMenus(gridOptions)
+      scope[gridElemId] = gridOptions
+
+      // set gridApi for this elem
+      const gridApiId = this.getGridApiId()
+      const onRegisterApiCallback = (gridApi) => {
+        scope[gridApiId] = gridApi
+        // should restore state before registering APIs
+
+        // register callbacks for change evens
+        // should persist `self.config` instead `config` (closure issue)
+        gridApi.core.on.columnVisibilityChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.colMovable.on.columnPositionChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.core.on.sortChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.core.on.filterChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.grouping.on.aggregationChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.grouping.on.groupingChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.treeBase.on.rowCollapsed(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        gridApi.treeBase.on.rowExpanded(scope, () => { 
self.persistConfigWithGridState(self.config) })
+
+        // pagination doesn't follow usual life-cycle in ui-grid v4.0.4
+        // gridApi.pagination.on.paginationChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        // TBD: do we need to propagate row selection?
+        // gridApi.selection.on.rowSelectionChanged(scope, () => { 
self.persistConfigWithGridState(self.config) })
+        // gridApi.selection.on.rowSelectionChangedBatch(scope, () => { 
self.persistConfigWithGridState(self.config) })
+      }
+      gridOptions.onRegisterApi = onRegisterApiCallback
+    } else {
+      // don't need to update gridOptions.data since it's synchronized by 
paragraph execution
+      const gridOptions = this.getGridOptions()
+      this.setDynamicGridOptions(gridOptions, config)
+      this.refreshGrid()
+    }
+
+    const columnDefs = this.getGridOptions().columnDefs
+    updateColumnTypeState(tableData.columns, config, columnDefs)
+    // SHOULD restore grid state after columnDefs are updated
+    this.restoreGridState(config.tableGridState)
+  }
+
+  restoreGridState(gridState) {
+    if (!gridState) { return }
+
+    // should set isRestoring to avoid that changed* events are triggered 
while restoring
+    this.isRestoring = true
+    const gridApi = this.getGridApi()
+
+    // restore grid state when gridApi is available
+    if (!gridApi) {
+      setTimeout(() => this.restoreGridState(gridState), 100)
+    } else {
+      gridApi.saveState.restore(this.getScope(), gridState)
+      this.isRestoring = false
     }
   }
 
+  destroy () {
+  }
+
   getTransformation () {
     return this.passthrough
   }
+
+  getScope() {
+    const scope = this.targetEl.scope()
+    return scope
+  }
+
+  getGridOptions() {
+    const scope = this.getScope()
+    const gridElemId = this.getGridElemId()
+    return scope[gridElemId]
+  }
+
+  getGridApi() {
+    const scope = this.targetEl.scope()
+    const gridApiId = this.getGridApiId()
+    return scope[gridApiId]
+  }
+
+  persistConfigImmediatelyWithGridState(config) {
+    this.persistConfigWithGridState(config)
+  }
+
+  persistConfigWithGridState(config) {
+    if (this.isRestoring) { return }
+
+    const gridApi = this.getGridApi()
+    config.tableGridState = gridApi.saveState.save()
+    this.emitConfig(config)
+  }
+
+  persistConfig(config) {
+    this.emitConfig(config)
+  }
+
+  getSetting (chart) {
+    const self = this // for closure in scope
+    const configObj = self.config
+
+    // emit config if it's updated in `render`
+    if (configObj.initialized) {
+      configObj.initialized = false
+      this.persistConfig(configObj) // should persist w/o state
+    } else if (configObj.tableColumnTypeState &&
+      configObj.tableColumnTypeState.updated) {
+      configObj.tableColumnTypeState.updated = false
+      this.persistConfig(configObj) // should persist w/o state
+    }
+
+    return {
+      template: SETTING_TEMPLATE,
+      scope: {
+        config: configObj,
+        tableOptionSpecs: TABLE_OPTION_SPECS,
+        isInputWidget: isInputWidget,
+        isOptionWidget: isOptionWidget,
+        isCheckboxWidget: isCheckboxWidget,
+        isTextareaWidget: isTextareaWidget,
+        isBtnGroupWidget: isBtnGroupWidget,
+        tableOptionValueChanged: () => {
+          self.persistConfigWithGridState(configObj)
+        },
+        saveTableOption: () => {
+          self.persistConfigWithGridState(configObj)
+        },
+        resetTableOption: () => {
+          resetTableOptionConfig(configObj)
+          initializeTableConfig(configObj, TABLE_OPTION_SPECS)
+          self.persistConfigWithGridState(configObj)
+        },
+        tableWidgetOnKeyDown: (event, optSpec) => {
+          const code = event.keyCode || event.which
+          if (code === 13 && isInputWidget(optSpec)) {
+            self.persistConfigWithGridState(configObj)
+          } else if (code === 13 && event.shiftKey && 
isTextareaWidget(optSpec)) {
+            self.persistConfigWithGridState(configObj)
+          }
+
+          event.stopPropagation() /** avoid to conflict with paragraph 
shortcuts */
+        }
+      }
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/app/visualization/builtins/visualization-util.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-util.js 
b/zeppelin-web/src/app/visualization/builtins/visualization-util.js
new file mode 100644
index 0000000..cd9cd48
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-util.js
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+export const Widget = {
+  CHECKBOX: 'checkbox',
+  INPUT: 'input',
+  TEXTAREA: 'textarea',
+  OPTION: 'option',
+  BTN_GROUP: 'btn-group',
+}
+
+export const ValueType = {
+  INT: 'int',
+  FLOAT: 'float',
+  BOOLEAN: 'boolean',
+  STRING: 'string',
+  JSON: 'JSON',
+}
+
+export const TableColumnType = {
+  STRING: 'string',
+  BOOLEAN: 'boolean',
+  NUMBER: 'number',
+  DATE: 'date',
+  OBJECT: 'object',
+  NUMBER_STR: 'numberStr',
+}
+
+export const DefaultTableColumnType = TableColumnType.STRING
+
+export function isInputWidget (spec) { return spec.widget === Widget.INPUT }
+export function isOptionWidget (spec) { return spec.widget === Widget.OPTION }
+export function isCheckboxWidget (spec) { return spec.widget === 
Widget.CHECKBOX }
+export function isTextareaWidget (spec) { return spec.widget === 
Widget.TEXTAREA }
+export function isBtnGroupWidget (spec) { return spec.widget === 
Widget.BTN_GROUP }
+
+export function resetTableOptionConfig(config) {
+  delete config.tableOptionSpecHash
+  config.tableOptionSpecHash = {}
+  delete config.tableOptionValue
+  config.tableOptionValue = {}
+  delete config.tableColumnTypeState.names
+  config.tableColumnTypeState.names = {}
+  config.updated = false
+  return config
+}
+
+export function initializeTableConfig(config, tableOptionSpecs) {
+  if (typeof config.tableOptionValue === 'undefined') { 
config.tableOptionValue = {} }
+  if (typeof config.tableGridState === 'undefined') { config.tableGridState = 
{} }
+  if (typeof config.tableColumnTypeState === 'undefined') { 
config.tableColumnTypeState = {} }
+
+  // should remove `$$hashKey` using angular.toJson
+  const newSpecHash = 
JSON.stringify(JSON.parse(angular.toJson(tableOptionSpecs)))
+  const previousSpecHash = config.tableOptionSpecHash
+
+  // check whether spec is updated or not
+  if (typeof previousSpecHash === 'undefined' || (previousSpecHash !== 
newSpecHash)) {
+    resetTableOptionConfig(config)
+
+    config.tableOptionSpecHash = newSpecHash
+    config.initialized = true
+
+    // reset all persisted option values if spec is updated
+    for (let i = 0; i < tableOptionSpecs.length; i++) {
+      const option = tableOptionSpecs[i]
+      config.tableOptionValue[option.name] = option.defaultValue
+    }
+  }
+
+  return config
+}
+
+export function parseTableOption(specs, persistedTableOption) {
+  /** copy original params */
+  const parsed = JSON.parse(JSON.stringify(persistedTableOption))
+
+  for (let i = 0; i < specs.length; i++) {
+    const s = specs[i]
+    const name = s.name
+
+    if (s.valueType === ValueType.INT &&
+      typeof parsed[name] !== 'number') {
+      try { parsed[name] = parseInt(parsed[name]) } catch (error) { 
parsed[name] = s.defaultValue }
+    } else if (s.valueType === ValueType.FLOAT &&
+      typeof parsed[name] !== 'number') {
+      try { parsed[name] = parseFloat(parsed[name]) } catch (error) { 
parsed[name] = s.defaultValue }
+    } else if (s.valueType === ValueType.BOOLEAN) {
+      if (parsed[name] === 'false') {
+        parsed[name] = false
+      } else if (parsed[name] === 'true') {
+        parsed[name] = true
+      } else if (typeof parsed[name] !== 'boolean') {
+        parsed[name] = s.defaultValue
+      }
+    } else if (s.valueType === ValueType.JSON) {
+      if (parsed[name] !== null && typeof parsed[name] !== 'object') {
+        try { parsed[name] = JSON.parse(parsed[name]) } catch (error) { 
parsed[name] = s.defaultValue }
+      } else if (parsed[name] === null) {
+        parsed[name] = s.defaultValue
+      }
+    }
+  }
+
+  return parsed
+}
+
+export function isColumnNameUpdated(prevColumnNames, newColumnNames) {
+  if (typeof prevColumnNames === 'undefined') { return true }
+
+  let columnNameUpdated = false
+
+  for (let prevColName in prevColumnNames) {
+    if (!newColumnNames[prevColName]) {
+      return true
+    }
+  }
+
+  if (!columnNameUpdated) {
+    for (let newColName in newColumnNames) {
+      if (!prevColumnNames[newColName]) {
+        return true
+      }
+    }
+  }
+
+  return false
+}
+
+export function updateColumnTypeState(columns, config, columnDefs) {
+  const columnTypeState = config.tableColumnTypeState
+
+  if (!columnTypeState) { return }
+
+  // compare objects because order might be changed
+  const prevColumnNames = columnTypeState.names || {}
+  const newColumnNames = columns.reduce((acc, c) => {
+    const prevColumnType = prevColumnNames[c.name]
+
+    // use previous column type if exists
+    if (prevColumnType) {
+      acc[c.name] = prevColumnType
+    } else {
+      acc[c.name] = DefaultTableColumnType
+    }
+    return acc
+  }, {})
+
+  let columnNameUpdated = isColumnNameUpdated(prevColumnNames, newColumnNames)
+
+  if (columnNameUpdated) {
+    columnTypeState.names = newColumnNames
+    columnTypeState.updated = true
+  }
+
+  // update `columnDefs[n].type`
+  for (let i = 0; i < columnDefs.length; i++) {
+    const colName = columnDefs[i].name
+    columnDefs[i].type = columnTypeState.names[colName]
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/components/editor/codeEditor.directive.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/editor/codeEditor.directive.js 
b/zeppelin-web/src/components/editor/codeEditor.directive.js
index b8e1b6a..d8cb73f 100644
--- a/zeppelin-web/src/components/editor/codeEditor.directive.js
+++ b/zeppelin-web/src/components/editor/codeEditor.directive.js
@@ -31,7 +31,7 @@ function codeEditor ($templateRequest, $compile) {
         editor.attr('id', scope.paragraphId + '_editor')
         element.append(editor)
         $compile(editor)(scope)
-        console.log('codeEditor directive revision view is ' + 
scope.revisionView)
+        console.debug('codeEditor directive revision view is ' + 
scope.revisionView)
       })
     }
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js 
b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
index 08eb16b..16b3f7a 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
@@ -54,8 +54,9 @@ function websocketEvents ($rootScope, $websocket, $location, 
baseUrlSrv) {
     if (event.data) {
       payload = angular.fromJson(event.data)
     }
-    console.log('Receive Json << %o', event.data)
+
     console.log('Receive << %o, %o', payload.op, payload)
+
     let op = payload.op
     let data = payload.data
     if (op === 'NOTE') {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index fc7a21f..7d48584 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -44,8 +44,6 @@ limitations under the License.
     <link rel="stylesheet" 
href="bower_components/highlightjs/styles/github.css" />
     <link rel="stylesheet" href="bower_components/ngtoast/dist/ngToast.css" />
     <link rel="stylesheet" 
href="bower_components/bootstrap3-dialog/dist/css/bootstrap-dialog.min.css" />
-    <link rel="stylesheet" href="bower_components/pikaday/css/pikaday.css" />
-    <link rel="stylesheet" 
href="bower_components/handsontable/dist/handsontable.css" />
     <!-- endbower -->
     <link rel="stylesheet" 
href="bower_components/jquery-ui/themes/base/jquery-ui.css" />
     <link rel="stylesheet" 
href="bower_components/select2/dist/css/select2.css" />
@@ -57,6 +55,7 @@ limitations under the License.
     <link rel="stylesheet" href="app/notebook/notebook.css" />
     <link rel="stylesheet" href="app/notebook/paragraph/paragraph.css" />
     <link rel="stylesheet" href="app/notebook/paragraph/result/result.css" />
+    <link rel="stylesheet" 
href="app/notebook/paragraph/result/display-table.css" />
     <link rel="stylesheet" href="app/jobmanager/jobmanager.css" />
     <link rel="stylesheet" href="app/jobmanager/jobs/job.css" />
     <link rel="stylesheet" href="app/interpreter/interpreter.css" />
@@ -163,11 +162,6 @@ limitations under the License.
     <script src="bower_components/ngtoast/dist/ngToast.js"></script>
     <script src="bower_components/ng-focus-if/focusIf.js"></script>
     <script 
src="bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js"></script>
-    <script 
src="bower_components/zeroclipboard/dist/ZeroClipboard.js"></script>
-    <script src="bower_components/moment/moment.js"></script>
-    <script src="bower_components/pikaday/pikaday.js"></script>
-    <script src="bower_components/handsontable/dist/handsontable.js"></script>
-    <script 
src="bower_components/moment-duration-format/lib/moment-duration-format.js"></script>
     <script src="bower_components/select2/dist/js/select2.js"></script>
     <script src="bower_components/MathJax/MathJax.js"></script>
     <script src="bower_components/clipboard/dist/clipboard.js"></script>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/src/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.js b/zeppelin-web/src/index.js
index fc2d65b..d94714a 100644
--- a/zeppelin-web/src/index.js
+++ b/zeppelin-web/src/index.js
@@ -18,7 +18,6 @@ import 'github-markdown-css/github-markdown.css'
 import './app/app.js'
 import './app/app.controller.js'
 import './app/home/home.controller.js'
-import './app/handsontable/handsonHelper.js'
 import './app/notebook/notebook.controller.js'
 
 import './app/tabledata/tabledata.js'

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/bed82eb4/zeppelin-web/webpack.config.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/webpack.config.js b/zeppelin-web/webpack.config.js
index 201649c..88c7acc 100644
--- a/zeppelin-web/webpack.config.js
+++ b/zeppelin-web/webpack.config.js
@@ -230,7 +230,6 @@ module.exports = function makeWebpackConfig () {
         template: './src/index.html',
         inject: 'body'
       }),
-
       // Reference: 
https://webpack.github.io/docs/list-of-plugins.html#defineplugin
       new webpack.DefinePlugin({
         'process.env': {

Reply via email to