├── .github ├── .gitignore └── workflows │ ├── check-standard.yaml │ └── test-coverage.yaml ├── .gitignore ├── .project ├── README.md ├── editbl.gif ├── editbl ├── DESCRIPTION ├── NAMESPACE ├── R │ ├── demoApp.R │ ├── devApp.R │ ├── eDT.R │ ├── eDT_app.R │ ├── foreignTbl.R │ ├── selectInputDT.R │ ├── shinyInput.R │ ├── tbl.R │ ├── tbl_dbi.R │ ├── tbl_df.R │ ├── tbl_dtplyr_step.R │ └── utils.R ├── README.md ├── inst │ ├── NEWS │ └── extdata │ │ ├── artists.xlsx │ │ └── chinook.sqlite ├── man │ ├── addButtons.Rd │ ├── beginTransaction.Rd │ ├── canXXXRowTemplate.Rd │ ├── castForDisplay.Rd │ ├── castFromTbl.Rd │ ├── castToFactor.Rd │ ├── castToSQLSupportedType.Rd │ ├── castToTbl.Rd │ ├── castToTemplate.Rd │ ├── checkForeignTbls.Rd │ ├── coalesce.Rd │ ├── coerceColumns.Rd │ ├── coerceValue.Rd │ ├── commitTransaction.Rd │ ├── connectDB.Rd │ ├── createButtons.Rd │ ├── createCloneButtonHTML.Rd │ ├── createCloneButtonHTML_shiny.Rd │ ├── createDeleteButtonHTML.Rd │ ├── createDeleteButtonHTML_shiny.Rd │ ├── createEditButtonHTML.Rd │ ├── createEditButtonHTML_shiny.Rd │ ├── customButton.Rd │ ├── demoServer_DB.Rd │ ├── demoServer_custom.Rd │ ├── demoServer_mtcars.Rd │ ├── demoUI_DB.Rd │ ├── demoUI_custom.Rd │ ├── demoUI_mtcars.Rd │ ├── devServer.Rd │ ├── devUI.Rd │ ├── disableDoubleClickButtonCss.Rd │ ├── eDT.Rd │ ├── eDTOutput.Rd │ ├── eDT_app.Rd │ ├── eDT_app_server.Rd │ ├── eDT_app_ui.Rd │ ├── e_rows_insert.Rd │ ├── e_rows_insert.default.Rd │ ├── e_rows_insert.dtplyr_step.Rd │ ├── e_rows_insert.tbl_dbi.Rd │ ├── e_rows_update.Rd │ ├── e_rows_update.data.frame.Rd │ ├── e_rows_update.default.Rd │ ├── e_rows_update.dtplyr_step.Rd │ ├── e_rows_update.tbl_dbi.Rd │ ├── evalCanCloneRow.Rd │ ├── evalCanDeleteRow.Rd │ ├── evalCanEditRow.Rd │ ├── fillDeductedColumns.Rd │ ├── fixInteger64.Rd │ ├── foreignTbl.Rd │ ├── getColumnTypeSums.Rd │ ├── getNonNaturalKeyCols.Rd │ ├── get_db_table_name.Rd │ ├── initData.Rd │ ├── inputServer.Rd │ ├── inputServer.default.Rd │ ├── inputUI.Rd │ ├── inputUI.default.Rd │ ├── joinForeignTbl.Rd │ ├── overwriteDefaults.Rd │ ├── rollbackTransaction.Rd │ ├── rowInsert.Rd │ ├── rowUpdate.Rd │ ├── rows_delete.dtplyr_step.Rd │ ├── runDemoApp.Rd │ ├── runDemoApp_DB.Rd │ ├── runDemoApp_custom.Rd │ ├── runDemoApp_mtcars.Rd │ ├── runDevApp.Rd │ ├── selectInputDT_Server.Rd │ ├── selectInputDT_UI.Rd │ ├── shinyInput.Rd │ ├── standardizeArgument_colnames.Rd │ ├── standardizeArgument_editable.Rd │ └── whereSQL.Rd ├── tests │ ├── testthat.R │ └── testthat │ │ ├── test-demoApp.R │ │ ├── test-devApp.R │ │ ├── test-eDT.R │ │ ├── test-foreignTbl.R │ │ ├── test-selectInputDT.R │ │ ├── test-shinyInput.R │ │ ├── test-tbl.R │ │ ├── test-tbl_dbi.R │ │ ├── test-tbl_df.R │ │ ├── test-tbl_dtplyr_step.R │ │ └── test-utils.R └── vignettes │ ├── howto_relational_db.rmd │ ├── howto_relational_db_dm.rmd │ ├── howto_row_level_access.rmd │ ├── howto_switch_from_DT.rmd │ └── screenshots │ ├── howto_relational_db_1.png │ ├── howto_relational_db_2.png │ ├── howto_relational_db_3.png │ ├── howto_relational_db_dm_1.png │ ├── howto_row_level_access_1.png │ ├── howto_row_level_access_2.png │ ├── howto_switch_from_DT_1.png │ └── howto_switch_from_DT_2.png ├── editbl_logo.drawio ├── editbl_logo.png └── editbl_logo.svg /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/check-standard.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | working-directory: "./editbl" 45 | extra-packages: any::rcmdcheck 46 | needs: check 47 | 48 | - uses: r-lib/actions/check-r-package@v2 49 | with: 50 | working-directory: "./editbl" 51 | upload-snapshots: true 52 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | working-directory: "./editbl" 27 | extra-packages: any::covr 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | covr::codecov( 33 | path = "./editbl", 34 | quiet = FALSE, 35 | clean = FALSE, 36 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 37 | ) 38 | shell: Rscript {0} 39 | 40 | - name: Show testthat output 41 | if: always() 42 | run: | 43 | ## -------------------------------------------------------------------- 44 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 45 | shell: bash 46 | 47 | - name: Upload test results 48 | if: failure() 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: coverage-test-failures 52 | path: ${{ runner.temp }}/package 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /editbl.Rcheck/ 2 | editbl_*.tar.gz 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | editbl 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.statet.r.resourceProjects.RBuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.statet.docmlet.resourceProjects.WikitextBuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.statet.ide.resourceProjects.Statet 21 | org.eclipse.statet.r.resourceProjects.R 22 | org.eclipse.statet.docmlet.resourceProjects.Wikitext 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | editbl/README.md -------------------------------------------------------------------------------- /editbl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl.gif -------------------------------------------------------------------------------- /editbl/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: editbl 2 | Type: Package 3 | Version: 1.3.0 4 | Date: 2025-04-23 5 | Title: 'DT' Extension for CRUD (Create, Read, Update, Delete) Applications in 'shiny' 6 | Authors@R: c(person("Jasper", "Schelfhout", "", "jasper.schelfhout@openanalytics.eu", 7 | role = c("aut", "cre")), 8 | person("Maxim", "Nazarov", "", "maxim.nazarov@openanalytics.eu", 9 | role = c("rev")), 10 | person("Daan", "Seynaeve", "", "daan.seynaeve@openanalytics.eu", 11 | role = c("rev")), 12 | person("Lennart", "Tuijnder", "", "lennart.tuijnder@openanalytics.eu", 13 | role = c("rev")), 14 | person("Saar", "Junius", "", "saar.junius@openanalytics.eu", 15 | role = c("aut"))) 16 | Maintainer: Jasper Schelfhout 17 | Description: The core of this package is a function eDT() which enhances DT::datatable() such that it can be used to interactively modify data in 'shiny'. By the use of generic 'dplyr' methods it supports many types of data storage, with relational databases ('dbplyr') being the main use case. 18 | License: GPL-3 19 | Copyright: Open Analytics NV, 2023 20 | Imports: 21 | shiny, 22 | shinyjs, 23 | DT, 24 | tibble, 25 | dplyr, 26 | rlang, 27 | uuid, 28 | fontawesome (>= 0.4.0) 29 | Suggests: 30 | testthat, 31 | dtplyr, 32 | data.table, 33 | vctrs, 34 | RSQLite, 35 | dbplyr, 36 | glue, 37 | DBI, 38 | bit64, 39 | knitr, 40 | dm 41 | URL: https://github.com/openanalytics/editbl 42 | BugReports: https://github.com/openanalytics/editbl/issues 43 | VignetteBuilder: knitr 44 | Encoding: UTF-8 45 | Roxygen: list(markdown = TRUE) 46 | RoxygenNote: 7.3.2 47 | -------------------------------------------------------------------------------- /editbl/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(e_rows_insert,default) 4 | S3method(e_rows_insert,dtplyr_step) 5 | S3method(e_rows_insert,tbl_dbi) 6 | S3method(e_rows_update,data.frame) 7 | S3method(e_rows_update,default) 8 | S3method(e_rows_update,dtplyr_step) 9 | S3method(e_rows_update,tbl_dbi) 10 | S3method(inputServer,default) 11 | S3method(inputUI,default) 12 | S3method(rows_delete,dtplyr_step) 13 | export(connectDB) 14 | export(customButton) 15 | export(eDT) 16 | export(eDTOutput) 17 | export(e_rows_insert) 18 | export(e_rows_update) 19 | export(foreignTbl) 20 | export(inputServer) 21 | export(inputUI) 22 | export(runDemoApp) 23 | export(selectInputDT_Server) 24 | export(selectInputDT_UI) 25 | import(fontawesome) 26 | importFrom(DT,DTOutput) 27 | importFrom(DT,coerceValue) 28 | importFrom(DT,dataTableProxy) 29 | importFrom(DT,datatable) 30 | importFrom(DT,formatStyle) 31 | importFrom(DT,hideCols) 32 | importFrom(DT,renderDT) 33 | importFrom(DT,renderDataTable) 34 | importFrom(DT,styleEqual) 35 | importFrom(dplyr,"%>%") 36 | importFrom(dplyr,across) 37 | importFrom(dplyr,all_of) 38 | importFrom(dplyr,anti_join) 39 | importFrom(dplyr,as_tibble) 40 | importFrom(dplyr,collect) 41 | importFrom(dplyr,count) 42 | importFrom(dplyr,distinct) 43 | importFrom(dplyr,everything) 44 | importFrom(dplyr,filter) 45 | importFrom(dplyr,if_any) 46 | importFrom(dplyr,inner_join) 47 | importFrom(dplyr,is.tbl) 48 | importFrom(dplyr,left_join) 49 | importFrom(dplyr,pull) 50 | importFrom(dplyr,relocate) 51 | importFrom(dplyr,rows_delete) 52 | importFrom(dplyr,rows_insert) 53 | importFrom(dplyr,rows_update) 54 | importFrom(dplyr,rowwise) 55 | importFrom(dplyr,select) 56 | importFrom(dplyr,tbl) 57 | importFrom(dplyr,tbl_vars) 58 | importFrom(dplyr,tibble) 59 | importFrom(dplyr,type_sum) 60 | importFrom(dplyr,union) 61 | importFrom(rlang,":=") 62 | importFrom(shiny,HTML) 63 | importFrom(shiny,NS) 64 | importFrom(shiny,actionButton) 65 | importFrom(shiny,dateInput) 66 | importFrom(shiny,div) 67 | importFrom(shiny,fluidPage) 68 | importFrom(shiny,freezeReactiveValue) 69 | importFrom(shiny,icon) 70 | importFrom(shiny,is.reactive) 71 | importFrom(shiny,isTruthy) 72 | importFrom(shiny,isolate) 73 | importFrom(shiny,modalButton) 74 | importFrom(shiny,modalDialog) 75 | importFrom(shiny,moduleServer) 76 | importFrom(shiny,numericInput) 77 | importFrom(shiny,observe) 78 | importFrom(shiny,observeEvent) 79 | importFrom(shiny,reactive) 80 | importFrom(shiny,reactiveValues) 81 | importFrom(shiny,renderPrint) 82 | importFrom(shiny,renderUI) 83 | importFrom(shiny,req) 84 | importFrom(shiny,selectInput) 85 | importFrom(shiny,shinyApp) 86 | importFrom(shiny,showModal) 87 | importFrom(shiny,showNotification) 88 | importFrom(shiny,stopApp) 89 | importFrom(shiny,tagList) 90 | importFrom(shiny,tags) 91 | importFrom(shiny,textInput) 92 | importFrom(shiny,uiOutput) 93 | importFrom(shiny,verbatimTextOutput) 94 | importFrom(shinyjs,disable) 95 | importFrom(shinyjs,disabled) 96 | importFrom(shinyjs,enable) 97 | importFrom(shinyjs,hidden) 98 | importFrom(shinyjs,useShinyjs) 99 | importFrom(tibble,as_tibble) 100 | importFrom(utils,head) 101 | importFrom(utils,packageName) 102 | importFrom(utils,str) 103 | importFrom(utils,tail) 104 | importFrom(uuid,UUIDgenerate) 105 | -------------------------------------------------------------------------------- /editbl/R/demoApp.R: -------------------------------------------------------------------------------- 1 | #' Run a demo app 2 | #' @param app demoApp to run. Options: database / mtcars / custom 3 | #' 4 | #' @details These apps are for illustrative purposes. 5 | #' 6 | #' @param ... arguments passed onto the demoApp 7 | #' @examples 8 | #' ## Only run this example in interactive R sessions 9 | #' if(interactive()){ 10 | #' 11 | #' # Database 12 | #' tmpFile <- tempfile(fileext = ".sqlite") 13 | #' file.copy(system.file("extdata", "chinook.sqlite", package = 'editbl'), tmpFile) 14 | #' 15 | #' conn <- connectDB(dbname = tmpFile) 16 | #' 17 | #' runDemoApp(app = "database", conn = conn) 18 | #' DBI::dbDisconnect(conn) 19 | #' 20 | #' unlink(tmpFile) 21 | #' 22 | #' # mtcars 23 | #' runDemoApp(app = "mtcars") 24 | #' 25 | #' # Any tibble of your liking 26 | #' runDemoApp(app = "custom", dplyr::tibble(iris)) 27 | #' } 28 | #' @inherit shiny::shinyApp return 29 | #' @export 30 | runDemoApp <- function(app = "database", ...){ 31 | fn <- switch(app, 32 | "database" = runDemoApp_DB, 33 | "mtcars" = runDemoApp_mtcars, 34 | "custom" = runDemoApp_custom, 35 | stop("demoApp not found") 36 | ) 37 | do.call(fn, list(...)) 38 | } 39 | 40 | #' Run a demo app 41 | #' @importFrom shiny shinyApp 42 | #' @inherit shiny::shinyApp return 43 | runDemoApp_DB <- function(){ 44 | tmpFile <- tempfile(fileext = ".sqlite") 45 | file.copy(system.file("extdata", "chinook.sqlite", package = 'editbl'), tmpFile) 46 | conn <- editbl::connectDB(dbname = tmpFile) 47 | 48 | ui <- demoUI_DB(id = "app", conn = conn) 49 | server <- function(input, output, session) { 50 | demoServer_DB(id = "app", conn = conn) 51 | } 52 | shinyApp(ui, server) 53 | } 54 | 55 | #' Run a demo app 56 | #' @importFrom shiny shinyApp 57 | #' @inherit shiny::shinyApp return 58 | runDemoApp_mtcars <- function(){ 59 | ui <- demoUI_mtcars(id = "app") 60 | server <- function(input, output, session) { 61 | demoServer_mtcars(id = "app") 62 | } 63 | shinyApp(ui, server) 64 | } 65 | 66 | #' Run a custom demo app 67 | #' @param x `tbl` 68 | #' @importFrom shiny shinyApp 69 | #' @inherit shiny::shinyApp return 70 | runDemoApp_custom <- function(x){ 71 | ui <- demoUI_custom(id = "app") 72 | server <- function(input, output, session) { 73 | demoServer_custom(id = "app", x= x) 74 | } 75 | shinyApp(ui, server) 76 | } 77 | 78 | #' UI of the DB demo app 79 | #' @param id `character(1)` 80 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 81 | #' @importFrom shiny NS tagList selectInput verbatimTextOutput 82 | #' @return HTML 83 | #' 84 | #' @author Jasper Schelfhout 85 | demoUI_DB <- function(id, conn) { 86 | ns <- NS(id) 87 | tagList( 88 | selectInput( 89 | inputId = ns("table"), 90 | label = "table", 91 | choices = DBI::dbListTables(conn)), 92 | eDTOutput(id = ns("editbl")) 93 | ) 94 | } 95 | 96 | #' Server of the DB demo app 97 | #' @param id `character(1)` 98 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 99 | #' @importFrom shiny reactive moduleServer renderPrint 100 | #' @importFrom dplyr tbl 101 | #' @return NULL, just executes the module server. 102 | #' @author Jasper Schelfhout 103 | demoServer_DB <- function(id, conn) { 104 | moduleServer( 105 | id, 106 | function(input, output, session) { 107 | data <- reactive({ 108 | dplyr::tbl(conn, input$table) 109 | }) 110 | 111 | modifiedData <- eDT( 112 | id = "editbl", 113 | data = data, 114 | in_place = TRUE 115 | ) 116 | 117 | invisible() 118 | } 119 | ) 120 | } 121 | 122 | #' UI of the demo mtcars app 123 | #' @param id `character(1)` 124 | #' @importFrom shiny NS tagList 125 | #' @return HTML 126 | #' 127 | #' @author Jasper Schelfhout 128 | demoUI_mtcars <- function(id) { 129 | demoUI_custom(id) 130 | } 131 | 132 | #' Server of the mtcars demo app 133 | #' @param id `character(1)` 134 | #' @importFrom dplyr tibble 135 | #' @inherit demoServer_custom return 136 | #' @author Jasper Schelfhout 137 | demoServer_mtcars <- function(id) { 138 | demoServer_custom(id, dplyr::tibble(datasets::mtcars)) 139 | } 140 | 141 | #' UI of the demo mtcars app 142 | #' @param id `character(1)` 143 | #' @importFrom shiny NS tagList 144 | #' @return HTML 145 | #' 146 | #' @author Jasper Schelfhout 147 | demoUI_custom <- function(id) { 148 | ns <- NS(id) 149 | tagList( 150 | eDTOutput(id = ns("editbl")), 151 | ) 152 | } 153 | 154 | #' Server of the mtcars demo app 155 | #' @param id `character(1)` 156 | #' @param x `tbl` 157 | #' @return NULL, just executes the module server. 158 | #' @author Jasper Schelfhout 159 | demoServer_custom <- function(id, x) { 160 | moduleServer( 161 | id, 162 | function(input, output, session) { 163 | modifiedData <- eDT( 164 | id = "editbl", 165 | data = x, 166 | in_place = FALSE 167 | )$result 168 | 169 | invisible() 170 | } 171 | ) 172 | } 173 | -------------------------------------------------------------------------------- /editbl/R/devApp.R: -------------------------------------------------------------------------------- 1 | #' Run a development app 2 | #' @details This app prints some of the server objects and has a button to interactively browse the code. 3 | #' This is useful for debugging and experimenting with new features. 4 | #' 5 | #' @importFrom shiny shinyApp 6 | #' @inherit shiny::shinyApp return 7 | runDevApp <- function(){ 8 | conn <- connectDB() 9 | ui <- devUI(id = "app", conn = conn) 10 | server <- function(input, output, session) { 11 | devServer(id = "app", conn = conn) 12 | } 13 | shinyApp(ui, server) 14 | } 15 | 16 | #' UI of the development app 17 | #' @param id `character(1)` 18 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 19 | #' @importFrom shiny NS tagList selectInput verbatimTextOutput actionButton 20 | #' @return HTML 21 | #' 22 | #' @author Jasper Schelfhout 23 | devUI <- function(id, conn) { 24 | ns <- NS(id) 25 | tagList( 26 | actionButton(ns("browse"), label = "browse parent"), 27 | selectInput( 28 | inputId = ns("table"), 29 | label = "table", 30 | choices = DBI::dbListTables(conn)), 31 | eDTOutput(id = ns("editbl")), 32 | verbatimTextOutput(ns("modifiedData")) 33 | ) 34 | } 35 | 36 | #' Server of the development app 37 | #' @param id `character(1)` 38 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 39 | #' @importFrom shiny reactive moduleServer renderPrint 40 | #' @importFrom dplyr tbl 41 | #' @return NULL, just executes the module server. 42 | #' @author Jasper Schelfhout 43 | devServer <- function(id, conn) { 44 | moduleServer( 45 | id, 46 | function(input, output, session) { 47 | data <- reactive({ 48 | dplyr::tbl(conn, input$table) 49 | }) 50 | 51 | modifiedData <- eDT( 52 | id = "editbl", 53 | data = data, 54 | options = list( 55 | columnDefs = list(list( 56 | visible = TRUE, 57 | targets = "_all"))), 58 | in_place = TRUE, 59 | filter = "top" 60 | )$result 61 | 62 | 63 | observeEvent(input$browse,{ 64 | browser() 65 | }) 66 | 67 | output$modifiedData <- renderPrint({ 68 | str(modifiedData()) 69 | }) 70 | invisible() 71 | } 72 | ) 73 | } -------------------------------------------------------------------------------- /editbl/R/eDT_app.R: -------------------------------------------------------------------------------- 1 | #' Open interactive app to explore and modify data 2 | #' 3 | #' @details When \code{\link{eDT}} is not used within the server of a shiny app, it will 4 | #' call this function to start up a shiny app itself. Just as `DT::datatable()` displays a table 5 | #' in the browser when called upon interactively. 6 | #' 7 | #' @param ... arguments past to \code{\link{eDT}} 8 | #' @importFrom shiny shinyApp 9 | #' @return data (or a modified version thereof) once you click 'close' 10 | eDT_app <- function(...){ 11 | args <- list(...) 12 | ui <- eDT_app_ui(eDTId = args$id) 13 | server <- function(input, output, session) { 14 | do.call(eDT_app_server, args) 15 | } 16 | shiny::runApp(list(ui = ui, server = server)) 17 | } 18 | 19 | #' UI of eDT_app 20 | #' @param moduleId `character(1)` id to connect with eDT_app_server 21 | #' @param eDTId `character(1)` id to connect \code{\link{eDTOutput}} to \code{\link{eDT}} within the module. 22 | #' @importFrom shiny NS tagList actionButton 23 | #' @return HTML 24 | #' 25 | #' @author Jasper Schelfhout 26 | eDT_app_ui <- function(moduleId = "nevergonnagiveyouup", eDTId = "nevergonnaletyoudown") { 27 | ns <- NS(moduleId) 28 | tagList( 29 | actionButton(inputId = ns("close"), "label" = "close"), 30 | eDTOutput(id = ns(eDTId)), 31 | ) 32 | } 33 | 34 | #' Server of eDT_app 35 | #' @param moduleId `character(1)` id to connect with eDT_app_server 36 | #' @param ... arguments passed to \link{eDT} 37 | #' @importFrom shiny reactive moduleServer observeEvent stopApp reactiveValues 38 | #' @importFrom dplyr tbl 39 | #' @return moduleServer which on application stop returns version of x with made changes 40 | #' 41 | #' @author Jasper Schelfhout 42 | eDT_app_server <- function(moduleId = "nevergonnagiveyouup",...) { 43 | args <- list(...) 44 | moduleServer( 45 | moduleId, 46 | function(input, output, session) { 47 | modifiedData <- do.call(eDTServer, args)$result 48 | 49 | observeEvent(input$close, { 50 | shiny::stopApp(modifiedData()) 51 | }) 52 | } 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /editbl/R/foreignTbl.R: -------------------------------------------------------------------------------- 1 | #' Create a foreign tibble 2 | #' 3 | #' @details This is a tibble that can be passed onto \code{\link{eDT}} as a referenced table. 4 | #' 5 | #' It is the equivalent of a database table to which the `data` tbl of eDT has a foreign key. 6 | #' 7 | #' It will be merged with the tbl passed onto the `data` argument allowing to provide restrictions 8 | #' for certain columns. 9 | #' 10 | #' Note that row uniqueness for the columns used in `by` and `naturalKey` is assumed. 11 | #' This assumption will however not be checked since it is an expensive operation on big datasets. 12 | #' However, if violated, it might give errors or unexpected results during usage of the eDT module. 13 | #' 14 | #' @param x `tbl`. The referencing table. 15 | #' @param y `tbl`. The referenced table. 16 | #' @param by `character`. Column names to match on. 17 | #' Note that you should rename and/or typecast the columns in y should they not exactly match the columns in x. 18 | #' @param naturalKey `character`. The columns that form the natural key in y. 19 | #' These are the only ones that can actually get modified in `eDT` when changing cells in the table. 20 | #' Reasoning being that these columns should be sufficient to uniquely identify a row in the referenced table. 21 | #' All other columns will be automatically fetched and filled in. 22 | #' 23 | #' @param allowNew `logical`. Whether or not new values are allowed. If `TRUE`, 24 | #' the rows in the foreignTbl will only be used as suggestions, not restrictions. 25 | #' 26 | #' @examples 27 | #' a <- tibble::tibble( 28 | #' first_name = c("Albert","Donald","Mickey"), 29 | #' last_name_id = c(1,2,2) 30 | #' ) 31 | #' 32 | #' b <- foreignTbl( 33 | #' a, 34 | #' tibble::tibble( 35 | #' last_name = c("Einstein", "Duck", "Mouse"), 36 | #' last_name_id = c(1,2,3) 37 | #' ), 38 | #' by = "last_name_id", 39 | #' naturalKey = "last_name" 40 | #') 41 | #' 42 | #' ## Only run this in interactive R sessions 43 | #' if(interactive()){ 44 | #' eDT(a, 45 | #' foreignTbls = list(b), 46 | #' options = list(columnDefs = list(list(visible=FALSE, targets="last_name_id"))) 47 | #' ) 48 | #' } 49 | #' 50 | #' 51 | #' @return List with unmodified arguments. However, they have now been checked for validity. 52 | #' - y, see argument `y`. 53 | #' - by, see argument `by`. 54 | #' - naturalKey, see argument `naturalKey`. 55 | #' - allowNew, see argument `allowNew` 56 | #' 57 | #' @importFrom dplyr tbl_vars all_of select 58 | #' 59 | #' @author Jasper Schelfhout 60 | #' @export 61 | foreignTbl <- function( 62 | x, 63 | y, 64 | by = intersect(dplyr::tbl_vars(x), dplyr::tbl_vars(y)), 65 | naturalKey = dplyr::tbl_vars(y), 66 | allowNew = FALSE 67 | ){ 68 | stopifnot(all(naturalKey %in% colnames(y))) 69 | stopifnot(is.logical(allowNew)) 70 | if(is.null(names(by))){ 71 | names(by) <- by 72 | } 73 | 74 | clashingNames <- intersect( 75 | setdiff(dplyr::tbl_vars(y), by), 76 | dplyr::tbl_vars(x) 77 | ) 78 | if(length(clashingNames)){ 79 | stop(sprintf("Name clashes on: %s. Please rename.", paste(clashingNames, collapse = ", "))) 80 | } 81 | 82 | x_types <- x %>% select(all_of(by)) %>% getColumnTypeSums %>% unlist() 83 | y_types <- y %>% select(all_of(by)) %>% getColumnTypeSums %>% unlist() 84 | typeMisMatches <- names(x_types[x_types != y_types]) 85 | if(length(typeMisMatches)){ 86 | stop(sprintf("%s is not of the same type: %s vs %s. Try casting with dplyr::mutate()", 87 | typeMisMatches, 88 | x_types[typeMisMatches], 89 | y_types[typeMisMatches])) 90 | } 91 | 92 | foreignTbl <- list( 93 | y = y, 94 | by = by, 95 | naturalKey = naturalKey, 96 | allowNew = allowNew 97 | ) 98 | 99 | return(foreignTbl) 100 | } 101 | 102 | #' Merge a tbl with it a foreignTbl 103 | #' 104 | #' @details see also `dplyr` join functions, for example `dplyr::left_join`. 105 | #' 106 | #' @param tbl `tbl` 107 | #' @param foreignTbl `list` as created by \code{\link{foreignTbl}} 108 | #' @param keepNA `logical` keep rows from tbl with NA keys. 109 | #' @param by named `character`, columns to join on. 110 | #' @param copy `logical`, whether or not to copy the `foreignTbl` to the source of argument `tbl` for joining. 111 | #' @param type `character(1)`, type of joint to perform. Can be 'inner' or 'left'. 112 | #' @return `tbl`, containing both columns from argument `tbl` and argument `foreignTbl`. 113 | #' 114 | #' @importFrom dplyr left_join inner_join filter union 115 | #' 116 | #' @author Jasper Schelfhout 117 | joinForeignTbl <- function( 118 | tbl, 119 | foreignTbl, 120 | keepNA = TRUE, 121 | by = foreignTbl$by, 122 | copy = TRUE, 123 | type = c("inner", "left")[1]){ 124 | .data <- NULL # for R CMD CHECK 125 | if(is.null(names(by))){ 126 | names(by) <- by 127 | } 128 | 129 | if(type == "inner"){ 130 | result <- dplyr::inner_join( 131 | x = tbl, 132 | y = foreignTbl$y, 133 | by = by, 134 | copy = copy) 135 | } else if(type == "left"){ 136 | result <- dplyr::left_join( 137 | x = tbl, 138 | y = foreignTbl$y, 139 | by = by, 140 | copy = copy) 141 | } else { 142 | stop("only inner and left join supported") 143 | } 144 | 145 | 146 | if(keepNA && type == "inner"){ 147 | naTbl <- tbl 148 | for(key in names(by)){ 149 | naTbl <- dplyr::filter(naTbl, is.na(.data[[key]])) 150 | } 151 | 152 | naTbl <- dplyr::left_join( 153 | x = naTbl, 154 | y = foreignTbl$y, 155 | by = by, 156 | copy = copy 157 | ) 158 | result <- dplyr::union(result, naTbl) 159 | } 160 | 161 | return(result) 162 | } 163 | 164 | #' Fill data columns based on foreignTbls 165 | #' 166 | #' @details 167 | #' When a combination of columns is not found in the foreignTbl, 168 | #' fill the deductedColumns with NA. 169 | #' 170 | #' @details on foreignTbls suggesting conflicting data, 171 | #' an arbitrary choice is made. It is best to afterwards check with 172 | #' checkForeignTbls to see if a valid result is obtained. 173 | #' 174 | #' @param tbl `tbl` 175 | #' @param foreignTbls list of foreign tbls as created by \code{\link{foreignTbl}} 176 | #' @return tbl 177 | #' 178 | #' @author Jasper Schelfhout 179 | fillDeductedColumns <- function(tbl, foreignTbls){ 180 | columnOrder <- names(tbl) 181 | nrow <- nrow(tbl) 182 | 183 | # Clear columns that should be deducted 184 | for (foreignTbl in foreignTbls){ 185 | autoFill <- setdiff(colnames(foreignTbl$y), foreignTbl$naturalKey) 186 | tbl[,autoFill] <- NULL 187 | } 188 | 189 | # Fill in columns 190 | for(foreignTbl in foreignTbls){ 191 | tbl <- joinForeignTbl(tbl, foreignTbl, by = foreignTbl$naturalKey, type = "left") 192 | } 193 | 194 | newNrow <- nrow(tbl) 195 | 196 | if(nrow < newNrow){ 197 | stop("One of the given foreign tbls has non unique keys.") 198 | } 199 | 200 | tbl[,columnOrder] 201 | } 202 | 203 | #' Check if all rows in tbl fufill `foreignTbl` constraints 204 | #' @param tbl `tbl` 205 | #' @param foreignTbls list of foreign tbls as created by \code{\link{foreignTbl}} 206 | #' @return `logical` stating if tbl fufills all constraints imposed by all foreign tbls. 207 | #' @importFrom dplyr count pull anti_join filter if_any all_of 208 | #' 209 | #' @author Jasper Schelfhout 210 | checkForeignTbls <- function(tbl, foreignTbls){ 211 | n <- NULL # R CMD CHECK fix 212 | 213 | 214 | for(foreignTbl in foreignTbls){ 215 | # match on combination on naturalKey and surrogateKey 216 | # E.g. check that the row actually exists in the foreignTbl 217 | if(foreignTbl$allowNew){ 218 | next() 219 | } 220 | by <- unique(unname(c(foreignTbl$by, foreignTbl$naturalKey))) 221 | 222 | nonExisting <- dplyr::anti_join(tbl, foreignTbl$y, 223 | by = by, 224 | copy = TRUE) %>% 225 | dplyr::filter(if_any(all_of(by), ~ !is.na(.))) # Do not complain about empty rows, might be nice as a parameter. 226 | 227 | if(dplyr::pull(dplyr::count(nonExisting), n)){ 228 | stop(sprintf("Invalid %s: %s", 229 | paste(foreignTbl$naturalKey, collapse = ", "), 230 | paste(as.character(nonExisting[1,foreignTbl$naturalKey]), collapse = ", ") 231 | )) 232 | } 233 | } 234 | TRUE 235 | } 236 | 237 | #' Cast all columns that exist in a foreignTbl to factor 238 | #' 239 | #' @details Can be used to fixate possible options when editing. 240 | #' 241 | #' @param data `data.frame` 242 | #' @param foreignTbls list of foreign tbls as created by \code{\link{foreignTbl}} 243 | #' @importFrom dplyr distinct select pull tbl_vars 244 | #' @return data.frame 245 | #' 246 | #' @author Jasper Schelfhout 247 | castToFactor <- function(data, foreignTbls){ 248 | levels <- list() 249 | for(foreignTbl in foreignTbls){ 250 | for(column in dplyr::tbl_vars(foreignTbl$y)){ 251 | currentLevels <- levels[[column]] 252 | newRestrictions <- dplyr::pull(dplyr::distinct(dplyr::select(foreignTbl$y, column))) 253 | if(is.null(currentLevels)){ 254 | levels[[column]] <- newRestrictions 255 | } else { 256 | levels[[column]] <- intersect(currentLevels, newRestrictions) 257 | } 258 | } 259 | } 260 | 261 | for(column in intersect(dplyr::tbl_vars(data), names(levels))){ 262 | data[[column]] <- factor(data[[column]], levels = levels[[column]]) 263 | } 264 | data 265 | } 266 | 267 | #' Get all columns that are not natural keys 268 | #' @param foreignTbls list of foreign tbls as created by \code{\link{foreignTbl}} 269 | #' @return `character` 270 | #' 271 | #' @author Jasper Schelfhout 272 | getNonNaturalKeyCols <- function(foreignTbls){ 273 | result <- c() 274 | for(foreignTbl in foreignTbls){ 275 | cols <- as.character(dplyr::tbl_vars(foreignTbl$y)) 276 | naturalKey <- foreignTbl$naturalKey 277 | result <- unique(c(result, setdiff(cols, naturalKey))) 278 | } 279 | result 280 | } 281 | -------------------------------------------------------------------------------- /editbl/R/selectInputDT.R: -------------------------------------------------------------------------------- 1 | #' UI part of a DT select input 2 | #' @param id `character(1)` same one as used in \code{\link{selectInputDT_Server}} 3 | #' @importFrom shiny NS uiOutput 4 | #' @return HTML 5 | #' @inherit selectInputDT_Server examples 6 | #' @export 7 | #' @author Jasper Schelfhout 8 | selectInputDT_UI <- function(id){ 9 | ns <- NS(id) 10 | uiOutput(ns("DT_UI")) 11 | } 12 | 13 | #' Server part to use a \code{\link[DT]{datatable}} as select input 14 | #' 15 | #' @seealso `shiny::selectInput`. This function can be more convenient for selecting rows 16 | #' with multiple columns. 17 | #' 18 | #' @param id `character(1)` same one as used in \code{\link{selectInputDT_UI}} 19 | #' @param choices `data.frame` 20 | #' @param label `character(1)` 21 | #' @param selected `data.frame` with rows available in `choices`. 22 | #' @param multiple `logical`. Whether or not multiple row selection is allowed 23 | #' 24 | #' @examples 25 | #' ## Only run this example in interactive R sessions 26 | #' if(interactive()){ 27 | #' ui <- selectInputDT_UI('id') 28 | #' data <- data.frame(id = 1:3, name = letters[1:3]) 29 | #' server <- function(input,output, session){ 30 | #' selected = selectInputDT_Server('id', choices = data, selected = data[1,] ) 31 | #' observe({print(selected())}) 32 | #' } 33 | #' shiny::shinyApp(ui, server) 34 | #' 35 | #' } 36 | #' @importFrom shiny is.reactive reactive renderUI moduleServer 37 | #' @importFrom DT renderDataTable datatable 38 | #' @return A selection of rows from the `data.frame` provided under choices. 39 | #' @export 40 | #' @author Jasper Schelfhout 41 | selectInputDT_Server <- function(id, 42 | label = "", 43 | choices, 44 | selected = NULL, 45 | multiple = FALSE){ 46 | moduleServer( 47 | id, 48 | function(input,output,session){ 49 | # Make arguments reactive 50 | # Need to be explicit about environement. Otherwhise they overwrite themselves. 51 | # This way users can pass on both reactive an non reactive arguments 52 | argEnv <- parent.frame(3) 53 | 54 | if(!shiny::is.reactive(label)){ 55 | label <- shiny::reactive(label, env = argEnv) 56 | } 57 | 58 | if(!shiny::is.reactive(choices)){ 59 | choices <- shiny::reactive(choices, env = argEnv) 60 | } 61 | 62 | if(!shiny::is.reactive(selected)){ 63 | selected <- shiny::reactive(selected, env = argEnv) 64 | } 65 | 66 | if(!shiny::is.reactive(multiple)){ 67 | multiple <- shiny::reactive(multiple, env = argEnv) 68 | } 69 | 70 | observe({ 71 | if(!multiple() && nrow(selected()) > 1){ 72 | stop("Can not have more than 1 row selected.") 73 | } 74 | }) 75 | 76 | ns <- session$ns 77 | 78 | hasSelection <- reactive({ 79 | if(is.null(selected())){ 80 | FALSE 81 | } else if ({ all(is.na(selected()))}){ 82 | FALSE 83 | } else { 84 | TRUE 85 | } 86 | }) 87 | 88 | data_selection_first <- reactive({ 89 | if(hasSelection()){ 90 | dt <- unique(rbind(selected(), choices())) 91 | } else { 92 | dt <- choices() 93 | } 94 | dt 95 | }) 96 | 97 | output$DT_UI <- renderUI({ 98 | tagList( 99 | label(), 100 | DT::DTOutput(ns("DT")) 101 | ) 102 | }) 103 | 104 | mode <- reactive({ 105 | if(multiple()){ 106 | mode = "multiple" 107 | } else { 108 | mode = "single" 109 | } 110 | mode 111 | }) 112 | 113 | rowNrs <- reactive({ 114 | if (hasSelection()){ 115 | rowNrs = seq_len(nrow(selected())) 116 | } else { 117 | c() 118 | } 119 | rowNrs 120 | }) 121 | 122 | output$DT <- DT::renderDataTable({ 123 | data <- data_selection_first() 124 | DT::datatable( 125 | data, 126 | rownames = TRUE, 127 | options = list( 128 | scrollX = TRUE, 129 | columnDefs = list( 130 | list( 131 | visible = FALSE, 132 | targets = c(0)) # hide row names https://github.com/rstudio/DT/issues/945 133 | ) 134 | ), 135 | filter = "top", 136 | selection = list( 137 | mode = mode(), 138 | selected = rowNrs(), 139 | target = 'row')) 140 | }) 141 | 142 | reactive({ 143 | data_selection_first()[input$DT_rows_selected,] 144 | }) 145 | }) 146 | } 147 | -------------------------------------------------------------------------------- /editbl/R/shinyInput.R: -------------------------------------------------------------------------------- 1 | #' An input UI for a `data.frame` 2 | #' 3 | #' @details A new method for this can be added if you wish to alter the default behavior of the pop-up modals in \code{\link{eDT}}. 4 | #' 5 | #' @param id `character(1)` module id 6 | #' @param ... arguments passed onto methods 7 | #' @inherit inputServer examples 8 | #' 9 | #' @return HTML. A set of input fields corresponding to the given row. 10 | #' 11 | #' @author Jasper Schelfhout 12 | #' @export 13 | inputUI <- function(id, ...){ 14 | UseMethod("inputUI") 15 | } 16 | 17 | #' An input server for a `data.frame` 18 | #' 19 | #' @details A new method for this can be added if you wish to alter the default behavior of the pop-up modals in \code{\link{eDT}}. 20 | #' 21 | #' @param id `character(1)` module id 22 | #' @param data single row `data.frame` 23 | #' @param ... further arguments for methods 24 | #' @return modified version of data 25 | #' 26 | #' @examples 27 | #' if(interactive()){ 28 | #' library(shiny) 29 | #' ui <- inputUI('id') 30 | #' server <- function(input,output,session){ 31 | #' input <- inputServer("id", mtcars[1,]) 32 | #' observe({print(input())}) 33 | #' } 34 | #' shinyApp(ui, server) 35 | #' } 36 | #' 37 | #' @author Jasper Schelfhout 38 | #' @export 39 | inputServer <- function(id, data, ...){ 40 | UseMethod("inputServer") 41 | } 42 | 43 | #' UI part for modal with input fields for editing 44 | #' 45 | #' @details The UI elements that have an id identical to a column name are used for updating the data. 46 | #' 47 | #' @param id character module id 48 | #' @param ... for compatibility with method 49 | #' @return HTML. A set of input fields corresponding to the given row. 50 | #' 51 | #' @author Jasper Schelfhout 52 | #' @export 53 | inputUI.default <- function(id, ...){ 54 | ns <- NS(id) 55 | uiOutput(ns("inputUI")) 56 | } 57 | 58 | #' An input server for a `data.frame` 59 | #' 60 | #' @details Reads all inputs ids that are identical to column names of the data 61 | #' and updates the data. 62 | #' 63 | #' @param id `character(1)` module id 64 | #' @param data single row `data.frame` 65 | #' @param notEditable `character` columns that should not be edited 66 | #' @param colnames named `character` 67 | #' @param foreignTbls list of foreignTbls. See \code{\link{foreignTbl}} 68 | #' @param ... for compatibility with other methods 69 | #' @return reactive modified version of data 70 | #' 71 | #' @author Jasper Schelfhout 72 | #' @export 73 | inputServer.default <- function(id, data, colnames, notEditable, foreignTbls, ...){ 74 | missingColnames <- missing(colnames) 75 | missingForeignTbls <- missing(foreignTbls) 76 | missingNotEditable <- missing(notEditable) 77 | 78 | moduleServer( 79 | id, 80 | function(input, output, session) { 81 | ns <- session$ns 82 | rv <- reactiveValues(inputDTData = list()) 83 | 84 | # Make arguments reactive 85 | # Need to be explicit about environement. Otherwhise they overwrite themselves. 86 | # This way users can pass on both reactive an non reactive arguments 87 | argEnv <- parent.frame(3) 88 | 89 | if(!shiny::is.reactive(data)){ 90 | data <- reactive(data, env = argEnv) 91 | } 92 | 93 | if(missingColnames){ 94 | colnames <- reactive({ 95 | colnames <- base::colnames(data()) 96 | names(colnames) <- colnames 97 | colnames 98 | }) 99 | } 100 | else if(!shiny::is.reactive(colnames)){ 101 | colnames <- reactive(colnames, env = argEnv) 102 | } 103 | 104 | if(missingNotEditable){ 105 | notEditable <- reactive({ 106 | character(0) 107 | }) 108 | } 109 | else if(!shiny::is.reactive(notEditable)){ 110 | notEditable <- reactive(notEditable, env = argEnv) 111 | } 112 | 113 | if(missingForeignTbls){ 114 | foreignTbls <- reactive({ 115 | list() 116 | }) 117 | } 118 | else if(!shiny::is.reactive(foreignTbls)){ 119 | foreignTbls <- reactive(foreignTbls, env = argEnv) 120 | } 121 | 122 | inputDTs <- reactive({ 123 | inputDTs <- lapply(foreignTbls(), function(x){ 124 | if(length(x$naturalKey) > 1){ 125 | id <- gsub("-", "_", force(uuid::UUIDgenerate())) 126 | list( 127 | id = id, 128 | choices = dplyr::collect( 129 | dplyr::select( 130 | x$y, 131 | dplyr::all_of(as.character(x$naturalKey)) 132 | ) 133 | ), 134 | selected = data()[,x$naturalKey] 135 | ) 136 | } 137 | }) 138 | if(length(inputDTs)){ 139 | inputDTs[sapply(inputDTs, is.null)] <- NULL 140 | } 141 | inputDTs 142 | }) 143 | 144 | observe({ 145 | inputDTData <- list() 146 | for(inputDT in inputDTs()){ 147 | inputDTData[[inputDT$id]] <- do.call(selectInputDT_Server, inputDT) 148 | } 149 | rv$inputDTData <- inputDTData 150 | }) 151 | 152 | inputDTCols <- reactive({ 153 | unique(unlist(lapply(inputDTs(), function(x){ 154 | names(x$choices) 155 | }))) 156 | }) 157 | 158 | normalInputs <- reactive({ 159 | uiData <- data()[,setdiff(base::colnames(data()), notEditable()), drop = FALSE] 160 | uiData <- castToFactor(uiData, foreignTbls()) 161 | inputNormalCols <- setdiff(base::colnames(uiData), inputDTCols()) 162 | 163 | inputs <- lapply(inputNormalCols, function(x){ 164 | if(x %in% colnames()){ 165 | label = names(colnames())[which(colnames() == x)] 166 | } else { 167 | label = x 168 | } 169 | 170 | shinyInput( 171 | x = uiData[[x]], 172 | inputId = ns(x), 173 | label = label, 174 | selected = uiData[,x] 175 | ) 176 | }) 177 | inputs 178 | }) 179 | 180 | DTInputs <- reactive({ 181 | lapply(inputDTs(), function(inputDT){ 182 | selectInputDT_UI(id = ns(inputDT$id)) 183 | }) 184 | }) 185 | 186 | output$inputUI <- renderUI({ 187 | do.call(tagList,c(normalInputs(), DTInputs())) 188 | }) 189 | 190 | newData <- reactive({ 191 | data <- data() 192 | 193 | # Table inputs 194 | for(DTinput in rv$inputDTData){ 195 | newData <- DTinput() 196 | data[,base::colnames(newData)] <- newData 197 | } 198 | 199 | # Normal inputs 200 | for(col in intersect(base::colnames(data), names(input))){ 201 | data[,col] = coerceValue(input[[col]], data[,col]) 202 | } 203 | data 204 | }) 205 | newData 206 | } 207 | ) 208 | } 209 | 210 | #' Get a shiny input for a column of a `tbl` 211 | #' @param x column 212 | #' @param inputId shiny input Id 213 | #' @param label `character(1)` 214 | #' @param selected object of class of x 215 | #' @importFrom shiny numericInput dateInput selectInput textInput 216 | #' @return shiny input 217 | #' 218 | #' @author Jasper Schelfhout 219 | shinyInput <- function(x, inputId, label, selected){ 220 | if(inherits(x, "logical")){ 221 | selectInput(inputId = inputId, label = label, choices = c(" " = NA, "TRUE" = TRUE, "FALSE" = FALSE), selected = selected) 222 | } 223 | else if(inherits(x, "numeric")){ 224 | numericInput(inputId = inputId, label = label, value = selected) 225 | } 226 | else if(inherits(x, "Date")){ 227 | dateInput(inputId = inputId, label = label, value = selected) 228 | } 229 | else if(inherits(x, "factor")){ 230 | selectInput(inputId = inputId, label = label, choices = sort(levels(x)), selected = selected) 231 | } else { 232 | textInput(inputId = inputId, label = label, value = as.character(selected)) 233 | } 234 | } 235 | 236 | -------------------------------------------------------------------------------- /editbl/R/tbl.R: -------------------------------------------------------------------------------- 1 | #' @importFrom dplyr rows_delete 2 | NULL 3 | 4 | #' Insert rows into a tibble 5 | #' @details Mainly a wrapper around \code{\link[dplyr]{rows_insert}}. 6 | #' Allows for specific implementations should the behavior differ from what's needed by `editbl`. 7 | #' Reason for separate method is to avoid conflicts on package loading. 8 | #' 9 | #' @inheritParams dplyr::rows_insert 10 | #' @inherit dplyr::rows_insert return details 11 | #' @export 12 | e_rows_insert <- function( 13 | x, 14 | y, 15 | by = NULL, 16 | ..., 17 | conflict = c("error", "ignore"), 18 | copy = FALSE, 19 | in_place = FALSE){ 20 | UseMethod("e_rows_insert") 21 | } 22 | 23 | #' @inherit e_rows_insert 24 | #' @export 25 | #' @importFrom dplyr rows_insert 26 | e_rows_insert.default <- function( 27 | x, 28 | y, 29 | by = NULL, 30 | ..., 31 | conflict = c("error", "ignore"), 32 | copy = FALSE, 33 | in_place = FALSE){ 34 | dplyr::rows_insert( 35 | x = x, 36 | y = y, 37 | by = by, 38 | ... , 39 | conflict = conflict, 40 | copy = copy, 41 | in_place = in_place) 42 | } 43 | 44 | #' Update rows of a tibble 45 | #' @details Mainly a wrapper around \code{\link[dplyr]{rows_update}}. 46 | #' Allows for specific implementations should the behavior differ from what's needed by `editbl`. 47 | #' Reason for separate method is to avoid conflicts on package loading. 48 | #' @param match named `list` consisting out of two equal length `data.frame`'s with columns defined in `by`. 49 | #' This allows for updates of columns defined in `by`. 50 | #' @inheritParams dplyr::rows_update 51 | #' @inherit dplyr::rows_update return details 52 | #' @export 53 | e_rows_update <- function( 54 | x, 55 | y, 56 | by = NULL, 57 | ..., 58 | match, 59 | unmatched = c("error", "ignore"), 60 | copy = FALSE, 61 | in_place = FALSE){ 62 | UseMethod("e_rows_update") 63 | } 64 | 65 | #' @inherit e_rows_update 66 | #' @importFrom dplyr rows_update 67 | #' @export 68 | e_rows_update.default <- function( 69 | x, 70 | y, 71 | by = NULL, 72 | ..., 73 | match = match, 74 | unmatched = c("error", "ignore"), 75 | copy = FALSE, 76 | in_place = FALSE){ 77 | dplyr::rows_update( 78 | x = x, 79 | y = y, 80 | by = by, 81 | ... , 82 | match = match, 83 | unmatched = unmatched, 84 | copy = copy, 85 | in_place = in_place 86 | ) 87 | } 88 | 89 | #' Start a transaction for a tibble 90 | #' @param tbl `tbl` 91 | #' 92 | #' @author Jasper Schelfhout 93 | beginTransaction <- function(tbl){ 94 | if(inherits(tbl, "tbl_dbi")){ 95 | DBI::dbBegin(tbl$src$con) 96 | } 97 | } 98 | 99 | #' Start a transaction for a tibble 100 | #' @param tbl `tbl` 101 | #' 102 | #' @author Jasper Schelfhout 103 | commitTransaction <- function(tbl){ 104 | if(inherits(tbl, "tbl_dbi")){ 105 | DBI::dbCommit(tbl$src$con) 106 | } 107 | } 108 | 109 | #' Start a transaction for a tibble 110 | #' @param tbl `tbl` 111 | #' 112 | #' @author Jasper Schelfhout 113 | rollbackTransaction <- function(tbl){ 114 | if(inherits(tbl, "tbl_dbi")){ 115 | DBI::dbRollback(tbl$src$con) 116 | } 117 | } -------------------------------------------------------------------------------- /editbl/R/tbl_dbi.R: -------------------------------------------------------------------------------- 1 | #' rows_insert implementation for DBI backends. 2 | #' 3 | #' @examples 4 | #' library(dplyr) 5 | #' 6 | #' # Set up a test table 7 | #' conn <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") 8 | #' artists_df <- data.frame( 9 | #' ArtistId = c(1,2), 10 | #' Name = c("AC/DC", "The Offspring") 11 | #' ) 12 | #' DBI::dbWriteTable(conn, "Artist", artists_df) 13 | #' 14 | #' # Insert new row 15 | #' artists <- tbl(conn, "Artist") 16 | #' DBI::dbBegin(conn) 17 | #' e_rows_insert(artists, 18 | #' data.frame(ArtistId = 999, Name = "testArtist"), 19 | #' in_place = TRUE) 20 | #' 21 | #' DBI::dbRollback(conn) 22 | #' DBI::dbDisconnect(conn) 23 | #' 24 | #' @inheritParams e_rows_insert 25 | #' @inherit e_rows_insert return details 26 | #' 27 | #' @author Jasper Schelfhout 28 | #' @export 29 | e_rows_insert.tbl_dbi <- function(x, y, by = NULL, ..., copy = FALSE, in_place = FALSE){ 30 | if(!in_place){ 31 | stop("Can only edit in place") 32 | } 33 | 34 | if(copy){ 35 | stop("copy TRUE not supported yet.") 36 | } 37 | 38 | if(is.null(by)){ 39 | by <- colnames(x)[1] 40 | } 41 | 42 | if(in_place){ 43 | lapply(seq_len(nrow(y)), function(i){ 44 | rowInsert( 45 | conn = x$src$con, 46 | table = get_db_table_name(x), 47 | values = as.list(y[i,]) 48 | ) 49 | }) 50 | return(invisible(x)) 51 | } 52 | return(x) 53 | } 54 | 55 | #' rows_update implementation for DBI backends. 56 | #' @inheritParams e_rows_update 57 | #' @inherit e_rows_update return details 58 | #' @examples 59 | #' library(dplyr) 60 | #' 61 | #' # Set up a test table 62 | #' conn <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") 63 | #' artists_df <- data.frame( 64 | #' ArtistId = c(1,2), 65 | #' Name = c("AC/DC", "The Offspring") 66 | #' ) 67 | #' DBI::dbWriteTable(conn, "Artist", artists_df) 68 | #' 69 | #' # Update rows without changing the key. 70 | #' artists <- tbl(conn, "Artist") 71 | #' DBI::dbBegin(conn) 72 | #' y <- data.frame(ArtistId = 1, Name = "DC/AC") 73 | #' e_rows_update( 74 | #' x = artists, 75 | #' y = y, 76 | #' by = "ArtistId", 77 | #' in_place = TRUE) 78 | #' DBI::dbRollback(conn) 79 | #' 80 | #' # Update key values of rows. 81 | #' DBI::dbBegin(conn) 82 | #' y <- data.frame(ArtistId = 999, Name = "DC/AC") 83 | #' match <- list( 84 | #' x = data.frame("ArtistId" = 1), 85 | #' y = data.frame("ArtistId" = 999) 86 | #' ) 87 | #' e_rows_update( 88 | #' x = artists, 89 | #' y = y, 90 | #' match = match, 91 | #' by = "ArtistId", 92 | #' in_place = TRUE) 93 | #' DBI::dbRollback(conn) 94 | #' DBI::dbDisconnect(conn) 95 | #' 96 | #' @author Jasper Schelfhout 97 | #' @export 98 | e_rows_update.tbl_dbi <- function(x, y, by = NULL, match = NULL,..., copy = FALSE, in_place = FALSE){ 99 | if(!in_place){ 100 | stop("Can only edit in place") 101 | } 102 | 103 | if(copy){ 104 | stop("copy TRUE not supported yet.") 105 | } 106 | 107 | if(is.null(by)){ 108 | by <- colnames(x)[1] 109 | } 110 | 111 | if(is.null(match)){ 112 | match <- list(x = y[by], y = y[by]) 113 | } 114 | 115 | lapply(seq_len(nrow(match$y)), function(i){ 116 | yMatch <- match$y[i,, drop = FALSE] 117 | values <- merge(yMatch, y, by = by) 118 | xMatch <- match$x[i,, drop = FALSE] 119 | 120 | rowUpdate( 121 | conn = x$src$con, 122 | table = get_db_table_name(x), 123 | values = as.list(values), 124 | where = as.list(xMatch) 125 | ) 126 | }) 127 | return(invisible(x)) 128 | } 129 | 130 | #' Get name of the tbl in the database 131 | #' @param x `tbl_dbi` 132 | #' @return SQL, the table name as used in the database 133 | get_db_table_name <- function(x){ 134 | name <- coalesce( 135 | tryCatch({dbplyr::remote_table(x)}, error = function(e){NULL}), # Since dbplyr 2.4.0 136 | tryCatch({x$lazy_query$x$x}, error = function(e){NULL}), # tbl can apparently get more nested 137 | tryCatch({x$lazy_query$x}, error = function(e){NULL}), # normal place 138 | tryCatch({x$ops$x}, error = function(e){NULL}) # old tbl compatibility 139 | ) 140 | 141 | if(is.null(name)){ 142 | stop("Can not find in-database table name based on the tibble object.") 143 | } 144 | 145 | if(inherits(name,'dbplyr_table_ident')){ 146 | attributes <- unclass(name) 147 | id <- list() 148 | if(!is.na(attributes$table)){ 149 | id$table <- attributes$table 150 | } 151 | if(!is.na(attributes$schema)){ 152 | id$schema <- attributes$schema 153 | } 154 | if(!is.na(attributes$catalog)){ 155 | id$catalog <- attributes$catalog 156 | } 157 | name <- DBI::dbQuoteIdentifier( 158 | conn = x$src$con, 159 | x = do.call(DBI::Id, id) 160 | ) 161 | } else { 162 | name <- DBI::SQL(name) 163 | } 164 | 165 | return(name) 166 | } 167 | 168 | #' Add a row to a table in the database. 169 | #' 170 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 171 | #' @param table character 172 | #' @param values named list, row to add. Names are database column names. Unspecified columns will get database defaults. 173 | #' @return integer number of affected rows. 174 | rowInsert <- function ( 175 | conn, 176 | table, 177 | values){ 178 | 179 | for(i in seq_along(values)){ 180 | values[[i]] <- castToSQLSupportedType(values[[i]]) 181 | } 182 | 183 | # Treat missing values as NULL and use database default. 184 | # This might need to be a flag in the function. 185 | # E.g. there is a difference between wanting to insert NA or let a database generate a default. 186 | values <- values[!is.na(values)] 187 | 188 | query <- glue::glue_sql( 189 | .con = conn, 190 | "INSERT INTO {`table`} ({`columns`*}) VALUES ({values*})", 191 | table = table, 192 | columns = names(values), 193 | values = values 194 | ) 195 | 196 | DBI::dbExecute( 197 | conn, 198 | query) 199 | } 200 | 201 | 202 | #' Update rows in the database. 203 | #' 204 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 205 | #' @param table character 206 | #' @param values named list, values to be set. Names are database column names. 207 | #' @param where named list, values to filter on. Names are database column names. If NULL no filter is applied. 208 | #' @return integer number of affected rows. 209 | rowUpdate <- function ( 210 | conn, 211 | table, 212 | values, 213 | where){ 214 | values <- lapply(values, function(x){ 215 | castToSQLSupportedType(x) 216 | }) 217 | 218 | query <- glue::glue_sql( 219 | .con =conn, 220 | "UPDATE {`table`} SET ({`columns`*}) = ({values*})", 221 | table = table, 222 | columns = names(values), 223 | values = values 224 | ) 225 | 226 | if(length(where)){ 227 | whereSQL <- lapply(seq_along(where), function(i){ 228 | whereSQL( 229 | conn = conn, 230 | table = table, 231 | column = names(where)[i], 232 | operator = "in", 233 | values = where[[i]] 234 | ) 235 | }) 236 | whereSQL <- paste(whereSQL, collapse = " AND ") 237 | query <- paste(query, "WHERE", whereSQL) 238 | } 239 | 240 | DBI::dbExecute( 241 | conn, 242 | query) 243 | } 244 | 245 | #' Cast the data type to something supported by SQL. 246 | #' @param x single value or vector of values 247 | #' @return x, possibly cast to different type 248 | #' 249 | #' @author Jasper Schelfhout 250 | castToSQLSupportedType <- function(x){ 251 | if(!inherits(x, c("numeric", "character"))){ 252 | x <- as.character(x) 253 | } 254 | x 255 | } 256 | 257 | #' Generate where sql 258 | #' 259 | #' @param conn database connection object as given by \code{\link[DBI]{dbConnect}}. 260 | #' @param table character table name (or alias used in query) 261 | #' @param column character column of table 262 | #' @param values character vector of values 263 | #' @param operator character 264 | #' @return character sql 265 | #' 266 | #' @author Jasper Schelfhout 267 | whereSQL <- function( 268 | conn, 269 | table, 270 | column, 271 | operator = 'in', 272 | values = NULL){ 273 | 274 | values <- castToSQLSupportedType(values) 275 | 276 | if(length(values) > 0){ 277 | 278 | singleOperators <- c("like", "not like", "=", "!=", ">", "<", ">=", "<=", "<>") 279 | multiOperators <- c("in", "not in") 280 | if(length(values) == 1){ 281 | operators <- c(singleOperators, multiOperators) 282 | } else { 283 | operators <- multiOperators 284 | } 285 | 286 | if(!operator %in% operators){ 287 | stop(sprintf("Should use on of following operators: %s", 288 | paste(operators, collapse = ", "))) 289 | } 290 | 291 | sql <- sprintf("{`table`}.{`column`} %s ({values*})", 292 | operator) 293 | sql <- glue::glue_sql( 294 | .con = conn, 295 | sql, 296 | table = table, 297 | column = column, 298 | operator = operator, 299 | values = values 300 | ) 301 | } else{ 302 | sql = "TRUE" 303 | } 304 | sql 305 | } 306 | -------------------------------------------------------------------------------- /editbl/R/tbl_df.R: -------------------------------------------------------------------------------- 1 | #' rows_update implementation for data.frame backends. 2 | #' @inheritParams e_rows_update 3 | #' @inherit e_rows_update return details 4 | #' @author Jasper Schelfhout 5 | #' @export 6 | e_rows_update.data.frame <- function(x, y, by = NULL, match = NULL,..., copy = FALSE, in_place = FALSE){ 7 | if(in_place){ 8 | stop("Can not edit in place") 9 | } 10 | 11 | if(is.null(by)){ 12 | by <- colnames(x)[1] 13 | } 14 | 15 | if(is.null(match)){ 16 | match <- list(x = y[by], y = y[by]) 17 | } 18 | 19 | idx <- vctrs::vec_match(match$x, x[by]) 20 | 21 | bad <- which(is.na(idx)) 22 | if (length(bad)) { 23 | stop("Attempting to update missing rows.") 24 | } 25 | 26 | x[idx, names(y)] <- y 27 | 28 | x 29 | } 30 | -------------------------------------------------------------------------------- /editbl/R/tbl_dtplyr_step.R: -------------------------------------------------------------------------------- 1 | #' rows_insert implementation for `data.table` backends. 2 | #' 3 | #' @inherit e_rows_insert 4 | #' 5 | #' @author Jasper Schelfhout 6 | #' @export 7 | e_rows_insert.dtplyr_step <- function(x, y, by = NULL, ..., copy = FALSE, in_place = FALSE){ 8 | x_dt <- data.table::copy(data.table::as.data.table(x)) 9 | y_dt <- data.table::as.data.table(y) 10 | 11 | if(is.null(by)){ 12 | by <- colnames(x)[1] 13 | } 14 | 15 | if(in_place){ 16 | stop("Adding rows by reference to a data.table is not supported yet.") 17 | } else { 18 | result <- dtplyr::lazy_dt(rbind(x_dt,y_dt)) 19 | } 20 | 21 | return(result) 22 | } 23 | 24 | #' rows_delete implementation for data.table backends. 25 | #' @inheritParams dplyr::rows_delete 26 | #' @inherit dplyr::rows_delete return details 27 | #' @export 28 | #' @author Jasper Schelfhout 29 | rows_delete.dtplyr_step <- function(x, y, by = NULL, ..., unmatched, copy = FALSE, in_place = FALSE){ 30 | x_dt <- data.table::copy(data.table::as.data.table(x)) 31 | y_dt <- data.table::as.data.table(y) 32 | 33 | if(!nrow(y_dt)){ 34 | return(x) 35 | } 36 | 37 | if(is.null(by)){ 38 | by <- colnames(x_dt)[1] 39 | } 40 | 41 | if(in_place){ 42 | stop("In place deletes for data.tables are not supported yet. 43 | See issue: https://github.com/Rdatatable/data.table/issues/635") 44 | } 45 | 46 | matches <- unlist(lapply(seq_len(nrow(x_dt)), function(i){ 47 | nrow(merge(x_dt[i,], y_dt, by = by)) > 0 48 | })) 49 | 50 | x_dt <- x_dt[!matches,] 51 | result <- dtplyr::lazy_dt(x_dt) 52 | 53 | result 54 | } 55 | 56 | 57 | 58 | #' rows_update implementation for data.table backends. 59 | #' @inherit e_rows_update 60 | #' @author Jasper Schelfhout 61 | #' @export 62 | e_rows_update.dtplyr_step <- function(x, y, by = NULL, match = NULL,..., copy = FALSE, in_place = FALSE){ 63 | args <- c(as.list(environment()), list(...)) 64 | 65 | x_dt <- data.table::as.data.table(x) 66 | y_dt <- data.table::as.data.table(y) 67 | 68 | if(!nrow(y_dt)){ 69 | return(x) 70 | } 71 | 72 | if(is.null(by)){ 73 | by <- colnames(x_dt)[1] 74 | } 75 | 76 | if(is.null(args$match)){ # Be explicit about argument since otherwhise base::match will used. 77 | match <- list(x = y_dt[,by, with = FALSE, drop = FALSE], 78 | y = y_dt[,by, with = FALSE, drop = FALSE]) 79 | } 80 | 81 | for (i in seq_len(nrow(match$y))){ 82 | yMatch <- match$y[i,, drop = FALSE] 83 | values <- merge(yMatch, y, by = by)[,colnames(y_dt)] 84 | xMatch <- match$x[i,, drop = FALSE] 85 | 86 | xRows <- which(unlist(lapply(seq_len(nrow(x_dt)), function(i){ 87 | nrow(merge(x_dt[i,], xMatch, by = by)) > 0 88 | }))) 89 | 90 | x_dt[xRows,] <- values 91 | } 92 | 93 | result <- dtplyr::lazy_dt(x_dt) 94 | return(invisible(result)) 95 | } 96 | -------------------------------------------------------------------------------- /editbl/R/utils.R: -------------------------------------------------------------------------------- 1 | #' @importFrom tibble as_tibble 2 | #' @import fontawesome 3 | testImports <- NULL 4 | 5 | #' Cast columns to the type of the template 6 | #' 7 | #' @param template `data.frame` 8 | #' @param x `data.frame` 9 | #' 10 | #' @details only affects columns in both the template and x 11 | coerceColumns <- function(template, x){ 12 | for(col in intersect(names(x), names(template))){ 13 | x[[col]] <- coerceValue(x[[col]], template[[col]]) 14 | } 15 | x 16 | } 17 | 18 | #' `DT::coerceValue` with better `POSIXct` support 19 | #' 20 | #' @details Will assume UTC in case no timezone is specified. 21 | #' 22 | #' @inheritParams DT::coerceValue 23 | #' @importFrom DT coerceValue 24 | #' 25 | #' @author Jasper Schelfhout 26 | coerceValue <- function(val,old){ 27 | 28 | if (inherits(old, c('POSIXlt', 'POSIXct'))) { 29 | 30 | # Try a bunch of formats supported by ISO 31 | newVal <- strptime(val, '%Y-%m-%dT%H:%M:%SZ', tz = 'UTC') 32 | if(is.na(newVal)){ 33 | newVal <- strptime(val, '%Y-%m-%d %H:%M:%SZ', tz = 'UTC') 34 | } 35 | if(is.na(newVal)){ 36 | newVal <- strptime(val, '%Y-%m-%d %H:%M:%S', tz = 'UTC') 37 | } 38 | if(is.na(newVal)){ 39 | newVal <- strptime(val, '%Y-%m-%dT%H:%M:%S', tz = 'UTC') 40 | } 41 | 42 | if (inherits(old, 'POSIXlt')) return(newVal) 43 | return(as.POSIXct(newVal)) 44 | } else if(inherits(old, "logical")){ 45 | newVal <- as.logical(val) 46 | } else { 47 | return(DT::coerceValue(val,old)) 48 | } 49 | } 50 | 51 | #' Connect to a database. 52 | #' 53 | #' @details Connects by default to a test SQLite database originally obtained here: 54 | #' [chinook_git](https://github.com/lerocha/chinook-database/blob/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite) 55 | #' 56 | #' @param dbname `character(0)` 57 | #' @param drv database driver 58 | #' @param ... arguments passed to `DBI::dbConnect` 59 | #' @importFrom utils packageName 60 | #' @examples 61 | #' 62 | #' conn <- connectDB() 63 | #' DBI::dbDisconnect(conn) 64 | #' 65 | #' @return database connection 66 | #' @export 67 | connectDB <- function( 68 | dbname = system.file("extdata", "chinook.sqlite", package = utils::packageName()), 69 | drv = RSQLite::SQLite(), 70 | ... 71 | ){ 72 | DBI::dbConnect( 73 | dbname = dbname, 74 | drv = drv, 75 | ... 76 | ) 77 | } 78 | 79 | #' Return first non `NULL` argument 80 | #' @param ... set of arguments 81 | #' 82 | #' @author Jasper Schelfhout 83 | coalesce <- function(...){ 84 | args <- list(...) 85 | result <- NULL 86 | for(arg in args){ 87 | if(!is.null(arg)){ 88 | return(arg) 89 | } 90 | } 91 | return(result) 92 | } 93 | 94 | #' Cast columns in `data.frame` to editable types in datatable 95 | #' @param data `data.frame` 96 | #' @param cols `character` columns to perform casting on. 97 | #' @return `data.frame` with some columns cast to another type 98 | #' 99 | #' @author Jasper Schelfhout 100 | castForDisplay <- function(data, cols = colnames(data)){ 101 | stopifnot(cols %in% colnames(data)) 102 | 103 | for(col in cols){ 104 | if(!inherits(data[[col]], c("integer", "character"))){ 105 | data[[col]] <- as.character(data[[col]]) 106 | } 107 | } 108 | data 109 | } 110 | 111 | #' Cast data to tbl 112 | #' @param data object 113 | #' @return tbl 114 | #' 115 | #' @importFrom dplyr as_tibble is.tbl 116 | #' 117 | #' @author Jasper Schelfhout 118 | castToTbl <- function(data){ 119 | if(dplyr::is.tbl(data)){ 120 | result <- data 121 | } else if(inherits(data, 'data.table')){ 122 | result <- dtplyr::lazy_dt(data) 123 | } else { 124 | result <- dplyr::as_tibble(data) 125 | } 126 | result 127 | } 128 | 129 | #' Cast `tbl` or `data.frame` x to the types of the template 130 | #' 131 | #' @details If template is a `tbl` with database source, convert to an in-memory tibble with same data types instead. 132 | #' @details Rownames might differ or get lost. 133 | #' 134 | #' @param x `data.frame`, `tbl` or `data.table` 135 | #' @param template `data.frame`, `tbl` or `data.table` 136 | #' @return object containing data of x in the class and structure of the template. 137 | #' 138 | #' @author Jasper Schelfhout 139 | castToTemplate <- function(x, template){ 140 | if(!all(base::colnames(x) == base::colnames(template))) 141 | stop("Template and casted tbl should have exactly the same colums") 142 | 143 | rowNames <- attr(x, 'row.names') 144 | 145 | templateRow <- dplyr::collect(dplyr::filter(template, dplyr::row_number()==1)) 146 | # In case the template is empty, templateRow will only contain the format (without content) 147 | # Add an empty row to make sure that the format will be correctly inherited 148 | if (nrow(templateRow) == 0) { 149 | templateRow[1,] <- NA 150 | } 151 | result <- rbind(templateRow,x)[-1,] 152 | 153 | # Tbl doesn't properly support row names 154 | if(!inherits(template, 'tbl')){ 155 | try({rownames(result) <- rowNames}, silent = TRUE) 156 | } 157 | 158 | result 159 | } 160 | 161 | #' Cast tbl to class of template 162 | #' @param tbl `tbl` 163 | #' @param template tabular object like `data.frame` or `data.table` or `tbl`. 164 | #' @return tbl cast to the type of template 165 | #' @importFrom dplyr is.tbl 166 | #' 167 | #' @author Jasper Schelfhout 168 | castFromTbl <- function(tbl, template){ 169 | if(dplyr::is.tbl(template)){ 170 | result <- tbl 171 | } else { 172 | result <- castToTemplate(tbl,template) 173 | } 174 | result 175 | } 176 | 177 | #' Standardize colnames argument to the format of named character vector 178 | #' @inheritParams eDT 179 | #' @importFrom dplyr tbl_vars 180 | #' @return named character vector 181 | #' 182 | #' @author Jasper Schelfhout 183 | standardizeArgument_colnames <- function(colnames, data){ 184 | if(is.null(colnames)){ 185 | result <- as.character(dplyr::tbl_vars(data)) 186 | names(result) <- result 187 | } else if (is.numeric(colnames)) { 188 | result <- dplyr::tbl_vars(data)[colnames] 189 | names(result) = names(colnames) 190 | } else if (is.character(colnames)){ 191 | if(!is.null(names(colnames))){ 192 | result <- colnames 193 | } else { 194 | result <- dplyr::tbl_vars(data)[seq_len(length(colnames))] 195 | names(result) <- colnames 196 | } 197 | } 198 | result 199 | } 200 | 201 | #' Standardized editable argument to be in the form of a list 202 | #' @inheritParams eDT 203 | #' @return list of the form `list(target = foo, ...)` 204 | #' 205 | #' @author Jasper Schelfhout 206 | standardizeArgument_editable <- function( 207 | editable, 208 | data 209 | ){ 210 | if(is.logical(editable)){ 211 | if(editable){ 212 | return(list(target = "cell")) 213 | } else { 214 | return(list(target = "cell", disable = list(columns = seq_len(ncol(data))))) 215 | } 216 | } 217 | 218 | if(is.character(editable)){ 219 | return(list(target = editable)) 220 | } 221 | 222 | if(is.list(editable)){ 223 | return(editable) 224 | } 225 | 226 | stop("editable is not in a standard format.") 227 | } 228 | 229 | #' Replace instances of integer64 with actual NA values instead of weird default 9218868437227407266 230 | #' 231 | #' @details [github issue](https://github.com/Rdatatable/data.table/issues/4561) 232 | #' 233 | #' @param x `data.frame` 234 | #' @return x with `integer64` columns set to `bit64::as.integer64(NA)` 235 | #' 236 | #' @author Jasper Schelfhout 237 | fixInteger64 <- function(x){ 238 | for(column in dplyr::tbl_vars(x)){ 239 | if(inherits(x[[column]], "integer64")){ 240 | x[[column]] <- rep(bit64::as.integer64(NA), nrow(x)) 241 | } 242 | } 243 | x 244 | } 245 | 246 | #' Get types of columns in a tbl 247 | #' @param tbl `tbl` 248 | #' @return named list with types of the colums 249 | #' 250 | #' @importFrom dplyr type_sum collect 251 | #' @importFrom utils head 252 | #' 253 | #' @author Jasper Schelfhout 254 | getColumnTypeSums <- function(tbl){ 255 | tbl %>% 256 | head %>% 257 | collect %>% 258 | lapply(dplyr::type_sum) 259 | } 260 | 261 | #' Generate a custom button for \code{\link{eDT}} 262 | #' 263 | #' @details 264 | #' Combines elements of `shiny::actionButton` and [datatable options](https://datatables.net/reference/option/) 265 | #' 266 | #' @param id `character(1)`, namespaced id 267 | #' @param label `character(1)` 268 | #' @param icon `shiny::icon` 269 | #' @param disabled `logical`. Whether or not the button should start in a disabled state. 270 | #' @return list to be used in `eDT(options = list(buttons = xxx))` 271 | #' 272 | #' @examples 273 | #' if(interactive()){ 274 | #' 275 | #' ui <- eDTOutput("data") 276 | #' server <- function(input,output,session){ 277 | #' b <- customButton('print', label = 'print') 278 | #' eDT_result <- eDT(id = "data", mtcars, options = list(buttons = list("save", b))) 279 | #' observeEvent(input$print,{ 280 | #' print(eDT_result$state()) 281 | #' }) 282 | #' } 283 | #' shinyApp(ui,server) 284 | #' } 285 | #' 286 | #' @author Jasper Schelfhout 287 | #' @export 288 | customButton <- function(id, label, icon = "", disabled = FALSE){ 289 | list( 290 | attr = list( 291 | id = id, 292 | class = "btn btn-default action-button shiny-bound-input", 293 | disabled = disabled 294 | ), 295 | extend = "", 296 | text = paste(as.character(icon), label, sep = " "), 297 | action = DT::JS(sprintf("function (e, dt, node, config ) { 298 | Shiny.setInputValue('%s', true, {priority: 'event'}); 299 | }", id)) 300 | ) 301 | } 302 | 303 | 304 | #' Overwrite default settings with provided settings 305 | #' @param defaults named character vector 306 | #' @param settings named character vector 307 | #' @return named character vector 308 | #' 309 | #' @author Jasper Schelfhout 310 | overwriteDefaults <- function(defaults,settings){ 311 | 312 | if(is.null(settings)){ 313 | return(defaults) 314 | } 315 | 316 | result <- defaults 317 | for(entry in names(settings)){ 318 | result[entry] <- settings[entry] 319 | } 320 | result 321 | } 322 | -------------------------------------------------------------------------------- /editbl/README.md: -------------------------------------------------------------------------------- 1 | # {editbl}: DT extension for CRUD 2 | 3 | [![CRAN status](https://www.r-pkg.org/badges/version/editbl)](https://cran.r-project.org/package=editbl) 4 | [![R-CMD-check](https://github.com/openanalytics/editbl/actions/workflows/check-standard.yaml/badge.svg)](https://github.com/openanalytics/editbl/actions/workflows/check-standard.yaml) 5 | [![codecov](https://codecov.io/gh/openanalytics/editbl/branch/main/graph/badge.svg)](https://app.codecov.io/gh/openanalytics/editbl) 6 | ![CRAN downloads](https://cranlogs.r-pkg.org/badges/editbl) 7 | 8 | `editbl` ('*edit [tibble](https://cran.r-project.org/package=tibble)*') allows you to modify tables in a spreadsheet-like fashion. Not just in-memory `data.frame` objects, but also 9 | data living in a database. 10 | 11 | ## Installation 12 | 13 | * From CRAN: 14 | 15 | ``` 16 | install.packages('editbl') 17 | ``` 18 | 19 | * Latest development version: 20 | 21 | ``` 22 | remotes::install_github("https://github.com/openanalytics/editbl", ref = "main", subdir = "editbl") 23 | ``` 24 | 25 | ## Get started 26 | 27 | Choose a dataset of your liking and use `eDT` to interactively explore and modify it! 28 | 29 | ``` 30 | modifiedData <- editbl::eDT(mtcars) 31 | print(modifiedData) 32 | ``` 33 | 34 | 35 | Run some demo apps 36 | 37 | ``` 38 | editbl::runDemoApp() 39 | ``` 40 | ![](https://github.com/openanalytics/editbl/blob/main/editbl.gif?raw=true) 41 | 42 | More introductory examples can be found [here](https://github.com/openanalytics/editbl/blob/main/editbl/R/demoApp.R). 43 | Advanced examples can be found in the [vignettes](https://github.com/openanalytics/editbl/tree/main/editbl/vignettes). 44 | 45 | 46 | ## Features 47 | 48 | ### Overview of main features 49 | 50 | * Supporting multiple backends and in-place editing 51 | * Customizable (lightweight [DT](https://CRAN.R-project.org/package=DT) wrapper) 52 | * Easy integration in [shiny](https://cran.r-project.org/package=shiny) apps 53 | * Undo/redo button 54 | * Copy rows 55 | * Drag cells 56 | * No need to have all data in-memory 57 | * Tackles challenges such as enforcing foreign keys and hiding of surrogate keys 58 | * Transactional commits (currently for `tbl_dbi` class and non in-place editing) 59 | * Default values for new rows (UUID's, 'current date', 'inserted by', ...) 60 | * Possible to set row level security 61 | 62 | ### Constraints and normalized tables 63 | 64 | Sometimes you want to restrict certain columns of your table to only contain specific values. 65 | Many of these restrictions would be implemented at database level through the use of foreign keys to other tables. 66 | 67 | `editbl` allows you to specify similar rules through the use of `foreignTbls` as an argument to `eDT()`. 68 | Note that you can additionally hide surrogate keys by the use of `naturalKey` and `columnDefs` if you wish to. 69 | 70 | ``` 71 | a <- tibble::tibble( 72 | first_name = c("Albert","Donald","Mickey"), 73 | last_name_id = c(1,2,2) 74 | ) 75 | 76 | b <- foreignTbl( 77 | a, 78 | tibble::tibble( 79 | last_name = c("Einstein", "Duck", "Mouse"), 80 | last_name_id = c(1,2,3) 81 | ), 82 | by = "last_name_id", 83 | naturalKey = "last_name" 84 | ) 85 | 86 | eDT(a, 87 | foreignTbls = list(b), 88 | options = list(columnDefs = list(list(visible=FALSE, targets="last_name_id"))) 89 | ) 90 | ``` 91 | 92 | ### Support for different backends 93 | 94 | `dplyr` code is used for all needed data manipulations and it is recommended to pass on your data as a `tbl`. 95 | This allows editbl to support multiple backends through the usage of other packages like `dtplyr`, `dbplyr` etc. 96 | 97 | In case you pass on other tabular objects like `data.frame` or `data.table` the function will internally automatically 98 | cast back and forth to `tbl`. Small side effects may occur because of this (like loosing rownames), so it might be better 99 | to cast yourself to `tbl` explicitly first. 100 | 101 | ``` 102 | # tibble support 103 | modifiedData <- editbl::eDT(tibble::as_tibble(mtcars)) 104 | 105 | # data.frame support 106 | modifiedData <- editbl::eDT(mtcars) 107 | 108 | # data.table support 109 | modifiedData <- editbl::eDT(data.table::data.table(mtcars)) 110 | 111 | # database support 112 | tmpFile <- tempfile(fileext = ".sqlite") 113 | file.copy(system.file("extdata", "chinook.sqlite", package = 'editbl'), tmpFile) 114 | conn <- editbl::connectDB(dbname = tmpFile) 115 | modifiedData <- editbl::eDT(dplyr::tbl(conn, "Artist"), in_place = TRUE) 116 | DBI::dbDisconnect(conn) 117 | unlink(tmpFile) 118 | 119 | ``` 120 | 121 | Note that there are some custom methods in the package itself for `rows_update` / `rows_delete` / `rows_insert`. The goal 122 | would be to fully rely on `dplyr` once these functions are not experimental anymore and support all needed requirements. 123 | These functions also explain the high amount of 'suggested' packages, while the core functionality of `editbl` has few 124 | dependencies. 125 | 126 | ## Switching from DT 127 | 128 | Let's say you already use `DT::datatable()` to display your data, but want to switch to `editbl::eDT()` to be able to edit it. Would this be a lot of effort? No! 129 | In fact, `eDT()` accepts the exact same arguments. So it is almost as easy as replacing the functions and you are done. 130 | Should you run into problems take a look [here](https://github.com/openanalytics/editbl/blob/main/editbl/vignettes/howto_switch_from_DT.rmd) for some pointers to look out for. 131 | 132 | ## Notes 133 | 134 | * https://github.com/tidyverse/dtplyr/issues/260 might cause errors / warnings when using `eDT` with `dtplyr`. If possible convert to normal tibble first. 135 | * `editbl` assumes that **all rows in your table are unique**. This assumption is the key (ba dum tss) to allow for only having the data partially in memory. 136 | * `editbl` does not attempt to detect/give notifications on concurrent updates by other users to the same data, nor does it 'lock' the rows you are updating. 137 | It just sends its updates to the backend by matching on the keys of a row. If other users have in the meantime made conflicting adjustments, 138 | the changes you made might not be executed correctly or errors might be thrown. 139 | 140 | ## General future goals for this package 141 | 142 | * Full `dplyr` compatibility so support for different backends is easily facilitated. Now there are 2 methods (`e_rows_update`, `e_rows_insert`) that need to be implemented to support a new backend. 143 | * Full `DT` compatibility, including all extensions. 144 | * Better editing / display options for time values. E.g. control over timezone and format of display / storage + nicer input forms. 145 | * Any addition that supports the concept of editing data as flexible/easy as possible while respecting backend schema's and constraints. 146 | 147 | ## References 148 | 149 | ### Alternatives 150 | 151 | These are other popular CRUD packages in R. 152 | Depending on your needs, they might be better alternatives. 153 | 154 | [**DataEditR**](https://cran.r-project.org/package=DataEditR) 155 | 156 | * Rstudio plugin 157 | * Really flexible excel-like feeling 158 | * Can only edit in-memory tables. Harder to support databases etc. 159 | 160 | [**editData**](https://cran.r-project.org/package=editData) 161 | 162 | * Rstudio plugin 163 | * Nice features in terms of editing (pop-ups, more buttons,...) 164 | * Can only edit in-memory tables. Harder to support databases etc. 165 | 166 | [**Editor**](https://editor.datatables.net/) 167 | 168 | * Premium datatable extension allowing for editing data. 169 | 170 | [**DT-Editor**](https://github.com/jienagu/DT-Editor) 171 | 172 | * data.table focused 173 | 174 | [**DTedit**](https://github.com/jbryer/DTedit) 175 | 176 | * `DT` extension 177 | * Very customizable (own callbacks) 178 | * Few dependencies 179 | 180 | ### Additional links: 181 | [CRAN `DT`](https://cran.r-project.org/package=DT) 182 | 183 | [CRAN `tibble`](https://cran.r-project.org/package=tibble) 184 | 185 | [Blogpost spreadsheets vs robust backends](https://www.openanalytics.eu/blog/2023/02/12/spreadsheets-backends-love/) 186 | 187 | [Blogpost buttons in DT](https://thatdatatho.com/adding-action-buttons-in-rows-of-dt-data-table-in-r-shiny/) 188 | 189 | [Blogpost shiny vs excel](https://appsilon.com/forget-about-excel-use-r-shiny-packages-instead/) 190 | 191 | [Generic CRUD application](https://github.com/garrylachman/ElectroCRUD) 192 | 193 | [Example SQLite database](https://www.sqlitetutorial.net/sqlite-sample-database/) 194 | -------------------------------------------------------------------------------- /editbl/inst/NEWS: -------------------------------------------------------------------------------- 1 | 1.3.0 2 | o FIX: castToTemplate removed first row of adapted data in case the template was empty. 3 | o FEAT: Add a clone button for each row. 4 | 1.2.0 5 | o FIX: State variable of eDT output does not contain deleted rows anymore. 6 | o FEAT: Allow to order the columns of the full table (including columns from foreignTbls). ([issue](https://github.com/openanalytics/editbl/issues/5)) 7 | 1.1.0 8 | o Allow to block edits and deletes with row-specific logic. 9 | o More performance since buttons get generated on row basis instead of dataset basis. 10 | o Adjustable renamed hidden utility columns ('_editbl_identity', '_editbl_deleted', '_editbl_status', '_editbl_buttons'). Prevents name clashes. 11 | 1.0.5 12 | o Use screenshots in vignettes for speed increase and to solve CRAN checks. 13 | 1.0.4 14 | o Fixed bug when using non-English locale ([issue](https://github.com/openanalytics/editbl/issues/3)) 15 | o Added vignettes + revised documentation 16 | o Prevent greyout when dragging value with increment ([issue](https://github.com/openanalytics/editbl/issues/4)). 17 | 1.0.3 18 | o FIX: inputUI argument was not working, now passing on the selected row. 19 | o FIX: Evaluate defaults argument for each new row 20 | o FIX: Use correct column types for in_place `rows_delete` on databases. 21 | o FIX: Row dragging during an active filter / search now modifies the correct cells. 22 | 1.0.2 23 | o Fix compatibility with dbplyr 2.4.0. Adjust method to retrieve table identifier. 24 | 1.0.1 25 | o Export `rows_insert.default()` and `rows_update.default()` 26 | 1.0.0 27 | o Use `dbplyr::rows_delete.tbl_dbi` instead of custom implementation. (https://github.com/openanalytics/editbl/issues/1) 28 | o BREAKING: rename custom `rows_insert` and `rows_update` functions to `e_rows_insert` and `e_rows_update`. This to avoid 29 | conflicts with other packages. The default for both functions will still be `dplyr::rows_insert` and `dplyr::rows_update`. 30 | (https://github.com/openanalytics/editbl/issues/1). Reason for not using the dplyr functions directly is because currently `rows_update` 31 | does not allow updating of the keys (see `match` argument). Secondly, `rows_insert.tbl_dbi` does not complain about inserting duplicated rows. 32 | o BREAKING: do not provide direct connection to in-package database in runDemoApp(). It's cleaner to work on a copy of the file. 33 | o Support for `is.atomic(NULL) = FALSE` (https://stat.ethz.ch/pipermail/r-devel/2023-September/082892.html). 34 | 0.9.6 35 | o Adjustments in examples and documenation to meet CRAN standards 36 | 0.9.3 37 | o Fixes in documentation to get CRAN standard 38 | 0.9.2 39 | o Documentation / test adaptations to get CRAN standard. 40 | o Exporting `selectInputDT` module since it might be convenient for users of the package. 41 | 0.9.0 42 | o Hide rownames for selectInutDT in a cleaner way 43 | o Disable button column clicking id-specific instead of page-wide css 44 | o Add tests 45 | o More documentation 46 | o Remove breaking change warning introduced at 0.8.3 47 | 0.8.5 48 | o More explicit code instead of string evaluation 49 | o Fix reactivity in case of identical edits 50 | o Check classes of default values for columns 51 | 0.8.3 52 | o BREAKING: eDT returns list instead of reactive. Adjust to eDT()$result for old behavior. 53 | o eDT now returns the state of the unsaved data as eDT()$state 54 | o Add pagination selector by default. Is closer to native DT behavior 55 | o Bugfix: dragging rows used to only work on the first page, now works for all pages. 56 | 0.7.4 57 | o Make buttons part of datatable buttons extension. Allows for easy configuration and nicer display. 58 | 0.7.3 59 | o 'defaults' argument evaluated in calling environment for each now row. Allows for example id generation. 60 | o Support for editable TRUE / FALSE 61 | 0.7.2 62 | o Add 'defaults' argument to eDT 63 | 0.7.1 64 | o Bugfix selectInputDT() 65 | o Add `allowNew` argument to foreignTbl to support 'suggestions' instead of 'restrictions'. 66 | 0.7.0 67 | o Support multi-column natural key editing by table selection 68 | 0.6.6 69 | o Disable modal editing for non-editable columns. 70 | o Fix modal for NA logical values by using selectInput instead of checkbox. 71 | 0.6.5 72 | o checkForeignTbls() allow empty rows by default 73 | 0.6.4 74 | o Enforce fontawesome >= 0.4.0 75 | 0.6.3 76 | o fix reactivity problem when switching data 77 | o support editable = FALSE 78 | 0.6.2 79 | o use getNonNaturalKeys() to block editing of deducted columns 80 | 0.6.1 81 | o increase package test coverage + various fixes 82 | o checkForeignTbls() more verbose 83 | o error for utility column name clashes 84 | 0.5.5 85 | o fix integer64 NA values 86 | 0.5.2 87 | o Make input modal more integerated (follow colnames argument, hide hidden / non-editable columns) 88 | 0.5.1 89 | o shinyjs disabling of buttons 90 | o bugfixes POSIXct 91 | 0.5.0 92 | o automatic casting to and from `tbl` 93 | o edit buttons with modal 94 | 0.4.0 95 | o dev version 96 | -------------------------------------------------------------------------------- /editbl/inst/extdata/artists.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/inst/extdata/artists.xlsx -------------------------------------------------------------------------------- /editbl/inst/extdata/chinook.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/inst/extdata/chinook.sqlite -------------------------------------------------------------------------------- /editbl/man/addButtons.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{addButtons} 4 | \alias{addButtons} 5 | \title{Add modification buttons as a column} 6 | \usage{ 7 | addButtons( 8 | df, 9 | columnName, 10 | ns, 11 | iCol = "i", 12 | canEditRow = TRUE, 13 | canDeleteRow = TRUE, 14 | canCloneRow = TRUE, 15 | statusCol = "_editbl_status" 16 | ) 17 | } 18 | \arguments{ 19 | \item{df}{\code{data.frame}} 20 | 21 | \item{columnName}{\code{character(1)}} 22 | 23 | \item{ns}{namespace function} 24 | 25 | \item{iCol}{\code{character(1)} name of column containing a unique identifier.} 26 | 27 | \item{canEditRow}{can be either of the following: 28 | \itemize{ 29 | \item \code{logical}, e.g. TRUE or FALSE 30 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 31 | }} 32 | 33 | \item{canDeleteRow}{can be either of the following: 34 | \itemize{ 35 | \item \code{logical}, e.g. TRUE or FALSE 36 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 37 | }} 38 | 39 | \item{canCloneRow}{can be either of the following: 40 | \itemize{ 41 | \item \code{logical}, e.g. TRUE or FALSE 42 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 43 | }} 44 | 45 | \item{statusCol}{\code{character(1)} name of column with general status (e.g. modified or not). 46 | if \code{NULL}, the data is interpreted as 'unmodified'.} 47 | } 48 | \value{ 49 | df with extra column containing buttons 50 | } 51 | \description{ 52 | Add modification buttons as a column 53 | } 54 | \author{ 55 | Jasper Schelfhout 56 | } 57 | -------------------------------------------------------------------------------- /editbl/man/beginTransaction.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{beginTransaction} 4 | \alias{beginTransaction} 5 | \title{Start a transaction for a tibble} 6 | \usage{ 7 | beginTransaction(tbl) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | } 12 | \description{ 13 | Start a transaction for a tibble 14 | } 15 | \author{ 16 | Jasper Schelfhout 17 | } 18 | -------------------------------------------------------------------------------- /editbl/man/canXXXRowTemplate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{canXXXRowTemplate} 4 | \alias{canXXXRowTemplate} 5 | \title{Re-usable documentation} 6 | \usage{ 7 | canXXXRowTemplate(canEditRow, canCloneRow, canDeleteRow) 8 | } 9 | \arguments{ 10 | \item{canEditRow}{can be either of the following: 11 | \itemize{ 12 | \item \code{logical}, e.g. TRUE or FALSE 13 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 14 | }} 15 | 16 | \item{canCloneRow}{can be either of the following: 17 | \itemize{ 18 | \item \code{logical}, e.g. TRUE or FALSE 19 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 20 | }} 21 | 22 | \item{canDeleteRow}{can be either of the following: 23 | \itemize{ 24 | \item \code{logical}, e.g. TRUE or FALSE 25 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 26 | }} 27 | } 28 | \description{ 29 | Re-usable documentation 30 | } 31 | -------------------------------------------------------------------------------- /editbl/man/castForDisplay.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{castForDisplay} 4 | \alias{castForDisplay} 5 | \title{Cast columns in \code{data.frame} to editable types in datatable} 6 | \usage{ 7 | castForDisplay(data, cols = colnames(data)) 8 | } 9 | \arguments{ 10 | \item{data}{\code{data.frame}} 11 | 12 | \item{cols}{\code{character} columns to perform casting on.} 13 | } 14 | \value{ 15 | \code{data.frame} with some columns cast to another type 16 | } 17 | \description{ 18 | Cast columns in \code{data.frame} to editable types in datatable 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/castFromTbl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{castFromTbl} 4 | \alias{castFromTbl} 5 | \title{Cast tbl to class of template} 6 | \usage{ 7 | castFromTbl(tbl, template) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | 12 | \item{template}{tabular object like \code{data.frame} or \code{data.table} or \code{tbl}.} 13 | } 14 | \value{ 15 | tbl cast to the type of template 16 | } 17 | \description{ 18 | Cast tbl to class of template 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/castToFactor.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/foreignTbl.R 3 | \name{castToFactor} 4 | \alias{castToFactor} 5 | \title{Cast all columns that exist in a foreignTbl to factor} 6 | \usage{ 7 | castToFactor(data, foreignTbls) 8 | } 9 | \arguments{ 10 | \item{data}{\code{data.frame}} 11 | 12 | \item{foreignTbls}{list of foreign tbls as created by \code{\link{foreignTbl}}} 13 | } 14 | \value{ 15 | data.frame 16 | } 17 | \description{ 18 | Cast all columns that exist in a foreignTbl to factor 19 | } 20 | \details{ 21 | Can be used to fixate possible options when editing. 22 | } 23 | \author{ 24 | Jasper Schelfhout 25 | } 26 | -------------------------------------------------------------------------------- /editbl/man/castToSQLSupportedType.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{castToSQLSupportedType} 4 | \alias{castToSQLSupportedType} 5 | \title{Cast the data type to something supported by SQL.} 6 | \usage{ 7 | castToSQLSupportedType(x) 8 | } 9 | \arguments{ 10 | \item{x}{single value or vector of values} 11 | } 12 | \value{ 13 | x, possibly cast to different type 14 | } 15 | \description{ 16 | Cast the data type to something supported by SQL. 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/castToTbl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{castToTbl} 4 | \alias{castToTbl} 5 | \title{Cast data to tbl} 6 | \usage{ 7 | castToTbl(data) 8 | } 9 | \arguments{ 10 | \item{data}{object} 11 | } 12 | \value{ 13 | tbl 14 | } 15 | \description{ 16 | Cast data to tbl 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/castToTemplate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{castToTemplate} 4 | \alias{castToTemplate} 5 | \title{Cast \code{tbl} or \code{data.frame} x to the types of the template} 6 | \usage{ 7 | castToTemplate(x, template) 8 | } 9 | \arguments{ 10 | \item{x}{\code{data.frame}, \code{tbl} or \code{data.table}} 11 | 12 | \item{template}{\code{data.frame}, \code{tbl} or \code{data.table}} 13 | } 14 | \value{ 15 | object containing data of x in the class and structure of the template. 16 | } 17 | \description{ 18 | Cast \code{tbl} or \code{data.frame} x to the types of the template 19 | } 20 | \details{ 21 | If template is a \code{tbl} with database source, convert to an in-memory tibble with same data types instead. 22 | 23 | Rownames might differ or get lost. 24 | } 25 | \author{ 26 | Jasper Schelfhout 27 | } 28 | -------------------------------------------------------------------------------- /editbl/man/checkForeignTbls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/foreignTbl.R 3 | \name{checkForeignTbls} 4 | \alias{checkForeignTbls} 5 | \title{Check if all rows in tbl fufill \code{foreignTbl} constraints} 6 | \usage{ 7 | checkForeignTbls(tbl, foreignTbls) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | 12 | \item{foreignTbls}{list of foreign tbls as created by \code{\link{foreignTbl}}} 13 | } 14 | \value{ 15 | \code{logical} stating if tbl fufills all constraints imposed by all foreign tbls. 16 | } 17 | \description{ 18 | Check if all rows in tbl fufill \code{foreignTbl} constraints 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/coalesce.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{coalesce} 4 | \alias{coalesce} 5 | \title{Return first non \code{NULL} argument} 6 | \usage{ 7 | coalesce(...) 8 | } 9 | \arguments{ 10 | \item{...}{set of arguments} 11 | } 12 | \description{ 13 | Return first non \code{NULL} argument 14 | } 15 | \author{ 16 | Jasper Schelfhout 17 | } 18 | -------------------------------------------------------------------------------- /editbl/man/coerceColumns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{coerceColumns} 4 | \alias{coerceColumns} 5 | \title{Cast columns to the type of the template} 6 | \usage{ 7 | coerceColumns(template, x) 8 | } 9 | \arguments{ 10 | \item{template}{\code{data.frame}} 11 | 12 | \item{x}{\code{data.frame}} 13 | } 14 | \description{ 15 | Cast columns to the type of the template 16 | } 17 | \details{ 18 | only affects columns in both the template and x 19 | } 20 | -------------------------------------------------------------------------------- /editbl/man/coerceValue.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{coerceValue} 4 | \alias{coerceValue} 5 | \title{\code{DT::coerceValue} with better \code{POSIXct} support} 6 | \usage{ 7 | coerceValue(val, old) 8 | } 9 | \arguments{ 10 | \item{val}{A character string.} 11 | 12 | \item{old}{An old value, whose type is the target type of \code{val}.} 13 | } 14 | \description{ 15 | \code{DT::coerceValue} with better \code{POSIXct} support 16 | } 17 | \details{ 18 | Will assume UTC in case no timezone is specified. 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/commitTransaction.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{commitTransaction} 4 | \alias{commitTransaction} 5 | \title{Start a transaction for a tibble} 6 | \usage{ 7 | commitTransaction(tbl) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | } 12 | \description{ 13 | Start a transaction for a tibble 14 | } 15 | \author{ 16 | Jasper Schelfhout 17 | } 18 | -------------------------------------------------------------------------------- /editbl/man/connectDB.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{connectDB} 4 | \alias{connectDB} 5 | \title{Connect to a database.} 6 | \usage{ 7 | connectDB( 8 | dbname = system.file("extdata", "chinook.sqlite", package = utils::packageName()), 9 | drv = RSQLite::SQLite(), 10 | ... 11 | ) 12 | } 13 | \arguments{ 14 | \item{dbname}{\code{character(0)}} 15 | 16 | \item{drv}{database driver} 17 | 18 | \item{...}{arguments passed to \code{DBI::dbConnect}} 19 | } 20 | \value{ 21 | database connection 22 | } 23 | \description{ 24 | Connect to a database. 25 | } 26 | \details{ 27 | Connects by default to a test SQLite database originally obtained here: 28 | \href{https://github.com/lerocha/chinook-database/blob/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite}{chinook_git} 29 | } 30 | \examples{ 31 | 32 | conn <- connectDB() 33 | DBI::dbDisconnect(conn) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /editbl/man/createButtons.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createButtons} 4 | \alias{createButtons} 5 | \title{Create buttons to modify the row.} 6 | \usage{ 7 | createButtons( 8 | row, 9 | suffix, 10 | ns, 11 | canEditRow = TRUE, 12 | canDeleteRow = TRUE, 13 | canCloneRow = TRUE, 14 | statusCol = "_editbl_status" 15 | ) 16 | } 17 | \arguments{ 18 | \item{row}{\code{tibble} with single row} 19 | 20 | \item{suffix}{\code{character(1)}} 21 | 22 | \item{ns}{\code{character(1)} namespace} 23 | 24 | \item{canEditRow}{can be either of the following: 25 | \itemize{ 26 | \item \code{logical}, e.g. TRUE or FALSE 27 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 28 | }} 29 | 30 | \item{canDeleteRow}{can be either of the following: 31 | \itemize{ 32 | \item \code{logical}, e.g. TRUE or FALSE 33 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 34 | }} 35 | 36 | \item{canCloneRow}{can be either of the following: 37 | \itemize{ 38 | \item \code{logical}, e.g. TRUE or FALSE 39 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 40 | }} 41 | 42 | \item{statusCol}{\code{character(1)} name of column with general status (e.g. modified or not). 43 | if \code{NULL}, the data is interpreted as 'unmodified'.} 44 | } 45 | \value{ 46 | \code{character(1)} HTML 47 | } 48 | \description{ 49 | Create buttons to modify the row. 50 | } 51 | \details{ 52 | buttons used per row in the app. 53 | } 54 | -------------------------------------------------------------------------------- /editbl/man/createCloneButtonHTML.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createCloneButtonHTML} 4 | \alias{createCloneButtonHTML} 5 | \title{Generate HTML for an in-row clone button} 6 | \usage{ 7 | createCloneButtonHTML(ns = "\%1$s", suffix = "\%2$s", disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{ns}{\code{character(1)} namespace} 11 | 12 | \item{suffix}{\code{character(1)} id of the row} 13 | 14 | \item{disabled}{\code{logical(1)} wether or not the button has to be disabled} 15 | } 16 | \value{ 17 | \code{character(1)} HTML 18 | } 19 | \description{ 20 | Generate HTML for an in-row clone button 21 | } 22 | -------------------------------------------------------------------------------- /editbl/man/createCloneButtonHTML_shiny.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createCloneButtonHTML_shiny} 4 | \alias{createCloneButtonHTML_shiny} 5 | \title{Helper function to write HTML} 6 | \usage{ 7 | createCloneButtonHTML_shiny(ns = "\%1$s", suffix = "\%2$s", disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{ns}{\code{character(1)} namespace} 11 | 12 | \item{suffix}{\code{character(1)} id of the row} 13 | 14 | \item{disabled}{\code{logical(1)} wether or not the button has to be disabled} 15 | } 16 | \description{ 17 | Helper function to write HTML 18 | } 19 | \details{ 20 | only to be used interactively. sprintf() implementation 21 | is faster. 22 | } 23 | \seealso{ 24 | createCloneButtonHTML 25 | } 26 | -------------------------------------------------------------------------------- /editbl/man/createDeleteButtonHTML.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createDeleteButtonHTML} 4 | \alias{createDeleteButtonHTML} 5 | \title{Generate HTML for an in-row delete button} 6 | \usage{ 7 | createDeleteButtonHTML(ns = "\%1$s", suffix = "\%2$s", disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{ns}{\code{character(1)} namespace} 11 | 12 | \item{suffix}{\code{character(1)} id of the row} 13 | 14 | \item{disabled}{\code{logical(1)} wether or not the button has to be disabled} 15 | } 16 | \value{ 17 | \code{character(1)} HTML 18 | } 19 | \description{ 20 | Generate HTML for an in-row delete button 21 | } 22 | -------------------------------------------------------------------------------- /editbl/man/createDeleteButtonHTML_shiny.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createDeleteButtonHTML_shiny} 4 | \alias{createDeleteButtonHTML_shiny} 5 | \title{Helper function to write HTML} 6 | \usage{ 7 | createDeleteButtonHTML_shiny(ns = "\%1$s", suffix = "\%2$s", disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{ns}{\code{character(1)} namespace} 11 | 12 | \item{suffix}{\code{character(1)} id of the row} 13 | 14 | \item{disabled}{\code{logical(1)} wether or not the button has to be disabled} 15 | } 16 | \description{ 17 | Helper function to write HTML 18 | } 19 | \details{ 20 | only to be used interactively. sprintf() implementation 21 | is faster. 22 | } 23 | \seealso{ 24 | createEditButtonHTML 25 | } 26 | -------------------------------------------------------------------------------- /editbl/man/createEditButtonHTML.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createEditButtonHTML} 4 | \alias{createEditButtonHTML} 5 | \title{Generate HTML for an in-row edit button} 6 | \usage{ 7 | createEditButtonHTML(ns, suffix, disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{ns}{\code{character(1)} namespace} 11 | 12 | \item{suffix}{\code{character(1)} id of the row} 13 | 14 | \item{disabled}{\code{logical(1)} wether or not the button has to be disabled} 15 | } 16 | \value{ 17 | \code{character(1)} HTML 18 | } 19 | \description{ 20 | Generate HTML for an in-row edit button 21 | } 22 | -------------------------------------------------------------------------------- /editbl/man/createEditButtonHTML_shiny.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{createEditButtonHTML_shiny} 4 | \alias{createEditButtonHTML_shiny} 5 | \title{Helper function to write HTML} 6 | \usage{ 7 | createEditButtonHTML_shiny(ns = "\%1$s", suffix = "\%2$s", disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{ns}{\code{character(1)} namespace} 11 | 12 | \item{suffix}{\code{character(1)} id of the row} 13 | 14 | \item{disabled}{\code{logical(1)} wether or not the button has to be disabled} 15 | } 16 | \description{ 17 | Helper function to write HTML 18 | } 19 | \details{ 20 | only to be used interactively. sprintf() implementation 21 | is faster. 22 | } 23 | \seealso{ 24 | createEditButtonHTML 25 | } 26 | -------------------------------------------------------------------------------- /editbl/man/customButton.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{customButton} 4 | \alias{customButton} 5 | \title{Generate a custom button for \code{\link{eDT}}} 6 | \usage{ 7 | customButton(id, label, icon = "", disabled = FALSE) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}, namespaced id} 11 | 12 | \item{label}{\code{character(1)}} 13 | 14 | \item{icon}{\code{shiny::icon}} 15 | 16 | \item{disabled}{\code{logical}. Whether or not the button should start in a disabled state.} 17 | } 18 | \value{ 19 | list to be used in \code{eDT(options = list(buttons = xxx))} 20 | } 21 | \description{ 22 | Generate a custom button for \code{\link{eDT}} 23 | } 24 | \details{ 25 | Combines elements of \code{shiny::actionButton} and \href{https://datatables.net/reference/option/}{datatable options} 26 | } 27 | \examples{ 28 | if(interactive()){ 29 | 30 | ui <- eDTOutput("data") 31 | server <- function(input,output,session){ 32 | b <- customButton('print', label = 'print') 33 | eDT_result <- eDT(id = "data", mtcars, options = list(buttons = list("save", b))) 34 | observeEvent(input$print,{ 35 | print(eDT_result$state()) 36 | }) 37 | } 38 | shinyApp(ui,server) 39 | } 40 | 41 | } 42 | \author{ 43 | Jasper Schelfhout 44 | } 45 | -------------------------------------------------------------------------------- /editbl/man/demoServer_DB.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{demoServer_DB} 4 | \alias{demoServer_DB} 5 | \title{Server of the DB demo app} 6 | \usage{ 7 | demoServer_DB(id, conn) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | 12 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 13 | } 14 | \value{ 15 | NULL, just executes the module server. 16 | } 17 | \description{ 18 | Server of the DB demo app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/demoServer_custom.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{demoServer_custom} 4 | \alias{demoServer_custom} 5 | \title{Server of the mtcars demo app} 6 | \usage{ 7 | demoServer_custom(id, x) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | 12 | \item{x}{\code{tbl}} 13 | } 14 | \value{ 15 | NULL, just executes the module server. 16 | } 17 | \description{ 18 | Server of the mtcars demo app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/demoServer_mtcars.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{demoServer_mtcars} 4 | \alias{demoServer_mtcars} 5 | \title{Server of the mtcars demo app} 6 | \usage{ 7 | demoServer_mtcars(id) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | } 12 | \value{ 13 | NULL, just executes the module server. 14 | } 15 | \description{ 16 | Server of the mtcars demo app 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/demoUI_DB.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{demoUI_DB} 4 | \alias{demoUI_DB} 5 | \title{UI of the DB demo app} 6 | \usage{ 7 | demoUI_DB(id, conn) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | 12 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 13 | } 14 | \value{ 15 | HTML 16 | } 17 | \description{ 18 | UI of the DB demo app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/demoUI_custom.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{demoUI_custom} 4 | \alias{demoUI_custom} 5 | \title{UI of the demo mtcars app} 6 | \usage{ 7 | demoUI_custom(id) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | } 12 | \value{ 13 | HTML 14 | } 15 | \description{ 16 | UI of the demo mtcars app 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/demoUI_mtcars.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{demoUI_mtcars} 4 | \alias{demoUI_mtcars} 5 | \title{UI of the demo mtcars app} 6 | \usage{ 7 | demoUI_mtcars(id) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | } 12 | \value{ 13 | HTML 14 | } 15 | \description{ 16 | UI of the demo mtcars app 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/devServer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/devApp.R 3 | \name{devServer} 4 | \alias{devServer} 5 | \title{Server of the development app} 6 | \usage{ 7 | devServer(id, conn) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | 12 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 13 | } 14 | \value{ 15 | NULL, just executes the module server. 16 | } 17 | \description{ 18 | Server of the development app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/devUI.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/devApp.R 3 | \name{devUI} 4 | \alias{devUI} 5 | \title{UI of the development app} 6 | \usage{ 7 | devUI(id, conn) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | 12 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 13 | } 14 | \value{ 15 | HTML 16 | } 17 | \description{ 18 | UI of the development app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/disableDoubleClickButtonCss.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{disableDoubleClickButtonCss} 4 | \alias{disableDoubleClickButtonCss} 5 | \title{Function to generate CSS to disable clicking events on a column} 6 | \usage{ 7 | disableDoubleClickButtonCss(id) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)} namespaced id of the datatable} 11 | } 12 | \value{ 13 | \code{character} CSS 14 | } 15 | \description{ 16 | Function to generate CSS to disable clicking events on a column 17 | } 18 | \details{ 19 | \url{https://stackoverflow.com/questions/60406027/how-to-disable-double-click-reactivity-for-specific-columns-in-r-datatable} 20 | 21 | \url{https://stackoverflow.com/questions/75406546/apply-css-styling-to-a-single-dt-datatable} 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/eDTOutput.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{eDTOutput} 4 | \alias{eDTOutput} 5 | \title{UI part of \code{\link{eDT}}} 6 | \usage{ 7 | eDTOutput(id, ...) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)}} 11 | 12 | \item{...}{arguments passed to \code{\link[DT]{DTOutput}}} 13 | } 14 | \value{ 15 | HTML 16 | } 17 | \description{ 18 | UI part of \code{\link{eDT}} 19 | } 20 | \details{ 21 | Works exactly like \code{\link[DT]{DTOutput}} apart from the fact that instead of the \code{outputId} 22 | argument, \code{id} is requested. Reason being that this function is a UI to a shiny module. 23 | This means that the datatable can be found under the id \code{'{namespace}-{id}-DT'} instead of \code{'{namespace}-{outputId}'}. 24 | 25 | Also some minor CSS and javascript is executed for functional puposes. 26 | } 27 | \examples{ 28 | ## Only run this example in interactive R sessions 29 | if(interactive()){ 30 | # tibble support 31 | modifiedData <- editbl::eDT(tibble::as_tibble(mtcars)) 32 | 33 | # data.table support 34 | modifiedData <- editbl::eDT(dtplyr::lazy_dt(data.table::data.table(mtcars))) 35 | 36 | # database support 37 | tmpFile <- tempfile(fileext = ".sqlite") 38 | file.copy(system.file("extdata", "chinook.sqlite", package = 'editbl'), tmpFile) 39 | 40 | conn <- editbl::connectDB(dbname = tmpFile) 41 | modifiedData <- editbl::eDT(dplyr::tbl(conn, "Artist"), in_place = TRUE) 42 | DBI::dbDisconnect(conn) 43 | 44 | unlink(tmpFile) 45 | 46 | # Within shiny 47 | library(shiny) 48 | library(editbl) 49 | shinyApp( 50 | ui = fluidPage(fluidRow(column(12, eDTOutput('tbl')))), 51 | server = function(input, output) { 52 | eDT('tbl',iris,) 53 | } 54 | ) 55 | 56 | # Custom inputUI 57 | editbl::eDT(mtcars, inputUI = function(id, data){ 58 | ns <- NS(id) 59 | textInput( 60 | ns("mpg"), 61 | label = "mpg", 62 | value = data$mpg)}) 63 | 64 | # Do not allow delete 65 | editbl::eDT(mtcars, canDeleteRow = FALSE) 66 | } 67 | 68 | } 69 | \author{ 70 | Jasper Schelfhout 71 | } 72 | -------------------------------------------------------------------------------- /editbl/man/eDT_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT_app.R 3 | \name{eDT_app} 4 | \alias{eDT_app} 5 | \title{Open interactive app to explore and modify data} 6 | \usage{ 7 | eDT_app(...) 8 | } 9 | \arguments{ 10 | \item{...}{arguments past to \code{\link{eDT}}} 11 | } 12 | \value{ 13 | data (or a modified version thereof) once you click 'close' 14 | } 15 | \description{ 16 | Open interactive app to explore and modify data 17 | } 18 | \details{ 19 | When \code{\link{eDT}} is not used within the server of a shiny app, it will 20 | call this function to start up a shiny app itself. Just as \code{DT::datatable()} displays a table 21 | in the browser when called upon interactively. 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/eDT_app_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT_app.R 3 | \name{eDT_app_server} 4 | \alias{eDT_app_server} 5 | \title{Server of eDT_app} 6 | \usage{ 7 | eDT_app_server(moduleId = "nevergonnagiveyouup", ...) 8 | } 9 | \arguments{ 10 | \item{moduleId}{\code{character(1)} id to connect with eDT_app_server} 11 | 12 | \item{...}{arguments passed to \link{eDT}} 13 | } 14 | \value{ 15 | moduleServer which on application stop returns version of x with made changes 16 | } 17 | \description{ 18 | Server of eDT_app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/eDT_app_ui.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT_app.R 3 | \name{eDT_app_ui} 4 | \alias{eDT_app_ui} 5 | \title{UI of eDT_app} 6 | \usage{ 7 | eDT_app_ui(moduleId = "nevergonnagiveyouup", eDTId = "nevergonnaletyoudown") 8 | } 9 | \arguments{ 10 | \item{moduleId}{\code{character(1)} id to connect with eDT_app_server} 11 | 12 | \item{eDTId}{\code{character(1)} id to connect \code{\link{eDTOutput}} to \code{\link{eDT}} within the module.} 13 | } 14 | \value{ 15 | HTML 16 | } 17 | \description{ 18 | UI of eDT_app 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/e_rows_insert.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{e_rows_insert} 4 | \alias{e_rows_insert} 5 | \title{Insert rows into a tibble} 6 | \usage{ 7 | e_rows_insert( 8 | x, 9 | y, 10 | by = NULL, 11 | ..., 12 | conflict = c("error", "ignore"), 13 | copy = FALSE, 14 | in_place = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 19 | \code{y} must have the same columns of \code{x} or a subset.} 20 | 21 | \item{by}{An unnamed character vector giving the key columns. The key columns 22 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 23 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 24 | \code{rows_patch()}, or \code{rows_upsert()} are used. 25 | 26 | By default, we use the first column in \code{y}, since the first column is 27 | a reasonable place to put an identifier variable.} 28 | 29 | \item{...}{Other parameters passed onto methods.} 30 | 31 | \item{conflict}{For \code{rows_insert()}, how should keys in \code{y} that conflict 32 | with keys in \code{x} be handled? A conflict arises if there is a key in \code{y} 33 | that already exists in \code{x}. 34 | 35 | One of: 36 | \itemize{ 37 | \item \code{"error"}, the default, will error if there are any keys in \code{y} that 38 | conflict with keys in \code{x}. 39 | \item \code{"ignore"} will ignore rows in \code{y} with keys that conflict with keys in 40 | \code{x}. 41 | }} 42 | 43 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 44 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 45 | same src as \code{x}. This allows you to join tables across srcs, but 46 | it is a potentially expensive operation so you must opt into it.} 47 | 48 | \item{in_place}{Should \code{x} be modified in place? This argument is only 49 | relevant for mutable backends (e.g. databases, data.tables). 50 | 51 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 52 | when \code{FALSE}, a new object representing the resulting changes is returned.} 53 | } 54 | \value{ 55 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 56 | is preserved as much as possible. The output has the following properties: 57 | \itemize{ 58 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 59 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 60 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 61 | rows. 62 | \item Columns are not added, removed, or relocated, though the data may be 63 | updated. 64 | \item Groups are taken from \code{x}. 65 | \item Data frame attributes are taken from \code{x}. 66 | } 67 | 68 | If \code{in_place = TRUE}, the result will be returned invisibly. 69 | } 70 | \description{ 71 | Insert rows into a tibble 72 | } 73 | \details{ 74 | Mainly a wrapper around \code{\link[dplyr]{rows_insert}}. 75 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 76 | Reason for separate method is to avoid conflicts on package loading. 77 | } 78 | -------------------------------------------------------------------------------- /editbl/man/e_rows_insert.default.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{e_rows_insert.default} 4 | \alias{e_rows_insert.default} 5 | \title{Insert rows into a tibble} 6 | \usage{ 7 | \method{e_rows_insert}{default}( 8 | x, 9 | y, 10 | by = NULL, 11 | ..., 12 | conflict = c("error", "ignore"), 13 | copy = FALSE, 14 | in_place = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 19 | \code{y} must have the same columns of \code{x} or a subset.} 20 | 21 | \item{by}{An unnamed character vector giving the key columns. The key columns 22 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 23 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 24 | \code{rows_patch()}, or \code{rows_upsert()} are used. 25 | 26 | By default, we use the first column in \code{y}, since the first column is 27 | a reasonable place to put an identifier variable.} 28 | 29 | \item{...}{Other parameters passed onto methods.} 30 | 31 | \item{conflict}{For \code{rows_insert()}, how should keys in \code{y} that conflict 32 | with keys in \code{x} be handled? A conflict arises if there is a key in \code{y} 33 | that already exists in \code{x}. 34 | 35 | One of: 36 | \itemize{ 37 | \item \code{"error"}, the default, will error if there are any keys in \code{y} that 38 | conflict with keys in \code{x}. 39 | \item \code{"ignore"} will ignore rows in \code{y} with keys that conflict with keys in 40 | \code{x}. 41 | }} 42 | 43 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 44 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 45 | same src as \code{x}. This allows you to join tables across srcs, but 46 | it is a potentially expensive operation so you must opt into it.} 47 | 48 | \item{in_place}{Should \code{x} be modified in place? This argument is only 49 | relevant for mutable backends (e.g. databases, data.tables). 50 | 51 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 52 | when \code{FALSE}, a new object representing the resulting changes is returned.} 53 | } 54 | \value{ 55 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 56 | is preserved as much as possible. The output has the following properties: 57 | \itemize{ 58 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 59 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 60 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 61 | rows. 62 | \item Columns are not added, removed, or relocated, though the data may be 63 | updated. 64 | \item Groups are taken from \code{x}. 65 | \item Data frame attributes are taken from \code{x}. 66 | } 67 | 68 | If \code{in_place = TRUE}, the result will be returned invisibly. 69 | } 70 | \description{ 71 | Insert rows into a tibble 72 | } 73 | \details{ 74 | Mainly a wrapper around \code{\link[dplyr]{rows_insert}}. 75 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 76 | Reason for separate method is to avoid conflicts on package loading. 77 | } 78 | -------------------------------------------------------------------------------- /editbl/man/e_rows_insert.dtplyr_step.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dtplyr_step.R 3 | \name{e_rows_insert.dtplyr_step} 4 | \alias{e_rows_insert.dtplyr_step} 5 | \title{rows_insert implementation for \code{data.table} backends.} 6 | \usage{ 7 | \method{e_rows_insert}{dtplyr_step}(x, y, by = NULL, ..., copy = FALSE, in_place = FALSE) 8 | } 9 | \arguments{ 10 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 11 | \code{y} must have the same columns of \code{x} or a subset.} 12 | 13 | \item{by}{An unnamed character vector giving the key columns. The key columns 14 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 15 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 16 | \code{rows_patch()}, or \code{rows_upsert()} are used. 17 | 18 | By default, we use the first column in \code{y}, since the first column is 19 | a reasonable place to put an identifier variable.} 20 | 21 | \item{...}{Other parameters passed onto methods.} 22 | 23 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 24 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 25 | same src as \code{x}. This allows you to join tables across srcs, but 26 | it is a potentially expensive operation so you must opt into it.} 27 | 28 | \item{in_place}{Should \code{x} be modified in place? This argument is only 29 | relevant for mutable backends (e.g. databases, data.tables). 30 | 31 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 32 | when \code{FALSE}, a new object representing the resulting changes is returned.} 33 | } 34 | \value{ 35 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 36 | is preserved as much as possible. The output has the following properties: 37 | \itemize{ 38 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 39 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 40 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 41 | rows. 42 | \item Columns are not added, removed, or relocated, though the data may be 43 | updated. 44 | \item Groups are taken from \code{x}. 45 | \item Data frame attributes are taken from \code{x}. 46 | } 47 | 48 | If \code{in_place = TRUE}, the result will be returned invisibly. 49 | } 50 | \description{ 51 | rows_insert implementation for \code{data.table} backends. 52 | } 53 | \details{ 54 | Mainly a wrapper around \code{\link[dplyr]{rows_insert}}. 55 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 56 | Reason for separate method is to avoid conflicts on package loading. 57 | } 58 | \author{ 59 | Jasper Schelfhout 60 | } 61 | -------------------------------------------------------------------------------- /editbl/man/e_rows_insert.tbl_dbi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{e_rows_insert.tbl_dbi} 4 | \alias{e_rows_insert.tbl_dbi} 5 | \title{rows_insert implementation for DBI backends.} 6 | \usage{ 7 | \method{e_rows_insert}{tbl_dbi}(x, y, by = NULL, ..., copy = FALSE, in_place = FALSE) 8 | } 9 | \arguments{ 10 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 11 | \code{y} must have the same columns of \code{x} or a subset.} 12 | 13 | \item{by}{An unnamed character vector giving the key columns. The key columns 14 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 15 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 16 | \code{rows_patch()}, or \code{rows_upsert()} are used. 17 | 18 | By default, we use the first column in \code{y}, since the first column is 19 | a reasonable place to put an identifier variable.} 20 | 21 | \item{...}{Other parameters passed onto methods.} 22 | 23 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 24 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 25 | same src as \code{x}. This allows you to join tables across srcs, but 26 | it is a potentially expensive operation so you must opt into it.} 27 | 28 | \item{in_place}{Should \code{x} be modified in place? This argument is only 29 | relevant for mutable backends (e.g. databases, data.tables). 30 | 31 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 32 | when \code{FALSE}, a new object representing the resulting changes is returned.} 33 | } 34 | \value{ 35 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 36 | is preserved as much as possible. The output has the following properties: 37 | \itemize{ 38 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 39 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 40 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 41 | rows. 42 | \item Columns are not added, removed, or relocated, though the data may be 43 | updated. 44 | \item Groups are taken from \code{x}. 45 | \item Data frame attributes are taken from \code{x}. 46 | } 47 | 48 | If \code{in_place = TRUE}, the result will be returned invisibly. 49 | } 50 | \description{ 51 | rows_insert implementation for DBI backends. 52 | } 53 | \details{ 54 | Mainly a wrapper around \code{\link[dplyr]{rows_insert}}. 55 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 56 | Reason for separate method is to avoid conflicts on package loading. 57 | } 58 | \examples{ 59 | library(dplyr) 60 | 61 | # Set up a test table 62 | conn <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") 63 | artists_df <- data.frame( 64 | ArtistId = c(1,2), 65 | Name = c("AC/DC", "The Offspring") 66 | ) 67 | DBI::dbWriteTable(conn, "Artist", artists_df) 68 | 69 | # Insert new row 70 | artists <- tbl(conn, "Artist") 71 | DBI::dbBegin(conn) 72 | e_rows_insert(artists, 73 | data.frame(ArtistId = 999, Name = "testArtist"), 74 | in_place = TRUE) 75 | 76 | DBI::dbRollback(conn) 77 | DBI::dbDisconnect(conn) 78 | 79 | } 80 | \author{ 81 | Jasper Schelfhout 82 | } 83 | -------------------------------------------------------------------------------- /editbl/man/e_rows_update.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{e_rows_update} 4 | \alias{e_rows_update} 5 | \title{Update rows of a tibble} 6 | \usage{ 7 | e_rows_update( 8 | x, 9 | y, 10 | by = NULL, 11 | ..., 12 | match, 13 | unmatched = c("error", "ignore"), 14 | copy = FALSE, 15 | in_place = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 20 | \code{y} must have the same columns of \code{x} or a subset.} 21 | 22 | \item{by}{An unnamed character vector giving the key columns. The key columns 23 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 24 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 25 | \code{rows_patch()}, or \code{rows_upsert()} are used. 26 | 27 | By default, we use the first column in \code{y}, since the first column is 28 | a reasonable place to put an identifier variable.} 29 | 30 | \item{...}{Other parameters passed onto methods.} 31 | 32 | \item{match}{named \code{list} consisting out of two equal length \code{data.frame}'s with columns defined in \code{by}. 33 | This allows for updates of columns defined in \code{by}.} 34 | 35 | \item{unmatched}{For \code{rows_update()}, \code{rows_patch()}, and \code{rows_delete()}, 36 | how should keys in \code{y} that are unmatched by the keys in \code{x} be handled? 37 | 38 | One of: 39 | \itemize{ 40 | \item \code{"error"}, the default, will error if there are any keys in \code{y} that 41 | are unmatched by the keys in \code{x}. 42 | \item \code{"ignore"} will ignore rows in \code{y} with keys that are unmatched by the 43 | keys in \code{x}. 44 | }} 45 | 46 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 47 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 48 | same src as \code{x}. This allows you to join tables across srcs, but 49 | it is a potentially expensive operation so you must opt into it.} 50 | 51 | \item{in_place}{Should \code{x} be modified in place? This argument is only 52 | relevant for mutable backends (e.g. databases, data.tables). 53 | 54 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 55 | when \code{FALSE}, a new object representing the resulting changes is returned.} 56 | } 57 | \value{ 58 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 59 | is preserved as much as possible. The output has the following properties: 60 | \itemize{ 61 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 62 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 63 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 64 | rows. 65 | \item Columns are not added, removed, or relocated, though the data may be 66 | updated. 67 | \item Groups are taken from \code{x}. 68 | \item Data frame attributes are taken from \code{x}. 69 | } 70 | 71 | If \code{in_place = TRUE}, the result will be returned invisibly. 72 | } 73 | \description{ 74 | Update rows of a tibble 75 | } 76 | \details{ 77 | Mainly a wrapper around \code{\link[dplyr]{rows_update}}. 78 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 79 | Reason for separate method is to avoid conflicts on package loading. 80 | } 81 | -------------------------------------------------------------------------------- /editbl/man/e_rows_update.data.frame.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_df.R 3 | \name{e_rows_update.data.frame} 4 | \alias{e_rows_update.data.frame} 5 | \title{rows_update implementation for data.frame backends.} 6 | \usage{ 7 | \method{e_rows_update}{data.frame}( 8 | x, 9 | y, 10 | by = NULL, 11 | match = NULL, 12 | ..., 13 | copy = FALSE, 14 | in_place = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 19 | \code{y} must have the same columns of \code{x} or a subset.} 20 | 21 | \item{by}{An unnamed character vector giving the key columns. The key columns 22 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 23 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 24 | \code{rows_patch()}, or \code{rows_upsert()} are used. 25 | 26 | By default, we use the first column in \code{y}, since the first column is 27 | a reasonable place to put an identifier variable.} 28 | 29 | \item{match}{named \code{list} consisting out of two equal length \code{data.frame}'s with columns defined in \code{by}. 30 | This allows for updates of columns defined in \code{by}.} 31 | 32 | \item{...}{Other parameters passed onto methods.} 33 | 34 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 35 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 36 | same src as \code{x}. This allows you to join tables across srcs, but 37 | it is a potentially expensive operation so you must opt into it.} 38 | 39 | \item{in_place}{Should \code{x} be modified in place? This argument is only 40 | relevant for mutable backends (e.g. databases, data.tables). 41 | 42 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 43 | when \code{FALSE}, a new object representing the resulting changes is returned.} 44 | } 45 | \value{ 46 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 47 | is preserved as much as possible. The output has the following properties: 48 | \itemize{ 49 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 50 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 51 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 52 | rows. 53 | \item Columns are not added, removed, or relocated, though the data may be 54 | updated. 55 | \item Groups are taken from \code{x}. 56 | \item Data frame attributes are taken from \code{x}. 57 | } 58 | 59 | If \code{in_place = TRUE}, the result will be returned invisibly. 60 | } 61 | \description{ 62 | rows_update implementation for data.frame backends. 63 | } 64 | \details{ 65 | Mainly a wrapper around \code{\link[dplyr]{rows_update}}. 66 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 67 | Reason for separate method is to avoid conflicts on package loading. 68 | } 69 | \author{ 70 | Jasper Schelfhout 71 | } 72 | -------------------------------------------------------------------------------- /editbl/man/e_rows_update.default.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{e_rows_update.default} 4 | \alias{e_rows_update.default} 5 | \title{Update rows of a tibble} 6 | \usage{ 7 | \method{e_rows_update}{default}( 8 | x, 9 | y, 10 | by = NULL, 11 | ..., 12 | match = match, 13 | unmatched = c("error", "ignore"), 14 | copy = FALSE, 15 | in_place = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 20 | \code{y} must have the same columns of \code{x} or a subset.} 21 | 22 | \item{by}{An unnamed character vector giving the key columns. The key columns 23 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 24 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 25 | \code{rows_patch()}, or \code{rows_upsert()} are used. 26 | 27 | By default, we use the first column in \code{y}, since the first column is 28 | a reasonable place to put an identifier variable.} 29 | 30 | \item{...}{Other parameters passed onto methods.} 31 | 32 | \item{match}{named \code{list} consisting out of two equal length \code{data.frame}'s with columns defined in \code{by}. 33 | This allows for updates of columns defined in \code{by}.} 34 | 35 | \item{unmatched}{For \code{rows_update()}, \code{rows_patch()}, and \code{rows_delete()}, 36 | how should keys in \code{y} that are unmatched by the keys in \code{x} be handled? 37 | 38 | One of: 39 | \itemize{ 40 | \item \code{"error"}, the default, will error if there are any keys in \code{y} that 41 | are unmatched by the keys in \code{x}. 42 | \item \code{"ignore"} will ignore rows in \code{y} with keys that are unmatched by the 43 | keys in \code{x}. 44 | }} 45 | 46 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 47 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 48 | same src as \code{x}. This allows you to join tables across srcs, but 49 | it is a potentially expensive operation so you must opt into it.} 50 | 51 | \item{in_place}{Should \code{x} be modified in place? This argument is only 52 | relevant for mutable backends (e.g. databases, data.tables). 53 | 54 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 55 | when \code{FALSE}, a new object representing the resulting changes is returned.} 56 | } 57 | \value{ 58 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 59 | is preserved as much as possible. The output has the following properties: 60 | \itemize{ 61 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 62 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 63 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 64 | rows. 65 | \item Columns are not added, removed, or relocated, though the data may be 66 | updated. 67 | \item Groups are taken from \code{x}. 68 | \item Data frame attributes are taken from \code{x}. 69 | } 70 | 71 | If \code{in_place = TRUE}, the result will be returned invisibly. 72 | } 73 | \description{ 74 | Update rows of a tibble 75 | } 76 | \details{ 77 | Mainly a wrapper around \code{\link[dplyr]{rows_update}}. 78 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 79 | Reason for separate method is to avoid conflicts on package loading. 80 | } 81 | -------------------------------------------------------------------------------- /editbl/man/e_rows_update.dtplyr_step.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dtplyr_step.R 3 | \name{e_rows_update.dtplyr_step} 4 | \alias{e_rows_update.dtplyr_step} 5 | \title{rows_update implementation for data.table backends.} 6 | \usage{ 7 | \method{e_rows_update}{dtplyr_step}( 8 | x, 9 | y, 10 | by = NULL, 11 | match = NULL, 12 | ..., 13 | copy = FALSE, 14 | in_place = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 19 | \code{y} must have the same columns of \code{x} or a subset.} 20 | 21 | \item{by}{An unnamed character vector giving the key columns. The key columns 22 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 23 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 24 | \code{rows_patch()}, or \code{rows_upsert()} are used. 25 | 26 | By default, we use the first column in \code{y}, since the first column is 27 | a reasonable place to put an identifier variable.} 28 | 29 | \item{match}{named \code{list} consisting out of two equal length \code{data.frame}'s with columns defined in \code{by}. 30 | This allows for updates of columns defined in \code{by}.} 31 | 32 | \item{...}{Other parameters passed onto methods.} 33 | 34 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 35 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 36 | same src as \code{x}. This allows you to join tables across srcs, but 37 | it is a potentially expensive operation so you must opt into it.} 38 | 39 | \item{in_place}{Should \code{x} be modified in place? This argument is only 40 | relevant for mutable backends (e.g. databases, data.tables). 41 | 42 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 43 | when \code{FALSE}, a new object representing the resulting changes is returned.} 44 | } 45 | \value{ 46 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 47 | is preserved as much as possible. The output has the following properties: 48 | \itemize{ 49 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 50 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 51 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 52 | rows. 53 | \item Columns are not added, removed, or relocated, though the data may be 54 | updated. 55 | \item Groups are taken from \code{x}. 56 | \item Data frame attributes are taken from \code{x}. 57 | } 58 | 59 | If \code{in_place = TRUE}, the result will be returned invisibly. 60 | } 61 | \description{ 62 | rows_update implementation for data.table backends. 63 | } 64 | \details{ 65 | Mainly a wrapper around \code{\link[dplyr]{rows_update}}. 66 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 67 | Reason for separate method is to avoid conflicts on package loading. 68 | } 69 | \author{ 70 | Jasper Schelfhout 71 | } 72 | -------------------------------------------------------------------------------- /editbl/man/e_rows_update.tbl_dbi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{e_rows_update.tbl_dbi} 4 | \alias{e_rows_update.tbl_dbi} 5 | \title{rows_update implementation for DBI backends.} 6 | \usage{ 7 | \method{e_rows_update}{tbl_dbi}( 8 | x, 9 | y, 10 | by = NULL, 11 | match = NULL, 12 | ..., 13 | copy = FALSE, 14 | in_place = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 19 | \code{y} must have the same columns of \code{x} or a subset.} 20 | 21 | \item{by}{An unnamed character vector giving the key columns. The key columns 22 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 23 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 24 | \code{rows_patch()}, or \code{rows_upsert()} are used. 25 | 26 | By default, we use the first column in \code{y}, since the first column is 27 | a reasonable place to put an identifier variable.} 28 | 29 | \item{match}{named \code{list} consisting out of two equal length \code{data.frame}'s with columns defined in \code{by}. 30 | This allows for updates of columns defined in \code{by}.} 31 | 32 | \item{...}{Other parameters passed onto methods.} 33 | 34 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 35 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 36 | same src as \code{x}. This allows you to join tables across srcs, but 37 | it is a potentially expensive operation so you must opt into it.} 38 | 39 | \item{in_place}{Should \code{x} be modified in place? This argument is only 40 | relevant for mutable backends (e.g. databases, data.tables). 41 | 42 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 43 | when \code{FALSE}, a new object representing the resulting changes is returned.} 44 | } 45 | \value{ 46 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 47 | is preserved as much as possible. The output has the following properties: 48 | \itemize{ 49 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 50 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 51 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 52 | rows. 53 | \item Columns are not added, removed, or relocated, though the data may be 54 | updated. 55 | \item Groups are taken from \code{x}. 56 | \item Data frame attributes are taken from \code{x}. 57 | } 58 | 59 | If \code{in_place = TRUE}, the result will be returned invisibly. 60 | } 61 | \description{ 62 | rows_update implementation for DBI backends. 63 | } 64 | \details{ 65 | Mainly a wrapper around \code{\link[dplyr]{rows_update}}. 66 | Allows for specific implementations should the behavior differ from what's needed by \code{editbl}. 67 | Reason for separate method is to avoid conflicts on package loading. 68 | } 69 | \examples{ 70 | library(dplyr) 71 | 72 | # Set up a test table 73 | conn <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") 74 | artists_df <- data.frame( 75 | ArtistId = c(1,2), 76 | Name = c("AC/DC", "The Offspring") 77 | ) 78 | DBI::dbWriteTable(conn, "Artist", artists_df) 79 | 80 | # Update rows without changing the key. 81 | artists <- tbl(conn, "Artist") 82 | DBI::dbBegin(conn) 83 | y <- data.frame(ArtistId = 1, Name = "DC/AC") 84 | e_rows_update( 85 | x = artists, 86 | y = y, 87 | by = "ArtistId", 88 | in_place = TRUE) 89 | DBI::dbRollback(conn) 90 | 91 | # Update key values of rows. 92 | DBI::dbBegin(conn) 93 | y <- data.frame(ArtistId = 999, Name = "DC/AC") 94 | match <- list( 95 | x = data.frame("ArtistId" = 1), 96 | y = data.frame("ArtistId" = 999) 97 | ) 98 | e_rows_update( 99 | x = artists, 100 | y = y, 101 | match = match, 102 | by = "ArtistId", 103 | in_place = TRUE) 104 | DBI::dbRollback(conn) 105 | DBI::dbDisconnect(conn) 106 | 107 | } 108 | \author{ 109 | Jasper Schelfhout 110 | } 111 | -------------------------------------------------------------------------------- /editbl/man/evalCanCloneRow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{evalCanCloneRow} 4 | \alias{evalCanCloneRow} 5 | \title{Determine if a row can be cloned} 6 | \usage{ 7 | evalCanCloneRow(row, canCloneRow = TRUE, statusCol = "_editbl_status") 8 | } 9 | \arguments{ 10 | \item{row}{\code{tibble}, single row.} 11 | 12 | \item{canCloneRow}{\code{function} with argument 'row' defining logic on wether or 13 | not the row can be cloned. Can also be \code{logical} TRUE or FALSE.} 14 | 15 | \item{statusCol}{\code{character(1)} name of column with general status (e.g. modified or not).} 16 | } 17 | \value{ 18 | \code{boolean} 19 | } 20 | \description{ 21 | Determine if a row can be cloned 22 | } 23 | \details{ 24 | calling this around the user passed on function ensures 25 | that newly inserted rows are being excempt from the logic. 26 | Moreover, the output of the function can be checked. 27 | } 28 | \author{ 29 | Saar Junius 30 | } 31 | -------------------------------------------------------------------------------- /editbl/man/evalCanDeleteRow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{evalCanDeleteRow} 4 | \alias{evalCanDeleteRow} 5 | \title{Determine if a row can be deleted} 6 | \usage{ 7 | evalCanDeleteRow(row, canDeleteRow = TRUE, statusCol = "_editbl_status") 8 | } 9 | \arguments{ 10 | \item{row}{\code{tibble}, single row} 11 | 12 | \item{canDeleteRow}{\code{function} with argument 'row' defining logic on wether or 13 | not the row can be modified. Can also be \code{logical} TRUE or FALSE.} 14 | 15 | \item{statusCol}{\code{character(1)} name of column with general status (e.g. modified or not).} 16 | } 17 | \value{ 18 | \code{boolean} 19 | } 20 | \description{ 21 | Determine if a row can be deleted 22 | } 23 | \details{ 24 | calling this around the user passed on function ensures 25 | that newly inserted rows are being excempt from the logic. 26 | Moreover, the output of the function can be checked. 27 | } 28 | \author{ 29 | Jasper Schelfhout 30 | } 31 | -------------------------------------------------------------------------------- /editbl/man/evalCanEditRow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{evalCanEditRow} 4 | \alias{evalCanEditRow} 5 | \title{Determine if a row can be edited} 6 | \usage{ 7 | evalCanEditRow(row, canEditRow = TRUE, statusCol = "_editbl_status") 8 | } 9 | \arguments{ 10 | \item{row}{\code{tibble}, single row.} 11 | 12 | \item{canEditRow}{\code{function} with argument 'row' defining logic on wether or 13 | not the row can be modified. Can also be \code{logical} TRUE or FALSE.} 14 | 15 | \item{statusCol}{\code{character(1)} name of column with general status (e.g. modified or not).} 16 | } 17 | \value{ 18 | \code{boolean} 19 | } 20 | \description{ 21 | Determine if a row can be edited 22 | } 23 | \details{ 24 | calling this around the user passed on function ensures 25 | that newly inserted rows are being excempt from the logic. 26 | Moreover, the output of the function can be checked. 27 | } 28 | \author{ 29 | Jasper Schelfhout 30 | } 31 | -------------------------------------------------------------------------------- /editbl/man/fillDeductedColumns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/foreignTbl.R 3 | \name{fillDeductedColumns} 4 | \alias{fillDeductedColumns} 5 | \title{Fill data columns based on foreignTbls} 6 | \usage{ 7 | fillDeductedColumns(tbl, foreignTbls) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | 12 | \item{foreignTbls}{list of foreign tbls as created by \code{\link{foreignTbl}}} 13 | } 14 | \value{ 15 | tbl 16 | } 17 | \description{ 18 | Fill data columns based on foreignTbls 19 | } 20 | \details{ 21 | When a combination of columns is not found in the foreignTbl, 22 | fill the deductedColumns with NA. 23 | 24 | on foreignTbls suggesting conflicting data, 25 | an arbitrary choice is made. It is best to afterwards check with 26 | checkForeignTbls to see if a valid result is obtained. 27 | } 28 | \author{ 29 | Jasper Schelfhout 30 | } 31 | -------------------------------------------------------------------------------- /editbl/man/fixInteger64.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{fixInteger64} 4 | \alias{fixInteger64} 5 | \title{Replace instances of integer64 with actual NA values instead of weird default 9218868437227407266} 6 | \usage{ 7 | fixInteger64(x) 8 | } 9 | \arguments{ 10 | \item{x}{\code{data.frame}} 11 | } 12 | \value{ 13 | x with \code{integer64} columns set to \code{bit64::as.integer64(NA)} 14 | } 15 | \description{ 16 | Replace instances of integer64 with actual NA values instead of weird default 9218868437227407266 17 | } 18 | \details{ 19 | \href{https://github.com/Rdatatable/data.table/issues/4561}{github issue} 20 | } 21 | \author{ 22 | Jasper Schelfhout 23 | } 24 | -------------------------------------------------------------------------------- /editbl/man/foreignTbl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/foreignTbl.R 3 | \name{foreignTbl} 4 | \alias{foreignTbl} 5 | \title{Create a foreign tibble} 6 | \usage{ 7 | foreignTbl( 8 | x, 9 | y, 10 | by = intersect(dplyr::tbl_vars(x), dplyr::tbl_vars(y)), 11 | naturalKey = dplyr::tbl_vars(y), 12 | allowNew = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{\code{tbl}. The referencing table.} 17 | 18 | \item{y}{\code{tbl}. The referenced table.} 19 | 20 | \item{by}{\code{character}. Column names to match on. 21 | Note that you should rename and/or typecast the columns in y should they not exactly match the columns in x.} 22 | 23 | \item{naturalKey}{\code{character}. The columns that form the natural key in y. 24 | These are the only ones that can actually get modified in \code{eDT} when changing cells in the table. 25 | Reasoning being that these columns should be sufficient to uniquely identify a row in the referenced table. 26 | All other columns will be automatically fetched and filled in.} 27 | 28 | \item{allowNew}{\code{logical}. Whether or not new values are allowed. If \code{TRUE}, 29 | the rows in the foreignTbl will only be used as suggestions, not restrictions.} 30 | } 31 | \value{ 32 | List with unmodified arguments. However, they have now been checked for validity. 33 | \itemize{ 34 | \item y, see argument \code{y}. 35 | \item by, see argument \code{by}. 36 | \item naturalKey, see argument \code{naturalKey}. 37 | \item allowNew, see argument \code{allowNew} 38 | } 39 | } 40 | \description{ 41 | Create a foreign tibble 42 | } 43 | \details{ 44 | This is a tibble that can be passed onto \code{\link{eDT}} as a referenced table. 45 | 46 | It is the equivalent of a database table to which the \code{data} tbl of eDT has a foreign key. 47 | 48 | It will be merged with the tbl passed onto the \code{data} argument allowing to provide restrictions 49 | for certain columns. 50 | 51 | Note that row uniqueness for the columns used in \code{by} and \code{naturalKey} is assumed. 52 | This assumption will however not be checked since it is an expensive operation on big datasets. 53 | However, if violated, it might give errors or unexpected results during usage of the eDT module. 54 | } 55 | \examples{ 56 | a <- tibble::tibble( 57 | first_name = c("Albert","Donald","Mickey"), 58 | last_name_id = c(1,2,2) 59 | ) 60 | 61 | b <- foreignTbl( 62 | a, 63 | tibble::tibble( 64 | last_name = c("Einstein", "Duck", "Mouse"), 65 | last_name_id = c(1,2,3) 66 | ), 67 | by = "last_name_id", 68 | naturalKey = "last_name" 69 | ) 70 | 71 | ## Only run this in interactive R sessions 72 | if(interactive()){ 73 | eDT(a, 74 | foreignTbls = list(b), 75 | options = list(columnDefs = list(list(visible=FALSE, targets="last_name_id"))) 76 | ) 77 | } 78 | 79 | 80 | } 81 | \author{ 82 | Jasper Schelfhout 83 | } 84 | -------------------------------------------------------------------------------- /editbl/man/getColumnTypeSums.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{getColumnTypeSums} 4 | \alias{getColumnTypeSums} 5 | \title{Get types of columns in a tbl} 6 | \usage{ 7 | getColumnTypeSums(tbl) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | } 12 | \value{ 13 | named list with types of the colums 14 | } 15 | \description{ 16 | Get types of columns in a tbl 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/getNonNaturalKeyCols.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/foreignTbl.R 3 | \name{getNonNaturalKeyCols} 4 | \alias{getNonNaturalKeyCols} 5 | \title{Get all columns that are not natural keys} 6 | \usage{ 7 | getNonNaturalKeyCols(foreignTbls) 8 | } 9 | \arguments{ 10 | \item{foreignTbls}{list of foreign tbls as created by \code{\link{foreignTbl}}} 11 | } 12 | \value{ 13 | \code{character} 14 | } 15 | \description{ 16 | Get all columns that are not natural keys 17 | } 18 | \author{ 19 | Jasper Schelfhout 20 | } 21 | -------------------------------------------------------------------------------- /editbl/man/get_db_table_name.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{get_db_table_name} 4 | \alias{get_db_table_name} 5 | \title{Get name of the tbl in the database} 6 | \usage{ 7 | get_db_table_name(x) 8 | } 9 | \arguments{ 10 | \item{x}{\code{tbl_dbi}} 11 | } 12 | \value{ 13 | SQL, the table name as used in the database 14 | } 15 | \description{ 16 | Get name of the tbl in the database 17 | } 18 | -------------------------------------------------------------------------------- /editbl/man/initData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eDT.R 3 | \name{initData} 4 | \alias{initData} 5 | \title{Add some extra columns to data to allow for / keep track of modifications} 6 | \usage{ 7 | initData( 8 | data, 9 | ns, 10 | buttonCol = "buttons", 11 | statusCol = "_editbl_status", 12 | deleteCol = "_editbl_deleted", 13 | iCol = "i", 14 | canDeleteRow = TRUE, 15 | canEditRow = TRUE, 16 | canCloneRow = TRUE 17 | ) 18 | } 19 | \arguments{ 20 | \item{data}{\code{data.frame}} 21 | 22 | \item{ns}{namespace function} 23 | 24 | \item{buttonCol}{\code{character(1)} name of column with buttons} 25 | 26 | \item{statusCol}{\code{character(1)} name of column with general status (e.g. modified or not).} 27 | 28 | \item{deleteCol}{\code{character(1)} name of the column with deletion status.} 29 | 30 | \item{iCol}{\code{character(1)} name of column containing a unique identifier.} 31 | 32 | \item{canDeleteRow}{can be either of the following: 33 | \itemize{ 34 | \item \code{logical}, e.g. TRUE or FALSE 35 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 36 | }} 37 | 38 | \item{canEditRow}{can be either of the following: 39 | \itemize{ 40 | \item \code{logical}, e.g. TRUE or FALSE 41 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 42 | }} 43 | 44 | \item{canCloneRow}{can be either of the following: 45 | \itemize{ 46 | \item \code{logical}, e.g. TRUE or FALSE 47 | \item \code{function}. Needs as input an argument \code{row} which accepts a single row \code{tibble} and as output TRUE/FALSE. 48 | }} 49 | } 50 | \value{ 51 | data with extra columns buttons, status, i. 52 | } 53 | \description{ 54 | Add some extra columns to data to allow for / keep track of modifications 55 | } 56 | \author{ 57 | Jasper Schelfhout 58 | } 59 | -------------------------------------------------------------------------------- /editbl/man/inputServer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinyInput.R 3 | \name{inputServer} 4 | \alias{inputServer} 5 | \title{An input server for a \code{data.frame}} 6 | \usage{ 7 | inputServer(id, data, ...) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)} module id} 11 | 12 | \item{data}{single row \code{data.frame}} 13 | 14 | \item{...}{further arguments for methods} 15 | } 16 | \value{ 17 | modified version of data 18 | } 19 | \description{ 20 | An input server for a \code{data.frame} 21 | } 22 | \details{ 23 | A new method for this can be added if you wish to alter the default behavior of the pop-up modals in \code{\link{eDT}}. 24 | } 25 | \examples{ 26 | if(interactive()){ 27 | library(shiny) 28 | ui <- inputUI('id') 29 | server <- function(input,output,session){ 30 | input <- inputServer("id", mtcars[1,]) 31 | observe({print(input())}) 32 | } 33 | shinyApp(ui, server) 34 | } 35 | 36 | } 37 | \author{ 38 | Jasper Schelfhout 39 | } 40 | -------------------------------------------------------------------------------- /editbl/man/inputServer.default.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinyInput.R 3 | \name{inputServer.default} 4 | \alias{inputServer.default} 5 | \title{An input server for a \code{data.frame}} 6 | \usage{ 7 | \method{inputServer}{default}(id, data, colnames, notEditable, foreignTbls, ...) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)} module id} 11 | 12 | \item{data}{single row \code{data.frame}} 13 | 14 | \item{colnames}{named \code{character}} 15 | 16 | \item{notEditable}{\code{character} columns that should not be edited} 17 | 18 | \item{foreignTbls}{list of foreignTbls. See \code{\link{foreignTbl}}} 19 | 20 | \item{...}{for compatibility with other methods} 21 | } 22 | \value{ 23 | reactive modified version of data 24 | } 25 | \description{ 26 | An input server for a \code{data.frame} 27 | } 28 | \details{ 29 | Reads all inputs ids that are identical to column names of the data 30 | and updates the data. 31 | } 32 | \author{ 33 | Jasper Schelfhout 34 | } 35 | -------------------------------------------------------------------------------- /editbl/man/inputUI.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinyInput.R 3 | \name{inputUI} 4 | \alias{inputUI} 5 | \title{An input UI for a \code{data.frame}} 6 | \usage{ 7 | inputUI(id, ...) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)} module id} 11 | 12 | \item{...}{arguments passed onto methods} 13 | } 14 | \value{ 15 | HTML. A set of input fields corresponding to the given row. 16 | } 17 | \description{ 18 | An input UI for a \code{data.frame} 19 | } 20 | \details{ 21 | A new method for this can be added if you wish to alter the default behavior of the pop-up modals in \code{\link{eDT}}. 22 | } 23 | \examples{ 24 | if(interactive()){ 25 | library(shiny) 26 | ui <- inputUI('id') 27 | server <- function(input,output,session){ 28 | input <- inputServer("id", mtcars[1,]) 29 | observe({print(input())}) 30 | } 31 | shinyApp(ui, server) 32 | } 33 | 34 | } 35 | \author{ 36 | Jasper Schelfhout 37 | } 38 | -------------------------------------------------------------------------------- /editbl/man/inputUI.default.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinyInput.R 3 | \name{inputUI.default} 4 | \alias{inputUI.default} 5 | \title{UI part for modal with input fields for editing} 6 | \usage{ 7 | \method{inputUI}{default}(id, ...) 8 | } 9 | \arguments{ 10 | \item{id}{character module id} 11 | 12 | \item{...}{for compatibility with method} 13 | } 14 | \value{ 15 | HTML. A set of input fields corresponding to the given row. 16 | } 17 | \description{ 18 | UI part for modal with input fields for editing 19 | } 20 | \details{ 21 | The UI elements that have an id identical to a column name are used for updating the data. 22 | } 23 | \author{ 24 | Jasper Schelfhout 25 | } 26 | -------------------------------------------------------------------------------- /editbl/man/joinForeignTbl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/foreignTbl.R 3 | \name{joinForeignTbl} 4 | \alias{joinForeignTbl} 5 | \title{Merge a tbl with it a foreignTbl} 6 | \usage{ 7 | joinForeignTbl( 8 | tbl, 9 | foreignTbl, 10 | keepNA = TRUE, 11 | by = foreignTbl$by, 12 | copy = TRUE, 13 | type = c("inner", "left")[1] 14 | ) 15 | } 16 | \arguments{ 17 | \item{tbl}{\code{tbl}} 18 | 19 | \item{foreignTbl}{\code{list} as created by \code{\link{foreignTbl}}} 20 | 21 | \item{keepNA}{\code{logical} keep rows from tbl with NA keys.} 22 | 23 | \item{by}{named \code{character}, columns to join on.} 24 | 25 | \item{copy}{\code{logical}, whether or not to copy the \code{foreignTbl} to the source of argument \code{tbl} for joining.} 26 | 27 | \item{type}{\code{character(1)}, type of joint to perform. Can be 'inner' or 'left'.} 28 | } 29 | \value{ 30 | \code{tbl}, containing both columns from argument \code{tbl} and argument \code{foreignTbl}. 31 | } 32 | \description{ 33 | Merge a tbl with it a foreignTbl 34 | } 35 | \details{ 36 | see also \code{dplyr} join functions, for example \code{dplyr::left_join}. 37 | } 38 | \author{ 39 | Jasper Schelfhout 40 | } 41 | -------------------------------------------------------------------------------- /editbl/man/overwriteDefaults.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{overwriteDefaults} 4 | \alias{overwriteDefaults} 5 | \title{Overwrite default settings with provided settings} 6 | \usage{ 7 | overwriteDefaults(defaults, settings) 8 | } 9 | \arguments{ 10 | \item{defaults}{named character vector} 11 | 12 | \item{settings}{named character vector} 13 | } 14 | \value{ 15 | named character vector 16 | } 17 | \description{ 18 | Overwrite default settings with provided settings 19 | } 20 | \author{ 21 | Jasper Schelfhout 22 | } 23 | -------------------------------------------------------------------------------- /editbl/man/rollbackTransaction.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl.R 3 | \name{rollbackTransaction} 4 | \alias{rollbackTransaction} 5 | \title{Start a transaction for a tibble} 6 | \usage{ 7 | rollbackTransaction(tbl) 8 | } 9 | \arguments{ 10 | \item{tbl}{\code{tbl}} 11 | } 12 | \description{ 13 | Start a transaction for a tibble 14 | } 15 | \author{ 16 | Jasper Schelfhout 17 | } 18 | -------------------------------------------------------------------------------- /editbl/man/rowInsert.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{rowInsert} 4 | \alias{rowInsert} 5 | \title{Add a row to a table in the database.} 6 | \usage{ 7 | rowInsert(conn, table, values) 8 | } 9 | \arguments{ 10 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 11 | 12 | \item{table}{character} 13 | 14 | \item{values}{named list, row to add. Names are database column names. Unspecified columns will get database defaults.} 15 | } 16 | \value{ 17 | integer number of affected rows. 18 | } 19 | \description{ 20 | Add a row to a table in the database. 21 | } 22 | -------------------------------------------------------------------------------- /editbl/man/rowUpdate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{rowUpdate} 4 | \alias{rowUpdate} 5 | \title{Update rows in the database.} 6 | \usage{ 7 | rowUpdate(conn, table, values, where) 8 | } 9 | \arguments{ 10 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 11 | 12 | \item{table}{character} 13 | 14 | \item{values}{named list, values to be set. Names are database column names.} 15 | 16 | \item{where}{named list, values to filter on. Names are database column names. If NULL no filter is applied.} 17 | } 18 | \value{ 19 | integer number of affected rows. 20 | } 21 | \description{ 22 | Update rows in the database. 23 | } 24 | -------------------------------------------------------------------------------- /editbl/man/rows_delete.dtplyr_step.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dtplyr_step.R 3 | \name{rows_delete.dtplyr_step} 4 | \alias{rows_delete.dtplyr_step} 5 | \title{rows_delete implementation for data.table backends.} 6 | \usage{ 7 | \method{rows_delete}{dtplyr_step}(x, y, by = NULL, ..., unmatched, copy = FALSE, in_place = FALSE) 8 | } 9 | \arguments{ 10 | \item{x, y}{A pair of data frames or data frame extensions (e.g. a tibble). 11 | \code{y} must have the same columns of \code{x} or a subset.} 12 | 13 | \item{by}{An unnamed character vector giving the key columns. The key columns 14 | must exist in both \code{x} and \code{y}. Keys typically uniquely identify each row, 15 | but this is only enforced for the key values of \code{y} when \code{rows_update()}, 16 | \code{rows_patch()}, or \code{rows_upsert()} are used. 17 | 18 | By default, we use the first column in \code{y}, since the first column is 19 | a reasonable place to put an identifier variable.} 20 | 21 | \item{...}{Other parameters passed onto methods.} 22 | 23 | \item{unmatched}{For \code{rows_update()}, \code{rows_patch()}, and \code{rows_delete()}, 24 | how should keys in \code{y} that are unmatched by the keys in \code{x} be handled? 25 | 26 | One of: 27 | \itemize{ 28 | \item \code{"error"}, the default, will error if there are any keys in \code{y} that 29 | are unmatched by the keys in \code{x}. 30 | \item \code{"ignore"} will ignore rows in \code{y} with keys that are unmatched by the 31 | keys in \code{x}. 32 | }} 33 | 34 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 35 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 36 | same src as \code{x}. This allows you to join tables across srcs, but 37 | it is a potentially expensive operation so you must opt into it.} 38 | 39 | \item{in_place}{Should \code{x} be modified in place? This argument is only 40 | relevant for mutable backends (e.g. databases, data.tables). 41 | 42 | When \code{TRUE}, a modified version of \code{x} is returned invisibly; 43 | when \code{FALSE}, a new object representing the resulting changes is returned.} 44 | } 45 | \value{ 46 | An object of the same type as \code{x}. The order of the rows and columns of \code{x} 47 | is preserved as much as possible. The output has the following properties: 48 | \itemize{ 49 | \item \code{rows_update()} and \code{rows_patch()} preserve the number of rows; 50 | \code{rows_insert()}, \code{rows_append()}, and \code{rows_upsert()} return all existing 51 | rows and potentially new rows; \code{rows_delete()} returns a subset of the 52 | rows. 53 | \item Columns are not added, removed, or relocated, though the data may be 54 | updated. 55 | \item Groups are taken from \code{x}. 56 | \item Data frame attributes are taken from \code{x}. 57 | } 58 | 59 | If \code{in_place = TRUE}, the result will be returned invisibly. 60 | } 61 | \description{ 62 | rows_delete implementation for data.table backends. 63 | } 64 | \author{ 65 | Jasper Schelfhout 66 | } 67 | -------------------------------------------------------------------------------- /editbl/man/runDemoApp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{runDemoApp} 4 | \alias{runDemoApp} 5 | \title{Run a demo app} 6 | \usage{ 7 | runDemoApp(app = "database", ...) 8 | } 9 | \arguments{ 10 | \item{app}{demoApp to run. Options: database / mtcars / custom} 11 | 12 | \item{...}{arguments passed onto the demoApp} 13 | } 14 | \value{ 15 | An object that represents the app. Printing the object or passing it 16 | to \code{\link[shiny:runApp]{runApp()}} will run the app. 17 | } 18 | \description{ 19 | Run a demo app 20 | } 21 | \details{ 22 | These apps are for illustrative purposes. 23 | } 24 | \examples{ 25 | ## Only run this example in interactive R sessions 26 | if(interactive()){ 27 | 28 | # Database 29 | tmpFile <- tempfile(fileext = ".sqlite") 30 | file.copy(system.file("extdata", "chinook.sqlite", package = 'editbl'), tmpFile) 31 | 32 | conn <- connectDB(dbname = tmpFile) 33 | 34 | runDemoApp(app = "database", conn = conn) 35 | DBI::dbDisconnect(conn) 36 | 37 | unlink(tmpFile) 38 | 39 | # mtcars 40 | runDemoApp(app = "mtcars") 41 | 42 | # Any tibble of your liking 43 | runDemoApp(app = "custom", dplyr::tibble(iris)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /editbl/man/runDemoApp_DB.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{runDemoApp_DB} 4 | \alias{runDemoApp_DB} 5 | \title{Run a demo app} 6 | \usage{ 7 | runDemoApp_DB() 8 | } 9 | \value{ 10 | An object that represents the app. Printing the object or passing it 11 | to \code{\link[shiny:runApp]{runApp()}} will run the app. 12 | } 13 | \description{ 14 | Run a demo app 15 | } 16 | -------------------------------------------------------------------------------- /editbl/man/runDemoApp_custom.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{runDemoApp_custom} 4 | \alias{runDemoApp_custom} 5 | \title{Run a custom demo app} 6 | \usage{ 7 | runDemoApp_custom(x) 8 | } 9 | \arguments{ 10 | \item{x}{\code{tbl}} 11 | } 12 | \value{ 13 | An object that represents the app. Printing the object or passing it 14 | to \code{\link[shiny:runApp]{runApp()}} will run the app. 15 | } 16 | \description{ 17 | Run a custom demo app 18 | } 19 | -------------------------------------------------------------------------------- /editbl/man/runDemoApp_mtcars.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demoApp.R 3 | \name{runDemoApp_mtcars} 4 | \alias{runDemoApp_mtcars} 5 | \title{Run a demo app} 6 | \usage{ 7 | runDemoApp_mtcars() 8 | } 9 | \value{ 10 | An object that represents the app. Printing the object or passing it 11 | to \code{\link[shiny:runApp]{runApp()}} will run the app. 12 | } 13 | \description{ 14 | Run a demo app 15 | } 16 | -------------------------------------------------------------------------------- /editbl/man/runDevApp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/devApp.R 3 | \name{runDevApp} 4 | \alias{runDevApp} 5 | \title{Run a development app} 6 | \usage{ 7 | runDevApp() 8 | } 9 | \value{ 10 | An object that represents the app. Printing the object or passing it 11 | to \code{\link[shiny:runApp]{runApp()}} will run the app. 12 | } 13 | \description{ 14 | Run a development app 15 | } 16 | \details{ 17 | This app prints some of the server objects and has a button to interactively browse the code. 18 | This is useful for debugging and experimenting with new features. 19 | } 20 | -------------------------------------------------------------------------------- /editbl/man/selectInputDT_Server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/selectInputDT.R 3 | \name{selectInputDT_Server} 4 | \alias{selectInputDT_Server} 5 | \title{Server part to use a \code{\link[DT]{datatable}} as select input} 6 | \usage{ 7 | selectInputDT_Server( 8 | id, 9 | label = "", 10 | choices, 11 | selected = NULL, 12 | multiple = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{id}{\code{character(1)} same one as used in \code{\link{selectInputDT_UI}}} 17 | 18 | \item{label}{\code{character(1)}} 19 | 20 | \item{choices}{\code{data.frame}} 21 | 22 | \item{selected}{\code{data.frame} with rows available in \code{choices}.} 23 | 24 | \item{multiple}{\code{logical}. Whether or not multiple row selection is allowed} 25 | } 26 | \value{ 27 | A selection of rows from the \code{data.frame} provided under choices. 28 | } 29 | \description{ 30 | Server part to use a \code{\link[DT]{datatable}} as select input 31 | } 32 | \examples{ 33 | ## Only run this example in interactive R sessions 34 | if(interactive()){ 35 | ui <- selectInputDT_UI('id') 36 | data <- data.frame(id = 1:3, name = letters[1:3]) 37 | server <- function(input,output, session){ 38 | selected = selectInputDT_Server('id', choices = data, selected = data[1,] ) 39 | observe({print(selected())}) 40 | } 41 | shiny::shinyApp(ui, server) 42 | 43 | } 44 | } 45 | \seealso{ 46 | \code{shiny::selectInput}. This function can be more convenient for selecting rows 47 | with multiple columns. 48 | } 49 | \author{ 50 | Jasper Schelfhout 51 | } 52 | -------------------------------------------------------------------------------- /editbl/man/selectInputDT_UI.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/selectInputDT.R 3 | \name{selectInputDT_UI} 4 | \alias{selectInputDT_UI} 5 | \title{UI part of a DT select input} 6 | \usage{ 7 | selectInputDT_UI(id) 8 | } 9 | \arguments{ 10 | \item{id}{\code{character(1)} same one as used in \code{\link{selectInputDT_Server}}} 11 | } 12 | \value{ 13 | HTML 14 | } 15 | \description{ 16 | UI part of a DT select input 17 | } 18 | \examples{ 19 | ## Only run this example in interactive R sessions 20 | if(interactive()){ 21 | ui <- selectInputDT_UI('id') 22 | data <- data.frame(id = 1:3, name = letters[1:3]) 23 | server <- function(input,output, session){ 24 | selected = selectInputDT_Server('id', choices = data, selected = data[1,] ) 25 | observe({print(selected())}) 26 | } 27 | shiny::shinyApp(ui, server) 28 | 29 | } 30 | } 31 | \author{ 32 | Jasper Schelfhout 33 | } 34 | -------------------------------------------------------------------------------- /editbl/man/shinyInput.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinyInput.R 3 | \name{shinyInput} 4 | \alias{shinyInput} 5 | \title{Get a shiny input for a column of a \code{tbl}} 6 | \usage{ 7 | shinyInput(x, inputId, label, selected) 8 | } 9 | \arguments{ 10 | \item{x}{column} 11 | 12 | \item{inputId}{shiny input Id} 13 | 14 | \item{label}{\code{character(1)}} 15 | 16 | \item{selected}{object of class of x} 17 | } 18 | \value{ 19 | shiny input 20 | } 21 | \description{ 22 | Get a shiny input for a column of a \code{tbl} 23 | } 24 | \author{ 25 | Jasper Schelfhout 26 | } 27 | -------------------------------------------------------------------------------- /editbl/man/standardizeArgument_colnames.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{standardizeArgument_colnames} 4 | \alias{standardizeArgument_colnames} 5 | \title{Standardize colnames argument to the format of named character vector} 6 | \usage{ 7 | standardizeArgument_colnames(colnames, data) 8 | } 9 | \arguments{ 10 | \item{colnames}{if missing, the column names of the data; otherwise it can be 11 | an unnamed character vector of names you want to show in the table header 12 | instead of the default data column names; alternatively, you can provide a 13 | \emph{named} numeric or character vector of the form \code{'newName1' = i1, 14 | 'newName2' = i2} or \code{c('newName1' = 'oldName1', 'newName2' = 15 | 'oldName2', ...)}, where \code{newName} is the new name you want to show in 16 | the table, and \code{i} or \code{oldName} is the index of the current 17 | column name} 18 | 19 | \item{data}{\code{tbl}. The function will automatically cast to tbl if needed.} 20 | } 21 | \value{ 22 | named character vector 23 | } 24 | \description{ 25 | Standardize colnames argument to the format of named character vector 26 | } 27 | \author{ 28 | Jasper Schelfhout 29 | } 30 | -------------------------------------------------------------------------------- /editbl/man/standardizeArgument_editable.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{standardizeArgument_editable} 4 | \alias{standardizeArgument_editable} 5 | \title{Standardized editable argument to be in the form of a list} 6 | \usage{ 7 | standardizeArgument_editable(editable, data) 8 | } 9 | \arguments{ 10 | \item{editable}{\code{FALSE} to disable the table editor, or \code{TRUE} (or 11 | \code{"cell"}) to enable editing a single cell. Alternatively, you can set 12 | it to \code{"row"} to be able to edit a row, or \code{"column"} to edit a 13 | column, or \code{"all"} to edit all cells on the current page of the table. 14 | In all modes, start editing by doubleclicking on a cell. This argument can 15 | also be a list of the form \code{list(target = TARGET, disable = 16 | list(columns = INDICES))}, where \code{TARGET} can be \code{"cell"}, 17 | \code{"row"}, \code{"column"}, or \code{"all"}, and \code{INDICES} is an 18 | integer vector of column indices. Use the list form if you want to disable 19 | editing certain columns. You can also restrict the editing to accept only 20 | numbers by setting this argument to a list of the form \code{list(target = 21 | TARGET, numeric = INDICES)} where \code{INDICES} can be the vector of the 22 | indices of the columns for which you want to restrict the editing to 23 | numbers or \code{"all"} to restrict the editing to numbers for all columns. 24 | If you don't set \code{numeric}, then the editing is restricted to numbers 25 | for all numeric columns; set \code{numeric = "none"} to disable this 26 | behavior. It is also possible to edit the cells in text areas, which are 27 | useful for large contents. For that, set the \code{editable} argument to a 28 | list of the form \code{list(target = TARGET, area = INDICES)} where 29 | \code{INDICES} can be the vector of the indices of the columns for which 30 | you want the text areas, or \code{"all"} if you want the text areas for 31 | all columns. Of course, you can request the numeric editing for some 32 | columns and the text areas for some other columns by setting 33 | \code{editable} to a list of the form \code{list(target = TARGET, numeric 34 | = INDICES1, area = INDICES2)}. Finally, you can edit date cells with a 35 | calendar with \code{list(target = TARGET, date = INDICES)}; the target 36 | columns must have the \code{Date} type. If you don't set \code{date} in 37 | the \code{editable} list, the editing with the calendar is automatically 38 | set for all \code{Date} columns.} 39 | 40 | \item{data}{\code{tbl}. The function will automatically cast to tbl if needed.} 41 | } 42 | \value{ 43 | list of the form \code{list(target = foo, ...)} 44 | } 45 | \description{ 46 | Standardized editable argument to be in the form of a list 47 | } 48 | \author{ 49 | Jasper Schelfhout 50 | } 51 | -------------------------------------------------------------------------------- /editbl/man/whereSQL.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tbl_dbi.R 3 | \name{whereSQL} 4 | \alias{whereSQL} 5 | \title{Generate where sql} 6 | \usage{ 7 | whereSQL(conn, table, column, operator = "in", values = NULL) 8 | } 9 | \arguments{ 10 | \item{conn}{database connection object as given by \code{\link[DBI]{dbConnect}}.} 11 | 12 | \item{table}{character table name (or alias used in query)} 13 | 14 | \item{column}{character column of table} 15 | 16 | \item{operator}{character} 17 | 18 | \item{values}{character vector of values} 19 | } 20 | \value{ 21 | character sql 22 | } 23 | \description{ 24 | Generate where sql 25 | } 26 | \author{ 27 | Jasper Schelfhout 28 | } 29 | -------------------------------------------------------------------------------- /editbl/tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(editbl) 3 | 4 | test_check("editbl") 5 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-demoApp.R: -------------------------------------------------------------------------------- 1 | test_that("Starting demo app db works", { 2 | runDemoApp (app = "database") 3 | expect_true(TRUE) 4 | }) 5 | 6 | test_that("Starting demo app mtcars works", { 7 | runDemoApp (app = "mtcars") 8 | expect_true(TRUE) 9 | }) 10 | 11 | test_that("Starting demo app custom works", { 12 | runDemoApp(app = "custom", dplyr::tibble(iris)) 13 | expect_true(TRUE) 14 | }) -------------------------------------------------------------------------------- /editbl/tests/testthat/test-devApp.R: -------------------------------------------------------------------------------- 1 | test_that("Starting dev app db works", { 2 | runDevApp() 3 | expect_true(TRUE) 4 | }) -------------------------------------------------------------------------------- /editbl/tests/testthat/test-eDT.R: -------------------------------------------------------------------------------- 1 | test_that("passing on an empty tibble works", { 2 | data <- dplyr::as_tibble(sleep) 3 | data <- data[0,] 4 | 5 | # Try running the app manually 6 | ui <- eDTOutput("app") 7 | server <- function(input,output,session){ 8 | eDTServer(id = "app", data = reactive(data)) 9 | } 10 | shiny::shinyApp(ui, server) 11 | 12 | # Actual test 13 | shiny::testServer( 14 | app = eDTServer, 15 | args = list(data = data), 16 | expr = { 17 | session$setInputs(add = 1) 18 | session$setInputs(save = 1) 19 | session$setInputs(confirmCommit = 1) 20 | expect_true(nrow(rv$committedData) == 1) 21 | }) 22 | }) 23 | 24 | test_that("Editing a single table column works", { 25 | data <- dplyr::tibble(one_column = c(1,2)) 26 | 27 | # Try running the app manually 28 | ui <- eDTOutput("app") 29 | server <- function(input,output,session){ 30 | eDTServer(id = "app", data = reactive(data)) 31 | } 32 | shiny::shinyApp(ui, server) 33 | 34 | # Actual test 35 | shiny::testServer( 36 | app = eDTServer, 37 | args = list(data = data), 38 | expr = { 39 | session$setInputs(edit_row_1 = 1) 40 | session$setInputs(confirmCommit = 1) 41 | }) 42 | expect_true(TRUE) 43 | }) 44 | 45 | test_that("Deletion of a row works", { 46 | data <- dplyr::tibble(id = 1:2, name = letters[1:2]) 47 | 48 | shiny::testServer( 49 | app = eDTServer, 50 | args = list(data = data), 51 | expr = { 52 | session$flushReact() 53 | test_id = rv$modifiedData[1,"_editbl_identity"] # generated uuid 54 | session$setInputs(current_id = paste0('delete_row_', test_id)) 55 | session$setInputs(delete = 1) 56 | session$flushReact() 57 | session$setInputs(confirmCommit = 1) 58 | expect_equal(nrow(result()),1) 59 | } 60 | ) 61 | }) 62 | 63 | test_that("Can not delete row when canDeleteRow blocks it", { 64 | data <- dplyr::tibble(id = 1:2, name = letters[1:2]) 65 | 66 | shiny::testServer( 67 | app = eDTServer, 68 | args = list(data = data, canDeleteRow = function(...){FALSE}), 69 | expr = { 70 | session$flushReact() 71 | test_id = rv$modifiedData[1,"_editbl_identity"] # generated uuid 72 | session$setInputs(current_id = paste0('delete_row_', test_id)) 73 | session$setInputs(delete = 1) 74 | session$flushReact() 75 | session$setInputs(confirmCommit = 1) 76 | expect_equal(nrow(result()),2) 77 | } 78 | ) 79 | }) 80 | 81 | test_that("working with selectInputDT works.", { 82 | songs <- tibble::tibble( 83 | song = c("Never gonna give you up", "Self Esteem"), 84 | artist_id = c(1,2) 85 | ) 86 | 87 | artists <- dplyr::tibble( 88 | artist_id = c(1,2), 89 | first_name = c("Rick", "Dexter"), 90 | last_name = c('Astley', "Holland") 91 | ) 92 | 93 | # Try running the app manually 94 | ui <- eDTOutput("app") 95 | server <- function(input,output,session){ 96 | eDTServer(id = "app", 97 | data = songs, 98 | foreignTbls = list( 99 | foreignTbl(songs, artists, by = "artist_id", naturalKey = c("first_name", "last_name")) 100 | ), 101 | columnOrder = c("artist_id", "last_name", "first_name", "song") 102 | ) 103 | } 104 | 105 | # Test if using edit a second time still works 106 | # Reactivity problem. 107 | # TODO: write proper tests 108 | 109 | expect_true(TRUE) 110 | 111 | shiny::shinyApp(ui, server) 112 | }) 113 | 114 | test_that("Row dragging works when filter is on", { 115 | ui <- eDTOutput("app") 116 | server <- function(input,output,session){ 117 | eDTServer(id = "app", 118 | data = mtcars, 119 | filter = "bottom") 120 | } 121 | 122 | # Test 123 | # 1. add on a filter for a column 124 | # 2. drag a cell using making use of the autofill extension 125 | # 3. remove the filter 126 | # 4. check if the same cell is still modified 127 | # TODO: try to encode this 128 | expect_true(TRUE) 129 | 130 | shiny::shinyApp(ui, server) 131 | }) 132 | 133 | test_that("Can support all data types",{ 134 | df = tibble( 135 | integer = 1L, 136 | double = 0.5, 137 | time = as.POSIXct('2020-01-01 01:02:03'), 138 | date = as.Date('2020-01-01') 139 | ) 140 | ui <- eDTOutput("app") 141 | server <- function(input,output,session){ 142 | eDTServer(id = "app",data = df) 143 | } 144 | 145 | # This just ensures the app doesn't crash on these different 146 | # data types 147 | expect_true(TRUE) 148 | 149 | shiny::shinyApp(ui, server) 150 | }) 151 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-foreignTbl.R: -------------------------------------------------------------------------------- 1 | test_that("passing on a foreign tibble works", { 2 | a <- tibble::tibble( 3 | a = c(1,2,3), 4 | key1 = c(NA,2,4), 5 | key2 = c(NA,2,4)) 6 | 7 | b <- foreignTbl( 8 | a, 9 | tibble::tibble( 10 | b = c("a", "b", "c"), 11 | key1 = c(1,2,3), 12 | key2 = c(1,2,3) 13 | ), 14 | naturalKey = "b" 15 | ) 16 | 17 | # Try running the app manually 18 | ui <- eDTOutput("app") 19 | server <- function(input,output,session){ 20 | eDT(id = "app", 21 | data = reactive(a), 22 | foreignTbls = list(b), 23 | options = list(columnDefs = list(list(visible=FALSE, targets="key2")))) 24 | } 25 | shiny::shinyApp(ui, server) 26 | 27 | # Actual test 28 | shiny::testServer( 29 | app = eDTServer, 30 | args = list(data = reactive(a), foreignTbls = list(b)), 31 | expr = { 32 | expect_true(TRUE) 33 | }) 34 | }) 35 | 36 | test_that("Can use foreign tibbles to fill in non natural key values.",{ 37 | tbl <- tibble::tibble( 38 | a = c(1,2), 39 | key1 = as.double(c(NA,NA)), 40 | key2 = as.double(c(NA,NA)) 41 | ) 42 | 43 | merged_tbl <- cbind(tbl, tibble( 44 | b = c("b1", "b2"), 45 | c = c("c1", "c2"))) 46 | 47 | 48 | b <- foreignTbl( 49 | tbl, 50 | tibble::tibble( 51 | b = c("b1", "b2", "b3"), 52 | key1 = c(1,2,3) 53 | ), 54 | by = "key1", 55 | naturalKey = "b" 56 | ) 57 | 58 | c <- foreignTbl( 59 | tbl, 60 | tibble::tibble( 61 | c = c("c1", "c2", "c3"), 62 | key2 = c(1,2,3) 63 | ), 64 | by = "key2", 65 | naturalKey = "c" 66 | ) 67 | 68 | foreignTbls = list(b, c) 69 | 70 | result <- fillDeductedColumns(merged_tbl, foreignTbls) 71 | 72 | expect_equal(result$key1, c(1,2)) 73 | expect_equal(result$key2, c(1,2)) 74 | }) 75 | 76 | test_that("Contradicting foreign tibbles give error when filling in data.",{ 77 | tbl <- tibble::tibble( 78 | a = c(1,2), 79 | key = as.double(c(NA,NA)) 80 | ) 81 | 82 | merged_tbl <- tibble::tibble(cbind(tbl, 83 | tibble::tibble(color = c("blue", "orange")) 84 | )) 85 | 86 | b <- foreignTbl( 87 | tbl, 88 | tibble::tibble( 89 | color = c("blue", "orange"), 90 | key = c(1,2) 91 | ), 92 | by = "key", 93 | naturalKey = "color" 94 | ) 95 | 96 | c <- foreignTbl( 97 | tbl, 98 | tibble::tibble( 99 | color = c("blue", "purple"), 100 | key = c(2,1) 101 | ), 102 | by = "key", 103 | naturalKey = "color" 104 | ) 105 | 106 | expect_error({fillDeductedColumns(tbl, foreignTbls = list(b,c))}) 107 | expect_error({fillDeductedColumns(tbl, foreignTbls = list(c,b))}) 108 | 109 | }) 110 | 111 | test_that("Empty foreign tibbles list returns given tibble",{ 112 | tbl <- tibble::tibble( 113 | a = c(1,2), 114 | key = as.double(c(NA,NA)), 115 | color = c("blue", "orange") 116 | ) 117 | 118 | result <- fillDeductedColumns(tbl, foreignTbls = list()) 119 | expect_equal(result, tbl) 120 | }) 121 | 122 | test_that("checkForeignTbls throws error",{ 123 | 124 | tbl <- tibble::tibble( 125 | a = c(1,2, 3), 126 | key = c(1,2, 3) 127 | ) 128 | 129 | merged_tbl <- cbind(tbl, 130 | tibble::tibble(color = c("blue", "orange", "purple")) 131 | ) 132 | 133 | b <- foreignTbl( 134 | tbl, 135 | tibble::tibble( 136 | color = c("blue", "orange"), 137 | key = c(1,2) 138 | ), 139 | by = "key", 140 | naturalKey = "color" 141 | ) 142 | 143 | expect_error(checkForeignTbls(tbl = merged_tbl,foreignTbls = list(b))) 144 | }) 145 | 146 | test_that("joinForeignTbl() type 'inner' works.",{ 147 | a <- tibble::tibble( 148 | a = c(1,2,3), 149 | key1 = c(NA,2,4), 150 | key2 = c(NA,2,4)) 151 | 152 | b <- tibble::tibble( 153 | b = c("a", "b", "c"), 154 | key1 = c(1,2,3), 155 | key2 = c(1,2,3) 156 | ) 157 | 158 | foreignTbl <- foreignTbl(a,b) 159 | 160 | result <- joinForeignTbl(a, foreignTbl, type = 'inner') 161 | 162 | # NA keys in 'a' are expected to stay. 163 | expect_equal(colnames(result), c("a", "key1", "key2", "b")) 164 | expect_true(nrow(result) == 2) 165 | }) 166 | 167 | test_that("joinForeignTbl() type 'left' works.",{ 168 | a <- tibble::tibble( 169 | a = c(1,2,3), 170 | key1 = c(NA,2,4), 171 | key2 = c(NA,2,4)) 172 | 173 | b <- tibble::tibble( 174 | b = c("a", "b", "c"), 175 | key1 = c(1,2,3), 176 | key2 = c(1,2,3) 177 | ) 178 | 179 | foreignTbl <- foreignTbl(a,b) 180 | 181 | result <- joinForeignTbl(a, foreignTbl, type = 'left') 182 | 183 | expect_equal(colnames(result), c("a", "key1", "key2", "b")) 184 | expect_true(nrow(result) == 3) 185 | }) 186 | 187 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-selectInputDT.R: -------------------------------------------------------------------------------- 1 | test_that("selectInputDT works", { 2 | # Try running the app manually 3 | df <- data.frame( 4 | id = 1:10, 5 | name = letters[1:10] 6 | ) 7 | 8 | ui <- selectInputDT_UI("app") 9 | 10 | server <- function(input,output,session){ 11 | selectInputDT_Server(id = "app", label = "test", choices = df, selected = df[4,]) 12 | } 13 | shiny::shinyApp(ui, server) 14 | 15 | # Actual test 16 | shiny::testServer( 17 | app = selectInputDT_Server, 18 | args = list(id = "app", label = "test", choices = df, selected = df[4,]), 19 | expr = { 20 | session$setInputs(DT_rows_selected = 2) 21 | expect_equal(data_selection_first()[input$DT_rows_selected,]$name, "a") # Not 'b' because selection always comes first 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-shinyInput.R: -------------------------------------------------------------------------------- 1 | test_that("Input generation for single column data.frame works", { 2 | data <- data.frame(x = as.numeric(1)) 3 | 4 | # Try running the app manually 5 | ui <- inputUI.default(id = "app") 6 | server <- function(input,output,session){ 7 | inputServer.default(id = "app", data = reactive(data)) 8 | } 9 | shiny::shinyApp(ui, server) 10 | 11 | # Actual test 12 | shiny::testServer( 13 | app = inputServer.default, 14 | args = list(data = data), 15 | expr = { 16 | session$flushReact() 17 | session$setInputs(x = 2) 18 | expect_equal(newData(), data.frame(x = 2)) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-tbl.R: -------------------------------------------------------------------------------- 1 | conn <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") 2 | test_df <- data.frame( 3 | id = 1:3, 4 | name = letters[1:3] 5 | ) 6 | DBI::dbCreateTable(conn, "test", test_df) 7 | DBI::dbAppendTable(conn, "test", test_df) 8 | 9 | test_tbl <- dplyr::tbl(conn, "test") 10 | 11 | test_that("beginTransaction and commitTransaction works for tbl_dbi",{ 12 | beginTransaction(test_tbl) 13 | 14 | # rows_delete will start nested transaction if 'y' is not of same source as 'x' 15 | dbplyr::db_copy_to(conn, table = 'y1_temp', values = data.frame(id = 2), in_transaction = FALSE) 16 | y <- tbl(conn, 'y1_temp') 17 | 18 | rows_delete(x = test_tbl, y = y, in_place = TRUE, copy = TRUE, unmatched = "ignore") 19 | commitTransaction(test_tbl) 20 | result <- DBI::dbGetQuery(conn, "select * from test") 21 | expect_true(nrow(result) == 2) 22 | }) 23 | 24 | test_that("beginTransaction and rollbackTransaction works for tbl_dbi",{ 25 | beginTransaction(test_tbl) 26 | 27 | # rows_delete will start nested transaction if 'y' is not of same source as 'x' 28 | dbplyr::db_copy_to(conn, table = 'y2_temp', values = data.frame(id = 1), in_transaction = FALSE) 29 | y <- tbl(conn, 'y2_temp') 30 | rows_delete(x = test_tbl, y = y, in_place = TRUE, copy = TRUE, unmatched = "ignore") 31 | 32 | rollbackTransaction(test_tbl) 33 | result <- DBI::dbGetQuery(conn, "select * from test") 34 | expect_true(nrow(result) == 2) 35 | }) 36 | 37 | DBI::dbDisconnect(conn) 38 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-tbl_dbi.R: -------------------------------------------------------------------------------- 1 | conn <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") 2 | test_df <- data.frame( 3 | id = 1:3, 4 | name = letters[1:3] 5 | ) 6 | DBI::dbCreateTable(conn, "test", test_df) 7 | DBI::dbAppendTable(conn, "test", test_df) 8 | 9 | test_tbl <- dplyr::tbl(conn, "test") 10 | 11 | test_that("e_rows_insert.tbl_dbi works",{ 12 | e_rows_insert.tbl_dbi(test_tbl, data.frame(id = 4, name = "insert"), in_place = TRUE) 13 | result <- DBI::dbGetQuery(conn, "select * from test where id = 4")$name 14 | expect_equal(result,'insert') 15 | }) 16 | 17 | test_that("e_rows_update.tbl_dbi works",{ 18 | e_rows_update(test_tbl, data.frame(id = 1, name = "update"), by = "id", in_place = TRUE) 19 | result <- DBI::dbGetQuery(conn, "select * from test where id = 1")$name 20 | expect_equal(result,'update') 21 | }) 22 | 23 | DBI::dbDisconnect(conn) 24 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-tbl_df.R: -------------------------------------------------------------------------------- 1 | test_that("e_rows_update can update a row in a data.frame", { 2 | x <- mtcars 3 | x$id <- seq_len(nrow(x)) 4 | y <- x[1,] 5 | y$mpg <- 1000 6 | 7 | result <- e_rows_update(x = x, y = y, by = "id") 8 | expect_equal(result[1,"mpg"][[1]], 1000) 9 | }) 10 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-tbl_dtplyr_step.R: -------------------------------------------------------------------------------- 1 | test_that("e_rows_insert add row to data.table", { 2 | x <- dtplyr::lazy_dt(data.table::data.table(mtcars)) 3 | y <- mtcars[1,] 4 | result <- e_rows_insert(x,y) 5 | expect_equal(nrow(x) + 1, nrow(result)) 6 | }) 7 | 8 | test_that("e_rows_update can update a row in a data.table", { 9 | dt <- data.table::data.table(mtcars) 10 | dt$id <- seq_len(nrow(dt)) 11 | x <- dtplyr::lazy_dt(dt) 12 | y <- dt[1,] 13 | y$mpg <- 1000 14 | 15 | result <- e_rows_update(x = x, y = y, by = "id") 16 | expect_equal(data.table::as.data.table(result)[1,"mpg"][[1]], 1000) 17 | }) 18 | 19 | test_that("rows_delete can delete a row in a data.table", { 20 | dt <- data.table::data.table(mtcars) 21 | dt$id <- seq_len(nrow(dt)) 22 | x <- dtplyr::lazy_dt(dt) 23 | y <- dt[1,] 24 | y$mpg <- 1000 25 | 26 | result <- rows_delete(x = x, y = y, by = "id") 27 | expect_equal(nrow(result), nrow(dt) - 1) 28 | }) 29 | 30 | -------------------------------------------------------------------------------- /editbl/tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | # Data.frame with multiple data types 2 | df_typed <- data.frame( 3 | char = "x", 4 | num = 1, 5 | int = 1L, 6 | date = as.Date('2023-01-01'), 7 | time = as.POSIXct('2023-01-01 00:00:01', tz = "UTC") 8 | ) 9 | 10 | df_char <- data.frame( 11 | char = "x", 12 | num = "1", 13 | int = "1", 14 | date = "2023-01-01", 15 | time = '2023-01-01 00:00:01' 16 | ) 17 | 18 | df_template <- df_typed[0,] 19 | 20 | 21 | 22 | # Actual tests 23 | test_that("coerceColumns works",{ 24 | result <- coerceColumns(df_template, df_char) 25 | expect_equal( 26 | result, 27 | df_typed 28 | ) 29 | }) 30 | 31 | test_that('coalesce returns first value',{ 32 | result <- coalesce(x = 2, y = 3) 33 | expect_equal(result,2) 34 | }) 35 | 36 | test_that('coalesce skips null',{ 37 | result <- coalesce(x = NULL, y = 3, z = 4) 38 | expect_equal(result,3) 39 | }) 40 | 41 | test_that('coalesce gives NULL when only NULL available',{ 42 | result <- coalesce(x = NULL, y = NULL) 43 | expect_equal(result,NULL) 44 | }) 45 | 46 | test_that('coalesce gives when no parameters available',{ 47 | result <- coalesce() 48 | expect_equal(result,NULL) 49 | }) 50 | 51 | test_that('castForDisplay gives when no parameters available',{ 52 | result <- castForDisplay(df_typed) 53 | int <- c("int") 54 | notInt <- setdiff(colnames(df_typed), "int") 55 | expect_equal(result[,notInt],df_char[,notInt]) 56 | expect_equal(result[,int],df_typed[,int]) 57 | }) 58 | 59 | test_that("castToTbl works for data.frame",{ 60 | result <- castToTbl(df_typed) 61 | expect_equal(result, tibble::tibble( 62 | char = "x", 63 | num = 1, 64 | int = 1L, 65 | date = as.Date("2023-01-01"), 66 | time = as.POSIXct("2023-01-01 00:00:01", tz = "UTC") 67 | )) 68 | }) 69 | 70 | test_that("castFromTbl can cast to data.frame",{ 71 | result <- castFromTbl(tibble::as_tibble(mtcars), mtcars) 72 | expect_true(is.data.frame(result)) 73 | expect_equal(colnames(result), colnames(mtcars)) 74 | }) 75 | 76 | test_that("castFromTbl can cast to data.table",{ 77 | result <- castFromTbl(tibble::as_tibble(mtcars), data.table::as.data.table(mtcars)) 78 | expect_true(data.table::is.data.table(result)) 79 | expect_equal(colnames(result), colnames(mtcars)) 80 | }) 81 | 82 | test_that("castToTemplate can cast from data.frame to data.table",{ 83 | result <- castToTemplate(x = iris, template = data.table::data.table(iris)) 84 | expect_equal(result, data.table::as.data.table(iris)) 85 | }) 86 | 87 | test_that("castToTemplate can cast from data.frame to data.table with empty template",{ 88 | result <- castToTemplate(x = iris, template = data.table::data.table(iris[0,])) 89 | expect_equal(result, data.table::as.data.table(iris)) 90 | }) 91 | 92 | test_that("castToTemplate can cast from data.frame to tbl",{ 93 | result <- castToTemplate(x = iris, template = dplyr::as_tibble(iris)) 94 | expect_equal(result, dplyr::as_tibble(iris)) 95 | }) 96 | 97 | test_that("castToTemplate can cast from data.frame to tbl with empty template",{ 98 | result <- castToTemplate(x = iris, template = dplyr::as_tibble(iris[0,])) 99 | expect_equal(result, dplyr::as_tibble(iris)) 100 | }) 101 | 102 | test_that("castToTemplate can cast from tbl to data.frame",{ 103 | result <- castToTemplate(x = dplyr::as_tibble(iris), template = iris) 104 | expect_equal(result, iris) 105 | }) 106 | 107 | test_that("castToTemplate can cast from tbl to data.frame with empty template",{ 108 | result <- castToTemplate(x = dplyr::as_tibble(iris), template = iris[0,]) 109 | expect_equal(result, iris) 110 | }) 111 | 112 | test_that("castToTemplate can cast from data.table to data.frame",{ 113 | result <- castToTemplate(x = data.table::as.data.table(iris), template = iris) 114 | expect_equal(result, iris) 115 | }) 116 | 117 | test_that("castToTemplate can cast from data.table to data.frame with empty template",{ 118 | result <- castToTemplate(x = data.table::as.data.table(iris), template = iris[0,]) 119 | expect_equal(result, iris) 120 | }) 121 | 122 | test_that("castToTemplate can cast from data.table to tbl",{ 123 | result <- castToTemplate(x = data.table::as.data.table(iris), template = dplyr::as_tibble(iris)) 124 | expect_equal(result, dplyr::as_tibble(iris)) 125 | }) 126 | 127 | test_that("castToTemplate can cast from data.table to tbl with empty template",{ 128 | result <- castToTemplate(x = data.table::as.data.table(iris), template = dplyr::as_tibble(iris[0,])) 129 | expect_equal(result, dplyr::as_tibble(iris)) 130 | }) 131 | 132 | test_that("castToTemplate can cast from tbl to data.table",{ 133 | result <- castToTemplate(x = dplyr::as_tibble(iris), template = data.table::as.data.table(iris)) 134 | expect_equal(result, data.table::as.data.table(iris)) 135 | }) 136 | 137 | test_that("castToTemplate can cast from tbl to data.table with empty template",{ 138 | result <- castToTemplate(x = iris, template = data.table::as.data.table(iris[0,])) 139 | expect_equal(result, data.table::as.data.table(iris)) 140 | }) 141 | 142 | test_that("standardizeArgument_colnames returns named character for named integer",{ 143 | result <- standardizeArgument_colnames(c("test" = 1), dplyr::as_tibble(mtcars)) 144 | expect_equal(result, c("test" = "mpg")) 145 | }) 146 | 147 | test_that("standardizeArgument_colnames returns named character for unnamed character",{ 148 | result <- standardizeArgument_colnames(c("test"), dplyr::as_tibble(mtcars)) 149 | expect_equal(result, c("test" = "mpg")) 150 | }) 151 | 152 | test_that("standardizeArgument_colnames returns named character for named character",{ 153 | result <- standardizeArgument_colnames(c("test" = "mpg"), dplyr::as_tibble(mtcars)) 154 | expect_equal(result, c("test" = "mpg")) 155 | }) 156 | 157 | test_that("standardizeArgument_editable TRUE",{ 158 | result <- standardizeArgument_editable(TRUE) 159 | expect_equal(result, list(target = "cell")) 160 | }) 161 | 162 | test_that("standardizeArgument_editable FALSE",{ 163 | result <- standardizeArgument_editable(FALSE, mtcars) 164 | expect_equal(result, list(target = "cell", disable = list(columns = 1:11))) 165 | }) 166 | 167 | test_that("standardizeArgument_editable 'row'",{ 168 | result <- standardizeArgument_editable('row') 169 | expect_equal(result, list(target = "row")) 170 | }) 171 | 172 | test_that("standardizeArgument_editable list",{ 173 | result <- standardizeArgument_editable(list(target = "cell", numeric = 2)) 174 | expect_equal(result, list(target = "cell", numeric = 2)) 175 | }) 176 | 177 | test_that("standardizeArgument_editable Error",{ 178 | expect_error(standardizeArgument_editable(1)) 179 | }) 180 | 181 | test_that('overwriteDefaults works',{ 182 | x = c(a = 'a', b = 'b') 183 | y = c(a='c') 184 | result <- overwriteDefaults(x,y) 185 | expect_equal(result, c(a = 'c', b = 'b')) 186 | }) 187 | -------------------------------------------------------------------------------- /editbl/vignettes/howto_relational_db_dm.rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Edit tables in a relational database - dm package" 3 | date: "`r Sys.Date()`" 4 | output: 5 | rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteEncoding{UTF-8} 8 | %\VignetteIndexEntry{How to: dm package integration for database editor.} 9 | %\VignetteEngine{knitr::rmarkdown} 10 | --- 11 | 12 | 13 | If it is your first time using 14 | `editbl`, make sure to first read the introductory vignette 15 | on how to work with relational databases (`vignette("howto_relational_db")`). 16 | 17 | This document describes how make use of the 18 | [{dm} package](https://CRAN.R-project.org/package=dm). This package is 19 | useful to extract relational data models out of a database into R. Which can 20 | help in setting up a correct configuration for `editbl`. 21 | 22 | ```{r echo = TRUE, results = 'hide'} 23 | library(dplyr) 24 | library(shiny) 25 | library(editbl) 26 | library(dm) 27 | ``` 28 | 29 | ## Creating a database connection 30 | 31 | The first thing you need is a database connection. Here we connect to an 32 | [sqlite](https://en.wikipedia.org/wiki/SQLite) file, which is a portable 33 | database format. 34 | 35 | ```{r echo = TRUE, results = 'hide'} 36 | tmpFile <- tempfile(fileext = ".sqlite") 37 | file.copy(system.file("extdata", "chinook.sqlite", package = 'editbl'), tmpFile) 38 | conn <- DBI::dbConnect( 39 | dbname = tmpFile, 40 | drv = RSQLite::SQLite() 41 | ) 42 | ``` 43 | 44 | ## Setting up the data model 45 | 46 | ```{r echo = TRUE, results = 'hide'} 47 | dm <- dm::dm_from_con(conn, learn_keys = FALSE) 48 | ``` 49 | 50 | For some databases this is all you need to do. 51 | Currently `dm` can not (yet) learn the keys from SQLite. 52 | Thus we still manually have to specify the primary and foreign keys. 53 | ```{r echo = TRUE, results = 'hide'} 54 | dm <- dm %>% 55 | dm_add_pk(Artist, ArtistId) %>% 56 | dm_add_pk(Album, AlbumId) %>% 57 | dm_add_pk(Customer, CustomerId) %>% 58 | dm_add_pk(Employee, EmployeeId) %>% 59 | dm_add_pk(Genre, GenreId) %>% 60 | dm_add_pk(Invoice, InvoiceId) %>% 61 | dm_add_pk(InvoiceLine, InvoiceLineId) %>% 62 | dm_add_pk(MediaType, MediaTypeId) %>% 63 | dm_add_pk(Playlist, PlaylistId) %>% 64 | dm_add_pk(PlaylistTrack, c(PlaylistId, TrackId)) %>% 65 | dm_add_pk(Track, TrackId) 66 | 67 | ``` 68 | 69 | ```{r echo = TRUE, results = 'hide'} 70 | dm <- dm %>% 71 | dm_add_fk( 72 | table = Album, 73 | columns = ArtistId, 74 | ref_table = Artist) %>% 75 | dm_add_fk( 76 | table = Invoice, 77 | columns = CustomerId, 78 | ref_table = Customer) %>% 79 | dm_add_fk( 80 | table = InvoiceLine, 81 | columns = InvoiceId, 82 | ref_table = Invoice) %>% 83 | dm_add_fk( 84 | table = InvoiceLine, 85 | columns = TrackId, 86 | ref_table = Track) %>% 87 | dm_add_fk( 88 | table = PlaylistTrack, 89 | columns = TrackId, 90 | ref_table = Track) %>% 91 | dm_add_fk( 92 | table = PlaylistTrack, 93 | columns = PlaylistId, 94 | ref_table = Playlist) %>% 95 | dm_add_fk( 96 | table = Track, 97 | columns = AlbumId, 98 | ref_table = Album) %>% 99 | dm_add_fk( 100 | table = Track, 101 | columns = MediaTypeId, 102 | ref_table = MediaType) %>% 103 | dm_add_fk( 104 | table = Track, 105 | columns = GenreId, 106 | ref_table = Genre) 107 | ``` 108 | 109 | ## Fully fletched table editor 110 | 111 | A relational database consists out of many normalized tables. This is a perfect 112 | model for storing data, since it avoids duplicate information. However, it 113 | often leads to rather incomprehensible tables with a lot of 'id' columns. The 114 | goal of this editor is therefore to give people the opportunity to edit a table 115 | in its 'flat' form. Meaning you join all tables with additional information 116 | based on these 'id' keys. See also this function of `dm`: 117 | 118 | ```{r} 119 | dm::dm_flatten_to_tbl(dm, "Album", .recursive = TRUE) 120 | ``` 121 | As you can see, providing the `ArtistName` to a user is way more convenient than 122 | just the `ArtistId`. 123 | 124 | `editbl::eDT` can handle similar joins by its 125 | `foreignTbls` argument. Let us define a function that 126 | extracts the needed information from a `dm` object. 127 | 128 | ```{r} 129 | getForeignTbls <- function(dm, table){ 130 | dm_fks <- dm::dm_get_all_fks(dm) 131 | dm_fks <- dm_fks[dm_fks$child_table == table,] 132 | tbl_list <- dm::dm_get_tables(dm) 133 | 134 | foreignTbls <- lapply(seq_len(nrow(dm_fks)), function(i){ 135 | r <- dm_fks[i,] 136 | x <- tbl_list[r$child_table][[1]] 137 | y <- dm::dm_flatten_to_tbl(dm, !!(r$parent_table), .recursive = TRUE) 138 | 139 | child_fks <- unlist(r$child_fk_cols) 140 | parent_fks <- unlist(r$parent_key_cols) 141 | 142 | # Renaming of parent colums to avoid naming conflicts 143 | # Done a bit heuristically here for convenience. 144 | lookup <- parent_fks 145 | names(lookup) <- child_fks 146 | other_parent_cols <- setdiff(colnames(y), parent_fks) 147 | names(other_parent_cols) <- paste(r$parent_table, other_parent_cols, sep = '.') 148 | lookup <- c(lookup, other_parent_cols) 149 | y <- y %>% dplyr::rename(all_of(lookup)) 150 | 151 | editbl::foreignTbl( 152 | x = x, 153 | y = y, 154 | by = child_fks, 155 | naturalKey = colnames(y) 156 | ) 157 | 158 | }) 159 | foreignTbls 160 | } 161 | 162 | ``` 163 | 164 | Next, let's use this to build a shiny app. 165 | 166 | ```{r} 167 | dbUI <- function(id) { 168 | ns <- NS(id) 169 | fluidPage( 170 | uiOutput(outputId = ns('selectUI')), 171 | eDTOutput(id = ns('DT')) 172 | ) 173 | } 174 | 175 | dbServer <- function(id, dm) { 176 | moduleServer( 177 | id, 178 | function(input, output, session) { 179 | ns <- session$ns 180 | 181 | tables <- dm::dm_get_tables(dm) 182 | 183 | output$selectUI <- renderUI({ 184 | selectInput(ns('table'), label = 'table', choices = names(tables)) 185 | }) 186 | 187 | data <- reactive({ 188 | req(input$table) 189 | tables[input$table][[1]] 190 | }) 191 | 192 | foreignTbls <- reactive({ 193 | req(input$table) 194 | getForeignTbls(dm, input$table) 195 | }) 196 | 197 | eDT( 198 | id = "DT", 199 | data = data, 200 | foreignTbls = foreignTbls, 201 | in_place = TRUE 202 | ) 203 | 204 | invisible() 205 | } 206 | ) 207 | } 208 | 209 | ``` 210 | 211 | 212 | ```{r, screenshot.opts = list(vwidth = 700, vheight = 500, delay = 1), screenshot.alt = 'screenshots/howto_relational_db_dm_1.png'} 213 | shiny::shinyApp( 214 | ui = dbUI('id'), 215 | server = function(input, output,session){ 216 | dbServer('id', dm) 217 | }) 218 | ``` 219 | 220 | As you click the 'edit' button, you will notice you can now select rows from the 221 | referenced tables. This makes it easier to navigate compared to just having 222 | id's to work with. 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /editbl/vignettes/howto_row_level_access.rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Row level access" 3 | date: "`r Sys.Date()`" 4 | output: 5 | rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteEncoding{UTF-8} 8 | %\VignetteIndexEntry{How to: implement row level access.} 9 | %\VignetteEngine{knitr::rmarkdown} 10 | --- 11 | 12 | Sometimes you do not want to give a user access to the entire dataset. 13 | You can either hide rows or give read-only access. 14 | 15 | ## Hide rows completely 16 | 17 | In this example we only allow user Mickey to see his own row. 18 | 19 | We simply can use `dplyr::filter()` on the table. 20 | Note that this is most useful if you combine this with backends that support 21 | `in_place` editing. E.g. you can retrieve only a subset 22 | of rows from a database and specifically modify those. 23 | Take a look at the 'relational database' vignettes for more information 24 | on how to work with a database. 25 | 26 | ```{r, screenshot.opts = list(vwidth = 700, vheight = 500), , screenshot.alt = 'screenshots/howto_row_level_access_1.png'} 27 | library(editbl) 28 | library(shiny) 29 | conn <- DBI::dbConnect(RSQLite::SQLite(), "") 30 | df <- data.frame( 31 | user = c("Albert","Donald","Mickey"), 32 | email = c('albert@einstein.com', 'donald@duck.com', 'mickey@mouse.com') 33 | ) 34 | DBI::dbWriteTable(conn, "characters", df) 35 | tibble <- dplyr::tbl(conn, 'characters') 36 | 37 | CURRENT_USER = 'Mickey' 38 | 39 | shiny::shinyApp( 40 | ui = editbl::eDTOutput('id'), 41 | server = function(input, output,session){ 42 | result <- eDT(id='id', 43 | data = tibble %>% filter(user == CURRENT_USER), 44 | in_place = TRUE 45 | ) 46 | }) 47 | 48 | print(tibble) 49 | 50 | DBI::dbDisconnect(conn) 51 | ``` 52 | 53 | ## Read-only access 54 | 55 | In this example we only allow user Mickey to modify his own row. In contrast to 56 | the previous example, he can still read data from others. 57 | 58 | The arguments `canEditRow` and `canDeleteRow` can be used 59 | to specify logic describing if modfications are allowed. 60 | The passed on logic shoud be a function with the argument `row`. This is a 61 | single row of the displayed table in datatype `tibble`. 62 | 63 | ```{r, screenshot.opts = list(vwidth = 700, vheight = 500), , screenshot.alt = 'screenshots/howto_row_level_access_2.png'} 64 | library(editbl) 65 | df <- tibble::tibble( 66 | user = c("Albert","Donald","Mickey"), 67 | email = c('albert@einstein.com', 'donald@duck.com', 'mickey@mouse.com') 68 | ) 69 | 70 | CURRENT_USER = 'Mickey' 71 | 72 | rowModificationLogic <- function(row){ 73 | if (row[,'user'] == CURRENT_USER){ 74 | TRUE 75 | } else { 76 | FALSE 77 | } 78 | } 79 | 80 | shiny::shinyApp( 81 | ui = editbl::eDTOutput('id'), 82 | server = function(input, output,session){ 83 | eDT(id='id', 84 | data = df, 85 | canEditRow = rowModificationLogic, 86 | canDeleteRow = rowModificationLogic 87 | ) 88 | }) 89 | 90 | ``` 91 | -------------------------------------------------------------------------------- /editbl/vignettes/howto_switch_from_DT.rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Switching from DT" 3 | date: "`r Sys.Date()`" 4 | output: 5 | rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteEncoding{UTF-8} 8 | %\VignetteIndexEntry{How to: adjust your `DT` code to use `eDT` instead.} 9 | %\VignetteEngine{knitr::rmarkdown} 10 | --- 11 | 12 | Let's say you already use `DT::datatable()` to display your data, but want to 13 | switch to `editbl::eDT()` to be able to edit it. What should you look out for? 14 | 15 | * `eDTOutput()` uses an `id` argument instead of `outputId` since it's actually a 16 | [shiny module](https://mastering-shiny.org/scaling-modules.html). 17 | * `eDT()` adds extra (hidden) columns to your `datatable`. Try to format using 18 | column names instead of indexes. 19 | * Your `datatable` now exists within a module (e.g. child namespace). This means 20 | your own chosen `outputId` is now `moduleId-DT`. This influences for example 21 | the values accessible under `input`. Example: switch from 22 | `input$outputId_cell_clicked` to `input[["moduleId-DT_cell_clicked"]]`. 23 | * `eDT()` accepts all arguments of `DT::datatable()`, but has some different 24 | defaults for convenience. 25 | * Any additional formatting should be done by passing a function to the 26 | `format` argument of `eDT()`. 27 | * As always be careful when using 28 | [extensions](https://datatables.net/extensions/index) or custom javascript, 29 | not everything works well together. The 30 | [KeyTable](https://datatables.net/extensions/keytable/) and 31 | [AutoFill](https://datatables.net/extensions/autofill/) extensions of 32 | datatable are used by default and should be well integrated. 33 | 34 | Here is an example covering the above: 35 | 36 | ```{r echo = TRUE, results = 'hide'} 37 | library(shiny) 38 | library(DT) 39 | library(editbl) 40 | ``` 41 | 42 | ```{r, screenshot.opts = list(vwidth = 700), screenshot.alt = 'screenshots/howto_switch_from_DT_1.png'} 43 | ui <- fluidPage(DTOutput("DT")) 44 | server <- function(input, output, session){ 45 | output$DT <- renderDataTable({ 46 | datatable(mtcars) %>% 47 | formatRound('disp', 1) 48 | }) 49 | observe({ 50 | print(input[["DT_cell_clicked"]]) 51 | }) 52 | } 53 | shinyApp(ui, server) 54 | ``` 55 | 56 | Reworked into `eDT()`: 57 | ```{r, screenshot.opts = list(vwidth = 700), screenshot.alt = 'screenshots/howto_switch_from_DT_2.png'} 58 | ui <- fluidPage(eDTOutput("DT")) 59 | server <- function(input, output, session){ 60 | editbl::eDT( 61 | id = "DT", 62 | data = mtcars, 63 | format = function(x){formatRound(x,'disp', 1)}) 64 | 65 | observe({ 66 | print(input[["DT-DT_cell_clicked"]]) 67 | }) 68 | } 69 | shinyApp(ui, server) 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_relational_db_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_relational_db_1.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_relational_db_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_relational_db_2.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_relational_db_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_relational_db_3.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_relational_db_dm_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_relational_db_dm_1.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_row_level_access_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_row_level_access_1.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_row_level_access_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_row_level_access_2.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_switch_from_DT_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_switch_from_DT_1.png -------------------------------------------------------------------------------- /editbl/vignettes/screenshots/howto_switch_from_DT_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl/vignettes/screenshots/howto_switch_from_DT_2.png -------------------------------------------------------------------------------- /editbl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/editbl/603a4e2702f7183b4ce8447bc8b274f6c8f71942/editbl_logo.png --------------------------------------------------------------------------------