In R Shiny, is it possible to select a group of check boxes by click-and-dragging over them?

56 views Asked by At

Problem

I want to represent a 6x8 grid of check boxes that have the same input ID. To save time, I want to be able to select multiple check boxes at once by holding 'click' and dragging over my intended selection.

Attempted ideas so far

This may require javascript, or something like jqui_selectable(), however I haven't been able to make these work. Is this possible?

1

There are 1 answers

0
Stéphane Laurent On

Here is a first step. Source.

Unfortunately, that doesn't work with Shiny checkboxes. So I added a click listener of the checkboxes in the JavaScript file but it doesn't work, I have no idea why.

R code:

library(shiny)


ui <- fluidPage(
  tags$head(
    tags$script(src = "checkboxes.js"),
    tags$link(href = "checkboxes.css", rel = "stylesheet")
  ),
  br(),
  h3("Left-click and drag to select."),
  h3("Right-click and drag to deselect."),
  br(),
  verbatimTextOutput("checked"),
  br(),
  
  tags$div(
    class = "checkboxes",
    tags$label(
      tags$input(type = "checkbox", id = "checkboxA"),
      tags$span("A")
    ),
    br(),
    tags$label(
      tags$input(type = "checkbox", id = "checkboxB"),
      tags$span("B")
    ),
    br(),
    tags$label(
      tags$input(type = "checkbox", id = "checkboxC"),
      tags$span("C")
    ),
    br(),
    tags$label(
      tags$input(type = "checkbox", id = "checkboxD"),
      tags$span("D")
    ),
    br(),
    tags$label(
      tags$input(type = "checkbox", id = "checkboxE"),
      tags$span("E")
    ),
    br(),
    tags$label(
      tags$input(type = "checkbox", id = "checkboxF"),
      tags$span("F")
    )
  ),
  
  br(),

)

server <- function(input, output, session) {
  output[["checked"]] <- renderPrint({
    input[["checkboxA"]]
  })
}

shinyApp(ui, server)

Files checkboxes.js and checkboxes.css, to put in the www subfolder of the app:

var sx;
var sy;
var ex;
var ey;
var ta;
var drag = false;

$(document).ready(function() {
  var dragArea = document.createElement("div");
  dragArea.setAttribute("class", "dragArea");
  document.body.appendChild(dragArea);
  
  $("input[type=checkbox]").on("click", function() {
    alert("checkbox clicked"); // this never happens!
    var id = $(this).attr("id");
    var checked = $(this).prop("checked");
    Shiny.setInputValue(id, checked);
  });
});

document.oncontextmenu = function() {
  return false;
};

$(document).on("mousemove", function(event) {
  if(drag) {
    var flipX = "";
    var flipY = "";
    var top = sy;
    var left = sx;
    var height = event.pageY - sy;
    var width = event.pageX - sx;

    if(event.pageX - sx < 0) {
      flipX = "scaleX(-1)";
      width = sx - event.pageX;
      left = event.pageX;
    }

    if(event.pageY - sy < 0) {
      flipY = "scaleY(-1)";
      height = sy - event.pageY;
      top = event.pageY;
    }

    $(".dragArea").attr(
      "style",
      "display:block; top:" +
        top +
        "px;  left:" +
        left +
        "px; width:" +
        width +
        "px; height: " +
        height +
        "px; transform:" +
        flipX +
        " " +
        flipY +
        ";"
    );
  }
});

$(document).on("mousedown", function(event) {
  drag = true;
  sx = event.pageX;
  sy = event.pageY;

  switch(event.which) {
    case 1:
      //Left Mouse button pressed.
      $(".dragArea").addClass("add");
      $(".dragArea").removeClass("remove");
      break;
    case 3:
      //Right Mouse button pressed.
      $(".dragArea").addClass("remove");
      $(".dragArea").removeClass("add");
      break;
    default:
    //do nothing
  }
  
  $(".dragArea").attr(
    "style",
    "display:block; top:" +
      event.pageY +
      "px; left:" +
      event.pageX +
      "px; width:0; height:0;"
  );
  
});

$(document).on("mouseup", function(event) {
  drag = false;
  $(".dragArea").attr("style", "display:none;");

  ex = event.pageX;
  ey = event.pageY;

  if(ex < sx) {
    ta = ex;
    ex = sx;
    sx = ta;
  }

  if(ey < sy) {
    ta = ey;
    ey = sy;
    sy = ta;
  }
  
  switch(event.which) {
    case 1:
      //Left Mouse button pressed. Check
      selectItems(true);
      break;
    case 3:
      //Right Mouse button pressed. Uncheck
      selectItems(false);
      break;
    default:
    //do nothing
  }
  
});

function selectItems(checked) {
  $("input[type=checkbox]").each(function() {
    var pos = $(this).position();
    var cxLow = pos.left;
    var cxHi = pos.left + $(this).width();
    var cyLow = pos.top;
    var cyHi = pos.top + $(this).height();
    if(cxLow <= sx && sx <= cxHi && cxLow <= ex && ex <= cxHi) {
      if(cyLow <= sy && sy <= cyHi && cyLow <= ey && ey <= cyHi) {
        $(this).prop("checked", !$(this).prop("checked"));
      }
    } else if(sx <= cxHi && ex >= cxLow) {
      if(sy <= cyHi && ey >= cyLow) {
        $(this).prop("checked", checked);
      }
    }

  });
}
.dragArea {
  position: absolute;
  display: none;
  width: 100px;
  height: 100px;
  border: 1px solid rgba(0, 0, 0, 0.15);
  background: rgba(0, 0, 0, 0.05);
}

.dragArea.remove {
  border: 1px solid rgba(255, 0, 0, 0.15);
  background: rgba(255, 0, 0, 0.05);
}

.dragArea.add {
  border: 1px solid rgba(0, 0, 255, 0.15);
  background: rgba(0, 0, 255, 0.05);
}

enter image description here