%%html
<style>@import url("http://handsontable.com/dist/jquery.handsontable.full.css")</style>

from __future__ import print_function # For py 2.7 compat

from IPython.html import widgets # Widget definitions
from IPython.display import display # Used to display widgets in the notebook
from IPython.utils.traitlets import List, Dict # Used to declare attributes of our widget

class HandsonTableWidget(widgets.DOMWidget):
    _view_name = Unicode('HandsonTableView', sync=True)
    value = Unicode(sync=True)
    settings = Dict(sync=True)

%%javascript
;(function(require){
  "use strict";
  var window = this;
  require([
    "base/js/namespace",
    "widgets/js/manager",
    "widgets/js/widget",
    "underscore",
    "jquery",
    "http://handsontable.com/dist/jquery.handsontable.full.js"
  ], function(IPython, manager, widget, _, $){
    // Just once, ensure that the scroll event is called on the window for HoT
    var $win = $(window);
    IPython.notebook.element.on("scroll", function(){ $win.scroll(); });

    // Define the HandsonTableView
    var HandsonTableView = widget.DOMWidgetView.extend({

      render: function(){
        // CREATION OF THE WIDGET IN THE NOTEBOOK.
        // Add a <div> in the widget area.
        this.$table = $('<div />').appendTo(this.$el);
        
        var view = this;

        // Create the Handsontable table.
        this.$table.handsontable({
          // when working in HoT, don't listen for command mode keys
          afterSelection: function(){ IPython.keyboard_manager.disable(); },
          afterDeselect: function(){ IPython.keyboard_manager.enable(); },
          // the data changed. `this` is the HoT instance
          afterChange: function(changes, source){
            // don't update if we did the changing!
            if(source === "loadData"){ return; }
            view.handle_table_change(this.getData());
          }
        });
      },

      update: function() {
        // PYTHON --> JS UPDATE.

        // Get the model's value (JSON)
        var json = this.model.get('value');
        
        // Parse it into data (list of lists)
        var data = JSON.parse(json);
        
        // Give it to the Handsontable widget.
        this.$table.handsontable({data: data});
        
        // Don't touch this...
        return HandsonTableView.__super__.update.apply(this);
      },

      handle_table_change: function(data) {
        // JS --> PYTHON UPDATE.
        // Update the model with the JSON string.
        this.model.set('value', JSON.stringify(data));

        // Don't touch this...
        this.touch();
      }
    });
    // Register the HandsonTableView with the widget manager.
    manager.WidgetManager.register_widget_view('HandsonTableView', HandsonTableView);
  });
}).call(this, require);

import StringIO
import numpy as np
import pandas as pd

class HandsonDataFrame(object):
    def __init__(self, df):
        self._df = df
        self._widget = HandsonTableWidget()
        self._widget.on_trait_change(self._on_data_changed, 'value')
        self._widget.on_displayed(self._on_displayed)
        
    def _on_displayed(self, e):
        # DataFrame ==> Widget (upon initialization only)
        json = self._df.to_json(orient='values')
        self._widget.value = json
        
    def _on_data_changed(self, e, val):
        # Widget ==> DataFrame (called every time the user
        # changes a value in the graphical widget)
        buf = StringIO.StringIO(val)
        self._df = pd.read_json(buf, orient='values')
        
    def to_dataframe(self):
        return self._df
        
    def show(self):
        display(self._widget)

data = np.random.randint(size=(3, 5), low=100, high=900)
df = pd.DataFrame(data)
df

ht = HandsonDataFrame(df)
ht.show()

ht.to_dataframe()