diff --git a/package-lock.json b/package-lock.json index 502b751..14da8e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "h5p-advanced-blanks", "version": "1.1.0", "license": "MIT", - "dependencies": { - "ractive": "^1.4.2" - }, "devDependencies": { "@babel/cli": "^7.18.10", "@babel/core": "^7.18.13", @@ -19,7 +16,6 @@ "@babel/preset-env": "^7.18.10", "@types/diff": "^5.0.3", "@types/jquery": "^3.5.16", - "@types/ractive": "^0.7.27", "autoprefixer": "^10.4.14", "ava": "^4.3.3", "babel-loader": "^8.2.5", @@ -1995,13 +1991,6 @@ "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", "dev": true }, - "node_modules/@types/ractive": { - "version": "0.7.27", - "resolved": "https://registry.npmjs.org/@types/ractive/-/ractive-0.7.27.tgz", - "integrity": "sha1-3CtGLftuz0gFIVOfpDR9wkBSxx0=", - "dev": true, - "license": "MIT" - }, "node_modules/@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", @@ -5116,18 +5105,6 @@ } ] }, - "node_modules/ractive": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/ractive/-/ractive-1.4.2.tgz", - "integrity": "sha512-fv11k0fvSlFaVhUHdYLBnEZ4FdYuBB4BV4nURycrzTXNy5lWSf9B2bVHN1B6uIIBvoWEUaEVTQUlRn61W+NFeg==", - "bin": { - "ractive": "bin/ractive.js" - }, - "engines": { - "node": ">=4.0.0", - "npm": ">=2.14.2" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7745,12 +7722,6 @@ "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", "dev": true }, - "@types/ractive": { - "version": "0.7.27", - "resolved": "https://registry.npmjs.org/@types/ractive/-/ractive-0.7.27.tgz", - "integrity": "sha1-3CtGLftuz0gFIVOfpDR9wkBSxx0=", - "dev": true - }, "@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", @@ -10001,11 +9972,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "ractive": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/ractive/-/ractive-1.4.2.tgz", - "integrity": "sha512-fv11k0fvSlFaVhUHdYLBnEZ4FdYuBB4BV4nURycrzTXNy5lWSf9B2bVHN1B6uIIBvoWEUaEVTQUlRn61W+NFeg==" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", diff --git a/package.json b/package.json index 0ef9b7c..46f200a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "test": "npx ava", - "build": "webpack --mode=development && cp node_modules/ractive/ractive.min.js ./dist", + "build": "webpack --mode=development", "watch": "webpack --watch --mode=development" }, "repository": { @@ -26,7 +26,6 @@ "@babel/preset-env": "^7.18.10", "@types/diff": "^5.0.3", "@types/jquery": "^3.5.16", - "@types/ractive": "^0.7.27", "autoprefixer": "^10.4.14", "ava": "^4.3.3", "babel-loader": "^8.2.5", @@ -41,7 +40,5 @@ "webpack": "^5.74.0", "webpack-cli": "^4.10.0" }, - "dependencies": { - "ractive": "^1.4.2" - } -} + "dependencies": {} +} \ No newline at end of file diff --git a/src/scripts/controllers/cloze-controller.ts b/src/scripts/controllers/cloze-controller.ts index 650a642..c5822ac 100644 --- a/src/scripts/controllers/cloze-controller.ts +++ b/src/scripts/controllers/cloze-controller.ts @@ -9,11 +9,7 @@ import { ClozeType, SelectAlternatives } from "../models/enums"; import { Highlight } from "../models/highlight"; import { Blank } from "../models/blank"; import { Correctness } from '../models/answer'; - -import highlightTemplate from '../views/highlight.ractive.html'; -import blankTemplate from '../views/blank.ractive.html'; - -import * as RactiveEventsKeys from '../../lib/ractive-events-keys'; +import { highlightTemplate, blankTemplate } from '../views/templates'; interface ScoreChanged { (score: number, maxScore: number): void; @@ -32,7 +28,7 @@ interface Typed { } interface TextChanged { - () : void; + (): void; } export class ClozeController { @@ -47,10 +43,6 @@ export class ClozeController { public onTyped: Typed; public onTextChanged: TextChanged; - // Storage of the ractive objects that link models and views - private highlightRactives: { [id: string]: Ractive.Ractive } = {}; - private blankRactives: { [id: string]: Ractive.Ractive } = {}; - public get maxScore(): number { return this.cloze.blanks.length; } @@ -106,18 +98,15 @@ export class ClozeController { } /** - * Sets up all blanks, the cloze itself and the ractive bindings. + * Sets up all blanks, the cloze itself, and binds event listeners using jQuery. * @param {HTMLElement} root */ initialize(root: HTMLElement, jquery: JQuery) { this.jquery = jquery; - this.isSelectCloze = this.settings.clozeType === ClozeType.Select ? true : false; + this.isSelectCloze = this.settings.clozeType === ClozeType.Select; var blanks = this.repository.getBlanks(); - // Stop ractive debug mode - Ractive.DEBUG = false; - if (this.isSelectCloze && this.settings.selectAlternatives === SelectAlternatives.All) { for (var blank of blanks) { let otherBlanks = blanks.filter(v => v !== blank); @@ -131,8 +120,8 @@ export class ClozeController { this.cloze = ClozeLoader.createCloze(this.repository.getClozeText(), blanks); var containers = this.createAndAddContainers(root); - containers.cloze.innerHTML = this.cloze.html; - this.createRactiveBindings(); + this.jquery.find(containers.cloze).html(this.cloze.html); + this.createBindings(); } checkAll = () => { @@ -145,37 +134,46 @@ export class ClozeController { this.checkAndNotifyCompleteness(); } - textTyped = (event, blank: Blank) => { - blank.onTyped(); - if (this.onTyped) - this.onTyped(); - this.refreshCloze(); - } + textTyped = (blank: Blank) => { + // Persist the current value from the input field to the Blank model + const newValue = this.jquery.find(`#${blank.id}`).val() as string; + blank.enteredText = newValue; // Store the new value in the Blank model + + blank.onTyped(); // Trigger any typed event + if (this.onTyped) this.onTyped(); - focus = (event, blank: Blank) => { + // Refresh only this specific blank field + this.refreshCloze(blank); + }; + + focus = (blank: Blank) => { blank.onFocused(); - this.refreshCloze(); - } + this.refreshCloze(blank); + }; - displayFeedback = (event, blank: Blank) => { + displayFeedback = (blank: Blank) => { blank.onDisplayFeedback(); - this.refreshCloze(); - } + this.refreshCloze(blank); + }; - showHint = (event, blank: Blank) => { + showHint = (blank: Blank) => { this.cloze.hideAllHighlights(); blank.showHint(); - this.refreshCloze(); - } + this.refreshCloze(blank); + }; - requestCloseTooltip = (event, blank: Blank) => { + requestCloseTooltip = (blank: Blank) => { blank.removeTooltip(); - this.refreshCloze(); - this.jquery.find("#" + blank.id).focus(); - } + this.refreshCloze(blank); + this.jquery.find(`#${blank.id}`).focus(); + }; + + checkBlank = (blank: Blank, cause: string) => { + // Persist the current value before checking + const newValue = this.jquery.find(`#${blank.id}`).val() as string; + blank.enteredText = newValue; // Store the new value in the Blank model - checkBlank = (event, blank: Blank, cause: string) => { - if ((cause === 'blur' || cause === 'change')) { + if (cause === 'blur' || cause === 'change') { blank.lostFocus(); } @@ -184,19 +182,17 @@ export class ClozeController { } if (this.settings.autoCheck) { - if (!blank.enteredText || blank.enteredText === "") - return; + if (!blank.enteredText || blank.enteredText === "") return; this.cloze.hideAllHighlights(); blank.evaluateAttempt(false); this.checkAndNotifyCompleteness(); - this.refreshCloze(); + this.refreshCloze(blank); // Refresh only this blank field this.onAutoChecked(); } if ((cause === 'enter') - && ((this.settings.autoCheck && blank.isCorrect && !this.isSolved) - || !this.settings.autoCheck)) { - // move to next blank + && ((this.settings.autoCheck && blank.isCorrect && !this.cloze.isSolved) || !this.settings.autoCheck) + ) { var index = this.cloze.blanks.indexOf(blank); var nextId; while (index < this.cloze.blanks.length - 1 && !nextId) { @@ -204,11 +200,11 @@ export class ClozeController { if (!this.cloze.blanks[index].isCorrect) nextId = this.cloze.blanks[index].id; } - - if (nextId) + if (nextId) { this.jquery.find("#" + nextId).focus(); + } } - } + }; reset = () => { this.cloze.reset(); @@ -235,64 +231,60 @@ export class ClozeController { }; } - private createHighlightBinding(highlight: Highlight) { - this.highlightRactives[highlight.id] = new Ractive({ - el: '#container_' + highlight.id, - template: highlightTemplate, - data: { - object: highlight - } + private createBindings() { + this.cloze.highlights.forEach((highlight) => { + this.bindHighlight(highlight); }); - } - private createBlankBinding(blank: Blank) { - var ractive = new Ractive({ - el: '#container_' + blank.id, - template: blankTemplate, - data: { - isSelectCloze: this.isSelectCloze, - blank: blank - }, - events: { - enter: RactiveEventsKeys.enter, - escape: RactiveEventsKeys.escape, - anykey: RactiveEventsKeys.anykey - } + this.cloze.blanks.forEach((blank) => { + this.bindBlank(blank); }); - ractive.on("checkBlank", this.checkBlank); - ractive.on("showHint", this.showHint); - ractive.on("textTyped", this.textTyped); - ractive.on("textChanged", this.onTextChanged); - ractive.on("closeMessage", this.requestCloseTooltip); - ractive.on("focus", this.focus); - ractive.on("displayFeedback", this.displayFeedback); - - this.blankRactives[blank.id] = ractive; } - private createRactiveBindings() { - for (var highlight of this.cloze.highlights) { - this.createHighlightBinding(highlight); - } + private bindHighlight(highlight: Highlight) { + const highlightContainer = this.jquery.find(`#container_${highlight.id}`); + highlightContainer.html(highlightTemplate(highlight.id, highlight.isHighlighted, highlight.text)); + } - for (var blank of this.cloze.blanks) { - this.createBlankBinding(blank); + private bindBlank(blank: Blank) { + const blankContainer = this.jquery.find(`#container_${blank.id}`); + blankContainer.html(blankTemplate(blank, this.isSelectCloze)); + + const blankInput = blankContainer.find(`#${blank.id}`); + if (this.isSelectCloze) { + blankInput.on('change', () => this.checkBlank(blank, 'change')); + } else { + blankInput.on('keyup', () => this.textTyped(blank)); + blankInput.on('blur', () => this.checkBlank(blank, 'blur')); + blankInput.on('focus', () => this.focus(blank)); } } - /** - * Updates all views of highlights and blanks. Can be called when a model - * was changed - */ - private refreshCloze() { - for (var highlight of this.cloze.highlights) { - var highlightRactive = this.highlightRactives[highlight.id]; - highlightRactive.set("object", highlight); - } + private createSelectOptions(blank: Blank) { + let optionsHTML = ''; + blank.choices.forEach((choice) => { + optionsHTML += ``; + }); + return ``; + } - for (var blank of this.cloze.blanks) { - var blankRactive = this.blankRactives[blank.id]; - blankRactive.set("blank", blank); + // Modify refreshCloze to update only specific blanks if needed + private refreshCloze(blank?: Blank) { + if (blank) { + // Update only the specific blank input field + const blankContainer = this.jquery.find(`#container_${blank.id}`); + blankContainer.html(blankTemplate(blank, this.isSelectCloze)); + } else { + // Update all blanks and highlights (if needed) + this.cloze.highlights.forEach((highlight) => { + const highlightContainer = this.jquery.find(`#container_${highlight.id}`); + highlightContainer.html(highlightTemplate(highlight.id, highlight.isHighlighted, highlight.text)); + }); + + this.cloze.blanks.forEach((blank) => { + const blankContainer = this.jquery.find(`#container_${blank.id}`); + blankContainer.html(blankTemplate(blank, this.isSelectCloze)); + }); } } @@ -325,10 +317,9 @@ export class ClozeController { if (!this.cloze || this.cloze.blanks.length === 0) return [[]]; let result = []; - for (var blank of this.cloze.blanks) { + this.cloze.blanks.forEach((blank) => { result.push(blank.getCorrectAnswers()); - } - + }); return result; } } diff --git a/src/scripts/views/templates.ts b/src/scripts/views/templates.ts new file mode 100644 index 0000000..4bb21d6 --- /dev/null +++ b/src/scripts/views/templates.ts @@ -0,0 +1,46 @@ +export const highlightTemplate = (id, isHighlighted, text) => ` + ${text} +`; + +export const blankTemplate = (blank, isSelectCloze) => ` + + ${isSelectCloze ? ` + + + + + ` : ` + + + ${blank.hasHint ? ` + + + ` : ''} + + `} + +`; + diff --git a/webpack.config.js b/webpack.config.js index 7539175..d392aea 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -26,20 +26,11 @@ module.exports = (env, argv) => { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ - }, - { - test: /\.ractive.html$/, - use: { - loader: 'html-loader', - options: { - minimize: false - } - } } ] }, resolve: { - extensions: [".tsx", ".ts", ".js", ".ractive.html"] + extensions: [".tsx", ".ts", ".js"] } };