GithubHelp home page GithubHelp logo

Comments (11)

yogat3ch avatar yogat3ch commented on June 15, 2024 1

@yogat3ch

Is there any simple way to signal DT (...) using the enter key (for keyboard accessbility) (...) but from Javascript?

Have you read and search "accessibility" about datatables.net ? If it's not possible in datatables.net, it will be difficult to implement it in DT, IMHO.

Hi @philibe ,
Thanks for jumping in here!
I had already perused a good number of those articles you suggested and just took a look at the rest. Part of the partial solution above was built based on the last SO post linked.
I'm not looking for full scale accessibility via Datatables, just to mimic the DT click to select functionality but bound to the enter key.
I've picked up the enter keypress and can add the .selected class to the tr but looking through the DT HTML widgets js suggests that there's more to emulating a single click select than just setting the class.
I'm looking to replicate that logic with JavaScript in the callback.
Does that clarify the scope of the issue?

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024 1

For whomever is also searching for a solution to this, a partial solution is detailed below. A JS file needs to be inserted as a script, either with includeScript or in your www folder.
The script needs to have the following:

Javascript
 /**
 * Return an array of all matching indices
 *
 * @param {Array} arr to be matched against
 * @param {Function} fn of function that returns {Logical}
 * @return {Array} of matching indexes
 */
function tagMatch(arr, fn) {
  let idx = [];
  if (arr instanceof jQuery) {
    idx = arr.map((i, e) => {
      let out = null;
      if (fn(e)) {
        out = i;
      }
      return out; 
    });
    idx = Array.from(jqueryObjsOnly(idx));
  } else {
    idx = arr.reduce(function(a, e, i) {
      if (fn(e))
          a.push(i);
      return a;
    }, []);
  }
  return idx;
}

/**
 * @param  {Object} jq - A jQuery DOM object
 * @returns {Array} stripped of jQuery methods
 */
function jqueryObjsOnly(jq) {
  var out = [];
  for (i = 0; i < jq.length; i++) {
    out.push(jq[[i]]);
  }
  return out;
}


  /**
   * Make a DT keyboard accessible by enabling return key to select, populates the `_row_last_clicked` and the `_rows_selected` Shiny inputs
   *
   * @param {DataTable} table A DataTable object, passed in via the callback automatically.
   */
  /**
   * Make a DT keyboard accessible by enabling return key to select, populates the `_row_last_clicked` and the `_rows_selected` Shiny inputs
   *
   * @param {DataTable} table A DataTable object, passed in via the callback automatically.
   */
  function dt_tab_accessible(table) {
    
    table.on('key', function(e, datatable, key, cell, originalEvent){
      var table_id = cell.table().node().id;
          var shiny_id = $('#' + table_id).parent().parent().attr('id');
          if (key == 13){
            // When return is pressed
            // Select the row
            let row_idx = cell.index().row;
            let tr = $(datatable.row(row_idx).node())
            if (tr.hasClass("selected")) {
              tr.removeClass('selected');
            } else {
              tr.addClass("selected")
            }
            
            // TODO how to set class on tr, update crosstalk inputs?
            // Update the Shiny inputs
            // row_last_clicked
            Shiny.setInputValue(shiny_id + '_row_last_clicked', row_idx, {priority: 'event'})
            // rows_selected
            let trs = tr.parent("tbody").children('tr');
            let sel_idx = tagMatch(trs, (e)=> {return $(e).hasClass('selected')});
            let rows_selected = sel_idx.map(i => i+1) 
            Shiny.setInputValue(shiny_id + '_rows_selected', rows_selected, {priority: 'event'})
          }
    });
  }

the R app looks like this, where the argument to includeScript is the path to your JS script.

R
library(shiny)
library(DT)
devtools::load_all()

ui <- fluidPage(
  shiny::includeScript(UU::path_strip_shiny(dirs$js("custom_fns.js"))),
  title = 'Select Table Rows',
  h1('A Server-side Table'),

  fluidRow(
    column(9, DT::dataTableOutput('x3')),
    column(3, verbatimTextOutput('x4'))
  )

)
server <- function(input, output, session) { output$x1 = DT::renderDataTable(cars, server = FALSE)

# highlight selected rows in the scatterplot
output$x2 = renderPlot({
  s = input$x1_rows_selected
  par(mar = c(4, 4, 1, .1))
  plot(cars)
  if (length(s)) points(cars[s, , drop = FALSE], pch = 19, cex = 2)
})

# server-side processing
mtcars2 = mtcars[, 1:8]
output$x3 = DT::renderDataTable({
  DT::datatable(
    mtcars2,
    callback = DT::JS("
      dt_tab_accessible(table);
    "),
    extensions = "KeyTable",  # Enable tabindexes,
    options = list(keys = TRUE) # Enable tabindexes
    )
}, server = TRUE)

# print the selected indices
output$x4 = renderPrint({
  s = input$x3_rows_selected
  if (length(s)) {
    cat('These rows were selected:\n\n')
    cat(s, sep = ', ')
  }
})
}

shinyApp(ui, server)

This enables the bare minimum functionality of using the Enter key to "select" rows which will populate the _row_last_clicked and the _rows_selected Shiny inputs with the table selections. Limitations: crosstalk functionality is omitted, so it will not update selections in crosstalk. _cells_clicked input is not updated.

from dt.

stla avatar stla commented on June 15, 2024

You want to use the server-side feature?

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024

Hi @stla,
The typical DT selection, not the one enabled by the Select extension, if that's what you're getting at?

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024

@stla,
It looks like changeInput is involved in setting the Shiny values

var changeInput = function(id, value, type, noCrosstalk, opts) {

selectedRows is used to create an index of selected rows (dependent on them having the .selected class):

var selectedRows = function() {
          var rows = table.rows('.' + selClass);
          var idx = rows.indexes().toArray();
          if (!server) {
            selected1 = addOne(idx);
            return selected1;
          }
          idx = idx.map(function(i) {
            return DT_rows_current[i];
          });
          selected1 = selMode === 'multiple' ? unique(selected1.concat(idx)) : idx;
          return selected1;
        }

selectRows appears to be involved in adding remove rows:

var selectRows = function(ignoreSelectable) {

The declaration of these inline functions has them inheriting a lot of variables from their parent scope though

from dt.

yihui avatar yihui commented on June 15, 2024

It looks like changeInput is involved in setting the Shiny values

@yogat3ch Yes, if you want to pass a value from JavaScript to R, Shiny.setInputValue() is the way to go. I talked about this a number of years ago: https://slides.yihui.org/2016-Shiny-DT-Yihui-Xie.html#(7)

Official documentation: https://shiny.posit.co/r/articles/build/communicating-with-js/

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024

Thanks @yihui & @stla , I think I've gotten that far. Here's a reprex with what's implemented thus far, with some interspersed pseudo-code with the outstanding questions:

library(shiny)
library(DT)

ui <- fluidPage(

  title = 'Select Table Rows',
  h1('A Server-side Table'),

  fluidRow(
    column(9, DT::dataTableOutput('x3')),
    column(3, verbatimTextOutput('x4'))
  )

)
server <- function(input, output, session) { output$x1 = DT::renderDataTable(cars, server = FALSE)

# highlight selected rows in the scatterplot
output$x2 = renderPlot({
  s = input$x1_rows_selected
  par(mar = c(4, 4, 1, .1))
  plot(cars)
  if (length(s)) points(cars[s, , drop = FALSE], pch = 19, cex = 2)
})

# server-side processing
mtcars2 = mtcars[, 1:8]
output$x3 = DT::renderDataTable({
  DT::datatable(
    mtcars2,
    callback = DT::JS("

      function dt_tab_accessible(table) {
        table.on('key', function(e, datatable, key, cell, originalEvent){
          var table_id = cell.table().node().id;
          var shiny_id = $('#' + table_id).parent().parent().attr('id');
          if (key == 13){
            debugger;
            // When return is pressed
            // Select the row
            let row_idx = cell.row().index() + 1;
            // TODO how to set class on tr, update crosstalk inputs?
            // Update the Shiny inputs
            // row_last_clicked
            Shiny.setInputValue(shiny_id + '_row_last_clicked', row_idx, {priority: 'event'})
            // rows_selected
            // TODO How to get currently selected rows as numeric index?
            let rows_selected = undefined;
            Shiny.setInputValue(shiny_id + '_rows_selected', rows_selected, {priority: 'event'})
          }
        });
      }
    dt_tab_accessible(table);
    "),
    extensions = "KeyTable",  # Enable tabindexes,
    options = list(keys = TRUE)
    )
}, server = TRUE)

# print the selected indices
output$x4 = renderPrint({
  s = input$x3_rows_selected
  if (length(s)) {
    cat('These rows were selected:\n\n')
    cat(s, sep = ', ')
  }
})
}

shinyApp(ui, server)

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024

Is the .selected class what signals to DT that a row is selected? If I just add the .selected class to the associated tr tag on each click, would the table.rows({selected: true}) return all the currently clicked/selected rows?

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024

It looks like DT doesn't classify a row as selected if it has the .selected class, I added it to the clicked row using JQuery and this is what it the rows({selected: true}) method returns:
image
Does this matter? Could I use Jquery to get the indices of the rows with .selected and just use those as the _rows_selected? Would there be a risk of the DT heuristic for selected rows desynchronizing from the Shiny inputs here?
I'm a little hesitant to override the Shiny inputs with this approach because I think subsequent mouse clicks may compute selected rows differently, and would rather just cue DT using Javascript to run it's own code for "selecting" a row if thats a safer approach?

from dt.

yogat3ch avatar yogat3ch commented on June 15, 2024

Thoughts on the above @stla & @yihui?
Is there any simple way to signal DT to highlight a row when using the enter key (for keyboard accessbility), mimicking a mouse click, but from Javascript? That seems like the safest approach but I'm not sure it's feasible...

from dt.

philibe avatar philibe commented on June 15, 2024

@yogat3ch

Is there any simple way to signal DT (...) using the enter key (for keyboard accessbility) (...) but from Javascript?

Have you read and search "accessibility" about datatables.net ? If it's not possible in datatables.net, it will be difficult to implement it in DT, IMHO.

from dt.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.