├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── .workbch ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── build_api.R ├── build_experiment.R ├── build_resources.R ├── build_timeline.R ├── data_condition.R ├── fullscreen.R ├── helpers_js.R ├── helpers_misc.R ├── insert.R ├── keycodes.R ├── pavlovia.R ├── randomisation.R ├── save_locally.R ├── save_webserver.R ├── timeline_modify.R ├── trial_animation.R ├── trial_audio_button_response.R ├── trial_audio_keyboard_response.R ├── trial_audio_slider_response.R ├── trial_categorize_animation.R ├── trial_categorize_html.R ├── trial_categorize_image.R ├── trial_generic.R ├── trial_html_button_response.R ├── trial_html_keyboard_response.R ├── trial_html_slider_response.R ├── trial_image_button_response.R ├── trial_image_keyboard_response.R ├── trial_image_slider_response.R ├── trial_instructions.R ├── trial_survey_likert.R ├── trial_survey_multi_choice.R ├── trial_survey_multi_select.R ├── trial_survey_text.R ├── trial_video_button_response.R ├── trial_video_keyboard_response.R └── trial_video_slider_response.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── docs ├── 404.html ├── CNAME ├── LICENSE-text.html ├── LICENSE.html ├── articles │ ├── index.html │ ├── jaysire01.html │ ├── jaysire01_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire02.html │ ├── jaysire02_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire03.html │ ├── jaysire03_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire04.html │ ├── jaysire04_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire05.html │ ├── jaysire05_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire06.html │ ├── jaysire06_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire07.html │ ├── jaysire07_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── jaysire08.html │ └── jaysire08_files │ │ └── accessible-code-block-0.0.1 │ │ └── empty-anchor.js ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── demos │ ├── example01 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── script │ │ │ ├── jspsych-html-button-response.js │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-instructions.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ └── style │ │ │ └── jspsych.css │ ├── example02 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── script │ │ │ ├── jspsych-html-button-response.js │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-instructions.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ └── style │ │ │ └── jspsych.css │ ├── example03 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── image │ │ │ ├── stimulus1.png │ │ │ ├── stimulus2.png │ │ │ ├── stimulus3.png │ │ │ ├── stimulus4.png │ │ │ ├── stimulus5.png │ │ │ ├── stimulus6.png │ │ │ ├── stimulus7.png │ │ │ └── stimulus8.png │ │ │ ├── script │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-image-button-response.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ └── style │ │ │ └── jspsych.css │ ├── example04 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── audio │ │ │ └── lukewarm_banjo.mp3 │ │ │ ├── image │ │ │ └── heart.png │ │ │ ├── script │ │ │ ├── jspsych-audio-button-response.js │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-image-button-response.js │ │ │ ├── jspsych-video-button-response.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ ├── style │ │ │ └── jspsych.css │ │ │ └── video │ │ │ ├── heart.mpg │ │ │ └── heart.webm │ ├── example05 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── audio │ │ │ └── lukewarm_banjo.mp3 │ │ │ ├── image │ │ │ └── heart.png │ │ │ ├── script │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-image-button-response.js │ │ │ ├── jspsych-image-keyboard-response.js │ │ │ ├── jspsych-image-slider-response.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ ├── style │ │ │ └── jspsych.css │ │ │ └── video │ │ │ ├── heart.mpg │ │ │ └── heart.webm │ ├── example06 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── script │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-survey-likert.js │ │ │ ├── jspsych-survey-multi-choice.js │ │ │ ├── jspsych-survey-multi-select.js │ │ │ ├── jspsych-survey-text.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ └── style │ │ │ └── jspsych.css │ ├── example07 │ │ └── experiment │ │ │ ├── experiment.js │ │ │ ├── index.html │ │ │ └── resource │ │ │ ├── audio │ │ │ └── lukewarm_banjo.mp3 │ │ │ ├── image │ │ │ └── heart.png │ │ │ ├── script │ │ │ ├── jspsych-html-button-response.js │ │ │ ├── jspsych-html-keyboard-response.js │ │ │ ├── jspsych-image-button-response.js │ │ │ ├── jspsych-survey-multi-select.js │ │ │ ├── jspsych.js │ │ │ └── xprmntr.js │ │ │ ├── style │ │ │ └── jspsych.css │ │ │ └── video │ │ │ ├── heart.mpg │ │ │ └── heart.webm │ └── example08 │ │ └── experiment │ │ ├── experiment.js │ │ ├── index.html │ │ └── resource │ │ ├── image │ │ ├── blue.png │ │ └── orange.png │ │ ├── script │ │ ├── jspsych-html-keyboard-response.js │ │ ├── jspsych-image-keyboard-response.js │ │ ├── jspsych-instructions.js │ │ ├── jspsych.js │ │ └── xprmntr.js │ │ └── style │ │ └── jspsych.css ├── docsearch.css ├── docsearch.js ├── index.html ├── link.svg ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml └── reference │ ├── Rplot001.png │ ├── add_parameters.html │ ├── add_properties.html │ ├── add_resources.html │ ├── add_variables.html │ ├── any_key.html │ ├── build_experiment.html │ ├── build_resources.html │ ├── build_timeline.html │ ├── code.html │ ├── cue_html.html │ ├── cue_image.html │ ├── cue_text.html │ ├── data_condition.html │ ├── define_resources.html │ ├── display_if.html │ ├── display_while.html │ ├── download_data_webserver.html │ ├── download_googlecloud.html │ ├── download_webserver.html │ ├── experiment.html │ ├── fn_data_condition.html │ ├── fn_sample.html │ ├── fn_save_datastore.html │ ├── fn_save_locally.html │ ├── fn_save_webserver.html │ ├── fullscreen.html │ ├── index.html │ ├── insert_javascript.html │ ├── insert_property.html │ ├── insert_resource.html │ ├── insert_variable.html │ ├── jshelpers.html │ ├── keycode.html │ ├── keycodes.html │ ├── no_key.html │ ├── pavlovia.html │ ├── question.html │ ├── question_likert.html │ ├── question_multi.html │ ├── question_text.html │ ├── reexports.html │ ├── resource.html │ ├── respond_any_key.html │ ├── respond_no_key.html │ ├── response_button.html │ ├── response_freetext.html │ ├── response_key.html │ ├── response_likert.html │ ├── response_pickone.html │ ├── response_picksome.html │ ├── response_slider.html │ ├── run_appengine.html │ ├── run_googlecloud.html │ ├── run_locally.html │ ├── run_webserver.html │ ├── save_appengine.html │ ├── save_datastore.html │ ├── save_googlecloud.html │ ├── save_locally.html │ ├── save_webserver.html │ ├── set_parameters.html │ ├── set_variables.html │ ├── temporary_folder.html │ ├── timeline.html │ ├── tl_add_parameters.html │ ├── tl_add_variables.html │ ├── tl_display_if.html │ ├── tl_display_while.html │ ├── tl_show_if.html │ ├── tl_show_while.html │ ├── tl_with_parameters.html │ ├── tl_with_variables.html │ ├── trial.html │ ├── trial_animation.html │ ├── trial_audio_button_response.html │ ├── trial_audio_keyboard_response.html │ ├── trial_audio_slider_response.html │ ├── trial_categorize.html │ ├── trial_categorize_animation.html │ ├── trial_categorize_html.html │ ├── trial_categorize_image.html │ ├── trial_generic.html │ ├── trial_html_button_response.html │ ├── trial_html_keyboard_response.html │ ├── trial_html_slider_response.html │ ├── trial_image_button_response.html │ ├── trial_image_keyboard_response.html │ ├── trial_image_slider_response.html │ ├── trial_instructions.html │ ├── trial_simple.html │ ├── trial_survey.html │ ├── trial_survey_likert.html │ ├── trial_survey_multi_choice.html │ ├── trial_survey_multi_select.html │ ├── trial_survey_text.html │ ├── trial_video_button_response.html │ ├── trial_video_keyboard_response.html │ ├── trial_video_slider_response.html │ ├── variable.html │ ├── with_condition.html │ ├── with_loop.html │ ├── with_parameters.html │ └── with_variables.html ├── inst └── extdata │ ├── app.yaml │ ├── backend.py │ ├── img │ ├── bisexual.png │ ├── bisexual.svg │ ├── heart.png │ ├── lesbian.svg │ ├── rainbow.svg │ └── transgender.svg │ ├── jquery.min.js │ ├── jsPsych │ ├── css │ │ └── jspsych.css │ ├── jspsych.js │ ├── license.txt │ └── plugins │ │ ├── jspsych-animation.js │ │ ├── jspsych-audio-button-response.js │ │ ├── jspsych-audio-keyboard-response.js │ │ ├── jspsych-audio-slider-response.js │ │ ├── jspsych-call-function.js │ │ ├── jspsych-categorize-animation.js │ │ ├── jspsych-categorize-html.js │ │ ├── jspsych-categorize-image.js │ │ ├── jspsych-cloze.js │ │ ├── jspsych-external-html.js │ │ ├── jspsych-free-sort.js │ │ ├── jspsych-fullscreen.js │ │ ├── jspsych-html-button-response.js │ │ ├── jspsych-html-keyboard-response.js │ │ ├── jspsych-html-slider-response.js │ │ ├── jspsych-iat-html.js │ │ ├── jspsych-iat-image.js │ │ ├── jspsych-image-button-response.js │ │ ├── jspsych-image-keyboard-response.js │ │ ├── jspsych-image-slider-response.js │ │ ├── jspsych-instructions.js │ │ ├── jspsych-rdk.js │ │ ├── jspsych-reconstruction.js │ │ ├── jspsych-resize.js │ │ ├── jspsych-same-different-html.js │ │ ├── jspsych-same-different-image.js │ │ ├── jspsych-serial-reaction-time-mouse.js │ │ ├── jspsych-serial-reaction-time.js │ │ ├── jspsych-survey-html-form.js │ │ ├── jspsych-survey-likert.js │ │ ├── jspsych-survey-multi-choice.js │ │ ├── jspsych-survey-multi-select.js │ │ ├── jspsych-survey-text.js │ │ ├── jspsych-video-button-response.js │ │ ├── jspsych-video-keyboard-response.js │ │ ├── jspsych-video-slider-response.js │ │ ├── jspsych-visual-search-circle.js │ │ ├── jspsych-vsl-animate-occlusion.js │ │ └── jspsych-vsl-grid-scene.js │ ├── jspsych-pavlovia.js │ ├── record_result.php │ └── resources │ ├── heart.mpg │ ├── heart.png │ ├── heart.webm │ └── lukewarm_banjo.mp3 ├── jaysire.Rproj ├── man ├── build_experiment.Rd ├── build_resources.Rd ├── build_timeline.Rd ├── display_if.Rd ├── display_while.Rd ├── download_googlecloud.Rd ├── download_webserver.Rd ├── fn_data_condition.Rd ├── fn_sample.Rd ├── fullscreen.Rd ├── insert_javascript.Rd ├── insert_property.Rd ├── insert_resource.Rd ├── insert_variable.Rd ├── keycode.Rd ├── pavlovia.Rd ├── question_likert.Rd ├── question_multi.Rd ├── question_text.Rd ├── reexports.Rd ├── respond_any_key.Rd ├── respond_no_key.Rd ├── run_googlecloud.Rd ├── run_locally.Rd ├── run_webserver.Rd ├── save_googlecloud.Rd ├── save_locally.Rd ├── save_webserver.Rd ├── set_parameters.Rd ├── set_variables.Rd ├── temporary_folder.Rd ├── trial_animation.Rd ├── trial_audio_button_response.Rd ├── trial_audio_keyboard_response.Rd ├── trial_audio_slider_response.Rd ├── trial_categorize_animation.Rd ├── trial_categorize_html.Rd ├── trial_categorize_image.Rd ├── trial_generic.Rd ├── trial_html_button_response.Rd ├── trial_html_keyboard_response.Rd ├── trial_html_slider_response.Rd ├── trial_image_button_response.Rd ├── trial_image_keyboard_response.Rd ├── trial_image_slider_response.Rd ├── trial_instructions.Rd ├── trial_survey_likert.Rd ├── trial_survey_multi_choice.Rd ├── trial_survey_multi_select.Rd ├── trial_survey_text.Rd ├── trial_video_button_response.Rd ├── trial_video_keyboard_response.Rd └── trial_video_slider_response.Rd ├── misc ├── demo1.R ├── demo2.R └── demo3.R ├── pkgdown-tweaks.R ├── tests ├── testthat.R └── testthat │ └── test-build_experiment.R └── vignettes ├── .gitignore ├── jaysire01.Rmd ├── jaysire02.Rmd ├── jaysire03.Rmd ├── jaysire04.Rmd ├── jaysire05.Rmd ├── jaysire06.Rmd ├── jaysire07.Rmd └── jaysire08.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^LICENSE\.md$ 5 | ^misc$ 6 | ^\.workbch$ 7 | ^docs$ 8 | ^\.travis\.yml$ 9 | ^vignettes/jaysire01\.Rmd$ 10 | ^vignettes/jaysire02\.Rmd$ 11 | ^vignettes/jaysire03\.Rmd$ 12 | ^vignettes/jaysire04\.Rmd$ 13 | ^vignettes/jaysire05\.Rmd$ 14 | ^vignettes/jaysire06\.Rmd$ 15 | ^vignettes/jaysire07\.Rmd$ 16 | ^codecov\.yml$ 17 | ^vignettes/jaysire08\.Rmd$ 18 | ^_pkgdown.yml$ 19 | ^pkgdown-tweaks.R$ 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | cache: packages 5 | after_success: 6 | - Rscript -e 'covr::codecov()' 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - libssh-dev -------------------------------------------------------------------------------- /.workbch: -------------------------------------------------------------------------------- 1 | jaysire 2 | dAFzePQsrE 3 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: jaysire 2 | Type: Package 3 | Title: Build jsPsych Experiments in R 4 | Version: 0.1.0 5 | Author: Danielle Navarro 6 | Maintainer: Danielle Navarro 7 | Description: The jaysire package allows the user to build browser based 8 | behavioral experiments within R by providing an interface to the jsPsych 9 | javascript library. 10 | License: MIT + file LICENSE 11 | Encoding: UTF-8 12 | SystemRequirements: libssh >= 0.6.0 (the original, not libssh2) 13 | LazyData: true 14 | Imports: 15 | jsonlite, 16 | methods, 17 | plumber, 18 | purrr, 19 | readr, 20 | tibble, 21 | magrittr, 22 | rlang, 23 | here 24 | RoxygenNote: 7.1.1 25 | URL: https://github.com/djnavarro/jaysire 26 | BugReports: https://github.com/djnavarro/jaysire/issues 27 | Suggests: 28 | rmarkdown, 29 | testthat (>= 2.1.0), 30 | covr, 31 | ssh 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: Danielle Navarro 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 Danielle Navarro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(build_experiment) 5 | export(build_resources) 6 | export(build_timeline) 7 | export(display_if) 8 | export(display_while) 9 | export(download_googlecloud) 10 | export(download_webserver) 11 | export(fn_data_condition) 12 | export(fn_sample) 13 | export(fullscreen) 14 | export(insert_javascript) 15 | export(insert_property) 16 | export(insert_resource) 17 | export(insert_variable) 18 | export(keycode) 19 | export(pavlovia) 20 | export(question_likert) 21 | export(question_multi) 22 | export(question_text) 23 | export(respond_any_key) 24 | export(respond_no_key) 25 | export(run_googlecloud) 26 | export(run_locally) 27 | export(run_webserver) 28 | export(save_googlecloud) 29 | export(save_locally) 30 | export(save_webserver) 31 | export(set_parameters) 32 | export(set_variables) 33 | export(temporary_folder) 34 | export(trial_animation) 35 | export(trial_audio_button_response) 36 | export(trial_audio_keyboard_response) 37 | export(trial_audio_slider_response) 38 | export(trial_categorize_animation) 39 | export(trial_categorize_html) 40 | export(trial_categorize_image) 41 | export(trial_generic) 42 | export(trial_html_button_response) 43 | export(trial_html_keyboard_response) 44 | export(trial_html_slider_response) 45 | export(trial_image_button_response) 46 | export(trial_image_keyboard_response) 47 | export(trial_image_slider_response) 48 | export(trial_instructions) 49 | export(trial_survey_likert) 50 | export(trial_survey_multi_choice) 51 | export(trial_survey_multi_select) 52 | export(trial_survey_text) 53 | export(trial_video_button_response) 54 | export(trial_video_keyboard_response) 55 | export(trial_video_slider_response) 56 | importFrom(magrittr,"%>%") 57 | importFrom(rlang,"%||%") 58 | -------------------------------------------------------------------------------- /R/build_timeline.R: -------------------------------------------------------------------------------- 1 | # file: timeline_build.R 2 | # author: Danielle Navarro 3 | 4 | #' Build a timeline from trials 5 | #' 6 | #' @param ... trial objects to add to this timeline 7 | #' 8 | #' @return An object of class "timeline" 9 | #' 10 | #' @details Experiments in jsPsych are specified in terms of a "timeline" 11 | #' object, where each timeline can consist of one or more "trial" objects 12 | #' and timelines can contain other timelines. In pure jsPsych it is possible 13 | #' to define a "bare" trial that is not contained within a timeline (the trial 14 | #' is essentially a timeline) but jaysire is slightly more restrictive. To 15 | #' build a timeline in jaysire, the output of \code{trial_} functions need to be 16 | #' passed through the \code{build_timeline()} function to create a properly 17 | #' constructed timeline object. 18 | #' 19 | #' Once constructed, behaviour and execution of a timeline can be modified 20 | #' using a variety of functions. A timeline can be looped using the 21 | #' \code{\link{display_while}()} function, or executed contiionally using 22 | #' the \code{\link{display_if}()} function. Timeline variables can be attached 23 | #' using \code{\link{set_variables}()} and other parameters can be passed to 24 | #' the timeline using \code{\link{set_parameters}()}. 25 | #' 26 | #' @export 27 | build_timeline <- function(...){ 28 | tl <- list(timeline = list(...)) 29 | class(tl) <- c("timeline", "list") 30 | tl 31 | } 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /R/data_condition.R: -------------------------------------------------------------------------------- 1 | 2 | #' Return a javascript function that checks a data value 3 | #' 4 | #' @param expr An expression to be evaluated within the jsPsych data store 5 | #' @param trials_back The number of trials before the present one for which to query the data 6 | #' @export 7 | #' @return A javascript function 8 | #' @details The \code{fn_data_condition()} function creates a javascript function that 9 | #' can query the jsPsych data store and evaluate the expression \code{expr} within the 10 | #' data store. It is (at present) very limited, and can only query the data store for 11 | #' a single trial (i.e., a single row in the data set). By default it queries the 12 | #' most recent trial (\code{trials_back = 1}) but this behaviour can be modified. 13 | #' 14 | #' The intention behind this function is that it be used in conjunction with functions 15 | #' such as \code{\link{display_if}()} and \code{\link{display_while}()} that 16 | #' require a javascript function that will evaluate to true or false, in order to 17 | #' determine whether to continue the while loop or whether the if condition holds. 18 | #' 19 | #' As an example, one might set \code{fn_data_condition(button_pressed == "0")} 20 | #' when calling \code{\link{display_if}()}. If the participant had pressed 21 | #' button "0" on the previous trial, then the timeline in question will be 22 | #' executed. Otherwise it is not. 23 | #' 24 | #' Note that this function is a work in progress and will likely change in 25 | #' future versions in order to allow more flexibility. 26 | #' 27 | fn_data_condition <- function(expr, trials_back = 1) { 28 | 29 | expr <- deparse(rlang::enexpr(expr)) 30 | condition <- paste0("data.", expr) 31 | 32 | js_fun <- js_code(paste0( 33 | "function(){ 34 | var data = jsPsych.data.get().last(", trials_back, ").values()[0]; 35 | if(", condition, "){ 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | }" 41 | )) 42 | return(js_fun) 43 | } 44 | -------------------------------------------------------------------------------- /R/fullscreen.R: -------------------------------------------------------------------------------- 1 | 2 | #' Toggle fullscreen mode in the browser 3 | #' 4 | #' @description The \code{fullscreen} function is used to toggle fullscreen mode in the browser. 5 | #' 6 | #' @param fullscreen_mode If TRUE, sets browser in full screen mode. Default: TRUE. 7 | #' @param message This is the message that is displayed in the browser to inform of the switch to fullscreen mode. 8 | #' @param button_label This is label of the button to acknowledge the switch. 9 | #' @param delay_after Time period in ms after the switch to proceed with the following element in the timeline. 10 | #' 11 | #' @export 12 | fullscreen <- function( 13 | fullscreen_mode = TRUE, 14 | message = "

The experiment will switch to full screen mode when you press the button below

", 15 | button_label = "Continue", 16 | delay_after = 1000 17 | ) { 18 | drop_nulls( 19 | trial( 20 | type = "fullscreen", 21 | fullscreen_mode = js_logical(fullscreen_mode), 22 | message = message, 23 | button_label = button_label, 24 | delay_after = delay_after 25 | ) 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /R/helpers_js.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | js_code <- function(x) { 4 | class(x) <- "json" 5 | x 6 | } 7 | 8 | # return a scalar js logical 9 | js_logical <- function(x) { 10 | x <- as.logical(x) 11 | if(x == TRUE) return(js_code("true")) 12 | return(js_code("false")) 13 | } 14 | 15 | js_numeric <- function(x) { 16 | as.numeric(x) 17 | } 18 | 19 | # the ... here are individual strings 20 | js_string <- function(...) { 21 | s <- list(...) 22 | string_quote <- function(c) { 23 | if(methods::is(c,"json")) { return(c) } 24 | return(paste0('"',c,'"')) 25 | } 26 | s <- paste(lapply(s, string_quote), collapse = " + ") 27 | js_code(s) 28 | } 29 | 30 | # the ... here are list of name/value pairs 31 | js_struct <- function(...) { 32 | list(...) 33 | } 34 | 35 | # the ... here are list of name/value pairs 36 | js_array <- function(...) { 37 | classes <- purrr::map_chr(list(...), ~ class(.x)[1]) 38 | if(any(classes == "list")) { 39 | return(list(...)) 40 | } 41 | return(c(...)) 42 | } 43 | 44 | list_to_jsarray <- purrr::lift_dl(js_array) 45 | 46 | 47 | -------------------------------------------------------------------------------- /R/helpers_misc.R: -------------------------------------------------------------------------------- 1 | # file: helpers.R 2 | # author: Danielle Navarro 3 | 4 | 5 | #' Response is accepted with any key press 6 | #' 7 | #' @export 8 | #' @details Many of the functions within the \code{trial_} family are designed 9 | #' to allow participants to respond using a key press, generally by specifying a 10 | #' a \code{choices} argument that indicates which keys will be accepted as valid 11 | #' responses (e.g., \code{choices = c("f","j")}). There are also cases where 12 | #' you may wish to allow every key to be a valid response (e.g., in situations 13 | #' where "Press any key to continue" is a sensible prompt). 14 | #' In those cases, specifying \code{choices = respond_any_key()} will 15 | #' produce the desired behaviour. 16 | #' 17 | #' @seealso \code{\link{respond_no_key}}, \code{\link{trial_html_keyboard_response}}, 18 | #' \code{\link{trial_image_keyboard_response}}, \code{\link{trial_audio_keyboard_response}}, 19 | #' \code{\link{trial_video_keyboard_response}} 20 | #' 21 | respond_any_key <- function() { 22 | js_code("jsPsych.ANY_KEY") 23 | } 24 | 25 | #' Response is not accepted for any key press 26 | #' 27 | #' @export 28 | #' 29 | #' @details Many of the functions within the \code{trial_} family are designed 30 | #' to allow participants to respond using a key press, generally by specifying a 31 | #' a \code{choices} argument that indicates which keys will be accepted as valid 32 | #' responses (e.g., \code{choices = c("f","j")}). There are also cases where 33 | #' you may wish to disable this, so that no key presses will be counted as valid 34 | #' responses (e.g., when a trial runs for a fixed duration but no response is 35 | #' expected). In those cases, specifying \code{choices = respond_no_key()} will 36 | #' produce the desired behaviour. 37 | #' 38 | #' @seealso \code{\link{respond_any_key}}, \code{\link{trial_html_keyboard_response}}, 39 | #' \code{\link{trial_image_keyboard_response}}, \code{\link{trial_audio_keyboard_response}}, 40 | #' \code{\link{trial_video_keyboard_response}} 41 | respond_no_key <- function() { 42 | js_code("jsPsych.NO_KEY") 43 | } 44 | 45 | 46 | 47 | drop_nulls <- function(x) { 48 | x[purrr::map_lgl(x, ~!is.null(.x))] 49 | } 50 | 51 | # returns a list of expressions 52 | capture_dots <- function(...) { 53 | as.list(substitute(list(...)))[-1L] 54 | } 55 | 56 | #' @importFrom magrittr %>% 57 | #' @export 58 | magrittr::`%>%` 59 | 60 | #' @importFrom rlang %||% 61 | NULL 62 | 63 | get_timestamp <- function() { 64 | tsp <- as.character(Sys.time()) 65 | tsp <- gsub("[ :]", "-", tsp) 66 | return(tsp) 67 | } 68 | 69 | get_alphanumeric <- function(n = 5) { 70 | x <- c("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r", 71 | "s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9") 72 | r <- sample(x, size = n, replace = TRUE) 73 | r <- paste(r, collapse = "") 74 | return(r) 75 | } 76 | 77 | #' Creates a temporary folder 78 | #' 79 | #' @return A string specifying the path to the folder 80 | #' @details The \code{temporary_folder()} function is a convenience function 81 | #' used to create a new temporary folder inside the temporary directory 82 | #' (see \code{tempdir}) for the current R session. The name of the subfolder 83 | #' is always "jaysire_" followed by a 5-character alphanumeric string. 84 | #' 85 | #' The purpose of this function is mostly expository: it makes it a little 86 | #' easier to create easy-to-follow tutorials on the package website. It is 87 | #' not expected that users of the jaysire package would have much need for this 88 | #' function 89 | #' 90 | #' @export 91 | temporary_folder <- function() { 92 | idstr <- paste0("jaysire_", get_alphanumeric(n = 5)) 93 | resource <- file.path(tempdir(), idstr) 94 | if(!dir.exists(resource)) { 95 | dir.create(resource) 96 | } 97 | return(resource) 98 | } 99 | -------------------------------------------------------------------------------- /R/keycodes.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #' Javascript key codes 6 | #' 7 | #' @param key character vector specifying keynames (default = NULL) 8 | #' @param code numeric vector specfiying keycodes (default = NULL) 9 | #' 10 | #' @return A numeric or character vector 11 | #' 12 | #' @details This function provides a mapping between the human-readable 13 | #' javascript \code{key} names and their corresponding numeric \code{code} 14 | #' values. If both input arguments are \code{NULL}, it returns a named 15 | #' numeric vector whose values correspond to the key codes and whose names 16 | #' correspond to the key names. If \code{key} is specified the return value 17 | #' is a vector with the corresponding numeric codes; whereas if \code{code} 18 | #' is specified the output is a character vector containing the corresponding 19 | #' key names. If neither argument is \code{NULL} the function throws an error. 20 | #' @export 21 | keycode <- function(key = NULL, code = NULL) { 22 | 23 | dictionary <- c( 24 | "backspace" = 8, 25 | "tab" = 9, 26 | "enter" = 13, 27 | "shift" = 16, 28 | "ctrl" = 17, 29 | "alt" = 18, 30 | "pause/break" = 19, 31 | "caps lock" = 20, 32 | "escape" = 27, 33 | "page up" = 33, 34 | "page down" = 34, 35 | "end" = 35, 36 | "home" = 36, 37 | "left arrow" = 37, 38 | "up arrow" = 38, 39 | "right arrow" = 39, 40 | "down arrow" = 40, 41 | "insert" = 45, 42 | "delete" = 46, 43 | "0" = 48, 44 | "1" = 49, 45 | "2" = 50, 46 | "3" = 51, 47 | "4" = 52, 48 | "5" = 53, 49 | "6" = 54, 50 | "7" = 55, 51 | "8" = 56, 52 | "9" = 57, 53 | "a" = 65, 54 | "b" = 66, 55 | "c" = 67, 56 | "d" = 68, 57 | "e" = 69, 58 | "f" = 70, 59 | "g" = 71, 60 | "h" = 72, 61 | "i" = 73, 62 | "j" = 74, 63 | "k" = 75, 64 | "l" = 76, 65 | "m" = 77, 66 | "n" = 78, 67 | "o" = 79, 68 | "p" = 80, 69 | "q" = 81, 70 | "r" = 82, 71 | "s" = 83, 72 | "t" = 84, 73 | "u" = 85, 74 | "v" = 86, 75 | "w" = 87, 76 | "x" = 88, 77 | "y" = 89, 78 | "z" = 90, 79 | "left window key" = 91, 80 | "right window key" =92, 81 | "select key" = 93, 82 | "numpad 0" = 96, 83 | "numpad 1" = 97, 84 | "numpad 2" = 98, 85 | "numpad 3" = 99, 86 | "numpad 4" = 100, 87 | "numpad 5" = 101, 88 | "numpad 6" = 102, 89 | "numpad 9" = 105, 90 | "numpad 7" = 103, 91 | "numpad 8" = 104, 92 | "multiply" = 106, 93 | "add" = 107, 94 | "subtract" = 109, 95 | "decimal point" = 110, 96 | "divide" = 111, 97 | "f1" = 112, 98 | "f2" = 113, 99 | "f3" = 114, 100 | "f4" = 115, 101 | "f5" = 116, 102 | "f6" = 117, 103 | "f7" = 118, 104 | "f8" = 119, 105 | "f9" = 120, 106 | "f10" = 121, 107 | "f11" = 122, 108 | "f12" = 123, 109 | "num lock" = 144, 110 | "scroll lock" = 145, 111 | "semi-colon" = 186, 112 | "equal sign" = 187, 113 | "comma" = 188, 114 | "dash" = 189, 115 | "period" = 190, 116 | "forward slash" = 191, 117 | "grave accent" = 192, 118 | "open bracket" = 219, 119 | "back slash" = 220, 120 | "close braket" = 221, 121 | "single quote" = 222 122 | ) 123 | 124 | # if no input return all codes 125 | if(is.null(key) & is.null(code)) { 126 | return(dictionary) 127 | } 128 | 129 | # if key is specified return the codes 130 | if(!is.null(key) & is.null(code)) { 131 | if(!is.character(key)) { 132 | stop("`key` must be a character vector", call. = FALSE) 133 | } 134 | return(unname(dictionary[key])) 135 | } 136 | 137 | # if code is specified return the keys 138 | if(is.null(key) & !is.null(code)) { 139 | if(!is.numeric(code)) { 140 | stop("`code` must be a numeric vector", call. = FALSE) 141 | } 142 | return(names(dictionary[match(code, dictionary)])) 143 | } 144 | 145 | # if both are specified return error 146 | stop("Input should use `key` or `code` arguments, not both", call. = FALSE) 147 | 148 | } 149 | 150 | -------------------------------------------------------------------------------- /R/pavlovia.R: -------------------------------------------------------------------------------- 1 | 2 | #' Communication with pavlovia.org 3 | #' 4 | #' @description The \code{pavlovia} plugin supports running experiments online 5 | #' in Pavlovia. 6 | #' 7 | #' @param command The pavlovia command: "init" (default) or "finish". 8 | #' @param participantId The participant Id: any string (NULL by default). 9 | #' @param errorCallback The callback function called whenever an error occurs 10 | #' (NULL by default). 11 | #' 12 | #' @export 13 | pavlovia <- function( 14 | command = js_string("init"), 15 | participantId = NULL, 16 | errorCallback = NULL 17 | ) { 18 | drop_nulls( 19 | trial( 20 | type = "pavlovia", 21 | command = js_string(command), 22 | participantId = participantId, 23 | errorCallback = errorCallback 24 | ) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /R/randomisation.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #jsPsych.randomization.sampleWithReplacement(array, sampleSize, weights) 4 | #jsPsych.randomization.sampleWithoutReplacement(array, sampleSize) 5 | 6 | 7 | #' Return a javascript function that samples from an array 8 | #' 9 | #' @param x A vector specifying the possible values 10 | #' @param size The number of values to sample 11 | #' @param replace Sample with replacement? (default = FALSE) 12 | #' @param weights Probability of sampling each item (ignored if replace = FALSE) 13 | #' 14 | #' @return Returns a javascript function that samples from an array of values 15 | #' @details The \code{fn_sample()} is used to return a function that, when called 16 | #' from within a jsPsych experiment, will mirror the behaviour of the \code{sample()} 17 | #' function from the base package using th jsPsych randomisation functions to 18 | #' at runtime. The input argument \code{x} specifies the set of values from which 19 | #' samples should be drawn, and the \code{size} argument specifies the number of 20 | #' samples to be drawn. When \code{replace = TRUE} items are sampled with 21 | #' replacement, and when \code{replace = FALSE} items are sampled without 22 | #' replacement. When sampling with replacement, the \code{weights} argument can 23 | #' be used to specify unequal sampling probabilities. 24 | #' 25 | #' The current implementation is limited. It does not work when \code{x} is a 26 | #' character vector, for example. Note also that the value returned within 27 | #' the jsPsych experiment is always an array (not a scalar), even when 28 | #' \code{size = 1}. 29 | #' 30 | #' @export 31 | fn_sample <- function(x, size, replace = FALSE, weights = NULL) { 32 | 33 | x <- paste(x, collapse = ", ") 34 | x <- paste0("[", x, "]") 35 | 36 | # sample with replacement 37 | if(replace == TRUE) { 38 | 39 | # arguments 40 | if(is.null(weights)) { 41 | args <- paste(x, size, sep = ", ") 42 | } else { 43 | args <- paste(x, size, weights, sep = ", ") 44 | } 45 | 46 | # the jsPsych function 47 | fn <- js_code(paste0( 48 | "function() {", 49 | " return jsPsych.randomization.sampleWithReplacement(", args, ");", 50 | "}" 51 | )) 52 | 53 | # sample without replacement 54 | } else { 55 | 56 | args <- paste(x, size, sep = ", ") 57 | fn <- js_code(paste0( 58 | "function() {", 59 | " return jsPsych.randomization.sampleWithoutReplacement(", args, ");", 60 | "}" 61 | )) 62 | 63 | } 64 | 65 | return(fn) 66 | } 67 | 68 | -------------------------------------------------------------------------------- /R/save_locally.R: -------------------------------------------------------------------------------- 1 | 2 | #' Return a javascript function to save data locally 3 | #' 4 | #' @export 5 | #' @return A javascript function to save data locally 6 | #' 7 | #' @details The purpose of the \code{save_locally()} is to return a 8 | #' javascript function that, when called from within the jsPsych experiment, 9 | #' will write the data to a CSV file on the local machine (in the data folder 10 | #' associated with the experiment). The intention is that when an experiment is 11 | #' to be deployed locally (i.e., using the \code{\link{run_locally}()} function 12 | #' to run the experiment using an R server on the local machine), the 13 | #' \code{save_locally()} function provides the mechanism for saving the data. 14 | #' If the goal is simply to save the data set at the end of the experiment, the 15 | #' easiest way to do this is when building the experiment using 16 | #' \code{\link{build_experiment}()}. Specifically, the method for doing this is 17 | #' to include the argument \code{on_finish = save_locally()} as part of the 18 | #' call to \code{\link{build_experiment}()}. 19 | #' 20 | #' @seealso \code{\link{run_locally}}, \code{\link{build_experiment}} 21 | #' 22 | save_locally <- function() { 23 | js_code( 24 | "function() { 25 | var data = jsPsych.data.get().csv(); 26 | var file = 'xprmntr_local_name'; 27 | var xhr = new XMLHttpRequest(); 28 | xhr.open('POST', 'submit'); 29 | xhr.setRequestHeader('Content-Type', 'application/json'); 30 | xhr.send(JSON.stringify({filename: file, filedata: data})); 31 | }") 32 | } 33 | 34 | 35 | #' Return a javascript function to save data to Google datastore 36 | #' 37 | #' @export 38 | #' @return A javascript function to write data to the Google datastore 39 | #' 40 | #' @details The purpose of the \code{save_googlecloud()} is to return a 41 | #' javascript function that, when called from within the jsPsych experiment, 42 | #' will write the data to the Google datastore. 43 | #' The intention is that when an experiment is 44 | #' to be deployed on Google App Engine (i.e., using the \code{\link{run_googlecloud}()} 45 | #' function to deploy the experiment), the 46 | #' \code{save_googlecloud()} function provides the mechanism for saving the data. 47 | #' If the goal is simply to save the data set at the end of the experiment, the 48 | #' easiest way to do this is when building the experiment using 49 | #' \code{\link{build_experiment}()}. Specifically, the method for doing this is 50 | #' to include the argument \code{on_finish = save_googlecloud()} as part of the 51 | #' call to \code{\link{build_experiment}()}. 52 | #' 53 | #' @seealso \code{\link{run_googlecloud}}, \code{\link{build_experiment}} 54 | #' 55 | save_googlecloud <- function() { 56 | js_code( 57 | "function() { 58 | $.post('submit',{\"content\": jsPsych.data.get().csv()}) 59 | }" 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /R/save_webserver.R: -------------------------------------------------------------------------------- 1 | #' Return a javascript function to save data via a script on the webserver 2 | #' 3 | #' @export 4 | #' @return Return a javascript function to save data via a script on the webserver 5 | #' 6 | #' @details The purpose of the \code{save_webserver()} is to return a 7 | #' javascript function that, when called from within the jsPsych experiment, 8 | #' will write the data to the server. This assumes that the experiment will 9 | #' be run on a (php)script-enabled webserver. This way, you know the data will 10 | #' never touch any other computer than the server you've presumably secured 11 | #' and have data processing agreements in place for. 12 | #' 13 | #' @seealso \code{\link{run_webserver}}, \code{\link{download_webserver}} 14 | save_webserver <- function() { 15 | js_code(paste0( 16 | "function() { 17 | var url = 'resource/script/record_result.php'; 18 | var data = {filedata: jsPsych.data.get().csv()}; 19 | fetch(url, { 20 | method: 'POST', 21 | body: JSON.stringify(data), 22 | headers: new Headers({ 23 | 'Content-Type': 'application/json'})});}")) 24 | } 25 | -------------------------------------------------------------------------------- /R/trial_generic.R: -------------------------------------------------------------------------------- 1 | #' Specify a trial using any plugin 2 | #' 3 | #' @description The \code{trial_generic} function is used to create a trial with 4 | #' an arbitrary jsPsych plugin. 5 | #' 6 | #' @param type the type of trial 7 | #' @param ... arguments passed to the trial plugin 8 | #' 9 | #' @return Functions with a \code{trial_} prefix always return a "trial" object. 10 | #' A trial object is simply a list containing the input arguments, with 11 | #' \code{NULL} elements removed. Logical values in the input (\code{TRUE} and 12 | #' \code{FALSE}) are transformed to character vectors \code{"true"} and \code{"false"} 13 | #' and are specified to be objects of class "json", ensuring that they will be 14 | #' written to file as the javascript logicals, \code{true} and \code{false}. 15 | #' 16 | #' @details The \code{trial_generic()} function is the most flexible of 17 | #' all the functions within the \code{trial_} family, and can be use to 18 | #' insert a trial of any \code{type}. For example, by setting 19 | #' \code{type = "image-keyboard-response"}, it will create an image trial 20 | #' using a keyboard response, precisely analogous to trials created using 21 | #' the \code{\link{trial_image_keyboard_response}()} function. More generally 22 | #' the \code{type} value should be a string that specifies the name of the 23 | #' corresponding jsPsych plugin file: in this case, the file name for the 24 | #' plugin "jspsych-image-keyboard-response.js" so the corresponding \code{type} 25 | #' value is "image-keyboard-response". 26 | #' 27 | #' While the advantage to \code{trial_generic()} is flexibility, the disadvantage 28 | #' is that all arguments to the plugin must be specified as named arguments passed 29 | #' via \code{...}, and it can take some trial and error to get a novel plugin to 30 | #' behave in the expected fashion. For example, if a particular argument to the 31 | #' jsPsych plugin takes a logical value, it may not always be sufficient to 32 | #' use logical values \code{TRUE} or \code{FALSE} when the trial is constructed 33 | #' from within R. The reason for this is that when the R code is converted to 34 | #' javascript (using the jsonlite package), it \emph{does} correctly convert 35 | #' the R logicals \code{TRUE} and \code{FALSE} to the corresponding javascript 36 | #' logical values \code{true} and \code{false}, but by default this value is 37 | #' written to a javascript array of length one rather than recorded as a scalar 38 | #' value (i.e., the javascript code becomes \code{[true]} rather than \code{true}). 39 | #' When this occurs, jsPsych often does not produce the desired behaviour as 40 | #' these two entities are not considered equivalent in javascript. 41 | #' 42 | #' In future versions of jaysire there may be better support for arbitary 43 | #' plugins, but for the moment users should be aware that \code{trial_generic()} 44 | #' can be somewhat finicky to work with. 45 | #' 46 | #' @export 47 | trial_generic <- function(type, ...) { 48 | trial(type, ...) 49 | } 50 | 51 | 52 | # internal version of trial_generic() using dots 53 | trial <- function(type, ...) { 54 | return(list(type = type, ...)) 55 | } 56 | 57 | # internal version of trial_generic() that lifts domain to list input 58 | trial_l <- purrr::lift_dl(trial) 59 | 60 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | # Behavioural experiments with jaysire 16 | 17 | 18 | [![Travis build status](https://travis-ci.org/djnavarro/jaysire.svg?branch=master)](https://travis-ci.org/djnavarro/jaysire) 19 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 20 | [![Codecov test coverage](https://codecov.io/gh/djnavarro/jaysire/branch/master/graph/badge.svg)](https://codecov.io/gh/djnavarro/jaysire?branch=master) 21 | [![CRAN status](https://www.r-pkg.org/badges/version/jaysire)](https://cran.r-project.org/package=jaysire) 22 | 23 | 24 | The goal of jaysire is to provide a method for writing behavioural experiments in R that can be deployed through a web browser. The package relies on the [jsPsych](https://www.jspsych.org) library by Josh de Leeuw ([GitHub page](https://github.com/jspsych/jsPsych/)) to create the experiments, and is structured so that functions in jaysire use the same argument names as the corresponding jsPsych functions. For the most part, function names in jaysire are organised around families that share a common prefix. For example, the `trial_` family is used to define individual trials within an experiment, `build_` functions construct more complex entities, and so on. See the [reference page](https://djnavarro.github.io/jaysire/reference/) for the complete list of all functions. 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ## Installation 38 | 39 | The jaysire package has not been released on CRAN, but you can install it directly from GitHub using the following commands: 40 | 41 | ``` r 42 | #install.packages("remotes") 43 | remotes::install_github("djnavarro/jaysire") 44 | ``` 45 | 46 | ## Getting started 47 | 48 | There are a series of tutorial articles: 49 | 50 | 1. [Getting started](https://djnavarro.github.io/jaysire/articles/jaysire01.html) 51 | 2. [Randomisation, repetition and variables](https://djnavarro.github.io/jaysire/articles/jaysire02.html) 52 | 3. [Using resource files](https://djnavarro.github.io/jaysire/articles/jaysire03.html) 53 | 4. [Image, video and audio files](https://djnavarro.github.io/jaysire/articles/jaysire04.html) 54 | 5. [Buttons, key presses and sliders](https://djnavarro.github.io/jaysire/articles/jaysire05.html) 55 | 6. [Survey pages](https://djnavarro.github.io/jaysire/articles/jaysire06.html) 56 | 7. [Loops and branches](https://djnavarro.github.io/jaysire/articles/jaysire07.html) 57 | 8. [A choice reaction time task](https://djnavarro.github.io/jaysire/articles/jaysire08.html) 58 | 59 | 60 | ## Related packages 61 | 62 | - The [jsPsychR](https://github.com/CrumpLab/jspsychr) package by Matt Crump 63 | - The [formr](https://github.com/rubenarslan/formr) package by Ruben Arslan 64 | - The [psychTestR](https://pmcharrison.github.io/psychTestR/) package by Peter Harrison 65 | - My [xprmtnr](https://github.com/djnavarro/xprmntr) package (in development) 66 | 67 | ## Name 68 | 69 | The name "jaysire" is a phonetic transcription of "j-psy-R", reflecting the fact that it adheres closely to the design principles used in the jsPsych javascript library. 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Behavioural experiments with jaysire 5 | 6 | 7 | 8 | [![Travis build 9 | status](https://travis-ci.org/djnavarro/jaysire.svg?branch=master)](https://travis-ci.org/djnavarro/jaysire) 10 | [![Lifecycle: 11 | experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 12 | [![Codecov test 13 | coverage](https://codecov.io/gh/djnavarro/jaysire/branch/master/graph/badge.svg)](https://codecov.io/gh/djnavarro/jaysire?branch=master) 14 | [![CRAN 15 | status](https://www.r-pkg.org/badges/version/jaysire)](https://cran.r-project.org/package=jaysire) 16 | 17 | 18 | The goal of jaysire is to provide a method for writing behavioural 19 | experiments in R that can be deployed through a web browser. The package 20 | relies on the [jsPsych](https://www.jspsych.org) library by Josh de 21 | Leeuw ([GitHub page](https://github.com/jspsych/jsPsych/)) to create the 22 | experiments, and is structured so that functions in jaysire use the same 23 | argument names as the corresponding jsPsych functions. For the most 24 | part, function names in jaysire are organised around families that share 25 | a common prefix. For example, the `trial_` family is used to define 26 | individual trials within an experiment, `build_` functions construct 27 | more complex entities, and so on. See the [reference 28 | page](https://djnavarro.github.io/jaysire/reference/) for the complete 29 | list of all 30 | functions. 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ## Installation 47 | 48 | The jaysire package has not been released on CRAN, but you can install 49 | it directly from GitHub using the following commands: 50 | 51 | ``` r 52 | #install.packages("remotes") 53 | remotes::install_github("djnavarro/jaysire") 54 | ``` 55 | 56 | ## Getting started 57 | 58 | There are a series of tutorial articles: 59 | 60 | 1. [Getting 61 | started](https://djnavarro.github.io/jaysire/articles/jaysire01.html) 62 | 2. [Randomisation, repetition and 63 | variables](https://djnavarro.github.io/jaysire/articles/jaysire02.html) 64 | 3. [Using resource 65 | files](https://djnavarro.github.io/jaysire/articles/jaysire03.html) 66 | 4. [Image, video and audio 67 | files](https://djnavarro.github.io/jaysire/articles/jaysire04.html) 68 | 5. [Buttons, key presses and 69 | sliders](https://djnavarro.github.io/jaysire/articles/jaysire05.html) 70 | 6. [Survey 71 | pages](https://djnavarro.github.io/jaysire/articles/jaysire06.html) 72 | 7. [Loops and 73 | branches](https://djnavarro.github.io/jaysire/articles/jaysire07.html) 74 | 8. [A choice reaction time 75 | task](https://djnavarro.github.io/jaysire/articles/jaysire08.html) 76 | 77 | ## Related packages 78 | 79 | - The [jsPsychR](https://github.com/CrumpLab/jspsychr) package by Matt 80 | Crump 81 | - The [formr](https://github.com/rubenarslan/formr) package by Ruben 82 | Arslan 83 | - The [psychTestR](https://pmcharrison.github.io/psychTestR/) package 84 | by Peter Harrison 85 | - My [xprmtnr](https://github.com/djnavarro/xprmntr) package (in 86 | development) 87 | 88 | ## Name 89 | 90 | The name “jaysire” is a phonetic transcription of “j-psy-R”, reflecting 91 | the fact that it adheres closely to the design principles used in the 92 | jsPsych javascript library. 93 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | authors: 2 | Danielle Navarro: 3 | href: https://compcogscisydney.org 4 | template: 5 | params: 6 | bootswatch: flatly 7 | navbar: 8 | structure: 9 | left: 10 | - home 11 | - intro 12 | - reference 13 | - articles 14 | - gallery 15 | - tutorials 16 | - news 17 | right: github 18 | components: 19 | home: 20 | icon: fas fa-home fa-lg 21 | href: index.html 22 | reference: 23 | text: Reference 24 | href: reference/index.html 25 | articles: 26 | text: Articles 27 | menu: 28 | - text: 1. Getting started 29 | href: articles/jaysire01.html 30 | - text: 2. Randomisation, repetition and variables 31 | href: articles/jaysire02.html 32 | - text: 3. Using resource files 33 | href: articles/jaysire03.html 34 | - text: 4. Image, video and audio files 35 | href: articles/jaysire04.html 36 | - text: 5. Buttons, key presses and sliders 37 | href: articles/jaysire05.html 38 | - text: 6. Survey pages 39 | href: articles/jaysire06.html 40 | - text: 7. Loops and branches 41 | href: articles/jaysire07.html 42 | - text: 8. A choice RT task 43 | href: articles/jaysire08.html 44 | github: 45 | icon: fab fa-github fa-lg 46 | href: https://github.com/djnavarro/jaysire 47 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | jaysire.djnavarro.net -------------------------------------------------------------------------------- /docs/articles/jaysire01_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire02_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire03_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire04_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire05_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire06_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire07_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/jaysire08_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/demos/example01/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "timeline": [ 5 | { 6 | "type": ["instructions"], 7 | "pages": ["Welcome! Use the arrow buttons to browse these instructions", "Your task is to decide if an equation like '2 + 2 = 4' is true or false", "You will respond by clicking a button", "Press the 'Next' button to begin!"], 8 | "key_forward": [39], 9 | "key_backward": [37], 10 | "allow_backward": true, 11 | "allow_keys": true, 12 | "show_clickable_nav": true, 13 | "button_label_previous": ["Previous"], 14 | "button_label_next": ["Next"], 15 | "post_trial_gap": [1000] 16 | }, 17 | { 18 | "type": ["html-button-response"], 19 | "stimulus": ["13 + 23 = 36"], 20 | "choices": ["true", "false"], 21 | "margin_vertical": ["0px"], 22 | "margin_horizontal": ["8px"], 23 | "response_ends_trial": true, 24 | "post_trial_gap": [1000] 25 | }, 26 | { 27 | "type": ["html-button-response"], 28 | "stimulus": ["17 - 9 = 6"], 29 | "choices": ["true", "false"], 30 | "margin_vertical": ["0px"], 31 | "margin_horizontal": ["8px"], 32 | "response_ends_trial": true, 33 | "post_trial_gap": [1000] 34 | } 35 | ] 36 | }, 37 | { 38 | "type": ["html-keyboard-response"], 39 | "stimulus": ["All done! Click here<\/a> to return to the vignette."], 40 | "choices": jsPsych.NO_KEY, 41 | "response_ends_trial": true, 42 | "post_trial_gap": [0] 43 | } 44 | ] 45 | }; 46 | 47 | jsPsych.init( 48 | { 49 | "timeline": [timeline] 50 | } 51 | ); 52 | -------------------------------------------------------------------------------- /docs/demos/example01/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/demos/example01/experiment/resource/script/jspsych-html-keyboard-response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jspsych-html-keyboard-response 3 | * Josh de Leeuw 4 | * 5 | * plugin for displaying a stimulus and getting a keyboard response 6 | * 7 | * documentation: docs.jspsych.org 8 | * 9 | **/ 10 | 11 | 12 | jsPsych.plugins["html-keyboard-response"] = (function() { 13 | 14 | var plugin = {}; 15 | 16 | plugin.info = { 17 | name: 'html-keyboard-response', 18 | description: '', 19 | parameters: { 20 | stimulus: { 21 | type: jsPsych.plugins.parameterType.HTML_STRING, 22 | pretty_name: 'Stimulus', 23 | default: undefined, 24 | description: 'The HTML string to be displayed' 25 | }, 26 | choices: { 27 | type: jsPsych.plugins.parameterType.KEYCODE, 28 | array: true, 29 | pretty_name: 'Choices', 30 | default: jsPsych.ALL_KEYS, 31 | description: 'The keys the subject is allowed to press to respond to the stimulus.' 32 | }, 33 | prompt: { 34 | type: jsPsych.plugins.parameterType.STRING, 35 | pretty_name: 'Prompt', 36 | default: null, 37 | description: 'Any content here will be displayed below the stimulus.' 38 | }, 39 | stimulus_duration: { 40 | type: jsPsych.plugins.parameterType.INT, 41 | pretty_name: 'Stimulus duration', 42 | default: null, 43 | description: 'How long to hide the stimulus.' 44 | }, 45 | trial_duration: { 46 | type: jsPsych.plugins.parameterType.INT, 47 | pretty_name: 'Trial duration', 48 | default: null, 49 | description: 'How long to show trial before it ends.' 50 | }, 51 | response_ends_trial: { 52 | type: jsPsych.plugins.parameterType.BOOL, 53 | pretty_name: 'Response ends trial', 54 | default: true, 55 | description: 'If true, trial will end when subject makes a response.' 56 | }, 57 | 58 | } 59 | } 60 | 61 | plugin.trial = function(display_element, trial) { 62 | 63 | var new_html = '
'+trial.stimulus+'
'; 64 | 65 | // add prompt 66 | if(trial.prompt !== null){ 67 | new_html += trial.prompt; 68 | } 69 | 70 | // draw 71 | display_element.innerHTML = new_html; 72 | 73 | // store response 74 | var response = { 75 | rt: null, 76 | key: null 77 | }; 78 | 79 | // function to end trial when it is time 80 | var end_trial = function() { 81 | 82 | // kill any remaining setTimeout handlers 83 | jsPsych.pluginAPI.clearAllTimeouts(); 84 | 85 | // kill keyboard listeners 86 | if (typeof keyboardListener !== 'undefined') { 87 | jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener); 88 | } 89 | 90 | // gather the data to store for the trial 91 | var trial_data = { 92 | "rt": response.rt, 93 | "stimulus": trial.stimulus, 94 | "key_press": response.key 95 | }; 96 | 97 | // clear the display 98 | display_element.innerHTML = ''; 99 | 100 | // move on to the next trial 101 | jsPsych.finishTrial(trial_data); 102 | }; 103 | 104 | // function to handle responses by the subject 105 | var after_response = function(info) { 106 | 107 | // after a valid response, the stimulus will have the CSS class 'responded' 108 | // which can be used to provide visual feedback that a response was recorded 109 | display_element.querySelector('#jspsych-html-keyboard-response-stimulus').className += ' responded'; 110 | 111 | // only record the first response 112 | if (response.key == null) { 113 | response = info; 114 | } 115 | 116 | if (trial.response_ends_trial) { 117 | end_trial(); 118 | } 119 | }; 120 | 121 | // start the response listener 122 | if (trial.choices != jsPsych.NO_KEYS) { 123 | var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({ 124 | callback_function: after_response, 125 | valid_responses: trial.choices, 126 | rt_method: 'performance', 127 | persist: false, 128 | allow_held_key: false 129 | }); 130 | } 131 | 132 | // hide stimulus if stimulus_duration is set 133 | if (trial.stimulus_duration !== null) { 134 | jsPsych.pluginAPI.setTimeout(function() { 135 | display_element.querySelector('#jspsych-html-keyboard-response-stimulus').style.visibility = 'hidden'; 136 | }, trial.stimulus_duration); 137 | } 138 | 139 | // end trial if trial_duration is set 140 | if (trial.trial_duration !== null) { 141 | jsPsych.pluginAPI.setTimeout(function() { 142 | end_trial(); 143 | }, trial.trial_duration); 144 | } 145 | 146 | }; 147 | 148 | return plugin; 149 | })(); 150 | -------------------------------------------------------------------------------- /docs/demos/example01/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example01/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example02/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "type": ["instructions"], 5 | "pages": ["Welcome! Use the arrow buttons to browse these instructions", "Your task is to decide if an equation like '2 + 2 = 4' is true or false", "You will respond by clicking a button", "Press the 'Next' button to begin!"], 6 | "key_forward": [39], 7 | "key_backward": [37], 8 | "allow_backward": true, 9 | "allow_keys": true, 10 | "show_clickable_nav": true, 11 | "button_label_previous": ["Previous"], 12 | "button_label_next": ["Next"], 13 | "post_trial_gap": [1000] 14 | }, 15 | { 16 | "timeline": [ 17 | { 18 | "type": ["html-button-response"], 19 | "stimulus": jsPsych.timelineVariable('stimulus'), 20 | "choices": ["true", "false"], 21 | "margin_vertical": ["0px"], 22 | "margin_horizontal": ["8px"], 23 | "response_ends_trial": true, 24 | "post_trial_gap": [1000] 25 | } 26 | ], 27 | "timeline_variables": [ 28 | { 29 | "stimulus": ["13 + 23 = 36"] 30 | }, 31 | { 32 | "stimulus": ["17 - 9 = 6"] 33 | }, 34 | { 35 | "stimulus": ["125 / 5 = 25"] 36 | }, 37 | { 38 | "stimulus": ["2 - 4 = 6"] 39 | }, 40 | { 41 | "stimulus": ["12 + 39 = 43"] 42 | }, 43 | { 44 | "stimulus": ["4 * 23 = 92"] 45 | } 46 | ], 47 | "randomize_order": true, 48 | "repetitions": [2] 49 | }, 50 | { 51 | "type": ["html-keyboard-response"], 52 | "stimulus": ["All done! Click
here<\/a> to return to the vignette."], 53 | "choices": jsPsych.NO_KEY, 54 | "response_ends_trial": true, 55 | "post_trial_gap": [0] 56 | } 57 | ] 58 | }; 59 | 60 | jsPsych.init( 61 | { 62 | "timeline": [timeline] 63 | } 64 | ); 65 | -------------------------------------------------------------------------------- /docs/demos/example02/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/demos/example02/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example02/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example03/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "timeline": [ 5 | { 6 | "type": ["image-button-response"], 7 | "stimulus": jsPsych.timelineVariable('my_stimulus'), 8 | "stimulus_height": [400], 9 | "stimulus_width": [400], 10 | "maintain_aspect_ratio": true, 11 | "choices": ["there are more red dots", "there are more blue dots"], 12 | "margin_vertical": ["0px"], 13 | "margin_horizontal": ["8px"], 14 | "response_ends_trial": true, 15 | "post_trial_gap": [1000] 16 | } 17 | ], 18 | "timeline_variables": [ 19 | { 20 | "my_stimulus": ["resource/image/stimulus1.png"] 21 | }, 22 | { 23 | "my_stimulus": ["resource/image/stimulus2.png"] 24 | }, 25 | { 26 | "my_stimulus": ["resource/image/stimulus3.png"] 27 | }, 28 | { 29 | "my_stimulus": ["resource/image/stimulus4.png"] 30 | }, 31 | { 32 | "my_stimulus": ["resource/image/stimulus5.png"] 33 | }, 34 | { 35 | "my_stimulus": ["resource/image/stimulus6.png"] 36 | }, 37 | { 38 | "my_stimulus": ["resource/image/stimulus7.png"] 39 | }, 40 | { 41 | "my_stimulus": ["resource/image/stimulus8.png"] 42 | } 43 | ], 44 | "randomize_order": true 45 | }, 46 | { 47 | "type": ["html-keyboard-response"], 48 | "stimulus": ["All done! Click here<\/a> to return to the vignette."], 49 | "choices": jsPsych.NO_KEY, 50 | "response_ends_trial": true, 51 | "post_trial_gap": [0] 52 | } 53 | ] 54 | }; 55 | 56 | jsPsych.init( 57 | { 58 | "timeline": [timeline] 59 | } 60 | ); 61 | -------------------------------------------------------------------------------- /docs/demos/example03/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus1.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus2.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus3.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus4.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus5.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus6.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus7.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/image/stimulus8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example03/experiment/resource/image/stimulus8.png -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example03/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example04/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "type": ["image-button-response"], 5 | "stimulus": ["resource/image/heart.png"], 6 | "stimulus_height": [400], 7 | "stimulus_width": [400], 8 | "maintain_aspect_ratio": true, 9 | "choices": ["Unpleasant", "Neutral", "Pleasant"], 10 | "margin_vertical": ["0px"], 11 | "margin_horizontal": ["8px"], 12 | "response_ends_trial": true, 13 | "post_trial_gap": [0] 14 | }, 15 | { 16 | "type": ["video-button-response"], 17 | "sources": ["resource/video/heart.mpg", "resource/video/heart.webm"], 18 | "trial_ends_after_video": false, 19 | "autoplay": true, 20 | "controls": false, 21 | "rate": [1], 22 | "choices": ["Unpleasant", "Neutral", "Pleasant"], 23 | "margin_vertical": ["0px"], 24 | "margin_horizontal": ["8px"], 25 | "response_ends_trial": true, 26 | "post_trial_gap": [0] 27 | }, 28 | { 29 | "type": ["audio-button-response"], 30 | "stimulus": ["resource/audio/lukewarm_banjo.mp3"], 31 | "choices": ["Unpleasant", "Neutral", "Pleasant"], 32 | "margin_vertical": ["0px"], 33 | "margin_horizontal": ["8px"], 34 | "trial_ends_after_audio": false, 35 | "response_ends_trial": true, 36 | "post_trial_gap": [0] 37 | }, 38 | { 39 | "type": ["html-keyboard-response"], 40 | "stimulus": ["All done! Click here<\/a> to return to the vignette."], 41 | "choices": jsPsych.NO_KEY, 42 | "response_ends_trial": true, 43 | "post_trial_gap": [0] 44 | } 45 | ] 46 | }; 47 | 48 | jsPsych.init( 49 | { 50 | "timeline": [timeline], 51 | "use_webaudio": [false] 52 | } 53 | ); 54 | -------------------------------------------------------------------------------- /docs/demos/example04/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example04/experiment/resource/audio/lukewarm_banjo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example04/experiment/resource/audio/lukewarm_banjo.mp3 -------------------------------------------------------------------------------- /docs/demos/example04/experiment/resource/image/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example04/experiment/resource/image/heart.png -------------------------------------------------------------------------------- /docs/demos/example04/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example04/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example04/experiment/resource/video/heart.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example04/experiment/resource/video/heart.mpg -------------------------------------------------------------------------------- /docs/demos/example04/experiment/resource/video/heart.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example04/experiment/resource/video/heart.webm -------------------------------------------------------------------------------- /docs/demos/example05/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "type": ["image-keyboard-response"], 5 | "stimulus": ["resource/image/heart.png"], 6 | "stimulus_height": [400], 7 | "stimulus_width": [400], 8 | "maintain_aspect_ratio": true, 9 | "choices": jsPsych.ANY_KEY, 10 | "prompt": ["
You will be asked judge the pleasantness of this image. Press any key to continue"], 11 | "response_ends_trial": true, 12 | "post_trial_gap": [0] 13 | }, 14 | { 15 | "timeline": [ 16 | { 17 | "type": ["image-button-response"], 18 | "stimulus": ["resource/image/heart.png"], 19 | "stimulus_height": [400], 20 | "stimulus_width": [400], 21 | "maintain_aspect_ratio": true, 22 | "choices": ["Unpleasant", "Neutral", "Pleasant"], 23 | "margin_vertical": ["0px"], 24 | "margin_horizontal": ["8px"], 25 | "response_ends_trial": true, 26 | "post_trial_gap": [0] 27 | }, 28 | { 29 | "type": ["image-slider-response"], 30 | "stimulus": ["resource/image/heart.png"], 31 | "stimulus_height": [400], 32 | "stimulus_width": [400], 33 | "maintain_aspect_ratio": true, 34 | "labels": ["Most unpleasant", "Neutral", "Most Pleasant"], 35 | "button_label": ["Continue"], 36 | "min": [0], 37 | "max": [100], 38 | "start": [50], 39 | "step": [1], 40 | "require_movement": false, 41 | "response_ends_trial": true, 42 | "post_trial_gap": [0] 43 | } 44 | ], 45 | "randomize_order": true 46 | }, 47 | { 48 | "type": ["html-keyboard-response"], 49 | "stimulus": ["All done! Click
here<\/a> to return to the vignette."], 50 | "choices": jsPsych.NO_KEY, 51 | "response_ends_trial": true, 52 | "post_trial_gap": [0] 53 | } 54 | ] 55 | }; 56 | 57 | jsPsych.init( 58 | { 59 | "timeline": [timeline] 60 | } 61 | ); 62 | -------------------------------------------------------------------------------- /docs/demos/example05/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example05/experiment/resource/audio/lukewarm_banjo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example05/experiment/resource/audio/lukewarm_banjo.mp3 -------------------------------------------------------------------------------- /docs/demos/example05/experiment/resource/image/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example05/experiment/resource/image/heart.png -------------------------------------------------------------------------------- /docs/demos/example05/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example05/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example05/experiment/resource/video/heart.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example05/experiment/resource/video/heart.mpg -------------------------------------------------------------------------------- /docs/demos/example05/experiment/resource/video/heart.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example05/experiment/resource/video/heart.webm -------------------------------------------------------------------------------- /docs/demos/example06/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "type": ["survey-multi-choice"], 5 | "questions": [ 6 | { 7 | "prompt": ["Please select the option that best matches your gender"], 8 | "options": ["Male", "Female", "Nonbinary", "Other", "Prefer not to say"], 9 | "horizontal": false, 10 | "required": false, 11 | "name": ["gender"] 12 | }, 13 | { 14 | "prompt": ["Do you consider yourself to be LGBTIQ+?"], 15 | "options": ["Yes", "No", "Unsure", "Prefer not to say"], 16 | "horizontal": false, 17 | "required": false 18 | } 19 | ], 20 | "randomize_question_order": false, 21 | "preamble": ["Welcome! We'd like to ask some demographic questions"], 22 | "button_label": ["Continue"], 23 | "required_message": ["You must choose at least one response for this question"], 24 | "post_trial_gap": [0] 25 | }, 26 | { 27 | "type": ["survey-multi-select"], 28 | "questions": [ 29 | { 30 | "prompt": ["Which of the following R packages to you use?"], 31 | "options": ["ggplot2", "dplyr", "purrr", "janitor", "data.table", "testthat", "usethis", "tibble", "magrittr", "rlang", "babynames", "janeaustenr"], 32 | "horizontal": false, 33 | "required": false 34 | } 35 | ], 36 | "randomize_question_order": false, 37 | "preamble": [""], 38 | "button_label": ["Continue"], 39 | "required_message": ["You must choose at least one response for this question"], 40 | "post_trial_gap": [0] 41 | }, 42 | { 43 | "type": ["survey-likert"], 44 | "questions": [ 45 | { 46 | "prompt": ["Data wrangling?"], 47 | "labels": ["Very unconfident", "Somewhat unconfident", "Somewhat confident", "Very confident"], 48 | "required": [false] 49 | }, 50 | { 51 | "prompt": ["Data visualisation?"], 52 | "labels": ["Very unconfident", "Somewhat unconfident", "Somewhat confident", "Very confident"], 53 | "required": [false] 54 | }, 55 | { 56 | "prompt": ["Statistical modelling?"], 57 | "labels": ["Very unconfident", "Somewhat unconfident", "Somewhat confident", "Very confident"], 58 | "required": [false] 59 | }, 60 | { 61 | "prompt": ["Designing experiments?"], 62 | "labels": ["Very unconfident", "Somewhat unconfident", "Somewhat confident", "Very confident"], 63 | "required": [false] 64 | }, 65 | { 66 | "prompt": ["R markdown documents?"], 67 | "labels": ["Very unconfident", "Somewhat unconfident", "Somewhat confident", "Very confident"], 68 | "required": [false] 69 | } 70 | ], 71 | "randomize_question_order": false, 72 | "preamble": ["How confident in you R skills?"], 73 | "button_label": ["Continue"], 74 | "post_trial_gap": [0] 75 | }, 76 | { 77 | "type": ["survey-text"], 78 | "questions": [ 79 | { 80 | "prompt": ["Anything else you would like to mention?"], 81 | "placeholder": ["Type your answer here"], 82 | "rows": [8], 83 | "columns": [60], 84 | "required": [false] 85 | } 86 | ], 87 | "randomize_question_order": false, 88 | "preamble": [""], 89 | "button_label": ["Continue"], 90 | "post_trial_gap": [0] 91 | }, 92 | { 93 | "type": ["html-keyboard-response"], 94 | "stimulus": ["All done! Click here<\/a> to return to the vignette."], 95 | "choices": jsPsych.NO_KEY, 96 | "response_ends_trial": true, 97 | "post_trial_gap": [0] 98 | } 99 | ] 100 | }; 101 | 102 | jsPsych.init( 103 | { 104 | "timeline": [timeline] 105 | } 106 | ); 107 | -------------------------------------------------------------------------------- /docs/demos/example06/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/demos/example06/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example06/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example07/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | var timeline = { 2 | "timeline": [ 3 | { 4 | "type": ["html-button-response"], 5 | "stimulus": ["Do you identify as LGBTIQ+?"], 6 | "choices": ["Yes", "No", "Prefer not to say"], 7 | "margin_vertical": ["0px"], 8 | "margin_horizontal": ["8px"], 9 | "response_ends_trial": true, 10 | "post_trial_gap": [0] 11 | }, 12 | { 13 | "timeline": [ 14 | { 15 | "type": ["survey-multi-select"], 16 | "questions": [ 17 | { 18 | "prompt": ["Select all that apply"], 19 | "options": ["Lesbian", "Gay", "Bisexual/Pansexual", "Transgender", "Nonbinary", "Genderqueer", "Intersex", "Asexual", "Other"], 20 | "horizontal": false, 21 | "required": false 22 | } 23 | ], 24 | "randomize_question_order": false, 25 | "preamble": [""], 26 | "button_label": ["Continue"], 27 | "required_message": ["You must choose at least one response for this question"], 28 | "post_trial_gap": [0] 29 | } 30 | ], 31 | "conditional_function": function(){ 32 | var data = jsPsych.data.get().last(1).values()[0]; 33 | if(data.button_pressed == "0"){ 34 | return true; 35 | } else { 36 | return false; 37 | } 38 | } 39 | }, 40 | { 41 | "timeline": [ 42 | { 43 | "type": ["image-button-response"], 44 | "stimulus": ["resource/image/heart.png"], 45 | "stimulus_height": [400], 46 | "stimulus_width": [400], 47 | "maintain_aspect_ratio": true, 48 | "choices": ["Unpleasant", "Neutral", "Pleasant"], 49 | "margin_vertical": ["0px"], 50 | "margin_horizontal": ["8px"], 51 | "prompt": ["You will not be allowed to continue unless you select 'Pleasant'"], 52 | "response_ends_trial": true, 53 | "post_trial_gap": [0] 54 | } 55 | ], 56 | "loop_function": function(){ 57 | var data = jsPsych.data.get().last(1).values()[0]; 58 | if(data.button_pressed != "2"){ 59 | return true; 60 | } else { 61 | return false; 62 | } 63 | } 64 | }, 65 | { 66 | "type": ["html-keyboard-response"], 67 | "stimulus": ["All done! Click here<\/a> to return to the vignette."], 68 | "choices": jsPsych.NO_KEY, 69 | "response_ends_trial": true, 70 | "post_trial_gap": [0] 71 | } 72 | ] 73 | }; 74 | 75 | jsPsych.init( 76 | { 77 | "timeline": [timeline] 78 | } 79 | ); 80 | -------------------------------------------------------------------------------- /docs/demos/example07/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example07/experiment/resource/audio/lukewarm_banjo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example07/experiment/resource/audio/lukewarm_banjo.mp3 -------------------------------------------------------------------------------- /docs/demos/example07/experiment/resource/image/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example07/experiment/resource/image/heart.png -------------------------------------------------------------------------------- /docs/demos/example07/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example07/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/demos/example07/experiment/resource/video/heart.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example07/experiment/resource/video/heart.mpg -------------------------------------------------------------------------------- /docs/demos/example07/experiment/resource/video/heart.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example07/experiment/resource/video/heart.webm -------------------------------------------------------------------------------- /docs/demos/example08/experiment/experiment.js: -------------------------------------------------------------------------------- 1 | jsPsych.data.addProperties({ 2 | "experiment": ["choice_rt"] 3 | }); 4 | 5 | var timeline = { 6 | "timeline": [ 7 | { 8 | "type": ["html-keyboard-response"], 9 | "stimulus": ["Welcome to the experiment! Press any key to begin"], 10 | "choices": jsPsych.ANY_KEY, 11 | "response_ends_trial": true, 12 | "post_trial_gap": [0], 13 | "data": { 14 | "stage": ["start"] 15 | } 16 | }, 17 | { 18 | "type": ["instructions"], 19 | "pages": ["To navigate these instructions, use the arrow keys on your keyboard. The right arrow will move you forward one page, and the left arrow will move you back one page. Press the right arrow key to continue.", "In this experiment, a circle will appear in the centre of the screen. If the circle is blue<\/b>, press the letter F on the keyboard as fast as you can. If the circle is orange<\/b>, press the letter J as fast asyou can.
", "If you see this blue circle, you should press F.
", "If you see this orange circle, you should press J.
", "When you are ready to begin, press the right arrow key."], 20 | "key_forward": [39], 21 | "key_backward": [37], 22 | "allow_backward": true, 23 | "allow_keys": true, 24 | "show_clickable_nav": false, 25 | "button_label_previous": ["Previous"], 26 | "button_label_next": ["Next"], 27 | "post_trial_gap": [2000], 28 | "data": { 29 | "stage": ["instruction"] 30 | } 31 | }, 32 | { 33 | "timeline": [ 34 | { 35 | "type": ["html-keyboard-response"], 36 | "stimulus": ["
+<\/div>"], 37 | "choices": jsPsych.NO_KEY, 38 | "trial_duration": function() { return jsPsych.randomization.sampleWithoutReplacement([250, 500, 750, 1000, 1250, 1500, 1750, 2000], 1);}, 39 | "response_ends_trial": true, 40 | "post_trial_gap": [0], 41 | "data": { 42 | "stage": ["fixation"] 43 | } 44 | }, 45 | { 46 | "type": ["image-keyboard-response"], 47 | "stimulus": jsPsych.timelineVariable('circle'), 48 | "stimulus_height": [300], 49 | "stimulus_width": [300], 50 | "maintain_aspect_ratio": true, 51 | "choices": ["f", "j"], 52 | "response_ends_trial": true, 53 | "post_trial_gap": [0], 54 | "data": { 55 | "stage": ["choice"], 56 | "colour": jsPsych.timelineVariable('colour'), 57 | "correct_key": jsPsych.timelineVariable('correct_key') 58 | } 59 | } 60 | ], 61 | "timeline_variables": [ 62 | { 63 | "circle": ["resource/image/orange.png"], 64 | "colour": ["orange"], 65 | "correct_key": [70] 66 | }, 67 | { 68 | "circle": ["resource/image/blue.png"], 69 | "colour": ["blue"], 70 | "correct_key": [74] 71 | } 72 | ], 73 | "repetitions": [5], 74 | "randomize_order": true 75 | }, 76 | { 77 | "type": ["html-keyboard-response"], 78 | "stimulus": ["All done! Click here<\/a> to return to the vignette."], 79 | "choices": jsPsych.NO_KEY, 80 | "response_ends_trial": true, 81 | "post_trial_gap": [0] 82 | } 83 | ] 84 | }; 85 | 86 | jsPsych.init( 87 | { 88 | "timeline": [timeline] 89 | } 90 | ); 91 | -------------------------------------------------------------------------------- /docs/demos/example08/experiment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/demos/example08/experiment/resource/image/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example08/experiment/resource/image/blue.png -------------------------------------------------------------------------------- /docs/demos/example08/experiment/resource/image/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/demos/example08/experiment/resource/image/orange.png -------------------------------------------------------------------------------- /docs/demos/example08/experiment/resource/script/xprmntr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * xprmntr.js 3 | * Danielle Navarro 4 | * 5 | **/ 6 | 7 | // bundle everything into the xprmntr object 8 | var xprmntr = {}; 9 | 10 | // for use in the jsPsych.init() call 11 | xprmntr.save_locally = function() { 12 | var data = jsPsych.data.get().csv(); 13 | var file = "xprmntr_local_name"; 14 | var xhr = new XMLHttpRequest(); 15 | xhr.open('POST', 'submit'); 16 | xhr.setRequestHeader('Content-Type', 'application/json'); 17 | xhr.send(JSON.stringify({filename: file, filedata: data})); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /docs/demos/example08/experiment/resource/style/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $(".examples, div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent; 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.7.3 2 | pkgdown: 1.6.1.9001 3 | pkgdown_sha: 58807eede312d522d04834d8efebe784a8b7229a 4 | articles: 5 | jaysire01: jaysire01.html 6 | jaysire02: jaysire02.html 7 | jaysire03: jaysire03.html 8 | jaysire04: jaysire04.html 9 | jaysire05: jaysire05.html 10 | jaysire06: jaysire06.html 11 | jaysire07: jaysire07.html 12 | jaysire08: jaysire08.html 13 | last_built: 2021-04-07T11:13Z 14 | 15 | -------------------------------------------------------------------------------- /docs/reference/Rplot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/docs/reference/Rplot001.png -------------------------------------------------------------------------------- /inst/extdata/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: python27 2 | api_version: 1 3 | threadsafe: true 4 | 5 | handlers: 6 | - url: /resource 7 | static_dir: resource 8 | 9 | - url: /experiment\.js 10 | static_files: experiment.js 11 | upload: experiment\.js 12 | 13 | - url: /.* 14 | script: backend.application 15 | 16 | libraries: 17 | - name: webapp2 18 | version: latest 19 | - name: jinja2 20 | version: latest 21 | 22 | builtins: 23 | - remote_api: on 24 | -------------------------------------------------------------------------------- /inst/extdata/backend.py: -------------------------------------------------------------------------------- 1 | import os 2 | import urllib 3 | 4 | from google.appengine.api import users 5 | from google.appengine.ext import ndb 6 | 7 | import jinja2 8 | import webapp2 9 | import csv 10 | import logging 11 | import re 12 | import threading 13 | 14 | JINJA_ENVIRONMENT = jinja2.Environment( 15 | loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), 16 | extensions=['jinja2.ext.autoescape']) 17 | 18 | import cgi 19 | import urllib 20 | 21 | #from google.appengine.api import users 22 | from google.appengine.ext import ndb 23 | 24 | import webapp2 25 | 26 | class DataObject(ndb.Model): 27 | """Models an individual Guestbook entry with author, content, and date.""" 28 | exp = ndb.StringProperty(required=True) 29 | content = ndb.TextProperty(required=True) # defaults to non-indexed 30 | date = ndb.DateTimeProperty(auto_now_add=True, indexed=False) 31 | 32 | # 33 | # This class represents an Entity (row) in the Google App Engine 34 | # database. 35 | # 36 | class UserData(ndb.Expando): 37 | date = ndb.DateTimeProperty(auto_now_add=True, indexed=False) 38 | 39 | @classmethod 40 | def _get_kind(cls): 41 | return "DataObject" 42 | 43 | 44 | class MainPage(webapp2.RequestHandler): 45 | 46 | def get(self): 47 | template = JINJA_ENVIRONMENT.get_template('index.html') 48 | self.response.write(template.render()) 49 | 50 | # 51 | # This request (when enabled) will return a CSV file 52 | # containing all result rows for the given experiment. 53 | # To disable this functionality, simply comment out the 54 | # appropriate line in the Web Application configuration 55 | # at the bottom of this file. 56 | # 57 | class LoadResults(webapp2.RequestHandler): 58 | 59 | def process(self): 60 | self.response.headers['Content-Type'] = 'text/csv' 61 | self.response.headers['Content-Disposition'] = 'inline;filename=results.csv' 62 | 63 | property_names = set() 64 | data = UserData.query().fetch() 65 | 66 | for u in data: 67 | logging.info("found uid: " + str(u.key.id())) 68 | property_names.update(u._properties.keys()) 69 | 70 | writer = csv.DictWriter(self.response.out, fieldnames=sorted(property_names)) 71 | writer.writeheader() 72 | 73 | for u in data: 74 | d = dict() 75 | 76 | try: 77 | for k, v in u._properties.iteritems(): 78 | d[k] = str(v._get_user_value(u)) 79 | 80 | writer.writerow(d) 81 | 82 | except UnicodeEncodeError: 83 | logging.error("UnicodeEncodeError detected, row ignored"); 84 | 85 | def post(self): 86 | self.process() 87 | 88 | def get(self): 89 | self.process() 90 | 91 | 92 | class WriteDataObject(webapp2.RequestHandler): 93 | 94 | def post(self): 95 | data = DataObject() 96 | 97 | data.content = self.request.get('content') 98 | data.exp = self.request.get('exp') 99 | data.put() 100 | 101 | 102 | application = webapp2.WSGIApplication([ 103 | ('/', MainPage), 104 | ('/submit', WriteDataObject), 105 | ('/info', LoadResults) 106 | ], debug=True) 107 | -------------------------------------------------------------------------------- /inst/extdata/img/bisexual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/inst/extdata/img/bisexual.png -------------------------------------------------------------------------------- /inst/extdata/img/bisexual.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /inst/extdata/img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/inst/extdata/img/heart.png -------------------------------------------------------------------------------- /inst/extdata/img/lesbian.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /inst/extdata/img/rainbow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /inst/extdata/img/transgender.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/css/jspsych.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS for jsPsych experiments. 3 | * 4 | * This stylesheet provides minimal styling to make jsPsych 5 | * experiments look polished without any additional styles. 6 | */ 7 | 8 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 9 | 10 | /* Container holding jsPsych content */ 11 | 12 | .jspsych-display-element { 13 | display: flex; 14 | flex-direction: column; 15 | overflow-y: auto; 16 | } 17 | 18 | .jspsych-display-element:focus { 19 | outline: none; 20 | } 21 | 22 | .jspsych-content-wrapper { 23 | display: flex; 24 | margin: auto; 25 | flex: 1 1 100%; 26 | width: 100%; 27 | } 28 | 29 | .jspsych-content { 30 | max-width: 95%; /* this is mainly an IE 10-11 fix */ 31 | text-align: center; 32 | margin: auto; /* this is for overflowing content */ 33 | } 34 | 35 | .jspsych-top { 36 | align-items: flex-start; 37 | } 38 | 39 | .jspsych-middle { 40 | align-items: center; 41 | } 42 | 43 | /* fonts and type */ 44 | 45 | .jspsych-display-element { 46 | font-family: 'Open Sans', 'Arial', sans-serif; 47 | font-size: 18px; 48 | line-height: 1.6em; 49 | } 50 | 51 | /* Form elements like input fields and buttons */ 52 | 53 | .jspsych-display-element input[type="text"] { 54 | font-family: 'Open Sans', 'Arial', sans-serif; 55 | font-size: 14px; 56 | } 57 | 58 | /* borrowing Bootstrap style for btn elements, but combining styles a bit */ 59 | .jspsych-btn { 60 | display: inline-block; 61 | padding: 6px 12px; 62 | margin: 0px; 63 | font-size: 14px; 64 | font-weight: 400; 65 | font-family: 'Open Sans', 'Arial', sans-serif; 66 | cursor: pointer; 67 | line-height: 1.4; 68 | text-align: center; 69 | white-space: nowrap; 70 | vertical-align: middle; 71 | background-image: none; 72 | border: 1px solid transparent; 73 | border-radius: 4px; 74 | color: #333; 75 | background-color: #fff; 76 | border-color: #ccc; 77 | } 78 | 79 | .jspsych-btn:hover { 80 | background-color: #ddd; 81 | border-color: #aaa; 82 | } 83 | 84 | .jspsych-btn:disabled { 85 | background-color: #eee; 86 | color: #aaa; 87 | border-color: #ccc; 88 | cursor: not-allowed; 89 | } 90 | 91 | /* jsPsych progress bar */ 92 | 93 | #jspsych-progressbar-container { 94 | color: #555; 95 | border-bottom: 1px solid #dedede; 96 | background-color: #f9f9f9; 97 | margin-bottom: 1em; 98 | text-align: center; 99 | padding: 8px 0px; 100 | width: 100%; 101 | line-height: 1em; 102 | } 103 | #jspsych-progressbar-container span { 104 | font-size: 14px; 105 | padding-right: 14px; 106 | } 107 | #jspsych-progressbar-outer { 108 | background-color: #eee; 109 | width: 50%; 110 | margin: auto; 111 | height: 14px; 112 | display: inline-block; 113 | vertical-align: middle; 114 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 115 | } 116 | #jspsych-progressbar-inner { 117 | background-color: #aaa; 118 | width: 0%; 119 | height: 100%; 120 | } 121 | 122 | /* Control appearance of jsPsych.data.displayData() */ 123 | #jspsych-data-display { 124 | text-align: left; 125 | } 126 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2018 Joshua R. de Leeuw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-call-function.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jspsych-call-function 3 | * plugin for calling an arbitrary function during a jspsych experiment 4 | * Josh de Leeuw 5 | * 6 | * documentation: docs.jspsych.org 7 | * 8 | **/ 9 | 10 | jsPsych.plugins['call-function'] = (function() { 11 | 12 | var plugin = {}; 13 | 14 | plugin.info = { 15 | name: 'call-function', 16 | description: '', 17 | parameters: { 18 | func: { 19 | type: jsPsych.plugins.parameterType.FUNCTION, 20 | pretty_name: 'Function', 21 | default: undefined, 22 | description: 'Function to call' 23 | }, 24 | async: { 25 | type: jsPsych.plugins.parameterType.BOOL, 26 | pretty_name: 'Asynchronous', 27 | default: false, 28 | description: 'Is the function call asynchronous?' 29 | } 30 | } 31 | } 32 | 33 | plugin.trial = function(display_element, trial) { 34 | trial.post_trial_gap = 0; 35 | var return_val; 36 | 37 | if(trial.async){ 38 | var done = function(data){ 39 | return_val = data; 40 | end_trial(); 41 | } 42 | trial.func(done); 43 | } else { 44 | return_val = trial.func(); 45 | end_trial(); 46 | } 47 | 48 | function end_trial(){ 49 | var trial_data = { 50 | value: return_val 51 | }; 52 | 53 | jsPsych.finishTrial(trial_data); 54 | } 55 | }; 56 | 57 | return plugin; 58 | })(); 59 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-cloze.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jspsych-cloze 3 | * Philipp Sprengholz 4 | * 5 | * Plugin for displaying a cloze test and checking participants answers against a correct solution. 6 | * 7 | * documentation: docs.jspsych.org 8 | **/ 9 | 10 | jsPsych.plugins['cloze'] = (function () { 11 | 12 | var plugin = {}; 13 | 14 | plugin.info = { 15 | name: 'cloze', 16 | description: '', 17 | parameters: { 18 | text: { 19 | type: jsPsych.plugins.parameterType.STRING, 20 | pretty_name: 'Cloze text', 21 | default: undefined, 22 | description: 'The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. %solution%).' 23 | }, 24 | button_text: { 25 | type: jsPsych.plugins.parameterType.STRING, 26 | pretty_name: 'Button text', 27 | default: 'OK', 28 | description: 'Text of the button participants have to press for finishing the cloze test.' 29 | }, 30 | check_answers: { 31 | type: jsPsych.plugins.parameterType.BOOL, 32 | pretty_name: 'Check answers', 33 | default: false, 34 | description: 'Boolean value indicating if the answers given by participants should be compared against a correct solution given in the text (between % signs) after the button was clicked.' 35 | }, 36 | mistake_fn: { 37 | type: jsPsych.plugins.parameterType.FUNCTION, 38 | pretty_name: 'Mistake function', 39 | default: function () {}, 40 | description: 'Function called if check_answers is set to TRUE and there is a difference between the participants answers and the correct solution provided in the text.' 41 | } 42 | } 43 | }; 44 | 45 | plugin.trial = function (display_element, trial) { 46 | 47 | var html = '
    '; 48 | var elements = trial.text.split('%'); 49 | var solutions = []; 50 | 51 | for (i=0; i'; 61 | } 62 | } 63 | html += '
    '; 64 | 65 | display_element.innerHTML = html; 66 | 67 | var check = function() { 68 | 69 | var answers = []; 70 | var answers_correct = true; 71 | 72 | for (i=0; i'; 108 | display_element.querySelector('#finish_cloze_button').addEventListener('click', check); 109 | }; 110 | 111 | return plugin; 112 | })(); -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-external-html.js: -------------------------------------------------------------------------------- 1 | /** (July 2012, Erik Weitnauer) 2 | The html-plugin will load and display an external html pages. To proceed to the next, the 3 | user might either press a button on the page or a specific key. Afterwards, the page get hidden and 4 | the plugin will wait of a specified time before it proceeds. 5 | 6 | documentation: docs.jspsych.org 7 | */ 8 | 9 | jsPsych.plugins['external-html'] = (function() { 10 | 11 | var plugin = {}; 12 | 13 | plugin.info = { 14 | name: 'external-html', 15 | description: '', 16 | parameters: { 17 | url: { 18 | type: jsPsych.plugins.parameterType.STRING, 19 | pretty_name: 'URL', 20 | default: undefined, 21 | description: 'The url of the external html page' 22 | }, 23 | cont_key: { 24 | type: jsPsych.plugins.parameterType.KEYCODE, 25 | pretty_name: 'Continue key', 26 | default: null, 27 | description: 'The key to continue to the next page.' 28 | }, 29 | cont_btn: { 30 | type: jsPsych.plugins.parameterType.STRING, 31 | pretty_name: 'Continue button', 32 | default: null, 33 | description: 'The button to continue to the next page.' 34 | }, 35 | check_fn: { 36 | type: jsPsych.plugins.parameterType.FUNCTION, 37 | pretty_name: 'Check function', 38 | default: function() { return true; }, 39 | description: '' 40 | }, 41 | force_refresh: { 42 | type: jsPsych.plugins.parameterType.BOOL, 43 | pretty_name: 'Force refresh', 44 | default: false, 45 | description: 'Refresh page.' 46 | }, 47 | // if execute_Script == true, then all javascript code on the external page 48 | // will be executed in the plugin site within your jsPsych test 49 | execute_script: { 50 | type: jsPsych.plugins.parameterType.BOOL, 51 | pretty_name: 'Execute scripts', 52 | default: false, 53 | description: 'If true, JS scripts on the external html file will be executed.' 54 | } 55 | } 56 | } 57 | 58 | plugin.trial = function(display_element, trial) { 59 | 60 | var url = trial.url; 61 | if (trial.force_refresh) { 62 | url = trial.url + "?t=" + performance.now(); 63 | } 64 | 65 | load(display_element, url, function() { 66 | var t0 = performance.now(); 67 | var finish = function() { 68 | if (trial.check_fn && !trial.check_fn(display_element)) { return }; 69 | if (trial.cont_key) { display_element.removeEventListener('keydown', key_listener); } 70 | var trial_data = { 71 | rt: performance.now() - t0, 72 | url: trial.url 73 | }; 74 | display_element.innerHTML = ''; 75 | jsPsych.finishTrial(trial_data); 76 | }; 77 | 78 | // by default, scripts on the external page are not executed with XMLHttpRequest(). 79 | // To activate their content through DOM manipulation, we need to relocate all script tags 80 | if (trial.execute_script) { 81 | for (const scriptElement of display_element.getElementsByTagName("script")) { 82 | const relocatedScript = document.createElement("script"); 83 | relocatedScript.text = scriptElement.text; 84 | scriptElement.parentNode.replaceChild(relocatedScript, scriptElement); 85 | }; 86 | } 87 | 88 | if (trial.cont_btn) { display_element.querySelector('#'+trial.cont_btn).addEventListener('click', finish); } 89 | if (trial.cont_key) { 90 | var key_listener = function(e) { 91 | if (e.which == trial.cont_key) finish(); 92 | }; 93 | display_element.addEventListener('keydown', key_listener); 94 | } 95 | }); 96 | }; 97 | 98 | // helper to load via XMLHttpRequest 99 | function load(element, file, callback){ 100 | var xmlhttp = new XMLHttpRequest(); 101 | xmlhttp.open("GET", file, true); 102 | xmlhttp.onload = function(){ 103 | if(xmlhttp.status == 200 || xmlhttp.status == 0){ //Check if loaded 104 | element.innerHTML = xmlhttp.responseText; 105 | callback(); 106 | } 107 | } 108 | xmlhttp.send(); 109 | } 110 | 111 | return plugin; 112 | })(); 113 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-fullscreen.js: -------------------------------------------------------------------------------- 1 | /* jspsych-fullscreen.js 2 | * Josh de Leeuw 3 | * 4 | * toggle fullscreen mode in the browser 5 | * 6 | */ 7 | 8 | jsPsych.plugins.fullscreen = (function() { 9 | 10 | var plugin = {}; 11 | 12 | plugin.info = { 13 | name: 'fullscreen', 14 | description: '', 15 | parameters: { 16 | fullscreen_mode: { 17 | type: jsPsych.plugins.parameterType.BOOL, 18 | pretty_name: 'Fullscreen mode', 19 | default: true, 20 | array: false, 21 | description: 'If true, experiment will enter fullscreen mode. If false, the browser will exit fullscreen mode.' 22 | }, 23 | message: { 24 | type: jsPsych.plugins.parameterType.STRING, 25 | pretty_name: 'Message', 26 | default: '

    The experiment will switch to full screen mode when you press the button below

    ', 27 | array: false, 28 | description: 'HTML content to display above the button to enter fullscreen mode.' 29 | }, 30 | button_label: { 31 | type: jsPsych.plugins.parameterType.STRING, 32 | pretty_name: 'Button label', 33 | default: 'Continue', 34 | array: false, 35 | description: 'The text that appears on the button to enter fullscreen.' 36 | }, 37 | delay_after: { 38 | type: jsPsych.plugins.parameterType.INT, 39 | pretty_name: 'Delay after', 40 | default: 1000, 41 | array: false, 42 | description: 'The length of time to delay after entering fullscreen mode before ending the trial.' 43 | }, 44 | } 45 | } 46 | 47 | plugin.trial = function(display_element, trial) { 48 | 49 | // check if keys are allowed in fullscreen mode 50 | var keyboardNotAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element; 51 | if (keyboardNotAllowed) { 52 | // This is Safari, and keyboard events will be disabled. Don't allow fullscreen here. 53 | // do something else? 54 | endTrial(); 55 | } else { 56 | if(trial.fullscreen_mode){ 57 | display_element.innerHTML = trial.message + ''; 58 | var listener = display_element.querySelector('#jspsych-fullscreen-btn').addEventListener('click', function() { 59 | var element = document.documentElement; 60 | if (element.requestFullscreen) { 61 | element.requestFullscreen(); 62 | } else if (element.mozRequestFullScreen) { 63 | element.mozRequestFullScreen(); 64 | } else if (element.webkitRequestFullscreen) { 65 | element.webkitRequestFullscreen(); 66 | } else if (element.msRequestFullscreen) { 67 | element.msRequestFullscreen(); 68 | } 69 | endTrial(); 70 | }); 71 | } else { 72 | if (document.exitFullscreen) { 73 | document.exitFullscreen(); 74 | } else if (document.msExitFullscreen) { 75 | document.msExitFullscreen(); 76 | } else if (document.mozCancelFullScreen) { 77 | document.mozCancelFullScreen(); 78 | } else if (document.webkitExitFullscreen) { 79 | document.webkitExitFullscreen(); 80 | } 81 | endTrial(); 82 | } 83 | } 84 | 85 | function endTrial() { 86 | 87 | display_element.innerHTML = ''; 88 | 89 | jsPsych.pluginAPI.setTimeout(function(){ 90 | 91 | var trial_data = { 92 | success: !keyboardNotAllowed 93 | }; 94 | 95 | jsPsych.finishTrial(trial_data); 96 | 97 | }, trial.delay_after); 98 | 99 | } 100 | 101 | }; 102 | 103 | return plugin; 104 | })(); 105 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-html-keyboard-response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jspsych-html-keyboard-response 3 | * Josh de Leeuw 4 | * 5 | * plugin for displaying a stimulus and getting a keyboard response 6 | * 7 | * documentation: docs.jspsych.org 8 | * 9 | **/ 10 | 11 | 12 | jsPsych.plugins["html-keyboard-response"] = (function() { 13 | 14 | var plugin = {}; 15 | 16 | plugin.info = { 17 | name: 'html-keyboard-response', 18 | description: '', 19 | parameters: { 20 | stimulus: { 21 | type: jsPsych.plugins.parameterType.HTML_STRING, 22 | pretty_name: 'Stimulus', 23 | default: undefined, 24 | description: 'The HTML string to be displayed' 25 | }, 26 | choices: { 27 | type: jsPsych.plugins.parameterType.KEYCODE, 28 | array: true, 29 | pretty_name: 'Choices', 30 | default: jsPsych.ALL_KEYS, 31 | description: 'The keys the subject is allowed to press to respond to the stimulus.' 32 | }, 33 | prompt: { 34 | type: jsPsych.plugins.parameterType.STRING, 35 | pretty_name: 'Prompt', 36 | default: null, 37 | description: 'Any content here will be displayed below the stimulus.' 38 | }, 39 | stimulus_duration: { 40 | type: jsPsych.plugins.parameterType.INT, 41 | pretty_name: 'Stimulus duration', 42 | default: null, 43 | description: 'How long to hide the stimulus.' 44 | }, 45 | trial_duration: { 46 | type: jsPsych.plugins.parameterType.INT, 47 | pretty_name: 'Trial duration', 48 | default: null, 49 | description: 'How long to show trial before it ends.' 50 | }, 51 | response_ends_trial: { 52 | type: jsPsych.plugins.parameterType.BOOL, 53 | pretty_name: 'Response ends trial', 54 | default: true, 55 | description: 'If true, trial will end when subject makes a response.' 56 | }, 57 | 58 | } 59 | } 60 | 61 | plugin.trial = function(display_element, trial) { 62 | 63 | var new_html = '
    '+trial.stimulus+'
    '; 64 | 65 | // add prompt 66 | if(trial.prompt !== null){ 67 | new_html += trial.prompt; 68 | } 69 | 70 | // draw 71 | display_element.innerHTML = new_html; 72 | 73 | // store response 74 | var response = { 75 | rt: null, 76 | key: null 77 | }; 78 | 79 | // function to end trial when it is time 80 | var end_trial = function() { 81 | 82 | // kill any remaining setTimeout handlers 83 | jsPsych.pluginAPI.clearAllTimeouts(); 84 | 85 | // kill keyboard listeners 86 | if (typeof keyboardListener !== 'undefined') { 87 | jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener); 88 | } 89 | 90 | // gather the data to store for the trial 91 | var trial_data = { 92 | "rt": response.rt, 93 | "stimulus": trial.stimulus, 94 | "key_press": response.key 95 | }; 96 | 97 | // clear the display 98 | display_element.innerHTML = ''; 99 | 100 | // move on to the next trial 101 | jsPsych.finishTrial(trial_data); 102 | }; 103 | 104 | // function to handle responses by the subject 105 | var after_response = function(info) { 106 | 107 | // after a valid response, the stimulus will have the CSS class 'responded' 108 | // which can be used to provide visual feedback that a response was recorded 109 | display_element.querySelector('#jspsych-html-keyboard-response-stimulus').className += ' responded'; 110 | 111 | // only record the first response 112 | if (response.key == null) { 113 | response = info; 114 | } 115 | 116 | if (trial.response_ends_trial) { 117 | end_trial(); 118 | } 119 | }; 120 | 121 | // start the response listener 122 | if (trial.choices != jsPsych.NO_KEYS) { 123 | var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({ 124 | callback_function: after_response, 125 | valid_responses: trial.choices, 126 | rt_method: 'performance', 127 | persist: false, 128 | allow_held_key: false 129 | }); 130 | } 131 | 132 | // hide stimulus if stimulus_duration is set 133 | if (trial.stimulus_duration !== null) { 134 | jsPsych.pluginAPI.setTimeout(function() { 135 | display_element.querySelector('#jspsych-html-keyboard-response-stimulus').style.visibility = 'hidden'; 136 | }, trial.stimulus_duration); 137 | } 138 | 139 | // end trial if trial_duration is set 140 | if (trial.trial_duration !== null) { 141 | jsPsych.pluginAPI.setTimeout(function() { 142 | end_trial(); 143 | }, trial.trial_duration); 144 | } 145 | 146 | }; 147 | 148 | return plugin; 149 | })(); 150 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-reconstruction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jspsych-reconstruction 3 | * a jspsych plugin for a reconstruction task where the subject recreates 4 | * a stimulus from memory 5 | * 6 | * Josh de Leeuw 7 | * 8 | * documentation: docs.jspsych.org 9 | * 10 | */ 11 | 12 | 13 | jsPsych.plugins['reconstruction'] = (function() { 14 | 15 | var plugin = {}; 16 | 17 | plugin.info = { 18 | name: 'reconstruction', 19 | description: '', 20 | parameters: { 21 | stim_function: { 22 | type: jsPsych.plugins.parameterType.FUNCTION, 23 | pretty_name: 'Stimulus function', 24 | default: undefined, 25 | description: 'A function with a single parameter that returns an HTML-formatted string representing the stimulus.' 26 | }, 27 | starting_value: { 28 | type: jsPsych.plugins.parameterType.FLOAT, 29 | pretty_name: 'Starting value', 30 | default: 0.5, 31 | description: 'The starting value of the stimulus parameter.' 32 | }, 33 | step_size: { 34 | type: jsPsych.plugins.parameterType.FLOAT, 35 | pretty_name: 'Step size', 36 | default: 0.05, 37 | description: 'The change in the stimulus parameter caused by pressing one of the modification keys.' 38 | }, 39 | key_increase: { 40 | type: jsPsych.plugins.parameterType.KEYCODE, 41 | pretty_name: 'Key increase', 42 | default: 'h', 43 | description: 'The key to press for increasing the parameter value.' 44 | }, 45 | key_decrease: { 46 | type: jsPsych.plugins.parameterType.KEYCODE, 47 | pretty_name: 'Key decrease', 48 | default: 'g', 49 | description: 'The key to press for decreasing the parameter value.' 50 | }, 51 | button_label: { 52 | type: jsPsych.plugins.parameterType.STRING, 53 | pretty_name: 'Button label', 54 | default: 'Continue', 55 | description: 'The text that appears on the button to finish the trial.' 56 | } 57 | } 58 | } 59 | 60 | plugin.trial = function(display_element, trial) { 61 | 62 | // current param level 63 | var param = trial.starting_value; 64 | 65 | // set-up key listeners 66 | var after_response = function(info) { 67 | 68 | //console.log('fire'); 69 | 70 | var key_i = (typeof trial.key_increase == 'string') ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_increase) : trial.key_increase; 71 | var key_d = (typeof trial.key_decrease == 'string') ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_decrease) : trial.key_decrease; 72 | 73 | // get new param value 74 | if (info.key == key_i) { 75 | param = param + trial.step_size; 76 | } else if (info.key == key_d) { 77 | param = param - trial.step_size; 78 | } 79 | param = Math.max(Math.min(1, param), 0); 80 | 81 | // refresh the display 82 | draw(param); 83 | } 84 | 85 | // listen for responses 86 | var key_listener = jsPsych.pluginAPI.getKeyboardResponse({ 87 | callback_function: after_response, 88 | valid_responses: [trial.key_increase, trial.key_decrease], 89 | rt_method: 'performance', 90 | persist: true, 91 | allow_held_key: true 92 | }); 93 | // draw first iteration 94 | draw(param); 95 | 96 | function draw(param) { 97 | 98 | //console.log(param); 99 | 100 | display_element.innerHTML = '
    '+trial.stim_function(param)+'
    '; 101 | 102 | // add submit button 103 | display_element.innerHTML += ''; 104 | 105 | display_element.querySelector('#jspsych-reconstruction-next').addEventListener('click', endTrial); 106 | } 107 | 108 | function endTrial() { 109 | // measure response time 110 | var endTime =performance.now(); 111 | var response_time = endTime - startTime; 112 | 113 | // clear keyboard response 114 | jsPsych.pluginAPI.cancelKeyboardResponse(key_listener); 115 | 116 | // save data 117 | var trial_data = { 118 | "rt": response_time, 119 | "final_value": param, 120 | "start_value": trial.starting_value 121 | }; 122 | 123 | display_element.innerHTML = ''; 124 | 125 | // next trial 126 | jsPsych.finishTrial(trial_data); 127 | } 128 | 129 | var startTime = performance.now(); 130 | 131 | }; 132 | 133 | return plugin; 134 | })(); 135 | -------------------------------------------------------------------------------- /inst/extdata/jsPsych/plugins/jspsych-vsl-grid-scene.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jsPsych plugin for showing scenes that mimic the experiments described in 3 | * 4 | * Fiser, J., & Aslin, R. N. (2001). Unsupervised statistical learning of 5 | * higher-order spatial structures from visual scenes. Psychological science, 6 | * 12(6), 499-504. 7 | * 8 | * Josh de Leeuw 9 | * 10 | * documentation: docs.jspsych.org 11 | * 12 | */ 13 | 14 | jsPsych.plugins['vsl-grid-scene'] = (function() { 15 | 16 | var plugin = {}; 17 | 18 | jsPsych.pluginAPI.registerPreload('vsl-grid-scene', 'stimuli', 'image'); 19 | 20 | plugin.info = { 21 | name: 'vsl-grid-scene', 22 | description: '', 23 | parameters: { 24 | stimuli: { 25 | type: jsPsych.plugins.parameterType.IMAGE, 26 | pretty_name: 'Stimuli', 27 | array: true, 28 | default: undefined, 29 | description: 'An array that defines a grid.' 30 | }, 31 | image_size: { 32 | type: jsPsych.plugins.parameterType.INT, 33 | pretty_name: 'Image size', 34 | array: true, 35 | default: [100,100], 36 | description: 'Array specifying the width and height of the images to show.' 37 | }, 38 | trial_duration: { 39 | type: jsPsych.plugins.parameterType.INT, 40 | pretty_name: 'Trial duration', 41 | default: 2000, 42 | description: 'How long to show the stimulus for in milliseconds.' 43 | } 44 | } 45 | } 46 | 47 | plugin.trial = function(display_element, trial) { 48 | 49 | display_element.innerHTML = plugin.generate_stimulus(trial.stimuli, trial.image_size); 50 | 51 | jsPsych.pluginAPI.setTimeout(function() { 52 | endTrial(); 53 | }, trial.trial_duration); 54 | 55 | function endTrial() { 56 | 57 | display_element.innerHTML = ''; 58 | 59 | var trial_data = { 60 | "stimulus": JSON.stringify(trial.stimuli) 61 | }; 62 | 63 | jsPsych.finishTrial(trial_data); 64 | } 65 | }; 66 | 67 | plugin.generate_stimulus = function(pattern, image_size) { 68 | var nrows = pattern.length; 69 | var ncols = pattern[0].length; 70 | 71 | // create blank element to hold code that we generate 72 | var html = '
    '; 73 | 74 | // create table 75 | html += ''; 77 | 78 | for (var row = 0; row < nrows; row++) { 79 | html += ''; 80 | 81 | for (var col = 0; col < ncols; col++) { 82 | html += ''; 91 | } 92 | html += ''; 93 | } 94 | 95 | html += '
    '+ 84 | '
    '; 85 | if (pattern[row][col] !== 0) { 86 | html += ''; 88 | } 89 | html += '
    '; 90 | html += '
    '; 96 | html += '
    '; 97 | 98 | return html; 99 | 100 | }; 101 | 102 | return plugin; 103 | })(); 104 | -------------------------------------------------------------------------------- /inst/extdata/record_result.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /inst/extdata/resources/heart.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/inst/extdata/resources/heart.mpg -------------------------------------------------------------------------------- /inst/extdata/resources/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/inst/extdata/resources/heart.png -------------------------------------------------------------------------------- /inst/extdata/resources/heart.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/inst/extdata/resources/heart.webm -------------------------------------------------------------------------------- /inst/extdata/resources/lukewarm_banjo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnavarro/jaysire/fd76ccbce491bc8575ac4e751d97db3d5227e327/inst/extdata/resources/lukewarm_banjo.mp3 -------------------------------------------------------------------------------- /jaysire.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace,vignette 22 | -------------------------------------------------------------------------------- /man/build_resources.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_resources.R 3 | \name{build_resources} 4 | \alias{build_resources} 5 | \title{Build the resource file specification from a directory path} 6 | \usage{ 7 | build_resources( 8 | from, 9 | audio = c(".mp3", ".wav", ".aif", ".mid"), 10 | video = c(".mp4", ".mpg", ".mov", ".wmv", ".webm", ".ogg"), 11 | image = c(".jpg", ".png", ".bmp", ".svg", ".tiff"), 12 | script = c(".js"), 13 | style = c(".css") 14 | ) 15 | } 16 | \arguments{ 17 | \item{from}{The paths to files/directories} 18 | 19 | \item{audio}{File extensions assumed to be audio} 20 | 21 | \item{video}{File extensions assumed to be video} 22 | 23 | \item{image}{File extensions assumed to be images} 24 | 25 | \item{script}{File extensions assumed to be scripts} 26 | 27 | \item{style}{File extensions assumed to be stylesheets} 28 | } 29 | \value{ 30 | The \code{build_resources()} function returns a tibble with four columns, 31 | called "name", "type", "from", and "to". The "name" column lists the name of 32 | every resource file discovered in the \code{from} folder and the "type" column lists 33 | the kind of resource file (image, audio, video, script, style or other file). Finally, 34 | the "from" column specifies the \emph{full} path to the existing location of the 35 | resource file, while the "to" column specifies the \emph{relative} path to which a 36 | copy of the resource file should be copied (relative to the "index.html" file for 37 | the experiment) 38 | } 39 | \description{ 40 | Build the resource file specification from a directory path 41 | } 42 | \details{ 43 | Because jsPsych experiments are designed to run through the browser 44 | rather than within R, the jaysire package incorporates "resource files" in a 45 | slightly complicated way. Resource files here are divided into several 46 | categories because the experiment has to incorporate them in different ways: 47 | the code for handling images is different to the code for handling audio files 48 | or video files, and both are different to how scripts and style files are loaded. 49 | As a consequence, the \code{\link{build_experiment}()} function needs to know 50 | what kind of file each resource corresponds to in order to construct the 51 | experiment properly. One part of what the \code{build_resources()} function 52 | does is make this a little easier for the user, by scanning all files that 53 | belong to a "resource folder" (located at the path specified by the \code{from} 54 | argument) and using the file extension to guess the type of each resource file. 55 | 56 | The second peculiarity is that the \code{\link{build_experiment}()} function 57 | will make copies of all resource files. Regardless of where the original 58 | files are taken \code{from}, a separate copy will be placed in an appropriate 59 | subfolder within the experiment. For example, if the primary experiment file 60 | is saved to "experiment/index.html" and it requires an image file called 61 | "picture.png", it will be copied to "experiment/resource/image/picture.png". 62 | The reason for this is to ensure that the "experiment" folder is entirely 63 | self contained, and includes \emph{all} source files necessary to run the 64 | experiment. This is important if the experiment is designed to be 65 | deployed to a remote server (e.g., using Google App Engine), as is very 66 | often the case if one wishes to run an online experiment. 67 | 68 | It is for this reason that the \code{\link{build_experiment}()} function 69 | creates copies of resource files: jaysire is designed on the presumption that 70 | the user may wish to keep the "original" versions of resource files somewhere 71 | else, and makes copies of them that can be deployed in the experiment. 72 | Viewed from this perspective, the \code{build_resources()} function is 73 | a helper function: as long as all the resource files your experiment requires 74 | are (at least temporarily) stored in the \code{from} folder, it will construct 75 | a tibble that contains all the information that \code{\link{build_experiment}()} 76 | needs to organise the experimental files appropriately. 77 | 78 | There are two important details to note. First, the \code{from} folder should 79 | be flat: it should not contain subfolders. Second, there are various arguments 80 | (e.g., \code{audio}, \code{video}, \code{script} etc) that specify the file 81 | extensions that are associated with each resource type. The default values are 82 | likely to change in future as the current lists are quite restrictive. 83 | } 84 | \seealso{ 85 | \code{\link{insert_resource}}, \code{\link{build_experiment}} 86 | } 87 | -------------------------------------------------------------------------------- /man/build_timeline.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_timeline.R 3 | \name{build_timeline} 4 | \alias{build_timeline} 5 | \title{Build a timeline from trials} 6 | \usage{ 7 | build_timeline(...) 8 | } 9 | \arguments{ 10 | \item{...}{trial objects to add to this timeline} 11 | } 12 | \value{ 13 | An object of class "timeline" 14 | } 15 | \description{ 16 | Build a timeline from trials 17 | } 18 | \details{ 19 | Experiments in jsPsych are specified in terms of a "timeline" 20 | object, where each timeline can consist of one or more "trial" objects 21 | and timelines can contain other timelines. In pure jsPsych it is possible 22 | to define a "bare" trial that is not contained within a timeline (the trial 23 | is essentially a timeline) but jaysire is slightly more restrictive. To 24 | build a timeline in jaysire, the output of \code{trial_} functions need to be 25 | passed through the \code{build_timeline()} function to create a properly 26 | constructed timeline object. 27 | 28 | Once constructed, behaviour and execution of a timeline can be modified 29 | using a variety of functions. A timeline can be looped using the 30 | \code{\link{display_while}()} function, or executed contiionally using 31 | the \code{\link{display_if}()} function. Timeline variables can be attached 32 | using \code{\link{set_variables}()} and other parameters can be passed to 33 | the timeline using \code{\link{set_parameters}()}. 34 | } 35 | -------------------------------------------------------------------------------- /man/display_if.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timeline_modify.R 3 | \name{display_if} 4 | \alias{display_if} 5 | \title{Modify a timeline to execute if a condition is met} 6 | \usage{ 7 | display_if(timeline, conditional_function) 8 | } 9 | \arguments{ 10 | \item{timeline}{A timeline object} 11 | 12 | \item{conditional_function}{A javascript function that returns true if the timeline should execute and false otherwise} 13 | } 14 | \value{ 15 | The modified timeline object 16 | } 17 | \description{ 18 | Modify a timeline to execute if a condition is met 19 | } 20 | \details{ 21 | The \code{display_if()} function is used to modify an existing 22 | \code{timeline} object, and provides the ability for conditional branching 23 | within an experiment. To use it, the user must supply the 24 | \code{conditional_function}, a javascript function that executes at 25 | runtime and should evaluate to true or false. If the 26 | conditional function returns true, then the timeline object 27 | will execute; if the conditional function returns false then jsPsych 28 | will not run this timeline. 29 | 30 | At present jaysire provides only limited tools for writing the 31 | conditional function. The \code{\link{fn_data_condition}()} function allows 32 | a simple approach that allows the conditional function to query the 33 | jsPsych data store, but only in a limited way. Future versions will 34 | (hopefully) provide a richer tool set for this. However, for 35 | users who are comfortable with writing javascript functions directly 36 | the \code{\link{insert_javascript}()} function may be useful. 37 | } 38 | \seealso{ 39 | \code{\link{display_while}}, \code{\link{build_timeline}}, 40 | \code{\link{fn_data_condition}}, \code{\link{insert_javascript}} 41 | } 42 | -------------------------------------------------------------------------------- /man/display_while.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timeline_modify.R 3 | \name{display_while} 4 | \alias{display_while} 5 | \title{Modify a timeline to execute within a loop} 6 | \usage{ 7 | display_while(timeline, loop_function) 8 | } 9 | \arguments{ 10 | \item{timeline}{The timeline object} 11 | 12 | \item{loop_function}{A javascript function that returns true if loop repeats, false if terminates} 13 | } 14 | \value{ 15 | The modified timeline object 16 | } 17 | \description{ 18 | Modify a timeline to execute within a loop 19 | } 20 | \details{ 21 | The \code{display_while()} function is used to modify an existing 22 | \code{timeline} object, and provides the ability to include while loops 23 | within an experiment. To use it, the user must supply the 24 | \code{loop_function}, a javascript function that executes at 25 | runtime and should evaluate to true or false. If the 26 | loop function returns true, then the timeline object 27 | will execute for another iteration; this condinues until the 28 | loop function returns false. 29 | 30 | At present jaysire provides only limited tools for writing the 31 | loop function. The \code{\link{fn_data_condition}()} function allows 32 | a simple approach that allows the loop function to query the 33 | jsPsych data store, but only in a limited way. Future versions will 34 | (hopefully) provide a richer tool set for this. However, for 35 | users who are comfortable with writing javascript functions directly 36 | the \code{\link{insert_javascript}()} function may be useful. 37 | } 38 | \seealso{ 39 | \code{\link{display_if}}, \code{\link{build_timeline}}, 40 | \code{\link{fn_data_condition}}, \code{\link{insert_javascript}} 41 | } 42 | -------------------------------------------------------------------------------- /man/download_googlecloud.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_api.R 3 | \name{download_googlecloud} 4 | \alias{download_googlecloud} 5 | \title{Download data from a jspsych experiment deployed on google cloud} 6 | \usage{ 7 | download_googlecloud() 8 | } 9 | \description{ 10 | Download data from a jspsych experiment deployed on google cloud 11 | } 12 | \details{ 13 | This function currently does nothing 14 | } 15 | \seealso{ 16 | \code{\link{save_googlecloud}}, \code{\link{run_googlecloud}} 17 | } 18 | -------------------------------------------------------------------------------- /man/download_webserver.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_api.R 3 | \name{download_webserver} 4 | \alias{download_webserver} 5 | \title{Download data from a jspsych experiment deployed on a webserver} 6 | \usage{ 7 | download_webserver(ssh, keyfile = NULL, to = ".") 8 | } 9 | \arguments{ 10 | \item{ssh}{ssh connection string to a webserver} 11 | 12 | \item{keyfile}{(optional) path to a ssh private key to log in to your webserver} 13 | 14 | \item{to}{local folder to download the data to} 15 | } 16 | \description{ 17 | Download data from a jspsych experiment deployed on a webserver 18 | } 19 | \details{ 20 | This function assumes the default setup by run_webserver, so all it does 21 | is download the folder /var/www/server_data 22 | } 23 | \seealso{ 24 | \code{\link{save_webserver}}, \code{\link{run_webserver}} 25 | } 26 | -------------------------------------------------------------------------------- /man/fn_data_condition.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data_condition.R 3 | \name{fn_data_condition} 4 | \alias{fn_data_condition} 5 | \title{Return a javascript function that checks a data value} 6 | \usage{ 7 | fn_data_condition(expr, trials_back = 1) 8 | } 9 | \arguments{ 10 | \item{expr}{An expression to be evaluated within the jsPsych data store} 11 | 12 | \item{trials_back}{The number of trials before the present one for which to query the data} 13 | } 14 | \value{ 15 | A javascript function 16 | } 17 | \description{ 18 | Return a javascript function that checks a data value 19 | } 20 | \details{ 21 | The \code{fn_data_condition()} function creates a javascript function that 22 | can query the jsPsych data store and evaluate the expression \code{expr} within the 23 | data store. It is (at present) very limited, and can only query the data store for 24 | a single trial (i.e., a single row in the data set). By default it queries the 25 | most recent trial (\code{trials_back = 1}) but this behaviour can be modified. 26 | 27 | The intention behind this function is that it be used in conjunction with functions 28 | such as \code{\link{display_if}()} and \code{\link{display_while}()} that 29 | require a javascript function that will evaluate to true or false, in order to 30 | determine whether to continue the while loop or whether the if condition holds. 31 | 32 | As an example, one might set \code{fn_data_condition(button_pressed == "0")} 33 | when calling \code{\link{display_if}()}. If the participant had pressed 34 | button "0" on the previous trial, then the timeline in question will be 35 | executed. Otherwise it is not. 36 | 37 | Note that this function is a work in progress and will likely change in 38 | future versions in order to allow more flexibility. 39 | } 40 | -------------------------------------------------------------------------------- /man/fn_sample.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/randomisation.R 3 | \name{fn_sample} 4 | \alias{fn_sample} 5 | \title{Return a javascript function that samples from an array} 6 | \usage{ 7 | fn_sample(x, size, replace = FALSE, weights = NULL) 8 | } 9 | \arguments{ 10 | \item{x}{A vector specifying the possible values} 11 | 12 | \item{size}{The number of values to sample} 13 | 14 | \item{replace}{Sample with replacement? (default = FALSE)} 15 | 16 | \item{weights}{Probability of sampling each item (ignored if replace = FALSE)} 17 | } 18 | \value{ 19 | Returns a javascript function that samples from an array of values 20 | } 21 | \description{ 22 | Return a javascript function that samples from an array 23 | } 24 | \details{ 25 | The \code{fn_sample()} is used to return a function that, when called 26 | from within a jsPsych experiment, will mirror the behaviour of the \code{sample()} 27 | function from the base package using th jsPsych randomisation functions to 28 | at runtime. The input argument \code{x} specifies the set of values from which 29 | samples should be drawn, and the \code{size} argument specifies the number of 30 | samples to be drawn. When \code{replace = TRUE} items are sampled with 31 | replacement, and when \code{replace = FALSE} items are sampled without 32 | replacement. When sampling with replacement, the \code{weights} argument can 33 | be used to specify unequal sampling probabilities. 34 | 35 | The current implementation is limited. It does not work when \code{x} is a 36 | character vector, for example. Note also that the value returned within 37 | the jsPsych experiment is always an array (not a scalar), even when 38 | \code{size = 1}. 39 | } 40 | -------------------------------------------------------------------------------- /man/fullscreen.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fullscreen.R 3 | \name{fullscreen} 4 | \alias{fullscreen} 5 | \title{Toggle fullscreen mode in the browser} 6 | \usage{ 7 | fullscreen( 8 | fullscreen_mode = TRUE, 9 | 10 | message = "

    The experiment will switch to full screen mode when you press the button below

    ", 11 | button_label = "Continue", 12 | delay_after = 1000 13 | ) 14 | } 15 | \arguments{ 16 | \item{fullscreen_mode}{If TRUE, sets browser in full screen mode. Default: TRUE.} 17 | 18 | \item{message}{This is the message that is displayed in the browser to inform of the switch to fullscreen mode.} 19 | 20 | \item{button_label}{This is label of the button to acknowledge the switch.} 21 | 22 | \item{delay_after}{Time period in ms after the switch to proceed with the following element in the timeline.} 23 | } 24 | \description{ 25 | The \code{fullscreen} function is used to toggle fullscreen mode in the browser. 26 | } 27 | -------------------------------------------------------------------------------- /man/insert_javascript.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/insert.R 3 | \name{insert_javascript} 4 | \alias{insert_javascript} 5 | \title{Insert input as raw javascript} 6 | \usage{ 7 | insert_javascript(string) 8 | } 9 | \arguments{ 10 | \item{string}{a string to be interpreted as javascript code} 11 | } 12 | \value{ 13 | An object of class "json" 14 | } 15 | \description{ 16 | Insert input as raw javascript 17 | } 18 | \details{ 19 | As much as possible, the jaysire package has been designed to 20 | allow the user to write a behavioural experiment from R that runs through 21 | the browser using the jsPsych javascript library, with no need to write 22 | any javascript code. However, in some cases this will not be possible and 23 | the user may need to pass raw javascript code to the experiment (e.g., when 24 | specifying an "on_finish" callback function). To do so, the javascript should 25 | be specified as a \code{string} that is passed to \code{insert_javascript()}. 26 | What this does is assign the string to the S3 class "json", which in turn 27 | means that it will be written to the "experiment.js" file as is. 28 | } 29 | -------------------------------------------------------------------------------- /man/insert_property.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/insert.R 3 | \name{insert_property} 4 | \alias{insert_property} 5 | \title{Insert a property to the jsPsych data store} 6 | \usage{ 7 | insert_property(...) 8 | } 9 | \arguments{ 10 | \item{...}{Name/value pairs} 11 | } 12 | \value{ 13 | A list of data values to add to the data store 14 | } 15 | \description{ 16 | Insert a property to the jsPsych data store 17 | } 18 | \details{ 19 | The intention behind \code{insert_property()} is that it 20 | be used when adding new columns to the jsPsych data store. This can 21 | be done in two ways. First, it can occur as part of the call to 22 | \code{\link{build_experiment}()}. Including an argument of the form 23 | \code{column = insert_property(column_name = "constant value")} 24 | will insert a new column to the jsPsych data store whose value is 25 | "constant value" in every row. 26 | 27 | The second possible way to use it is during a call to a \code{trial_} 28 | function. Including an argument of the form 29 | \code{data = insert_property(column_name = "this value")} 30 | will insert "this value" as the value for the current row only. 31 | 32 | Note that, at present \code{insert_property()} simply returns a 33 | named list of its inputs. In future versions of jaysire it may have 34 | more functionality, but at the moment it is simply a call to \code{list()} 35 | } 36 | -------------------------------------------------------------------------------- /man/insert_resource.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/insert.R 3 | \name{insert_resource} 4 | \alias{insert_resource} 5 | \title{Insert input as path to a resource file} 6 | \usage{ 7 | insert_resource(file, type = NULL) 8 | } 9 | \arguments{ 10 | \item{file}{A character vector of file names} 11 | 12 | \item{type}{A character vector of file types (if \code{NULL}, type is guessed from file extension)} 13 | } 14 | \value{ 15 | A character vector of file paths specified relative to the 16 | location of the main "index.html" file for the experiment. 17 | } 18 | \description{ 19 | Insert input as path to a resource file 20 | } 21 | \details{ 22 | The \code{insert_resource()} function is designed to take a vector 23 | of filenames as input (the \code{file} argument), categorise files depending 24 | on their \code{type} ("audio", "video", "image", "script", "style" or "other") 25 | and construct the path to where those files will end up in the final experiment. 26 | 27 | The logic for including this functionality is as follow. 28 | Because jsPsych experiments are designed to run through the browser 29 | rather than within R, the jaysire package incorporates "resource files" in a 30 | slightly complicated way. Resource files here are divided into several 31 | categories because the experiment has to incorporate them in different ways: 32 | the code for handling images is different to the code for handling audio files 33 | or video files, and both are different to how scripts and style files are loaded. 34 | As a consequence, the \code{\link{build_experiment}()} function needs to know 35 | what kind of file each resource corresponds to in order to construct the 36 | experiment properly. 37 | 38 | When using jaysire, the \code{insert_resource()} function is generally used 39 | when building trials, and serves as a kind of "promissory note" to specify 40 | where the relevant files \emph{will be} when the experiment is constructed 41 | using \code{\link{build_experiment}()}. In contrast \code{\link{build_resources}()} 42 | is generally used when calling \code{\link{build_experiment}()}, and is in 43 | essence a set of "instructions" that \code{\link{build_experiment}()} can use 44 | to ensure that this promise is kept. 45 | } 46 | \seealso{ 47 | \code{\link{build_resources}}, \code{\link{build_experiment}} 48 | } 49 | -------------------------------------------------------------------------------- /man/insert_variable.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/insert.R 3 | \name{insert_variable} 4 | \alias{insert_variable} 5 | \title{Insert reference to a timeline variable} 6 | \usage{ 7 | insert_variable(name) 8 | } 9 | \arguments{ 10 | \item{name}{String specifying name of the variable to insert} 11 | } 12 | \value{ 13 | Javascript code that calls the jsPsych.timelineVariable() function 14 | } 15 | \description{ 16 | Insert reference to a timeline variable 17 | } 18 | \details{ 19 | When creating an experiment, a common pattern is to create a 20 | series of trials that are identical in every respect except for one thing 21 | that varies across the trial (e.g., a collection of 22 | \code{\link{trial_html_button_response}()} trials that are the same except 23 | for the text that is displayed). A natural way to handle this in the 24 | jsPsych framework is to create the trial in the usual fashion, except that 25 | instead of specifying the \emph{value} that needs to be included in the 26 | trial (e.g., the text itself) the code includes a reference to a 27 | \emph{timeline variable}. This is the job of the \code{insert_resource()} 28 | function. As an example, instead of creating a trial in which 29 | \code{stimulus = "cat"} and another one that is identical except that 30 | \code{stimulus = "dog"}, you could create a "template" for both trials by 31 | setting \code{stimulus = insert_variable("animal")}. This acts as a kind 32 | promise that is filled (at runtime) by looking for an "animal" variable 33 | attached to the timeline. See the examples section for an illustration of 34 | how these functions are intended to work together. 35 | } 36 | \examples{ 37 | # create a template from which a series of trials can be built 38 | template <- trial_html_button_response(stimulus = insert_variable("animal")) 39 | 40 | # create a timeline with three trials, all using the same template 41 | # but with a different value for the "animal" variable 42 | timeline <- build_timeline(template) \%>\% 43 | set_variables(animal = c("cat", "dog", "pig")) 44 | 45 | 46 | 47 | 48 | } 49 | \seealso{ 50 | \code{\link{set_variables}()}, \code{\link{build_timeline}()} 51 | } 52 | -------------------------------------------------------------------------------- /man/keycode.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/keycodes.R 3 | \name{keycode} 4 | \alias{keycode} 5 | \title{Javascript key codes} 6 | \usage{ 7 | keycode(key = NULL, code = NULL) 8 | } 9 | \arguments{ 10 | \item{key}{character vector specifying keynames (default = NULL)} 11 | 12 | \item{code}{numeric vector specfiying keycodes (default = NULL)} 13 | } 14 | \value{ 15 | A numeric or character vector 16 | } 17 | \description{ 18 | Javascript key codes 19 | } 20 | \details{ 21 | This function provides a mapping between the human-readable 22 | javascript \code{key} names and their corresponding numeric \code{code} 23 | values. If both input arguments are \code{NULL}, it returns a named 24 | numeric vector whose values correspond to the key codes and whose names 25 | correspond to the key names. If \code{key} is specified the return value 26 | is a vector with the corresponding numeric codes; whereas if \code{code} 27 | is specified the output is a character vector containing the corresponding 28 | key names. If neither argument is \code{NULL} the function throws an error. 29 | } 30 | -------------------------------------------------------------------------------- /man/pavlovia.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pavlovia.R 3 | \name{pavlovia} 4 | \alias{pavlovia} 5 | \title{Communication with pavlovia.org} 6 | \usage{ 7 | pavlovia( 8 | command = js_string("init"), 9 | participantId = NULL, 10 | errorCallback = NULL 11 | ) 12 | } 13 | \arguments{ 14 | \item{command}{The pavlovia command: "init" (default) or "finish".} 15 | 16 | \item{participantId}{The participant Id: any string (NULL by default).} 17 | 18 | \item{errorCallback}{The callback function called whenever an error occurs 19 | (NULL by default).} 20 | } 21 | \description{ 22 | The \code{pavlovia} plugin supports running experiments online 23 | in Pavlovia. 24 | } 25 | -------------------------------------------------------------------------------- /man/question_likert.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trial_survey_likert.R 3 | \name{question_likert} 4 | \alias{question_likert} 5 | \title{Create a Likert question} 6 | \usage{ 7 | question_likert(prompt, labels, required = FALSE, name = NULL) 8 | } 9 | \arguments{ 10 | \item{prompt}{the prompt for the question} 11 | 12 | \item{labels}{the labels on the Likert scale} 13 | 14 | \item{required}{is a response to the question required?} 15 | 16 | \item{name}{a convenient label for the question} 17 | } 18 | \value{ 19 | A question object to be passed to \code{\link{trial_survey_likert}()}. 20 | } 21 | \description{ 22 | Create a Likert question 23 | } 24 | \details{ 25 | The \code{question_likert()} function is designed to be called when 26 | using \code{\link{trial_survey_likert}()} to construct a survey page that contains 27 | Likert scale response items. When rendered as part of the study, the text specified 28 | by the \code{prompt} argument is shown to the participant, with a set of ordered 29 | categories displayed along a horizontal line. The \code{labels} for these categories 30 | are shown beneath the line, and the participant responds by selecting a radio button 31 | that is placed along the line. If \code{required = TRUE} the participant will not 32 | be allowed to continue to the next trial unless an answer is provided. 33 | 34 | The \code{name} argument should be a string that provides a convenient 35 | label for the question. If left unspecified, jsPsych defaults to labelling 36 | the questions within a survey page as "Q0", "Q1", "Q2", etc. 37 | } 38 | \seealso{ 39 | Survey page trials are constructed using the \code{\link{trial_survey_text}}, 40 | \code{\link{trial_survey_likert}}, \code{\link{trial_survey_multi_choice}} and 41 | \code{\link{trial_survey_multi_select}} functions. Individual questions for survey 42 | trials can be specified using \code{\link{question_text}}, 43 | \code{\link{question_likert}} and \code{\link{question_multi}}. 44 | } 45 | -------------------------------------------------------------------------------- /man/question_multi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trial_survey_multi_choice.R 3 | \name{question_multi} 4 | \alias{question_multi} 5 | \title{Create a multiple choice/select question} 6 | \usage{ 7 | question_multi( 8 | prompt, 9 | options, 10 | horizontal = FALSE, 11 | required = FALSE, 12 | name = NULL 13 | ) 14 | } 15 | \arguments{ 16 | \item{prompt}{the prompt for the question} 17 | 18 | \item{options}{character vector of options} 19 | 20 | \item{horizontal}{should radio buttons be laid out horizontally?} 21 | 22 | \item{required}{is a response to the question required?} 23 | 24 | \item{name}{a convenient label for the question} 25 | } 26 | \value{ 27 | A question object to be passed to \code{\link{trial_survey_multi_choice}()} 28 | or \code{\link{trial_survey_multi_select}()}. 29 | } 30 | \description{ 31 | Create a multiple choice/select question 32 | } 33 | \details{ 34 | The \code{question_multi()} function is designed to be called when 35 | using \code{\link{trial_survey_multi_choice}()} to construct a survey page that 36 | contains multiple choice items, or \code{\link{trial_survey_multi_select}()} to 37 | construct one with multi-selection items. 38 | 39 | When rendered as part of the study, the text specified 40 | by the \code{prompt} argument is shown to the participant, with a set of 41 | options presented either as radio buttons (for a multiple choice trial) or 42 | checkboxes (for a multiple selection trial). The text placed adjacent to the 43 | response options is specified by the \code{options} argument, and by default 44 | the options are laid out vertically. A horizontal arrangement can be 45 | produced by setting \code{horizontal = TRUE}. If \code{required = TRUE} the 46 | participant will not 47 | be allowed to continue to the next trial unless an answer is provided. 48 | 49 | The \code{name} argument should be a string that provides a convenient 50 | label for the question. If left unspecified, jsPsych defaults to labelling 51 | the questions within a survey page as "Q0", "Q1", "Q2", etc. 52 | } 53 | \seealso{ 54 | Survey page trials are constructed using the \code{\link{trial_survey_text}}, 55 | \code{\link{trial_survey_likert}}, \code{\link{trial_survey_multi_choice}} and 56 | \code{\link{trial_survey_multi_select}} functions. Individual questions for survey 57 | trials can be specified using \code{\link{question_text}}, 58 | \code{\link{question_likert}} and \code{\link{question_multi}}. 59 | } 60 | -------------------------------------------------------------------------------- /man/question_text.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trial_survey_text.R 3 | \name{question_text} 4 | \alias{question_text} 5 | \title{Create a free text response question} 6 | \usage{ 7 | question_text( 8 | prompt, 9 | placeholder = "", 10 | rows = 1, 11 | columns = 40, 12 | required = FALSE, 13 | name = NULL 14 | ) 15 | } 16 | \arguments{ 17 | \item{prompt}{The prompt for the question} 18 | 19 | \item{placeholder}{A string specifying the placeholder text} 20 | 21 | \item{rows}{Number of rows spanned by the text box} 22 | 23 | \item{columns}{Number of columns spanned by the text box} 24 | 25 | \item{required}{Is a response to the question required?} 26 | 27 | \item{name}{A convenient label for the question} 28 | } 29 | \value{ 30 | A question object to be passed to \code{\link{trial_survey_text}()}. 31 | } 32 | \description{ 33 | Create a free text response question 34 | } 35 | \details{ 36 | The \code{question_text()} function is designed to be called when 37 | using \code{\link{trial_survey_text}()} to construct a survey page that contains 38 | free text response items. When rendered as part of the study, the text specified 39 | by the \code{prompt} argument is shown to the participant, with a text box placed 40 | underneath into which a response may be typed. The size of the text box can be 41 | customised by specifying the number of text \code{rows} and \code{columns}. If 42 | a \code{placeholder} string is specified 43 | (e.g., \code{placeholder = "Type your answer here"}) 44 | it is displayed in faded text within the box, and will disappear as soon as 45 | the participant begins typing the response. 46 | 47 | If \code{required = TRUE} the participant will not 48 | be allowed to continue to the next trial unless an answer is provided. 49 | 50 | The \code{name} argument should be a string that provides a convenient 51 | label for the question. If left unspecified, jsPsych defaults to labelling 52 | the questions within a survey page as "Q0", "Q1", "Q2", etc. 53 | } 54 | \seealso{ 55 | Survey page trials are constructed using the \code{\link{trial_survey_text}}, 56 | \code{\link{trial_survey_likert}}, \code{\link{trial_survey_multi_choice}} and 57 | \code{\link{trial_survey_multi_select}} functions. Individual questions for survey 58 | trials can be specified using \code{\link{question_text}}, 59 | \code{\link{question_likert}} and \code{\link{question_multi}}. 60 | } 61 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers_misc.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{\%>\%} 7 | \title{Objects exported from other packages} 8 | \keyword{internal} 9 | \description{ 10 | These objects are imported from other packages. Follow the links 11 | below to see their documentation. 12 | 13 | \describe{ 14 | \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} 15 | }} 16 | 17 | -------------------------------------------------------------------------------- /man/respond_any_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers_misc.R 3 | \name{respond_any_key} 4 | \alias{respond_any_key} 5 | \title{Response is accepted with any key press} 6 | \usage{ 7 | respond_any_key() 8 | } 9 | \description{ 10 | Response is accepted with any key press 11 | } 12 | \details{ 13 | Many of the functions within the \code{trial_} family are designed 14 | to allow participants to respond using a key press, generally by specifying a 15 | a \code{choices} argument that indicates which keys will be accepted as valid 16 | responses (e.g., \code{choices = c("f","j")}). There are also cases where 17 | you may wish to allow every key to be a valid response (e.g., in situations 18 | where "Press any key to continue" is a sensible prompt). 19 | In those cases, specifying \code{choices = respond_any_key()} will 20 | produce the desired behaviour. 21 | } 22 | \seealso{ 23 | \code{\link{respond_no_key}}, \code{\link{trial_html_keyboard_response}}, 24 | \code{\link{trial_image_keyboard_response}}, \code{\link{trial_audio_keyboard_response}}, 25 | \code{\link{trial_video_keyboard_response}} 26 | } 27 | -------------------------------------------------------------------------------- /man/respond_no_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers_misc.R 3 | \name{respond_no_key} 4 | \alias{respond_no_key} 5 | \title{Response is not accepted for any key press} 6 | \usage{ 7 | respond_no_key() 8 | } 9 | \description{ 10 | Response is not accepted for any key press 11 | } 12 | \details{ 13 | Many of the functions within the \code{trial_} family are designed 14 | to allow participants to respond using a key press, generally by specifying a 15 | a \code{choices} argument that indicates which keys will be accepted as valid 16 | responses (e.g., \code{choices = c("f","j")}). There are also cases where 17 | you may wish to disable this, so that no key presses will be counted as valid 18 | responses (e.g., when a trial runs for a fixed duration but no response is 19 | expected). In those cases, specifying \code{choices = respond_no_key()} will 20 | produce the desired behaviour. 21 | } 22 | \seealso{ 23 | \code{\link{respond_any_key}}, \code{\link{trial_html_keyboard_response}}, 24 | \code{\link{trial_image_keyboard_response}}, \code{\link{trial_audio_keyboard_response}}, 25 | \code{\link{trial_video_keyboard_response}} 26 | } 27 | -------------------------------------------------------------------------------- /man/run_googlecloud.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_api.R 3 | \name{run_googlecloud} 4 | \alias{run_googlecloud} 5 | \title{Deploy a jspsych experiment on google app engine} 6 | \usage{ 7 | run_googlecloud(path, experiment_folder = "experiment", project_id) 8 | } 9 | \arguments{ 10 | \item{path}{Path where the experiment is deployed} 11 | 12 | \item{experiment_folder}{Experiment subfolder} 13 | 14 | \item{project_id}{the google app engine project id} 15 | } 16 | \description{ 17 | Deploy a jspsych experiment on google app engine 18 | } 19 | \details{ 20 | The purpose of the \code{run_googlecloud()} function is to make it 21 | somewhat easier to deploy a jsPsych experiment to Google App Engine, so that 22 | the experiment can run in the cloud rather than on the local machine. 23 | The \code{path} and \code{experiment_folder} arguments 24 | specify where the experiment should be deployed, and should be the same 25 | that was used when calling \code{\link{build_experiment}()}) to build the 26 | experiment originally. The \code{project_id} is the name of the Google App 27 | Engine project that will host the experiment. 28 | 29 | At present, the functionality of \code{run_googlecloud()} is quite limited. All 30 | it does is construct the appropriate command that you will need to enter at 31 | the terminal. It does not execute that command, nor does it assist you in 32 | creating the Google App Engine project itself (it is assumed that the user 33 | already has a Google Cloud account and is authorised to deploy to the project) 34 | } 35 | \seealso{ 36 | \code{\link{save_googlecloud}}, \code{\link{build_experiment}} 37 | } 38 | -------------------------------------------------------------------------------- /man/run_locally.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_api.R 3 | \name{run_locally} 4 | \alias{run_locally} 5 | \title{Deploy a jspsych experiment locally} 6 | \usage{ 7 | run_locally( 8 | path, 9 | experiment_folder = "experiment", 10 | data_folder = "data", 11 | port = 8000 12 | ) 13 | } 14 | \arguments{ 15 | \item{path}{Path where the experiment is deployed} 16 | 17 | \item{experiment_folder}{Experiment subfolder} 18 | 19 | \item{data_folder}{Data subfolder} 20 | 21 | \item{port}{The port to use} 22 | } 23 | \description{ 24 | Deploy a jspsych experiment locally 25 | } 26 | \details{ 27 | The purpose of the \code{run_locally()} function is to start an 28 | R server running (using the plumber package) that will 29 | serve the experiment from the local machine. The \code{path}, 30 | \code{experiment_folder} and \code{data_folder} arguments specify the location 31 | specify where the experiment should be deployed, and should be the same 32 | that was used when calling \code{\link{build_experiment}()}) to build the 33 | experiment originally. The \code{port} is the numeric value of the port on 34 | which the experiment is served. 35 | Once \code{run_locally()} has been called, a browser window should open 36 | showing the relevant page. 37 | 38 | There are two reasons to deploy a local experiment using \code{run_locally()} 39 | rather than simply opening the relevant "index.html" file in the browser. The 40 | first is for the purpose of saving data. For security reasons, browsers do not 41 | generally permit client-side javascript (e.g., the code that runs the jsPsych 42 | experiment) to save files to arbitrary locations. For this reason writing the 43 | data to file is the job of the R server, not the javascript code that 44 | is running through the browser. In other words, if the experiment is deployed 45 | locally using the \code{run_locally()} function, then the \code{\link{save_locally}()} 46 | function that used to record data locally will work properly. If, however, the 47 | "index.html" file is opened without starting the R server, data will not be 48 | saved to file. 49 | 50 | The second reason for using \code{run_locally()} is that it opens up the possibility 51 | that an experiment could use server-side R code at runtime. At the moment jaysire 52 | does not have any functionality to do so, but in principle there is nothing 53 | preventing the R server from playing a more active role when the experiment is 54 | running, and future versions of the package may develop this functionality 55 | further. 56 | } 57 | \seealso{ 58 | \code{\link{save_locally}}, \code{\link{build_experiment}} 59 | } 60 | -------------------------------------------------------------------------------- /man/run_webserver.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build_api.R 3 | \name{run_webserver} 4 | \alias{run_webserver} 5 | \title{Deploy a jspsych experiment to a webserver} 6 | \usage{ 7 | run_webserver( 8 | path, 9 | experiment_folder = "experiment", 10 | ssh, 11 | keyfile = NULL, 12 | webserver_configured = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{path}{Path where the experiment is deployed} 17 | 18 | \item{experiment_folder}{Experiment subfolder} 19 | 20 | \item{ssh}{ssh connection string to a webserver} 21 | 22 | \item{keyfile}{(optional) path to a ssh private key to log in to your webserver} 23 | 24 | \item{webserver_configured}{set this to true when you've configured your webserver} 25 | } 26 | \description{ 27 | Deploy a jspsych experiment to a webserver 28 | } 29 | \details{ 30 | The purpose of the \code{run_webserver()} function is to make it 31 | somewhat easier to deploy a jsPsych experiment to a webserver that you control, 32 | so that the experiment can run in the cloud rather than on the local machine. 33 | 34 | For this to work, you need to configure your webserver first. On a recent ubuntu image 35 | (available from most cloud providers), you can install apache and php with 36 | "\code{sudo apt install apache2 php}". You should probably also secure the connection 37 | between the webserver and the participants with https. If you have configured a domain 38 | name for your server, it is pretty simple to enable https via let's encrypt: 39 | "\code{sudo add-apt-repository ppa:certbot/certbot}", 40 | "\code{sudo apt install certbot python-certbot-apache}", and 41 | "\code{sudo certbot --apache}" 42 | 43 | Used together with \code{\link{save_webserver}}, this will set up a folder on the server 44 | that apache can write to, and a little php script to receive said data 45 | (as recommended on \href{https://www.jspsych.org/overview/data/#storing-data-permanently-as-a-file}{the jspsych website}). 46 | } 47 | \examples{ 48 | \dontrun{ 49 | build_experiment(..., on_finish = save_webserver()) 50 | run_webserver(ssh = "user@server.com", keyfile = "~/.ssh/id_rsa") 51 | download_webserver(ssh = "user@server.com", keyfile = "~/.ssh/id_rsa") 52 | } 53 | 54 | } 55 | \seealso{ 56 | \code{\link{save_webserver}}, \code{\link{build_experiment}}, \code{\link{download_webserver}} 57 | } 58 | -------------------------------------------------------------------------------- /man/save_googlecloud.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/save_locally.R 3 | \name{save_googlecloud} 4 | \alias{save_googlecloud} 5 | \title{Return a javascript function to save data to Google datastore} 6 | \usage{ 7 | save_googlecloud() 8 | } 9 | \value{ 10 | A javascript function to write data to the Google datastore 11 | } 12 | \description{ 13 | Return a javascript function to save data to Google datastore 14 | } 15 | \details{ 16 | The purpose of the \code{save_googlecloud()} is to return a 17 | javascript function that, when called from within the jsPsych experiment, 18 | will write the data to the Google datastore. 19 | The intention is that when an experiment is 20 | to be deployed on Google App Engine (i.e., using the \code{\link{run_googlecloud}()} 21 | function to deploy the experiment), the 22 | \code{save_googlecloud()} function provides the mechanism for saving the data. 23 | If the goal is simply to save the data set at the end of the experiment, the 24 | easiest way to do this is when building the experiment using 25 | \code{\link{build_experiment}()}. Specifically, the method for doing this is 26 | to include the argument \code{on_finish = save_googlecloud()} as part of the 27 | call to \code{\link{build_experiment}()}. 28 | } 29 | \seealso{ 30 | \code{\link{run_googlecloud}}, \code{\link{build_experiment}} 31 | } 32 | -------------------------------------------------------------------------------- /man/save_locally.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/save_locally.R 3 | \name{save_locally} 4 | \alias{save_locally} 5 | \title{Return a javascript function to save data locally} 6 | \usage{ 7 | save_locally() 8 | } 9 | \value{ 10 | A javascript function to save data locally 11 | } 12 | \description{ 13 | Return a javascript function to save data locally 14 | } 15 | \details{ 16 | The purpose of the \code{save_locally()} is to return a 17 | javascript function that, when called from within the jsPsych experiment, 18 | will write the data to a CSV file on the local machine (in the data folder 19 | associated with the experiment). The intention is that when an experiment is 20 | to be deployed locally (i.e., using the \code{\link{run_locally}()} function 21 | to run the experiment using an R server on the local machine), the 22 | \code{save_locally()} function provides the mechanism for saving the data. 23 | If the goal is simply to save the data set at the end of the experiment, the 24 | easiest way to do this is when building the experiment using 25 | \code{\link{build_experiment}()}. Specifically, the method for doing this is 26 | to include the argument \code{on_finish = save_locally()} as part of the 27 | call to \code{\link{build_experiment}()}. 28 | } 29 | \seealso{ 30 | \code{\link{run_locally}}, \code{\link{build_experiment}} 31 | } 32 | -------------------------------------------------------------------------------- /man/save_webserver.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/save_webserver.R 3 | \name{save_webserver} 4 | \alias{save_webserver} 5 | \title{Return a javascript function to save data via a script on the webserver} 6 | \usage{ 7 | save_webserver() 8 | } 9 | \value{ 10 | Return a javascript function to save data via a script on the webserver 11 | } 12 | \description{ 13 | Return a javascript function to save data via a script on the webserver 14 | } 15 | \details{ 16 | The purpose of the \code{save_webserver()} is to return a 17 | javascript function that, when called from within the jsPsych experiment, 18 | will write the data to the server. This assumes that the experiment will 19 | be run on a (php)script-enabled webserver. This way, you know the data will 20 | never touch any other computer than the server you've presumably secured 21 | and have data processing agreements in place for. 22 | } 23 | \seealso{ 24 | \code{\link{run_webserver}}, \code{\link{download_webserver}} 25 | } 26 | -------------------------------------------------------------------------------- /man/set_parameters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timeline_modify.R 3 | \name{set_parameters} 4 | \alias{set_parameters} 5 | \title{Modify a timeline to set parameter values} 6 | \usage{ 7 | set_parameters(timeline, ...) 8 | } 9 | \arguments{ 10 | \item{timeline}{The timeline object} 11 | 12 | \item{...}{A set of name/value pairs defining the parameters} 13 | } 14 | \value{ 15 | The modified timeline object 16 | } 17 | \description{ 18 | Modify a timeline to set parameter values 19 | } 20 | \details{ 21 | The \code{set_parameters()} function provides a general 22 | purpose method of adding arbitrary parameters to an existing 23 | \code{timeline}. Anything that jsPsych recognises as a possible 24 | timeline parameter can be inserted using this method. Some possibilities 25 | are shown in the examples section. 26 | } 27 | \examples{ 28 | # typically we begin with a trial template: 29 | trial_template <- trial_html_button_response( 30 | stimulus = insert_variable(name = "my_stimulus"), 31 | choices = c("true", "false") 32 | ) 33 | 34 | # then we fill it out so that there is now a "block" of trials: 35 | equations <- c("13 + 23 = 36", "17 - 9 = 6", "125 / 5 = 25") 36 | trials <- build_timeline(trial_template) \%>\% 37 | set_variables(my_stimulus = equations) 38 | 39 | # we can randomise presentation order and repeat the block: 40 | trials <- trials \%>\% 41 | set_parameters(randomize_order = TRUE, repetitions = 2) 42 | 43 | } 44 | \seealso{ 45 | \code{\link{build_timeline}}, \code{\link{set_variables}} 46 | } 47 | -------------------------------------------------------------------------------- /man/set_variables.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timeline_modify.R 3 | \name{set_variables} 4 | \alias{set_variables} 5 | \title{Modify a timeline to set possible values for variables} 6 | \usage{ 7 | set_variables(timeline, ...) 8 | } 9 | \arguments{ 10 | \item{timeline}{The timeline object} 11 | 12 | \item{...}{A set of name/value pairs defining the timeline variables} 13 | } 14 | \value{ 15 | The modified timeline object 16 | } 17 | \description{ 18 | Modify a timeline to set possible values for variables 19 | } 20 | \details{ 21 | When creating an experiment, a common pattern is to create a 22 | series of trials that are identical in every respect except for one thing 23 | that varies across the trial (e.g., a collection of 24 | \code{\link{trial_html_button_response}()} trials that are the same except 25 | for the text that is displayed). A natural way to handle this in the 26 | jsPsych framework is to create the trial in the usual fashion, except that 27 | instead of specifying the \emph{value} that needs to be included in the 28 | trial (e.g., the text itself) the code includes a reference to a 29 | \emph{timeline variable}. Inserting the \emph{reference} to the variable 30 | is the job of the \code{\link{insert_variable}()} function; \emph{attaching} 31 | that variable to the timeline and specifying its possible values is the 32 | job of \code{set_variables}. This is most easily explained by using 33 | an example, as shown below. 34 | } 35 | \examples{ 36 | # create a template from which a series of trials can be built 37 | template <- trial_html_button_response(stimulus = insert_variable("animal")) 38 | 39 | # create a timeline with three trials, all using the same template 40 | # but with a different value for the "animal" variable 41 | timeline <- build_timeline(template) \%>\% 42 | set_variables(animal = c("cat", "dog", "pig")) 43 | 44 | } 45 | \seealso{ 46 | \code{\link{build_timeline}}, \code{\link{insert_variable}} 47 | } 48 | -------------------------------------------------------------------------------- /man/temporary_folder.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers_misc.R 3 | \name{temporary_folder} 4 | \alias{temporary_folder} 5 | \title{Creates a temporary folder} 6 | \usage{ 7 | temporary_folder() 8 | } 9 | \value{ 10 | A string specifying the path to the folder 11 | } 12 | \description{ 13 | Creates a temporary folder 14 | } 15 | \details{ 16 | The \code{temporary_folder()} function is a convenience function 17 | used to create a new temporary folder inside the temporary directory 18 | (see \code{tempdir}) for the current R session. The name of the subfolder 19 | is always "jaysire_" followed by a 5-character alphanumeric string. 20 | 21 | The purpose of this function is mostly expository: it makes it a little 22 | easier to create easy-to-follow tutorials on the package website. It is 23 | not expected that users of the jaysire package would have much need for this 24 | function 25 | } 26 | -------------------------------------------------------------------------------- /man/trial_generic.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trial_generic.R 3 | \name{trial_generic} 4 | \alias{trial_generic} 5 | \title{Specify a trial using any plugin} 6 | \usage{ 7 | trial_generic(type, ...) 8 | } 9 | \arguments{ 10 | \item{type}{the type of trial} 11 | 12 | \item{...}{arguments passed to the trial plugin} 13 | } 14 | \value{ 15 | Functions with a \code{trial_} prefix always return a "trial" object. 16 | A trial object is simply a list containing the input arguments, with 17 | \code{NULL} elements removed. Logical values in the input (\code{TRUE} and 18 | \code{FALSE}) are transformed to character vectors \code{"true"} and \code{"false"} 19 | and are specified to be objects of class "json", ensuring that they will be 20 | written to file as the javascript logicals, \code{true} and \code{false}. 21 | } 22 | \description{ 23 | The \code{trial_generic} function is used to create a trial with 24 | an arbitrary jsPsych plugin. 25 | } 26 | \details{ 27 | The \code{trial_generic()} function is the most flexible of 28 | all the functions within the \code{trial_} family, and can be use to 29 | insert a trial of any \code{type}. For example, by setting 30 | \code{type = "image-keyboard-response"}, it will create an image trial 31 | using a keyboard response, precisely analogous to trials created using 32 | the \code{\link{trial_image_keyboard_response}()} function. More generally 33 | the \code{type} value should be a string that specifies the name of the 34 | corresponding jsPsych plugin file: in this case, the file name for the 35 | plugin "jspsych-image-keyboard-response.js" so the corresponding \code{type} 36 | value is "image-keyboard-response". 37 | 38 | While the advantage to \code{trial_generic()} is flexibility, the disadvantage 39 | is that all arguments to the plugin must be specified as named arguments passed 40 | via \code{...}, and it can take some trial and error to get a novel plugin to 41 | behave in the expected fashion. For example, if a particular argument to the 42 | jsPsych plugin takes a logical value, it may not always be sufficient to 43 | use logical values \code{TRUE} or \code{FALSE} when the trial is constructed 44 | from within R. The reason for this is that when the R code is converted to 45 | javascript (using the jsonlite package), it \emph{does} correctly convert 46 | the R logicals \code{TRUE} and \code{FALSE} to the corresponding javascript 47 | logical values \code{true} and \code{false}, but by default this value is 48 | written to a javascript array of length one rather than recorded as a scalar 49 | value (i.e., the javascript code becomes \code{[true]} rather than \code{true}). 50 | When this occurs, jsPsych often does not produce the desired behaviour as 51 | these two entities are not considered equivalent in javascript. 52 | 53 | In future versions of jaysire there may be better support for arbitary 54 | plugins, but for the moment users should be aware that \code{trial_generic()} 55 | can be somewhat finicky to work with. 56 | } 57 | -------------------------------------------------------------------------------- /misc/demo2.R: -------------------------------------------------------------------------------- 1 | library(jaysire) 2 | 3 | 4 | 5 | # welcome trial ----------------------------------------------------------- 6 | 7 | welcome_trial <- trial_instructions( 8 | pages = c( 9 | "Welcome to the experiment! Click on the buttons to navigate", 10 | "This experiment is intended to illustrate loops and conditions" 11 | ), 12 | show_clickable_nav = TRUE 13 | ) 14 | 15 | 16 | # instruction check ------------------------------------------------------- 17 | 18 | check_trial <- trial_survey_multi_choice( 19 | preamble = "Choose the correct response to continue", 20 | questions = list( 21 | question_multi("Instruction check question 1", c("correct", "wrong")), 22 | question_multi("Instruction check question 2", c("correct", "wrong")) 23 | ), 24 | randomize_question_order = FALSE 25 | ) 26 | 27 | check_pass <- trial_html_button_response( 28 | stimulus = "Well done!", 29 | choices = "Begin experiment" 30 | ) 31 | 32 | check_fail <- trial_html_button_response( 33 | stimulus = "Sorry, at least one of your answers was incorrect", 34 | choices = "Return to instructions" 35 | ) 36 | 37 | 38 | # flow control ----------------------------------------------------------- 39 | 40 | required_answer <- '{"Q0":"correct","Q1":"correct"}' 41 | 42 | check_fail <- check_fail %>% 43 | timeline() %>% 44 | display_if(fn_data_condition(responses != !!required_answer)) 45 | 46 | looped_start <- timeline(welcome_trial, check_trial, check_fail) %>% 47 | display_while(fn_data_condition(responses != !!required_answer)) 48 | 49 | 50 | 51 | # global experiment structure --------------------------------------------- 52 | 53 | experiment( 54 | timeline = timeline(looped_start, check_pass), 55 | path = "~/Desktop/demo2", 56 | default_iti = 250, 57 | on_finish = save_locally() 58 | ) 59 | 60 | 61 | # run the experiment ------------------------------------------------------ 62 | 63 | if(FALSE) { 64 | run_locally( 65 | path = "~/Desktop/demo2", 66 | port = 8000 67 | ) 68 | } 69 | 70 | -------------------------------------------------------------------------------- /misc/demo3.R: -------------------------------------------------------------------------------- 1 | library(jaysire) 2 | 3 | instructions <- trial_instructions( 4 | pages = c( 5 | "Welcome! Use the arrow buttons to browse these instructions", 6 | "Your task is to decide if an equation like '2 + 2 = 4' is true or false", 7 | "You will respond by clicking a button", 8 | "Press the 'Next' button to begin!" 9 | ), 10 | show_clickable_nav = TRUE, 11 | post_trial_gap = 2000 12 | ) 13 | 14 | trial1 <- trial_html_button_response( 15 | stimulus = "13 * 3 = 39", 16 | choices = c("true", "false"), 17 | post_trial_gap = 2000 18 | ) 19 | 20 | trial2 <- trial_html_button_response( 21 | stimulus = "17 - 9 = 6", 22 | choices = c("true", "false") 23 | ) 24 | 25 | all_trials <- timeline(instructions, trial1, trial2) 26 | 27 | experiment( 28 | timeline = all_trials, 29 | path = "~/Desktop/mathstudy", 30 | on_finish = save_locally() 31 | ) 32 | 33 | run_locally("~/Desktop/mathstudy") 34 | -------------------------------------------------------------------------------- /pkgdown-tweaks.R: -------------------------------------------------------------------------------- 1 | 2 | # inserts plausible.io trackers into pkgdown site pages 3 | 4 | site <- "jaysire.djnavarro.net" 5 | 6 | # lines to be inserted into header 7 | new_lines <- c( 8 | ' ', 9 | '', 10 | paste0(''), 11 | ' ' 12 | ) 13 | 14 | # files into which lines need insertion 15 | html_files <- list.files( 16 | path = here::here("docs"), 17 | pattern = "html$", 18 | recursive = TRUE, 19 | full.names = TRUE 20 | ) 21 | 22 | # function to insert lines 23 | insert_lines <- function(file, new_lines) { 24 | lines <- brio::read_lines(file) 25 | ind <- stringr::str_which(lines, "") 26 | if(is.null(ind)) rlang::warn(paste0("no line found in: ", file)) 27 | if(length(ind) > 1) rlang::warn(paste0("multiple lines found in: ", file)) 28 | 29 | # don't add lines if the tracker is already there 30 | tracker_ind <- stringr::str_which(lines, stringr::fixed(site)) 31 | if(length(tracker_ind) == 0) { 32 | 33 | # assume file doesn't begin or end with 34 | lines <- c( 35 | lines[1:(ind[1]-1)], 36 | new_lines, 37 | lines[ind[1]:length(lines)] 38 | ) 39 | brio::write_lines(lines, file) 40 | 41 | } 42 | } 43 | 44 | # insert for all files 45 | purrr::walk(html_files, insert_lines, new_lines = new_lines) 46 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(jaysire) 3 | 4 | test_check("jaysire") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-build_experiment.R: -------------------------------------------------------------------------------- 1 | test_that("multiplication works", { 2 | expect_equal(2 * 2, 4) 3 | }) 4 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | --------------------------------------------------------------------------------