diff --git a/src/gapfill-open.js b/src/gapfill-open.js new file mode 100644 index 0000000000000000000000000000000000000000..1b3b3662b8333e54a25a675b9467b7e01b49b981 --- /dev/null +++ b/src/gapfill-open.js @@ -0,0 +1,63 @@ +/** + * Custom SurveyJS question type for a "fill-in-the-gaps" text, + * with an open-ended text input for each gap + */ +export const gapfillOpenWidget = { + name: "gapfill-open", + title: "Gap-Fill Text (Open)", + /** + * This function should return true when the widget and all needed resources + * are loaded + */ + widgetIsLoaded: function () { + return true; + }, + /** + * This function should return true if the widget should be applied to the question */ + isFit: function (question) { + return question.getType() === this.name; + }, + init() { + //Register a new type using the empty question as the base. + Survey.Serializer.addClass(this.name, [], null, "empty"); + }, + /** Static HTML template rendered by SurveyJS */ + htmlTemplate: '<p id="gapfill-container"><template id="template-gap"><input type="text" class="sd-input inline-input"/></template></p>', + /** + * Function called after the HTML template is rendered. This time we actually have the `question` object + * and the `el` element, to build the question according to the JSON + */ + afterRender: function (question, el) { + // The gap-fill text is made of segments, which are either plain pieces + // of text (strings) or "gaps" (objects). + // We append these to build the text, turning strings into <span>s and + // gaps into <input> text fields + let nbGaps = 0; + const segmentElems = new DocumentFragment(); // a bit faster than mutating the DOM all the time + const gapTemplate = document.getElementById("template-gap").content.firstChild; + for (const segment of question.jsonObj.segments) { + let segmentElem; + if (typeof segment === 'string' || segment instanceof String) { + segmentElem = document.createElement("span"); + segmentElem.innerText = segment; + } else { + // It's a gap + // Create the <input> element + segmentElem = gapTemplate.cloneNode(true); + segmentElem.setAttribute("data-index", nbGaps); // The node knows its index + // Add listener to update the question's value when the input value changes + segmentElem.addEventListener("change", (e) => { + // The input node knows its index, therefore is able to update the question value at the correct index + question.value[parseInt(e.target.getAttribute("data-index"))] = e.target.value; + }); + nbGaps++; + } + // Add segment + segmentElems.appendChild(segmentElem); + } + // Initialize question value array + question.value = new Array(nbGaps); + // Finally add everything to the DOM + el.appendChild(segmentElems); + }, +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 5de1494f5823abf7611e8130f9166db1b18d642d..7cacf5f0b056d72801c2035700d980121d3daa36 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,13 @@ import {json} from "./json.js" import {gapfillSelectWidget} from "./gapfill-select.js"; +import {gapfillOpenWidget} from "./gapfill-open.js"; window.addEventListener('load', main) function main() { // Register our custom question types Survey.CustomWidgetCollection.Instance.add(gapfillSelectWidget, gapfillSelectWidget.name); + Survey.CustomWidgetCollection.Instance.add(gapfillOpenWidget, gapfillOpenWidget.name); let survey = new Survey.Model(json); diff --git a/src/json.js b/src/json.js index a1ed56016ae7637c9223f339892e11c7c165a013..7b3a22e272c64c8324de926ff70dfc8e85911b26 100644 --- a/src/json.js +++ b/src/json.js @@ -2,7 +2,7 @@ export const json = { elements: [ { type: "gapfill-select", - name: "pg20", + name: "q1", title: "The greatest song in the world", segments: [ "🎶\nWe're no strangers to ", @@ -37,6 +37,16 @@ export const json = { "run around", "desert" ] + }, + { + type: "gapfill-open", + name: "q2", + title: "Open-ended gapfill", + segments: [ + "Lorem ", + {}, + " dolor sit amet [...]" + ], correctAnswer: ["ipsum"] } ] }; \ No newline at end of file diff --git a/src/style.css b/src/style.css index d201ece86408fc53de2ff95d4ac18c594cd26cf8..b9f9dfd699ad5b35c9a79e3d7355d2863cb4618d 100644 --- a/src/style.css +++ b/src/style.css @@ -8,7 +8,7 @@ p#gapfill-container { white-space: pre-wrap; line-height: 3em; } -select.sd-dropdown.inline-dropdown { +select.sd-dropdown.inline-dropdown, input.sd-input.inline-input { display: inline-block; width: fit-content; padding: 8px;