/* * jspsych-form (Version 1.1) * Junyan Qi * * plugin for generating a form from a json schema. * * Documentation: docs.jspsych.org * * Dependency: jsPsych, Material Design Lite * * Required links in the html file: SCHEMA EXAMPLE: var schema = { form: {form_title: "Test #1", ribbon_bg: "somePicture", form_description: ""}, ## for more avaliable settings, check the attributes of classes for each type "Question #1": {type: "short answer", label: ""}, "Question #2": {type: "password"}, "Question #3": {type: "checkbox", labels: ["option1", "option2"], images:[image1, image2], values:[1, 2, 3, 4]}, "Question #4": {type: "radio", labels: ["option1", "option2"]}, ## will automatically fill valuse "Question #5": {type: "range"}, ## not display question "Question #6": {type: "dropdown", needQuestion: false}, ## insert paragraph (similar for form) "Question #7": {type: "long answer", question_description: ""}, ## better styled "Question #8
Contents
": {type: "long answer"}, onSubmit: {label: "Next"} }; */ jsPsych.plugins["form"] = (function() { var plugin = {}; plugin.trial = function(display_element, trial) { // set default values for parameters trial.schema = trial.schema || {}; trial.schema.onSubmit = trial.schema.onSubmit || { label: "Submit" }; var tags = createForm(display_element, trial.schema); var form = tags[0]; var questions = tags[1]; var button = tags[2]; var trial_data = { Form: form.form_title, Form_Description: form.form_description }; function end_trial() { var customized_output = undefined; if (trial.schema.onSubmit.onclick != undefined) var customized_output = docReady(trial.schema.onSubmit.onclick); for (var i = 0; i < questions.length; i++) { var question = questions[i] var key = question.question || question.label || question.type; var type = question.type; var value; switch (type) { // codes for select component case "select": // check answer if (question.correct == undefined) value = document.getElementById(question.label_id).value; else { var tempVal = document.getElementById(question.label_id).value; value = { "Result": (question.correct == tempVal) ? "Correct" : "Wrong", "Expected Answer": question.correct, "Answer Received": tempVal }; } // check answer // check required field if (question.required != "") { if (!value || value["Answer Received"] == "") { focus(question.label_id); return; } } // check required field break; // codes for toggle type component case "checkbox": case "radio": case "switch": var flag = false; var checed; var asExpected = true; value = { "Result": "Correct", "Expected Answers": [], "Answers Chosen": [] }; // check answer var expectedAnswers = Object.keys(question.correctAnswers); for (var j = 0; j < expectedAnswers.length; j++) { var k = expectedAnswers[j]; if (question.correctAnswers[k]) value["Expected Answers"].push(k) } for (var j = 0; j < question.products.length; j++) { product = question.products[j]; checked = document.getElementById(product.id).checked; if (checked) { flag = true; value["Answers Chosen"].push(product.value); } if (product.correct != checked) asExpected = false; } value["Result"] = (asExpected) ? "Correct" : "Wrong"; // check answer // check required field if (question.required != "" && !flag) { document.getElementById(question.id).scrollIntoView(); focus(question.products[0].id); return; } // check required field break; // codes for other type questions default: // check answer if (question.correct == undefined) value = document.getElementById(question.id).value; else { // standardize the inputs to string var tempVal = "{0}".format(document.getElementById(question.id).value); var correct = "{0}".format(question.correct); value = { "Result": (correct.trim() == tempVal.trim()) ? "Correct" : "Wrong", "Expected Answer": question.correct, "Answer Received": document.getElementById(question.id).value }; } // check answer // check required field if (question.required != "" && (!value || value["Answer Received"] == "")) { focus(question.id); return; } break; } // check required field trial_data[key] = value; } if (customized_output) trial_data["#Customized Output#"] = customized_output; display_element.innerHTML = ''; jsPsych.finishTrial(trial_data); } document.getElementById(button.id).onclick = function() { end_trial(); } }; // Help functions if (!String.prototype.format) { String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; } if (!String.prototype.startsWith) { String.prototype.startsWith = function(prefix) { return this.slice(0, prefix.length) === prefix; }; } if (!String.prototype.endsWith) { String.prototype.endsWith = function(suffix) { return this.indexOf(suffix, this.length - suffix.length) !== -1; }; } if (!String.prototype.capitalize) { String.prototype.capitalize = function() { return this.charAt(0).toUpperCase() + this.slice(1); } } function inherit(proto) { function F() {} F.prototype = proto; return new F; } function focus(id) { document.getElementById(id).focus(); } function docReady(callback) { if (callback == undefined) return; if (document.readyState === "complete" || document.readyState !== "loading" && !document.documentElement.doScroll) return callback(); else document.addEventListener("DOMContentLoaded", callback); } // track unique ids for each element var __INPUT_CHECKBOX = 0; var __INPUT_DATE = 0; var __INPUT_DATETIME = 0; var __INPUT_DATETIME_LOCAL = 0; var __INPUT_EMAIL = 0; var __INPUT_FILE = 0; var __INPUT_MONTH = 0; var __INPUT_NUMBER = 0; var __INPUT_PASSWORD = 0; var __INPUT_RADIO = 0; var __INPUT_RANGE = 0; var __INPUT_SEARCH = 0; var __INPUT_TEL = 0; var __INPUT_TEXT = 0; var __INPUT_TIME = 0; var __INPUT_URL = 0; var __INPUT_WEEK = 0; var __TOGGLE_GROUP = 0; var __BUTTON = 0; var __SELECT = 0; var __OPTION = 0; var __TEXTAREA = 0; var __FORM = 0; function createForm(display_element, schema) { schema.form = schema.form || {}; var form = new Form(display_element, schema.form); var form_id = form.id; var questions = []; for (var i in Object.keys(schema)) { i = Object.keys(schema)[i] if (i == "form" || i == "onSubmit") continue; item = schema[i] item.question = item.question || i; var type = item.type; var question; switch (type) { // major uses case "short answer": item.type = "text"; case "text": question = new InputText(form_id, item); break; case "long answer": item.type = "textarea"; case "textarea": question = new Textarea(form_id, item); break; case "dropdown": question = new Dropdown(form_id, item); break; case "checkbox": case "switch": case "radio": question = new ToggleGroup(form_id, item); break; case "range": question = new Range(form_id, item); break; // minor features case "date": question = new InputDate(form_id, item); break; case "datetime": question = new InputDatetime(form_id, item); break; case "datetime-local": question = new InputDatetimeLocal(form_id, item); break; case "email": question = new InputEmail(form_id, item); break; case "file": question = new UploadFile(form_id, item); break; case "month": question = new InputMonth(form_id, item); break; case "password": question = new InputPassword(form_id, item); break; case "search": question = new InputSearch(form_id, item); break; case "telephone": question = new InputTel(form_id, item); break; case "time": question = new InputTime(form_id, item); break; case "url": question = new InputUrl(form_id, item); break; case "week": question = new InputWeek(form_id, item); break; } questions.push(question); } var button = new Button(form_id, schema.onSubmit); return [form, questions, button]; } /* ############################################################ # Form # Form does the following: render a MDL style form # # Arbitrary settings: # item.type <-- automatically assigned # item.id <-- automatically assigned # # @param display_element # @param item --> a set of values for setting # @param item.form_title # @param item.form_title_size # @param item.form_title_color # @param item.form_description # @param item.form_description_color # @param item.form_description_size # layout settings: # @param item.layout_color # @param item.ribbon_color # @param item.ribbon_height # @param item.ribbon_bg # @param item.ribbon_bg_size # @param item.content_bg_color # @param item.content_text_color ############################################################ # @return # ############################################################ */ function Form(display_element, item = {}) { this.type = "form"; this.display_element = display_element || "body"; this.id = item.id || "{0}_{1}".format(this.type, __FORM++); // this.layout_color = item.layout_color || "grey-300"; this.layout_color = item.layout_color || "white-300"; this.boxShadow = item.boxShadow || 0; // this.ribbon_color = item.ribbon_color || '#3F51B5'; this.ribbon_color = item.ribbon_color || 'white-300'; this.ribbon_height = item.ribbon_height || '40vh'; this.ribbon_bg = (item.ribbon_bg) ? "background: url({0});".format(item.ribbon_bg) : ""; this.ribbon_bg_size = item.ribbon_bg_size || "background-size: contain cover;"; this.ribbon = ''.format( this.ribbon_height, this.ribbon_color, this.ribbon_bg, this.ribbon_bg_size); // this.content_bg_color = item.content_bg_color || "grey-100"; this.content_bg_color = item.content_bg_color || "white-300"; this.context_text_color = item.context_text_color || "black-800"; this.form_title = item.form_title || "Untitled Form"; this.form_title_size = item.form_title_size || "40px"; this.form_title_color = item.form_title_color || "black-800"; this.form_description = item.form_description || ""; this.form_description_size = item.form_description_size || "14px"; this.form_description_color = item.form_description_color || "grey-600"; if (this.form_description) this.form_description_html = '{2}
'.format(this.form_description_size, this.form_description_color, this.form_description); else this.form_description_html = ""; this.form_title_html = ''.format( this.form_title_size, this.form_title_color, this.form_title, this.form_description_html) // this.content = '{2}
'.format( this.question_description_size, this.question_description_color, this.question_description ); else this.question_description_html = ""; this.needQuestion = (item.needQuestion == false) ? false : true; if (this.needQuestion) { this.question = item.question || "Untitled Question"; this.question_html = ''.format( this.question_color, this.question, this.star ); } else { this.question_html = ""; } this.html = ""; } Tag.prototype = { render: function() { if (this.newline) this.html = "