diff --git a/Example Experiment/Stimuli/101.jpg b/Example Experiment/Stimuli/101.jpg new file mode 100644 index 0000000..6a64a62 Binary files /dev/null and b/Example Experiment/Stimuli/101.jpg differ diff --git a/Example Experiment/Stimuli/188.jpg b/Example Experiment/Stimuli/188.jpg new file mode 100644 index 0000000..fff0f68 Binary files /dev/null and b/Example Experiment/Stimuli/188.jpg differ diff --git a/Example Experiment/Stimuli/blank.jpg b/Example Experiment/Stimuli/blank.jpg new file mode 100644 index 0000000..da60c1d Binary files /dev/null and b/Example Experiment/Stimuli/blank.jpg differ diff --git a/Example Experiment/Stimuli/instr_taste.jpg b/Example Experiment/Stimuli/instr_taste.jpg new file mode 100644 index 0000000..583c353 Binary files /dev/null and b/Example Experiment/Stimuli/instr_taste.jpg differ diff --git a/Example Experiment/Stimuli/maximize_window.png b/Example Experiment/Stimuli/maximize_window.png new file mode 100644 index 0000000..cd93a26 Binary files /dev/null and b/Example Experiment/Stimuli/maximize_window.png differ diff --git a/Example Experiment/Stimuli/prac.jpg b/Example Experiment/Stimuli/prac.jpg new file mode 100644 index 0000000..ea7d438 Binary files /dev/null and b/Example Experiment/Stimuli/prac.jpg differ diff --git a/Example Experiment/attrition_aesAtt.txt b/Example Experiment/attrition_aesAtt.txt deleted file mode 100644 index e69de29..0000000 diff --git a/Example Experiment/expt.js b/Example Experiment/expt.js deleted file mode 100644 index ce141ad..0000000 --- a/Example Experiment/expt.js +++ /dev/null @@ -1,306 +0,0 @@ -// by Yi-Chia Chen -// required func.js by Yi-Chia Chen -// required mobile-detect.js by Heinrich Goebl - - -// ###### ## ## ######## ## ######## ###### ######## -// ## ## ## ## ## ## ## ## ## ## ## -// ## ## ## ## ## ## ## ## ## -// ###### ## ## ######## ## ###### ## ## -// ## ## ## ## ## ## ## ## ## ## -// ## ## ## ## ## ## ## ## ## ## ## ## -// ###### ####### ######## ###### ######## ###### ## - -class subjObject { - constructor(options = {}) { - Object.assign(this, { - num: 'pre-post', - subjNumScript: 'subjNum.php', - subjNumFile: '', - condition: false, - conditionList: [''], - titles: [''], - mturk: true, - prolific: false, - idFunc: false, - invalidIDFunc: false, - viewportMinW: 0, - viewportMinH: 0, - savingScript: 'save.php', - attritionFile: 'attrition', - subjFile: 'subj', - savingDir: 'testing' - }, options); - if (this.num == 'pre-post') { - this.obtainSubjNum(this.subjNumScript, this.subjNumFile); - } - this.data = LIST_TO_FORMATTED_STRING(this.titles); - this.dateObj = new Date(); - this.date = FORMAT_DATE(this.dateObj, 'UTC', '-', true); - this.startTime = FORMAT_TIME(this.dateObj, 'UTC', ':', true); - this.userAgent = window.navigator.userAgent; - } - - obtainSubjNum(subjNumScript, subjNumFile) { - var that = this; - function SUBJ_NUM_UPDATE_SUCCEEDED(number) { - that.num = number; - if (that.condition == 'auto') { - that.assignCondition(); - } - } - function SUBJ_NUM_UPDATE_FAILED() { - that.num = -999; - } - POST_DATA(subjNumScript, { 'fileName': subjNumFile }, SUBJ_NUM_UPDATE_SUCCEEDED, SUBJ_NUM_UPDATE_FAILED); - } - - assignCondition() { - var that = this; - const CHECK_SUBJ_NUM = function(){ - if(that.num != 'pre-post'){ - clearInterval(interval_id); - that.condition = that.conditionList[(that.num-1) % that.conditionList.length]; - that.conditionAssigned = true; - } - }; - var interval_id = setInterval(CHECK_SUBJ_NUM, 10); - } - - obtainID(){ - if (this.mturk) { - this.id = this.obtainWorkerID(); - } else if(this.prolific){ - this.id = this.obtainProlificID(); - } else if (this.idFunc != false) { - this.id = this.idFunc(); - } - } - - obtainWorkerID() { - var workerID = GET_PARAMETERS('workerId', 'getFailed'); - if (workerID == 'getFailed') { - workerID = prompt('Please enter your worker ID:', ''); - } - var invalidID = (workerID == null); - if (!invalidID) { - workerID = workerID.replace(/\s+/g, ''); - invalidID = (workerID == ''); - } - if (invalidID) { - this.invalidIDFunc(); - return null; - } else { - return workerID; - } - } - - obtainProlificID() { - var prolificID = GET_PARAMETERS('PROLIFIC_PID', 'getFailed'); - if (prolificID == 'getFailed') { - prolificID = prompt('Please enter your Prolific ID:', ''); - } - var invalidID = (prolificID == null); - if (!invalidID) { - prolificID = prolificID.replace(/\s+/g, ''); - invalidID = (prolificID == ''); - } - if (invalidID) { - this.invalidIDFunc(); - return null; - } else { - return prolificID; - } - } - - get phone() { // getter runs when you ask for the property - var md = new MobileDetect(this.userAgent); - return md.mobile() ? true : false; - } - - get viewportSize() { - var w = $(window).width(); - var h = $(window).height(); - var inView = (w >= this.viewportMinW) && (h >= this.viewportMinH); - return { 'h': h, 'w': w, 'inView': inView }; - } - - saveAttrition() { - var data = 'subjNum\tstartDate\tstartTime\tid\tuserAgent\tinView\tviewportW\tviewportH\n'; - this.viewport = this.viewportSize; - this.inView = this.viewport['inView']; - this.viewportW = this.viewport['w']; - this.viewportH = this.viewport['h']; - var dataList = [this.num, this.date, this.startTime, this.id, this.userAgent, this.inView, this.viewportW, this.viewportH]; - data += LIST_TO_FORMATTED_STRING(dataList); - var postData = { - 'id': this.attritionFile, //filename to save the data with - 'experimenter': 'ycc', // experimenter folder to save it in - 'experimentName': this.savingDir, //directory to save it in - 'curData': data // data to save - }; - $.ajax({ - type: 'POST', - url: this.savingScript, - data: postData, - }); - } - - submitQ() { - var endTimeObj = new Date(); - this.endTime = FORMAT_TIME(endTimeObj, 'UTC', ':', true); - this.duration = (endTimeObj - this.dateObj) / 60000; // in minutes - var dataList = LIST_FROM_ATTRIBUTE_NAMES(this, this.titles); - this.data += LIST_TO_FORMATTED_STRING(dataList); - var postData = { - 'id': this.subjFile, //filename to save the data with - 'experimenter': 'ycc', // experimenter folder to save it in - 'experimentName': this.savingDir, //directory to save it in - 'curData': this.data // data to save - }; - $.ajax({ - type: 'POST', - url: this.savingScript, - data: postData, - }); - } -} - - -// ######## ######## #### ### ## -// ## ## ## ## ## ## ## -// ## ## ## ## ## ## ## -// ## ######## ## ## ## ## -// ## ## ## ## ######### ## -// ## ## ## ## ## ## ## -// ## ## ## #### ## ## ######## - -class trialObject { - constructor(options = {}) { - Object.assign(this, { - subj: false, - pracTrialN: 0, - trialN: 0, - titles: '', - stimPath: 'Stimuli/', - dataFile: '', - savingScript: 'mySave.php', - savingDir: 'testing', - trialList: [], - pracList: [], - intertrialInterval: 0.5, - updateFunc: false, - trialFunc: false, - endExptFunc: false, - progressInfo: false - }, options); - this.num = this.subj.num; - this.date = this.subj.date; - this.subjStartTime = this.subj.startTime; - this.blockNum = 0; - this.trialNum = -this.pracTrialN; - this.allData = LIST_TO_FORMATTED_STRING(this.titles); - this.complete = false; - } - - run() { - if (this.progressInfo) { - this.progress = Math.round( 100 * (this.trialNum+this.pracTrialN) / (this.trialN+this.pracTrialN) ); - } - this.trialNum++; - var formal = this.trialNum > 0; - if (formal) { - var last = this.trialNum == this.trialN; - this.thisTrial = this.trialList.pop(); - } else { - var last = this.trialNum == 0; - this.thisTrial = this.pracList.pop(); - } - if (!last) { - if (formal) { - var nextTrial = this.trialList[this.trialList.length - 1]; - } else { - var nextTrial = this.pracList[this.pracList.length - 1]; - } - } else { - var nextTrial = false; - } - this.updateFunc(formal, last, this.thisTrial, nextTrial, this.stimPath); - - var that = this; - const START_STIM = function() { - that.trialFunc(); - that.startTime = Date.now(); - }; - - setTimeout(START_STIM, this.intertrialInterval * 1000); - } - - end(resp) { - var currentTime = Date.now(); - this.rt = (currentTime - this.startTime) / 1000; // in second - this.response = resp; - if (this.trialNum > 0) { - var dataList = LIST_FROM_ATTRIBUTE_NAMES(this, this.titles); - this.allData += LIST_TO_FORMATTED_STRING(dataList); - } - if (this.trialNum < this.trialN) { - this.run(); - } else { - this.complete = true; - this.endExptFunc(); - } - } - - save() { - var postData = { - 'id': this.dataFile, //filename to save the data with - 'experimenter': 'ycc', // experimenter folder to save it in - 'experimentName': this.savingDir, //directory to save it in - 'curData': this.allData // data to save - }; - $.ajax({ - type: 'POST', - url: this.savingScript, - data: postData, - }); - } -} - - -// #### ## ## ###### ######## ######## -// ## ### ## ## ## ## ## ## -// ## #### ## ## ## ## ## -// ## ## ## ## ###### ## ######## -// ## ## #### ## ## ## ## -// ## ## ### ## ## ## ## ## -// #### ## ## ###### ## ## ## - -class instrObject { - constructor(options = {}) { - Object.assign(this, { - text: [], - funcDict: {}, - qConditions: [], - startExptFunc: false - }, options); - this.index = 0; - this.instrKeys = Object.keys(this.funcDict).map(Number); - this.qAttemptN = {}; - for (var i=0;i 0; i--) { - j = Math.floor(Math.random() * (i + 1)); - temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } - return array; -} - -function SAMPLE_WO_REPLACEMENT(list, sample_n) { - sample_n = (sample_n === undefined) ? 1 : sample_n; - var sample = []; - var local_list = list.slice(0); - for (var i = 0; i < sample_n; i++) { - var random_index = Math.floor(Math.random() * local_list.length); - sample.push(local_list.splice(random_index, 1)[0]); - } - return { - sample: sample, - remainder: local_list - }; -} - -function SAMPLE_W_REPLACEMENT(list, sample_n) { - sample_n = (sample_n === undefined) ? 1 : sample_n; - var sample = []; - var local_list = list.slice(0); - for (var i = 0; i < sample_n; i++) { - var random_index = Math.floor(Math.random() * local_list.length); - sample.push(local_list[random_index]); - } - return sample; -} - -function RAND_CHOICE(list) { - return list[Math.floor(Math.random() * list.length)]; -} - -function RANGE(start_num, end_num, interval) { - start_num = (start_num === undefined) ? 0 : start_num; - interval = (interval === undefined) ? 1 : interval; - var list = []; - for (var i = start_num; i < end_num; i += interval) { - list.push(i); - } - return list; -} - -// formatting date object into date string -function FORMAT_DATE(date_obj, time_zone, divider, padded) { - date_obj = (date_obj === undefined) ? new Date() : date_obj; - time_zone = (time_zone === undefined) ? 'UTC' : time_zone; - divider = (divider === undefined) ? '.' : divider; - padded = (padded === undefined) ? true : padded; - if (time_zone == 'UTC') { - var now_year = date_obj.getUTCFullYear(); - var now_month = date_obj.getUTCMonth() + 1; - var now_date = date_obj.getUTCDate(); - } else { - var now_year = date_obj.getFullYear(); - var now_month = date_obj.getMonth() + 1; - var now_date = date_obj.getDate(); - } - if (padded) { - now_month = ('0' + now_month).slice(-2); - now_date = ('0' + now_date).slice(-2); - } - var now_full_date = now_year + divider + now_month + divider + now_date; - return now_full_date; -} - -// formatting date object into 24-hour format time string -function FORMAT_TIME(date_obj, time_zone, divider, padded) { - date_obj = (date_obj === undefined) ? new Date() : date_obj; - time_zone = (time_zone === undefined) ? 'UTC' : time_zone; - divider = (divider === undefined) ? ':' : divider; - padded = (padded === undefined) ? true : padded; - if (time_zone == 'UTC') { - var now_hours = date_obj.getUTCHours(); - var now_minutes = date_obj.getUTCMinutes() + 1; - var now_seconds = date_obj.getUTCSeconds(); - } else { - var now_hours = date_obj.getHours(); - var now_minutes = date_obj.getMinutes() + 1; - var now_seconds = date_obj.getSeconds(); - } - if (padded) { - now_hours = ('0' + now_hours).slice(-2); - now_minutes = ('0' + now_minutes).slice(-2); - now_seconds = ('0' + now_seconds).slice(-2); - } - var now_full_time = now_hours + divider + now_minutes + divider + now_seconds; - return now_full_time; -} - -// preloading images -function LOAD_IMG(index, stim_path, img_list, after_func) { - after_func = (after_func === undefined) ? function() { return; } : after_func; - if (index >= img_list.length) { - return; - } - var image = new Image(); - if (index < img_list.length - 1) { - image.onload = function() { - LOAD_IMG(index + 1, stim_path, img_list, after_func); - }; - } else { - image.onload = after_func; - } - image.src = stim_path + img_list[index]; -} - -// preloading audio files -function LOAD_SOUNDS(index, stim_path, sound_list, after_func) { - if (index >= sound_list.length) { - return; - } - var sound = new Audio(); - - sound.src = stim_path + sound_list[index]; - - function CHECK_STATE() { - if (sound.readyState == 4) { - clearInterval(check_loading); - if (index < sound_list.length - 1) { - LOAD_SOUNDS(index + 1, stim_path, sound_list, after_func); - } else { - after_func(); - } - } else { - var current_time = Date.now(); - var current_duration = (current_time - start_time) / 1000; // in second - if (current_duration > 2) { - clearInterval(check_loading); - if (reload_num > 3) { // giving up - if (index < sound_list.length - 1) { - LOAD_SOUNDS(index + 1, stim_path, sound_list, after_func); - } else { - after_func(); - } - } else { // try reloading again - reload_num++; - sound.load(); - check_loading = window.setInterval(CHECK_STATE, 20); // update progress every intervalD ms - } - } - } - }; - - var start_time = Date.now(); - var reload_num = 0; - var check_loading = window.setInterval(check_state, 20); // update progress every intervalD ms -} - -function TO_RADIANS(degrees) { - return degrees * Math.PI / 180; -} - -function POLAR_TO_CARTESIAN(r, theta) { - return [r * Math.cos(TO_RADIANS(theta)), r * Math.sin(TO_RADIANS(theta))]; -} - -function REPEAT_ELEMENTS_IN_ARRAY(arr, repeat_n) { - var new_arr = []; - for (var i = 0; i < arr.length; i++) { - for (var j = 0; j < repeat_n; j++) { - new_arr.push(Array.from(arr[i])); - } - } - return new_arr; -} - -function LIST_FROM_ATTRIBUTE_NAMES(obj, string_list) { - var list = [] - for (var i = 0; i < string_list.length; i++) { - list.push(obj[string_list[i]]); - } - return list; -} - -function CHECK_IF_RESPONDED(open_ended_list, choice_list) { - var all_responded = true; - for (var i in open_ended_list) { - all_responded = all_responded && (open_ended_list[i].replace(/(?:\r\n|\r|\n|\s)/g, '') != ''); - } - for (var i in choice_list) { - all_responded = all_responded && (typeof choice_list[i] != 'undefined'); - } - return all_responded; -} - -function DISTANCE_BETWEEN_POINTS(point_a, point_b){ - var x_a = point_a[0]; - var y_a = point_a[1]; - var x_b = point_b[0]; - var y_b = point_b[1]; - return Math.sqrt(Math.pow(x_a-x_b,2)+Math.pow(y_a-y_b,2)); -} - -function CHECK_FULLY_IN_VIEW(el) { - el = el.get(0); - var rect = el.getBoundingClientRect(); - var top = rect.top; - var bottom = rect.bottom; - var left = rect.left; - var right = rect.right; - - var w = $(window).width(); - var h = $(window).height(); - var is_visible = (top >= 0) && (bottom <= h) && (left >= 0) && (right <= w); - return is_visible; -} \ No newline at end of file diff --git a/Example Experiment/hCaptcha_verification.php b/Example Experiment/hCaptcha_verification.php new file mode 100644 index 0000000..02258da --- /dev/null +++ b/Example Experiment/hCaptcha_verification.php @@ -0,0 +1,24 @@ + 'XXX', // enter your secret token here from your hCaptcha account + 'response'=> $_POST['hCaptcha_token'] + ]; + + $CH = curl_init(); + $OPTIONS = array( + CURLOPT_RETURNTRANSFER => true, // return web page + CURLOPT_URL => 'https://hcaptcha.com/siteverify', + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($DATA) + ); + curl_setopt_array($CH, $OPTIONS); + $JSON_RESPONSE = curl_exec($CH); + curl_close($CH); + + $RESULTS = json_decode($JSON_RESPONSE, true); + if ($RESULTS['success']) { + echo 'passed'; + } else { + echo 'not working'; + } +?> diff --git a/Example Experiment/index.html b/Example Experiment/index.html index 85f4001..4d9df29 100644 --- a/Example Experiment/index.html +++ b/Example Experiment/index.html @@ -1,4 +1,3 @@ - @@ -6,176 +5,176 @@ - Yale University Experiment - + UCLA Experiment + + + +
+

Just making sure no robot is participating......

+
+
+

+ Image Error: Please contact experimenter@domain.edu and include your sona ID and the code "IMG-ERROR" to receive your credit. +
+

Certainly
Pleasing

+

Probably
Pleasing

+

Guess
Pleasing

+

Guess
Not Pleasing

+

Probably
Not Pleasing

+

Certainly
Not Pleasing

+
NEXT
- In order for us to conduct this experiment online, we need to include the standard consent form below.

+
To conduct this experiment, we need to include this information sheet:
-
Consent for Participation in a Research Study
-
Yale University (Version: 5/29/19)

- Study Title: Perceiving Objects and Events

- Research Study Summary, Risks, and Benefits: Thank you for volunteering to participate in this research study. The purpose of this study is to better understand how we see and how we think. Study activities will include examining simple displays and then responding by answering questions, pressing some keys, or using a computer mouse. Because these are activities that many people already experience hundreds or thousands of times every day, there are no risks involved in this study. The study may have no benefits to you, but it may help the scientific community come to better understand how the human mind works. Taking part in this study is your choice. You can choose to take part, or you can choose not to take part in this study. You can also change your mind at any time, with no penalty.

- Duration: If you agree to take part, the study will last approximately 20 minutes.

- Costs and Compensation: There are no costs associated with participation in this study. You will receive $2.2 for participating.

- Confidentiality: No personally identifying information will be collected, so your participation will be anonymous. Your data will be pooled with those from other participants, and may be included in scientific publications and uploaded to public data repositories.

- Learning More: If you have questions about this study, you may contact your experimenter Clara Colombatto at clara.colombatto@yale.edu. If you have questions about your rights as a research participant, or you have complaints about this research, you can contact the Yale Institutional Review Boards at 203-785-4688 or hrpp@yale.edu.

- Informed Consent: Your participation indicates that you have read and understood this consent form and the information presented and that you agree to be in this study.

+ CONSENT FORM
+ Your consent form here.
- -
-
Based on the instructions you've just received, what is your job in this part of experiment?
- - - - - -
SUBMIT
-
- - -
-
Based on the instructions you've just received, what is your job in this part of experiment?
- - - - - + +
+
What is your job in this part of the experiment?
+ + + + +
SUBMIT
+
+
+
- +
-
0% completed
- +
0% completed
+
+ +
-
6
-
-
5
-
4
-
3
-
2
-
1
-
+

Certainly
Pleasing

+

Probably
Pleasing

+

Guess
Pleasing

+

Guess
Not Pleasing

+

Probably
Not Pleasing

+

Certainly
Not Pleasing

- -
- You're almost done! Please answer these last questions:


+ +
+ After answering these two questions, the second part will start:


-
How often do you spontaneously notice how visually appealing (or unappealing) things are in your day-to-day life?
+
How often do you spontaneously notice how visually appealing (or unappealing) things are in your day-to-day life?
-
-
-
-
-
-
-
+
+
+
+
+
+
+



-
How often do you spontaneously notice how attractive (or unattractive) people's appearances are in your day-to-day life?
-
-
-
-
-
-
-
-
-
-
-


-
In terms of judging visual appeal of objects and scenes, how similar do you think your visual preferences are compares to the majority?
+
In terms of judging visual appeal of objects and scenes, how similar do you think your visual preferences are compares to the majority?
-
-
-
-
-
-
-
+
+
+
+
+
+
+



-
In terms of judging people's appearances, how similar do you think your visual preferences are compares to the majority?
-
-
-
-
-
-
-
-
-
+ +
SUBMIT
+
+
+
+
+ + +
+

You have completed the first part!

In the second part, you will answer some questions. This will take about 2 minutes and likely shorter.

Hit SPACE to start!

+
+
0% completed
+
+
+

Definitely
Disagree

+

Slightly
Disagree

+

Slightly
Agree

+

Definitely
Agree

+
+
-


-
Did you complete this experiment seriously throughout (without randomly responding)?
-
You will receive your payment regardless what your answer is here. Please be honest for science! Thanks!
+ +
+ You are almost done! Please answer these last questions:


+ +
Did you complete this experiment seriously throughout (without randomly responding)?
+
You will receive credit regardless what your answer is here. Please be honest for science! Thanks!



-
Was any part of the procedure unclear? Did you have any problems completing any of the tasks?
-
You will receive your payment regardless what your answer is here. Please be honest for science! Thanks!
+ +
Was any part of the procedure unclear? Did you have any problems completing any of the tasks?
+
You will receive credit regardless what your answer is here. Please be honest for science! Thanks!



-
What is your gender?
+ +
What is your gender?



-
Please enter your age:
-


-
SUBMIT
+ +
What is your age?
+ + +


+ +
SUBMIT

+

- You have finished the experiment! Thank you very much!

Whenever you are ready, click END to submit your work. You may read the text below if you are interested in what we are studying.

-
END
+ You are done! Thank you very much! Whenever you are ready, click END to get credit.

+ Interested in what we are testing? See below for more information!

+
END


- Debriefing Sheet
- In this study, we are interested in whether individual's aesthetic taste is a general trait that can be observed in both preferences in images and preferences for people's appearances. If your taste is different from most others in terms of image preferences, would it be different from most others as well in your preferences for faces?

Please don't hesitate to contact the researchers if you have any questions: -

- Clara Colombatto
- Yale Perception and Cognition Laboratory
- clara.colombatto@yale.edu
-
- Yi-Chia Chen
- Harvard Vision Sciences Laboratory
- UCLA Computational Vision and Learning Laboratory
- https://ycc.vision/
- chenyichia.g@gmail.com
+ Debriefing Sheet
+ Your debriefing here.
diff --git a/Example Experiment/index.js b/Example Experiment/index.js index 7589c82..58ebd5c 100644 --- a/Example Experiment/index.js +++ b/Example Experiment/index.js @@ -9,52 +9,44 @@ // ## ## ## ## ## // ######## ## ## ## ## -const FORMAL = false; // XXX -const EXPERIMENT_NAME = 'aesAtt'; -const SUBJ_NUM_FILE = 'subjNum_' + EXPERIMENT_NAME; -const TRIAL_FILE = 'trial_' + EXPERIMENT_NAME; -const SUBJ_FILE = 'subj_' + EXPERIMENT_NAME; -const ATTRITION_FILE = 'attrition_' + EXPERIMENT_NAME; +const FORMAL = false; +const EXPERIMENT_NAME = 'Example'; +const SUBJ_NUM_FILE = 'subjNum_' + EXPERIMENT_NAME + '.txt'; +const TRIAL_FILE = 'trial_' + EXPERIMENT_NAME + '.txt'; +const SUBJ_FILE = 'subj_' + EXPERIMENT_NAME + '.txt'; +const VISIT_FILE = 'visit_' + EXPERIMENT_NAME + '.txt'; +const ATTRITION_FILE = 'attrition_' + EXPERIMENT_NAME + '.txt'; const SAVING_SCRIPT = 'save.php'; -const SAVING_DIR = ''; +const SAVING_DIR = FORMAL ? 'data/formal':'data/testing'; -const BLOCK_TYPES = ['photo','face']; -const CONDITIONS = ['photo_face','face_photo']; -const BLOCK_N = BLOCK_TYPES.length; +const BLOCK_N = 1; const VIEWPORT_MIN_W = 800; const VIEWPORT_MIN_H = 600; +const INSTR_READING_TIME_MIN = 2; + // trial variables -const PHOTO_PRACTICE_LIST = SHUFFLE_ARRAY(['photo_prac1.jpg', 'photo_prac2.jpg']); -const FACE_PRACTICE_LIST = SHUFFLE_ARRAY(['face_prac1_CFD-WF-233-112-N.jpg', 'face_prac2_CFD-AM-218-085-N.jpg']); -const PRAC_TRIAL_LIST_DICT = { - 'photo':PHOTO_PRACTICE_LIST, - 'face':FACE_PRACTICE_LIST -}; -const PRACTICE_TRIAL_N = PHOTO_PRACTICE_LIST.length; // 2 -const REPEAT_TRIAL_N = 35; +const PRACTICE_LIST = ['prac.jpg']; +const PRACTICE_TRIAL_N = PRACTICE_LIST.length; +const REPEAT_TRIAL_N = 1; const STIM_N = 2; const STIM_PATH = 'Stimuli/'; -const PHOTO_NAME_LIST = SHUFFLE_ARRAY(RANGE(101, 101 + STIM_N)); -const PHOTO_LIST = PHOTO_NAME_LIST.map(x => x+'.jpg'); -const FACE_LIST = [ - 'face1.jpg', - 'face2.jpg']; -var repeat_photo_list = PHOTO_LIST.slice(); -var repeat_face_list = FACE_LIST.slice(); -repeat_photo_list = SHUFFLE_ARRAY(repeat_photo_list).splice(0, REPEAT_TRIAL_N); -repeat_face_list = SHUFFLE_ARRAY(repeat_face_list).splice(0, REPEAT_TRIAL_N); -const PHOTO_TRIAL_LIST = repeat_photo_list.concat(PHOTO_LIST); // trial list -- the trials are popped from the end of array so the repeats are in the beginning -const FACE_TRIAL_LIST = repeat_face_list.concat(FACE_LIST); -const TRIAL_LIST_DICT = { - 'photo':PHOTO_TRIAL_LIST, - 'face':FACE_TRIAL_LIST -}; +const NAME_LIST = SHUFFLE_ARRAY(['101','188']); +const STIM_LIST = NAME_LIST.map(x => x+'.jpg'); -const TRIAL_N = PHOTO_TRIAL_LIST.length; +var repeat_list = STIM_LIST.slice(); +const TRIAL_LIST = CREATE_RANDOM_REPEAT_BEGINNING_LIST(STIM_LIST, REPEAT_TRIAL_N); +const TRIAL_N = TRIAL_LIST.length; const INSTR_TRIAL_N = PRACTICE_TRIAL_N + TRIAL_N; +// AQ variables +const AQ_QUESTION_DICT = { + 1: 'Statement 1', + 2: 'Statement 2' +} +const AQ_LENGTH = Object.keys(AQ_QUESTION_DICT).length; + // duration variables (in seconds) var INTERTRIAL_INTERVAL = 0.5; @@ -71,30 +63,15 @@ var instr, subj, trial; // ## ## ######## ## ## ######## ## $(document).ready(function() { - subj = new subjObject(subj_options); // obtain subject number - subj.obtainID(); - subj.assignCondition(); - if (subj.id != null) { - if (subj.phone) { // asking for subj.phone will detect phone - $('#instrText').html('It seems that you are using a touchscreen device. Please use a laptop or desktop instead.

If you believe you have received this message in error, please contact the experimenter at clara.colombatto@yale.edu

Otherwise, please switch to a laptop or a desktop computer for this experiment.'); - $('#nextButton').hide(); - $('#instrBox').show(); - } else { - instr = new instrObject(instr_options); - var CHECK_EXPT_READY = function(){ - if(subj.conditionAssigned){ - clearInterval(interval_id); - instr.condition = subj.condition.split('_'); - instr.text = INSTR_TEXT_DICT[instr.condition[0]]; - $('#instrText').html(instr.text[0]); - $('#instrBox').show(); - subj.saveAttrition(); - trial_options['subj'] = subj; - trial = new trialObject(trial_options); - } - }; - var interval_id = setInterval(CHECK_EXPT_READY, 10); - } + subj = new subjObject(subj_options); // getting subject number + subj.id = subj.getID('sonacode'); + subj.saveVisit(); + if (subj.phone) { // asking for subj.phone will detect phone + $('#instrText').html('It seems that you are using a touchscreen device or a phone. Please use a laptop or desktop instead.

If you believe you have received this message in error, please contact the experimenter at experimenter@domain.edu

Otherwise, please switch to a laptop or a desktop computer for this experiment.'); + $('#nextButton').hide(); + $('#instrBox').show(); + } else if (subj.id !== null){ + $('#captchaBox').show(); } }); @@ -114,13 +91,14 @@ const SUBJ_TITLES = ['num', 'userAgent', 'endTime', 'duration', - 'condition', - 'instrQPhotoAttemptN', - 'instrQFaceAttemptN', - 'dailyPhoto', - 'dailyFace', - 'typicalityPhoto', - 'typicalityFace', + 'instrQAttemptN', + 'instrReadingTimes', + 'quickReadingPageN', + 'hiddenCount', + 'hiddenDurations', + 'daily', + 'typicality', + 'aqResponses', 'serious', 'problems', 'gender', @@ -131,50 +109,124 @@ const SUBJ_TITLES = ['num', ]; function INVALID_ID_FUNC() { - $('#instrText').html("We can't identify a valid Prolific ID. Please click here to try again. Thank you!"); + $('#instrText').html("We can't identify a valid code from subject pool website. Please reopen the study from the subject pool website again. Thank you!"); $('#nextButton').hide(); $('#instrBox').show(); } -function SUBMIT_Q() { - subj.dailyPhoto = $('input[name=dailyPhoto]:checked').val(); - subj.dailyFace = $('input[name=dailyFace]:checked').val(); - subj.typicalityPhoto = $('input[name=typicalityPhoto]:checked').val(); - subj.typicalityFace = $('input[name=typicalityFace]:checked').val(); +function HCAPTCHA_SUBMIT() { + const HCAPTCHA_RESPONSE = hcaptcha.getResponse(); + function CAPTCHA_RESULTS(results) { + if (results == 'passed') { + $('#captchaBox').hide(); + instr = new instrObject(instr_options); + instr.start(); + trial_options['subj'] = subj; + trial = new trialObject(trial_options); + } else { + $('#captchaBox').hide(); + $('#instrText').html('Oops! An error has occurred. Please contact the experiment experimenter@domain.edu with the code "CAPTCHA_ERR". Sorry!'); + $('#nextButton').hide(); + $('#instrBox').show(); + } + } + function CAPTCHA_AJAX_FAILED() { + $('#instrText').html('Oops! An error has occurred. Please contact the experiment experimenter@domain.edu with the code "CAPTCHA_AJAX_ERR". Sorry!'); + $('#nextButton').hide(); + $('#instrBox').show(); + } + POST_DATA('hCaptcha_verification.php', { 'hCaptcha_token': HCAPTCHA_RESPONSE }, CAPTCHA_RESULTS, CAPTCHA_AJAX_FAILED); +} + +function HANDLE_VISIBILITY_CHANGE() { + if (document.hidden) { + subj.hiddenCount += 1; + subj.hiddenStartTime = Date.now(); + } else { + subj.hiddenDurations.push((Date.now() - subj.hiddenStartTime)/1000); + } +} + +function SUBMIT_LIKERT_Q() { + subj.daily = $('input[name=daily]:checked').val(); + subj.typicality = $('input[name=typicality]:checked').val(); + var all_responded = CHECK_IF_RESPONDED([], [subj.daily, subj.typicality]); + if (all_responded) { + $('#likertBox').hide(); + $('#aqBox').show(); + $(document).keyup(function(e) { + if (e.which == 32) { // the 'space' key + $(document).off('keyup'); + START_AQ(); + } + }); + } else { + $('#LikertWarning').text('Please answer all questions to complete the first part. Thank you!'); + } +} + +function START_AQ() { + $('#aqInstrText').hide(); + subj.aqResponses = {}; + subj.aqNowQ = 1; + $('#aqQ').text(AQ_QUESTION_DICT[1]); + $('#aqContainer').show(); + AQ_RESPONSE(); +} + +function AQ_RESPONSE() { + $('.aqButton').mouseup(function(event) { + $('.aqButton').unbind('mouseup'); + subj.aqResponses[subj.aqNowQ] = event.target.id; + if (subj.aqNowQ == AQ_LENGTH){ + $('#aqBox').hide(); + $('#questionsBox').show(); + subj.detectVisibilityEnd(); + } else { + subj.aqNowQ += 1; + $('#aqQ').text(AQ_QUESTION_DICT[subj.aqNowQ]); + $('#aqProgress').text( Math.round(100 * subj.aqNowQ / (AQ_LENGTH+2)) ); + AQ_RESPONSE(); + } + }); +} + +function SUBMIT_DEBRIEFING_Q() { subj.serious = $('input[name=serious]:checked').val(); subj.problems = $('#problems').val(); subj.gender = $('input[name=gender]:checked').val(); subj.age = $('#age').val(); var open_ended_list = [subj.problems, subj.age]; - var all_responded = CHECK_IF_RESPONDED(open_ended_list, [subj.dailyPhoto, subj.dailyFace, subj.typicalityPhoto, subj.typicalityFace, subj.serious, subj.gender]); + var all_responded = CHECK_IF_RESPONDED(open_ended_list, [subj.daily, subj.typicality]); if (all_responded) { for (var i = 0; i < open_ended_list.length; i++) { open_ended_list[i] = open_ended_list[i].replace(/(?:\r\n|\r|\n)/g, '
'); } - subj.instrQPhotoAttemptN = instr.qAttemptN['photo']; - subj.instrQFaceAttemptN = instr.qAttemptN['face']; + subj.instrQAttemptN = instr.qAttemptN['onlyQ']; + subj.instrReadingTimes = instr.readingTimes; + subj.quickReadingPageN = subj.instrReadingTimes.filter(d => d < INSTR_READING_TIME_MIN).length; + subj.aqResponses = JSON.stringify(subj.aqResponses); subj.submitQ(); $('#questionsBox').hide(); $('#debriefingBox').show(); + $('html')[0].scrollIntoView(); } else { - alert('Please answer all questions to complete the experiment. Thank you!'); + $('#Qwarning').text('Please answer all questions to complete the experiment. Thank you!'); } } var subj_options = { subjNumFile: SUBJ_NUM_FILE, - condition: 'auto', - conditionList: CONDITIONS, titles: SUBJ_TITLES, - mturk: false, - prolific: true, invalidIDFunc: INVALID_ID_FUNC, viewportMinW: VIEWPORT_MIN_W, viewportMinH: VIEWPORT_MIN_H, savingScript: SAVING_SCRIPT, + visitFile: VISIT_FILE, attritionFile: ATTRITION_FILE, subjFile: SUBJ_FILE, - savingDir: SAVING_DIR + savingDir: SAVING_DIR, + handleVisibilityChange: HANDLE_VISIBILITY_CHANGE }; @@ -186,89 +238,83 @@ var subj_options = { // ## ## ### ## ## ## ## ## // #### ## ## ###### ## ## ## -var instr_photo_first = new Array; -instr_photo_first[0] = 'Thank you for participating! Please read all the instructions carefully in the next few pages.

Do not use the refresh or back buttons, as you may be locked out from completing the experiment.

This experiment will take about 20 minutes to complete.'; -instr_photo_first[1] = 'IMPORTANT: Please maximize the size of your browser window NOW to make sure the experiment works as expected.'; -instr_photo_first[2] = 'We are interested in your aesthetic preferences. There are 2 parts in this experiment. Each part will take about 10 minutes.'; -instr_photo_first[3] = 'In the first half of the experiment, you will be shown ' + INSTR_TRIAL_N + ' images, one at a time. We are interested in how visually appealing you find each image to be.

In other words, how good/beautiful do you think the image looks?

For each image, use the mouse to click on one of the options from 1 to 6 to rate how visually appealing you find that image to be -- where "6" indicates that you find it very visually appealing, and "1" indicates that you find it very not visually appealing.'; -instr_photo_first[4] = 'You might sometimes find that you like an image because of its meaning or the subject it depicts (e.g. that it contains a cat, if you like cats) -- but what we are really asking about is just how visually appealing you think it is.

(As a result, a image of a cat could nevertheless look dull to you even if you like cats.)

Please try your best NOT to consider the meaning of the images, and just to evaluate how visually appealing each one is.'; -instr_photo_first[5] = "Of course, this task might seem a bit odd since we're rarely asked to explicitly rate how visually appealing an image is, and it might be a bit awkward. Nonetheless you might find that you have a strong initial intuition about how appealing each image looks: and that's what we're after. So when making your responses, don't think about it too much: we're really just interested in your gut reaction."; -instr_photo_first[6] = 'On the next page, we will ask you a question about the instructions.'; -instr_photo_first[7] = ''; // instruction question 1 -instr_photo_first[8] = 'Great! You can press SPACE to start the first part. Please do not interrupt the task after you start (e.g. by switching to other windows or tabs on your computer)'; - -instr_photo_first[9] = 'Thank you! You have completed the first half of the experiment!' -instr_photo_first[10] = 'In the second half, you will view ' + INSTR_TRIAL_N + ' faces. ' + "We are interested in how visually attractive you find each person to be, based on just looking at their face. " + 'You will rate the face from 1 to 6 in the same way as the first half (i.e., "6" indicates that you find the face very visually attractive).

' -instr_photo_first[11] = "You might sometimes find that you like a face because of its facial expression or the personality it seems to show (e.g. from looking at their face, you might think that the person is happy or has a warm personality) -- but what we are really asking about is just how visually attractive you think the face is.

(As a result, a person that appears warm and happy could nevertheless look unattractive to you even if you like their current emotional state or personality.)

Please try your best NOT to consider other attributes of the faces, and just to evaluate how visually attractive you think the face is."; -instr_photo_first[12] = "Of course, this task might seem a bit odd since we're rarely asked to explicitly rate how visually attractive people are, and it might be a bit awkward. Nonetheless you might find that you have a strong initial intuition about how attractive each face looks: and that's what we're after. So when making your responses, don't think about it too much: we're really just interested in your gut reaction."; -instr_photo_first[13] = 'On the next page, we will ask you a question about the instructions of the second half.'; -instr_photo_first[14] = ''; // instruction question 2 -instr_photo_first[15] = 'Great! Please press SPACE to start when ' + "you're ready."; - -var instr_face_first = new Array; -instr_face_first[0] = instr_photo_first[0]; -instr_face_first[1] = instr_photo_first[1]; -instr_face_first[2] = instr_photo_first[2]; -instr_face_first[3] = 'In the first half of the experiment, you will be shown ' + INSTR_TRIAL_N + " faces, one at a time. We are interested in how visually attractive you find each person to be, based on just looking at their face.

In other words, how good-looking do you think the person's face is?

" + 'For each face, use the mouse to click on one of the options from 1 to 6 to rate how visually attractive you find that face to be -- where "6" indicates that you find it very visually attractive, and "1" indicates that you find it very not visually attractive.'; -instr_face_first[4] = "You might sometimes find that you like a face because of its facial expression or the personality it seems to show (e.g. from looking at their face, you might think that the person is happy or has a warm personality) -- but what we are really asking about is just how visually attractive you think the face is.

(As a result, a person that appears warm and happy could nevertheless look unattractive to you even if you like their current emotional state or personality.)

Please try your best NOT to consider other attributes of the faces, and just to evaluate how visually attractive you think the face is."; -instr_face_first[5] = "Of course, this task might seem a bit odd since we're rarely asked to explicitly rate how visually attractive people are, and it might be a bit awkward. Nonetheless you might find that you have a strong initial intuition about how attractive each face looks: and that's what we're after. So when making your responses, don't think about it too much: we're really just interested in your gut reaction."; -instr_face_first[6] = instr_photo_first[6] -instr_face_first[7] = instr_photo_first[7] // instruction question 1 -instr_face_first[8] = instr_photo_first[8] - -instr_face_first[9] = instr_photo_first[9] -instr_face_first[10] = 'In the second half, you will view ' + INSTR_TRIAL_N + ' non-face images. ' + "We are interested in how visually appealing you find each image to be." + 'You will rate the image from 1 to 6 in the same way as the first half (i.e., "6" indicates that you find the image very visually appealing).

' -instr_face_first[11] = 'You might sometimes find that you like an image because of its meaning or the subject it depicts (e.g. that it contains a cat, if you like cats) -- but what we are really asking about is just how visually appealing you think it is.

(As a result, a image of a cat could nevertheless look dull to you even if you like cats.)

Please try your best NOT to consider the meaning of the images, and just to evaluate how visually appealing each one is.'; -instr_face_first[12] = "Of course, this task might seem a bit odd since we're rarely asked to explicitly rate how visually appealing an image is, and it might be a bit awkward. Nonetheless you might find that you have a strong initial intuition about how appealing each image looks: and that's what we're after. So when making your responses, don't think about it too much: we're really just interested in your gut reaction."; -instr_face_first[13] = instr_photo_first[13]; -instr_face_first[14] = instr_photo_first[14]; // instruction question 2 -instr_face_first[15] = instr_photo_first[15]; - -const INSTR_Q_INDICES = [7,14]; - -const INSTR_TEXT_DICT = { - 'photo':instr_photo_first, - 'face':instr_face_first +var instr_text = new Array; +instr_text[0] = "Welcome, fellow human!

Do you sometimes notice what people consider as beautiful can be very different? This study is about that!

We are a bunch of scientists fascinated by the human aesthetic experience, and we want to learn how people's taste differ."; +instr_text[1] = "Your contributions may help in making AI, analyzing art, and designing things around us in everyday life!

And, most importantly, we hope this is fun for you, too!"; +instr_text[2] = 'Please help us by reading the instructions in the next few pages carefully, and avoid using the refresh or back buttons.'; +instr_text[3] = 'Now, please maximize your browser window.'; +instr_text[4] = 'This study takes about 30 minutes, and there are 2 parts, with each taking about 15 minutes.'; +instr_text[5] = "Here's what your job is in the first part: you will be shown " + INSTR_TRIAL_N + ' images, one at a time. Here is an example:'; +instr_text[6] = 'We are interested in how visually pleasing you find each image to be.

In other words, how good/beautiful do you think the image looks?'; +instr_text[7] = 'Six options will be available below the images as six buttons (as in below). Just click one of the options based on your preference.'; +instr_text[8] = 'You might sometimes find that you like an image because of its meaning or the subject it depicts (e.g. that it contains a cat, if you like cats) — but what we are really asking about is just how visually pleasing you think it is.

(As a result, an image of a cat could nevertheless look dull to you even if you like cats.)

'; +instr_text[9] = 'Please try your best NOT to consider the meaning of the images, and just to evaluate how visually pleasing each one is.'; +instr_text[10] = "The next page is a quick instruction quiz. (It's very simple!)"; +instr_text[11] = ''; // instruction question 1 +instr_text[12] = "Great! You can press SPACE to start. Please focus after you start (Don't switch to other windows or tabs!)"; + +const INSTR_FUNC_DICT = { + 0: SHOW_TASTE_IMG, + 1: HIDE_INSTR_IMG, + 3: SHOW_MAXIMIZE_WINDOW, + 4: HIDE_INSTR_IMG, + 5: SHOW_EXAMPLE_IMG, + 6: HIDE_INSTR_IMG, + 7: SHOW_RATING_BUTTONS, + 8: HIDE_RATING_BUTTONS, + 11: SHOW_INSTR_QUESTION, + 12: SHOW_CONSENT }; -function RETRIEVE_CURRENT_CONDITION(index) { - if (index == INSTR_Q_INDICES[0]) { // first instruction question - var now_condition_num = 1; - } - else { // second instruction question - var now_condition_num = 2; - } - return instr.condition[now_condition_num-1].charAt(0).toUpperCase() + instr.condition[now_condition_num-1].slice(1) +function SHOW_INSTR_IMG(file_name) { + $('#instrImg').attr('src', STIM_PATH + file_name); + $('#instrImg').css('display', 'block'); +} + +function HIDE_INSTR_IMG() { + $('#instrImg').css('display', 'none'); +} + +function SHOW_TASTE_IMG() { + SHOW_INSTR_IMG('instr_taste.jpg'); +} + +function SHOW_MAXIMIZE_WINDOW() { + SHOW_INSTR_IMG('maximize_window.png'); +} + +function SHOW_EXAMPLE_IMG() { + SHOW_INSTR_IMG('prac.jpg'); +} + +function SHOW_RATING_BUTTONS() { + $('#ratingExample').show(); +} + +function HIDE_RATING_BUTTONS() { + $('#ratingExample').hide(); } function SHOW_INSTR_QUESTION() { $('#instrBox').hide(); - var now_question = RETRIEVE_CURRENT_CONDITION(instr.index); - $('#instrQ'+now_question).show(); + $('#instrQBox').show(); } function SUBMIT_INSTR_Q() { - var now_question = RETRIEVE_CURRENT_CONDITION(instr.index); - var instrChoice = $('input[name=instrQ'+now_question+']:checked').val(); - + var instrChoice = $('input[name="instrQ"]:checked').val(); if (typeof instrChoice === 'undefined') { - alert('Please answer the question to start the experiment. Thank you!'); - } else if (instrChoice != 'you') { - instr.qAttemptN[now_question.toLowerCase()] += 1; + $('#instrQWarning').text('Please answer the question. Thank you!'); + } else if (instrChoice != 'aesthetics') { + instr.qAttemptN['onlyQ'] += 1; $('#instrText').html('You have given an incorrect answer. Please read the instructions again carefully.'); $('#instrBox').show(); - $('#instrQ'+now_question).hide(); - $('input[name=instrQ'+now_question+']:checked').prop('checked', false); - if (instr.index == INSTR_Q_INDICES[0]) { // first instruction question - instr.index = 0; // skipping the first page of instructions if they are reading them the second time - } else { // second instruction question - instr.index = INSTR_Q_INDICES[0]+2; - } + $('#instrQBox').hide(); + $('input[name="instrQ"]:checked').prop('checked', false); + instr.index = -1; } else { instr.next(); + $('#instrQBox').hide(); $('#instrBox').show(); - $('#instrQ'+now_question).hide(); - $('input[name=instrQ'+now_question+']:checked').prop('checked', false); } } @@ -283,35 +329,16 @@ function SHOW_CONSENT() { $('#instrBoxScroll').attr('id', 'instrBox'); $('#instrBox').hide(); $('#consentBox').hide(); - trial.firstCondition = instr.condition[0]; - SHOW_BLOCK(trial.firstCondition); + subj.saveAttrition(); + SHOW_BLOCK(); } }); } -function READY_SECOND_BLOCK() { - $('#nextButton').hide(); - $(document).keyup(function(e) { - if (e.which == 32) { // the 'space' key - $(document).off('keyup'); - $('#instrBox').hide(); - $('#consentBox').hide(); - SHOW_BLOCK(instr.condition[1]); - } - }); -} - - - -var instr_func_dict = {}; -instr_func_dict[INSTR_Q_INDICES[0]] = SHOW_INSTR_QUESTION; -instr_func_dict[INSTR_Q_INDICES[0]+1] = SHOW_CONSENT; -instr_func_dict[INSTR_Q_INDICES[1]] = SHOW_INSTR_QUESTION; -instr_func_dict[instr_photo_first.length-1] = READY_SECOND_BLOCK; - var instr_options = { - funcDict: instr_func_dict, - qConditions: BLOCK_TYPES + text: instr_text, + funcDict: INSTR_FUNC_DICT, + qConditions: ['onlyQ'] }; @@ -323,28 +350,26 @@ var instr_options = { // ## ## ## ## ## ## ## // ## ## ## #### ## ## ######## -function SHOW_BLOCK(condition) { - if (condition == 'photo') { - var now_label = 'appealing'; - } - else { - var now_label = 'attractive'; - } - $('#r6label').html('Very visually
' + now_label); - $('#r1label').html('Very not
visually ' + now_label); +const TRIAL_TITLES = [ + 'num', + 'date', + 'subjStartTime', + 'trialNum', + 'stimName', + 'inView', + 'response', + 'rt']; + +function SHOW_BLOCK() { $('#instrBox').hide(); $('#trialBox').show(); - trial.blockNum = trial.blockNum + 1; - trial.condition = condition; - trial.trialNum = -trial.pracTrialN; - trial.trialList = TRIAL_LIST_DICT[condition]; - trial.pracList = PRAC_TRIAL_LIST_DICT[condition]; + subj.detectVisibilityStart(); trial.run(); } function TRIAL_UPDATE(formal_trial, last, this_trial, next_trial, path) { trial.stimName = this_trial; - $('#progress').text(trial.progress + '% completed'); + $('#progress').text(trial.progress); $('#testImg').attr('src', path + this_trial); if (!last) { $('#bufferImg').attr('src', path + next_trial); @@ -353,44 +378,25 @@ function TRIAL_UPDATE(formal_trial, last, this_trial, next_trial, path) { function TRIAL() { $('#testImg').show(); - $('.ratingButton').mouseup(function(event) { - $('.ratingButton').unbind('mouseup'); + $('.aesButton').mouseup(function(event) { + $('.aesButton').unbind('mouseup'); trial.inView = CHECK_FULLY_IN_VIEW($('#testImg')); $('#testImg').hide(); trial.end(event.target.id); }); } -function END_BLOCK() { +function END_EXPT() { $('#trialBox').hide(); - $('#nextButton').show(); - if (trial.blockNum == BLOCK_N) { - trial.save(); - $('#questionsBox').show(); - } - else { - instr.next(); - $('#instrBox').show(); - } + $('#likertBox').show(); + trial.save(); } -function END_TO_PROLIFIC() { - window.location.href = 'https://app.prolific.co/submissions/complete?cc=4EECC2A1'; +function END_TO_SONA() { + const COMPLETION_URL = /*automatically generated url XXX */ + subj.id; + window.location.href = COMPLETION_URL; } -const TRIAL_TITLES = [ - 'num', - 'date', - 'subjStartTime', - 'firstCondition', - 'blockNum', - 'condition', - 'trialNum', - 'stimName', - 'inView', - 'response', - 'rt']; - var trial_options = { subj: 'pre-define', // assign after subj is created pracTrialN: PRACTICE_TRIAL_N, @@ -400,11 +406,11 @@ var trial_options = { dataFile: TRIAL_FILE, savingScript: SAVING_SCRIPT, savingDir: SAVING_DIR, - trialList: false, // assign in each block - pracList: false, // assign in each block + trialList: TRIAL_LIST, + pracList: PRACTICE_LIST, intertrialInterval: INTERTRIAL_INTERVAL, updateFunc: TRIAL_UPDATE, trialFunc: TRIAL, - endExptFunc: END_BLOCK, + endExptFunc: END_EXPT, progressInfo: true } diff --git a/Example Experiment/save.php b/Example Experiment/save.php index 7f5c0f5..1237c97 100644 --- a/Example Experiment/save.php +++ b/Example Experiment/save.php @@ -1,5 +1,9 @@ diff --git a/Example Experiment/style.css b/Example Experiment/style.css index 4701d4f..0e3a9cc 100644 --- a/Example Experiment/style.css +++ b/Example Experiment/style.css @@ -1,59 +1,87 @@ /* Yi-Chia Chen */ - +* { + box-sizing: border-box; +} body{ - color: #dddddd; - background: #404040; + color: #ddd; + background: #444; margin: 0; padding: 0; - font-family: 'Quicksand', Sans-serif; - font-size: 20px; + font-family: 'Raleway', Sans-serif; + font-size: 22px; line-height: 30px; - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Safari */ - -khtml-user-select: none; /* Konqueror HTML */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } div.pageBox{ display: none; position: relative; - margin: 20px auto auto auto; - padding: 5px; + margin: 50px auto auto auto; width: 800px; } +#captchaBox { + position: fixed; + top: 50%; + right: 50%; + transform: translateY(-50%) translateY(-25px) translateX(50%); +} +.captchaText { + position: relative; + text-align: center; +} +.h-captcha { + position: relative; + text-align: center; +} #instrBox { position: fixed; top: 50%; right: 50%; - -webkit-transform: translateY(-50%) translateY(-25px) translateX(50%); - -moz-transform: translateY(-50%) translateY(-25px) translateX(50%); - -ms-transform: translateY(-50%) translateY(-25px) translateX(50%); - -o-transform: translateY(-50%) translateY(-25px) translateX(50%); transform: translateY(-50%) translateY(-25px) translateX(50%); } #instrBoxScroll { position: relative; } -#instrText { +/* #instrText { margin: 0; padding: 0; +} */ +.consentInstr { + color: #bbb; + font-size: 20px; + line-height: 60px; } #consentBox { - margin: 20px auto; + margin: 40px 0; + width: 100%; } #consentTextBox { - position: relative; + /* position: relative; */ margin: 0 auto; - padding: 20px 20px 20px 20px; + padding: 20px; + width: 95%; font-size: 16px; - line-height: 24px; - background: #606060; + line-height: 25px; + background: #354545; border-style: dotted; - border-color: #dddddd; + border-color: #ddd; +} +#instrImg { + display: none; + position: relative; + margin: 0 auto; + padding: 30px; +} +#ratingExample { + display: none; +} +.warning { + color: #ffc8c8; } #debriefingBox{ - margin: 20px auto 100px auto; + margin: 20px auto 50px auto; } .button { position: relative; @@ -63,53 +91,53 @@ div.pageBox{ font-size: 18px; line-height: 35px; text-align: center; - -moz-border-radius: 8px; - -webkit-border-radius: 8px; border-radius: 8px; background: #252525; color: #dddddd; + cursor: pointer; } .button:hover { - cursor: pointer; - background: #303030; + background: #333; } .button:active { - background: #111111; + background: #111; } #trialBox{ position: relative; margin: 20px auto 0 auto; - width: 650px; - height: 550px; + /* height: 550px; */ } -#progress{ - position: absolute; +.progressBar{ + position: relative; + margin: 10px 0; width: 100%; + height: 30px; text-align: center; } -#testImg{ - display: none; - position: absolute; - top: 50px; - width: 650px; - height: 450px; +.testImgPlaceHolder { + position: relative; + margin: 0 auto; + width: 650px; + height: 450px; } #bufferDiv{ display: none; } .ratingContainer { - position: absolute; - top: 530px; - width: 650px; + position: relative; + padding: 30px; + width: 100%; + height: 114px; } .ratingButton{ position: absolute; - width: 45px; - height: 35px; + width: 110px; + height: 54px; color: black; border: 2px solid white; text-align: center; - line-height: 35px; + font-size: 16px; + line-height: 20px; cursor: pointer; } .ratingButton:hover{ @@ -118,93 +146,105 @@ div.pageBox{ .ratingButton:active{ background: white; } -#r1{ - left: 165px; +.r1{ + left: 20px; background: #e6e6ff; } -#r2{ - left: 220px; +.r2{ + left: 150px; background: #ccccff; } -#r3{ - left: 275px; +.r3{ + left: 280px; background: #b3b3ff; } -#r4{ - left: 330px; +.r4{ + left: 410px; background: #9999ff; } -#r5{ - left: 385px; +.r5{ + left: 540px; background: #8080ff; } -#r6{ - left: 440px; +.r6{ + left: 670px; background: #6666ff; } -#r1label, #r6label{ - position: absolute; - top: 45px; - height: 35px; - color: #dddddd; - line-height: 35px; -} -#r1label{ - left: 50px; - text-align: right; -} -#r6label{ - left: 440px; +.ratingLabels { + margin: 0; + padding: 5px 0; } /* Questions */ +#aqContainer { + display: none; +} +.aqQ { + padding: 100px 0 10px 0; + text-align: center; +} +.aqButton:hover{ + border-color: #006060; +} +.aqButton:active{ + background: white; +} +.aq4 { + background: #d9f2f2; +} +.aq3 { + background: #a0dede; +} +.aq2 { + background: #68caca; +} +.aq1 { + background: #3caaaa; +} .likertWrap{ position: relative; - margin: 20px auto auto auto; - width: 630px; - height: 120px; + margin: 20px auto 0 auto; + width: 560px; + height: 123px; } .likertLine{ - position: absolute; - top: 9px; - left: 45px; - margin: 0 0 0 0; - width: 540px; - height: 6px; + position: relative; + margin: 0 auto; + width: 480px; + height: 5px; background: #ccc; - z-index:1; + z-index: 1; } .likert{ position: relative; - margin: 0 0 0 0; - width: 90px; - float:left; - z-index:2; + float: left; + margin: 0 auto; + top: -18px; + width: 80px; + text-align: center; + z-index: 2; } -.qMain { - margin: 0; - padding: 0; +.likertText{ + margin: 5px auto; +} +.boldText { font-weight: bold; } -.qSupporting { - margin: 0; - padding: 0; - color: #bbbbbb; +.smallDimText { + color: #bbb; font-size: 16px; line-height: 25px; } .option { position: relative; - width: 100%; - -moz-border-radius: 8px; - -webkit-border-radius: 8px; - border-radius: 8px; - background: #555555; margin: 10px 0; padding: 7px; + width: 100%; + border-radius: 8px; + background: #555; + cursor: pointer; } .option:hover { - cursor: pointer; background: gray; } .optionRadio { @@ -212,17 +252,13 @@ div.pageBox{ margin: 0; left: 13px; top: 50%; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - -o-transform: translateY(-50%); transform: translateY(-50%); cursor: pointer; } .optionText { position: relative; left: 28px; - width: 765px; + width: calc(100% - 28px); } input[type="text"], textarea { @@ -230,6 +266,6 @@ textarea { } #qProblems { position: relative; - width: 100%; margin: 10px 0; + width: 100%; } diff --git a/Example Experiment/subjNum.php b/Example Experiment/subjNum.php index 5981946..3c4d73a 100644 --- a/Example Experiment/subjNum.php +++ b/Example Experiment/subjNum.php @@ -1,10 +1,18 @@ diff --git a/Example Experiment/subjNum_aesAtt.txt b/Example Experiment/subjNum_aesAtt.txt deleted file mode 100644 index c227083..0000000 --- a/Example Experiment/subjNum_aesAtt.txt +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/Example Experiment/subj_aesAtt.txt b/Example Experiment/subj_aesAtt.txt deleted file mode 100644 index e69de29..0000000 diff --git a/Example Experiment/trial_aesAtt.txt b/Example Experiment/trial_aesAtt.txt deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index fbd8f29..507d97b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ This is a tiny js/jQuery toolbox for psychological experiment by Yi-Chia Chen. ## Version History +- 3.0.0 (2020.05.18): Add tab switching detection, + Add rest (untested), + Add instruction reading time recording, + Add visit recording besides attrition recording, + Add CAPITALIZE() to func.js, + Delete ID recording for privacy reasons, + Update the example experiment to include space key detection, hCaptcha, directory/file creation, and sona credit granting, + Clean up syntax + - Used in Autistic Traits & Aesthetic Taste - 2.0.1 (2020.04.09): Fix a bug - 2.0.0 (2020.04.06): Add a progress indicator, Add detection for elements in full view, @@ -9,7 +18,7 @@ This is a tiny js/jQuery toolbox for psychological experiment by Yi-Chia Chen. Separate obtain subject ID into its own method, Add subj.obtainCondition(), Add an example experiment, - fixed some formatting (but not all...) + Fixed some formatting (but not all...) - Used in Aesthetic Versus Attractiveness - 1.1.0 (2020.01.20): Renamed the files and fixed formatting style - Used in Future Bias Ball on Hill @@ -25,11 +34,7 @@ This is a tiny js/jQuery toolbox for psychological experiment by Yi-Chia Chen. ## Planned Improvements ### Features -- "Press space bar to continue" to limit access from phones -- Listen to tab switching and add instructions about it - End the experiment if people refresh after a certain point in formal experiment -- Add rest (to let them know how much they've completed) -- Show alert with a customized floating div in the center that dim everything else ### Cleaning up - Make all functions do one thing only diff --git a/expt.js b/expt.js index ce141ad..6845a1a 100644 --- a/expt.js +++ b/expt.js @@ -20,16 +20,16 @@ class subjObject { condition: false, conditionList: [''], titles: [''], - mturk: true, - prolific: false, - idFunc: false, invalidIDFunc: false, + validIDFunc: false, viewportMinW: 0, viewportMinH: 0, savingScript: 'save.php', - attritionFile: 'attrition', - subjFile: 'subj', - savingDir: 'testing' + attritionFile: 'attrition.txt', + visitFile: 'visit.txt', + subjFile: 'subj.txt', + savingDir: 'data/testing', + handleVisibilityChange: function(){}, }, options); if (this.num == 'pre-post') { this.obtainSubjNum(this.subjNumScript, this.subjNumFile); @@ -39,6 +39,8 @@ class subjObject { this.date = FORMAT_DATE(this.dateObj, 'UTC', '-', true); this.startTime = FORMAT_TIME(this.dateObj, 'UTC', ':', true); this.userAgent = window.navigator.userAgent; + this.hiddenCount = 0; + this.hiddenDurations = []; } obtainSubjNum(subjNumScript, subjNumFile) { @@ -52,7 +54,7 @@ class subjObject { function SUBJ_NUM_UPDATE_FAILED() { that.num = -999; } - POST_DATA(subjNumScript, { 'fileName': subjNumFile }, SUBJ_NUM_UPDATE_SUCCEEDED, SUBJ_NUM_UPDATE_FAILED); + POST_DATA(subjNumScript, { 'directory_path': this.savingDir, 'file_name': this.subjNumFile }, SUBJ_NUM_UPDATE_SUCCEEDED, SUBJ_NUM_UPDATE_FAILED); } assignCondition() { @@ -67,49 +69,56 @@ class subjObject { var interval_id = setInterval(CHECK_SUBJ_NUM, 10); } - obtainID(){ - if (this.mturk) { - this.id = this.obtainWorkerID(); - } else if(this.prolific){ - this.id = this.obtainProlificID(); - } else if (this.idFunc != false) { - this.id = this.idFunc(); - } + saveVisit() { + var data = 'subjNum\tstartDate\tstartTime\tid\tuserAgent\tinView\tviewportW\tviewportH\n'; + this.viewport = this.viewportSize; + this.inView = this.viewport['inView']; + this.viewportW = this.viewport['w']; + this.viewportH = this.viewport['h']; + var dataList = [this.num, this.date, this.startTime, this.id, this.userAgent, this.inView, this.viewportW, this.viewportH]; + data += LIST_TO_FORMATTED_STRING(dataList); + var postData = { + 'directory_path': this.savingDir, + 'file_name': this.visitFile, + 'data': data + }; + $.ajax({ + type: 'POST', + url: this.savingScript, + data: postData, + }); } - obtainWorkerID() { - var workerID = GET_PARAMETERS('workerId', 'getFailed'); - if (workerID == 'getFailed') { - workerID = prompt('Please enter your worker ID:', ''); + getID(get_variable) { + var id = GET_PARAMETERS(get_variable, null); + var invalid_id = (id == null); + if (!invalid_id) { + id = id.replace(/\s+/g, ''); + invalid_id = (id == ''); } - var invalidID = (workerID == null); - if (!invalidID) { - workerID = workerID.replace(/\s+/g, ''); - invalidID = (workerID == ''); - } - if (invalidID) { - this.invalidIDFunc(); + if (invalid_id) { + if (this.invalidIDFunc !== false) { + this.invalidIDFunc(); + } return null; } else { - return workerID; + if (this.validIDFunc !== false) { + this.validIDFunc(); + } + return id; } } - obtainProlificID() { - var prolificID = GET_PARAMETERS('PROLIFIC_PID', 'getFailed'); - if (prolificID == 'getFailed') { - prolificID = prompt('Please enter your Prolific ID:', ''); + checkID(id) { + var invalid_id = (id == null); + if (!invalid_id) { + id = id.replace(/\s+/g, ''); + invalid_id = (id == ''); } - var invalidID = (prolificID == null); - if (!invalidID) { - prolificID = prolificID.replace(/\s+/g, ''); - invalidID = (prolificID == ''); - } - if (invalidID) { - this.invalidIDFunc(); + if (invalid_id) { return null; } else { - return prolificID; + return id; } } @@ -134,10 +143,9 @@ class subjObject { var dataList = [this.num, this.date, this.startTime, this.id, this.userAgent, this.inView, this.viewportW, this.viewportH]; data += LIST_TO_FORMATTED_STRING(dataList); var postData = { - 'id': this.attritionFile, //filename to save the data with - 'experimenter': 'ycc', // experimenter folder to save it in - 'experimentName': this.savingDir, //directory to save it in - 'curData': data // data to save + 'directory_path': this.savingDir, + 'file_name': this.attritionFile, + 'data': data }; $.ajax({ type: 'POST', @@ -153,10 +161,9 @@ class subjObject { var dataList = LIST_FROM_ATTRIBUTE_NAMES(this, this.titles); this.data += LIST_TO_FORMATTED_STRING(dataList); var postData = { - 'id': this.subjFile, //filename to save the data with - 'experimenter': 'ycc', // experimenter folder to save it in - 'experimentName': this.savingDir, //directory to save it in - 'curData': this.data // data to save + 'directory_path': this.savingDir, + 'file_name': this.subjFile, + 'data': this.data }; $.ajax({ type: 'POST', @@ -164,6 +171,16 @@ class subjObject { data: postData, }); } + + detectVisibilityStart() { + var that = this; + document.addEventListener('visibilitychange', that.handleVisibilityChange); + } + + detectVisibilityEnd() { + var that = this; + document.removeEventListener('visibilitychange', that.handleVisibilityChange); + } } @@ -184,8 +201,8 @@ class trialObject { titles: '', stimPath: 'Stimuli/', dataFile: '', - savingScript: 'mySave.php', - savingDir: 'testing', + savingScript: 'save.php', + savingDir: 'data/testing', trialList: [], pracList: [], intertrialInterval: 0.5, @@ -208,24 +225,21 @@ class trialObject { this.progress = Math.round( 100 * (this.trialNum+this.pracTrialN) / (this.trialN+this.pracTrialN) ); } this.trialNum++; - var formal = this.trialNum > 0; - if (formal) { - var last = this.trialNum == this.trialN; - this.thisTrial = this.trialList.pop(); - } else { - var last = this.trialNum == 0; - this.thisTrial = this.pracList.pop(); - } - if (!last) { - if (formal) { - var nextTrial = this.trialList[this.trialList.length - 1]; + const FORMAL = this.trialNum > 0; + const LAST = FORMAL ? this.trialNum == this.trialN : this.trialNum == 0; + this.thisTrial = FORMAL ? this.trialList.pop() : this.pracList.pop(); + + var that = this; + function findNextTrial(last, formal) { + if (last){ + return false } else { - var nextTrial = this.pracList[this.pracList.length - 1]; + return formal ? that.trialList[that.trialList.length - 1] : that.pracList[that.pracList.length - 1]; } - } else { - var nextTrial = false; } - this.updateFunc(formal, last, this.thisTrial, nextTrial, this.stimPath); + const NEXT_TRIAL = findNextTrial(LAST, FORMAL); + + this.updateFunc(FORMAL, LAST, this.thisTrial, NEXT_TRIAL, this.stimPath); var that = this; const START_STIM = function() { @@ -252,12 +266,23 @@ class trialObject { } } + rest(box_element, text_element, callback, callback_parameters) { + text_element.html('You are done with '+ this.progress + '% of the study!

Take a short break now and hit space to continue whenever you are ready.') + box_element.show(); + $(document).keyup(function(e) { + if (e.which == 32) { + $(document).off('keyup'); + box_element.hide(); + callback(callback_parameters); + } + }); + } + save() { var postData = { - 'id': this.dataFile, //filename to save the data with - 'experimenter': 'ycc', // experimenter folder to save it in - 'experimentName': this.savingDir, //directory to save it in - 'curData': this.allData // data to save + 'directory_path': this.savingDir, + 'file_name': this.dataFile, + 'data': this.allData // data to save }; $.ajax({ type: 'POST', @@ -290,15 +315,27 @@ class instrObject { for (var i=0;i= 0) && (bottom <= h) && (left >= 0) && (right <= w); return is_visible; -} \ No newline at end of file +} + +function CAPITALIZE(s) { + if (typeof s !== 'string'){ + return ''; + } else { + return s.charAt(0).toUpperCase() + s.slice(1); + } +} + +function CREATE_RANDOM_REPEAT_BEGINNING_LIST(stim_list, repeat_trial_n) { + const REPEAT_LIST = SHUFFLE_ARRAY(stim_list.slice()).splice(0, repeat_trial_n); + return REPEAT_LIST.concat(stim_list); +}