This is a follow-up question to this Drag and drop with shinyjqui to a grid-table
This is the code:
library(shiny)
library(shinyjqui)
connections <- paste0("droppable_cell_", 1:7) # id of the grid cells
ui <- fluidPage(
tags$head(tags$script(
JS(
"
$(function() {
$('[id^=droppable_cell]').sortable({
connectWith: '#letters',
drop: function(event, ui) {
$(this).append(ui.draggable);
}
})
});
"
)
),
# some styling
tags$style(
HTML(
"
.grid-table {
width: 150px;
border-collapse: collapse;
}
.grid-cell {
width: 100%;
height: 50px;
border: 1px solid black;
background-color: white;
text-align: center;
margin: 0;
padding: 5px;
}
.grid-cell-text {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background-color: steelblue;
color: white;
font-size: 18px;
}
.droppable-cell {
background-color: lightgray;
}
.table-container {
display: flex;
position: absolute;
left: 550px;
top: 30px;
margin-top: 0px;
overflow: hidden;
}
"
)
)),
div(
class = "table-container",
div(
class = "grid-table",
id = "my_grid",
div(
class = "grid-row",
div(class = "grid-cell grid-cell-text", "my_grid"),
div(id = "droppable_cell_1", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_2", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_3", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_4", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_5", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_6", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_7", class = "grid-cell droppable-cell", "")
)
),
orderInput('letters', 'Letters', items = LETTERS[1:7],
connect = connections) # defined above
)
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
I am attempting to implement drag-and-drop functionality using an observeEvent triggered by an actionButton. My objective is to enable dragging and dropping of a vector, let's say vec <- c(A, B, C), onto the my_grid table through a button click.
Context: The underlying concept involves preselecting and positioning items in specific locations within the my_grid. It's important to note that when items are moved to the my_grid, they should also vanish from the letters section.
As discussed in the comments, we define two suggestions for the placement of the letters at the beginning, e.g. here as given by you
connections <- paste0("droppable_cell_", 1:7) # id of the grid cells
vec_suggestion1 <- c("A", NA, "G", NA, "B", "C", "D")
vec_suggestion2 <- c("A", "B", "C", "D", "E", "F", "G")
df <- data.frame(
connections = connections,
vec_suggestion1 = vec_suggestion1,
vec_suggestion2 = vec_suggestion2
)
, and we would like to implement three buttons. Two of them are used for filling the grid as given by the two suggestions and one of them shall have a functionality for resetting the Drag and Drop. Also, the original Drag and Drop functionality has to stay as it is. This should look like this:
We use a shinyjs
approach here which adds some custom JS
functions to the buttons. I explain the important changes in the following and include the full minimal working example at the end.
The buttons are defined as usual inside the ui
, e.g.
actionButton("btn_suggestion1", "Suggestion 1")
Inside the server, we use
shinyjs::js$pageCol(df)
observeEvent(input$btn_suggestion1, {
shinyjs::disable("btn_suggestion1")
shinyjs::js$setSuggestion(1)
shinyjs::enable("btn_suggestion1")
})
As requested by you, the observeEvent
is triggered by the button and will basically call a function setSuggestion
with parameter 1. The 1 is the column index of the choices in df
(since JS
indices start with 0). The function is defined inside pageCol
, which is the important part here. pageCol
gets df
as a parameter and contains
var dataArray = Object.values(params[0]);
dataArray = dataArray[0].map((col, i) => dataArray.map(row => row[i]));
var cacheLetters = $('#letters').html();
var cacheGridCells = $('[id^=droppable_cell]').html();
dataArray
is just df
converted into JS
, and we transpose it for convenience. cacheLetters
and cacheGridCells
are used for saving the original state of the letters and the grid (this will be used for the reset button later).
setSuggestion
function shinyjs.setSuggestion = function (idxSuggestion) {
// loop over the array rows
$.each(dataArray, function (index, value) {
// define the selector for the grid cell using the first array column
var cellSelector = '#' + dataArray[index][0];
// define the new innerHTML of the grid cell such that it will
// contain the shinyjqui sortable element
var cellHTML = '<div data-value=\"'
+ dataArray[index][idxSuggestion]
+ '\" class=\"btn btn-default ui-sortable-handle\" style=\"margin: 1px;\" jqui_sortable_idx=\"letters__'
+ (index + 1).toString()
+ '\">'
+ dataArray[index][idxSuggestion]
+ '</div>';
// if the current value is na, next
if (dataArray[index][idxSuggestion] === null) {
return true;
}
// change the innerHTML of the grid cell such that it gets the letter attached
$(cellSelector).html(cellHTML);
// drop the current letter from the original list
$('#letters').find(`[data-value='${dataArray[index][idxSuggestion]}']`)[0].remove()
})
}
resetDnD
function. shinyjs.resetDnD = function (params){
$('#letters').html(cacheLetters).sortable('refresh');
$('[id^=droppable_cell]').html(cacheGridCells).sortable('refresh');
}
It just refreshes the letters and the droppable cells using the initially cached HTML
of the elements.
Here is the complete example:
library(shiny)
library(shinyjqui)
library(shinyjs)
connections <- paste0("droppable_cell_", 1:7) # id of the grid cells
vec_suggestion1 <- c("A", NA, "G", NA, "B", "C", "D")
vec_suggestion2 <- c("A", "B", "C", "D", "E", "F", "G")
df <- data.frame(
connections = connections,
vec_suggestion1 = vec_suggestion1,
vec_suggestion2 = vec_suggestion2
)
js <- "shinyjs.pageCol = function(params){
$('[id^=droppable_cell]').sortable({
connectWith: '#letters',
drop: function(event, ui) {
$(this).append(ui.draggable);
}
})
var dataArray = Object.values(params[0]);
dataArray = dataArray[0].map((col, i) => dataArray.map(row => row[i]));
var cacheLetters = $('#letters').html();
var cacheGridCells = $('[id^=droppable_cell]').html();
shinyjs.setSuggestion = function (idxSuggestion) {
$.each(dataArray, function (index, value) {
var cellSelector = '#' + dataArray[index][0];
var cellHTML = '<div data-value=\"'
+ dataArray[index][idxSuggestion]
+ '\" class=\"btn btn-default ui-sortable-handle\" style=\"margin: 1px;\" jqui_sortable_idx=\"letters__'
+ (index + 1).toString()
+ '\">'
+ dataArray[index][idxSuggestion]
+ '</div>';
if (dataArray[index][idxSuggestion] === null) {
return true;
}
$(cellSelector).html(cellHTML);
$('#letters').find(`[data-value='${dataArray[index][idxSuggestion]}']`)[0].remove()
})
}
shinyjs.resetDnD = function (params){
$('#letters').html(cacheLetters).sortable('refresh');
$('[id^=droppable_cell]').html(cacheGridCells).sortable('refresh');
}
};
"
ui <- fluidPage(
useShinyjs(),
extendShinyjs(text = js, functions = c("pageCol", "resetDnD", "setSuggestion")),
tags$head(
# some styling
tags$style(
HTML(
"
.grid-table {
width: 150px;
border-collapse: collapse;
}
.grid-cell {
width: 100%;
height: 50px;
border: 1px solid black;
background-color: white;
text-align: center;
margin: 0;
padding: 5px;
}
.grid-cell-text {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background-color: steelblue;
color: white;
font-size: 18px;
}
.droppable-cell {
background-color: lightgray;
}
.table-container {
display: flex;
position: absolute;
left: 400px;
top: 30px;
margin-top: 0px;
overflow: hidden;
}
#btn_suggestion1, #btn_suggestion2 {
background-color: lightblue;
}
#btn_resetDnD {
background-color: pink;
}
"
)
)),
actionButton("btn_suggestion1", "Suggestion 1"),
actionButton("btn_suggestion2", "Suggestion 2"),
actionButton("btn_resetDnD", "Reset Drag and Drop"),
div(
class = "table-container",
div(
class = "grid-table",
id = "my_grid",
div(
class = "grid-row",
div(class = "grid-cell grid-cell-text", "my_grid"),
div(id = "droppable_cell_1", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_2", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_3", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_4", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_5", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_6", class = "grid-cell droppable-cell", ""),
div(id = "droppable_cell_7", class = "grid-cell droppable-cell", "")
)
),
orderInput('letters', 'Letters', items = LETTERS[1:7],
connect = connections) # defined above
)
)
server <- function(input, output, session) {
shinyjs::js$pageCol(df)
observeEvent(input$btn_suggestion1, {
shinyjs::disable("btn_suggestion1")
shinyjs::js$setSuggestion(1)
shinyjs::enable("btn_suggestion1")
})
observeEvent(input$btn_suggestion2, {
shinyjs::disable("btn_suggestion2")
shinyjs::js$setSuggestion(2)
shinyjs::enable("btn_suggestion2")
})
observeEvent(input$btn_resetDnD, {
shinyjs::disable("btn_resetDnD")
shinyjs::js$resetDnD()
shinyjs::enable("btn_resetDnD")
})
}
shinyApp(ui, server)