Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterHi,
PennController elements do not correspond to any native javascript type, and in particular PennController Var elements do not correspond to javascript variables. As illustrated here, you can pass a Var element to the
text
command on a Text element to set its value dynamically. In your case, it would look something like this:newVar("hitext").set(getVar("ID")).set(v=>`hi, ${v}`), newText("instructions-1").text( getVar("hitext") )
Jeremy
Jeremy
KeymasterMy bad, I shouldn’t have used the word “link”.
DownloadRecordingButton
returns a string of HTML code that represents aBUTTON
HTML element, but that string needs to be parse to effectively add aBUTTON
element to the page, which is why you need to pass it tonewText
for example. So you wouldn’t be inserting a link in a button, you would be inserting aBUTTON
HTML element inside a Text PennController element, so the button can be added to the page for the participant to click, and download the archive containing the recordingsJeremy
Jeremy
KeymasterThere is no javascript function called
setVar
. What you can do is:getButton("B1") .callback( newFunction( ()=>{ try { getCanvas( getVar("canvasnr")._element.value ).visible()._runPromises(); } catch (e) { } }).call() , getVar("canvasnr").set("C3") )
Jeremy
Jeremy
KeymasterThe experiment will automatically invite the participant to download an archive containing the recordings if upload fails. You can also use
DownloadRecordingButton
to manually insert a link in a trial of your own, but make sure to do so after the results have been sent to the serverJeremy
Jeremy
KeymasterHello,
If you haven’t done so already, I invite you to read <the Core Concepts section in addition to the guide your reference, which also contains some pointers about the proper syntax and scope of PennController commands
I’ll start with your second message. All PennController commands are javascript functions. When you do
newTrial( /* ... */ )
whatever you write in place of/* ... */
are arguments to the functionnewTrial
. It is illegal in javascript to use thelet
keyword to define a variable as part as an argument, so you cannot do things likenewTrial( "myTrial", let time = 100 , newTimer("timeout", time) , /* ... */ )
. However, you can do things likenewTrial( "myTrial", time = 100 , newTimer("timeout", time) , /* ... */ )
because omittinglet
will make the expressiontime=100
return100
, which is a valid argument to pass to a function. Note that this would not settime
as a local variable, as in “in the scope ofnewTrial
“, because you simply are not in its scope (you’re just passing arguments)Now back to your first message. The files you upload to Resources are not just all thrown into the page when you open the experiment, otherwise it would be a real mess. What happens at the beginning of the experiment is your scripts get executed before
body
even gets populated, so any reference likedocument.querySelector(".form__btn--submit");
that you put at the root of your script will simply returnnull
. However, Ibex with its Message and Form controllers, and PennController withnewHtml
(ornewController
+ Message/Form) allow you to dynamically inject HTML files from the project’s Resources folder at some point during your experiment. The HTML files you upload to Resources can include CSS and javascript. However, the references in<link rel="stylesheet" href="style.css" />
and<script defer src="script.js"></script>
will fail on the farm, because the project’s files are not simply exposed at the local path. So you might want to include CSS + javascript in the HTML document, as in:<html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="description" content="..." /> <script> "use strict"; // embed code in an async function so as to wait for the button element to be added to the page const runScript = async () => { /// SELECT BUTTON: const btnSubmit = await new Promise(function getBtn(r) { const b = document.querySelector(".form__btn--submit"); if (b===null) window.requestAnimationFrame(()=>getBtn(r)); else r(b); }); // SELECT INPUT: const inputSubmitName = document.querySelector(".form__input--name"); const inputSubmitAge = document.querySelector(".form__input--age"); // SELECT BOX MESSAGE: let welcomeMsg = document.querySelector(".welcome"); // SELECT FORM DIV const formDiv = document.querySelector(".operation--welcome"); // SELECT AND MANIPULATE CONTENT: btnSubmit.addEventListener("click", function (e) { // Prevent default: e.preventDefault(); // Get name and age const name = inputSubmitName.value; const age = +inputSubmitAge.value; // transform string to a number // Make it disappear: formDiv.classList.add("welcome--hidden"); // Display Welcome User Message: welcomeMsg.classList.remove("welcome--hidden"); welcomeMsg.textContent = `Hello, ${name}! You are ${age} years old. Welcome! 🤗`; ////////////// HERE!! // log console.log(age, name); }); }; runScript(); </script> <style> /* general properties of document */ * { margin: 0; padding: 0; box-sizing: inherit; } body { font-family: "Poppins", sans-serif; font-weight: 300; color: yellow; /* cor do texto */ line-height: 1.9; background-color: #3616d9; /* cor do fundo */ } .form--title { position: absolute; left: 400px; height: 350px; width: 550px; bottom: 90px; } .form--personal__info { position: absolute; left: 300px; height: 350px; width: 550px; bottom: 50px; } .welcome-message { background-color: aquamarine; text-align: center; font-size: 30px; width: 100%; position: absolute; height: 80px; bottom: 280px; color: red; } .welcome--hidden { opacity: 0; transform: translate(20px, 20px); } </style> <title>My first form</title> </head> <body> <div class="operation operation--welcome"> <h2 class="form--title">Personal Info:</h2> <form class="form form--personal__info"> <input type="text" class="form__input form__input--name" placeholder="Your Name here"/> <input type="number" class="form__input form__input--age" placeholder="Your Age Here"/> <button class="form__btn form__btn--submit">→</button> <label class="form__label">Submit your data 👈</label> <!-- <label class="form__label">your data 👈</label> --> </form> </div> <div class = "welcome welcome-message welcome--hidden"> <h2 class="new--message">Welcome, user! 😀</h2> </div> </body> </html>
Note that the CSS rules will come after those already in place by Ibex + PennController, so you will want to adapt them (in particular, the
position:absolute;
shifts your content outside the visible portion of the page)Then you can do something like this:
newTrial( newHtml("my_file.html").print() , newButton("Next") .print() .wait() )
Jeremy
Jeremy
KeymasterThe “Item” column reports the item number according to Ibex. The other number corresponds to the PennController trial’s index: since PennController trials do not necessarily constitute the entirety of the study’s items (and certainly, in your study, you have non-PennController items, for example the one labeled “intro” does not contain a PennController trial but instead contains a single Message controller) the two number do not necessarily align. The example you mention means that the 40th item in your study corresponded to the 35th PennController trial, and that there likely are at least 5 items that are not PennController trials (I say “likely” because there are other potential sources of mismatch between Ibex’s item numbers and PennController’s trial numbers, eg. one could create some PennController trials first in a certain order and pass them as Ibex items later in a different order)
Jeremy
Jeremy
KeymasterHi,
No, no version of Google Drive, or any other such out-of-the-box cloud storage option for the matter, will work. The destination platform needs to be able to accept (cross-origin) POST requests, which requires configuration options that only some services make available (eg. AWS or webservers with Apache + PHP, among others)
EDIT: note that I am talking about Google Drive here, which is distinct from Cloud Storage (similar to AWS S3)
Jeremy
Jeremy
KeymasterHello,
You have four lines per trial because, by default, each PennController trial reports one line for when it starts and one line for when it ends. Then your trials use the
log
command on an Image element and on a Scale element, which will report two more lines per trial, one for each: one line reports when the Image element was printed onto the page, the other line reports what value was selected on the scale + when the selection occurred. This way, if you need to calculate the time difference between any pair of those four events, you have the option to do so. If all you care about is the value that was selected on the scale, you can just subset the table to those rows that report about the Scale elementThis page of the advanced tutorial illustrates how one can parse the data in R. In particular,
read.pcibex
will automatically name the table’s columns using the comment lines from the results file (the line starting with#
) and the commandfilter
can be used to subset to certain rows only, for examplefilter(PennElementName == "side-by-side" | PennElementName == "selection") %>%
keeps only the rows that correspond to the elements named “side-by-side” or “selection” in the tutorial’s studyJeremy
May 9, 2023 at 10:08 am in reply to: Monitor submission times and limit the number of submissions #10530Jeremy
KeymasterHi Chloris,
The limit on the number of rows per batch of download is 100K; you’re far from that, so that shouldn’t be a problem. Did you try logging out, closing and reopening your browser before logging in and trying to download the results again? Feel free to share the link to your study with me, here or at support@pcibex.net if the problem persists
Jeremy
May 9, 2023 at 5:53 am in reply to: Monitor submission times and limit the number of submissions #10527Jeremy
KeymasterHi Chloris,
You cannot know how many submissions you have per group unless you download the results file. The preview of the results (accessible by clicking the “…” button to the right of the “Results” button) will tell you how many submissions you have received so far in total at the corresponding link — just make sure you refresh the page to get the latest information
You cannot set a limit to the number of submissions on the farm, as multiple participants might start the experiment in parallel and a subset of them might of might not complete it: there is no principled way of knowing in advance how many submissions you will eventually receive. One common way of handling this point, assuming you are using a crowd-sourcing platform, is to open a limited number of slots on that platform, and open new ones as needed
Jeremy
Jeremy
KeymasterHello Christin,
You cannot use a reference to a PennController element inside a
getX
command, because the execution of (plain) JavaScript is immediate, but that of PennController commands is delayedWhat you can do is use a Function element in which you manipulate the PennController elements by accessing their methods and attributes:
getButton("B1") .callback( newFunction( ()=>{ try { getCanvas( getVar("canvasnr")._element.value ).visible()._runPromises(); } catch (e) { } }).call() )
Jeremy
Jeremy
KeymasterHi,
My bad, the class
\w
will only capture ASCII based characters. Maybe use\S
instead to capture any non-space character:.wait(getTextInput("ID").test.text(/\S/))
Jeremy
Jeremy
KeymasterHi,
Use
.log("first")
instead of just.log()
so you can capture keypresses that do not validate await
command on the Key element:newKey("answerTarget", "10") .log("first") .callback(getTimer("hurry").stop()) , getTimer("hurry").wait() , getKey("answerTarget") .test.pressed() .success(getText("target").remove()) .failure(getText("target").remove()) // removed this .log() as to not overwrite the first one ,
Jeremy
Jeremy
KeymasterMake sure your file .htaccess is owned by the user running PHP. If you need to find out that user, you can upload a PHP file containing
<?php echo exec('whoami'); ?>
and visit the page in your browser. Once you know the username, just usechown apache .htaccess
(assumingapache
is the username). Also make sure its permissions are 755:chmod 755 .htaccess
. If you do not know how to executechown
/chmod
, some programs such as FileZilla will let you modify the owner and permissions of files via their graphic interface (usually through a right-click on the file and an option like “properties”)Jeremy
Jeremy
KeymasterHello Evgeniya,
What this chunk of code does is embed a
Template
command inside anewTrial
command, which won’t produce the desired result. If you’d like to brush up on PCIbex experiment structure, I recommend taking a look at this section of the documentationWhat you want to do is insert the commands in the
newTrial
you already have:PennController.ResetPrefix(null); const PROGRESS_SIZE = {w: 100, h: 20}; // Experimental trial Template("items_pilot.csv", row => newTrial("pilot_experiment", defaultText .cssContainer({"margin-bottom":"1em"}) .css({"font-size": "large"}) .center() .print() , newTimer("break", 1000) //пауза между предложениями = 1 с .start() .wait() , // Printing the Canvas element above the context (feel free to move it somewhere else) newCanvas("progressContainer", PROGRESS_SIZE.w, PROGRESS_SIZE.h) .add(0,0,newCanvas("progress", PROGRESS_SIZE.w, PROGRESS_SIZE.h).color("green")) .css("border","solid 1px black") .center() .print() , newText("context", row.context) .css({"background-color": "LightCyan"}) , newText("sentence", row.sentence) , newScale("natural", "1", "2", "3", "4", "5", "6", "7") .before( newText("left", "Полностью неприемлемо ") ) .after( newText("right", " Полностью приемлемо") ) .log("last") .print() .callback(getTimer("timeout").stop()) , // Initiating the countdown just before starting the "timeout" Timer element newVar("timerInfo").set(()=>[Date.now(),15000]) // set the duration here , newFunction( "updateProgress", ()=>{ const [s,d] = getVar("timerInfo")._element.value; const p = (Date.now()-s) / d; if (p>1) return; getCanvas("progress").size((1-p)*PROGRESS_SIZE.w,PROGRESS_SIZE.h)._runPromises(); window.requestAnimationFrame( getFunction("updateProgress")._element.function ); } ).call() , newTimer("timeout", 15000) .start() .wait() , // Remove the progress bar once the timer ends (either naturally or by a selection) getCanvas("progressContainer").remove() , // I moved this here because I presume you want to wait for a selection to happen before setting the Var element newVar("rating") .global() .set(getScale("natural")) ) .log("group", row.group) .log( "rating" , getVar("rating") ) .log("condition", row.condition) .log("item", row.item) .log("exp_number",row.exp_number) .log("construction", row.construction) .log("governor", row.governor) .log("noun", row.noun) .log("pr_n", row.pr_n) )
Jeremy
-
AuthorPosts