Hello,
I'm following up on a similar question to this thread (
https://groups.google.com/d/msgid/lioness-lab/2186f6bc-6978-40ce-824c-454ac7096279n%40googlegroups.com). I am also trying to program an experiment where participants are presented with letter-grids and asked to identify how many times a specific letter appears in each grid. Each letter-grid occupies its own stage in Lioness. I want to allow participants 2 minutes to complete as many letter-search grids as possible. Half of my participants will count the letter Ws and the other half will count the letter Ms. After 2 minutes, participants will auto-advance to the next part of the experiment.
- Can you help me program a 2-minute timer that covers multiple stages in LIONESS and then auto-advances participants to the next part of the experiment when time is up?
- In the second part of the experiment, participants who counted Ws will be paired with another participant who counted Ms to exchange messages.
I have began to write some of the javascript (shared below) but there are 2 main issues that I cannot seem to fix: the 2-minute timer restarts whenever participants move to the next letter-search grid (next stage) and it doesn't auto advance to the next part of the experiment once the timer is out and it instead refreshes the page it is on. I have also included the HTML for the timer in one of the elements.
I have tried keeping all of the possible grids in one stage and just using the built in auto advance function in Lioness. The issue with that approach is that I have 30 possible grids that participants can do and the page lags to the point where the built in timer would not start until the entire page loads which takes more than 2 minutes. Because of the lag, the built in auto advance doesn't work and doesn't take participants to the next part of the experiment. If it's easier to help me fix that issue than write the code, I'm open to going down that approach. But at the moment, neither approach is working.
I can also make my lab public if that's easier for someone to help me. Thank you!
// ------------------- Vars ---------------------
var stagePrefix = 'task1grid'; // make this unique per grid page if you want per-page start/end/duration
var roundPrefix = 'round'; // shared across the whole round
var TIME_LIMIT_SEC = 120; // 2 minutes for the round
var TIMER_EL_ID = 'round-timer'; // <div id="round-timer">02:00</div>
// ----------------------------------------------
function nowMs(){ return Date.now(); }
function toIso(ms){ return new Date(ms).toISOString(); }
function pad2(n){ return (n<10?'0':'') + n; }
function fmtMMSS(sec){ sec=Math.max(0,Math.floor(sec)); var m=Math.floor(sec/60), s=sec%60; return pad2(m)+':'+pad2(s); }
function read(k){ try { return (typeof getValue==='function') ? getValue(k) : null; } catch(e){ return null; } }
function write(k,v){ record(k, v); } // Lioness-native
// Round-level vars (shared)
var roundStartVar = roundPrefix + '_globalStart'; // ms since epoch (string)
var roundLimitVar = roundPrefix + '_timeLimitSec'; // "120"
var roundLeftVar = roundPrefix + '_timeLeftSec'; // remaining (secs)
var roundTimeUpVar = roundPrefix + '_timeUp'; // "1" if ended
// Per-page vars (for page-level timing)
var startVar = stagePrefix + '_start';
var endVar = stagePrefix + '_end';
var durationVar = stagePrefix + '_duration';
var answerCountVar = stagePrefix + '_answerCount';
// Figure out which numeric input is present on THIS page
// You showed two inputs gated by getValue('gridtype') == '1' or '2'
var pageAnswerVar = (read('gridtype') === '2') ? 'rd1_grid2answer' : 'rd1_grid1answer';
// -------- Init on page load --------
(function initPage(){
// Start the round clock on the first grid page
if (read(roundStartVar) == null) {
write(roundStartVar, String(nowMs()));
write(roundLimitVar, String(TIME_LIMIT_SEC));
write(roundTimeUpVar, "0");
}
// Per-page timing (optional but useful)
if (read(startVar) == null) write(startVar, toIso(nowMs()));
// Start / resume countdown display + hard timeout
startOrResumeCountdown();
// Hook manual submission so we capture the final value even if they click Continue before timeout
hookManualContinue();
})();
// -------- Countdown --------
var countdownHandle = null;
function startOrResumeCountdown(){
var startMs = Number(read(roundStartVar) || 0);
var limitSec = Number(read(roundLimitVar) || TIME_LIMIT_SEC);
var timeUp = String(read(roundTimeUpVar) || "0");
if (!isFinite(startMs) || startMs <= 0) {
startMs = nowMs(); write(roundStartVar, String(startMs));
}
// Already expired? finalize and move on
if (timeUp === "1") { finalizePage(read(pageAnswerVar) || "0", /*autoAdvance=*/true); return; }
// Draw now + tick each second
updateCountdown(startMs, limitSec);
if (countdownHandle) clearInterval(countdownHandle);
countdownHandle = setInterval(function(){ updateCountdown(startMs, limitSec); }, 1000);
}
function updateCountdown(startMs, limitSec){
var elapsedSec = (nowMs() - startMs) / 1000;
var leftSec = Math.max(0, Math.ceil(limitSec - elapsedSec));
write(roundLeftVar, String(leftSec));
var el = document.getElementById(TIMER_EL_ID);
if (el) el.textContent = fmtMMSS(leftSec);
if (leftSec <= 0) {
clearInterval(countdownHandle);
write(roundTimeUpVar, "1");
finalizePage(read(pageAnswerVar) || "0", /*autoAdvance=*/true);
}
}
// -------- Finalize writes (shared by manual + timeout) --------
function finalizePage(finalCountValue, autoAdvance){
var endMs = nowMs();
write(endVar, toIso(endMs));
// duration from this page's start
var startIso = read(startVar);
try {
var sMs = Date.parse(startIso);
if (isFinite(sMs)) {
var durSec = Math.max(0, Math.round((endMs - sMs)/1000));
write(durationVar, String(durSec));
}
} catch(e){}
// store final numeric answer (optional mirror + guaranteed write)
var n = parseInt(finalCountValue, 10);
if (isNaN(n)) n = 0;
write(answerCountVar, String(n));
if (autoAdvance) tryClickNextOrSubmit();
}
// -------- Manual continue hook (no double-submit) --------
function hookManualContinue(){
// If page uses a form, capture submit:
var form = (document.forms && document.forms[0]) || null;
if (form) {
form.addEventListener('submit', function(){
finalizePage(read(pageAnswerVar) || "0", /*autoAdvance=*/false);
});
}
// Also try a common Continue button (in case it isn't a form submit)
var btn = document.querySelector('button[type=submit], input[type=submit], .NextButton, [data-continue]');
if (btn) {
btn.addEventListener('click', function(){
finalizePage(read(pageAnswerVar) || "0", /*autoAdvance=*/false);
});
}
}
// -------- Auto-advance helper (timeout path only) --------
function tryClickNextOrSubmit(){
var btn = document.querySelector('button[type=submit], input[type=submit], .NextButton, [data-continue], [name=next]');
if (btn) { btn.click(); return; }
var form = (document.forms && document.forms[0]) || null;
if (form && typeof form.submit === 'function') form.submit();
}