{"id":13707868,"url":"https://github.com/ijlyttle/shinychord","last_synced_at":"2025-02-24T05:43:25.100Z","repository":{"id":72603108,"uuid":"40910215","full_name":"ijlyttle/shinychord","owner":"ijlyttle","description":"Reusable shiny modules","archived":false,"fork":false,"pushed_at":"2016-01-29T23:52:00.000Z","size":9898,"stargazers_count":12,"open_issues_count":7,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-15T11:48:12.985Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ijlyttle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-17T16:27:03.000Z","updated_at":"2019-12-31T19:43:44.000Z","dependencies_parsed_at":"2023-04-23T11:04:45.110Z","dependency_job_id":null,"html_url":"https://github.com/ijlyttle/shinychord","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijlyttle%2Fshinychord","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijlyttle%2Fshinychord/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijlyttle%2Fshinychord/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijlyttle%2Fshinychord/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ijlyttle","download_url":"https://codeload.github.com/ijlyttle/shinychord/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240427195,"owners_count":19799466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T22:01:46.363Z","updated_at":"2025-02-24T05:43:25.078Z","avatar_url":"https://github.com/ijlyttle.png","language":"HTML","funding_links":[],"categories":["HTML"],"sub_categories":[],"readme":"# shinychord\n\n## Please note\n\n**Shinychord is no longer supported**, as it is incompatible with shiny 0.13.0.\n\nThis is a **very good thing**, because the [shiny modules](http://shiny.rstudio.com/articles/modules.html) system is superior to what is being done in shinychord.\n\nInstead, you are referred to [shinypod](https://github.com/ijlyttle/shinypod), a package for shiny modules that will do everything that shinychord does (did?) - but it is cleaner, more understandable, and more maintainable.\n\n\n## Introduction\n\nThe purpose of this package is to propose a convention for reusable shiny modules.\n\nThis might be useful for shiny code where:\n\n- things can be broken down into modules\n- within each module, the are a lot of controls are important only within the module\n- there may be a need to use modules in different apps\n\nOne benefit is that the server logic for a given module is encapsulated, while exposing only a few reactive values and the ui elements.\n\n## tl;dr\n\n- [PDF of a presentation](https://ijlyttle.github.io/shinychord/doc/isu_presentation.pdf) made to the ISU Statistics Graphics Group - 2015-10-01. Revised 2015-10-05.\n\n- [Demonstration app](https://ijlyttle.shinyapps.io/shinychord_dygraphs), showing a [dygraph](https://rstudio.github.io/dygraphs/) shinychord in a shiny-app.\n\n- [Demonstration app](https://ijlyttle.shinyapps.io/shinychord_readr_dygraphs), showing a [csv-parser](https://github.com/hadley/readr) shinychord and a [dygraph](https://rstudio.github.io/dygraphs/) shinychord in a shiny app.\n\n## Structure of a shinychord\n\nA shinychord is simply a function that takes `id` as its argument. It returns a list with three items:\n\n- `ui_controller` a `shiny::tagList` containing ui elements focusing on inputs\n- `ui_view` a `shiny::tagList` containing ui elements focusing on outputs\n- `server_model` a function that contains all the server logic, and exposes reactive values\n\nAs you might imagine, the names on this list are inspired by the model-view-controller paradigm.\n\nThere is nothing mandatory or scared about the names of these elements, it is only a proposed convention. Furthermore, the elements of each `shiny::taglist` are available if you want to select or arrange them differently.\n\n## Example\n\nLet's say we wanted a shinychord to upload and parse a delimited file. We start with a template for a shinychord:\n\n```R\nch_upload_parse \u003c- function(id){\n\n  # controller\n  ui_controller \u003c- shiny::tagList()\n\n  # view\n  ui_view \u003c- shiny::tagList()\n\n  # model\n  server_model \u003c- function(\n    input, output, session\n  ){\n  \n  }\n  \n  list(\n    ui_controller = ui_controller,\n    ui_view = ui_view,\n    server_model = server_model\n  )\n}\n```\n\nOver the next few sections, we show how the parts of the template are filled out so that we end with a complete shinychord.\n\n### Naming function\n\nOne of the challenges we have in composing shiny applications is a way to keep the names unique among each of the elements.\n\nTo help us, I put a function at the top of the shinychord:\n\n```R\nid_name \u003c- function(...){\n  paste(list(id, ...), collapse = \"_\")\n}\n```\n\nThis function will be very handy as we define the inputs and outputs.\n\n### Controller\n\nWe could make this as fancy as we like, but let's consider a simple set of controls, comprised of a file-upload input and a select input (to choose a delimiter)\n\n```R\nui_controller \u003c- shiny::tagList()\n\nid_controller_file \u003c- id_name(\"controller\", \"file\")\nui_controller$file \u003c-\n  shiny::fileInput(\n    inputId = id_controller_file,\n    label = \"File\",\n    accept = c(\"text/csv\", \".csv\", \"text/comma-separated-values\", \"text/plain\")\n  )\n```\n\nYou can see that we use the `id_name()` function to combine the `id` string (supplied as an\nargument to the shiny chord) with the local identifier.\n\nWe also want an input to choose the delimiter:\n\n```R\nid_controller_delim \u003c- id_name(\"controller\", \"delim\")\nui_controller$delim \u003c-\n  shiny::selectizeInput(\n    inputId = id_controller_delim,\n    label = \"Delimiter\",\n    choices = c(Comma = \",\", Semicolon = \";\", Tab = \"\\t\"),\n    selected = \";\"\n  )\n```\n\n### View\n\nWe will want a couple of view elements - one to preview the text that has been uploaded, and another to preview the parsed dataframe.\n\n```R\nui_view \u003c- shiny::tagList()\n\n# shows the raw text of the file (first few lines)\nid_view_text \u003c- id_name(\"view\", \"text\")\nui_view$text \u003c- shiny::verbatimTextOutput(id_view_text)\n\n# shows a glimpse of the parsed data-frame\nid_view_data \u003c- id_name(\"view\", \"data\")\nui_view$data \u003c- shiny::verbatimTextOutput(id_view_data)\n```\n\n### Model\n\nA couple of notes here:\n\n- Whenever we refer to a reactive value, we have to wrap it in a reactive expression.\n\n- For this shinychord, the ultimate goal is to encapsulate the details so that we expose an `id`, when we create the shinychord, and a reactive value, when we place the `server_model()` function in the shiny `server()` function. We have to pass to this `server_model()` function the reactive value and the name of the item within the reactive value. It will be in this reactive value that the parsed dataframe will be placed.\n\n```R\nserver_model \u003c- function(\n  input, output, session,\n  rctval_data, item_data\n){\n\n  ## reactives\n\n  # reactive to read in the raw text from the file-specification input\n  rct_txt \u003c- reactive({\n\n    shiny::validate(\n      shiny::need(env$input[[id_controller_file]], \"File not selected\")\n    )\n\n    infile \u003c- input[[id_controller_file]]$datapath\n\n    readr::read_file(infile)\n  })\n\n  ## observers\n\n  # this needs to be wrapped in a reactive expression\n  observe({\n    rctval_data[[item_data]] \u003c-\n      readr::read_delim(\n        file = rct_txt(),\n        delim = input[[id_controller_delim]]\n      )\n  })\n\n  ## outputs\n\n  # sets the output for the raw text\n  output[[id_view_text]] \u003c-\n    renderText({\n      \n      shiny::validate(\n        shiny::need(rct_txt(), \"File did not load properly\")\n      )\n      \n      h \u003c- rct_txt()\n      h \u003c- readr::read_lines(h, n_max = 10)\n      \n      paste(h, collapse = \"\\n\")\n    })\n\n\n  # sets the output for the parsed dataframe\n  output[[id_view_data]] \u003c-\n    renderPrint({\n      \n      shiny::validate(\n        shiny::need(rctval_data[[item_data]], \"No data\")\n      )\n      \n      dplyr::glimpse(rctval_data[[item_data]])\n    })\n\n}\n```\n\n## Completed shinychord\n\nSo here are all the elements from above, combined into a shinychord function:\n\n```R\nch_upload_parse \u003c- function(id){\n\n  id_name \u003c- function(...){\n    paste(list(id, ...), collapse = \"_\")\n  }\n\n  # controller\n  ui_controller \u003c- shiny::tagList()\n\n  id_controller_file \u003c- id_name(\"controller\", \"file\")\n  ui_controller$file \u003c-\n    shiny::fileInput(\n      inputId = id_controller_file,\n      label = \"File\",\n      accept = c(\"text/csv\", \".csv\", \"text/comma-separated-values\", \"text/plain\")\n    )\n\n  id_controller_delim \u003c- id_name(\"controller\", \"delim\")\n  ui_controller$delim \u003c-\n    shiny::selectizeInput(\n      inputId = id_controller_delim,\n      label = \"Delimiter\",\n      choices = c(Comma = \",\", Semicolon = \";\", Tab = \"\\t\"),\n      selected = \";\"\n    )\n  \n  # view\n  ui_view \u003c- shiny::tagList()\n  \n  # shows the raw text of the file (first few lines)\n  id_view_text \u003c- id_name(\"view\", \"text\")\n  ui_view$text \u003c- shiny::verbatimTextOutput(id_view_text)\n  \n  # shows a glimpse of the parsed data-frame\n  id_view_data \u003c- id_name(\"view\", \"data\")\n  ui_view$data \u003c- shiny::verbatimTextOutput(id_view_data)\n  \n  # model\nserver_model \u003c- function(\n  input, output, session,\n  rctval_data, item_data\n){\n\n  ## reactives\n\n  # reactive to read in the raw text from the file-specification input\n  rct_txt \u003c- reactive({\n\n    shiny::validate(\n      shiny::need(input[[id_controller_file]], \"File not selected\")\n    )\n\n    infile \u003c- input[[id_controller_file]]$datapath\n\n    readr::read_file(infile)\n  })\n\n  ## observers\n\n  # this needs to be wrapped in a reactive expression\n  observe({\n    rctval_data[[item_data]] \u003c-\n      readr::read_delim(\n        file = rct_txt(),\n        delim = input[[id_controller_delim]]\n      )\n  })\n\n  ## outputs\n\n  # sets the output for the raw text\n  output[[id_view_text]] \u003c-\n    renderText({\n      \n      shiny::validate(\n        shiny::need(rct_txt(), \"File did not load properly\")\n      )\n      \n      h \u003c- rct_txt()\n      h \u003c- readr::read_lines(h, n_max = 10)\n      \n      paste(h, collapse = \"\\n\")\n    })\n\n\n  # sets the output for the parsed dataframe\n  output[[id_view_data]] \u003c-\n    renderPrint({\n      \n      shiny::validate(\n        shiny::need(rctval_data[[item_data]], \"No data\")\n      )\n      \n      dplyr::glimpse(rctval_data[[item_data]])\n    })\n\n}  \n  list(\n    ui_controller = ui_controller,\n    ui_view = ui_view,\n    server_model = server_model\n  )\n}\n```\n\n### Using the shinychord in an app\n\nHere's where we can see a benefit of this approach, as all the interal complexity of the shinychord has been encapuslated, exposing only the bits we need.\n\nFor the shiny developer, instead of developing a file-parser for every occasion, or cutting and pasting from a template (and managing the id's), she or he can use a shinychord - set an `id` in one place, and create a reactive value into which the `server_model` function can leave the resulting dataframe.\n\nThe shinychord function can be created once and used everywhere.\n\n```R\nlibrary(\"shiny\")\nlibrary(\"readr\")\nlibrary(\"dplyr\")\n\nchord_parse \u003c- ch_upload_parse(\"parse\")\n\nshinyApp(\n\n  ui = fluidPage(\n    sidebarLayout(\n      sidebarPanel(chord_parse$ui_controller),\n      mainPanel(chord_parse$ui_view)\n    )\n  ),\n  \n  server = function(input, output, session){\n  \n    rctval \u003c- reactiveValues(data_csv = NULL)\n    \n    chord_parse$server_model(\n      input, output, session,\n      rctval_data = rctval, item_data = \"data_csv\"\n    )\n    \n    observe(print(rctval$data_csv))\n    \n  }\n  \n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fijlyttle%2Fshinychord","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fijlyttle%2Fshinychord","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fijlyttle%2Fshinychord/lists"}