text: function createContextMenu() {
    var instance = this
        , selectorId = instance.rootElement[0].id
        , allItems = {
          "row_above": {name: "Insert row above", disabled: isDisabled},
          "row_below": {name: "Insert row below", disabled: isDisabled},
          "hsep1": "---------",
          "col_left": {name: "Insert column on the left", disabled: isDisabled},
          "col_right": {name: "Insert column on the right", disabled: isDisabled},
          "hsep2": "---------",
          "remove_row": {name: "Remove row", disabled: isDisabled},
          "remove_col": {name: "Remove column", disabled: isDisabled},
          "hsep3": "---------",
          "undo": {name: "Undo", disabled: function () {
            return !instance.isUndoAvailable();
          }},
          "redo": {name: "Redo", disabled: function () {
            return !instance.isRedoAvailable();
          }}
        }
        , defaultOptions = {
            selector : "#" + selectorId + ' table, #' + selectorId + ' div',
            trigger  : 'right',
            callback : onContextClick
          }
        , options = {}
        , i
        , ilen
        , settings = instance.getSettings();
  
    function onContextClick(key) {
      var corners = instance.getSelected(); //[top left row, top left col, bottom right row, bottom right col]
  
      if (!corners) {
        return; //needed when there are 2 grids on a page
      }
  
      switch (key) {
        case "row_above":
          instance.alter("insert_row", corners[0]);
          break;
  
        case "row_below":
          instance.alter("insert_row", corners[2] + 1);
          break;
  
        case "col_left":
          instance.alter("insert_col", corners[1]);
          break;
  
        case "col_right":
          instance.alter("insert_col", corners[3] + 1);
          break;
  
        case "remove_row":
          instance.alter(key, corners[0], (corners[2] - corners[0]) + 1);
          break;
  
        case "remove_col":
          instance.alter(key, corners[1], (corners[3] - corners[1]) + 1);
          break;
  
        case "undo":
          instance.undo();
          break;
  
        case "redo":
          instance.redo();
          break;
      }
    }
  
    function isDisabled(key) {
      //TODO rewrite
      /*if (instance.blockedCols.main.find('th.htRowHeader.active').length && (key === "remove_col" || key === "col_left" || key === "col_right")) {
       return true;
       }
       else if (instance.blockedRows.main.find('th.htColHeader.active').length && (key === "remove_row" || key === "row_above" || key === "row_below")) {
       return true;
       }
       else*/
      if (instance.countRows() >= instance.getSettings().maxRows && (key === "row_above" || key === "row_below")) {
        return true;
      }
      else if (instance.countCols() >= instance.getSettings().maxCols && (key === "col_left" || key === "col_right")) {
        return true;
      }
      else {
        return false;
      }
    }
  
    if (!settings.contextMenu) {
      return;
    }
    else if (settings.contextMenu === true) { //contextMenu is true
      options.items = allItems;
    }
    else if (Object.prototype.toString.apply(settings.contextMenu) === '[object Array]') { //contextMenu is an array
      options.items = {};
      for (i = 0, ilen = settings.contextMenu.length; i < ilen; i++) {
        var key = settings.contextMenu[i];
        if (typeof allItems[key] === 'undefined') {
          throw new Error('Context menu key "' + key + '" is not recognised');
        }
        options.items[key] = allItems[key];
      }
    }
    else if (Object.prototype.toString.apply(settings.contextMenu) === '[object Object]') { //contextMenu is an options object as defined in http://medialize.github.com/jQuery-contextMenu/docs.html
      options = settings.contextMenu;
      if (options.items) {
        for (i in options.items) {
          if (options.items.hasOwnProperty(i) && allItems[i]) {
            if (typeof options.items[i] === 'string') {
              options.items[i] = allItems[i];
            }
            else {
              options.items[i] = $.extend(true, allItems[i], options.items[i]);
            }
          }
        }
      }
      else {
        options.items = allItems;
      }
  
      if (options.callback) {
        var handsontableCallback = defaultOptions.callback;
        var customCallback = options.callback;
        options.callback = function (key, options) {
          handsontableCallback(key, options);
          customCallback(key, options);
        }
      }
    }
  
    if (!selectorId) {
      throw new Error("Handsontable container must have an id");
    }
  
    $.contextMenu($.extend(true, defaultOptions, options));
  }
  
  function destroyContextMenu() {
    var id = this.rootElement[0].id;
    $.contextMenu('destroy', "#" + id + ' table, #' + id + ' div');
  }
  
  Handsontable.PluginHooks.push('afterInit', createContextMenu);
  Handsontable.PluginHooks.push('afterDestroy', destroyContextMenu);