diff --git a/DESCRIPTION b/DESCRIPTION
index e60b385..8a0b37f 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
Package: clinsight
Title: ClinSight
Version: 0.1.1.9011
-DevexVersion: 9000
+DevexVersion: 9001
Authors@R: c(
person("Leonard Daniël", "Samson", , "lsamson@gcp-service.com", role = c("cre", "aut"),
comment = c(ORCID = "0000-0002-6252-7639")),
diff --git a/NEWS.md b/NEWS.md
index 6110346..4f1fc0f 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -9,12 +9,16 @@
- Added form type as a class to be used in `create_table()` to display tables.
- Add a logging table to the DB for reviews.
- Simplify pulling data from DB for reviews.
-- Review data by records IDs instead of subject & form
## Bug fixes
- The test-coverage GHA workflow is updated so that codecov uploads work again.
+## `devex` changes
+- Added `Excel` download button to Queries table & patient listings that need review.
+- Added helper function to automatically determine when adding said excel button is appropriate.
+- Review data by records IDs instead of subject & form
+
# clinsight 0.1.1
## Changed
@@ -26,10 +30,6 @@
- Fixed inconsistencies in app messages when saving a review for a form with items with different review states (with some items reviewed previously by a different reviewer, and some items being completely new).
- Fixed a bug where clinsight deployed with `shinyproxy` would crash when a user with non-ASCII letters in their name would attempt to login. In this new version, when using the `shinyproxy` deployment configuration, the user name is now expected to be base64 encoded, and will now be base64 encoded by `clinsight` by default, so that the app can also handle non-ASCII signs in user names that are stored in HTTP headers. To display the user name correctly, use base64 encoding in the `application.yml` in ShinyProxy settings (for example: `http-headers.X_SP_USERNAME: "#{T(java.util.Base64).getEncoder().encodeToString(oidcUser.getFullName().getBytes())}"`).
-## `devex` changes
-- Added `Excel` download button to Queries table & patient listings that need review.
-- Added helper function to automatically determine when adding said excel button is appropriate.
-
# clinsight 0.1.0
## Changed
diff --git a/R/app_server.R b/R/app_server.R
index 0135aac..3d0ff4f 100644
--- a/R/app_server.R
+++ b/R/app_server.R
@@ -30,6 +30,9 @@ app_server <- function(
})
check_appdata(app_data, meta)
+ session$userData$review_records <- reactiveValues()
+ session$userData$update_checkboxes <- reactiveValues()
+
res_auth <- authenticate_server(
all_sites = app_vars$Sites$site_code,
credentials_db = credentials_db,
diff --git a/R/fct_tables.R b/R/fct_tables.R
index 6d9337a..e54a425 100644
--- a/R/fct_tables.R
+++ b/R/fct_tables.R
@@ -51,6 +51,17 @@ create_table.default <- function(
stopifnot(is.character(keep_vars))
stopifnot(is.character(name_column))
stopifnot(is.character(value_column))
+ if ("reviewed" %in% names(data)) {
+ data <- dplyr::mutate(
+ data,
+ o_reviewed = dplyr::case_when(
+ any(reviewed == "No") & any(reviewed == "Yes") ~ list(list(reviewed = NA, ids = id)),
+ any(reviewed == "Yes") ~ list(list(reviewed = TRUE, ids = id)),
+ .default = list(list(reviewed = FALSE, ids = id))
+ ),
+ .by = dplyr::all_of(keep_vars))
+ keep_vars <- c("o_reviewed", keep_vars)
+ }
df <- data[c(keep_vars, name_column, value_column)] |>
tidyr::pivot_wider(
names_from = {{name_column}},
@@ -298,6 +309,7 @@ create_table.medication <- function(
) |>
dplyr::arrange(dplyr::desc(in_use), dplyr::desc(`Start Date`)) |>
dplyr::select(
+ dplyr::any_of("o_reviewed"),
dplyr::all_of(c(keep_vars, "Name")),
dplyr::everything(),
-dplyr::all_of(c("in_use", "Active Ingredient", "Trade Name",
diff --git a/R/mod_common_forms.R b/R/mod_common_forms.R
index 5551c9a..0861d00 100644
--- a/R/mod_common_forms.R
+++ b/R/mod_common_forms.R
@@ -94,6 +94,7 @@ mod_common_forms_server <- function(
ns <- session$ns
data_active <- reactive({
+ req(!is.null(input$show_all_data))
shiny::validate(need(
!is.null(r$filtered_data[[form]]),
paste0("Warning: no data found in the database for the form '", form, "'.")
@@ -125,23 +126,90 @@ mod_common_forms_server <- function(
SAE_data <- data_active() |>
dplyr::filter(grepl("Yes", `Serious Adverse Event`)) |>
dplyr::select(dplyr::any_of(
- c("subject_id","form_repeat", "Name", "AESI", "SAE Start date",
+ c("o_reviewed", "subject_id","form_repeat", "Name", "AESI", "SAE Start date",
"SAE End date", "CTCAE severity", "Treatment related",
"Treatment action", "Other action", "SAE Category",
"SAE Awareness date", "SAE Date of death", "SAE Death reason")
)) |>
adjust_colnames("^SAE ")
if(!input$show_all_data) SAE_data$subject_id <- NULL
-
+
# determine DT dom / exts / opts
DT <- dt_config(SAE_data,
table_name = paste("SAE", ifelse(input$show_all_data,
"all_patients", r$subject_id), sep = "."))
datatable_custom(
- SAE_data, rename_vars = table_names, rownames= FALSE,
- title = "Serious Adverse Events", escape = FALSE,
- dom = DT$dom, extensions = DT$exts, options = DT$opts
- )
+ SAE_data,
+ rename_vars = c("Review Status" = "o_reviewed", table_names),
+ rownames = FALSE,
+ title = "Serious Adverse Events",
+ escape = FALSE,
+ dom = DT$dom,
+ extensions = DT$exts,
+ selection = "none",
+ callback = checkbox_callback,
+ options = append(list(
+ columnDefs = list(list(
+ targets = 0,
+ render = checkbox_render
+ )),
+ createdRow = checkbox_create_callback
+ ),
+ DT$opts))
+ }, server = FALSE)
+
+ observeEvent(data_active(), {
+ session$userData$update_checkboxes[[form]] <- NULL
+ session$userData$review_records[[form]] <- data.frame(id = integer(), reviewed = character())
+ })
+
+ observeEvent(input$common_form_table_review_selection, {
+ session$userData$update_checkboxes[[form]] <- NULL
+
+ session$userData$review_records[[form]] <-
+ dplyr::rows_upsert(
+ session$userData$review_records[[form]],
+ input$common_form_table_review_selection,
+ by = "id"
+ ) |>
+ dplyr::filter(!is.na(reviewed)) |>
+ dplyr::semi_join(
+ subset(r$review_data, subject_id == r$subject_id & item_group == form),
+ by = "id"
+ ) |>
+ dplyr::anti_join(
+ subset(r$review_data, subject_id == r$subject_id & item_group == form),
+ by = c("id", "reviewed")
+ ) |>
+ dplyr::arrange(id)
+ })
+
+ observeEvent(session$userData$update_checkboxes[[form]], {
+ checked <- session$userData$update_checkboxes[[form]]
+
+ update_cbs(ns("common_form_table"), checked)
+ update_cbs(ns("SAE_table"), checked)
+ })
+
+ observeEvent(input$SAE_table_review_selection, {
+ session$userData$update_checkboxes[[form]] <- NULL
+
+ session$userData$review_records[[form]] <-
+ dplyr::rows_upsert(
+ session$userData$review_records[[form]],
+ input$SAE_table_review_selection,
+ by = "id"
+ ) |>
+ dplyr::filter(!is.na(reviewed)) |>
+ dplyr::semi_join(
+ subset(r$review_data, subject_id == r$subject_id & item_group == form),
+ by = "id"
+ ) |>
+ dplyr::anti_join(
+ subset(r$review_data, subject_id == r$subject_id & item_group == form),
+ by = c("id", "reviewed")
+ ) |>
+ dplyr::arrange(id)
})
output[["common_form_table"]] <- DT::renderDT({
@@ -153,16 +221,31 @@ mod_common_forms_server <- function(
dplyr::select(-dplyr::starts_with("SAE"))
}
if(!input$show_all_data) df$subject_id <- NULL
-
+
# determine DT dom / exts / opts
DT <- dt_config(df,
table_name = paste(form, ifelse(input$show_all_data,
"all_patients", r$subject_id), sep = "."))
datatable_custom(
- df, rename_vars = table_names, rownames= FALSE,title = form,
- escape = FALSE, dom = DT$dom, extensions = DT$exts, options = DT$opts)
- })
-
+ df,
+ rename_vars = c("Review Status" = "o_reviewed", table_names),
+ rownames= FALSE,
+ title = form,
+ escape = FALSE,
+ dom = DT$dom,
+ extensions = DT$exts,
+ selection = "none",
+ callback = checkbox_callback,
+ options = append(list(
+ columnDefs = list(list(
+ targets = 0,
+ render = checkbox_render
+ )),
+ createdRow = checkbox_create_callback
+ ),
+ DT$opts))
+ }, server = FALSE)
+
})
}
diff --git a/R/mod_review_forms.R b/R/mod_review_forms.R
index 2cea435..b9c5c14 100644
--- a/R/mod_review_forms.R
+++ b/R/mod_review_forms.R
@@ -20,7 +20,8 @@ mod_review_forms_ui <- function(id){
inputId = ns("form_reviewed"),
label = "Reviewed",
value = FALSE
- ),
+ ) |>
+ shiny::tagAppendAttributes(class = "cs_checkbox", .cssSelector = "input"),
"Mark as reviewed",
placement = "bottom"
),
@@ -44,6 +45,7 @@ mod_review_forms_ui <- function(id){
label = NULL
)
),
+ progress_bar(ns("progress_bar")),
bslib::layout_columns(
col_widths = c(11, 12),
shiny::actionButton(
@@ -113,13 +115,52 @@ mod_review_forms_server <- function(
with(r$review_data, r$review_data[
subject_id == r$subject_id & item_group == active_form(),
])
- })
+ })
+
+ review_indeterminate <- reactiveVal()
+
+ observeEvent(review_indeterminate(), {
+ shinyjs::runjs(sprintf("$('#%s').prop('indeterminate', %s)", ns("form_reviewed"), tolower(review_indeterminate())))
+ })
+
+ observe({
+ req(session$userData$review_records[[active_form()]])
+ review_status <-
+ review_data_active()[,c("id", "reviewed")] |>
+ dplyr::rows_update(session$userData$review_records[[active_form()]][,c("id", "reviewed")], by = "id") |>
+ dplyr::distinct(reviewed) |>
+ dplyr::pull()
+
+ shinyjs::runjs(sprintf("$('#%s').prop('checked', %s)", ns("form_reviewed"), tolower(identical(review_status, "Yes"))))
+ review_indeterminate(length(review_status) > 1)
+ }) |>
+ bindEvent(active_form(), session$userData$review_records[[active_form()]])
+
+ observeEvent(r$subject_id, {
+ session$userData$update_checkboxes[[active_form()]] <- NULL
+ session$userData$review_records[[active_form()]] <- data.frame(id = integer(), reviewed = character())
+ })
+
+ observeEvent(input$form_reviewed, {
+ session$userData$update_checkboxes[[active_form()]] <- input$form_reviewed
+
+ session$userData$review_records[[active_form()]] <-
+ review_data_active() |>
+ dplyr::mutate(reviewed = ifelse(input$form_reviewed, "Yes", "No")) |>
+ dplyr::select(id, reviewed) |>
+ dplyr::anti_join(
+ subset(r$review_data, item_group == active_form()),
+ by = c("id", "reviewed")
+ ) |>
+ dplyr::arrange(id)
+ }, ignoreInit = TRUE)
observeEvent(c(active_form(), r$subject_id), {
cat("Update confirm review button\n\n\n")
req(r$review_data)
golem::cat_dev("review_data_active:\n")
golem::print_dev(review_data_active())
+ review_indeterminate(FALSE)
if(nrow(review_data_active()) == 0){
cat("No review data found for Subject id: ", r$subject_id,
" and group: ", active_form(), "\n")
@@ -131,16 +172,14 @@ mod_review_forms_server <- function(
# it will give a warning. This would be rare since it would mean a datapoint with the same edit date-time was reviewed but another one was not.
# probably better to use defensive coding here to ensure the app does not crash in that case. However we need to define which review status we need to select
# in this case get the reviewed = "No"
- review_status <- with(review_data_active(), reviewed[edit_date_time == max(as.POSIXct(edit_date_time))]) |> unique()
- review_comment <- with(review_data_active(), comment[edit_date_time == max(as.POSIXct(edit_date_time))]) |> unique()
- if(length(review_status) != 1) warning("multiple variables in review_status, namely: ",
- review_status, "Verify data.")
+ review_status <- unique(review_data_active()[["reviewed"]])
+ review_comment <- with(review_data_active(), comment[edit_date_time == max(as.POSIXct(edit_date_time))]) |> unique() |> paste(collapse = "; ")
+ if(length(review_status) != 1)
+ review_indeterminate(TRUE)
}
- updateCheckboxInput(
- inputId = "form_reviewed",
- value = identical(review_status, "Yes")
- )
+ shinyjs::runjs(sprintf("$('#%s').prop('checked', %s)", ns("form_reviewed"), tolower(identical(review_status, "Yes"))))
+ shinyjs::runjs(sprintf("$('#%s').prop('indeterminate', %s)", ns("form_reviewed"), tolower(review_indeterminate())))
shinyWidgets::updatePrettySwitch(
session = session,
@@ -180,15 +219,12 @@ mod_review_forms_server <- function(
enable_save_review <- reactive({
req(
- review_data_active(),
- is.logical(input$form_reviewed),
+ active_form(),
+ session$userData$review_records[[active_form()]],
is.logical(enable_any_review())
)
if(!enable_any_review()) return(FALSE)
- any(c(
- unique(with(review_data_active(), reviewed[edit_date_time == max(as.POSIXct(edit_date_time))])) == "No" & input$form_reviewed,
- unique(with(review_data_active(), reviewed[edit_date_time == max(as.POSIXct(edit_date_time))])) == "Yes" & !input$form_reviewed
- ))
+ nrow(session$userData$review_records[[active_form()]]) != 0
})
observeEvent(c(enable_any_review(), enable_save_review()), {
@@ -198,7 +234,7 @@ mod_review_forms_server <- function(
shinyjs::enable("save_review")
shinyjs::enable("add_comment")
shinyjs::enable("review_comment")
- } else{
+ } else {
shinyjs::disable("save_review")
shinyjs::disable("add_comment")
shinyjs::disable("review_comment")
@@ -221,23 +257,17 @@ mod_review_forms_server <- function(
review_save_error <- reactiveVal(FALSE)
observeEvent(input$save_review, {
- req(is.logical(input$form_reviewed), review_data_active())
+ req(review_data_active())
req(enable_save_review())
review_save_error(FALSE)
- golem::cat_dev("Save review status reviewed:", input$form_reviewed, "\n")
+ # golem::cat_dev("Save review status reviewed:", input$form_reviewed, "\n")
- old_review_status <- if (!input$form_reviewed) "Yes" else "No"
- review_records <- review_data_active()[
- review_data_active()$reviewed == old_review_status,
- "id",
- drop = FALSE
- ] |>
+ review_records <- session$userData$review_records[[active_form()]][c("id", "reviewed")] |>
dplyr::mutate(
- reviewed = if(input$form_reviewed) "Yes" else "No",
comment = ifelse(is.null(input$review_comment), "", input$review_comment),
reviewer = paste0(r$user_name, " (", r$user_role, ")"),
timestamp = time_stamp(),
- status = if(input$form_reviewed) "old" else "new"
+ status = ifelse(reviewed == "Yes", "old", "new")
)
golem::cat_dev("review records to add:\n")
@@ -265,6 +295,9 @@ mod_review_forms_server <- function(
names(review_records_db)
]
+ session$userData$update_checkboxes[[active_form()]] <- NULL
+ session$userData$review_records[[active_form()]] <- data.frame(id = integer(), reviewed = character())
+
review_save_error(any(
!isTRUE(all.equal(review_records_db, review_records, check.attributes = FALSE)),
!isTRUE(all.equal(updated_records_memory, review_records_db, check.attributes = FALSE))
@@ -287,6 +320,21 @@ mod_review_forms_server <- function(
showNotification("Input saved successfully", duration = 1, type = "message")
})
+ output[["progress_bar"]] <- render_progress_bar({
+ req(
+ review_data_active(),
+ active_form(),
+ session$userData$review_records[[active_form()]]
+ )
+
+ list(
+ completed = sum(review_data_active()$reviewed == "Yes"),
+ unmarking = sum(session$userData$review_records[[active_form()]]$reviewed == "No"),
+ marking = sum(session$userData$review_records[[active_form()]]$reviewed == "Yes"),
+ total = nrow(review_data_active())
+ )
+ })
+
output[["review_header"]] <- renderText({active_form()})
output[["save_review_error"]] <- renderPrint({
@@ -307,7 +355,7 @@ mod_review_forms_server <- function(
"No user name found. Cannot save review"
))
validate(need(
- !unique(with(review_data_active(), reviewed[edit_date_time == max(as.POSIXct(edit_date_time))])) == "Yes",
+ any(review_data_active()[["reviewed"]] != "Yes"),
"Form already reviewed"
))
validate(need(input$form_reviewed, "Requires review"))
diff --git a/R/mod_study_forms.R b/R/mod_study_forms.R
index 8db7185..ad34d43 100644
--- a/R/mod_study_forms.R
+++ b/R/mod_study_forms.R
@@ -163,6 +163,7 @@ mod_study_forms_server <- function(
})
table_data_active <- reactive({
+ req(!is.null(input$show_all))
validate(need(
r$filtered_data[[form]],
paste0("Warning: no data found in database for the form '", form, "'")
@@ -193,6 +194,38 @@ mod_study_forms_server <- function(
lapply(add_missing_columns(item_info, cols)[1, cols], isTRUE)
})
+ observeEvent(table_data_active(), {
+ session$userData$update_checkboxes[[form]] <- NULL
+ session$userData$review_records[[form]] <- data.frame(id = integer(), reviewed = character(), row_index = character())
+ })
+
+ observeEvent(input$table_review_selection, {
+ session$userData$update_checkboxes[[form]] <- NULL
+
+ session$userData$review_records[[form]] <-
+ dplyr::rows_upsert(
+ session$userData$review_records[[form]],
+ input$table_review_selection,
+ by = "id"
+ ) |>
+ dplyr::filter(!is.na(reviewed)) |>
+ dplyr::semi_join(
+ subset(r$review_data, subject_id == r$subject_id & item_group == form),
+ by = "id"
+ ) |>
+ dplyr::anti_join(
+ subset(r$review_data, subject_id == r$subject_id & item_group == form),
+ by = c("id", "reviewed")
+ ) |>
+ dplyr::arrange(id)
+ })
+
+ observeEvent(session$userData$update_checkboxes[[form]], {
+ checked <- session$userData$update_checkboxes[[form]]
+
+ update_cbs(ns("table"), checked)
+ })
+
############################### Outputs: ###################################
dynamic_figure <- reactive({
req(nrow(fig_data()) > 0, scaling_data())
@@ -223,14 +256,28 @@ mod_study_forms_server <- function(
output[["table"]] <- DT::renderDT({
req(table_data_active())
- # determine DT dom / exts / opts
- DT <- dt_config(table_data_active(),
- table_name = paste(form, ifelse(input$show_all,
- "all_patients", r$subject_id), sep = "."))
- datatable_custom(table_data_active(), table_names, escape = FALSE,
- dom = DT$dom, extensions = DT$exts, options = DT$opts)
- })
-
+ DT <- dt_config(table_data_active(),
+ table_name = paste(form, ifelse(input$show_all,
+ "all_patients", r$subject_id), sep = "."))
+ datatable_custom(
+ table_data_active(),
+ rename_vars = c("Review Status" = "o_reviewed", table_names),
+ rownames= FALSE,
+ escape = FALSE,
+ dom = DT$dom,
+ extensions = DT$exts,
+ selection = "none",
+ callback = checkbox_callback,
+ options = append(list(
+ columnDefs = list(list(
+ targets = 0,
+ render = checkbox_render
+ )),
+ createdRow = checkbox_create_callback
+ ),
+ DT$opts))
+ }, server = FALSE)
+
if(form %in% c("Vital signs", "Vitals adjusted")){
shiny::exportTestValues(
table_data = table_data_active(),
diff --git a/R/shiny.R b/R/shiny.R
new file mode 100644
index 0000000..4a697a9
--- /dev/null
+++ b/R/shiny.R
@@ -0,0 +1,72 @@
+shiny::registerInputHandler('CS.reviewInfo', function(val, ...) {
+ with(val, data.frame(
+ id = unlist(ids),
+ reviewed = ifelse(isTRUE(review), "Yes", ifelse(isFALSE(review), "No", NA_character_))
+ ))
+}, TRUE)
+
+checkbox_callback <- DT::JS(
+ "table.on('click', 'input[type=\"checkbox\"]', function(){",
+ "var tblId = $(this).closest('.datatables').attr('id');",
+ "var cell = table.cell($(this).closest('td'));",
+ "var rowIdx = table.row($(this).closest('tr')).index();",
+ "var ids = cell.data().ids;",
+ "var review = $(this).is(':indeterminate') ? null : $(this).is(':checked');",
+ "cell.data().updated = review;",
+ "var info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};",
+ "Shiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);",
+ "})"
+)
+
+checkbox_render <- DT::JS(
+ "function(data, type, row, meta) {",
+ "var reviewed = data.reviewed;",
+ "var updated = data.updated;",
+ "return ``;",
+ "}"
+)
+
+checkbox_create_callback <- DT::JS(
+ "function(row, data, dataIndex) {",
+ "if (data[0].reviewed == null) {",
+ "let cb = row.cells[0].getElementsByTagName('input')[0]",
+ "cb.indeterminate = true;",
+ "}",
+ "}"
+)
+
+update_cbs <- function(tblId, checked) {
+ "$(':checkbox:not(.indeterminate)', $('#%s .table').DataTable().rows().nodes()).prop('checked', %s)" |>
+ sprintf(tblId, tolower(checked)) |>
+ shinyjs::runjs()
+ "$(':checkbox.indeterminate', $('#%s .table').DataTable().rows().nodes()).prop('checked', %s).prop('indeterminate', false).prop('readOnly', %s)" |>
+ sprintf(tblId, tolower(checked), tolower(!checked)) |>
+ shinyjs::runjs()
+}
+
+progress_bar <- function(outputId) {
+ div(
+ id = outputId,
+ class = "cs-progress-container",
+ div(
+ class = "cs-progress-bar",
+ div(class = c("cs-progress", "completed")),
+ div(class = c("cs-progress", "unmarking")),
+ div(class = c("cs-progress", "marking"))
+ ),
+ div(
+ class = "cs-completed"
+ )
+ )
+}
+
+render_progress_bar <- function(expr, env = parent.frame(), quoted = FALSE) {
+ func <- exprToFunction(expr, env, quoted)
+
+ function(){
+ func()
+ }
+}
diff --git a/inst/app/www/custom.css b/inst/app/www/custom.css
index b69e7f6..7ee0afc 100644
--- a/inst/app/www/custom.css
+++ b/inst/app/www/custom.css
@@ -1,3 +1,8 @@
+:root {
+ --cs-completed: #91C483;
+ --cs-unmarking: #FF6464;
+ --cs-marking: #97b0f8;
+}
.dataTables_wrapper .dt-buttons {
padding-left: 0.75em;
@@ -66,3 +71,57 @@ div.datatables div.header {
font-weight: bold;
}
+tr:has(td input[type="checkbox"].checked:not(:checked))>td,
+ tr:has(td input[type="checkbox"].indeterminate:not(:indeterminate):not(:checked))>td {
+ background-color: var(--cs-unmarking, #FF6464);
+ border-color: var(--cs-unmarking, #FF6464);
+ color: #000000;
+}
+
+tr:has(td input[type="checkbox"].unchecked:checked)>td,
+ tr:has(td input[type="checkbox"].indeterminate:not(:indeterminate):checked)>td {
+ background-color: var(--cs-marking, #97b0f8);
+ border-color: var(--cs-marking, #97b0f8);
+ color: #000000;
+}
+
+div.cs-progress-container {
+ display: flex;
+ margin-bottom: 1rem;
+ align-items: center;
+ gap: 5px;
+}
+
+div.cs-progress-container>.cs-progress-bar {
+ width: 100%;
+ background-color: #eeeeee;
+ border-color: #b3b3b3;
+ border-style: solid;
+ border-width: thin;
+ color: #ffffff;
+ border-radius: 10px;
+ height: 10px;
+ overflow: hidden;
+ display: flex;
+}
+
+div.cs-progress-container>.cs-completed {
+ cursor: default;
+}
+
+div.cs-progress-container>.cs-progress-bar>.cs-progress {
+ height: 100%;
+ transition: width 1s;
+}
+
+div.cs-progress-container>.cs-progress-bar>.cs-progress.completed {
+ background-color: var(--cs-completed, #91C483);
+}
+
+div.cs-progress-container>.cs-progress-bar>.cs-progress.unmarking {
+ background-color: var(--cs-unmarking, #FF6464);
+}
+
+div.cs-progress-container>.cs-progress-bar>.cs-progress.marking {
+ background-color: var(--cs-marking, #97b0f8);
+}
diff --git a/inst/app/www/custom.js b/inst/app/www/custom.js
new file mode 100644
index 0000000..c2a61be
--- /dev/null
+++ b/inst/app/www/custom.js
@@ -0,0 +1,61 @@
+function ts(cb) {
+ if (cb.readOnly) {
+ cb.indeterminate=true;
+ cb.readOnly=cb.checked=false;
+ } else if (!cb.checked) {
+ cb.readOnly=true;
+ cb.indeterminate=false;
+ }
+}
+
+$(document).ready(function() {
+
+ /* Define custom Shiny input binding for overall review checkbox.
+ This is needed to assign an event priority to the checkbox.*/
+ var customCheckbox = new Shiny.InputBinding();
+
+ $.extend(customCheckbox, {
+ find: function(scope) {
+ return $(scope).find("input[type='checkbox'].cs_checkbox");
+ },
+ getValue: function(el) {
+ return el.checked;
+ },
+ setValue: function(el, value) {
+ el.checked = value;
+ },
+ subscribe: function(el, callback) {
+ $(el).on("change.checkboxInputBinding", function() {
+ Shiny.onInputChange($(this).attr('id'), this.checked, {priority: 'event'});
+ });
+ },
+ unsubscribe: function(el) {
+ $(el).off(".checkboxInputBinding");
+ }
+ });
+
+ Shiny.inputBindings.register(customCheckbox);
+
+ /* Define custom Shiny output binding for review progress bar.
+ It expects 4 values: completed, unmarking, marking, and total.*/
+ var customProgressBar = new Shiny.OutputBinding();
+
+ $.extend(customProgressBar, {
+ find: function(scope) {
+ return $(scope).find("div.cs-progress-container");
+ },
+ renderValue: function(el, data) {
+ let cmp_pct = (data.completed-data.unmarking)/data.total*100;
+ let um_pct = data.unmarking/data.total*100;
+ let m_pct = data.marking/data.total*100;
+ let true_cmp_pct = data.completed/data.total*100;
+ $('#' + el.id + " .cs-progress.completed").width(cmp_pct.toFixed(2) + "%")
+ $('#' + el.id + " .cs-progress.unmarking").width(um_pct.toFixed(2) + "%")
+ $('#' + el.id + " .cs-progress.marking").width(m_pct.toFixed(2) + "%")
+ $('#' + el.id + " .cs-completed").html(true_cmp_pct.toFixed(1) + "%")
+ }
+ });
+
+ Shiny.outputBindings.register(customProgressBar)
+
+});
diff --git a/tests/testthat/_snaps/app_feature_01/app-feature-1-002.json b/tests/testthat/_snaps/app_feature_01/app-feature-1-002.json
index e24a32e..5861e60 100644
--- a/tests/testthat/_snaps/app_feature_01/app-feature-1-002.json
+++ b/tests/testthat/_snaps/app_feature_01/app-feature-1-002.json
@@ -9,7 +9,51 @@
"Scroller",
"ColReorder"
],
- "container": "
\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -18,88 +62,94 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
- "dom": "f<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "Start date",
- "targets": 3
+ "targets": 4
},
{
"name": "End date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Category",
- "targets": 9
+ "targets": 10
},
{
"name": "Awareness date",
- "targets": 10
+ "targets": 11
},
{
"name": "Date of death",
- "targets": 11
+ "targets": 12
},
{
"name": "Death reason",
- "targets": 12
+ "targets": 13
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
+ "dom": "f<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -243,7 +293,151 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+ {
+ "reviewed": false,
+ "ids": [
+ 2195,
+ 2196,
+ 2197,
+ 2198,
+ 2199,
+ 2200,
+ 2201,
+ 2202,
+ 2235
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2203,
+ 2204,
+ 2205,
+ 2206,
+ 2207,
+ 2208,
+ 2209,
+ 2210,
+ 2236
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2211,
+ 2212,
+ 2213,
+ 2214,
+ 2215,
+ 2216,
+ 2217,
+ 2218,
+ 2237
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2219,
+ 2220,
+ 2221,
+ 2222,
+ 2223,
+ 2224,
+ 2225,
+ 2226,
+ 2238
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2227,
+ 2228,
+ 2229,
+ 2230,
+ 2231,
+ 2232,
+ 2233,
+ 2234,
+ 2239
+ ]
+ }
+ ],
+ [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ],
+ [
+ "Hypotension*<\/b>",
+ "Atrial Fibrillation*<\/b>",
+ "Tachycardia*<\/b>",
+ "Urinary Tract Infection*<\/b>",
+ "Atrial Fibrillation*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>"
+ ],
+ [
+ "2023-07-07*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-08-16*<\/b>"
+ ],
+ [
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "Grade 1*<\/b>",
+ "Grade 2*<\/b>",
+ "Grade 2*<\/b>",
+ "Grade 1*<\/b>",
+ "Grade 2*<\/b>"
+ ],
+ [
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>"
+ ],
+ [
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>"
+ ],
+ [
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>"
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -252,83 +446,89 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "buttons": [
- {
- "extend": "excel",
- "text": "<\/i>",
- "filename": "clinsight.Adverse-events.BEL_04_772"
- }
- ],
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Adverse events\")\n}",
- "dom": "Bf<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "start date",
- "targets": 3
+ "targets": 4
},
{
"name": "end date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Serious Adverse Event",
- "targets": 9
+ "targets": 10
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "buttons": [
+ {
+ "extend": "excel",
+ "text": "<\/i>",
+ "filename": "clinsight.Adverse-events.BEL_04_772"
+ }
+ ],
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Adverse events\")\n}",
+ "dom": "Bf<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -815,6 +1015,12 @@
]
},
"main_sidebar_1-navigate_forms_1-form_name": "Adverse events<\/b><\/center>",
+ "main_sidebar_1-review_forms_1-progress_bar": {
+ "completed": 0,
+ "unmarking": 0,
+ "marking": 0,
+ "total": 45
+ },
"main_sidebar_1-review_forms_1-save_review_error": {
"message": "Requires review",
"call": "NULL",
diff --git a/tests/testthat/_snaps/app_feature_01/app-feature-1-003.json b/tests/testthat/_snaps/app_feature_01/app-feature-1-003.json
index e0fd19b..3f12732 100644
--- a/tests/testthat/_snaps/app_feature_01/app-feature-1-003.json
+++ b/tests/testthat/_snaps/app_feature_01/app-feature-1-003.json
@@ -9,7 +9,51 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -18,88 +62,94 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
- "dom": "f<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "Start date",
- "targets": 3
+ "targets": 4
},
{
"name": "End date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Category",
- "targets": 9
+ "targets": 10
},
{
"name": "Awareness date",
- "targets": 10
+ "targets": 11
},
{
"name": "Date of death",
- "targets": 11
+ "targets": 12
},
{
"name": "Death reason",
- "targets": 12
+ "targets": 13
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
+ "dom": "f<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -243,7 +293,151 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+ {
+ "reviewed": false,
+ "ids": [
+ 2195,
+ 2196,
+ 2197,
+ 2198,
+ 2199,
+ 2200,
+ 2201,
+ 2202,
+ 2235
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2203,
+ 2204,
+ 2205,
+ 2206,
+ 2207,
+ 2208,
+ 2209,
+ 2210,
+ 2236
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2211,
+ 2212,
+ 2213,
+ 2214,
+ 2215,
+ 2216,
+ 2217,
+ 2218,
+ 2237
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2219,
+ 2220,
+ 2221,
+ 2222,
+ 2223,
+ 2224,
+ 2225,
+ 2226,
+ 2238
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2227,
+ 2228,
+ 2229,
+ 2230,
+ 2231,
+ 2232,
+ 2233,
+ 2234,
+ 2239
+ ]
+ }
+ ],
+ [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ],
+ [
+ "Hypotension*<\/b>",
+ "Atrial Fibrillation*<\/b>",
+ "Tachycardia*<\/b>",
+ "Urinary Tract Infection*<\/b>",
+ "Atrial Fibrillation*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>"
+ ],
+ [
+ "2023-07-07*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-08-16*<\/b>"
+ ],
+ [
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "Grade 1*<\/b>",
+ "Grade 2*<\/b>",
+ "Grade 2*<\/b>",
+ "Grade 1*<\/b>",
+ "Grade 2*<\/b>"
+ ],
+ [
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>"
+ ],
+ [
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>"
+ ],
+ [
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>"
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -252,83 +446,89 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "buttons": [
- {
- "extend": "excel",
- "text": "<\/i>",
- "filename": "clinsight.Adverse-events.BEL_04_772"
- }
- ],
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Adverse events\")\n}",
- "dom": "Bf<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "start date",
- "targets": 3
+ "targets": 4
},
{
"name": "end date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Serious Adverse Event",
- "targets": 9
+ "targets": 10
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "buttons": [
+ {
+ "extend": "excel",
+ "text": "<\/i>",
+ "filename": "clinsight.Adverse-events.BEL_04_772"
+ }
+ ],
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Adverse events\")\n}",
+ "dom": "Bf<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -815,6 +1015,12 @@
]
},
"main_sidebar_1-navigate_forms_1-form_name": "Vital signs<\/b><\/center>",
+ "main_sidebar_1-review_forms_1-progress_bar": {
+ "completed": 0,
+ "unmarking": 0,
+ "marking": 0,
+ "total": 47
+ },
"main_sidebar_1-review_forms_1-save_review_error": {
"message": "Requires review",
"call": "NULL",
diff --git a/tests/testthat/_snaps/app_feature_01/app-feature-1-004.json b/tests/testthat/_snaps/app_feature_01/app-feature-1-004.json
index f1c108f..c6551b7 100644
--- a/tests/testthat/_snaps/app_feature_01/app-feature-1-004.json
+++ b/tests/testthat/_snaps/app_feature_01/app-feature-1-004.json
@@ -9,7 +9,51 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -18,88 +62,94 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
- "dom": "f<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "Start date",
- "targets": 3
+ "targets": 4
},
{
"name": "End date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Category",
- "targets": 9
+ "targets": 10
},
{
"name": "Awareness date",
- "targets": 10
+ "targets": 11
},
{
"name": "Date of death",
- "targets": 11
+ "targets": 12
},
{
"name": "Death reason",
- "targets": 12
+ "targets": 13
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
+ "dom": "f<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -243,7 +293,151 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+ {
+ "reviewed": false,
+ "ids": [
+ 2195,
+ 2196,
+ 2197,
+ 2198,
+ 2199,
+ 2200,
+ 2201,
+ 2202,
+ 2235
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2203,
+ 2204,
+ 2205,
+ 2206,
+ 2207,
+ 2208,
+ 2209,
+ 2210,
+ 2236
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2211,
+ 2212,
+ 2213,
+ 2214,
+ 2215,
+ 2216,
+ 2217,
+ 2218,
+ 2237
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2219,
+ 2220,
+ 2221,
+ 2222,
+ 2223,
+ 2224,
+ 2225,
+ 2226,
+ 2238
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2227,
+ 2228,
+ 2229,
+ 2230,
+ 2231,
+ 2232,
+ 2233,
+ 2234,
+ 2239
+ ]
+ }
+ ],
+ [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ],
+ [
+ "Hypotension*<\/b>",
+ "Atrial Fibrillation*<\/b>",
+ "Tachycardia*<\/b>",
+ "Urinary Tract Infection*<\/b>",
+ "Atrial Fibrillation*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>"
+ ],
+ [
+ "2023-07-07*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-07-05*<\/b>",
+ "2023-08-16*<\/b>"
+ ],
+ [
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "Grade 1*<\/b>",
+ "Grade 2*<\/b>",
+ "Grade 2*<\/b>",
+ "Grade 1*<\/b>",
+ "Grade 2*<\/b>"
+ ],
+ [
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>",
+ "Not related*<\/b>"
+ ],
+ [
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>",
+ "Not Applicable*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>",
+ "Concomitant therapy/medication*<\/b>"
+ ],
+ [
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>",
+ "No*<\/b>"
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -252,83 +446,89 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "buttons": [
- {
- "extend": "excel",
- "text": "<\/i>",
- "filename": "clinsight.Adverse-events.BEL_04_772"
- }
- ],
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Adverse events\")\n}",
- "dom": "Bf<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "start date",
- "targets": 3
+ "targets": 4
},
{
"name": "end date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Serious Adverse Event",
- "targets": 9
+ "targets": 10
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "buttons": [
+ {
+ "extend": "excel",
+ "text": "<\/i>",
+ "filename": "clinsight.Adverse-events.BEL_04_772"
+ }
+ ],
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Adverse events\")\n}",
+ "dom": "Bf<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -815,6 +1015,12 @@
]
},
"main_sidebar_1-navigate_forms_1-form_name": "Vital signs<\/b><\/center>",
+ "main_sidebar_1-review_forms_1-progress_bar": {
+ "completed": 0,
+ "unmarking": 0,
+ "marking": 0,
+ "total": 47
+ },
"main_sidebar_1-review_forms_1-save_review_error": {
"message": "Requires review",
"call": "NULL",
@@ -8313,7 +8519,174 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n <\/th>\n | Event<\/th>\n | Systolic blood pressure<\/th>\n | Diastolic blood pressure<\/th>\n | Pulse<\/th>\n | Resp<\/th>\n | Temperature<\/th>\n | Weight change since screening<\/th>\n | BMI<\/th>\n | Weight<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+ {
+ "reviewed": false,
+ "ids": [
+ 1217,
+ 1218,
+ 1219,
+ 1220,
+ 1221,
+ 1222,
+ 1223,
+ 1441
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 1224,
+ 1225,
+ 1226,
+ 1227,
+ 1228,
+ 1229,
+ 1230,
+ 1442
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 1231,
+ 1232,
+ 1233,
+ 1234,
+ 1235
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 1236,
+ 1237,
+ 1238,
+ 1239,
+ 1240,
+ 1241,
+ 1242,
+ 1443
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 1243,
+ 1244,
+ 1245,
+ 1246,
+ 1247
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 1248,
+ 1249,
+ 1250,
+ 1251,
+ 1252,
+ 1253,
+ 1254,
+ 1444
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 1255,
+ 1256,
+ 1257,
+ 1258,
+ 1259
+ ]
+ }
+ ],
+ [
+ "Screening",
+ "Visit 1",
+ "Visit 2",
+ "Visit 3",
+ "Visit 4",
+ "Visit 5",
+ "Visit 6"
+ ],
+ [
+ "131*<\/b> mmHg",
+ "132*<\/b> mmHg",
+ "129*<\/b> mmHg",
+ "117*<\/b> mmHg",
+ "128*<\/b> mmHg",
+ "137*<\/b> mmHg",
+ "131*<\/b> mmHg"
+ ],
+ [
+ "68*<\/b> mmHg",
+ "79*<\/b> mmHg",
+ "73*<\/b> mmHg",
+ "77*<\/b> mmHg",
+ "62*<\/b> mmHg",
+ "76*<\/b> mmHg",
+ "61*<\/b> mmHg"
+ ],
+ [
+ "76*<\/b> beats/min",
+ "63*<\/b> beats/min",
+ "73*<\/b> beats/min",
+ "70*<\/b> beats/min",
+ "70*<\/b> beats/min",
+ "75*<\/b> beats/min",
+ "72*<\/b> beats/min"
+ ],
+ [
+ "17*<\/b> breaths/min",
+ "16*<\/b> breaths/min",
+ "16*<\/b> breaths/min",
+ "18*<\/b> breaths/min",
+ "18*<\/b> breaths/min",
+ "18*<\/b> breaths/min",
+ "19*<\/b> breaths/min"
+ ],
+ [
+ "37*<\/b> °C",
+ "36.7*<\/b> °C",
+ "36.9*<\/b> °C",
+ "36*<\/b> °C",
+ "36.6*<\/b> °C",
+ "36.1*<\/b> °C",
+ "36.9*<\/b> °C"
+ ],
+ [
+ "0.01*<\/b> %",
+ "2*<\/b> %",
+ null,
+ "0.01*<\/b> %",
+ null,
+ "0.01*<\/b> %",
+ null
+ ],
+ [
+ "17.44*<\/b> kg/m2",
+ "17.1*<\/b> kg/m2",
+ null,
+ "17.1*<\/b> kg/m2",
+ null,
+ "24.13*<\/b> kg/m2",
+ null
+ ],
+ [
+ "61*<\/b> kg",
+ "62*<\/b> kg",
+ null,
+ "50*<\/b> kg",
+ null,
+ "50*<\/b> kg",
+ null
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | Event<\/th>\n | Systolic blood pressure<\/th>\n | Diastolic blood pressure<\/th>\n | Pulse<\/th>\n | Resp<\/th>\n | Temperature<\/th>\n | Weight change since screening<\/th>\n | BMI<\/th>\n | Weight<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -8322,22 +8695,13 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "buttons": [
- {
- "extend": "excel",
- "text": "<\/i>",
- "filename": "clinsight.Vital-signs.BEL_04_772"
- }
- ],
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"\")\n}",
- "dom": "Bf<\"header h5\">ti",
"columnDefs": [
{
- "orderable": false,
- "targets": 0
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
},
{
- "name": " ",
+ "name": "Review Status",
"targets": 0
},
{
@@ -8377,28 +8741,35 @@
"targets": 9
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "buttons": [
+ {
+ "extend": "excel",
+ "text": "<\/i>",
+ "filename": "clinsight.Vital-signs.BEL_04_772"
+ }
+ ],
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"\")\n}",
+ "dom": "Bf<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
diff --git a/tests/testthat/_snaps/app_feature_01/app-feature-1-005.json b/tests/testthat/_snaps/app_feature_01/app-feature-1-005.json
index a97090f..4ad1f05 100644
--- a/tests/testthat/_snaps/app_feature_01/app-feature-1-005.json
+++ b/tests/testthat/_snaps/app_feature_01/app-feature-1-005.json
@@ -9,7 +9,51 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ]
+ ],
+ "container": "\n \n \n Review Status<\/th>\n | N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | Start date<\/th>\n | End date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Category<\/th>\n | Awareness date<\/th>\n | Date of death<\/th>\n | Death reason<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
"options": {
"scrollY": 400,
"scrollX": true,
@@ -18,88 +62,94 @@
"scrollResize": true,
"scrollCollapse": true,
"colReorder": true,
- "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
- "dom": "f<\"header h5\">ti",
"columnDefs": [
+ {
+ "targets": 0,
+ "render": "function(data, type, row, meta) {\nvar reviewed = data.reviewed;\nvar updated = data.updated;\nreturn ``;\n}"
+ },
{
"className": "dt-right",
+ "targets": 1
+ },
+ {
+ "name": "Review Status",
"targets": 0
},
{
"name": "N",
- "targets": 0
+ "targets": 1
},
{
"name": "Name",
- "targets": 1
+ "targets": 2
},
{
"name": "AESI",
- "targets": 2
+ "targets": 3
},
{
"name": "Start date",
- "targets": 3
+ "targets": 4
},
{
"name": "End date",
- "targets": 4
+ "targets": 5
},
{
"name": "CTCAE severity",
- "targets": 5
+ "targets": 6
},
{
"name": "Treatment related",
- "targets": 6
+ "targets": 7
},
{
"name": "Treatment action",
- "targets": 7
+ "targets": 8
},
{
"name": "Other action",
- "targets": 8
+ "targets": 9
},
{
"name": "Category",
- "targets": 9
+ "targets": 10
},
{
"name": "Awareness date",
- "targets": 10
+ "targets": 11
},
{
"name": "Date of death",
- "targets": 11
+ "targets": 12
},
{
"name": "Death reason",
- "targets": 12
+ "targets": 13
}
],
+ "createdRow": "function(row, data, dataIndex) {\nif (data[0].reviewed == null) {\nlet cb = row.cells[0].getElementsByTagName('input')[0]\ncb.indeterminate = true;\n}\n}",
+ "initComplete": "function() {\n$(this.api().table().container()).find('.header').html(\"Serious Adverse Events\")\n}",
+ "dom": "f<\"header h5\">ti",
"order": [
],
"autoWidth": false,
- "orderClasses": false,
- "ajax": {
- "type": "POST",
- "data": "function(d) {\nd.search.caseInsensitive = true;\nd.search.smart = true;\nd.escape = false;\nvar encodeAmp = function(x) { x.value = x.value.replace(/&/g, \"%26\"); }\nencodeAmp(d.search);\n$.each(d.columns, function(i, v) {encodeAmp(v.search);});\n}"
- },
- "serverSide": true,
- "processing": true
+ "orderClasses": false
},
+ "callback": "function(table) {\ntable.on('click', 'input[type=\"checkbox\"]', function(){\nvar tblId = $(this).closest('.datatables').attr('id');\nvar cell = table.cell($(this).closest('td'));\nvar rowIdx = table.row($(this).closest('tr')).index();\nvar ids = cell.data().ids;\nvar review = $(this).is(':indeterminate') ? null : $(this).is(':checked');\ncell.data().updated = review;\nvar info = {review: review, ids: ids, row: tblId + '_row_' + rowIdx};\nShiny.setInputValue(tblId + '_review_selection:CS.reviewInfo', info);\n})\n}",
"selection": {
- "mode": "single",
+ "mode": "none",
"selected": null,
"target": "row",
"selectable": null
}
},
"evals": [
+ "options.columnDefs.0.render",
+ "options.createdRow",
"options.initComplete",
- "options.ajax.data"
+ "callback"
],
"jsHooks": [
@@ -243,7 +293,151 @@
"Scroller",
"ColReorder"
],
- "container": "\n \n \n N<\/th>\n | Name<\/th>\n | AESI<\/th>\n | start date<\/th>\n | end date<\/th>\n | CTCAE severity<\/th>\n | Treatment related<\/th>\n | Treatment action<\/th>\n | Other action<\/th>\n | Serious Adverse Event<\/th>\n <\/tr>\n <\/thead>\n<\/table>",
+ "data": [
+ [
+ {
+ "reviewed": false,
+ "ids": [
+ 2195,
+ 2196,
+ 2197,
+ 2198,
+ 2199,
+ 2200,
+ 2201,
+ 2202,
+ 2235
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2203,
+ 2204,
+ 2205,
+ 2206,
+ 2207,
+ 2208,
+ 2209,
+ 2210,
+ 2236
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2211,
+ 2212,
+ 2213,
+ 2214,
+ 2215,
+ 2216,
+ 2217,
+ 2218,
+ 2237
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2219,
+ 2220,
+ 2221,
+ 2222,
+ 2223,
+ 2224,
+ 2225,
+ 2226,
+ 2238
+ ]
+ },
+ {
+ "reviewed": false,
+ "ids": [
+ 2227,
+ 2228,
+ 2229,
+ 2230,
+ 2231,
+ 2232,
+ 2233,
+ 2234,
+ 2239
+ ]
+ }
+ ],
+ [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ],
+ [
+ "Hypotension*<\/b>",
+ "Atrial Fibrillation*<\/b>",
+ "Tachycardia*<\/b>",
+ "Urinary Tract Infection*<\/b>",
+ "Atrial Fibrillation*<\/b>"
+ ],
+ [
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>",
+ "None*<\/b>"
+ ],
+ [
+ " | | | | | | | | | | | | | | | | |