mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 19:20:55 +00:00
173 lines
7.5 KiB
JavaScript
173 lines
7.5 KiB
JavaScript
/**
|
|
* jspsych-maxdiff
|
|
* Angus Hughes
|
|
*
|
|
* a jspsych plugin for maxdiff/conjoint analysis designs
|
|
*
|
|
*/
|
|
|
|
jsPsych.plugins['maxdiff'] = (function () {
|
|
|
|
var plugin = {};
|
|
|
|
plugin.info = {
|
|
name: 'maxdiff',
|
|
description: '',
|
|
parameters: {
|
|
alternatives: {
|
|
type: jsPsych.plugins.parameterType.STRING,
|
|
pretty_name: 'Alternatives',
|
|
array: true,
|
|
default: undefined,
|
|
description: 'Alternatives presented in the maxdiff table.'
|
|
},
|
|
labels: {
|
|
type: jsPsych.plugins.parameterType.STRING,
|
|
array: true,
|
|
pretty_name: 'Labels',
|
|
default: undefined,
|
|
description: 'Labels to display for left and right response columns.'
|
|
},
|
|
randomize_alternative_order: {
|
|
type: jsPsych.plugins.parameterType.BOOL,
|
|
pretty_name: 'Randomize Alternative Order',
|
|
default: false,
|
|
description: 'If true, the order of the alternatives will be randomized'
|
|
},
|
|
preamble: {
|
|
type: jsPsych.plugins.parameterType.STRING,
|
|
pretty_name: 'Preamble',
|
|
default: '',
|
|
description: 'String to display at top of the page.'
|
|
},
|
|
button_label: {
|
|
type: jsPsych.plugins.parameterType.STRING,
|
|
pretty_name: 'Button Label',
|
|
default: 'Continue',
|
|
description: 'Label of the button.'
|
|
},
|
|
required: {
|
|
type: jsPsych.plugins.parameterType.BOOL,
|
|
pretty_name: 'Required',
|
|
default: false,
|
|
description: 'Makes answering the alternative required.'
|
|
}
|
|
}
|
|
}
|
|
|
|
plugin.trial = function (display_element, trial) {
|
|
|
|
var html = "";
|
|
// inject CSS for trial
|
|
html += '<style id="jspsych-maxdiff-css">';
|
|
html += ".jspsych-maxdiff-statement {display:block; font-size: 16px; padding-top: 40px; margin-bottom:10px;}" +
|
|
'table.jspsych-maxdiff-table {border-collapse: collapse; padding: 15px; margin-left: auto; margin-right: auto;}' +
|
|
'table.jspsych-maxdiff-table td, th {border-bottom: 1px solid #dddddd; text-align: center; padding: 8px;}' +
|
|
'table.jspsych-maxdiff-table tr:nth-child(even) {background-color: #dddddd;}';
|
|
html += '</style>';
|
|
|
|
// show preamble text
|
|
if (trial.preamble !== null) {
|
|
html += '<div id="jspsych-maxdiff-preamble" class="jspsych-maxdiff-preamble">' + trial.preamble + '</div>';
|
|
}
|
|
html += '<form id="jspsych-maxdiff-form">';
|
|
|
|
// add maxdiff options ///
|
|
// first generate alternative order, randomized here as opposed to randomizing the order of alternatives
|
|
// so that the data are always associated with the same alternative regardless of order.
|
|
var alternative_order = [];
|
|
for (var i = 0; i < trial.alternatives.length; i++) {
|
|
alternative_order.push(i);
|
|
}
|
|
if (trial.randomize_alternative_order) {
|
|
alternative_order = jsPsych.randomization.shuffle(alternative_order);
|
|
}
|
|
|
|
// Start with column headings
|
|
var maxdiff_table = '<table class="jspsych-maxdiff-table"><tr><th id="jspsych-maxdiff-left-label">' + trial.labels[0] + '</th><th></th><th id="jspsych-maxdiff-right-label">' + trial.labels[1] + '</th></tr>';
|
|
|
|
// construct each row of the maxdiff table
|
|
for (var i = 0; i < trial.alternatives.length; i++) {
|
|
var alternative = trial.alternatives[alternative_order[i]];
|
|
// add alternative
|
|
maxdiff_table += '<tr><td><input class= "jspsych-maxdiff-alt-' + i.toString() + '" type="radio" name="left" data-name = ' + alternative_order[i].toString() + ' /><br></td>';
|
|
maxdiff_table += '<td id="jspsych-maxdiff-alternative-' + i.toString() + '">' + alternative + '</td>';
|
|
maxdiff_table += '<td><input class= "jspsych-maxdiff-alt-' + i.toString() + '" type="radio" name="right" data-name = ' + alternative_order[i].toString() + ' /><br></td></tr>';
|
|
}
|
|
maxdiff_table += '</table><br><br>';
|
|
html += maxdiff_table;
|
|
|
|
// add submit button
|
|
var enable_submit = trial.required == true ? 'disabled = "disabled"' : '';
|
|
html += '<input type="submit" id="jspsych-maxdiff-next" class="jspsych-maxdiff jspsych-btn" ' + enable_submit + ' value="' + trial.button_label + '"></input>';
|
|
html += '</form>';
|
|
|
|
display_element.innerHTML = html;
|
|
|
|
// function to control responses
|
|
// first checks that the same alternative cannot be endorsed in the left and right columns simultaneously.
|
|
// then enables the submit button if the trial is required.
|
|
const left_right = ["left", "right"]
|
|
left_right.forEach(function(p) {
|
|
// Get all elements either 'left' or 'right'
|
|
document.getElementsByName(p).forEach(function(alt) {
|
|
alt.addEventListener('click', function() {
|
|
// Find the opposite (if left, then right & vice versa) identified by the class (jspsych-maxdiff-alt-1, 2, etc)
|
|
var op = alt.name == 'left' ? 'right' : 'left';
|
|
var n = document.getElementsByClassName(alt.className).namedItem(op);
|
|
// If it's checked, uncheck it.
|
|
if (n.checked) {
|
|
n.checked = false;
|
|
}
|
|
|
|
// check response
|
|
if (trial.required){
|
|
// Now check if one of both left and right have been enabled to allow submission
|
|
var left_checked = [...document.getElementsByName('left')].some(c => c.checked);
|
|
var right_checked = [...document.getElementsByName('right')].some(c => c.checked);
|
|
if (left_checked && right_checked) {
|
|
document.getElementById("jspsych-maxdiff-next").disabled = false;
|
|
} else {
|
|
document.getElementById("jspsych-maxdiff-next").disabled = true;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get the data once the submit button is clicked
|
|
// Get the data once the submit button is clicked
|
|
display_element.querySelector('#jspsych-maxdiff-form').addEventListener('submit', function(e){
|
|
e.preventDefault();
|
|
|
|
// measure response time
|
|
var endTime = performance.now();
|
|
var response_time = endTime - startTime;
|
|
|
|
// get the alternative by the data-name attribute, allowing a null response if unchecked
|
|
get_response = function(side){
|
|
var col = display_element.querySelectorAll('[name=\"' + side + '\"]:checked')[0];
|
|
if (col === undefined){
|
|
return null;
|
|
} else {
|
|
var i = parseInt(col.getAttribute('data-name'));
|
|
return trial.alternatives[i];
|
|
}
|
|
}
|
|
|
|
// data saving
|
|
var trial_data = {
|
|
rt: response_time,
|
|
labels: {left: trial.labels[0], right: trial.labels[1]},
|
|
response: {left: get_response('left'), right: get_response('right')}
|
|
};
|
|
|
|
// next trial
|
|
jsPsych.finishTrial(trial_data);
|
|
});
|
|
|
|
var startTime = performance.now();
|
|
};
|
|
|
|
return plugin;
|
|
})(); |