При запуске приведенного ниже упрощенного кода пользователь может добавлять или удалять строки таблицы, щелкнув правой кнопкой мыши строку, которая через контекстное меню пакета rhandsontable
генерирует всплывающее окно с выбором действий. В коде вы можете увидеть, как я использовал функцию onRender(...)
и JavaScript, чтобы отключить удаление строк, когда в таблице только 1 строка. Это работает.
Однако я также хотел бы либо деактивировать, либо удалить (в зависимости от того, что проще) выбор «Вставить строку выше», когда пользователь обращается к контекстному меню из первой строки таблицы. Когда пользователь обращается к контекстному меню из любой строки таблицы, кроме первой, функция «Вставить строку выше» должна работать. По сути, мне никогда не нужна пустая верхняя строка. Есть идеи, как это сделать?
Я просмотрел список хуков handsontable
и не смог найти эквивалента «beforeAddRow» или тому подобного.
library(shiny)
library(rhandsontable)
library(htmlwidgets)
ui <- fluidPage(
br(),
rHandsontableOutput("simple_table")
)
server <- function(input, output) {
output$simple_table <- renderRHandsontable({
rhandsontable(data.frame(Value = numeric(1)), contextMenu = TRUE, rowHeaders = TRUE) %>%
hot_cols(colWidths = 100, type = "numeric") %>%
# Below prevents row deletion when there is only 1 row in table
onRender(
c(
"function(el, x) {",
" var hot = this.hot;",
" Handsontable.hooks.add('beforeRemoveRow', function(index, amount){",
" var nrows = hot.countRows();",
" if (nrows === 1) {",
" return false;",
" }",
" }, hot);",
"}"
)
)
})
}
shinyApp(ui = ui, server = server)
Поправьте Ян, если есть несколько строк, пользователю не должно быть разрешено добавлять строку выше верхней строки. Спасибо, что обдумали это!
Я думаю, что лучший вариант — удалить ненужные элементы из контекстного меню, потому что в противном случае пользователя может сбить с толку, почему при нажатии на некоторые элементы ничего не происходит. Вот решение, которое не требует кастомизации Javascript
. Здесь я рассматриваю ситуацию, когда вы хотите, чтобы «Вставить строку выше» и «Удалить строку» не были видны, если в таблице есть только одна строка.
Идея состоит в том, что внутри renderRHandsontable
у нас также есть задание rhot$x$contextMenu <- ContextMenuItems()
, где ContextMenuItems
— это reactive
, содержащее пункты меню, которые мы обновляем внутри observeEvent
на таблице. Внутри этого observeEvent
мы различаем регистр nrow() == 1
и nrow() > 1
и устанавливаем элементы соответственно.
library(shiny)
library(rhandsontable)
ui <- fluidPage(
br(),
rHandsontableOutput("simple_table")
)
server <- function(input, output) {
ContextMenuItems <- reactiveVal(list(items = c("row_below",
"---------", "undo", "redo",
"---------", "alignment")))
Data <- reactiveVal(data.frame(Value = numeric(1)))
observeEvent(input$simple_table, {
Data(hot_to_r(input$simple_table))
nrows_table <- nrow(Data())
newIt <- ContextMenuItems()
if (nrows_table == 1) {
newIt$items <- newIt$items[!(newIt$items %in% c("row_above", "remove_row"))]
} else if (!(any(c("row_above", "remove_row") %in% newIt$items))) {
newIt$items <- newIt$items |> append("row_above", 0) |> append("remove_row", 2)
}
ContextMenuItems(newIt)
})
output$simple_table <- renderRHandsontable({
rhot <- rhandsontable(Data(), contextMenu = TRUE, rowHeaders = TRUE) %>%
hot_cols(colWidths = 100, type = "numeric")
rhot$x$contextMenu <- ContextMenuItems()
rhot
})
}
shinyApp(ui = ui, server = server)
Альтернативный подход, использующий только JavaScript, просто игнорирует любую попытку пользователя вставить строку над первой строкой таблицы. Вот ключевой модифицированный код из раздела сервера:
rhandsontable(data.frame(Value = numeric(1)), contextMenu = TRUE, rowHeaders = TRUE) %>%
hot_cols(colWidths = 100, type = "numeric") %>%
onRender(
c(
"function(el, x) {",
" var hot = this.hot;",
" Handsontable.hooks.add('beforeCreateRow', function(index, amount) {",
" if (index === 0) {",
" return false;",
" }",
" });",
"}"
)
)
Свойство disabled
для пункта контекстного меню может быть функцией JS, которая имеет
доступ к таблице как this
. В отличие от подхода с крючком, это немного
более сложно определить, где строки будут вставлены или удалены, но это
должно привести к улучшению пользовательского опыта.
contextMenuSettings <- list(
items = list(
row_above = list(
# Disable "insert above" if new row would go above the first row.
disabled = htmlwidgets::JS("function() {
// Insertion happens above last selection.
var selected = this.getSelectedRangeLast();
var topRow = Math.min(selected.from.row, selected.to.row);
return topRow <= 0;
}")
),
row_below = list(),
remove_row = list(
# Disable "remove row" if all rows would be removed.
disabled = htmlwidgets::JS("function() {
// Same row can be selected in multiple ranges.
var selectedRows = new Set();
for (var selected of this.getSelectedRange()) {
var topRow = Math.min(selected.from.row, selected.to.row);
var bottomRow = Math.max(selected.from.row, selected.to.row);
for (var row = topRow; row <= bottomRow; row++) {
selectedRows.add(row);
}
}
return this.countRows() <= selectedRows.size;
}")
),
sp1 = '---------',
undo = list(),
redo = list()
)
)
Пользовательские настройки необходимо установить после того, как rhandsontable()
был установлен.
созданный.
library(shiny)
library(rhandsontable)
ui <- fluidPage(
br(),
rHandsontableOutput("simple_table")
)
server <- function(input, output) {
output$simple_table <- renderRHandsontable({
hot <- rhandsontable(
data.frame(Value = numeric(1), Value2 = numeric(1)),
contextMenu = TRUE,
rowHeaders = TRUE
)
# Customize context menu.
hot$x$contextMenu <- contextMenuSettings
hot
})
}
shinyApp(ui = ui, server = server)
Basically, I never want a top row that is blank.
Разве вы не получите то же самое, если строк несколько, а пользователь выбирает верхнюю и нажимает «Вставить строку выше»?