3
votes

I have a selectizeInput UI object in my Shiny app and I would like to be able to copy and paste a comma-delimited list of inputs (i.e. copy from and paste to the selectizeInput).

At the moment I can copy the comma-delimited list of inputs (i.e. A, B, C, D) from elsewhere and then paste it in my selectizeInput. Paste only works using "Ctrl + V", not "right-click + paste", but this is fine.

I would like to be able to also copy my inputs from the selectizeInput object so I can paste them elsewhere.

See code below (the first choice is an empty string, "", as I do not want anything to be selected at the beginning):

selectizeInput(
    inputId = "genes_list",
    label = "Genes",
    width = "100%",
    multiple = TRUE,
    choices = c("", genes),
    selected = "",
    options = list(
    delimiter = ',',
    create = I("function(input, callback){
        return {
        value: input,
        text: input };
    }")))

I can select all inputs using "Ctrl + A" or specific inputs using "Ctrl + mouse-click" (I know inputs have been selected as they change color when selected) but then "Ctrl + C" or "Ctrl + X" do not work. Also, right-clicking on the selected inputs does not provide a "Copy" option.

Ideally, I would like to use "Ctrl + A" or "Ctrl + mouse-click" to select my inputs and then use "Ctrl + C" to copy them.

Thanks

1

1 Answers

1
votes

This is a bit of a long-winded solution, but it works. It injects a javascript behaviour into your selectizeInput of copying into clipboard/pastebin when a person uses copy-paste shortcuts.

gif of some items being selected, then copied into a text-editor, and then pasted back

There are much cleaner ways to do it, but they require more advanced concepts, like separate .js files. So here is the essier, but messier way.

Below is the code, so you see roughly what it does (all the console.log() bits can be removed and are there for you to see all steps and how they happen. To see the 'console' onen dev tools in your browser, and there is a console panel there (it's sort of like a 'kitchen-door/gossip wall' of your app).

Here's the javascript that will do it, below is the explanation where to add it. In short it will:

  • once the page is loaded
  • add a new behaviour whenever someone tries to copy
  • when someone tries to copy, check if the field they copy from is a selectize-input
  • if it is, grab text in it, separate it by comas, and put thsat in the clipboard/pastebin

Javascript Code:

console.log("page will load now");
document.addEventListener("DOMContentLoaded", function(){
  console.log("page loaded");
  document.addEventListener("copy", (event) => {
    console.log("coppying from item:", event.target);
    const anchorNode = document.getSelection().anchorNode
    if (anchorNode instanceof HTMLElement && anchorNode.classList.contains("selectize-input")) {
       const items = Array.from(anchorNode.getElementsByClassName("item active"))
      const selectedItemsAsString = items.map(i => i.innerText).join(", ")
      console.log("coppied content:", selectedItemsAsString);
      event.clipboardData.setData("text/plain", selectedItemsAsString)
      event.preventDefault()
    }
  })
});

Explanation where to put it, at the end of your ui <- fluidPage( ..... )

ui <- fluidPage( some_components_of_yours,
                 selectizeInput(
                       "codeInput", 
                       label = "Codes (if pasting, coma separated)",
                       choices = c("", genes), 
                       multiple = T,
                       options = list(delimiter = ",", create = T), 
                 ),
                 some_components_of_yours, 
                 tags$script(HTML(
                    'HERE IN THESE SINGLE QUOTES PUT THE JAVASCRIPT CODE FROM ABOVE'
                )))

So it will look a bit like this:

ui <- fluidPage(
                 selectizeInput(
                       "codeInput", 
                       label = "Codes (if pasting, coma separated)",
                       choices = c("", genes), 
                       multiple = T,
                       options = list(delimiter = ",", create = T), 
                 ),
                 tags$script(HTML(
                    '    console.log("page will load now");
    document.addEventListener("DOMContentLoaded", function(){
      console.log("page loaded");
      document.addEventListener("copy", (event) => {
        console.log("coppying from item:", event.target);
        const anchorNode = document.getSelection().anchorNode
        if (anchorNode instanceof HTMLElement && anchorNode.classList.contains("selectize-input")) {
           const items = Array.from(anchorNode.getElementsByClassName("item active"))
          const selectedItemsAsString = items.map(i => i.innerText).join(", ")
          console.log("coppied content:", selectedItemsAsString);
          event.clipboardData.setData("text/plain", selectedItemsAsString)
          event.preventDefault()
        }
      })
    });'
                )))

My solution is based on these answers of other people: