diff --git a/_freeze/blog/2024-08-21-introducing-surveydown/index/execute-results/html.json b/_freeze/blog/2024-08-21-introducing-surveydown/index/execute-results/html.json deleted file mode 100644 index dec5a8ab..00000000 --- a/_freeze/blog/2024-08-21-introducing-surveydown/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "76519ef644e00c2da16c79f1642e85d4", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: \"Introducing surveydown: A markdown-based framework for generating surveys with Quarto and shiny\"\ndescription: |\n A quick overview of the {surveydown} R package for making markdown-based surveys with open-source technologies: Quarto, shiny, and supabase.\ndate: '2024-08-21'\nimage: \"banner.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: logo.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - package\n - markdown\n - shiny\n - package\n - quarto\nformat: html\ntoc: true\ntoc-depth: 3\nlightbox: true\nexecute:\n eval: false\n---\n\n\n\n
\n\n
\n
\n\n::: {.callout-important}\n\n**This post was made just after launching surveydown. Much of the platform architecture has changed since then, so check the [documentation](https://surveydown.org/docs.html) for the latest correct information.**\n\n:::\n\n::: {.callout-note}\n\n**Note:** This post is largely a copy of the [a similar post](https://www.jhelvy.com/blog/2024-08-10-introducing-surveydown/) I made on my personal website, with some minor edits.\n\n:::\n\nThis post introduces the {surveydown} R package, a new way to design surveys using markdown, R, Quarto, and shiny. The idea for this platform has been brewing for a while (see [this blog post](https://jhelvy.com/blog/2023-04-06-markdown-surveys/) for more on the motivation for this project), but now the package is finally here! \n\nIn this post, I'm going to show you a quick overview of the {surveydown} R package for making markdown-based surveys as well as a little about _why_ we built surveydown.\n\n# A new way to design surveys\n\n[surveydown](https://surveydown.org/) is a flexible platform for making surveys in {{< fa brands r-project >}} using three open source technologies:\n[Quarto](https://quarto.org/), [shiny](https://shiny.posit.co/), and\n[supabase](https://supabase.com/). The package is still in development, but you can already use it to create surveys. \n\n
\n
\n\n
\n
\n\nThe basic concept is this:\n\n1. Design your survey as a [Quarto shiny\n document](https://quarto.org/docs/dashboards/interactivity/shiny-r.html)\n using markdown and R code.\n2. Render your doc into a [shiny](https://shiny.posit.co/) app that can\n be hosted online and sent to respondents.\n3. Store your survey responses in a [supabase](https://supabase.com/)\n database.\n\nIn surveydown, your entire survey is designed using markdown and R code in a single Quarto document. There is no GUI or drag-and-drop interface - simply write plain text (markdown and R code) and boom - you have a survey! \n\nThe [{surveydown} package](https://pkg.surveydown.org/) provides a set of functions for defining the survey content and control logic. Each function starts with `sd_` to make them easy to identify. You can add content to your survey using markdown formatting, or in RStudio you can edit with the visual editor. Survey questions are defined in R code chunks with the `sd_question()` function. Pages are defined using fences (`:::`), and navigation buttons handled with the `sd_next()` function. You can modify the control logic in the server code chunk (the last code chunk at the bottom of the .qmd file) with the `sd_config()` function, and you can configure the database with the `sd_database()`. \n\nThe [documentation](https://surveydown.org/docs.html) has many more details on all of this, and later in this post I'll give a quick overview of a few of these features. But first, let me tell you a little about _why_ we decided to build surveydown.\n\n# Why did we build surveydown?\n\n> Do we really need another survey platform?\n\nLike many researchers who do a lot of survey work, I've been frustrated with most survey platforms available. Commerical platforms like Qualtrics and SurveyMonkey are great, but they are expensive and are difficult to version control, and collaboration with others is near impossible, especially if your collaborator doesn't have a license of their own. They also don't allow me to own my own survey data, meaning I always am at the mercy of the platform owner. And the features are often limited. It's not easy to do more complex things like randomization, conditional display, etc.\n\nThe only other open-source survey platform I have used is [formr](https://formr.org/), which is a very powerful platform, but it is rather clunky to use (you define your survey in Google sheet cells...there's a learning curve), and it is not as easy to edit as a simple markdown file. \n\nWhat we've come up with is a survey platform that is flexible, _relatively_ easy to use, and built entirely with open-source technologies. I think it solves a lot of problems, and hopefully someone out there will find it useful.\n\n## Open source\n\nsurveydown is built entirely with open-source technologies, making it transparent and customizable. Best of all, no expensive licenses! Just install and use it! \n\n## Own your data\n\nWith surveydown, you retain full ownership and control of your survey data. The responses are stored in your own Supabase database, ensuring that you have complete access to the data. This is particularly important for researchers dealing with sensitive information or those who need to comply with specific data protection regulations. We're still working on enabling you to use your own hosted database, which will provide even more flexibility.\n\n## Ease of editing\n\nDesigning a survey in surveydown is a pretty straightforward process. The markdown-based approach allows for quick modifications and easy navigation through your survey. And since it's built on [Quarto](https://quarto.org/), you can use all of the features of Quarto to make your survey look great, like changing the theme, adding custom CSS, etc. You can also easily preview your survey as you edit it, and even run your survey locally to test it out before you deploy it, either with a button click in RStudio or with a `quarto serve` command in the terminal.\n\n## Did I mention you can run R code in your survey?\n\nEvery surveydown survey uses R code chunks for questions. But you can also insert R code for all sorts of other things. For example, if you wanted to randomize the values shown in a question, you could write some R code for that. Want to insert a plot of something? Write a little ggplot code. You can also add interactive components to your surveys, such as showing a respondent how their responses compare to others in real time.\n\n## Easy version control and collaboration\n\nBecause the entire survey is defined in a single plain text file, surveydown naturally integrates with version control systems like Git. This allows you to track changes over time, collaborate with team members, and maintain a clear history of your survey's development.\n\n## Reproducible\n\nSurveydown promotes reproducible research by allowing you to define your entire survey in a single, self-contained plain text document. This has a ton of benefits:\n\n- Want someone else to be able to reproduce your experiment? Just give them the .qmd file and any other files they need (e.g., images, data, etc.), and they can reproduce your survey on their own computer.\n- Want to print out your survey for an appendix? Render the survey with all pages visible then print it to pdf.\n- Want others to see your survey live? Just set the database into pause mode and your survey will function without recording any responses.\n\nReproducibility is something we had in mind from the start with this project, and we've tried to make it as easy as possible for your surveydown surveys to be fully reproducible.\n\n# Introduction to surveydown\n\n## Getting started \n\nAfter getting everything [installed](https://surveydown.org//docs/getting-started.html#install), we recommend starting with a template survey project. To do so, run the following in the R console:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsurveydown::sd_create_survey(\"path/to/folder\")\n```\n:::\n\n\nThis will create a folder with the following files:\n\n- `example.qmd`: a template survey you should edit.\n- `example.Rproj`: An RStudio project file (helpful if you're working in RStudio)\n- `_extensions`: A folder with the surveydown Quarto extension needed to make everything work (don't modify this).\n\nIf you have the example open in RStudio, you can click the \"Run document\" button, or in your terminal run `quarto serve example.qmd`. Either approach should render the example survey into a shiny app that you can preview in a browser. Don't worry just yet about setting up your database or making the survey live - for now, we're going to focus on designing the survey and running it locally to preview it. The example survey should look like this:\n\n
\n\n
\n\n## Adding pages\n\nIn surveydown, pages are delineated using \"fences\", like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n::: {#welcome .sd-page}\n\nPage 1 content here\n\n:::\n\n::: {#page2 .sd-page}\n\nPage 2 content here\n\n:::\n```\n:::\n\n\nAs you can see, we use three colon symbols `:::`, called a \"fence\", to mark the start and end of pages. This notation is commonly used in Quarto for a variety of use cases, like defining [subfigures](https://quarto.org/docs/authoring/figures.html#subfigures) in images.\n\nIn the starting fence, you need to define a page name (e.g. `welcome` and `page2` in the example above) and you need to define the class as `.sd-page`. Then anything you put between the page fences will appear on that page. \n\nTo navigate to the next page, you need to insert a `sd_next()` function call inside a code chunk, like this: \n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_next(next_page = 'page2')\n```\n````\n:::\n\n\nThe above code chunk will create a \"Next\" button that goes on to page 2 that looks like this:\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n\n
\n```\n\n:::\n:::\n\n\nYou would need to place the code chunk in between the `:::` fences of the `welcome` page in order to have a \"Next\" button that goes on to page 2. You can also send the user to other pages by just changing the `next_page` argument. Finally, you can also change the label of the button by changing the `label` argument, like this:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_next(next_page = 'page2', label = 'Next page')\n```\n````\n:::\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n\n
\n```\n\n:::\n:::\n\n\n## Adding questions\n\nEvery survey question is created using the `sd_question()` function inside a code chunk. The question type is defined by the `type` argument. For example, to add a multiple choice question, you could insert the following code chunk:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_question(\n type = 'mc',\n id = 'penguins',\n label = \"Which is your favorite type of penguin?\",\n option = c(\n 'Adélie' = 'adelie',\n 'Chinstrap' = 'chinstrap',\n 'Gentoo' = 'gentoo'\n )\n)\n```\n````\n:::\n\n\nThe above code chunk will create a multiple choice question that looks like this:\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n
\n\n
\n
\n\n
\n
\n\n
\n
\n\n
\n
\n
\n*\n
\n```\n\n:::\n:::\n\n\n
\n\nThe `sd_question()` function can be used to create a variety of [question types](https://surveydown.org/docs/question-types.html), like text input, select drop down choices, and more by changing the `type` argument.\n\nThe function has many other arguments for customizing the look and feel of the question (e.g., `height` and `width`, etc.).\n\n## The server chunk\n\nAt the very bottom of the .qmd file is a special \"server\" code chunk (that's the `#| context: server` bit) that defines the app server. This is where you can customize and control the survey flow logic as well as where you define the database that will store the survey response data. It looks like this:\n\n\n::: {.cell context='server'}\n\n````{.cell-code}\n```{{r}}\n#| context: server\n\n# Define the database settings\ndb <- sd_database()\n\n# Define the configuration settings\nconfig <- sd_config()\n\n# The sd_server() function initiates your survey - don't change this\nsd_server(\n input = input,\n session = session,\n config = config,\n db = db\n)\n```\n````\n:::\n\n\nThe `sd_database()` function is where you set up your database. The `sd_server()` function makes everything run, which you can safely ignore - just don't change it and all will be good!\n\nThe middle part (the `sd_config()` function) is where you can define custom control logic for the survey, such as [*conditional display*](#conditional-display) (conditionally displaying a question based on responses to questions), or [*conditional skip*](#conditional-skip) (conditionally sending the respondent to a page based on responses to questions).\n\n## Going live!\n\nOnce you are happy with your survey, you can deploy it live to any server of your choice. Since it's a shiny app, you can [deploy it to shinyapps.io](https://surveydown.org/docs/deployment.html#deploying-to-shinyapps.io) for free!\n\n# Features\n\nSince surveydown is built on top of Shiny, it provides tremendous flexibility in terms of what you can do with your survey. Below are a few examples of some commons things you may want to do with your survey.\n\n## Conditional display\n\nLet's say we had a fourth option for \"other\" in our multiple choice question about penguins. If the respondent chose it, you may want a second question to popup that allows them to specify the other penguin type. To implement this, you would need to define both questions, e.g.:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_question(\n type = 'mc',\n id = 'penguins',\n label = \"Which is your favorite type of penguin?\",\n option = c(\n 'Adélie' = 'adelie',\n 'Chinstrap' = 'chinstrap',\n 'Gentoo' = 'gentoo',\n 'Other' = 'other'\n )\n)\n\nsd_question(\n type = \"text\",\n id = \"penguins_other\",\n label = \"Please specify the other penguin type:\"\n)\n```\n````\n:::\n\n\nThen in the server code chunk, you could use the `show_if` argument to define that the `penguins_other` question would only be shown if the respondent chose the `other` option in the `penguins` question, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n show_if = tibble::tribble(\n ~question_id, ~question_value, ~target,\n \"penguins\", \"other\", \"penguins_other\"\n )\n)\n```\n:::\n\n\nThis will make the `penguins_other` question only appear if the respondent chose the `other` option in the `penguins` question, like this:\n\n
\n\n
\n
\n\nHere we're using the `tibble::tribble()` function to define a data frame with three columns:\n\n- `question_id`: The `id` of the triggering question.\n- `question_value`: The triggering value.\n- `target`: The `id` of the target question to display.\n\nYou don't have to use `tibble::tribble()`, and in fact if you have a lot of `show_if` conditions, then you could create a csv file with all of your conditions in it and read it in to set the `show_if` conditions (just make sure the header has the same three column names), e.g.:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n show_if = readr::read_csv('path/to/show_if_conditions.csv')\n )\n)\n```\n:::\n\n\n## Conditional skip\n\nOften times you'll want to screen people out of a survey based on responses to questions. For example, let's say you only wanted to only include people who own a vehicle. On your first page (e.g., with page name `welcome`), you could screen out people who do _not_ own a vehicle.\n\nFirst, define a question about their vehicle ownership, e.g.:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_question(\n type = 'mc',\n id = 'vehicle_ownership',\n label = \"Do you own your vehicle?\",\n option = c(\n 'Yes' = 'yes',\n 'No' = 'no'\n )\n)\n```\n````\n:::\n\n\nThen in the server code chunk, you could use the `skip_if` argument in `sd_config()` to define the behavior of the next button on the `welcome` page, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n skip_if = tibble::tribble(\n ~question_id, ~question_value, ~target,\n \"vehicle_ownership\", \"no\", \"screenout\"\n )\n)\n```\n:::\n\n\nThis sets up a condition where if the respondent chooses `no` on the `vehicle_ownership` question, they will be sent to a page named `screenout`. You could put such a page at the end of the survey, something like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n::: {#screenout .sd-page}\n\nSorry, but you are not qualified to take our survey.\n\n:::\n```\n:::\n\n\nNotice that I don't have a `sd_next()` on this screenout page. That is how you define an end point for the survey taker. If there's no \"Next\" button, then they cannot navigate anywhere else, so the survey is over.\n\n## Required responses\n\nBy default, no questions are required. However, you can make questions required by adding the question `id` to the `required` argument in `sd_config()`, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n required_questions = c(\"vehicle_ownership\", \"penguins_other\")\n)\n```\n:::\n\n\nThis will make the respondent unable to proceed until they have answered the required questions. It will also place a red asterisk (*) next to the question label to indicate that the question is required.\n\nYou can also make all questions required by setting `all_questions_required = TRUE` like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n all_questions_required = TRUE\n)\n```\n:::\n\n\n## Reactivity\n\nOne other feature that is particularly powerful is the ability to use R code in your survey via Shiny's reactive programming. This allows you to make your survey more interactive and to use the full power of R to create custom functionality.\n\n### Demo 1: Displaying content based on previous responses\n\nLet's say you wanted to create a survey that asked the respondent's name, and then displayed a personalized message based on their name. You could do this by first asking their name:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_question(\n type = \"text\",\n id = \"name\",\n label = \"What is your name?\"\n)\n```\n:::\n\n\nThen you can use the `sd_display_value(\"name\")` function to display the value of the `name` question in other parts of your survey. For example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nWelcome, `r sd_display_value(\"name\")`!\n```\n:::\n\n\nWhich would render as something like \"Welcome, Dave!\" (assuming the respondent entered \"Dave\" in the `name` question). This works because the `sd_display_value()` function is reactive, meaning it will update the display based on the respondent's responses.\n\n### Demo 2: Displaying randomized question labels\n\nLet's say you wanted to show a series of questions, but you wanted to randomize the labels shown for each question. You could do this by first defining a list of labels, and then using the `sd_question_reactive()` function to create a question that will display a random label from the list. \n\nFor example, let's ask the respondent to rate different car brands from a random set of brands. You could first pre-define the randomized sets of brands for each respondent and store it as a csv file, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands <- c(\"Toyota\", \"Ford\", \"Chevrolet\", \"Honda\", \"Nissan\", \"Tesla\")\ndesign <- data.frame(\n respondent_id = rep(1:10, each = 3),\n brand = unlist(lapply(1:10, function(x) sample(brands, 3, replace = FALSE)))\n)\nwrite_csv(design, \"design.csv\")\n```\n:::\n\n\nThis would make a design file that looks like this:\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n\n```\n#> respondent_id brand\n#> 1 1 Nissan\n#> 2 1 Ford\n#> 3 1 Honda\n#> 4 2 Honda\n#> 5 2 Tesla\n#> 6 2 Chevrolet\n```\n\n\n:::\n:::\n\n\nNote that this would not be done in your survey.qmd file - it's just a one-time thing to create the design (probably stored in an R file).\n\nThen in your server code chunk, you could read in the design file and use it to randomize the labels for each question based on a randomly chosen respondent:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesign <- read_csv(\"design.csv\")\nresp_id <- sample(design$respondent_id, 1)\ndf_resp <- design %>% filter(respondent_id == resp_id)\n\noptions <- c(1, 2, 3)\nnames(options) <- df_resp$brand\n\nsd_question_reactive(\n type = \"mc\",\n id = \"brands\",\n label = \"Which of these brands do you like best?\",\n option = options\n)\n```\n:::\n\n\nHere the `sd_question_reactive()` function is used because the labels depend on the randomly chosen respondent. This means the labels will be different for each respondent.\n\nSince this is a reactive question, **this code must be placed inside the server code chunk**, not where you want it to appear in the survey. To define where in the survey the question should appear, you use the `sd_display_question()` function with the `id` set to the same value as the `id` in the `sd_question_reactive()` function, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_display_question(id = \"brands\")\n```\n:::\n\n\nNow the `brands` question will be displayed in the survey where you put this code chunk.\n\nNote that all question responses are automatically saved to the database, but if you wanted to store some other value (e.g. the randomly chosen `respondent_id`), you could do that with the `sd_store_value()` function, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_store_value(resp_id)\n```\n:::\n\n\n# How we built it\n\nBefore I wrap up, I just want to say that I am absolutely amazed at the time we live in. I have had this idea in mind for many years, but I'm not a web developer, and I never could come up with a way to make it happen. That was actually what motivated me to write my [previous blog post](https://jhelvy.com/blog/2023-04-06-markdown-surveys/) - it was a call for help from others!\n\nBut two things happened relatively recently that made it possible:\n\n1. The rise of Quarto\n2. The rise of LLMs\n\nAfter I switched my website over from distill to Quarto, I began to learn more and more about how powerful Quarto really is for building things on the web. Then I saw the [Quarto shiny document](https://quarto.org/docs/dashboards/interactivity/shiny-r.html) framework and I immediately thought that this just might be the missing piece I needed to make surveydown a reality. It does all the legwork of converting markdown and R code into a shiny app. \n\nOf course, implementing this idea was still really, really hard. There were many different ways to start, and I got some [excellent feedback](https://github.com/quarto-dev/quarto-cli/discussions/6975) from people in the R / Quarto dev community. Garrick Aden-Buie in particular was the first to [propose the idea of using fences](https://github.com/quarto-dev/quarto-cli/discussions/6975#discussioncomment-8828907) to denote page breaks, which was a big breakthrough early on. \n\nBut the biggest breakthrough came when I started using GPT-4 to help me brainstorm many different ideas while developing the overall platform architecture. [This conversation](https://chatgpt.com/share/57fda3b1-2e76-4ab1-b7af-e53eac597986) in particular was game changing. In it, I came to solutions for multiple complex problems, including the page navigation logic and which platform to use for the database (we originally started with using Googlesheets, but ultimately decided on Supabase because it is open-source and just far easier to use).\n\nOf course, the AI didn't do everything. Two of my students, [Pingfan Hu](https://github.com/pingfan-hu) and [Bogdan Bunea](https://github.com/Buneabt) have been instrumental in helping implement many of the features the package now has, and they too have leveraged LLMs to accelerate their problem solving. Thank you guys for all of your hard work! 🙏\n\nIt's been amazing watching this project come together over such a short period of time. The original conversations I had with GPT-4 and others in the R / Quarto community were just in March and April of this year (2024). We really didn't start developing in earnest until the summer, and really only late June / early July at that. In just a few months, we've gone from an idea to a fully functional survey platform.\n\nIf you give surveydown a try, please let us know what you think! And if you find a bug or something you wished existed, please post an issue on [github](https://github.com/surveydown-dev/surveydown/issues).\n\nI'm so excited to see what you all will build with surveydown!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blog/2024-08-28-choice-based-conjoint-surveys-with-surveydown/index/execute-results/html.json b/_freeze/blog/2024-08-28-choice-based-conjoint-surveys-with-surveydown/index/execute-results/html.json deleted file mode 100644 index 6a5e049b..00000000 --- a/_freeze/blog/2024-08-28-choice-based-conjoint-surveys-with-surveydown/index/execute-results/html.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "hash": "76d050cbea01e4167a43b5893c592334", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: Choice-based conjoint surveys in R with surveydown\ndescription: |\n A how-to guide for using R to design and implement choice-based conjoint surveys using the surveydown R package\ndate: '2024-08-28'\nimage: \"images/banner.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: images/banner.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - tutorial\n - conjoint\nformat: html\nexecute: \n eval: false\ntoc: true\ncss: /css/surveydown.css\n---\n\n\n\n::: {.callout-important}\n\n**This post was updated on 2025-09-14 to match the latest package version**\n\n:::\n\nBecause [surveydown](https://surveydown.org/) surveys run as a shiny app, you can include custom logic in the background by writing some {{< fa brands r-project>}} code in your server. In this post, I'm going to show you one approach for using surveydown to create a particular type of complex survey: a **choice-based conjoint survey**. \n\n::: {.callout-note}\n\nIf you're unfamiliar with what a conjoint survey is, take a look at this [quick introduction](https://sawtoothsoftware.com/conjoint-analysis/cbc).\n\n:::\n\nThe key component of a choice-based conjoint survey is asking repsondents to make choices from randomized sets of choice questions. So the hard part is figuring out a way to show each respondent a different set of randomized questions. This post shows how you can achieve this in surveydown.\n\nThroughout this post, I will use a demo survey about people's preferences for apples with three attributes: `type`, `price`, and `freshness`.^[Yes, people have [actually done conjoint surveys on fruit](https://www.emerald.com/insight/content/doi/10.1108/00070709610150879/full/html) before.] I will focus on one structure for the conjoint survey that uses the [`mc_buttons`](https://surveydown.org/docs/question-types.html#mc_buttons) question type for the main choice questions. You can view a live demo of that survey and the template used to create it [here](https://surveydown.org/templates/conjoint_buttons.html). An alternative structure is to display choice options in a table - that template can be found [here](https://surveydown.org/templates/conjoint_tables).\n\n## Introduction\n\nIf you've never used surveydown before, take a look at the [Getting Started](https://surveydown.org/docs/getting-started.html) page to get a quick introduction to the package and how to use it to make a survey.\n\nThe basic concept is this:\n\n1. Design your survey as a [Quarto](https://quarto.org/) document using markdown and R code.\n2. Convert your survey into a [Shiny](https://shiny.posit.co/) app that can be hosted online and sent to respondents.\n3. Store your survey responses in a [Supabase](https://supabase.com/) database.\n\n## Getting started \n\nFor this post, we recommend starting from the demo survey available [here](https://surveydown.org/templates/conjoint_buttons.html). It provides an already working survey that you can modify to the needs of your conjoint survey.\n\nThe demo repo has a lot of files in it, but the main files defining the survey itself are:\n\n- `survey.qmd`: The main body of the survey. \n- `app.R`: The app file containing the server logic implemented in the survey, including randomizing questions, connecting to a database, etc.\n\n::: {.callout-note}\n\nWe recommend opening the `survey.Rproj` if you're working in RStudio to make sure RStudio opens to the correct project folder.\n\n:::\n\n## Content in the survey body\n\nAfter the setup code chunk where we load the surveydown package, we have a series of pages (defined with `:::` fences) that include markdown-formatted text and survey questions (defined with `sd_question()`). You can modify any of this content as you wish to suit the needs of your survey. \n\nIn this demo, we have a few other examples included, like a conditionally displayed question (the `fav_fruit` question will not display if you choose \"No\" on the first question about liking fruit) as well as a question that skips people to the end (if you choose \"blue\" and not \"red\" on the `screening` page). The logic controlling the conditional display and skipping is defined with the `sd_skip_if()` function in the `app.R` file.\n\nNone of this is absolutely necessary for a conjoint survey, but often times these are features that you may want to include, such as screening people out of the survey if they don't qualify to take it, so we include it for demonstration purposes.\n\n## Defining the choice questions\n\nThe central component of every conjoint survey is the set of randomized choice questions. To implement these in surveydown, we pre-define our choice questions in a design file that we later use in the survey to select randomized sets of choice questions to display each respondent.\n\nWe use the [cbcTools](https://jhelvy.github.io/cbcTools/) package to create the pre-defined design file. The code to create the choice questions for this demo survey is in the [`1_make_choice_questions.R`](https://github.com/surveydown-dev/templates/blob/main/conjoint_buttons/code/1_make_choice_questions.R) file in the demo repo. This code generates a data frame of randomized choice questions that we then save in the project directory as `choice_questions.csv`.\n\n## Implementing the choice questions\n\nThe choice questions are implemented at the top of the `server()` function in the `app.R` file in the demo repo. This code does the following steps:\n\n### 1. Read in the design file\n\nPretty straightforward - this is one line to read in the `choice_questions.csv` design file that we saved in the project folder.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesign <- readr::read_csv(\"choice_questions.csv\")\n```\n:::\n\n\n### 2. Sample and store a random respondent ID\n\nSince we want each respondent to see a different set of choice questions, we randomly sample a respondent ID from the set of all respondent IDs in the design file. We also need to keep track of this and store it in our response data so that later we can know what each respondent was actually shown. \n\nSince this is a value that we generated in the server (and not a value from a survey question to a respondent), we have to manually add it to the survey response data using `sd_store_value()`. Here we modified the name so that in the resulting survey data the column name will be `\"respID\"`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Sample a random respondentID\nrespondentID <- sample(design$respID, 1)\n\n# Store the respondentID\nsd_store_value(respondentID, \"respID\")\n```\n:::\n\n\n### 3. Filter the design for the respondentID\n\nWe create a subset dataframe called `df` that stores only the rows for the randomly chosen respondent ID. We also append the `\"images/\"` string onto the values in the `image` column as this will create the relative path to the images in our survey, e.g. `\"images/fuji.jpg\"` (all the images we show are in the `\"images\"` folder in the repo).\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Filter for the rows for the chosen respondentID\ndf <- design %>%\n filter(respID == respondentID) %>%\n mutate(image = paste0(\"images/\", image))\n```\n:::\n\n\n### 4. Define a function to create question options\n\nThis is the most complex component in the server logic. Here we created a function that takes a dataframe and returns a named vector defining the options to show in each choice question. In this case, we only have 3 options per choice question, so each time we call this function we will use a small dataframe that has just 3 rows defining the 3 choice alternatives in a single choice question. \n\nThe function does several things. First, it extracts three single-row data frames that store the values of each of the 3 alternatives (`alt1`, `alt2`, and `alt3`). It then creates an `options` vector that has just 3 values: `\"option_1\"`, `\"option_2\"`, and `\"option_3\"`. Then we have to define the names of each of those options. Remember that the _values_ in the `options` vector are what gets stored in our resulting survey data based on what the respondent chooses, but the _names_ are what respondents see. So in the context of a choice survey like this, we need to embed all of the attributes and their levels in the names of the `options` vector.\n\nWe use the `glue()` function to easily inject the values stored in `alt1`, `alt2`, and `alt3` into our labels. The `glue()` function is similar to `paste()` in that is just concatenates object values into a string, but it has an easier syntax to work with. Anything inside `{}` brackets is evaluated, and the resulting value is inserted into the string. So for example, the line `glue(\"1 plus 1 equals {1+1}\")` would produce the string `\"1 plus 1 equals 2\"`.\n\nIn our case, we're including some html code to insert an image of the apple type (``), the apply type itself (`**Type**: {alt1$type}`), and the apple price (`**Price**: $ {alt1$price} / lb`). \n\nNotice also that we're mixing markdown (e.g. `**Option 1**`) and html (e.g. `
`), which will all get rendered into proper html in the resulting shiny app. The full function looks like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Function to create the labels for a choice question\n# based on the values in df\n\nmake_cbc_options <- function(df) {\n alt1 <- df |> filter(altID == 1)\n alt2 <- df |> filter(altID == 2)\n alt3 <- df |> filter(altID == 3)\n\n options <- c(\"option_1\", \"option_2\", \"option_3\")\n\n names(options) <- c(\n glue(\n \"\n **Option 1**
\n
\n **Type**: {alt1$type}
\n **Price**: $ {alt1$price} / lb
\n **Freshness**: {alt1$freshness}\n \"\n ),\n glue(\n \"\n **Option 2**
\n
\n **Type**: {alt2$type}
\n **Price**: $ {alt2$price} / lb
\n **Freshness**: {alt2$freshness}\n \"\n ),\n glue(\n \"\n **Option 3**
\n
\n **Type**: {alt3$type}
\n **Price**: $ {alt3$price} / lb
\n **Freshness**: {alt3$freshness}\n \"\n )\n )\n return(options)\n}\n```\n:::\n\n\n### 5. Create the options for each choice question\n\nOne of the benefits of making the function the way we did in the previous step is that we can now easily call it to generate the option vector for each of the 6 choice questions in `df`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Create the options for each choice question\n\ncbc1_options <- make_cbc_options(df |> filter(qID == 1))\ncbc2_options <- make_cbc_options(df |> filter(qID == 2))\ncbc3_options <- make_cbc_options(df |> filter(qID == 3))\ncbc4_options <- make_cbc_options(df |> filter(qID == 4))\ncbc5_options <- make_cbc_options(df |> filter(qID == 5))\ncbc6_options <- make_cbc_options(df |> filter(qID == 6))\n```\n:::\n\n\n### 6. Create each choice question (6 in total)\n\nFinally, we now have everything we need to generate each choice question. Here we're using the `mc_buttons` question type so that the labels we generated will be displayed on a large button, which looks good both on a computer and phone. We give the question a unique `id` (e.g. `cbc_q1`), and a label, and then set the `option` to the corresponding option vector we defined above. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_question(\n type = 'mc_buttons',\n id = 'cbc_q1',\n label = \"(1 of 6) If these were your only options, which would you choose?\",\n option = cbc1_options\n)\n\n# ...and 5 more questions like this\n```\n:::\n\n\nRemember that since the labels in the options are being _dynamically_ generated on each new session (each respondent), they have to be created in the server, not in the main survey body. As a result, the `sd_question()` function must also be created in the server code (if you put this code in the main body, only one random set of choice options will be generated, and they'll be the same for everyone). \n\nTo display each question in the survey body, we use `sd_output(\"id\", type = \"question\")`, changing `id` to each corresponding choice question we created. In the demo `survey.qmd` file, you'll see that there are 6 choice questions displayed in the main survey body (each on their own page), and each of those 6 questions are defined in the `server()` function in the `app.R` file. \n\nWhen rendered, a choice question will look like this, with the values matching whatever alternative was chosen in the design file:\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n
\n\n
\n
\n
\n\n\n\n\n\n\n
\n
\n\n
\n*\n
\n```\n\n:::\n:::\n\n\n
\n\nAnd that's it! You now have 6 randomized choice questions!\n\n## Buttons versus tables \n\nIn the example above, the conjoint choice questions are displayed as \"buttons\" where all the information for each alternative is shown as a button. This works particularly well for mobile phone applications where the user may need to scroll vertically to see each option. \n\nAn alternative is to use a table layout where each column represents an alternative and the row names explain the attribute. This takes a little manipulation to get it right, but the key concept is to use `kable()` to display the transpose of the `df` data frame (the subset of rows for a particular respondent). We also use the wonderful [kableExtra](https://cran.r-project.org/web/packages/kableExtra/vignettes/awesome_table_in_html.html) package to modify some of the table stying. We don't explain this code in detail in this blog post, but the gist of what we're doing here is creating a dataframe with our alternatives and displaying it as a table with `kable()`. \n\nThe main function doing this work in the [conjoint table](https://surveydown.org/templates/conjoint_tables) template is the `make_cbc_table()` function, which looks like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(dplyr)\nlibrary(kableExtra)\n\n# Function to create the options table for a given choice question\nmake_cbc_table <- function(df) {\n alts <- df |>\n mutate(\n price = paste(scales::dollar(price), \"/ lb\"),\n image = paste0('')\n ) |>\n # Make nicer attribute labels\n select(\n `Option:` = altID,\n ` ` = image,\n `Price:` = price,\n `Type:` = type,\n `Freshness:` = freshness\n )\n row.names(alts) <- NULL # Drop row names\n\n table <- kbl(t(alts), escape = FALSE) |>\n kable_styling(\n bootstrap_options = c(\"striped\", \"hover\", \"condensed\"),\n full_width = FALSE,\n position = \"center\"\n )\n function() {\n table\n }\n}\n```\n:::\n\n\nIt is important to note that the table just shows the options, but it doesn't allow respondents to indicate their choice. So we still need to create a basic choice question like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbc1_options <- c(\"option_1\", \"option_2\", \"option_3\")\nnames(cbc1_options) <- c(\"Option 1\", \"Option 2\", \"Option 3\")\n\nsd_question(\n type = 'mc_buttons',\n id = 'cbc_q1',\n label = \"(1 of 6) If the above options were your only options, which would you choose?\",\n option = cbc1_options\n)\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
\n
\n\n
\n
\n
\n\n\n\n\n\n\n
\n
\n\n
\n*\n
\n```\n\n:::\n:::\n\n\nThe final result is a set of choice questions that each look something like this:\n\n
\n\n
\n\n## Preview and check\n\nThe rest of the `server()` function in the `app.R` file has the remaining components we need, like any conditional display or skip logic. This is all standard features of any surveydown survey, so we won't cover them in detail here and instead direct you to the [documentation](https://surveydown.org/docs.html) for details.\n\nBut before you go live, it's a good idea to do some quick testing. You can test your survey even without having it connected to a database by setting `ignore = TRUE` in the `sd_db_connect()` function. Of course, you probably should also test it after connecting it to a database to ensure that responses are being properly stored. \n\nWhen testing, you might get an error - don't panic! Read the terminal output carefully and debug. There's a good chance you may have missed a bug somewhere in your server code. Look in your `app.R` file to see if you can spot the error.\n\n## Getting the data\n\nOnce your survey is live and you start collecting responses, you can easily access your data with the `sd_get_data()` function. This is typically done in a separate R file (e.g., see [here](https://github.com/surveydown-dev/templates/blob/main/conjoint_buttons/code/2_get_raw_data.R)), which might look something like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(surveydown)\n\n# Connect to database\ndb <- sd_db_connect()\n\n# Pull in the data\ndf <- sd_get_data(db)\n```\n:::\n\n\nFor this to work, you will first have to make sure you've set up your database connection using `sd_db_config` (see [here](https://surveydown.org/docs/storing-data) for more).\n\nAnd that's it! We hope this post was helpful, and do go check out the [conjoint buttons](https://surveydown.org/templates/conjoint_buttons) and [conjoint table](https://surveydown.org/templates/conjoint_tables) templates to try out the demos yourself!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": { - "include-in-header": [ - "\n" - ] - }, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blog/2024-09-18-new-app-design/index/execute-results/html.json b/_freeze/blog/2024-09-18-new-app-design/index/execute-results/html.json deleted file mode 100644 index 8187f6a0..00000000 --- a/_freeze/blog/2024-09-18-new-app-design/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "df4411b3662b72f51e5e354fc5c89141", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: New architecture in v0.3.0 (and loads of breaking changes)!\ndescription: |\n We're releasing v0.3.0, and with it multiple breaking changes.\ndate: '2024-09-18'\nimage: \"banner.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: image.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\nformat: html\nexecute: \n eval: false\ntoc: true\nlightbox: true\n---\n\n\n\nThe surveydown package is only a couple months old, and thanks to many early users we learned about some design flaws that needed to be fixed. As a result, with the release of {surveydown} 0.3.0, the package has undergone a major overhaul to improve security, modularity, and extensibility. \n\nWe introduced several major breaking changes in this release, so we felt we should make a blog post to both explain why we felt these changes were needed as well as introduce the new architecture we have adopted.\n\n## Motivation\n\nIn the original conception of surveydown, the entire survey was defined in a single Quarto shiny document that would render into a shiny app. While this was a rather parsimonious design, it also had some flaws that weren't immediately apparent.\n\nPerhaps the largest issue was **page security** (see [this issue](https://github.com/surveydown-dev/surveydown/issues/93)). With Quarto shiny documents, the qmd file is first rendered into a static html page that is then used to define the elements of the user interface in the resulting shiny app. This meant that for us to introduce _pages_, we relied on a combination of JS and CSS to hide and show the page divs based on users clicking on next buttons. While this resulted in a nice user experience, under the hood the entire app was still just one big html page with all of the content available. \n\nThis design meant that anyone could still see the source code to any component of the survey they wanted. A user could simply right-click and open \"Inspect\" then manually change the CSS of a page div from `style=\"display: none;\"` to `style=\"display: show;\"` and boom - the \"page\" would appear!\n\nThis was obviously a major security issue as pages with things like completion codes or redirect buttons at the end could be easily shown without going through the whole survey. The only solution was an architectural overhaul that would only show the content on one page at a time.\n\n## New architecture\n\nThe new architecture employs a two-file design composed of a _survey_ and an _app_ that renders to a traditional Shiny app:\n\n- `survey.qmd`: A Quarto document that contains the survey content (pages, questions, etc), which renders to an HTML file.\n- `app.R`: An R script defining a Shiny app that contains the global settings (libraries, database configuration, etc.) and server configuration options (e.g., conditional skipping / display, etc.).\n\n**These files must be named `survey.qmd` and `app.R`**.\n\nThey typically look something like this:\n\n::: {.panel-tabset}\n\n## `survey.qmd` file\n\n````markdown\n---\nformat: html\necho: false\nwarning: false\n---\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(surveydown)\n```\n:::\n\n\n::: {#welcome .sd-page}\n\n# Welcome to our survey!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_question(\n type = 'mc',\n id = 'penguins',\n label = \"Which type of penguin do you like the best?\",\n option = c(\n 'Adélie' = 'adelie',\n 'Chinstrap' = 'chinstrap',\n 'Gentoo' = 'gentoo'\n )\n)\n\nsd_next(next_page = 'end')\n```\n:::\n\n\n:::\n\n::: {#end .sd-page}\n\nThis it the last page in the survey\n\n:::\n\n````\n\n## `app.R` file\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(surveydown)\n\ndb <- sd_database(\n host = \"\",\n dbname = \"\",\n port = \"\",\n user = \"\",\n table = \"\",\n ignore = TRUE\n)\n\nserver <- function(input, output, session) {\n\n # Define conditional skip logic here (skip to page if a condition is true)\n sd_skip_if()\n\n # Define conditional display logic here (show a question if a condition is true)\n sd_show_if()\n\n # Main server to control the app\n sd_server()\n}\n\nshiny::shinyApp(ui = sd_ui(), server = server)\n```\n:::\n\n\n:::\n\nThis approach allows us to separate the _survey content_ (in the `survey.qmd` file) from the _survey logic_ (in the `app.R` file), which comes with a few benefits:\n\n1. **Security**: Since the rendered survey content is no longer directly embedded in the app as a single html page, it makes it much harder for users to tamper with the content. Only the content on one page at a time will be rendered by the server.\n2. **Clarity**: With two files, it is now clearer where the survey content versus control logic should be defined. Before, all of the server logic was in a single server code chunk at the end of the `survey.qmd` file, which required the user to scroll up and down to edit the server logic versus the survey content. Now a user can have both files open in two tabs in an IDE and more easily edit the survey content and server content.\n3. **Simplicity**: The new design eliminates the need for a Quarto extension to render the survey. This allows us to ship all of the core functionality of surveydown as a single R package, which is installed globally on your system.\n\nThe updated documentation of the [Survey Components](https://surveydown.org/manuals/survey-components) page reflects this new design.\n\n## New page architecture\n\nThe motivation to secure the page content led to a totally new approach to designing the survey pages. Our new approach actually renders the `survey.qmd` into a static html page and then parses it into a list of page objects. Each page object is itself a list of elements, including the page ID, question IDs, etc., as well as the rendered html content for that page. \n\nThe `sd_server()` function then uses this list of page objects to display one page at a time via a `shiny::renderUI()` function into a single \"main\" output. This approach allowed us to control what content is being served, eliminating the ability of survey respondents to see anything other than the content on the current page.\n\nThis approach also gave us the opportunity to overhaul how pagination works in general. Previously, users had to add a `sd_next(next_page = \"page_id\")` button at the end of each page, making sure to specify the next page to go to. This was a bit annoying as most of the time you just want to go to the next page, so specifying it felt redundant. Now users can simply add `sd_next()` at the bottom of each page and the server will go to the next page by default. If you want to direct the respondent to a different page, then you specify the target page using `sd_next(next_page = \"page_id\")`.\n\n## Improved conditional show and skip logic\n\nConditionally displaying questions or skipping to pages is a core logic that many surveys need. Our original approach was relatively clunky, so since we were already introducing many breaking designs, we figured we should overhaul the logic for conditional skipping and displaying.\n\nThe new approach uses just two functions: `sd_skip_if()` and `sd_show_if()`. These functions can be provided in the main `server()` function in the `app.R` file to define the conditions and targets for either conditional displaying a question or conditionally skipping to a page. The structure for each condition in these new functions is always as follows:\n\n> ` ~ \"target_question_id\"`\n\nAs an example, let's say we want to show a question called `\"penguins_other\"` if the respondent chose the `\"other\"` option in a question called `\"penguins\"`. We could do this with the following code in the `app.R` file:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nserver <- function(input, output, session) {\n\n sd_show_if(\n input$penguins == \"other\" ~ \"penguins_other\"\n )\n\n sd_server(db = db)\n\n}\n```\n:::\n\n\nYou can provide multiple conditions to the `sd_show_if()` function, each separated by a comma. The `sd_skip_if()` function works the same way, but it will skip to a target page instead of showing a target question. See the revised [Conditional Control](https://surveydown.org/manuals/conditional-control) page for more details on the new changes.\n\n## No more `sd_config()` function\n\nOne more small change we made is that the `sd_config()` function is no longer needed. Since we moved the conditional skip and show logic into their own functions, we took the remaining arguments that used to be provided to `sd_config()` and added them to the `sd_server()` function as options. You can now simply pass these arguments to the `sd_server()` function in the `app.R` file.\n\n## Our apologies\n\nThat's about it for the changes with v0.3.0. We want to send our deepest apologies for anyone who has already begun a study using the orginal design. The most recent version prior to v0.3.0 was v0.2.4, so this is the version you should install if you want to stick with the old design. \n\nThat said, all development will now continue on this new design, so we strongly recommend updating to the new version and converting any existing surveys to the new design. The biggest change you'll need to make is to move your server logic out of the `survey.qmd` file and into the `app.R` file. We've also updated all our [demos](https://surveydown.org/demos) to the new design, so you can refer to these for examples on how to convert your existing surveys.\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blog/2024-11-01-surveydown-on-cran/index/execute-results/html.json b/_freeze/blog/2024-11-01-surveydown-on-cran/index/execute-results/html.json deleted file mode 100644 index 86452bd2..00000000 --- a/_freeze/blog/2024-11-01-surveydown-on-cran/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "d80a46f4c759d9bb14867f4cd382455b", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: surveydown is on CRAN 🎉!\ndescription: |\n It's actually been on CRAN since v0.4.0, but we've been making so many updates that we're now already on v0.7.2!\ndate: '2024-12-20'\nimage: \"image.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: image.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - CRAN\nformat: html\nexecute: \n eval: false\ntoc: true\nlightbox: true\n---\n\n\nWe have some exciting news to share - surveydown is on CRAN 🎉!\n\nYou can now install surveydown from CRAN using the usual `install.packages()` function:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"surveydown\")\n```\n:::\n\n\nActually, surveydown has been on CRAN since v0.4.0, but we've been making so many updates that we're now already on v0.7.2 before we could even make a post about being on CRAN!\n\nSo it seems about time we highlight some of the improvements and new features we've added recently.\n\n## New functions / features\n\nWe added a whole bunch of new functions and features since v0.3.0, which was a major architectural change to the package. But the changes we made in v0.3.0 made it lot easier to support more features. There have been a lot, so I'm probably missing some, but here are some quick highlights that are worth calling out:\n\n- In `sd_question()`, we added a new `type = \"matrix\"` to support matrix type questions.\n- We added a new `sd_is_answered()` function to check if a question is answered or not. This is useful for conditional reactive questions defined in the server.\n- We added a new `sd_completion_code()` function that generates a random completion code for your survey.\n- We added a new `sd_close()` function to make a button that closes the survey.\n- [Stefan Munnes](https://github.com/StefanMunnes) [added](https://github.com/surveydown-dev/surveydown/pull/138) a translation feature that allows you to customize the system messages in the app to any language you want.\n- We modified how data handling is done so that each respondent's state in the survey can be restored via cookies if they refresh the browser window or close and re-open the window. Before, respondents would be sent back to the start of the survey and a new entry in the database would be created, so this features allows respondents to pause and come back to the survey without that happening.\n- We added two new helper functions, `sd_add_question()` and `sd_add_page()`, that make it easier to quickly create template questions and pages. They work like this:\n\n
\n\n
\n\n## Performance improvements \n\nOne major improvement we've made is to streamline how the `survey.qmd` file is updating. Before you needed to render the `survey.qmd` file yourself before running the `app.R` file, but now you actually never need to render it. All rendering is handled when you run the shiny app via the `app.R` file. \n\nAnd this rendering is \"smart\" too - it renders the `survey.qmd` file into an html file, then it parses that file for all of the content needed for the survey and stores each piece in a `_survey` folder. This folder is then what the shiny app uses to load the survey. If no changes are detected in the `survey.qmd` file or any of the components in the `_survey` folder, then the app will always load content from the pre-rendered content in the `_survey` folder, which will load nearly instantaneously! Only if you make a change to your survey will it be re-rendered, and again once that rendering is done once it won't run again until another change is detected. This results in a much more efficient app that loads nearly instantaneously.\n\nWe also improved the database updating to be much more efficient. It used to run a write operation to the database on every single question ineraction, but that slowed down the app significantly. Now that we have cookies enabled, we do the immediate record keeping in the cookies and only write to the database when the respondent closes the survey or turns the page. This is a more reasonable compromise that makes the app feel snappy while still ensuring that the data is saved even if the respondent accidentally closes the window.\n\n## Our first community contributions!\n\nOver the past few months, we've been getting a lot of contributions from the community! We've been able to merge a few pull requests that add some new features and fix some bugs. Here are some of the highlights:\n\n- [Stefan Munnes](https://github.com/StefanMunnes) added a new translation feature that allows you to customize the system messages in the app to any language you want. He also helped us update `sd_output()` to be able to output the chosen question values, chosen question option label(s), and the question label itself. This addresses [feature request #128](https://github.com/orgs/surveydown-dev/discussions/128).\n- [Zain Hoda](https://github.com/zainhoda1) contributed a new `auto_scroll` parameter to the `sd_server()` function that allows you to turn off auto scrolling. This is useful if you have a lot of questions and want to make sure the respondent can see the entire question.\n\nWe're excited to see what the community will build with surveydown! If you build something with surveydown, please let us know on the [GitHub Discussions](https://github.com/orgs/surveydown-dev/discussions) so we can highlight your work!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blog/2024-12-20-surveydown-on-cran/index/execute-results/html.json b/_freeze/blog/2024-12-20-surveydown-on-cran/index/execute-results/html.json deleted file mode 100644 index e6abb141..00000000 --- a/_freeze/blog/2024-12-20-surveydown-on-cran/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "b77d1f8f3570d78c1618a419c854f113", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: surveydown is on CRAN 🎉!\ndescription: |\n It's actually been on CRAN since v0.4.0, but we've been making so many updates that we're now already on v0.7.2!\ndate: '2024-12-20'\nimage: \"banner.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: banner.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - CRAN\nformat: html\nexecute: \n eval: false\ntoc: true\nlightbox: true\n---\n\nWe have some exciting news to share - surveydown is on CRAN 🎉!\n\nYou can now install surveydown from CRAN using the usual `install.packages()` function:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"surveydown\")\n```\n:::\n\n\nActually, surveydown has been on CRAN since v0.4.0, but we've been making so many updates that we're now already on v0.7.2 before we could even make a post about being on CRAN!\n\nSo it seems about time we highlight some of the improvements and new features we've added recently.\n\n## New functions / features\n\nWe added a whole bunch of new functions and features since v0.3.0, which was a major architectural change to the package. But the changes we made in v0.3.0 made it lot easier to support more features. There have been a lot, so I'm probably missing some, but here are some quick highlights that are worth calling out:\n\n- In `sd_question()`, we added a new `type = \"matrix\"` to support matrix type questions.\n- We added a new `sd_is_answered()` function to check if a question is answered or not. This is useful for conditional reactive questions defined in the server.\n- We added a new `sd_completion_code()` function that generates a random completion code for your survey.\n- We added a new `sd_close()` function to make a button that closes the survey.\n- [Stefan Munnes](https://github.com/StefanMunnes) [added](https://github.com/surveydown-dev/surveydown/pull/138) a translation feature that allows you to customize the system messages in the app to any language you want.\n- We modified how data handling is done so that each respondent's state in the survey can be restored via cookies if they refresh the browser window or close and re-open the window. Before, respondents would be sent back to the start of the survey and a new entry in the database would be created, so this features allows respondents to pause and come back to the survey without that happening.\n- We added two new helper functions, `sd_add_question()` and `sd_add_page()`, that make it easier to quickly create template questions and pages. They work like this:\n\n
\n\n
\n\n## Performance improvements \n\nOne major improvement we've made is to streamline how the `survey.qmd` file is updating. Before you needed to render the `survey.qmd` file yourself before running the `app.R` file, but now you actually never need to render it. All rendering is handled when you run the shiny app via the `app.R` file. \n\nAnd this rendering is \"smart\" too - it renders the `survey.qmd` file into an html file, then it parses that file for all of the content needed for the survey and stores each piece in a `_survey` folder. This folder is then what the shiny app uses to load the survey. If no changes are detected in the `survey.qmd` file or any of the components in the `_survey` folder, then the app will always load content from the pre-rendered content in the `_survey` folder, which will load nearly instantaneously! Only if you make a change to your survey will it be re-rendered, and again once that rendering is done once it won't run again until another change is detected. This results in a much more efficient app that loads nearly instantaneously.\n\nWe also improved the database updating to be much more efficient. It used to run a write operation to the database on every single question ineraction, but that slowed down the app significantly. Now that we have cookies enabled, we do the immediate record keeping in the cookies and only write to the database when the respondent closes the survey or turns the page. This is a more reasonable compromise that makes the app feel snappy while still ensuring that the data is saved even if the respondent accidentally closes the window.\n\n## Our first community contributions!\n\nOver the past few months, we've been getting a lot of contributions from the community! We've been able to merge a few pull requests that add some new features and fix some bugs. Here are some of the highlights:\n\n- [Stefan Munnes](https://github.com/StefanMunnes) added a new translation feature that allows you to customize the system messages in the app to any language you want. He also helped us update `sd_output()` to be able to output the chosen question values, chosen question option label(s), and the question label itself. This addresses [feature request #128](https://github.com/orgs/surveydown-dev/discussions/128).\n- [Zain Hoda](https://github.com/zainhoda1) contributed a new `auto_scroll` parameter to the `sd_server()` function that allows you to turn off auto scrolling. This is useful if you have a lot of questions and want to make sure the respondent can see the entire question.\n\nWe're excited to see what the community will build with surveydown! If you build something with surveydown, please let us know on the [GitHub Discussions](https://github.com/orgs/surveydown-dev/discussions) so we can highlight your work!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blog/2025-04-08-surveydown-gadgets/index/execute-results/html.json b/_freeze/blog/2025-04-08-surveydown-gadgets/index/execute-results/html.json deleted file mode 100644 index 1848d6f6..00000000 --- a/_freeze/blog/2025-04-08-surveydown-gadgets/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "08a43fd02e90b33e92ded8bad13cd4d2", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: surveydown Gadgets for Page and Question Creation\nauthor: Pingfan Hu\ndescription: |\n A brief walkthrough of surveydown gadgets for creating pages and questions using RStudio.\ndate: '2025-04-08'\nimage: \"banner.png\"\nnumber-sections: true\ncategories:\n - RStudio\n - Tutorial\nformat: html\nexecute: \n eval: false\ntoc: true\nlightbox: true\n---\n\n\n\n## Introduction\n\nWhile surveydown works with various IDEs, the gadget features perform best in RStudio. These gadgets provide a user-friendly interface for creating survey elements without having to remember the exact syntax or function parameters.\n\nTwo primary gadgets are offered by surveydown:\n\n1. **Survey Page Gadget** - Creates a new survey page\n2. **Survey Question Gadget** - Creates a new survey question\n\nHere is a showcase of the **Survey Page Gadget** in RStudio:\n\n
\n
\n\n
\n
\n\nThe **Survey Question Gadget**:\n\n
\n
\n\n
\n
\n\nAs you can see, these gadgets simplify the process of adding survey components. This blog walks you through how to access and use these gadgets.\n\n## Accessing the Gadgets\n\nThe gadgets are powered by the `sd_page_gadget()` and `sd_question_gadget()` functions, but you don't need to call these functions directly.\n\nYou can access these gadgets in RStudio in two ways:\n\n### Using the Addins Menu\n\n1. Click on the \"Addins\" dropdown menu in the RStudio toolbar\n2. Type \"surveydown\" in the search box\n3. Select either \"Add Survey Page\" or \"Add Survey Question\"\n\nBelow is a screenshot of the Addins menu with surveydown gadgets:\n\n
\n
\n\n
\n
\n\n### Keyboard Shortcuts (Recommended)\n\nFor more efficient workflow, set up keyboard shortcuts:\n\n1. Go to Tools → Addins → Browse Addins...\n\n
\n
\n \n
\n
\n\n2. In the Addins popup window, click on the \"Keyboard shortcuts...\" button on the bottom left corner.\n\n
\n
\n \n
\n
\n\n3. Input \"survey\" in the search box.\n\n
\n
\n \n
\n
\n\n4. Assign the following shortcuts:\n - `Ctrl+Shift+P` for the Survey Page Gadget\n - `Ctrl+Shift+Q` for the Survey Question Gadget\n \n Or if you have other preferences, feel free to customize them.\n\n## Using the Gadgets\n\n### Survey Page Gadget\n\n
\n
\n\n
\n
\n\nThe page gadget is straightforward:\n\n1. Press `Ctrl+Shift+P` (or use the Addins menu)\n2. Enter a Page ID (no spaces allowed)\n3. Click \"Create Page\" or press Enter\n\nThis will insert a properly formatted page block at your cursor position, including an R code chunk ready for adding questions.\n\nLet's test it! After creating a page with ID \"demographic_info\", you should see:\n\n```r\n## Demographic Information {#demographic_info}\n\n::: {.cell}\n\n```{.r .cell-code}\n# Add your questions here\n```\n:::\n\n### Survey Question Gadget\n\nThe question gadget offers more options:\n\n1. Press `Ctrl+Shift+Q` (or use the Addins menu)\n2. Fill in the following:\n - **Question Type**: Select from the dropdown (default is \"Multiple Choice\")\n - **Question ID**: Enter a unique identifier (no spaces)\n - **Question Label**: Enter the actual question text\n3. **R Chunk Option**: Check this box if you need the question to be inserted within an R code chunk\n4. Click \"Create Question\" or press Enter\n\nFor example, creating a multiple-choice question with ID \"gender\" and label \"What is your gender?\" would produce:\n\n```r\nsd_multiple_choice(\n id = \"gender\",\n label = \"What is your gender?\",\n choices = c(\"Male\", \"Female\", \"Non-binary\", \"Prefer not to say\")\n)\n```\n\nNote that if you created the question inside an existing page's R chunk, you don't need to check the \"R Chunk Option\". If you're adding a question elsewhere and need it to be in an R chunk, check that box.\n\n## Example Workflow\n\nHere's how a typical workflow might look:\n\n1. Create a new page for demographics:\n - Press `Ctrl+Shift+P`\n - Enter \"demographics\" as Page ID\n - Press Enter\n\n2. Add a gender question:\n - Press `Ctrl+Shift+Q`\n - Select \"Multiple Choice\" as the question type\n - Enter \"gender\" as Question ID\n - Enter \"What is your gender?\" as Question Label\n - Press Enter\n\n3. Add an age question:\n - Press `Ctrl+Shift+Q`\n - Select \"Numeric\" as the question type\n - Enter \"age\" as Question ID\n - Enter \"What is your age?\" as Question Label\n - Press Enter\n\nBy following these steps and using the gadgets, you'll create well-structured surveys much faster than coding everything manually.\n\n## Conclusion\n\nThe surveydown gadgets make survey creation in R significantly more efficient, especially for those who prefer a visual interface for building survey components. With keyboard shortcuts set up, you can rapidly build complex surveys with minimal typing and without having to remember the specific function syntax for different question types.\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blog/2025-11-15-shorthand-page-syntax/index/execute-results/html.json b/_freeze/blog/2025-11-15-shorthand-page-syntax/index/execute-results/html.json deleted file mode 100644 index c29d7865..00000000 --- a/_freeze/blog/2025-11-15-shorthand-page-syntax/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "a82905770e861fd347e12aeb03e4e0b3", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: \"Introducing Shorthand Page Syntax in v0.14.0\"\nauthor:\n - name: John Paul Helveston\ndescription: |\n Introducing a new shorthand syntax for page definition to make your survey code cleaner and easier to read.\ndate: '2025-11-01'\nimage: \"banner.png\"\ncategories:\n - R\n - package\n - syntax\n - feature\nformat: html\nexecute:\n eval: false\ntoc: true\nlightbox: true\n---\n\n\nWe are excited to announce a significant improvement to the surveydown developer experience in package version 0.14.0: **shorthand page syntax**. This new syntax makes defining pages in your surveys dramatically cleaner and easier to read, while maintaining full backward compatibility with the existing fence syntax.\n\n## The Problem: Verbose Fence Syntax\n\nSince the beginning, surveydown has used Quarto's fence syntax (three colons `:::`) to define pages. While this approach works well and leverages familiar Quarto conventions, it requires explicit opening and closing tags for every page:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n::: {.sd_page id=welcome}\n\nWelcome to our survey!\n\n:::\n\n::: {.sd_page id=demographics}\n\nPlease tell us about yourself.\n\n:::\n\n::: {.sd_page id=questions}\n\nNow for the main questions...\n\n:::\n```\n:::\n\n\nAs your surveys grow, these closing `:::` tags add visual clutter and make it harder to see where one page ends and another begins. You also need to remember to close every page - forgetting a closing fence can cause parsing errors.\n\n## The Solution: Shorthand Syntax\n\nWith surveydown package version 0.14.0, we're introducing a new **shorthand syntax** that eliminates the need for closing tags. Instead, you simply use three dashes `---` followed by the page ID:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n--- welcome\n\nWelcome to our survey!\n\n--- demographics\n\nPlease tell us about yourself.\n\n--- questions\n\nNow for the main questions...\n```\n:::\n\n\nThat's it! Each new page delimiter automatically closes the previous page, so there's no need to track opening and closing tags. The syntax is cleaner, more concise, and easier to scan visually.\n\nThe shorthand syntax offers several advantages:\n\n1. **Cleaner code**: Eliminates visual clutter from closing tags\n2. **Easier to scan**: Page delimiters stand out clearly, making survey structure obvious\n3. **Less typing**: Fewer characters to type means faster survey development\n4. **Fewer errors**: No need to remember closing tags - can't forget what doesn't exist!\n5. **Better git diffs**: When you add or modify pages, diffs are cleaner without closing tag changes\n\n## Example\n\nLet's look at another survey example to see the improvement. Here's the same survey written both ways:\n\n::: {.panel-tabset}\n\n## Fence Syntax (Old)\n\n\n::: {.cell}\n\n````{.r .cell-code}\n---\nformat: html\necho: false\n---\n\n```{r}\nlibrary(surveydown)\n```\n\n::: {.sd_page id=welcome}\n\n# Welcome!\n\nThank you for participating in our study.\n\n```{r}\nsd_next()\n```\n\n:::\n\n::: {.sd_page id=consent}\n\n## Informed Consent\n\nPlease read the following information carefully...\n\n```{r}\nsd_question(\n type = 'mc',\n id = 'consent',\n label = \"Do you consent to participate?\",\n option = c(\n 'Yes, I consent' = 'yes',\n 'No, I decline' = 'no'\n )\n)\n\nsd_next()\n```\n\n:::\n\n::: {.sd_page id=demographics}\n\n## Demographics\n\n```{r}\nsd_question(\n type = 'numeric',\n id = 'age',\n label = \"What is your age?\"\n)\n\nsd_question(\n type = 'mc',\n id = 'education',\n label = \"What is your highest level of education?\",\n option = c(\n 'High school' = 'hs',\n 'Bachelor\\'s degree' = 'ba',\n 'Graduate degree' = 'grad'\n )\n)\n\nsd_next()\n```\n\n:::\n\n::: {.sd_page id=survey_questions}\n\n## Main Questions\n\n```{r}\nsd_question(\n type = 'mc',\n id = 'satisfaction',\n label = \"How satisfied are you with our product?\",\n option = c(\n 'Very satisfied' = '5',\n 'Satisfied' = '4',\n 'Neutral' = '3',\n 'Dissatisfied' = '2',\n 'Very dissatisfied' = '1'\n )\n)\n\nsd_next()\n```\n\n:::\n\n::: {.sd_page id=end}\n\n## Thank You!\n\nYour responses have been recorded. You may now close this window.\n\n```{r}\nsd_close()\n```\n\n:::\n````\n:::\n\n\n## Shorthand Syntax (New)\n\n\n::: {.cell}\n\n````{.r .cell-code}\n---\nformat: html\necho: false\n---\n\n```{r}\nlibrary(surveydown)\n```\n\n--- welcome\n\n# Welcome!\n\nThank you for participating in our study.\n\n```{r}\nsd_next()\n```\n\n--- consent\n\n## Informed Consent\n\nPlease read the following information carefully...\n\n```{r}\nsd_question(\n type = 'mc',\n id = 'consent',\n label = \"Do you consent to participate?\",\n option = c(\n 'Yes, I consent' = 'yes',\n 'No, I decline' = 'no'\n )\n)\n\nsd_next()\n```\n\n--- demographics\n\n## Demographics\n\n```{r}\nsd_question(\n type = 'numeric',\n id = 'age',\n label = \"What is your age?\"\n)\n\nsd_question(\n type = 'mc',\n id = 'education',\n label = \"What is your highest level of education?\",\n option = c(\n 'High school' = 'hs',\n 'Bachelor\\'s degree' = 'ba',\n 'Graduate degree' = 'grad'\n )\n)\n\nsd_next()\n```\n\n--- survey_questions\n\n## Main Questions\n\n```{r}\nsd_question(\n type = 'mc',\n id = 'satisfaction',\n label = \"How satisfied are you with our product?\",\n option = c(\n 'Very satisfied' = '5',\n 'Satisfied' = '4',\n 'Neutral' = '3',\n 'Dissatisfied' = '2',\n 'Very dissatisfied' = '1'\n )\n)\n\nsd_next()\n```\n\n--- end\n\n## Thank You!\n\nYour responses have been recorded. You may now close this window.\n\n```{r}\nsd_close()\n```\n````\n:::\n\n\n:::\n\nNotice how much cleaner the shorthand version is? The page boundaries are immediately visible, and there's no visual clutter from closing tags. Your survey structure becomes easier to understand at a glance.\n\n## Backward Compatibility\n\nWe want to emphasize that **both syntaxes work perfectly** in v0.14.0 and beyond. Your existing surveys using fence syntax will continue to work without any changes. We're introducing shorthand syntax as the **recommended** approach going forward, but you're free to use whichever style you prefer.\n\n::: {.callout-caution}\n\nBe careful not to mix both syntaxes within the same survey. Each survey must define pages using either the fence syntax or shorthand syntax exclusively.\n\n:::\n\n## Updated Documentation\n\nAll of our documentation has been updated to use the new shorthand syntax as the primary example. However, we've kept documentation for both approaches:\n\n- The [Page Navigation](https://surveydown.org/docs/page-navigation.html#defining-pages) page explains both syntaxes in detail\n- The [Basic Components](https://surveydown.org/docs/basic-components.html#defining-pages) page introduces shorthand syntax\n- All our [templates](https://surveydown.org/templates.html) will be updated to use shorthand syntax\n\n## Migration Guide\n\nThe good news is you do not need to change anything in your existing surveys. They will continue to work as before. However, if you want to take advantage of the cleaner shorthand syntax, migrating is easy:\n\n1. Replace `::: {.sd_page id=PAGE_ID}` with `--- PAGE_ID`\n2. Remove all closing `:::` tags\n3. Make sure each page starts with `---` followed by a space and the page ID\n\nFor example, convert this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n::: {.sd_page id=page1} \n\nContent here\n\n:::\n\n::: {.sd_page id=page2}\n\nMore content\n\n:::\n```\n:::\n\n\nTo this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n--- page1\n\nContent here\n\n--- page2\n\nMore content\n```\n:::\n\n\nThat's all there is to it!\n\n## Looking Forward\n\nThis improvement is part of our ongoing effort to make surveydown as developer-friendly as possible. We're constantly listening to user feedback and looking for ways to reduce friction in the survey development process.\n\nIf you have suggestions for future improvements, please let us know through [GitHub Discussions](https://github.com/orgs/surveydown-dev/discussions) or by filing an issue on our [GitHub repository](https://github.com/surveydown-dev/surveydown).\n\n## Getting Started\n\nTo start using the shorthand syntax, simply update to surveydown v0.14.0 or later:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Install from CRAN (once 0.14.0 is released)\ninstall.packages(\"surveydown\")\n\n# Or install the development version from GitHub\npak::pak(\"surveydown-dev/surveydown\")\n```\n:::\n\n\nThen start using `---` followed by your page IDs in your survey.qmd files. It's that simple!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blogs/2024-08-21-introducing-surveydown/index/execute-results/html.json b/_freeze/blogs/2024-08-21-introducing-surveydown/index/execute-results/html.json deleted file mode 100644 index 285dd859..00000000 --- a/_freeze/blogs/2024-08-21-introducing-surveydown/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "62e64feb0c4f0ea50456ed00c68510dc", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: \"Introducing surveydown: A markdown-based framework for generating surveys with Quarto and shiny\"\ndescription: |\n A quick overview of the {surveydown} R package for making markdown-based surveys with open-source technologies: Quarto, shiny, and supabase.\ndate: '2024-08-21'\nimage: \"logo.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: logo.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - package\n - markdown\n - shiny\n - package\n - quarto\nformat: html\ntoc: true\ntoc-depth: 3\nlightbox: true\nexecute:\n eval: false\n---\n\n\n\n
\n\n
\n
\n\n::: {.callout-important}\n\n**This post was made just after launching surveydown. Much of the platform architecture has changed since then, so check the [documentation](https://surveydown.org/docs.html) for the latest correct information.**\n\n:::\n\n::: {.callout-note}\n\n**Note:** This post is largely a copy of the [a similar post](https://www.jhelvy.com/blog/2024-08-10-introducing-surveydown/) I made on my personal website, with some minor edits.\n\n:::\n\nThis post introduces the {surveydown} R package, a new way to design surveys using markdown, R, Quarto, and shiny. The idea for this platform has been brewing for a while (see [this blog post](https://jhelvy.com/blog/2023-04-06-markdown-surveys/) for more on the motivation for this project), but now the package is finally here! \n\nIn this post, I'm going to show you a quick overview of the {surveydown} R package for making markdown-based surveys as well as a little about _why_ we built surveydown.\n\n# A new way to design surveys\n\n[surveydown](https://surveydown.org/) is a flexible platform for making surveys in {{< fa brands r-project >}} using three open source technologies:\n[Quarto](https://quarto.org/), [shiny](https://shiny.posit.co/), and\n[supabase](https://supabase.com/). The package is still in development, but you can already use it to create surveys. \n\n
\n
\n\n
\n
\n\nThe basic concept is this:\n\n1. Design your survey as a [Quarto shiny\n document](https://quarto.org/docs/dashboards/interactivity/shiny-r.html)\n using markdown and R code.\n2. Render your doc into a [shiny](https://shiny.posit.co/) app that can\n be hosted online and sent to respondents.\n3. Store your survey responses in a [supabase](https://supabase.com/)\n database.\n\nIn surveydown, your entire survey is designed using markdown and R code in a single Quarto document. There is no GUI or drag-and-drop interface - simply write plain text (markdown and R code) and boom - you have a survey! \n\nThe [{surveydown} package](https://pkg.surveydown.org/) provides a set of functions for defining the survey content and control logic. Each function starts with `sd_` to make them easy to identify. You can add content to your survey using markdown formatting, or in RStudio you can edit with the visual editor. Survey questions are defined in R code chunks with the `sd_question()` function. Pages are defined using fences (`:::`), and navigation buttons handled with the `sd_next()` function. You can modify the control logic in the server code chunk (the last code chunk at the bottom of the .qmd file) with the `sd_config()` function, and you can configure the database with the `sd_database()`. \n\nThe [documentation](https://surveydown.org/docs.html) has many more details on all of this, and later in this post I'll give a quick overview of a few of these features. But first, let me tell you a little about _why_ we decided to build surveydown.\n\n# Why did we build surveydown?\n\n> Do we really need another survey platform?\n\nLike many researchers who do a lot of survey work, I've been frustrated with most survey platforms available. Commerical platforms like Qualtrics and SurveyMonkey are great, but they are expensive and are difficult to version control, and collaboration with others is near impossible, especially if your collaborator doesn't have a license of their own. They also don't allow me to own my own survey data, meaning I always am at the mercy of the platform owner. And the features are often limited. It's not easy to do more complex things like randomization, conditional display, etc.\n\nThe only other open-source survey platform I have used is [formr](https://formr.org/), which is a very powerful platform, but it is rather clunky to use (you define your survey in Google sheet cells...there's a learning curve), and it is not as easy to edit as a simple markdown file. \n\nWhat we've come up with is a survey platform that is flexible, _relatively_ easy to use, and built entirely with open-source technologies. I think it solves a lot of problems, and hopefully someone out there will find it useful.\n\n## Open source\n\nsurveydown is built entirely with open-source technologies, making it transparent and customizable. Best of all, no expensive licenses! Just install and use it! \n\n## Own your data\n\nWith surveydown, you retain full ownership and control of your survey data. The responses are stored in your own Supabase database, ensuring that you have complete access to the data. This is particularly important for researchers dealing with sensitive information or those who need to comply with specific data protection regulations. We're still working on enabling you to use your own hosted database, which will provide even more flexibility.\n\n## Ease of editing\n\nDesigning a survey in surveydown is a pretty straightforward process. The markdown-based approach allows for quick modifications and easy navigation through your survey. And since it's built on [Quarto](https://quarto.org/), you can use all of the features of Quarto to make your survey look great, like changing the theme, adding custom CSS, etc. You can also easily preview your survey as you edit it, and even run your survey locally to test it out before you deploy it, either with a button click in RStudio or with a `quarto serve` command in the terminal.\n\n## Did I mention you can run R code in your survey?\n\nEvery surveydown survey uses R code chunks for questions. But you can also insert R code for all sorts of other things. For example, if you wanted to randomize the values shown in a question, you could write some R code for that. Want to insert a plot of something? Write a little ggplot code. You can also add interactive components to your surveys, such as showing a respondent how their responses compare to others in real time.\n\n## Easy version control and collaboration\n\nBecause the entire survey is defined in a single plain text file, surveydown naturally integrates with version control systems like Git. This allows you to track changes over time, collaborate with team members, and maintain a clear history of your survey's development.\n\n## Reproducible\n\nSurveydown promotes reproducible research by allowing you to define your entire survey in a single, self-contained plain text document. This has a ton of benefits:\n\n- Want someone else to be able to reproduce your experiment? Just give them the .qmd file and any other files they need (e.g., images, data, etc.), and they can reproduce your survey on their own computer.\n- Want to print out your survey for an appendix? Render the survey with all pages visible then print it to pdf.\n- Want others to see your survey live? Just set the database into pause mode and your survey will function without recording any responses.\n\nReproducibility is something we had in mind from the start with this project, and we've tried to make it as easy as possible for your surveydown surveys to be fully reproducible.\n\n# Introduction to surveydown\n\n## Getting started \n\nAfter getting everything [installed](https://surveydown.org//docs/getting-started.html#install), we recommend starting with a template survey project. To do so, run the following in the R console:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsurveydown::sd_create_survey(\"path/to/folder\")\n```\n:::\n\n\nThis will create a folder with the following files:\n\n- `example.qmd`: a template survey you should edit.\n- `example.Rproj`: An RStudio project file (helpful if you're working in RStudio)\n- `_extensions`: A folder with the surveydown Quarto extension needed to make everything work (don't modify this).\n\nIf you have the example open in RStudio, you can click the \"Run document\" button, or in your terminal run `quarto serve example.qmd`. Either approach should render the example survey into a shiny app that you can preview in a browser. Don't worry just yet about setting up your database or making the survey live - for now, we're going to focus on designing the survey and running it locally to preview it. The example survey should look like this:\n\n
\n\n
\n\n## Adding pages\n\nIn surveydown, pages are delineated using \"fences\", like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n::: {#welcome .sd-page}\n\nPage 1 content here\n\n:::\n\n::: {#page2 .sd-page}\n\nPage 2 content here\n\n:::\n```\n:::\n\n\nAs you can see, we use three colon symbols `:::`, called a \"fence\", to mark the start and end of pages. This notation is commonly used in Quarto for a variety of use cases, like defining [subfigures](https://quarto.org/docs/authoring/figures.html#subfigures) in images.\n\nIn the starting fence, you need to define a page name (e.g. `welcome` and `page2` in the example above) and you need to define the class as `.sd-page`. Then anything you put between the page fences will appear on that page. \n\nTo navigate to the next page, you need to insert a `sd_next()` function call inside a code chunk, like this: \n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_next(next_page = 'page2')\n```\n````\n:::\n\n\nThe above code chunk will create a \"Next\" button that goes on to page 2 that looks like this:\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n\n
\n```\n\n:::\n:::\n\n\nYou would need to place the code chunk in between the `:::` fences of the `welcome` page in order to have a \"Next\" button that goes on to page 2. You can also send the user to other pages by just changing the `next_page` argument. Finally, you can also change the label of the button by changing the `label` argument, like this:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_next(next_page = 'page2', label = 'Next page')\n```\n````\n:::\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n\n
\n```\n\n:::\n:::\n\n\n## Adding questions\n\nEvery survey question is created using the `sd_question()` function inside a code chunk. The question type is defined by the `type` argument. For example, to add a multiple choice question, you could insert the following code chunk:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_question(\n type = 'mc',\n id = 'penguins',\n label = \"Which is your favorite type of penguin?\",\n option = c(\n 'Adélie' = 'adelie',\n 'Chinstrap' = 'chinstrap',\n 'Gentoo' = 'gentoo'\n )\n)\n```\n````\n:::\n\n\nThe above code chunk will create a multiple choice question that looks like this:\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n
\n\n
\n
\n\n
\n
\n\n
\n
\n\n
\n
\n
\n*\n
\n```\n\n:::\n:::\n\n\n
\n\nThe `sd_question()` function can be used to create a variety of [question types](https://surveydown.org/docs/question-types.html), like text input, select drop down choices, and more by changing the `type` argument.\n\nThe function has many other arguments for customizing the look and feel of the question (e.g., `height` and `width`, etc.).\n\n## The server chunk\n\nAt the very bottom of the .qmd file is a special \"server\" code chunk (that's the `#| context: server` bit) that defines the app server. This is where you can customize and control the survey flow logic as well as where you define the database that will store the survey response data. It looks like this:\n\n\n::: {.cell context='server'}\n\n````{.cell-code}\n```{{r}}\n#| context: server\n\n# Define the database settings\ndb <- sd_database()\n\n# Define the configuration settings\nconfig <- sd_config()\n\n# The sd_server() function initiates your survey - don't change this\nsd_server(\n input = input,\n session = session,\n config = config,\n db = db\n)\n```\n````\n:::\n\n\nThe `sd_database()` function is where you set up your database. The `sd_server()` function makes everything run, which you can safely ignore - just don't change it and all will be good!\n\nThe middle part (the `sd_config()` function) is where you can define custom control logic for the survey, such as [*conditional display*](#conditional-display) (conditionally displaying a question based on responses to questions), or [*conditional skip*](#conditional-skip) (conditionally sending the respondent to a page based on responses to questions).\n\n## Going live!\n\nOnce you are happy with your survey, you can deploy it live to any server of your choice. Since it's a shiny app, you can [deploy it to shinyapps.io](https://surveydown.org/docs/deployment.html#deploying-to-shinyapps.io) for free!\n\n# Features\n\nSince surveydown is built on top of Shiny, it provides tremendous flexibility in terms of what you can do with your survey. Below are a few examples of some commons things you may want to do with your survey.\n\n## Conditional display\n\nLet's say we had a fourth option for \"other\" in our multiple choice question about penguins. If the respondent chose it, you may want a second question to popup that allows them to specify the other penguin type. To implement this, you would need to define both questions, e.g.:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_question(\n type = 'mc',\n id = 'penguins',\n label = \"Which is your favorite type of penguin?\",\n option = c(\n 'Adélie' = 'adelie',\n 'Chinstrap' = 'chinstrap',\n 'Gentoo' = 'gentoo',\n 'Other' = 'other'\n )\n)\n\nsd_question(\n type = \"text\",\n id = \"penguins_other\",\n label = \"Please specify the other penguin type:\"\n)\n```\n````\n:::\n\n\nThen in the server code chunk, you could use the `show_if` argument to define that the `penguins_other` question would only be shown if the respondent chose the `other` option in the `penguins` question, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n show_if = tibble::tribble(\n ~question_id, ~question_value, ~target,\n \"penguins\", \"other\", \"penguins_other\"\n )\n)\n```\n:::\n\n\nThis will make the `penguins_other` question only appear if the respondent chose the `other` option in the `penguins` question, like this:\n\n
\n\n
\n
\n\nHere we're using the `tibble::tribble()` function to define a data frame with three columns:\n\n- `question_id`: The `id` of the triggering question.\n- `question_value`: The triggering value.\n- `target`: The `id` of the target question to display.\n\nYou don't have to use `tibble::tribble()`, and in fact if you have a lot of `show_if` conditions, then you could create a csv file with all of your conditions in it and read it in to set the `show_if` conditions (just make sure the header has the same three column names), e.g.:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n show_if = readr::read_csv('path/to/show_if_conditions.csv')\n )\n)\n```\n:::\n\n\n## Conditional skip\n\nOften times you'll want to screen people out of a survey based on responses to questions. For example, let's say you only wanted to only include people who own a vehicle. On your first page (e.g., with page name `welcome`), you could screen out people who do _not_ own a vehicle.\n\nFirst, define a question about their vehicle ownership, e.g.:\n\n\n::: {.cell}\n\n````{.cell-code}\n```{{r}}\nsd_question(\n type = 'mc',\n id = 'vehicle_ownership',\n label = \"Do you own your vehicle?\",\n option = c(\n 'Yes' = 'yes',\n 'No' = 'no'\n )\n)\n```\n````\n:::\n\n\nThen in the server code chunk, you could use the `skip_if` argument in `sd_config()` to define the behavior of the next button on the `welcome` page, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n skip_if = tibble::tribble(\n ~question_id, ~question_value, ~target,\n \"vehicle_ownership\", \"no\", \"screenout\"\n )\n)\n```\n:::\n\n\nThis sets up a condition where if the respondent chooses `no` on the `vehicle_ownership` question, they will be sent to a page named `screenout`. You could put such a page at the end of the survey, something like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n::: {#screenout .sd-page}\n\nSorry, but you are not qualified to take our survey.\n\n:::\n```\n:::\n\n\nNotice that I don't have a `sd_next()` on this screenout page. That is how you define an end point for the survey taker. If there's no \"Next\" button, then they cannot navigate anywhere else, so the survey is over.\n\n## Required responses\n\nBy default, no questions are required. However, you can make questions required by adding the question `id` to the `required` argument in `sd_config()`, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n required_questions = c(\"vehicle_ownership\", \"penguins_other\")\n)\n```\n:::\n\n\nThis will make the respondent unable to proceed until they have answered the required questions. It will also place a red asterisk (*) next to the question label to indicate that the question is required.\n\nYou can also make all questions required by setting `all_questions_required = TRUE` like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nconfig <- sd_config(\n all_questions_required = TRUE\n)\n```\n:::\n\n\n## Reactivity\n\nOne other feature that is particularly powerful is the ability to use R code in your survey via Shiny's reactive programming. This allows you to make your survey more interactive and to use the full power of R to create custom functionality.\n\n### Demo 1: Displaying content based on previous responses\n\nLet's say you wanted to create a survey that asked the respondent's name, and then displayed a personalized message based on their name. You could do this by first asking their name:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_question(\n type = \"text\",\n id = \"name\",\n label = \"What is your name?\"\n)\n```\n:::\n\n\nThen you can use the `sd_display_value(\"name\")` function to display the value of the `name` question in other parts of your survey. For example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nWelcome, `r sd_display_value(\"name\")`!\n```\n:::\n\n\nWhich would render as something like \"Welcome, Dave!\" (assuming the respondent entered \"Dave\" in the `name` question). This works because the `sd_display_value()` function is reactive, meaning it will update the display based on the respondent's responses.\n\n### Demo 2: Displaying randomized question labels\n\nLet's say you wanted to show a series of questions, but you wanted to randomize the labels shown for each question. You could do this by first defining a list of labels, and then using the `sd_question_reactive()` function to create a question that will display a random label from the list. \n\nFor example, let's ask the respondent to rate different car brands from a random set of brands. You could first pre-define the randomized sets of brands for each respondent and store it as a csv file, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands <- c(\"Toyota\", \"Ford\", \"Chevrolet\", \"Honda\", \"Nissan\", \"Tesla\")\ndesign <- data.frame(\n respondent_id = rep(1:10, each = 3),\n brand = unlist(lapply(1:10, function(x) sample(brands, 3, replace = FALSE)))\n)\nwrite_csv(design, \"design.csv\")\n```\n:::\n\n\nThis would make a design file that looks like this:\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n\n```\n#> respondent_id brand\n#> 1 1 Toyota\n#> 2 1 Honda\n#> 3 1 Tesla\n#> 4 2 Honda\n#> 5 2 Tesla\n#> 6 2 Toyota\n```\n\n\n:::\n:::\n\n\nNote that this would not be done in your survey.qmd file - it's just a one-time thing to create the design (probably stored in an R file).\n\nThen in your server code chunk, you could read in the design file and use it to randomize the labels for each question based on a randomly chosen respondent:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesign <- read_csv(\"design.csv\")\nresp_id <- sample(design$respondent_id, 1)\ndf_resp <- design %>% filter(respondent_id == resp_id)\n\noptions <- c(1, 2, 3)\nnames(options) <- df_resp$brand\n\nsd_question_reactive(\n type = \"mc\",\n id = \"brands\",\n label = \"Which of these brands do you like best?\",\n option = options\n)\n```\n:::\n\n\nHere the `sd_question_reactive()` function is used because the labels depend on the randomly chosen respondent. This means the labels will be different for each respondent.\n\nSince this is a reactive question, **this code must be placed inside the server code chunk**, not where you want it to appear in the survey. To define where in the survey the question should appear, you use the `sd_display_question()` function with the `id` set to the same value as the `id` in the `sd_question_reactive()` function, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_display_question(id = \"brands\")\n```\n:::\n\n\nNow the `brands` question will be displayed in the survey where you put this code chunk.\n\nNote that all question responses are automatically saved to the database, but if you wanted to store some other value (e.g. the randomly chosen `respondent_id`), you could do that with the `sd_store_value()` function, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_store_value(resp_id)\n```\n:::\n\n\n# How we built it\n\nBefore I wrap up, I just want to say that I am absolutely amazed at the time we live in. I have had this idea in mind for many years, but I'm not a web developer, and I never could come up with a way to make it happen. That was actually what motivated me to write my [previous blog post](https://jhelvy.com/blog/2023-04-06-markdown-surveys/) - it was a call for help from others!\n\nBut two things happened relatively recently that made it possible:\n\n1. The rise of Quarto\n2. The rise of LLMs\n\nAfter I switched my website over from distill to Quarto, I began to learn more and more about how powerful Quarto really is for building things on the web. Then I saw the [Quarto shiny document](https://quarto.org/docs/dashboards/interactivity/shiny-r.html) framework and I immediately thought that this just might be the missing piece I needed to make surveydown a reality. It does all the legwork of converting markdown and R code into a shiny app. \n\nOf course, implementing this idea was still really, really hard. There were many different ways to start, and I got some [excellent feedback](https://github.com/quarto-dev/quarto-cli/discussions/6975) from people in the R / Quarto dev community. Garrick Aden-Buie in particular was the first to [propose the idea of using fences](https://github.com/quarto-dev/quarto-cli/discussions/6975#discussioncomment-8828907) to denote page breaks, which was a big breakthrough early on. \n\nBut the biggest breakthrough came when I started using GPT-4 to help me brainstorm many different ideas while developing the overall platform architecture. [This conversation](https://chatgpt.com/share/57fda3b1-2e76-4ab1-b7af-e53eac597986) in particular was game changing. In it, I came to solutions for multiple complex problems, including the page navigation logic and which platform to use for the database (we originally started with using Googlesheets, but ultimately decided on Supabase because it is open-source and just far easier to use).\n\nOf course, the AI didn't do everything. Two of my students, [Pingfan Hu](https://github.com/pingfan-hu) and [Bogdan Bunea](https://github.com/Buneabt) have been instrumental in helping implement many of the features the package now has, and they too have leveraged LLMs to accelerate their problem solving. Thank you guys for all of your hard work! 🙏\n\nIt's been amazing watching this project come together over such a short period of time. The original conversations I had with GPT-4 and others in the R / Quarto community were just in March and April of this year (2024). We really didn't start developing in earnest until the summer, and really only late June / early July at that. In just a few months, we've gone from an idea to a fully functional survey platform.\n\nIf you give surveydown a try, please let us know what you think! And if you find a bug or something you wished existed, please post an issue on [github](https://github.com/surveydown-dev/surveydown/issues).\n\nI'm so excited to see what you all will build with surveydown!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blogs/2024-08-28-choice-based-conjoint-surveys-with-surveydown/index/execute-results/html.json b/_freeze/blogs/2024-08-28-choice-based-conjoint-surveys-with-surveydown/index/execute-results/html.json deleted file mode 100644 index 78d6ef67..00000000 --- a/_freeze/blogs/2024-08-28-choice-based-conjoint-surveys-with-surveydown/index/execute-results/html.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "hash": "be824c3fe67c50e31ac1d0ae4f808724", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: Choice-based conjoint surveys in R with surveydown\ndescription: |\n A how-to guide for using R to design and implement choice-based conjoint surveys using the surveydown R package\ndate: '2024-08-28'\nimage: \"images/example.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: images/example.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - tutorial\n - conjoint\nformat: html\nexecute: \n eval: false\ntoc: true\n---\n\n\n\n::: {.callout-important}\n\n**This post was updated on 2024-09-23 to match the new architecture introduced in v0.3.0.** See the [blog post](https://surveydown.org/blog/2024-09-18-new-app-design/) on the v0.3.0 release for more details.\n\n:::\n\nBecause [surveydown](https://surveydown.org/) surveys run as a shiny app, you can include custom logic in the background by writing some {{< fa brands r-project>}} code in your server. In this post, I'm going to show you one approach for using surveydown to create a particular type of complex survey: a **choice-based conjoint survey**. \n\n::: {.callout-note}\n\nIf you're unfamiliar with what a conjoint survey is, take a look at this [quick introduction](https://sawtoothsoftware.com/conjoint-analysis/cbc).\n\n:::\n\nThe key component of a choice-based conjoint survey is asking repsondents to make choices from randomized sets of choice questions. So the hard part is figuring out a way to show each respondent a different set of randomized questions. This post shows how you can achieve this in surveydown.\n\nThroughout this post, I will use a demo survey about people's preferences for apples with three attributes: `type`, `price`, and `freshness`.^[Yes, people have [actually done conjoint surveys on fruit](https://www.emerald.com/insight/content/doi/10.1108/00070709610150879/full/html) before.] \n\nYou can view the live demo survey [here](https://surveydown.shinyapps.io/conjoint/), and all files used to create the survey are on [this GitHub repo](https://github.com/surveydown-dev/demos/tree/main/conjoint-buttons).\n\n## Introduction\n\nIf you've never used surveydown before, take a look at the [Getting Started](https://surveydown.org/docs/getting-started.html) page to get a quick introduction to the package and how to use it to make a survey.\n\nThe basic concept is this:\n\n1. Design your survey as a [Quarto]({{< url_quarto >}}) document using markdown and R code.\n2. Convert your survey into a [Shiny]({{< url_shiny >}}) app that can be hosted online and sent to respondents.\n3. Store your survey responses in a [Supabase]({{< url_supabase >}}) database.\n\n## Getting started \n\nIf you want to start from a blank slate, take a look at the [Getting Started](https://surveydown.org/docs/getting-started.html) documentation page. \n\nFor this post, we recommend starting from the demo survey available at [this GitHub repo](https://github.com/surveydown-dev/demos/tree/main/conjoint-buttons). It provides an already working survey that you can modify to the needs of your conjoint survey.\n\nThe demo repo has a lot of files in it, but the main files defining the survey itself are:\n\n- `survey.qmd`: The main body of the survey. \n- `app.R`: The app file containing the server logic implemented in the survey, including randomizing questions, connecting to a database, etc.\n\n::: {.callout-note}\n\nWe recommend opening the `survey.Rproj` if you're working in RStudio to make sure RStudio opens to the correct project folder. \n\n:::\n\n## Content in the survey body\n\nAfter the setup code chunk where we load the surveydown package, we have a series of pages (defined with `:::` fences) that include markdown-formatted text and survey questions (defined with `sd_question()`). You can modify any of this content as you wish to suit the needs of your survey. \n\nIn this demo, we have a few other examples included, like a conditionally displayed question (the `fav_fruit` question will not display if you choose \"No\" on the first question about liking fruit) as well as a question that skips people to the end (if you choose \"blue\" and not \"red\" on the `screening` page). The logic controlling the conditional display and skipping is defined with the `sd_skip_if()` function in the `app.R` file.\n\nNone of this is necessary for a conjoint survey, but often times these are features that you may want to include, such as screening people out of the survey if they don't qualify to take it, so we include it for demonstration purposes.\n\n## Defining the choice questions\n\nThe central component of every conjoint survey is the set of randomized choice questions. To implement these in surveydown, we pre-define our choice questions in a design file that we later use in the survey to select randomized sets of choice questions to display each respondent.\n\nWe use the [cbcTools](https://jhelvy.github.io/cbcTools/) package to create the pre-defined design file. The code to create the choice questions for this demo survey is in the [`1_make_choice_questions.R`](https://github.com/surveydown-dev/demos/blob/main/conjoint-buttons/code/1_make_choice_questions.R) file in the demo repo. This code generates a data frame of randomized choice questions that we then save in the project directory as `choice_questions.csv`.\n\n## Implementing the choice questions\n\nThe choice questions are implemented at the top of the `server()` function in the `app.R` file in the demo repo. This code does the following steps:\n\n### 1. Read in the design file\n\nPretty straightforward - this is one line to read in the `choice_questions.csv` design file that we saved in the project folder.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesign <- readr::read_csv(\"choice_questions.csv\")\n```\n:::\n\n\n### 2. Sample and store a random respondent ID\n\nSince we want each respondent to see a different set of choice questions, we randomly sample a respondent ID from the set of all respondent IDs in the design file. We also need to keep track of this and store it in our response data so that later we can know what each respondent was actually shown. \n\nSince this is a value that we generated in the server (and not a value from a survey question to a respondent), we have to manually add it to the survey response data using `sd_store_value()`. Here we modified the name so that in the resulting survey data the column name will be `\"respID\"`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Sample a random respondentID\nrespondentID <- sample(design$respID, 1)\n\n# Store the respondentID\nsd_store_value(respondentID, \"respID\")\n```\n:::\n\n\n### 3. Filter the design for the respondentID\n\nWe create a subset dataframe called `df` that stores only the rows for the randomly chosen respondent ID. We also append the `\"images/\"` string onto the values in the `image` column as this will create the relative path to the images in our survey, e.g. `\"images/fuji.jpg\"` (all the images we show are in the `\"images\"` folder in the repo).\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Filter for the rows for the chosen respondentID\ndf <- design %>%\n filter(respID == respondentID) %>%\n mutate(image = paste0(\"images/\", image))\n```\n:::\n\n\n### 4. Define a function to create question options\n\nThis is the most complex component in the server logic. Here we created a function that takes a dataframe and returns a named vector defining the options to show in each choice question. In this case, we only have 3 options per choice question, so each time we call this function we will use a small dataframe that has just 3 rows defining the 3 choice alternatives in a single choice question. \n\nThe function does several things. First, it extracts three single-row data frames that store the values of each of the 3 alternatives (`alt1`, `alt2`, and `alt3`). It then creates an `options` vector that has just 3 values: `\"option_1\"`, `\"option_2\"`, and `\"option_3\"`. Then we have to define the names of each of those options. Remember that the _values_ in the `options` vector are what gets stored in our resulting survey data based on what the respondent chooses, but the _names_ are what respondents see. So in the context of a choice survey like this, we need to embed all of the attributes and their levels in the names of the `options` vector.\n\nWe use the `glue()` function to easily inject the values stored in `alt1`, `alt2`, and `alt3` into our labels. The `glue()` function is similar to `paste()` in that is just concatenates object values into a string, but it has an easier syntax to work with. Anything inside `{}` brackets is evaluated, and the resulting value is inserted into the string. So for example, the line `glue(\"1 plus 1 equals {1+1}\")` would produce the string `\"1 plus 1 equals 2\"`.\n\nIn our case, we're including some html code to insert an image of the apple type (``), the apply type itself (`**Type**: {alt1$type}`), and the apple price (`**Price**: $ {alt1$price} / lb`). \n\nNotice also that we're mixing markdown (e.g. `**Option 1**`) and html (e.g. `
`), which will all get rendered into proper html in the resulting shiny app. The full function looks like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Function to create the labels for a choice question\n# based on the values in df\n\nmake_cbc_options <- function(df) {\n alt1 <- df |> filter(altID == 1)\n alt2 <- df |> filter(altID == 2)\n alt3 <- df |> filter(altID == 3)\n\n options <- c(\"option_1\", \"option_2\", \"option_3\")\n\n names(options) <- c(\n glue(\"\n **Option 1**
\n
\n **Type**: {alt1$type}
\n **Price**: $ {alt1$price} / lb\n \"),\n glue(\"\n **Option 2**
\n
\n **Type**: {alt2$type}
\n **Price**: $ {alt2$price} / lb\n \"),\n glue(\"\n **Option 3**
\n
\n **Type**: {alt3$type}
\n **Price**: $ {alt3$price} / lb\n \")\n )\n return(options)\n}\n```\n:::\n\n\n### 5. Create the options for each choice question\n\nOne of the benefits of making the function the way we did in the previous step is that we can now easily call it to generate the option vector for each of the 6 choice questions in `df`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Create the options for each choice question\n\ncbc1_options <- make_cbc_options(df |> filter(qID == 1))\ncbc2_options <- make_cbc_options(df |> filter(qID == 2))\ncbc3_options <- make_cbc_options(df |> filter(qID == 3))\ncbc4_options <- make_cbc_options(df |> filter(qID == 4))\ncbc5_options <- make_cbc_options(df |> filter(qID == 5))\ncbc6_options <- make_cbc_options(df |> filter(qID == 6))\n```\n:::\n\n\n### 6. Create each choice question (6 in total)\n\nFinally, we now have everything we need to generate each choice question. Here we're using the `mc_buttons` question type so that the labels we generated will be displayed on a large button, which looks good both on a computer and phone. We give the question a unique `id` (e.g. `cbc_q1`), and a label, and then set the `option` to the corresponding option vector we defined above. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_question(\n type = 'mc_buttons',\n id = 'cbc_q1',\n label = \"(1 of 6) If these were your only options, which would you choose?\",\n option = cbc1_options\n)\n\n# ...and 5 more questions like this\n```\n:::\n\n\nRemember that since the labels in the options are being _dynamically_ generated on each new session (each respondent), they have to be created in the server, not in the main survey body. As a result, the `sd_question()` function must also be created in the server code (if you put this code in the main body, only one random set of choice options will be generated, and they'll be the same for everyone). \n\nTo display each question in the survey body, we use `sd_output(\"id\", type = \"question\")`, changing `id` to each corresponding choice question we created. In the demo `survey.qmd` file, you'll see that there are 6 choice questions displayed in the main survey body (each on their own page), and each of those 6 questions are defined in the `server()` function in the `app.R` file. \n\nWhen rendered, a choice question will look like this, with the values matching whatever alternative was chosen in the design file:\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
\n
\n\n
\n
\n
\n
\n\n
\n
\n\n
\n
\n\n
\n
\n
\n\n
\n*\n
\n```\n\n:::\n:::\n\n\n
\n\nAnd that's it! You now have 6 randomized choice questions!\n\n\n\n\n## Preview and check\n\nThe rest of the `server()` function in the `app.R` file has the remaining components we need, like any conditional display or skip logic. This is all standard features of any surveydown survey, so we won't cover them in detail here and instead direct you to the [documentation](https://surveydown.org/docs.html) for details.\n\nBut before you go live, it's a good idea to do some quick testing. You can test your survey even without having it connected to a database by setting `ignore = TRUE` in the `sd_database()` function. Of course, you probably should also test it after connecting it to a database to ensure that responses are being properly stored. \n\nWhen testing, you might get an error - don't panic! Read the terminal output carefully and debug. There's a good chance you may have missed a bug somewhere in your server code. Look in your `app.R` file to see if you can spot the error.\n\n## Getting the data\n\nOnce your survey is live and you start collecting responses, you can easily access your data with the `sd_get_data()` function. This is typically done in a separate R file, which might look something like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(surveydown)\n\ndb <- sd_database(\n user = 'postgres.axzkymswaxcasjdflkurrj',\n host = 'aws-0-us-east-1.pooler.supabase.com',\n port = 5678,\n db_name = 'postgres',\n table_name = 'my_table'\n)\n\ndata <- sd_get_data(db)\n```\n:::\n\n\nObviously your settings in `sd_database()` would need to match those of your Supabase database that you created for your survey.\n\nAnd that's it! We hope this post was helpful, and do go check out the [this GitHub repo](https://github.com/surveydown-dev/demos/blob/main/conjoint-buttons) to try out the demo yourself.\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": { - "include-in-header": [ - "\n\n\n" - ] - }, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blogs/2024-09-18-new-app-design/index/execute-results/html.json b/_freeze/blogs/2024-09-18-new-app-design/index/execute-results/html.json deleted file mode 100644 index 7d553464..00000000 --- a/_freeze/blogs/2024-09-18-new-app-design/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "3a9befcbf94cc9eaa75e53019174015e", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: New architecture in v0.3.0 (and loads of breaking changes)!\ndescription: |\n We're releasing v0.3.0, and with it multiple breaking changes.\ndate: '2024-09-18'\nimage: \"image.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: image.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\nformat: html\nexecute: \n eval: false\ntoc: true\nlightbox: true\n---\n\n\n\nThe surveydown package is only a couple months old, and thanks to many early users we learned about some design flaws that needed to be fixed. As a result, with the release of {surveydown} 0.3.0, the package has undergone a major overhaul to improve security, modularity, and extensibility. \n\nWe introduced several major breaking changes in this release, so we felt we should make a blog post to both explain why we felt these changes were needed as well as introduce the new architecture we have adopted.\n\n## Motivation\n\nIn the original conception of surveydown, the entire survey was defined in a single Quarto shiny document that would render into a shiny app. While this was a rather parsimonious design, it also had some flaws that weren't immediately apparent.\n\nPerhaps the largest issue was **page security** (see [this issue](https://github.com/surveydown-dev/surveydown/issues/93)). With Quarto shiny documents, the qmd file is first rendered into a static html page that is then used to define the elements of the user interface in the resulting shiny app. This meant that for us to introduce _pages_, we relied on a combination of JS and CSS to hide and show the page divs based on users clicking on next buttons. While this resulted in a nice user experience, under the hood the entire app was still just one big html page with all of the content available. \n\nThis design meant that anyone could still see the source code to any component of the survey they wanted. A user could simply right-click and open \"Inspect\" then manually change the CSS of a page div from `style=\"display: none;\"` to `style=\"display: show;\"` and boom - the \"page\" would appear!\n\nThis was obviously a major security issue as pages with things like completion codes or redirect buttons at the end could be easily shown without going through the whole survey. The only solution was an architectural overhaul that would only show the content on one page at a time.\n\n## New architecture\n\nThe new architecture employs a two-file design composed of a _survey_ and an _app_ that renders to a traditional Shiny app:\n\n- `survey.qmd`: A Quarto document that contains the survey content (pages, questions, etc), which renders to an HTML file.\n- `app.R`: An R script defining a Shiny app that contains the global settings (libraries, database configuration, etc.) and server configuration options (e.g., conditional skipping / display, etc.).\n\n**These files must be named `survey.qmd` and `app.R`**.\n\nThey typically look something like this:\n\n::: {.panel-tabset}\n\n## `survey.qmd` file\n\n````markdown\n---\nformat: html\necho: false\nwarning: false\n---\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(surveydown)\n```\n:::\n\n\n::: {#welcome .sd-page}\n\n# Welcome to our survey!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsd_question(\n type = 'mc',\n id = 'penguins',\n label = \"Which type of penguin do you like the best?\",\n option = c(\n 'Adélie' = 'adelie',\n 'Chinstrap' = 'chinstrap',\n 'Gentoo' = 'gentoo'\n )\n)\n\nsd_next(next_page = 'end')\n```\n:::\n\n\n:::\n\n::: {#end .sd-page}\n\nThis it the last page in the survey\n\n:::\n\n````\n\n## `app.R` file\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(surveydown)\n\ndb <- sd_database(\n host = \"\",\n dbname = \"\",\n port = \"\",\n user = \"\",\n table = \"\",\n ignore = TRUE\n)\n\nserver <- function(input, output, session) {\n\n # Define conditional skip logic here (skip to page if a condition is true)\n sd_skip_if()\n\n # Define conditional display logic here (show a question if a condition is true)\n sd_show_if()\n\n # Main server to control the app\n sd_server()\n}\n\nshiny::shinyApp(ui = sd_ui(), server = server)\n```\n:::\n\n\n:::\n\nThis approach allows us to separate the _survey content_ (in the `survey.qmd` file) from the _survey logic_ (in the `app.R` file), which comes with a few benefits:\n\n1. **Security**: Since the rendered survey content is no longer directly embedded in the app as a single html page, it makes it much harder for users to tamper with the content. Only the content on one page at a time will be rendered by the server.\n2. **Clarity**: With two files, it is now clearer where the survey content versus control logic should be defined. Before, all of the server logic was in a single server code chunk at the end of the `survey.qmd` file, which required the user to scroll up and down to edit the server logic versus the survey content. Now a user can have both files open in two tabs in an IDE and more easily edit the survey content and server content.\n3. **Simplicity**: The new design eliminates the need for a Quarto extension to render the survey. This allows us to ship all of the core functionality of surveydown as a single R package, which is installed globally on your system.\n\nThe updated documentation of the [Survey Components](https://surveydown.org/manuals/survey-components) page reflects this new design.\n\n## New page architecture\n\nThe motivation to secure the page content led to a totally new approach to designing the survey pages. Our new approach actually renders the `survey.qmd` into a static html page and then parses it into a list of page objects. Each page object is itself a list of elements, including the page ID, question IDs, etc., as well as the rendered html content for that page. \n\nThe `sd_server()` function then uses this list of page objects to display one page at a time via a `shiny::renderUI()` function into a single \"main\" output. This approach allowed us to control what content is being served, eliminating the ability of survey respondents to see anything other than the content on the current page.\n\nThis approach also gave us the opportunity to overhaul how pagination works in general. Previously, users had to add a `sd_next(next_page = \"page_id\")` button at the end of each page, making sure to specify the next page to go to. This was a bit annoying as most of the time you just want to go to the next page, so specifying it felt redundant. Now users can simply add `sd_next()` at the bottom of each page and the server will go to the next page by default. If you want to direct the respondent to a different page, then you specify the target page using `sd_next(next_page = \"page_id\")`.\n\n## Improved conditional show and skip logic\n\nConditionally displaying questions or skipping to pages is a core logic that many surveys need. Our original approach was relatively clunky, so since we were already introducing many breaking designs, we figured we should overhaul the logic for conditional skipping and displaying.\n\nThe new approach uses just two functions: `sd_skip_if()` and `sd_show_if()`. These functions can be provided in the main `server()` function in the `app.R` file to define the conditions and targets for either conditional displaying a question or conditionally skipping to a page. The structure for each condition in these new functions is always as follows:\n\n> ` ~ \"target_question_id\"`\n\nAs an example, let's say we want to show a question called `\"penguins_other\"` if the respondent chose the `\"other\"` option in a question called `\"penguins\"`. We could do this with the following code in the `app.R` file:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nserver <- function(input, output, session) {\n\n sd_show_if(\n input$penguins == \"other\" ~ \"penguins_other\"\n )\n\n sd_server(db = db)\n\n}\n```\n:::\n\n\nYou can provide multiple conditions to the `sd_show_if()` function, each separated by a comma. The `sd_skip_if()` function works the same way, but it will skip to a target page instead of showing a target question. See the revised [Conditional Control](https://surveydown.org/manuals/conditional-control) page for more details on the new changes.\n\n## No more `sd_config()` function\n\nOne more small change we made is that the `sd_config()` function is no longer needed. Since we moved the conditional skip and show logic into their own functions, we took the remaining arguments that used to be provided to `sd_config()` and added them to the `sd_server()` function as options. You can now simply pass these arguments to the `sd_server()` function in the `app.R` file.\n\n## Our apologies\n\nThat's about it for the changes with v0.3.0. We want to send our deepest apologies for anyone who has already begun a study using the orginal design. The most recent version prior to v0.3.0 was v0.2.4, so this is the version you should install if you want to stick with the old design. \n\nThat said, all development will now continue on this new design, so we strongly recommend updating to the new version and converting any existing surveys to the new design. The biggest change you'll need to make is to move your server logic out of the `survey.qmd` file and into the `app.R` file. We've also updated all our [demos](https://surveydown.org/demos) to the new design, so you can refer to these for examples on how to convert your existing surveys.\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_freeze/blogs/2024-12-20-surveydown-on-cran/index/execute-results/html.json b/_freeze/blogs/2024-12-20-surveydown-on-cran/index/execute-results/html.json deleted file mode 100644 index 700072ca..00000000 --- a/_freeze/blogs/2024-12-20-surveydown-on-cran/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "d80a46f4c759d9bb14867f4cd382455b", - "result": { - "engine": "knitr", - "markdown": "---\ntitle: surveydown is on CRAN 🎉!\ndescription: |\n It's actually been on CRAN since v0.4.0, but we've been making so many updates that we're now already on v0.7.2!\ndate: '2024-12-20'\nimage: \"image.png\"\ntwitter-card:\n creator: \"@johnhelveston\"\n site: \"@johnhelveston\"\n image: image.png\n card-style: summary_large_image\n image-width: 150\n image-height: 150\ncategories:\n - R\n - CRAN\nformat: html\nexecute: \n eval: false\ntoc: true\nlightbox: true\n---\n\nWe have some exciting news to share - surveydown is on CRAN 🎉!\n\nYou can now install surveydown from CRAN using the usual `install.packages()` function:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"surveydown\")\n```\n:::\n\n\nActually, surveydown has been on CRAN since v0.4.0, but we've been making so many updates that we're now already on v0.7.2 before we could even make a post about being on CRAN!\n\nSo it seems about time we highlight some of the improvements and new features we've added recently.\n\n## New functions / features\n\nWe added a whole bunch of new functions and features since v0.3.0, which was a major architectural change to the package. But the changes we made in v0.3.0 made it lot easier to support more features. There have been a lot, so I'm probably missing some, but here are some quick highlights that are worth calling out:\n\n- In `sd_question()`, we added a new `type = \"matrix\"` to support matrix type questions.\n- We added a new `sd_is_answered()` function to check if a question is answered or not. This is useful for conditional reactive questions defined in the server.\n- We added a new `sd_completion_code()` function that generates a random completion code for your survey.\n- We added a new `sd_close()` function to make a button that closes the survey.\n- [Stefan Munnes](https://github.com/StefanMunnes) [added](https://github.com/surveydown-dev/surveydown/pull/138) a translation feature that allows you to customize the system messages in the app to any language you want.\n- We modified how data handling is done so that each respondent's state in the survey can be restored via cookies if they refresh the browser window or close and re-open the window. Before, respondents would be sent back to the start of the survey and a new entry in the database would be created, so this features allows respondents to pause and come back to the survey without that happening.\n- We added two new helper functions, `sd_add_question()` and `sd_add_page()`, that make it easier to quickly create template questions and pages. They work like this:\n\n
\n\n
\n\n## Performance improvements \n\nOne major improvement we've made is to streamline how the `survey.qmd` file is updating. Before you needed to render the `survey.qmd` file yourself before running the `app.R` file, but now you actually never need to render it. All rendering is handled when you run the shiny app via the `app.R` file. \n\nAnd this rendering is \"smart\" too - it renders the `survey.qmd` file into an html file, then it parses that file for all of the content needed for the survey and stores each piece in a `_survey` folder. This folder is then what the shiny app uses to load the survey. If no changes are detected in the `survey.qmd` file or any of the components in the `_survey` folder, then the app will always load content from the pre-rendered content in the `_survey` folder, which will load nearly instantaneously! Only if you make a change to your survey will it be re-rendered, and again once that rendering is done once it won't run again until another change is detected. This results in a much more efficient app that loads nearly instantaneously.\n\nWe also improved the database updating to be much more efficient. It used to run a write operation to the database on every single question ineraction, but that slowed down the app significantly. Now that we have cookies enabled, we do the immediate record keeping in the cookies and only write to the database when the respondent closes the survey or turns the page. This is a more reasonable compromise that makes the app feel snappy while still ensuring that the data is saved even if the respondent accidentally closes the window.\n\n## Our first community contributions!\n\nOver the past few months, we've been getting a lot of contributions from the community! We've been able to merge a few pull requests that add some new features and fix some bugs. Here are some of the highlights:\n\n- [Stefan Munnes](https://github.com/StefanMunnes) added a new translation feature that allows you to customize the system messages in the app to any language you want. He also helped us update `sd_output()` to be able to output the chosen question values, chosen question option label(s), and the question label itself. This addresses [feature request #128](https://github.com/orgs/surveydown-dev/discussions/128).\n- [Zain Hoda](https://github.com/zainhoda1) contributed a new `auto_scroll` parameter to the `sd_server()` function that allows you to turn off auto scrolling. This is useful if you have a lot of questions and want to make sure the respondent can see the entire question.\n\nWe're excited to see what the community will build with surveydown! If you build something with surveydown, please let us know on the [GitHub Discussions](https://github.com/orgs/surveydown-dev/discussions) so we can highlight your work!\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_quarto.yml b/_quarto.yml index 36a76ebb..e763e8f1 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -67,6 +67,10 @@ website: href: docs/getting-started.qmd - text: "Basic Components" href: docs/basic-components.qmd + - text: "Page Navigation" + href: docs/page-navigation.qmd + - text: "Survey Settings" + href: docs/survey-settings.qmd - section: "Question Development" contents: @@ -78,22 +82,11 @@ website: href: docs/question-formatting.qmd - text: "Custom Questions" href: docs/custom-questions.qmd - - - section: "Survey Design Concepts" - contents: - - text: "Survey Settings" - href: docs/survey-settings.qmd - - text: "Page Navigation" - href: docs/page-navigation.qmd - - text: "Conditional Logic" - href: docs/conditional-logic.qmd - - text: "System Translations" - href: docs/system-translations.qmd - - text: "External Resources" - href: docs/external-resources.qmd - section: "Interactivity" contents: + - text: "Conditional Logic" + href: docs/conditional-logic.qmd - text: "Reactivity" href: docs/reactivity.qmd - text: "Randomization" @@ -107,8 +100,6 @@ website: href: docs/storing-data.qmd - text: "Fetching Data" href: docs/fetching-data.qmd - - text: "Local Dashboard" - href: docs/local-dashboard.qmd - section: "Deployment & Operations" contents: @@ -121,12 +112,14 @@ website: - section: "Other" contents: + - text: "sdstudio package" + href: docs/sdstudio.qmd - text: "Architecture" href: docs/architecture.qmd + - text: "External Resources" + href: docs/external-resources.qmd - text: "Tips" href: docs/tips.qmd - - text: "Troubleshooting" - href: docs/troubleshooting.qmd - title: "Templates" style: "docked" diff --git a/about.qmd b/about.qmd index 2d33806a..163388a2 100644 --- a/about.qmd +++ b/about.qmd @@ -7,10 +7,16 @@ execute: ## Overview -::: {style="width:100%; text-align:center;"} -{{< video https://www.youtube.com/watch?v=VwoeFKNvN5k >}} ***Intro to surveydown by Dr Helveston*** +
+ +::: {style="width:80%; text-align:center;"} + +{{< video https://www.youtube.com/watch?v=VwoeFKNvN5k >}} ***Intro to surveydown by JP Helveston*** + ::: +
+ ## What is surveydown?
diff --git a/blog/2024-09-18-new-app-design/index.qmd b/blog/2024-09-18-new-app-design/index.qmd index e1a53f6c..dc309efb 100644 --- a/blog/2024-09-18-new-app-design/index.qmd +++ b/blog/2024-09-18-new-app-design/index.qmd @@ -134,7 +134,7 @@ This approach allows us to separate the _survey content_ (in the `survey.qmd` fi 2. **Clarity**: With two files, it is now clearer where the survey content versus control logic should be defined. Before, all of the server logic was in a single server code chunk at the end of the `survey.qmd` file, which required the user to scroll up and down to edit the server logic versus the survey content. Now a user can have both files open in two tabs in an IDE and more easily edit the survey content and server content. 3. **Simplicity**: The new design eliminates the need for a Quarto extension to render the survey. This allows us to ship all of the core functionality of surveydown as a single R package, which is installed globally on your system. -The updated documentation of the [Survey Components](https://surveydown.org/manuals/survey-components) page reflects this new design. +The updated documentation of the [Basic Components](https://surveydown.org/docs/basic-components) page reflects this new design. ## New page architecture @@ -166,7 +166,7 @@ server <- function(input, output, session) { } ``` -You can provide multiple conditions to the `sd_show_if()` function, each separated by a comma. The `sd_skip_if()` function works the same way, but it will skip to a target page instead of showing a target question. See the revised [Conditional Control](https://surveydown.org/manuals/conditional-control) page for more details on the new changes. +You can provide multiple conditions to the `sd_show_if()` function, each separated by a comma. The `sd_skip_if()` function works the same way, but it will skip to a target page instead of showing a target question. There is also an `sd_stop_if()` to stop the navigation under centain conditions. See the revised [Conditional Logic](https://surveydown.org/docs/conditional-logic) page for more details on the new changes. ## No more `sd_config()` function @@ -176,4 +176,4 @@ One more small change we made is that the `sd_config()` function is no longer ne That's about it for the changes with v0.3.0. We want to send our deepest apologies for anyone who has already begun a study using the orginal design. The most recent version prior to v0.3.0 was v0.2.4, so this is the version you should install if you want to stick with the old design. -That said, all development will now continue on this new design, so we strongly recommend updating to the new version and converting any existing surveys to the new design. The biggest change you'll need to make is to move your server logic out of the `survey.qmd` file and into the `app.R` file. We've also updated all our [demos](https://surveydown.org/demos) to the new design, so you can refer to these for examples on how to convert your existing surveys. +That said, all development will now continue on this new design, so we strongly recommend updating to the new version and converting any existing surveys to the new design. The biggest change you'll need to make is to move your server logic out of the `survey.qmd` file and into the `app.R` file. We've also updated all our [templates](https://surveydown.org/templates) to the new design, so you can refer to these for examples on how to convert your existing surveys. diff --git a/blog/2025-06-29-sdstudio/index.qmd b/blog/2025-06-29-sdstudio/index.qmd index 7e615328..f2e397f6 100644 --- a/blog/2025-06-29-sdstudio/index.qmd +++ b/blog/2025-06-29-sdstudio/index.qmd @@ -74,7 +74,7 @@ The "Build" tab is your workspace of survey construction. You can either start f ::: {.callout-note} -We currently have 15 templates to choose from. The ***default*** template contains a minimum basic structure for a clean start. You might also try the ***question_type*** template for a showcase of all question types, or try some advanced features with ***conditional_display***, ***conditional_navigation***, etc. For a full list of templates, see the [templates repo](https://github.com/surveydown-dev/templates) on GitHub. +We currently have 15 templates to choose from. The ***default*** template contains a minimum basic structure for a clean start. You might also try the ***question_type*** template for a showcase of all question types, or try some advanced features with ***conditional_display***, ***conditional_navigation***, etc. Our [surveydown-dev GitHub organization](https://github.com/surveydown-dev) has template repositories starting with `template_`. Feel free to search for your desired templates, clone or download the source code and try them out! ::: diff --git a/blog/2025-11-15-shorthand-page-syntax/index.qmd b/blog/2025-11-15-shorthand-page-syntax/index.qmd index 3b322ae4..775bb8e5 100644 --- a/blog/2025-11-15-shorthand-page-syntax/index.qmd +++ b/blog/2025-11-15-shorthand-page-syntax/index.qmd @@ -4,18 +4,13 @@ author: - name: John Paul Helveston description: | Introducing a new shorthand syntax for page definition to make your survey code cleaner and easier to read. -date: '2025-11-01' +date: '2025-11-15' image: "banner.png" categories: - R - package - syntax - feature -format: html -execute: - eval: false -toc: true -lightbox: true --- We are excited to announce a significant improvement to the surveydown developer experience in package version 0.14.0: **shorthand page syntax**. This new syntax makes defining pages in your surveys dramatically cleaner and easier to read, while maintaining full backward compatibility with the existing fence syntax. diff --git a/blog/2025-11-21-surveydown-version-1-released/banner.png b/blog/2025-11-21-surveydown-version-1-released/banner.png new file mode 100644 index 00000000..a1c6ca76 Binary files /dev/null and b/blog/2025-11-21-surveydown-version-1-released/banner.png differ diff --git a/blog/2025-11-21-surveydown-version-1-released/images/previous_off.png b/blog/2025-11-21-surveydown-version-1-released/images/previous_off.png new file mode 100644 index 00000000..ed1aac65 Binary files /dev/null and b/blog/2025-11-21-surveydown-version-1-released/images/previous_off.png differ diff --git a/blog/2025-11-21-surveydown-version-1-released/images/previous_on.png b/blog/2025-11-21-surveydown-version-1-released/images/previous_on.png new file mode 100644 index 00000000..88f6a989 Binary files /dev/null and b/blog/2025-11-21-surveydown-version-1-released/images/previous_on.png differ diff --git a/blog/2025-11-21-surveydown-version-1-released/index.qmd b/blog/2025-11-21-surveydown-version-1-released/index.qmd new file mode 100644 index 00000000..c585e7bf --- /dev/null +++ b/blog/2025-11-21-surveydown-version-1-released/index.qmd @@ -0,0 +1,254 @@ +--- +title: "Announcing surveydown v1.0.0!" +author: + - name: John Paul Helveston + - name: Pingfan Hu +description: | + We are thrilled to announce the `v1.0.0` release of `surveydown`, a stable release with improvements to YAML settings, page navigation, question creation, and a smoother overall developer experience. +date: '2025-11-21' +image: "banner.png" +categories: + - package + - feature +--- + +## Installation + +Verion 1.0.0 is coming to CRAN soon, but in the meantime you can upgrade to it by installing it from GitHub: + +```{r} +# install.packages("pak") +pak::pak("surveydown-dev/surveydown") +``` + +Now, let's check out the new features! + +## YAML settings + +A YAML header is used in every `.qmd` file as metadata to define page behavior. The `survey.qmd` file in each `surveydown` survey also relies on the YAML header, but we've made some significant changes to improve the experience. + +**YAML omission:** First, you no longer need to include any YAML header at all now as `surveydown` now auto-loads the following: + +```yaml +--- +format: html +echo: false +warning: false +--- +``` + +This used to be the standard YAML header in `survey.qmd`, but now if you leave it out, these settings will be used by default. + +**New YAML experience:** An empty YAML is fine if you want to use all default settings, so your `survey.qmd` file can start directly with your library calls and survey content: + +````{r} +```{r} +library(surveydown) +library(other_packages) +``` + +Survey content... +```` + +**YAML settings:** We have also expanded the YAML header to include all other survey settings to customize your survey behavior. Below is a full list of all possible YAML settings with their default values, including **theme**, **survey**, and **system message** settings. The comprehensive list of settings used in any one survey are generated in your `_survey/settings.yml` file of any `surveydown` survey project. + +```yaml +--- +theme-settings: + theme: default + barposition: top + footer-left: '' + footer-center: '' + footer-right: '' + +survey-settings: + show-previous: no + use-cookies: no + auto-scroll: no + rate-survey: no + all-questions-required: no + start-page: your_first_page + system-language: en + highlight-unanswered: yes + highlight-color: gray + capture-metadata: yes + required-questions: [] + +system-messages: + cancel: Cancel + confirm-exit: Confirm Exit + sure-exit: Are you sure you want to exit the survey? + submit-exit: Submit and Exit + warning: Warning + required: Please answer all required questions before proceeding. + rating-title: Before you go... + rating-text: 'Rate your survey experience:' + rating-scale: from 1-poor to 5-excellent + previous: Previous + next: Next + exit: Exit Survey + close-tab: Please close this tab manually to exit the survey. + choose-option: Choose an option... + click: Click here + redirect: Redirecting in + seconds: seconds + new-tab: Opens in a new tab + redirect-error: "Error: This text won't trigger any redirection..." +--- +``` + +You can customize any of them in `survey.qmd` to fit your survey needs. For example, say you want to **enable cookies**, you can have these YAML settings in your `survey.qmd` file (cookies are on by default): + +```yaml +--- +survey-settings: + use-cookies: yes +--- +``` + +Note also the `system-messages` category. Here you can customize the text of all system messages used in your survey. For example, to change the "Next" button label to "Continue", you can do this: + +```yaml +--- +system-messages: + next: Continue +--- +``` + +This used to be handled in a separate `translations.yml` file, but now they're all consolidated into the YAML header for easier access. + +## Previous Button Support + +One of the most requested features is finally here: **previous buttons**! Survey respondents can now navigate backwards through your survey, and you have full control over whether to enable this feature globally or on a per-page basis. + +**Global setting:** By default, previous buttons are **not** enabled, but you can enable them globally by changing the YAML header setting in the `survey.qmd` file: + +```yaml +--- +survey-settings: + show-previous: true +--- +``` + +**Page-Level override:** You can override the global setting on specific pages using the newly introduced `sd_nav()` function in your `survey.qmd` file: + +```{r} +# Enable previous button on a page +sd_nav(show_previous = TRUE) + +# Disable previous button on a page +sd_nav(show_previous = FALSE) +``` + +Here are examples of previous button being enabled and disabled: + +
+ +

Previous button disabled

+
+
+ +
+ +

Previous button enabled

+
+ +## Auto Page Navigation + +One of our favorite new additions is auto-injecting page navigation. In previous versions of `surveydown`, you needed to add `sd_next()` on each page to insert next buttons. But now navigation buttons are auto injected at the end of each page unless you call `sd_nav()` or `sd_close()` on that page, which will override the global settings. + +This means survey page definition can be as simple as: + +```{r} +--- page_1 + +Page 1 content... + +--- page_2 + +Page 2 content... + +--- page_3 + +Page 3 content... +``` + +No navigation code needed - `surveydown` handles it for you! + +::: {.callout-tip} + +The auto nav injection is equivalent to having `sd_nav()` at the end of a page with a succeeding page. If you manually call `sd_nav()` or `sd_close()`, the auto injection will be overridden. + +::: + +**Manual navigation control**: Use `sd_nav()` or `sd_close()` to customize navigation. + +The new `sd_nav()` function replaces the old `sd_next()`, providing a unified interface for both previous and next buttons. + +In most cases, calling `sd_nav()` with empty parameters will suffice, as it defaults to showing the navigation buttons based on your global settings. + +`sd_nav()` has the following arguments: + +- `page_next`: Which page to go to next? (defaults to the next page in order) +- `label_previous`: Override previous button label (defaults to the system message "Previous") +- `label_next`: Override next button label (defaults to the system message "Next") +- `show_previous`: Show previous button? (defaults to the global setting) +- `show_next`: Show next button? (defaults to the global setting) + +Likewise, `sd_close()` supports both previous and next buttons with the following arguments: + +- `label_close`: Label on the close page buttons (defaults to the system message "Close") +- `label_previous`: Override previous button label (defaults to the system message "Previous") +- `show_previous`: Show previous button? (defaults to the global setting) + +## `sd_question()` Enhancements + +Since `surveydown` is built on top of shiny, we originally used the singular word `option` to define multiple-choice options in `sd_question()` (this is what shiny uses). But this can be confusing, since most of the time you want to provide multiple `options` (plural). + +To address this, now **both `option` and `options` are supported** for `sd_question()` when using multiple-choice type questions, such as `mc`, `mc_buttons`, etc. + +**Auto label to value conversion**: When defining `option` or `options`, the traditional way to define options is the pattern `label = value`, like this: + +```{r} +sd_question( + id = "favorite_color", + type = "mc", + label = "What's your favorite color?", + options = c( + "Light Blue" = "light_blue", + "Dark Green" = "dark_green", + "Bright Red" = "bright_red" + ) +) +``` + +But in case you forget this pattern, you can now just provide a single vector of labels like this: + +```{r} +sd_question( + id = "favorite_color", + type = "mc", + label = "What's your favorite color?", + options = c( + "Light Blue", + "Dark Green", + "Bright Red" + ) +) +``` + +In this case, the value stored in the data will be automatically converted to snake case. In this example, the above question will store the following values in the database: + +| Display Label | Database Value | +|--------------|----------------| +| Light Blue | `light_blue` | +| Dark Green | `dark_green` | +| Bright Red | `bright_red` | + +## What's Next? + +This `v1.0.0` release marks a major milestone for `surveydown`, but we're not stopping here. We have exciting features planned for future releases. Stay tuned! + +As always, we welcome your feedback and contributions. If you encounter any issues or have suggestions, please [open an issue for the package](https://github.com/surveydown-dev/surveydown/issues), [create a discussion in our organization](https://github.com/orgs/surveydown-dev/discussions), or contribute directly via pull requests. + +Happy surveying! diff --git a/blog/_metadata.yml b/blog/_metadata.yml index ca407058..573bf7e4 100644 --- a/blog/_metadata.yml +++ b/blog/_metadata.yml @@ -1,9 +1,5 @@ # options specified here will apply to all posts in this folder -# freeze computational output -# (see https://quarto.org/docs/projects/code-execution.html#freeze) -freeze: true - # Date format date-format: "YYYY-MM-DD" diff --git a/chunks/files.qmd b/chunks/files.qmd index 9dba616c..bbeefc81 100644 --- a/chunks/files.qmd +++ b/chunks/files.qmd @@ -1,6 +1,6 @@ Every surveydown survey is composed of a _survey_ and an _app_, defined in two separate files: -- **survey.qmd**: A Quarto document that contains the survey content (pages, questions, etc), which renders to an HTML file. -- **app.R**: An R script defining a Shiny app that contains the global settings (libraries, database configuration, etc.) and server configuration options (e.g., conditional page skipping / question display, etc.). +- **survey.qmd**: A {{< var quarto >}} document that contains the main survey content, such as pages, questions, and navigation buttons. It is a standard Quarto document, so you can use any text editor or IDE to insert content. +- **app.R**: An R script defining a Shiny app that contains the global settings (libraries, database configuration, etc.) and any server configurations (e.g., conditional page skipping / question display, etc.). -These files must be named **survey.qmd** and **app.R**. +These files must be named **survey.qmd** and **app.R**. \ No newline at end of file diff --git a/chunks/server.qmd b/chunks/server.qmd index 06f9aa55..43ff8784 100644 --- a/chunks/server.qmd +++ b/chunks/server.qmd @@ -1,12 +1,14 @@ -If you create a new survey using a [template](getting-started.html#start-with-a-template), the `server()` function looks like this: +If you create a new survey using a [template](getting-started.html#start-with-a-template), the `server()` function might look something like this: ```{r} server <- function(input, output, session) { - # Define conditional skip logic (skip forward to page if a condition is TRUE) - sd_skip_forward() + # Define conditional skip logic + # (skip forward to page if a condition is TRUE) + sd_skip_if() - # Define conditional display logic (show a question if a condition is TRUE) + # Define conditional display logic + # (show a question if a condition is TRUE) sd_show_if() # Main server to control the app diff --git a/chunks/skip-if.qmd b/chunks/skip-if.qmd index ac26d2fb..8cd67bce 100644 --- a/chunks/skip-if.qmd +++ b/chunks/skip-if.qmd @@ -1,4 +1,4 @@ -While basic page navigation is handled using `sd_next()`, you can override this static navigation in your server function with the `sd_skip_if()` function to send the respondent to a forward page based on some condition. +While basic page navigation is handled using `sd_nav()`, you can override this static navigation in your server function with the `sd_skip_if()` function to send the respondent to a forward page based on some condition. A common example is the need to **screen out** people based on their response(s) to a question. Let's say you need to screen out people who do not own a vehicle. To do this, you would first define a question in your **survey.qmd** file about their vehicle ownership, e.g.: diff --git a/docs/basic-components.qmd b/docs/basic-components.qmd index 2afe0902..f7664df3 100644 --- a/docs/basic-components.qmd +++ b/docs/basic-components.qmd @@ -11,10 +11,7 @@ library(surveydown) ## Overview -Every surveydown survey is composed of a _survey_ and an _app_, defined in two separate files: - -- **survey.qmd**: This file is where you define all of the main survey content, such as pages, questions, and navigation buttons. It is a standard Quarto document, so you can use any text editor or IDE to insert text, images, etc. just like you would in any Quarto document. See the [survey.qmd](#survey.qmd-1) section for details. -- **app.R**: This file is where you define the global settings (libraries, database configuration, etc.) and server logic (e.g., conditional page skipping / question display, etc.) for your survey. It defines a Shiny app, so if you are familiar with Shiny you should feel right at home. See the [app.R](#app.r-1) section for details. +{{< include ../chunks/files.qmd >}} ::: {.callout-note} @@ -31,8 +28,6 @@ Here is an example set of files for a basic survey: ````{r} --- format: html -echo: false -warning: false --- ```{r} @@ -54,8 +49,6 @@ sd_question( 'Gentoo' = 'gentoo' ) ) - -sd_next() ``` --- end @@ -95,19 +88,15 @@ The **survey.qmd** file is where you will define all of the main survey content, ### YAML header -The YAML header is at the top of the **survey.qmd** file. It contains some basic settings, like this: +The YAML header is at the top of the **survey.qmd** file. It is where you specify all global settings for your survey. If you do not specify a YAML header, all default settings will be used. The simplest YAML header just specifies the output format as HTML, like this: ``` yaml --- format: html -echo: false -warning: false --- ``` -The `format: html` setting is optional since by default a Quarto doc is formatted as HTML. We put it here to explicitly state that this document should be rendered as an HTML document. The other settings, `echo: false` and `warning: false`, are recommended for a clean user experience, so that the survey does not display any code scripts or warnings to the participants. - -The YAML header can also contain other survey settings, such as theme settings and survey behavior settings. See the [Survey Settings](survey-settings.html) page for details. +The YAML header can also contain other settings, such as theme settings, survey settings, and system message settings. See the [Survey Settings](survey-settings.html) page for details. The rest of the content in the **survey.qmd** file is the content you want in your survey, including **pages**, **navigation buttons**, and **questions**. @@ -129,50 +118,6 @@ Each new page delimiter automatically closes the previous page, so you don't nee Alternatively, you can use fence syntax with `:::` to explicitly mark the start and end of pages (see the [Page Navigation](page-navigation.html) page for details on both syntax options). -If you are using RStudio, you can also make use of the page gadget to create pages under the "Addins" dropdown menu. Here is what the **Survey Page Gadget** looks like in RStudio: - -
-
- -
-
- -### Adding navigation buttons - -To navigate to the next page, you need to insert a `sd_next()` function call inside a code chunk, like this: - -```{r} -#| echo: fenced - -sd_next() -``` - -The above code chunk will create a "Next" button that goes on to the immediate next page. The button will look like this: - -```{r} -#| eval: true -#| echo: false - -sd_next() -``` - -You can also provide an alternative label on the next page button using the `label` argument, and you can send the user to other pages by just changing the value assigned to the `next_page` argument. For example, to send the user to a page with the id `page3`, you can use: - -```{r} -#| echo: fenced - -sd_next(label = "Go to page 3", next_page = 'page3') -``` - -```{r} -#| eval: true -#| echo: false - -sd_next(label = "Go to page 3", next_page = 'page3') -``` - -You can conditionally override these controls using the `sd_skip_forward()` function in your server function. See the [Conditional Survey Flow](conditional-survey-flow.html) page or [Page Navigation](page-navigation.html) page for more details. - ### Adding questions Every survey question is created using the `sd_question()` function inside a code chunk. The question type is defined by the `type` argument. @@ -224,13 +169,19 @@ The function has many other arguments for customizing the look and feel of the q By default all questions are optional, but you can make questions required in the survey settings (see the [Survey Settings](survey-settings.html#required-questions) page for details). -Similar to page creation, you can also make use of the [question gadget](defining-questions.html#using-a-gui-in-rstudio) to create questions under the "Addins" dropdown menu following similar operations. Below is a showcase of the **Survey Question Gadget** in RStudio: +### Navigation buttons -
-
- -
-
+By default, navigation buttons are automatically added to each page to allow respondents to move forward through the survey. To change the global settings (e.g., add a previous button on all pages, change the message shown on all next buttons, etc.), you can edit the survey settings in the YAML header (see the [Survey Settings](survey-settings.html#navigation-buttons) page for details). + +Additionally, you can also manually add navigation buttons using the `sd_nav()` function inside code chunks on any page, which will override the global settings for that specific page, like this: + +```{r} +#| echo: fenced + +sd_nav() +``` + +The `sd_nav()` function allows you to control mutliple aspects of the navigation buttons on a given page, including control over the labels used in the navigation buttons, control over whether to show either button at all, and control over which page to navigate to next. See the [Navigation Buttons](page-navigation.html#navigation-buttons) page for more details. ### Ending the survey @@ -323,14 +274,12 @@ The `db` object is used to store survey data - see the [Storing Data](storing-da ### Server -The `server()` function is a standard Shiny server function that takes `input`, `output`, and `session` as arguments. It is where you can set custom control logic and other configuration options, such as [conditional question display](conditional-question-display.html) logic with the `sd_show_if()` function, or [conditional survey flow](conditional-survey-flow.html) logic with the `sd_skip_forward()` function. +The `server()` function is a standard Shiny server function that takes `input`, `output`, and `session` as arguments. It is where you can set custom control logic and other configuration options, such as [conditional showing](conditional-logic.html#showing-questions) logic with the `sd_show_if()` function, [conditional page skipping](conditional-logic.html#conditional-skipping) logic with the `sd_skip_if()` function, or [conditional stopping](conditional-logic.html#conditional-stopping) logic with the `sd_stop_if()` function. It is also where you can control the logic for [reactive elements](reactivity.html) in your survey. {{< include ../chunks/server.qmd >}} The `sd_server()` function at the bottom makes everything run. It also includes some optional arguments that you can use to customize the survey. -See the [Survey Settings](survey-settings.html) page for details on the different options you can use to customize the server. - ::: {.callout-note} The `db = db` argument in `sd_server()` is required if you are using a database connection, which should be defined using the `sd_database()` function as mentioned above in the [Global Settings](#global-settings) section. See the [Store Data](storing-data.html) page for more details. @@ -342,19 +291,11 @@ The `db = db` argument in `sd_server()` is required if you are using a database At the very bottom of the **app.R** file, you will see the following code: ```{r} -shiny::shinyApp(ui = sd_ui(), server = server) +shiny::shinyApp(ui = ui, server = server) ``` This code defines the Shiny app and should always be at the bottom of the **app.R** file. -::: {.callout-note} - -**What's with the `sd_ui()` thing?** - -In a typical Shiny app, you have to define the UI and server functions separately. In a surveydown survey, the UI is a fixed structure that is defined by the `sd_ui()` function, so simply provide it to the `ui` argument in `shiny::shinyApp()` to run the survey. - -::: - ### Local run To preview your survey, you can run the Shiny app locally by clicking the "Run App" button in RStudio or in your R console run the code `shiny::runApp('app.R')`. Typically, RStudio will launch the app in a new window, but you can also choose to have the app launch in a dedicated viewer pane, or in your external web browser. Make your selection by clicking the icon next to Run App: diff --git a/docs/defining-questions.qmd b/docs/defining-questions.qmd index 8beb84f7..5de2e547 100644 --- a/docs/defining-questions.qmd +++ b/docs/defining-questions.qmd @@ -63,6 +63,16 @@ sd_question( ::: +::: {.callout-tip} + +Note that either `option` or `options` can be used as the argument name for specifying the choices. If both are used, `option` takes precedence. + +The standard way of defining options is to use the `c("Label" = "value")` format, where the left side is the label shown to respondents, and the right side is the internal value stored in the survey results. + +The short-hand format `c("Label 1", "Label 2")` is also supported, in which case the values will be the snake case version of the labels (e.g., `"Label 1"` becomes `"label_1"`). + +::: + Different question types require different arguments. For example, a **text input** question is much simpler: ::: {.panel-tabset} diff --git a/docs/external-redirect.qmd b/docs/external-redirect.qmd index 7f61d0a5..0178830d 100644 --- a/docs/external-redirect.qmd +++ b/docs/external-redirect.qmd @@ -13,7 +13,7 @@ shiny::shinyOptions(bootstrapTheme = bslib::bs_theme(version = 5L)) Like other operations, there are also two types of redirection: static and reactive. A **static** redirect provides a hard-coded URL, like [google.com](https://www.google.com), [apple.com](https://www.apple.com), etc. A **reactive** redirect grabs parameters from your survey URL and allows you to use them to construct a new redirect URL. -We recommend you try out our [demo survey](https://github.com/surveydown-dev/demos/tree/main/external-redirect) that showcases both static and reactive redirects so that you can have a full understanding of how this works: +We recommend you try out our [external redirect template](https://github.com/surveydown-dev/template_external_redirect) that showcases both static and reactive redirects so that you can have a full understanding of how this works: ## Static Redirect diff --git a/docs/fetching-data.qmd b/docs/fetching-data.qmd index 4e7f39a3..e5af3d89 100644 --- a/docs/fetching-data.qmd +++ b/docs/fetching-data.qmd @@ -20,15 +20,15 @@ data <- sd_get_data(db) Here the `data` object will be a data.frame with the data from your database table. -## Reactive Fetching +## Live-polling Data ::: {.callout-tip} -Run the [Demo survey](https://github.com/surveydown-dev/demos/tree/main/reactive-plot) of a reactive plot for an example of reactively fetching data. +Run the [live-polling template](https://github.com/surveydown-dev/template_live_polling) of a reactive plot for an example of live-polling data. ::: -The above example is intended for an analysis context, where all you want to do is fetch the latest data from your database. However, you may also want to fetch the data in a reactive context (i.e. in your survey). For example, perhaps you want to show respondents a bar chart of the most common responses to a question. The `sd_get_data()` function is compatible with reactive fetching as well - just call it inside the `server()` function in your **app.R** file: +The above example is intended for an analysis context, where all you want to do is to fetch the latest data from your database. However, you may also want to fetch the data in a reactive context (i.e. in your survey). For example, perhaps you want to show respondents a bar chart of the most common responses to a question. The `sd_get_data()` function is compatible with reactive fetching as well - just call it inside the `server()` function in your **app.R** file: ```{r} server <- function(input, output, session) { diff --git a/docs/getting-started.qmd b/docs/getting-started.qmd index 8773b708..a56fa1b7 100644 --- a/docs/getting-started.qmd +++ b/docs/getting-started.qmd @@ -22,14 +22,10 @@ The [**surveydown**]({{< var url_package >}}) R package provides a set of functi The platform is based on some basic principles: -- Add content to your **survey.qmd** file using markdown text (or in RStudio use the visual editor). -- Define survey questions in R code chunks with the `sd_question()` function. -- Define pages using fences (`:::`), with navigation buttons handled using the `sd_next()` function. -- Add rich functionality to your survey using a variety of [Survey Settings](survey-settings.html) in the `server()` function in the **app.R** file. -- Apply [Conditional Logic](conditional-logic.html) to question display with `sd_show_if()` or page navigation with `sd_skip_forward()`. -- Store your respondent data in a database (see [Storing Data](storing-data.html)). - -This approach ensures a flexible survey platform that is fully reproducible and easy to customize. +- All content is defined and controlled in code, making it fully reproducible and version controllable. +- Survey content is defined inside a [Quarto](https://quarto.org/) document, allowing for rich content combining Markdown syntax and code chunks. +- Each survey is rendered as a single Shiny application, allowing for interactivity and dynamic behavior. +- Data storage is flexible and managed independently of where the survey app is hosted, providing flexibility and control over all data handling and storage. The remaining steps on this page will guide you through the process of creating a surveydown survey. diff --git a/docs/page-navigation.qmd b/docs/page-navigation.qmd index 802a95ce..de639479 100644 --- a/docs/page-navigation.qmd +++ b/docs/page-navigation.qmd @@ -68,50 +68,41 @@ Here is what the **Survey Page Gadget** looks like in RStudio:

-## Adding navigation buttons +## Navigation buttons -To navigate to the next page, you need to insert a `sd_next()` function call inside a code chunk, like this: +By default, navigation buttons are automatically added to each page to allow respondents to move forward through the survey. To change the global settings (e.g., add a previous button on all pages, change the message shown on all next buttons, etc.), you can edit the survey settings in the YAML header (see the [Survey Settings](survey-settings.html#navigation-buttons) page for details). -```{r} -#| echo: fenced - -sd_next() -``` - -The above code chunk will create a "Next" button that goes on to the immediate next page. The button will look like this: +Additionally, you can also manually add navigation buttons using the `sd_nav()` function inside code chunks on any page, which will override the global settings for that specific page, like this: ```{r} -#| eval: true -#| echo: false +#| echo: fenced -sd_next() +sd_nav() ``` -You can also provide an alternative label on the next page button using the `label` argument, like this: +The `sd_nav()` function allows you control over the labels using the `label_next` and `label_prev` arguments, e.g.: -```{r} -#| echo: fenced - -sd_next(label = "Go to the next page") -``` +- `label_next = "Next Page"` +- `label_prev = "Previous Page"` + +You can also control whether to show either button at all using the following arguments: -```{r} -#| eval: true -#| echo: false +- `show_next`: Set to `TRUE` or `FALSE` to show the next button or not. +- `show_prev`: Set to `TRUE` or `FALSE` to show the previous button or not. +- `show_buttons`: Set to `TRUE` or `FALSE` to show both buttons or not. -sd_next(label = "Go to the next page") -``` +Finally, you can also control which page to navigate to next using the `page_next` argument (see [Direct Forward Skipping](#direct-forward-skipping) below). ## Skipping Forward ### Direct Forward Skipping -You can send the survey respondent to other forward pages by changing the value assigned to the `next_page` argument in the `sd_next()` function. For example, to send the user to a page with the id `page3`, you can use: +You can send the survey respondent to other forward pages by changing the value assigned to the `page_next` argument in the `sd_nav()` function. For example, to send the user to a page with the id `page3`, you can use: ```{r} #| echo: fenced -sd_next(next_page = 'page3') +sd_nav(page_next = 'page3') ``` ### Conditional Forward Skipping diff --git a/docs/question-formatting.qmd b/docs/question-formatting.qmd index 6c4ad0a3..71dc4f4d 100644 --- a/docs/question-formatting.qmd +++ b/docs/question-formatting.qmd @@ -123,19 +123,19 @@ html_button_options <- c("option_1", "option_2", "option_3") names(html_button_options) <- c( "**Option 1**
-
+
**Type**: Fuji
**Price**: $ 2 / lb
**Freshness**: Average", "**Option 2**
-
+
**Type**: Pink Lady
**Price**: $ 1.5 / lb
**Freshness**: Excellent", "**Option 3**
-
+
**Type**: Honeycrisp
**Price**: $ 2 / lb
**Freshness**: Poor" @@ -162,19 +162,19 @@ html_button_options <- c("option_1", "option_2", "option_3") names(html_button_options) <- c( "**Option 1**
-
+
**Type**: Fuji
**Price**: $ 2 / lb
**Freshness**: Average", "**Option 2**
-
+
**Type**: Pink Lady
**Price**: $ 1.5 / lb
**Freshness**: Excellent", "**Option 3**
-
+
**Type**: Honeycrisp
**Price**: $ 2 / lb
**Freshness**: Poor" diff --git a/docs/local-dashboard.qmd b/docs/sdstudio.qmd similarity index 66% rename from docs/local-dashboard.qmd rename to docs/sdstudio.qmd index 6565c98e..65879393 100644 --- a/docs/local-dashboard.qmd +++ b/docs/sdstudio.qmd @@ -1,29 +1,51 @@ --- -title: "Local Dashboard" +title: "sdstudio package" --- -This feature requires the **sdstudio** package. Install it using code below: +**sdstudio** is a companion R package with **surveydown**. It has a single function: `sdstudio::launch()`, which launches the **Surveydown Studio** - a locally run shiny app that supports a graphic interface for editing, previewing, and monitoring a survey. + +::: {.callout-tip} + +For a full introduction to the surveydown studio, proceed to our blog post [Introducing sdstudio: A companion GUI for surveydown](/blog/2025-06-29-sdstudio/index.html). + +::: + +## Installation + +You can install sdstudio from CRAN in your R console: + +```{r} +install.packages("sdstudio") +``` + +or you can install the development version from [GitHub](https://github.com/surveydown-dev/sdstudio): ```{r} # install.packages("pak") pak::pak("surveydown-dev/sdstudio", ask = FALSE) ``` -With the **sdstudio** package, you can launch the **surveydown studio**. It is a local shiny app that provides a variety of extended capabilities that support **surveydown**. For a full introduction to the surveydown studio, proceed to our blog post [Introducing sdstudio: A companion GUI for surveydown](/blog/2025-06-29-sdstudio/index.html). +## Running the studio -In the studio, there is a dashboard for viewing and modifying your database credentials as well as viewing your survey data. To launch the studio, run the following in your R console: +Once installed, run this to launch the studio: ```{r} sdstudio::launch() ``` -This will open a new browser window with 3 tabs. Nagivate to the **Responses** tab to access the local dashboard. +
+surveydown Studio +
+ +## Monitoring Survey Responses + +After launching the studio, navigate to the **Responses** tab to access the local dashboard. The **Responses** tab in the surveydown studio allows you to view your survey data in a user-friendly interface. It contains survey answering statistics and response summarization charts. On the lower part you can preview the survey data sheet. There is a download button for you to obtain the survey data in CSV format. On the top right corner, you can toggle between **Local** and **DB** mode. The **Local** mode grabs the data from your `preview_data.csv` file, which is an auto-generated file when you don't connect to a database. The **DB** mode grabs the data from your connected database. -## Local Mode +### Local Mode This is the **Local** mode dashboard: @@ -31,7 +53,7 @@ This is the **Local** mode dashboard: In this mode, the `preview_data.csv` file is auto-selected. The attributes of this data file are displayed in different cards, followed by the actual data sheet preview in the "Survey Responses" card. -## DB Mode +### DB Mode In this mode, a "Database Connection" card is provided on the top. You can toggle it on and off by clicking the "Settings" button next to the card name. Input your database credentials here and click "Connect". You'll see the connection status on the top right corner. diff --git a/docs/storing-data.qmd b/docs/storing-data.qmd index 97feb6da..7140f1d3 100644 --- a/docs/storing-data.qmd +++ b/docs/storing-data.qmd @@ -122,11 +122,11 @@ If you're having trouble connecting to your database, try these steps: - Are you certain your password is correct? You can open your .env file in a text reader app or in your IDE to check it. - Are you certain your credentials are correct? If you run `surveydown::sd_db_config()` again in your R console, you can see the current values stored in the `[]` symbols to check if those are correct. -- Are you getting a message about something related to `gssencmode`? Take a look [here](https://surveydown.org/docs/troubleshooting#database-connection). +- Consider modifying the `gssencmode` parameter. Take a look [here](https://surveydown.org/faq.html#im-having-trouble-with-the-database-connection). ## Cookies -By default, the data on each page will be locally stored in the browser cookies, though you can turn this feature off if you wish (see [here](https://surveydown.org/docs/survey-settings#cookies)). +By default, the data on each page will be locally stored in the browser cookies, though you can turn this feature off if you wish (see [here](survey-settings.html#cookies)). This is done so that the session state can be restored should a respondent lose an internet connection or close the browser on accident, etc. The responses on each page will not be written to the database until the respondent clicks the next button or if they close the browser. diff --git a/docs/survey-settings.qmd b/docs/survey-settings.qmd index 4a1d969e..7a6dcd55 100644 --- a/docs/survey-settings.qmd +++ b/docs/survey-settings.qmd @@ -3,109 +3,96 @@ title: "Survey Settings" code-annotations: hover --- -**surveydown** provides 2 ways of configuring survey settings: +All survey settings are configured in the YAML header of the **survey.qmd** file. If you wish to use the default settings, no YAML header is required. -1. (Recommended) Using YAML header in **survey.qmd**. -2. Using `sd_server()` in **app.R**. +## Default settings -We chose to use YAML header keys in our [templates](/templates.html), and we intentionally made them verbose to demonstrate all available options. If you prefer using `sd_server()`, you can call `?sd_server()` to see all available options. +There are three categories of settings: -::: {.callout-note} -If you define the same setting in both the YAML header and `sd_server()`, the setting in `sd_server()` will take precedence. -::: +- The `theme-settings` control the visual appearance of the survey. You can customize the theme, position of the progress bar, and footer text. +- The `survey-settings` includes various options to customize the survey behavior, such as enabling cookies, enabling auto-scrolling, changing the system language, highlighting unanswered questions, and specifying required questions. +- The `system-messages` section allows you to customize the text for various buttons and messages displayed throughout the survey. -## Option 1: YAML in survey.qmd (Recommended) - -This is a comprehensive list of all survey configuration settings that can be defined in the YAML header of the **survey.qmd** file. +This is the comprehensive list of all default survey configuration settings: ```yaml -# General Settings - Must Have -format: html -echo: false -warning: false - -# Theme Settings -theme: default -barcolor: "#768692" -barposition: top -footer: Footer Text -footer-left: Footer Left Test -footer-center: Footer Center Test -footer-right: Footer Right Test - -# Survey Settings - Compatible with sd_server() arguments -use-cookies: true -auto-scroll: false -rate-survey: false -system-language: en -highlight-unanswered: true -highlight-color: gray -capture-metadata: true -start-page: page_1 -all-questions-required: false -required-questions: - - question_1 - - question_2 +--- +theme-settings: + theme: default + barposition: top + footer-left: '' + footer-center: '' + footer-right: '' + +survey-settings: + show-previous: no + use-cookies: yes + auto-scroll: no + rate-survey: no + all-questions-required: no + start-page: initial_page + system-language: en + highlight-unanswered: yes + highlight-color: gray + capture-metadata: yes + required-questions: [] + +system-messages: + cancel: Cancel + confirm-exit: Confirm Exit + sure-exit: Are you sure you want to exit the survey? + submit-exit: Submit and Exit + warning: Warning + required: Please answer all required questions before proceeding. + rating-title: Before you go... + rating-text: 'Rate your survey experience:' + rating-scale: from 1-poor to 5-excellent + previous: Previous + next: Next + exit: Exit Survey + close-tab: Please close this tab manually to exit the survey. + choose-option: Choose an option... + click: Click here + redirect: Redirecting in + seconds: seconds + new-tab: Opens in a new tab + redirect-error: "Error: This text won't trigger any redirection..." +--- ``` -The **General Settings** are standard Quarto options. Our `format` is set to `html`, indicating a web-based survey. The `echo` and `warning` options are set to `false` to suppress code and warning messages in the survey output. +::: {.callout-note} + +- All YAML keys can be written with underscores (`_`) or hyphens (`-`), so both `use-cookies` and `use_cookies` are valid. +- In YAML keys, you have 2 ways to represent boolean values: `true`/`false` or `yes`/`no`. Both are acceptable. For `true`/`false`, you can also use capitalized versions: `True`/`False`. -The **Theme Settings** control the visual appearance of the survey. You can customize the theme, position of the progress bar, and footer text. Notice that `footer` is equivalent to `footer-center`. +::: -The **Survey Settings** section includes various options to customize the survey behavior, such as enabling cookies, auto-scrolling, survey rating, system language, highlighting unanswered questions, capturing metadata, and specifying required questions. +## `settings.yml` file -::: {.callout-note} -Both **Theme Settings** and **Survey Settings** YAML keys can be written with underscores (`_`) or hyphens (`-`), so that `use-cookies` and `use_cookies` are interchangeable. +When you render your surveydown survey, a `_survey` folder is generated that contains all files needed to run the survey. In that folder, there is a `settings.yml` file that contains all survey settings in YAML format. This file is automatically generated based on the YAML header in your **survey.qmd** file. You can open and inspect this file to see how your survey settings are stored. -In YAML keys, you have 2 ways to represent boolean values: `true`/`false` or `yes`/`no`. Both are acceptable. For `true`/`false`, you can also use capitalized versions: `True`/`False`. -::: +::: {.callout-warning} -## Option 2: `sd_server()` in app.R - -Alternatively, you can configure survey settings using the `sd_server()` function in the **app.R** file, under the `server` function definition. The `server()` function is a standard Shiny server function that takes `input`, `output`, and `session` as arguments. - -Define your `sd_server()` like this: - -```{r} -server <- function(input, output, session) { - sd_server( - db = db, # Left out from YAML - use_cookies = TRUE, - auto_scroll = FALSE, - rate_survey = FALSE, - language = "en", - highlight_unanswered = TRUE, - highlight_color = "gray", - capture_metadata = TRUE, - start_page = "page_1", - all_questions_required = FALSE, - required_questions = c("question_1", "question_2") - ) -} -``` +Do not manually edit the `settings.yml` file, as any changes you make will be overwritten the next time you render your survey. -Above are the equivalent settings to those defined in the YAML header example. Note that the `db` argument is not available in the YAML header and must be specified in `sd_server()` if you are using a database connection. +::: -## Setting explanations +## Theme settings -### Appearance settings +### Overall theme Because the **survey.qmd** is a Quarto document, you can use any of the [Quarto formatting options](https://quarto.org/docs/reference/formats/html.html#format-options) to change the appearance of your survey. For example, you can change the overall survey theme using the `theme` key: ``` yaml ---- theme: united # Any bootswatch theme ---- ``` There are [25 bootswatch themes](https://quarto.org/docs/output-formats/html-themes.html) to choose from. You can also provide a `custom.scss` file to further modify the theme, or even combine the two, e.g.: ``` yaml ---- theme: [united, custom.scss] ---- ``` ### Progress bar @@ -113,10 +100,8 @@ theme: [united, custom.scss] You can modify the survey progress bar with the `barcolor` and `barposition` keys, e.g.: ``` yaml ---- barcolor: "#768692" barposition: top ---- ``` The `barcolor` key defines the color of the progress bar. It defaults to the primary theme color, but you can change it to any hex code you wish here to overwrite the theme color. @@ -130,21 +115,19 @@ You can customize the footer text using the `footer`, `footer-left`, `footer-cen If you only want to set a centered footer text, use the `footer` key, like this: ``` yaml ---- footer: This is your centered footer text. ---- ``` Or, you can customize the left, center, and right footer texts separately using the `footer-left`, `footer-center`, and `footer-right` keys, like this: ``` yaml ---- footer-left: Footer Left Test footer-center: Footer Center Test footer-right: Footer Right Test ---- ``` +## Survey settings + ### Cookies Cookies are used to store user sessions, including their session ID, survey data storage (backend), and their survey progress (frontend). @@ -152,9 +135,7 @@ Cookies are used to store user sessions, including their session ID, survey data Define your cookies like this in YAML: ``` yaml ---- use-cookies: true ---- ``` By default, the cookies are **enabled**. You can disable the cookies by changing the YAML key to `false`. @@ -166,9 +147,7 @@ The auto scroll feature allows the page to automatically scroll according to the Define auto scroll like this in YAML: ``` yaml ---- auto-scroll: true ---- ``` By default, auto scroll is **disabled**. @@ -178,9 +157,7 @@ By default, auto scroll is **disabled**. You may want to add a survey rating question by the end of the survey to collect user reflections. Simply set `rate-survey` to `true` (Default to `false`) in YAML: ``` yaml ---- rate-survey: true ---- ``` By default, survey rating is **disabled**. @@ -216,9 +193,7 @@ To systematically display the preset system messages and button text elements in By default, the system language is set to English (`en`): ``` yaml ---- system-language: en ---- ``` Six languages are internally supported by **surveydown**: @@ -237,17 +212,15 @@ Below is an example of the system language set to **Spanish** (`es`):
-You can also customize the system language. See the [System Translations](system-translations.html) page for more details. +You can also customize any system message beyond these default values (see the [System Messages](#system-messages) section below). ### Highlight unanswered questions You can highlight unanswered questions to help respondents identify which questions they have not answered yet: ``` yaml ---- highlight-unanswered: true highlight-color: gray ---- ``` By default, highlighting unanswered questions is **enabled**. You can disable this feature by setting `highlight-unanswered` to `false`. @@ -276,9 +249,7 @@ We auto-trigger **red** highlighting for required questions that are not answere To enable metadata capturing, set `capture-metadata` to `true` in YAML: ``` yaml ---- capture-metadata: true ---- ``` By default, metadata capturing is **enabled**. @@ -288,9 +259,7 @@ By default, metadata capturing is **enabled**. When editing your survey, it can be helpful to start the survey at a specific page. You can define the start page in YAML like this: ``` yaml ---- start-page: page_1 ---- ``` By default, the survey starts at the first page. @@ -302,9 +271,7 @@ By default, no questions are required. We provide 2 YAML keys to define required **Firstly**, set `all-questions-required` to `true` to make all questions required: ``` yaml ---- all-questions-required: true ---- ``` By default, `all-questions-required` is set to `false`. @@ -312,11 +279,37 @@ By default, `all-questions-required` is set to `false`. **Secondly**, use `required-questions` to provide a list of question IDs to make specific questions required: ``` yaml ---- required-questions: - question_1 - question_2 ---- ``` The questions set to required will make the respondent unable to proceed until they have answered all of them on the current page. It will also place a red asterisk (*) next to the question label to indicate that the question is required. + +## System messages + +You can customize all system messages and button text elements displayed throughout the survey by modifying the `system-messages` section in YAML. By default, all system messages are in English, though you can set the default language to one of six supported languages by changing the [`system-language`](#system-language) setting. + +Any customizations you make in the `system-messages` section will override the default messages, regardless of the system language setting. + +Here is a description of each system message key and its default value: + +- `cancel`: Text for the cancel button in dialog boxes. +- `confirm-exit`: Title text for the confirm exit dialog box (when using `sd_close()` to insert a closing button). +- `sure-exit`: Message text asking if the user is sure they want to exit the survey (when using `sd_close()` to insert a closing button). +- `submit-exit`: Text for the submit and exit button in the confirm exit dialog box. +- `warning`: Title text for warning dialog boxes. +- `required`: Message text prompting the user to answer all required questions before proceeding. +- `rating-title`: Title text for the survey rating dialog box. +- `rating-text`: Message text prompting the user to rate their survey experience. +- `rating-scale`: Description of the rating scale used in the survey rating question. +- `previous`: Text for all previous navigation buttons. +- `next`: Text for all next navigation buttons. +- `exit`: Text for the exit survey button. +- `close-tab`: Message prompting the user to close the tab manually to exit the survey. +- `choose-option`: Placeholder text for dropdown menus. +- `click`: Text for clickable links. +- `redirect`: Text indicating that the user is being redirected. +- `seconds`: Text indicating the number of seconds remaining before redirection. +- `new-tab`: Text indicating that a link opens in a new tab. +- `redirect-error`: Error message displayed if redirection fails. diff --git a/docs/system-translations.qmd b/docs/system-translations.qmd deleted file mode 100644 index f53b752c..00000000 --- a/docs/system-translations.qmd +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: "System Translations" ---- - -As of v0.4.2, surveydown supports the ability to set the system language for all system messages (i.e. text elements such as button labels and warnings) and to provide the language specific format for [date type questions](question-types.html#date). You can also [customize](#custom-messages) the system messages by providing a `translations.yml` file in your root project directory. - -## Setting the Language - -To set the language of your survey, you can either define the `system-language` key in YAML of **survey.qmd**, or use the `system_language` argument in the `sd_server()` function in your **app.R** file. For example, to set the survey language to **Spanish**, you would write: - -Either in YAML of **survey.qmd**: - -```yaml ---- -system-language: es ---- -``` - -Or in the **app.R** file: - -```{r} -server <- function(input, output, session) { - sd_server( - system_language = "es" - ) -} -``` - -When setting the language, make sure to use a valid language code. The list of supported language codes is the same as those supported by Shiny's `dateInput()` (a [full list](#full-language-code-list) of codes is provided at the bottom of this page). - -For now, surveydown comes with built-in translations for the following languages: - -* `en` - English (Default) -* `de` - German -* `es` - Spanish -* `fr` - French -* `it` - Italian -* `zh-CN` - Chinese (Simplified) - -These translations cover all system messages, meaning you can simply set the language and all default messages should be in the chosen language. - -## Custom Messages - -If you want to customize the system translations or provide translations in a language that is not yet supported, you can create a `translations.yml` file in the root folder of your survey project using the following function: - -```{r} -surveydown::sd_create_translations(language = 'en') -``` - -This will create a file named `translations.yml` in your root project directory with the language set to whatever you provided as the `language` argument. You can modify any of the system messages in this file to be used in your survey. - -Below is an example of the `translations.yml` file for all default English messages. The keys represent the system message identifiers, and the values are the translated messages: - -```yaml -en: - cancel: Cancel - confirm_exit: Confirm Exit - sure_exit: Are you sure you want to exit the survey? - submit_exit: Submit and Exit - warning: Warning - required: Please answer all required questions before proceeding. - rating_title: Before you go... - rating_text: 'Rate your survey experience:' - rating_scale: from 1-poor to 5-excellent - next: Next - exit: Exit Survey - close_tab: Please close this tab manually to exit the survey. - choose_option: Choose an option... - click: Click here - redirect: Redirecting in - seconds: seconds - new_tab: Opens in a new tab - redirect_error: 'Error: This text won''t trigger any redirection...' -``` - -When you run your survey, surveydown will detect the `translations.yml` file in your root folder and use it to override the default translations. If you provide translations for a language not supported by default, you can use the `language` argument in `sd_server()` to set your custom language code. Note that the chosen `language` still must be from the current [list of supported languages](#full-language-code-list). - -For example, suppose you added translations for Portuguese in your `translations.yml`: - -```yaml -pt: - next: 'Próximo' - exit: 'Sair da Pesquisa' - # ... other messages -``` - -In your **app.R** file, set the language to Portuguese: - -```{r} -server <- function(input, output, session) { - sd_server( - language = "pt" - ) -} -``` - -It is also possible to have multiple translations for different translations in a single `translations.yml` file and simply select the required language one within the `sd_server()` function with the matching country code. - -## Overriding Specific Messages - -You don't have to provide translations for _all_ system messages. If you only want to change specific messages, you can provide translations only for those messages, and surveydown will use the default translations for any missing messages. - -For example, to customize only all "Next" button label in English, you could include the following in your `translations.yml` file: - -```yaml -en: - next: 'Continue' -``` - -Since English is the default language, the language doesn't even have to be defined in the `sd_server()` function in this case. - -## Button Defaults - -The translations also apply to default labels in `sd_next()`, `sd_close()`, and `sd_redirect()`. For example, if you use `sd_next()` without specifying a `label`, it will use the translated label based on the chosen language. - -```{r} -# In your survey.qmd file - -# This will display "Weiter" if language is set to "de" in your app.R file -sd_next() -``` - -However, it is still possible to manually adjust the translations in the mentioned functions inside the **survey.qmd** file using the `label` argument for each case. - -::: {.callout-note} - -If you separately render your **survey.qmd** file before running your **app.R** file, these buttons messages may appear in English in the rendered `survey.html` file in your root project folder. This is because the survey file doesn't "know" the language setting until you locally run your **app.R** at least once. But don't worry - just run the **app.R** file once and the language will then be set. Also, the language setting will always be used when the app is run regardless of what the local `survey.html` file looks like. - -::: - -## Notes - -- When providing custom translations, make sure the keys match exactly the expected message identifiers. -- If you create the `translations.yml` file manually, please note that each text element containing a `:` character must be written in quotation marks and the last line must contain a line break. -- If you provide an invalid language code in `language`, surveydown will fall back to English. -- If a message is not translated in your `translations.yml` file, surveydown will use the default translation for that message. - -## Full Language Code List - -The full list of supported language codes can be found in the documentation for [`shiny::dateInput()`](https://shiny.posit.co/r/reference/shiny/1.7.0/dateinput). We list them here as a quick reference: - -* `ar` - Arabic -* `az` - Azerbaijani -* `bg` - Bulgarian -* `bs` - Bosnian -* `ca` - Catalan -* `cs` - Czech -* `cy` - Welsh -* `da` - Danish -* `de` - German -* `el` - Greek -* `en` - English (Default) -* `en-AU` - English (Australia) -* `en-GB` - English (UK) -* `eo` - Esperanto -* `es` - Spanish -* `et` - Estonian -* `eu` - Basque -* `fa` - Persian -* `fi` - Finnish -* `fo` - Faroese -* `fr` - French -* `fr-CH` - French (Switzerland) -* `gl` - Galician -* `he` - Hebrew -* `hr` - Croatian -* `hu` - Hungarian -* `hy` - Armenian -* `id` - Indonesian -* `is` - Icelandic -* `it` - Italian -* `it-CH` - Italian (Switzerland) -* `ja` - Japanese -* `ka` - Georgian -* `kh` - Khmer -* `kk` - Kazakh -* `ko` - Korean -* `kr` - Korean -* `lt` - Lithuanian -* `lv` - Latvian -* `me` - Montenegrin -* `mk` - Macedonian -* `mn` - Mongolian -* `ms` - Malay -* `nb` - Norwegian Bokmål -* `nl` - Dutch -* `nl-BE` - Dutch (Belgium) -* `no` - Norwegian -* `pl` - Polish -* `pt` - Portuguese -* `pt-BR` - Portuguese (Brazil) -* `ro` - Romanian -* `rs` - Serbian -* `rs-latin` - Serbian (Latin) -* `ru` - Russian -* `sk` - Slovak -* `sl` - Slovenian -* `sq` - Albanian -* `sr` - Serbian -* `sr-latin` - Serbian (Latin) -* `sv` - Swedish -* `sw` - Swahili -* `th` - Thai -* `tr` - Turkish -* `uk` - Ukrainian -* `vi` - Vietnamese -* `zh-CN` - Chinese (Simplified) -* `zh-TW` - Chinese (Traditional) diff --git a/docs/troubleshooting.qmd b/docs/troubleshooting.qmd deleted file mode 100644 index e7f9d6ec..00000000 --- a/docs/troubleshooting.qmd +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Troubleshooting" ---- - -::: {.callout-note} - -**surveydown** requires some basic setups in order to get running. If you find glitches or failures, this page is for you. - -::: - -## Required software - -Make sure you have all of the required software for surveydown - see the [installation instructions](docs/getting-started.html#install) on the [Getting Started](docs/getting-started.html) page for details. - -::: {.callout-note} - -Sometimes the R packages like `surveydown` may not install properly, often because your path is managed by some package managing system like [Anaconda](https://www.anaconda.com). In this case, a third alternative is to download the zip file of the package source code and then install it locally. - -To download the zip file, go the the [surveydown repo](https://github.com/surveydown-dev/surveydown), click on the green "Code" button and click on "Download Zip", or simplify click on [this link](https://github.com/surveydown-dev/surveydown/archive/refs/heads/main.zip). - -Unzip this repo, then open the **surveydown.Rproj** file. In your R Console panel, run this code to install: - -```{r} -# install.packages("pak") -pak::local_install(ask = FALSE) -``` - -::: - -Some useful links: - -- [surveydown CRAN Page](https://cran.r-project.org/web/packages/surveydown/index.html) -- [surveydown GitHub repo](https://github.com/surveydown-dev/surveydown/) - -### sdstudio - -**sdstudio** is a companion R package with **surveydown**. It is intended for supportive webapps for surveydown. It has a single function: `sdstudio::launch()`, which launches the **surveydown Studio**. This is a locally run shiny app that supports a graphic interface for survey construction, preview, and data management. - -In your R Console, run this to install **sdstudio** from GitHub (it's not yet on CRAN): - -```{r} -# install.packages('pak') -pak::pak("surveydown-dev/sdstudio", ask = FALSE) -``` - -Once installed, run this to launch the studio: - -```{r} -sdstudio::launch() -``` - -
-surveydown Studio -
- -## Database Connection - -You might encounter connection problem caused by failure of GSSAPI (Generic Security Services Application Program Interface). It is a a protection layer for data security supported by PostgreSQL. In SQL management, it is controlled by the `gssencmode` argument. - -In previous versions of **surveydown** (before `v0.12.5`), the `sd_db_connect()` and `sd_dashboard()` functions have a `gssencmode` default to `"prefer"`, which enables GSSAPI, but may cause connection failure under some network conditions (VPN, for example). Our previous solution is to manually change `gssencmode` from `"prefer"` to `"disable"`, but it is less intuitive and causes more trouble than efficiency. - -Therefore, our current solution (versions after `v0.12.5`) is to remove the `gssencmode` argument from these functions, in which the GSSAPI is set to `"prefer"` by default, but if the connection errors due to network problem, it will auto-switch to `"disable"` and leave a message. - -## Still Can't Deploy? - -If your shinyapps deployment fails, you should firstly make sure your Supabase credentials are correctly defined, including your Supabase project settings and password settings. The password defined by `sd_db_config()` should be the same as your Supabase project password. Access the [Storing Data](docs/storing-data.html) page for how to set all Supabase credentials. Then, make sure your survey runs on your local machine and can successfully link with your Supabase project table. With these confirmed, your shinyapps deployment should work without problem. - -If you still encounter an error (e.g., the page shows the app failed to start, or you see the page but it doesn't run properly), try clearing your cache. The simplest way to do so is to **reboot your computer**. It may also help if you delete previously generated files, such as the `survey.html` file if you rendered it and the `rsconnect` folder. After re-rendering, you should be able to deploy the app without error. diff --git a/faq.qmd b/faq.qmd index a4136f8e..eb804201 100644 --- a/faq.qmd +++ b/faq.qmd @@ -39,6 +39,46 @@ When a surveydown survey first runs, it checks for files in a **\_survey** folde Also, it is important that you run your survey locally **at least once** before deploying it live. This will render all your survey content into the **\_survey** folder, which will also get uploaded when you deploy it. -## How do I customize my survey? +## How do I customize the look and feel of my survey? Please see our [Survey Settings](/docs/survey-settings.html) page for a full walkthrough of all available customization options, including themes, colors, progress bar settings, and survey behaviors. + +## Installation issues + +Make sure you have all of the required software for surveydown - see the [installation instructions](docs/getting-started.html#install) for details. + +::: {.callout-note} + +Sometimes the R packages like `surveydown` may not install properly, often because your path is managed by some package managing system like [Anaconda](https://www.anaconda.com). In this case, a third alternative is to download the zip file of the package source code and then install it locally. + +To download the zip file, go the the [surveydown repo](https://github.com/surveydown-dev/surveydown), click on the green "Code" button and click on "Download Zip", or simplify click on [this link](https://github.com/surveydown-dev/surveydown/archive/refs/heads/main.zip). + +Unzip this repo, then open the **surveydown.Rproj** file. In your R Console panel, run this code to install: + +```{r} +# install.packages("pak") +pak::local_install(ask = FALSE) +``` + +::: + +Some useful links: + +- [surveydown CRAN Page](https://cran.r-project.org/web/packages/surveydown/index.html) +- [surveydown GitHub repo](https://github.com/surveydown-dev/surveydown/) + +## I'm having trouble with the database connection + +You might encounter connection problem caused by failure of GSSAPI (Generic Security Services Application Program Interface). It is a a protection layer for data security supported by PostgreSQL. In SQL management, it is controlled by the `gssencmode` argument. + +In previous versions of **surveydown** (before `v0.12.5`), the `sd_db_connect()` and `sd_dashboard()` functions have a `gssencmode` default to `"prefer"`, which enables GSSAPI, but may cause connection failure under some network conditions (VPN, for example). Our previous solution is to manually change `gssencmode` from `"prefer"` to `"disable"`, but it is less intuitive and causes more trouble than efficiency. + +Therefore, our current solution (versions after `v0.12.5`) is to remove the `gssencmode` argument from these functions, in which the GSSAPI is set to `"prefer"` by default, but if the connection errors due to network problem, it will auto-switch to `"disable"` and leave a message. + +## I'm having trouble deploying my survey to shinyapps.io + +If your shinyapps deployment fails, you should firstly make sure your database credentials are correctly defined, including your Supabase project settings and password settings (if you're using Supabase). The password defined by `sd_db_config()` should be the same as your Supabase project / database password. Access the [Storing Data](docs/storing-data.html) page for how to set all database credentials. + +Then, make sure your survey runs on your local machine and can successfully link with your database table. With these confirmed, your shinyapps deployment should work without problem. + +If you still encounter an error (e.g., the page shows the app failed to start, or you see the page but it doesn't run properly), try clearing your cache. The simplest way to do so is to **reboot your computer**. It may also help if you delete previously generated files, such as the `survey.html` file if you rendered it and the `rsconnect` folder. After re-rendering, you should be able to deploy the app without error.