Search code examples
javascriptrhandsontablerhandsontable

Preventing Column Looping in (R)handsontable


I'm currently working with Rhandsontable and I'm facing an issue with user cell selection. When a user selects a cell from the last column (say column C) and then presses the right keyboard arrow, the selection loops back to the first column (column A). I would like to prevent this behavior and make it similar to Excel, where you cannot go further once you reach the final column.

Here's a simple example of my current Rhandsontable

library(rhandsontable)
df <- data.frame(A = 1:5, B = 6:10, C = 11:15)
rhandsontable(df)

In this example, if a user selects a cell from column C and presses the right arrow key, the selection jumps back to column A. I want to prevent this looping behavior.


Edit

Based on Stéphane answers, I generalized his code to all the arrow keys and also included an exception for the SHIFT and CTRL key combination. Although the SHIFT combination is working as default, the CTRL one is not working.

library(rhandsontable) library(htmlwidgets) # to use the onRender function

js <- c(
  "function(el, x) {",
    "var hot = this.hot;",
    "const firstCol = 0;",
    "const lastCol = hot.countCols() - 1;",
    "const firstRow = 0;",
    "const lastRow = hot.countRows() - 1;",
    "Handsontable.hooks.add('beforeKeyDown', function(e) {",
      "if(e.which == 37 && !e.shiftKey && !e.ctrlKey) { // Left key",
        "e.stopImmediatePropagation();",
        "e.preventDefault();",
        "const selected = hot.getSelected();",
        "if(selected !== void 0) {",
          "const [row, column, row2, column2] = selected[0];",
          "if(column != firstCol) {",
            "hot.selectCell(row, column - 1);",
          "}",
        "}",
      "}",
      "if(e.which == 38 && !e.shiftKey && !e.ctrlKey) { // Up key",
        "e.stopImmediatePropagation();",
        "e.preventDefault();",
        "const selected = hot.getSelected();",
        "if(selected !== void 0) {",
          "const [row, column, row2, column2] = selected[0];",
          "if(row != firstRow) {",
            "hot.selectCell(row - 1, column);",
          "}",
        "}",
      "}",
      "if(e.which == 39 && !e.shiftKey && !e.ctrlKey) { // Right key",
        "e.stopImmediatePropagation();",
        "e.preventDefault();",
        "const selected = hot.getSelected();",
        "if(selected !== void 0) {",
          "const [row, column, row2, column2] = selected[0];",
          "if(column != lastCol) {",
            "hot.selectCell(row, column + 1);",
          "}",
        "}",
      "}",
      "if(e.which == 40 && !e.shiftKey && !e.ctrlKey) { // Down key",
        "e.stopImmediatePropagation();",
        "e.preventDefault();",
        "const selected = hot.getSelected();",
        "if(selected !== void 0) {",
          "const [row, column, row2, column2] = selected[0];",
          "if(row != lastRow) {",
            "hot.selectCell(row + 1, column);",
          "}",
        "}",
      "}",
    "});",
  "}"
)

df <- data.frame(A = 1:5, B = 6:10, C = 11:15)
rhandsontable(df) %>% onRender(js)

Solution

  • Here is how to do for the right arrow.

    library(rhandsontable)
    library(htmlwidgets) # to use the onRender function
    
    js <- c(
      "function(el, x) {",
      "  var hot = this.hot;",
      "  const lastcol = hot.countCols() - 1;", # the index of the last column
      "  Handsontable.hooks.add('beforeKeyDown', function(e) {",
      "    if(e.which == 39) {", # 39 is the key code for the right arrow
      "      e.stopImmediatePropagation();",
      "      e.preventDefault();",
      "      const selected = hot.getSelected();",
      "      if(selected !== void 0) {",
      "        const [row, column, row2, column2] = selected[0];",
      "        if(column != lastcol) {",
      "          hot.selectCell(row, column+1);",
      "        }",
      "      }",
      "    }",
      "  });",
      "}"
    )
    
    df <- data.frame(A = 1:5, B = 6:10, C = 11:15)
    rhandsontable(df) %>% onRender(js)
    

    With newer versions of the handsontable library, we could use the following JavaScript code instead:

    js <- c(
      "function(el, x) {",
      "  var hot = this.hot;",
      "  const lastcol = hot.countCols() - 1;",
      "  const gridContext = hot.getShortcutManager().getContext('grid');",
      "  gridContext.removeShortcutsByKeys(['ArrowRight']);",
      "  gridContext.addShortcut({",
      "    group: 'navigation',",
      "    keys: [[ArrowRight]],",
      "    runOnlyIf: () => hot.getSelected() !== void 0,",
      "    callback: () => {",
      "      const selected = hot.getSelected();",
      "      const [row, column, row2, column2] = selected[0];",
      "      if(column != lastcol) {",
      "        hot.selectCell(row, column+1);",
      "      }",
      "    }",
      "  });",
      "}"
    )
    

    But rhandsontable is not maintained and uses an old version of handsontable.