From 5878d7c3eb126dfe413569716d30d0f80331f47d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 4 Apr 2018 09:00:29 +0200 Subject: [PATCH] MOBILE-2348 quiz: Fix question types --- src/addon/notes/pages/add/add.html | 2 +- .../deferredcbm/component/deferredcbm.html | 22 +++- .../calculated/component/calculated.html | 54 +++++---- .../ddimageortext/classes/ddimageortext.ts | 16 ++- .../component/ddimageortext.html | 6 +- .../component/ddimageortext.scss | 101 ++++++++++++++++ .../ddimageortext/component/ddimageortext.ts | 22 ++-- src/addon/qtype/ddmarker/classes/ddmarker.ts | 26 ++--- .../qtype/ddmarker/component/ddmarker.html | 6 +- .../qtype/ddmarker/component/ddmarker.scss | 97 ++++++++++++++++ .../qtype/ddmarker/component/ddmarker.ts | 22 ++-- src/addon/qtype/ddwtos/classes/ddwtos.ts | 15 +-- src/addon/qtype/ddwtos/component/ddwtos.html | 10 +- src/addon/qtype/ddwtos/component/ddwtos.scss | 108 ++++++++++++++++++ src/addon/qtype/ddwtos/component/ddwtos.ts | 29 ++--- src/addon/qtype/essay/component/essay.html | 8 +- src/addon/qtype/essay/component/essay.ts | 7 +- .../qtype/gapselect/component/gapselect.scss | 19 +++ src/addon/qtype/match/component/match.html | 28 +++-- .../multianswer/component/multianswer.scss | 43 +++++++ .../multichoice/component/multichoice.html | 19 ++- src/app/app.scss | 11 ++ .../rich-text-editor/rich-text-editor.html | 2 +- .../rich-text-editor/rich-text-editor.ts | 3 + .../classes/base-question-component.ts | 15 +-- .../question/components/question/question.ts | 2 +- src/core/question/providers/helper.ts | 6 +- 27 files changed, 562 insertions(+), 137 deletions(-) create mode 100644 src/addon/qtype/ddimageortext/component/ddimageortext.scss create mode 100644 src/addon/qtype/ddmarker/component/ddmarker.scss create mode 100644 src/addon/qtype/ddwtos/component/ddwtos.scss create mode 100644 src/addon/qtype/gapselect/component/gapselect.scss create mode 100644 src/addon/qtype/multianswer/component/multianswer.scss diff --git a/src/addon/notes/pages/add/add.html b/src/addon/notes/pages/add/add.html index e0a41c7b1..856fddb6e 100644 --- a/src/addon/notes/pages/add/add.html +++ b/src/addon/notes/pages/add/add.html @@ -12,7 +12,7 @@
{{ 'addon.notes.publishstate' | translate }} - + {{ 'addon.notes.personalnotes' | translate }} {{ 'addon.notes.coursenotes' | translate }} {{ 'addon.notes.sitenotes' | translate }} diff --git a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html index 07c1ffc7d..0975635c1 100644 --- a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html +++ b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html @@ -1,6 +1,16 @@ - -

{{ 'core.question.certainty' | translate }}

-
- -

-
+
+ +

{{ 'core.question.certainty' | translate }}

+
+
+ + + + + + +
+ + + +
diff --git a/src/addon/qtype/calculated/component/calculated.html b/src/addon/qtype/calculated/component/calculated.html index af87387cc..abff5a86f 100644 --- a/src/addon/qtype/calculated/component/calculated.html +++ b/src/addon/qtype/calculated/component/calculated.html @@ -8,24 +8,26 @@ - - - - - - + + + + + + + - - - - - + + + + + - - - - - + + + + + + @@ -38,18 +40,26 @@ - + {{option.label}} - + + + -
- -

{{option.text}}

-
+
+ + +

{{option.text}}

+
+ +
+ + +
diff --git a/src/addon/qtype/ddimageortext/classes/ddimageortext.ts b/src/addon/qtype/ddimageortext/classes/ddimageortext.ts index 64123bee7..4d77e03f0 100644 --- a/src/addon/qtype/ddimageortext/classes/ddimageortext.ts +++ b/src/addon/qtype/ddimageortext/classes/ddimageortext.ts @@ -45,6 +45,7 @@ export class AddonQtypeDdImageOrTextQuestion { protected topNode: HTMLElement; protected proportion = 1; protected selected: HTMLElement; // Selected element (being "dragged"). + protected resizeFunction; /** * Create the this. @@ -182,7 +183,10 @@ export class AddonQtypeDdImageOrTextQuestion { */ destroy(): void { this.stopPolling(); - window.removeEventListener('resize', this.resizeFunction); + + if (this.resizeFunction) { + window.removeEventListener('resize', this.resizeFunction); + } } /** @@ -192,7 +196,7 @@ export class AddonQtypeDdImageOrTextQuestion { * @return {AddonQtypeDdImageOrTextQuestionDocStructure} The object. */ docStructure(slot: number): AddonQtypeDdImageOrTextQuestionDocStructure { - const topNode = this.container.querySelector(`#core-question-${slot} .addon-qtype-ddimageortext-container`), + const topNode = this.container.querySelector('.addon-qtype-ddimageortext-container'), dragItemsArea = topNode.querySelector('div.dragitems'), doc: AddonQtypeDdImageOrTextQuestionDocStructure = {}; @@ -456,6 +460,7 @@ export class AddonQtypeDdImageOrTextQuestion { this.pollForImageLoad(); }); + this.resizeFunction = this.repositionDragsForQuestion.bind(this); window.addEventListener('resize', this.resizeFunction); } @@ -637,13 +642,6 @@ export class AddonQtypeDdImageOrTextQuestion { } } - /** - * Function to call when the window is resized. - */ - resizeFunction(): void { - this.repositionDragsForQuestion(); - } - /** * Mark a draggable element as selected. * diff --git a/src/addon/qtype/ddimageortext/component/ddimageortext.html b/src/addon/qtype/ddimageortext/component/ddimageortext.html index b6394b880..23d97495f 100644 --- a/src/addon/qtype/ddimageortext/component/ddimageortext.html +++ b/src/addon/qtype/ddimageortext/component/ddimageortext.html @@ -3,11 +3,11 @@ -

- +

+ {{ 'core.question.howtodraganddrop' | translate }}

- +
diff --git a/src/addon/qtype/ddimageortext/component/ddimageortext.scss b/src/addon/qtype/ddimageortext/component/ddimageortext.scss new file mode 100644 index 000000000..48c69d82e --- /dev/null +++ b/src/addon/qtype/ddimageortext/component/ddimageortext.scss @@ -0,0 +1,101 @@ +// Style ddimageortext content a bit. Almost all these styles are copied from Moodle. +addon-qtype-ddimageortext { + .qtext { + margin-bottom: 0.5em; + display: block; + } + + div.droparea img { + border: 1px solid $gray-darker; + max-width: 100%; + } + + .draghome { + vertical-align: top; + margin: 5px; + visibility : hidden; + } + .draghome img { + display: block; + } + + div.draghome { + border: 1px solid $gray-darker; + cursor: pointer; + background-color: #B0C4DE; + display:inline-block; + height: auto; + width: auto; + zoom: 1; + } + + .group1 { + background-color: $white; + } + .group2 { + background-color: $blue-light; + } + .group3 { + background-color: #DCDCDC; + } + .group4 { + background-color: #D8BFD8; + } + .group5 { + background-color: #87CEFA; + } + .group6 { + background-color: #DAA520; + } + .group7 { + background-color: #FFD700; + } + .group8 { + background-color: #F0E68C; + } + .drag { + border: 1px solid $gray-darker; + cursor: pointer; + z-index: 2; + } + .dragitems.readonly .drag { + cursor: auto; + } + .dragitems>div { + clear: both; + } + .dragitems { + cursor: pointer; + } + .dragitems.readonly { + cursor: auto; + } + .drag img { + display: block; + } + + div.ddarea { + text-align : center; + position: relative; + } + .dropbackground { + margin:0 auto; + } + .dropzone { + border: 1px solid $gray-darker; + position: absolute; + z-index: 1; + cursor: pointer; + } + .readonly .dropzone { + cursor: auto; + } + + div.dragitems div.draghome, div.dragitems div.drag { + font:13px/1.231 arial,helvetica,clean,sans-serif; + } + .drag.beingdragged { + z-index: 3; + box-shadow: 3px 3px 4px $gray-darker; + } +} diff --git a/src/addon/qtype/ddimageortext/component/ddimageortext.ts b/src/addon/qtype/ddimageortext/component/ddimageortext.ts index cb96133bd..cc927fbc9 100644 --- a/src/addon/qtype/ddimageortext/component/ddimageortext.ts +++ b/src/addon/qtype/ddimageortext/component/ddimageortext.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy, AfterViewInit, Injector, ElementRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, Injector, ElementRef } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; import { AddonQtypeDdImageOrTextQuestion } from '../classes/ddimageortext'; @@ -24,14 +24,15 @@ import { AddonQtypeDdImageOrTextQuestion } from '../classes/ddimageortext'; selector: 'addon-qtype-ddimageortext', templateUrl: 'ddimageortext.html' }) -export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent implements OnInit, AfterViewInit, OnDestroy { +export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent implements OnInit, OnDestroy { protected element: HTMLElement; protected questionInstance: AddonQtypeDdImageOrTextQuestion; protected drops: any[]; // The drop zones received in the init object of the question. + protected destroyed = false; - constructor(logger: CoreLoggerProvider, injector: Injector, element: ElementRef) { - super(logger, 'AddonQtypeDdImageOrTextComponent', injector); + constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef) { + super(loggerProvider, 'AddonQtypeDdImageOrTextComponent', injector); this.element = element.nativeElement; } @@ -76,18 +77,21 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent } /** - * View has been initialized. + * The question has been rendered. */ - ngAfterViewInit(): void { - // Create the instance. - this.questionInstance = new AddonQtypeDdImageOrTextQuestion(this.logger, this.domUtils, this.element, - this.question, this.question.readOnly, this.drops); + questionRendered(): void { + if (!this.destroyed) { + // Create the instance. + this.questionInstance = new AddonQtypeDdImageOrTextQuestion(this.loggerProvider, this.domUtils, this.element, + this.question, this.question.readOnly, this.drops); + } } /** * Component being destroyed. */ ngOnDestroy(): void { + this.destroyed = true; this.questionInstance && this.questionInstance.destroy(); } } diff --git a/src/addon/qtype/ddmarker/classes/ddmarker.ts b/src/addon/qtype/ddmarker/classes/ddmarker.ts index 3624bf46a..93a368242 100644 --- a/src/addon/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addon/qtype/ddmarker/classes/ddmarker.ts @@ -50,6 +50,7 @@ export class AddonQtypeDdMarkerQuestion { protected proportion = 1; protected selected: HTMLElement; // Selected element (being "dragged"). protected graphics: AddonQtypeDdMarkerGraphicsApi; + protected resizeFunction; doc: AddonQtypeDdMarkerQuestionDocStructure; shapes = []; @@ -157,7 +158,9 @@ export class AddonQtypeDdMarkerQuestion { * Function to call when the instance is no longer needed. */ destroy(): void { - window.removeEventListener('resize', this.resizeFunction); + if (this.resizeFunction) { + window.removeEventListener('resize', this.resizeFunction); + } } /** @@ -167,7 +170,7 @@ export class AddonQtypeDdMarkerQuestion { * @return {AddonQtypeDdMarkerQuestionDocStructure} The object. */ docStructure(slot: number): AddonQtypeDdMarkerQuestionDocStructure { - const topNode = this.container.querySelector('#core-question-' + slot + ' .addon-qtype-ddmarker-container'), + const topNode = this.container.querySelector('.addon-qtype-ddmarker-container'), dragItemsArea = topNode.querySelector('div.dragitems'); return { @@ -293,9 +296,9 @@ export class AddonQtypeDdMarkerQuestion { const markerTexts = this.doc.markerTexts(); // Check if there is already a marker text for this drop zone. if (link) { - existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo + ' a'); + existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo + ' a'); } else { - existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo); + existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo); } if (existingMarkerText) { @@ -538,7 +541,7 @@ export class AddonQtypeDdMarkerQuestion { dragging = (this.doc.dragItemBeingDragged(choiceNo) !== null), coords: number[][] = []; - if (fv !== '' && typeof fv != 'undefined') { + if (fv !== '' && typeof fv != 'undefined' && fv !== null) { // Get all the coordinates in the input and add them to the coords list. const coordsStrings = fv.split(';'); @@ -645,6 +648,7 @@ export class AddonQtypeDdMarkerQuestion { this.pollForImageLoad(); }); + this.resizeFunction = this.redrawDragsAndDrops.bind(this); window.addEventListener('resize', this.resizeFunction); } @@ -731,6 +735,9 @@ export class AddonQtypeDdMarkerQuestion { }, 500); } + /** + * Redraw all draggables and drop zones. + */ redrawDragsAndDrops(): void { // Mark all the draggable items as not placed. const drags = this.doc.dragItems(); @@ -789,7 +796,7 @@ export class AddonQtypeDdMarkerQuestion { dropZone = this.dropZones[dropZoneNo], dzNo = Number(dropZoneNo); - this.drawDropZone(dzNo, dropZone.markerText, dropZone.shape, dropZone.coords, colourForDropZone, true); + this.drawDropZone(dzNo, dropZone.markertext, dropZone.shape, dropZone.coords, colourForDropZone, true); } } } @@ -803,13 +810,6 @@ export class AddonQtypeDdMarkerQuestion { this.setFormValue(choiceNo, ''); } - /** - * Function to call when the window is resized. - */ - resizeFunction(): void { - this.redrawDragsAndDrops(); - } - /** * Restart the colour index. */ diff --git a/src/addon/qtype/ddmarker/component/ddmarker.html b/src/addon/qtype/ddmarker/component/ddmarker.html index eb3a16b81..f6ada3e34 100644 --- a/src/addon/qtype/ddmarker/component/ddmarker.html +++ b/src/addon/qtype/ddmarker/component/ddmarker.html @@ -3,11 +3,11 @@ -

- +

+ {{ 'core.question.howtodraganddrop' | translate }}

- +
diff --git a/src/addon/qtype/ddmarker/component/ddmarker.scss b/src/addon/qtype/ddmarker/component/ddmarker.scss new file mode 100644 index 000000000..7502a91a6 --- /dev/null +++ b/src/addon/qtype/ddmarker/component/ddmarker.scss @@ -0,0 +1,97 @@ +// Style ddmarker content a bit. Almost all these styles are copied from Moodle. +addon-qtype-ddmarker { + .qtext { + margin-bottom: 0.5em; + display: block; + } + + div.droparea img { + border: 1px solid $gray-darker; + max-width: 100%; + } + + .draghome img, .draghome span { + visibility: hidden; + } + + .dragitems .dragitem { + cursor: pointer; + position: absolute; + z-index: 2; + } + + .dropzones { + position: absolute; + } + .dropzones svg { + z-index: 3; + } + + .dragitem.beingdragged .markertext { + z-index: 5; + box-shadow: 3px 3px 4px $gray-darker; + } + .dragitems .draghome { + margin: 10px; + display: inline-block; + } + + .dragitems.readonly .dragitem { + cursor: auto; + } + div.ddarea { + text-align: center; + } + div.ddarea .markertexts { + min-height: 80px; + position: absolute; + text-align: left; + } + .dropbackground { + margin: 0 auto; + } + + div.dragitems div.draghome, div.dragitems div.dragitem, + div.draghome, div.drag { + font: 13px/1.231 arial,helvetica,clean,sans-serif; + } + div.dragitems span.markertext, + div.markertexts span.markertext { + margin: 0 5px; + z-index: 2; + background-color: $white; + border: 2px solid $gray-darker; + padding: 5px; + display: inline-block; + zoom: 1; + border-radius: 10px; + } + div.markertexts span.markertext { + z-index: 3; + background-color: $yellow-light; + border-style: solid; + border-width: 2px; + border-color: $yellow; + position: absolute; + } + span.wrongpart { + background-color: $yellow-light; + border-style: solid; + border-width: 2px; + border-color: $yellow; + padding: 5px; + border-radius: 10px; + filter: alpha(opacity=60); + opacity: 0.6; + margin: 5px; + display: inline-block; + } + div.dragitems img.target { + position: absolute; + left: -7px; /* This must be half the size of the target image, minus 0.5. */ + top: -7px; /* In other words, this works for a 15x15 cross-hair. */ + } + div.dragitems div.draghome img.target { + display: none; + } +} diff --git a/src/addon/qtype/ddmarker/component/ddmarker.ts b/src/addon/qtype/ddmarker/component/ddmarker.ts index 2afbe0a1f..e9b16521a 100644 --- a/src/addon/qtype/ddmarker/component/ddmarker.ts +++ b/src/addon/qtype/ddmarker/component/ddmarker.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy, AfterViewInit, Injector, ElementRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, Injector, ElementRef } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; import { AddonQtypeDdMarkerQuestion } from '../classes/ddmarker'; @@ -24,14 +24,15 @@ import { AddonQtypeDdMarkerQuestion } from '../classes/ddmarker'; selector: 'addon-qtype-ddmarker', templateUrl: 'ddmarker.html' }) -export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent implements OnInit, AfterViewInit, OnDestroy { +export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent implements OnInit, OnDestroy { protected element: HTMLElement; protected questionInstance: AddonQtypeDdMarkerQuestion; protected dropZones: any[]; // The drop zones received in the init object of the question. + protected destroyed = false; - constructor(logger: CoreLoggerProvider, injector: Injector, element: ElementRef) { - super(logger, 'AddonQtypeDdMarkerComponent', injector); + constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef) { + super(loggerProvider, 'AddonQtypeDdMarkerComponent', injector); this.element = element.nativeElement; } @@ -83,18 +84,21 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple } /** - * View has been initialized. + * The question has been rendered. */ - ngAfterViewInit(): void { - // Create the instance. - this.questionInstance = new AddonQtypeDdMarkerQuestion(this.logger, this.domUtils, this.textUtils, this.element, - this.question, this.question.readOnly, this.dropZones); + questionRendered(): void { + if (!this.destroyed) { + // Create the instance. + this.questionInstance = new AddonQtypeDdMarkerQuestion(this.loggerProvider, this.domUtils, this.textUtils, this.element, + this.question, this.question.readOnly, this.dropZones); + } } /** * Component being destroyed. */ ngOnDestroy(): void { + this.destroyed = true; this.questionInstance && this.questionInstance.destroy(); } } diff --git a/src/addon/qtype/ddwtos/classes/ddwtos.ts b/src/addon/qtype/ddwtos/classes/ddwtos.ts index 648c907fa..c6fb9c365 100644 --- a/src/addon/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addon/qtype/ddwtos/classes/ddwtos.ts @@ -46,6 +46,7 @@ export class AddonQtypeDdwtosQuestion { protected selectors: AddonQtypeDdwtosQuestionCSSSelectors; // Result of cssSelectors. protected placed: {[no: number]: number}; // Map that relates drag elements numbers with drop zones numbers. protected selected: HTMLElement; // Selected element (being "dragged"). + protected resizeFunction; /** * Create the instance. @@ -125,7 +126,7 @@ export class AddonQtypeDdwtosQuestion { * @return {AddonQtypeDdwtosQuestionCSSSelectors} Object with the functions to get the selectors. */ cssSelectors(slot: number): AddonQtypeDdwtosQuestionCSSSelectors { - const topNode = '#core-question-' + slot + ' .addon-qtype-ddwtos-container', + const topNode = '.addon-qtype-ddwtos-container', selectors: AddonQtypeDdwtosQuestionCSSSelectors = {}; selectors.topNode = (): string => { @@ -193,7 +194,9 @@ export class AddonQtypeDdwtosQuestion { * Function to call when the instance is no longer needed. */ destroy(): void { - window.removeEventListener('resize', this.resizeFunction); + if (this.resizeFunction) { + window.removeEventListener('resize', this.resizeFunction); + } } /** @@ -285,6 +288,7 @@ export class AddonQtypeDdwtosQuestion { this.positionDragItems(); }); + this.resizeFunction = this.positionDragItems.bind(this); window.addEventListener('resize', this.resizeFunction); } @@ -488,13 +492,6 @@ export class AddonQtypeDdwtosQuestion { this.placeDragInDrop(null, drop); } - /** - * Function to call when the window is resized. - */ - resizeFunction(): void { - this.positionDragItems(); - } - /** * Select a certain element as being "dragged". * diff --git a/src/addon/qtype/ddwtos/component/ddwtos.html b/src/addon/qtype/ddwtos/component/ddwtos.html index 689a75406..1152d54f3 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.html +++ b/src/addon/qtype/ddwtos/component/ddwtos.html @@ -1,11 +1,11 @@ -
- -

- +

+ +

+ {{ 'core.question.howtodraganddrop' | translate }}

- +
diff --git a/src/addon/qtype/ddwtos/component/ddwtos.scss b/src/addon/qtype/ddwtos/component/ddwtos.scss new file mode 100644 index 000000000..69d5ae1c1 --- /dev/null +++ b/src/addon/qtype/ddwtos/component/ddwtos.scss @@ -0,0 +1,108 @@ +// Style ddwtos content a bit. Almost all these styles are copied from Moodle. +addon-qtype-ddwtos { + .qtext { + margin-bottom: 0.5em; + display: block; + } + + .draghome { + margin-bottom: 1em; + } + + .answertext { + margin-bottom: 0.5em; + } + + .drop { + display: inline-block; + text-align: center; + border: 1px solid $gray-darker; + margin-bottom: 2px; + border-radius: 5px; + } + .draghome, .drag { + display: inline-block; + text-align: center; + background: transparent; + border: 0; + } + .draghome, .drag.unplaced{ + border: 1px solid $gray-darker; + border-radius: 5px; + } + .draghome { + visibility: hidden; + } + .drag { + z-index: 2; + border-radius: 5px; + } + .drag.selected { + z-index: 3; + box-shadow: 3px 3px 4px $gray-darker; + } + + .drop.selected { + border-color: $yellow-light; + box-shadow: 0 0 5px 5px $yellow-light; + } + + &.notreadonly .drag, + &.notreadonly .draghome, + &.notreadonly .drop, + &.notreadonly .answercontainer { + cursor: pointer; + border-radius: 5px; + } + + &.readonly .drag, + &.readonly .draghome, + &.readonly .drop, + &.readonly .answercontainer { + cursor: default; + } + + span.incorrect { + background-color: $red-light; + } + span.correct { + background-color: $green-light; + } + + .group1 { + background-color: $white; + } + .group2 { + background-color: #DCDCDC; + } + .group3 { + background-color: $blue-light; + } + .group4 { + background-color: #D8BFD8; + } + .group5 { + background-color: #87CEFA; + } + .group6 { + background-color: #DAA520; + } + .group7 { + background-color: #FFD700; + } + .group8 { + background-color: #F0E68C; + } + + sub, sup { + font-size: 80%; + position: relative; + vertical-align: baseline; + } + sup { + top: -0.4em; + } + sub { + bottom: -0.2em; + } +} diff --git a/src/addon/qtype/ddwtos/component/ddwtos.ts b/src/addon/qtype/ddwtos/component/ddwtos.ts index 18a4a121a..3241e435d 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.ts +++ b/src/addon/qtype/ddwtos/component/ddwtos.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy, AfterViewInit, Injector, ElementRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, Injector, ElementRef } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; import { AddonQtypeDdwtosQuestion } from '../classes/ddwtos'; @@ -24,14 +24,15 @@ import { AddonQtypeDdwtosQuestion } from '../classes/ddwtos'; selector: 'addon-qtype-ddwtos', templateUrl: 'ddwtos.html' }) -export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent implements OnInit, AfterViewInit, OnDestroy { +export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent implements OnInit, OnDestroy { protected element: HTMLElement; protected questionInstance: AddonQtypeDdwtosQuestion; - protected inputIds: string[]; // Ids of the inputs of the question (where the answers will be stored). + protected inputIds: string[] = []; // Ids of the inputs of the question (where the answers will be stored). + protected destroyed = false; - constructor(logger: CoreLoggerProvider, injector: Injector, element: ElementRef) { - super(logger, 'AddonQtypeDdwtosComponent', injector); + constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef) { + super(loggerProvider, 'AddonQtypeDdwtosComponent', injector); this.element = element.nativeElement; } @@ -74,28 +75,30 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme } // Get the inputs where the answers will be stored and add them to the question text. - const inputEls = Array.from(div.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])')), - inputIds = []; + const inputEls = Array.from(div.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])')); inputEls.forEach((inputEl) => { this.question.text += inputEl.outerHTML; - inputIds.push(inputEl.getAttribute('id')); + this.inputIds.push(inputEl.getAttribute('id')); }); } /** - * View has been initialized. + * The question has been rendered. */ - ngAfterViewInit(): void { - // Create the instance. - this.questionInstance = new AddonQtypeDdwtosQuestion(this.logger, this.domUtils, this.element, this.question, - this.question.readOnly, this.inputIds); + questionRendered(): void { + if (!this.destroyed) { + // Create the instance. + this.questionInstance = new AddonQtypeDdwtosQuestion(this.loggerProvider, this.domUtils, this.element, this.question, + this.question.readOnly, this.inputIds); + } } /** * Component being destroyed. */ ngOnDestroy(): void { + this.destroyed = true; this.questionInstance && this.questionInstance.destroy(); } } diff --git a/src/addon/qtype/essay/component/essay.html b/src/addon/qtype/essay/component/essay.html index 1098c8304..0b098d82e 100644 --- a/src/addon/qtype/essay/component/essay.html +++ b/src/addon/qtype/essay/component/essay.html @@ -7,13 +7,13 @@ - + - {{question.textarea.text}} + - + + [component]="component" [componentId]="componentId" --> diff --git a/src/addon/qtype/essay/component/essay.ts b/src/addon/qtype/essay/component/essay.ts index c421fa629..ba298ce37 100644 --- a/src/addon/qtype/essay/component/essay.ts +++ b/src/addon/qtype/essay/component/essay.ts @@ -15,6 +15,7 @@ import { Component, OnInit, Injector } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; +import { FormControl, FormBuilder } from '@angular/forms'; /** * Component to render an essay question. @@ -25,7 +26,9 @@ import { CoreQuestionBaseComponent } from '@core/question/classes/base-question- }) export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implements OnInit { - constructor(logger: CoreLoggerProvider, injector: Injector) { + protected formControl: FormControl; + + constructor(logger: CoreLoggerProvider, injector: Injector, protected fb: FormBuilder) { super(logger, 'AddonQtypeEssayComponent', injector); } @@ -34,5 +37,7 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen */ ngOnInit(): void { this.initEssayComponent(); + + this.formControl = this.fb.control(this.question.textarea && this.question.textarea.text); } } diff --git a/src/addon/qtype/gapselect/component/gapselect.scss b/src/addon/qtype/gapselect/component/gapselect.scss new file mode 100644 index 000000000..b467d9351 --- /dev/null +++ b/src/addon/qtype/gapselect/component/gapselect.scss @@ -0,0 +1,19 @@ +// Style gapselect content a bit. All these styles are copied from Moodle. +addon-qtype-gapselect { + p { + margin: 0 0 .5em; + } + + select { + height: 30px; + line-height: 30px; + display: inline-block; + border: 1px solid $gray-dark; + padding: 4px 6px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + margin-bottom: 10px; + background: $gray-lighter; + } +} diff --git a/src/addon/qtype/match/component/match.html b/src/addon/qtype/match/component/match.html index fb40981c6..cfa627279 100644 --- a/src/addon/qtype/match/component/match.html +++ b/src/addon/qtype/match/component/match.html @@ -3,17 +3,21 @@

- - -

-
- - - - {{option.label}} - - - -
+ + + +

+
+ + + + {{option.label}} + + + + + +
+
diff --git a/src/addon/qtype/multianswer/component/multianswer.scss b/src/addon/qtype/multianswer/component/multianswer.scss new file mode 100644 index 000000000..d449e8a26 --- /dev/null +++ b/src/addon/qtype/multianswer/component/multianswer.scss @@ -0,0 +1,43 @@ +// Style multianswer content a bit. All these styles are copied from Moodle. +addon-qtype-multianswer { + p { + margin: 0 0 .5em; + } + + .answer div.r0, .answer div.r1, .answer td.r0, .answer td.r1 { + padding: 0.3em; + } + + table { + width: 100%; + display: table; + } + + tr { + display: table-row; + } + + td { + display: table-cell; + } + + input, select { + display: inline-block; + border: 1px solid #ccc; + padding: 4px 6px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + margin-bottom: 10px; + } + + select { + height: 30px; + line-height: 30px; + } + + input[type="radio"], input[type="checkbox"] { + margin-top: -4px; + margin-right: 7px; + } +} diff --git a/src/addon/qtype/multichoice/component/multichoice.html b/src/addon/qtype/multichoice/component/multichoice.html index b9271c852..e7282a9df 100644 --- a/src/addon/qtype/multichoice/component/multichoice.html +++ b/src/addon/qtype/multichoice/component/multichoice.html @@ -7,19 +7,28 @@ - -

-

-
+ + + +

+
+ + + + +
-
+

+ + +
diff --git a/src/app/app.scss b/src/app/app.scss index c42c14c87..23f901078 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -37,6 +37,7 @@ .opacity-hide { opacity: 0; } .core-big { font-size: 115%; } +.invisible { visibility: hidden; } @include media-breakpoint-up(sm) { .core-center-view .scroll-content { @@ -510,3 +511,13 @@ textarea { color: $color-base; } } + +.accesshide { + position: absolute; + left: -10000px; + font-weight: normal; + font-size: 1em; } + +.core-monospaced { + font-family: Andale Mono,Monaco,Courier New,DejaVu Sans Mono,monospace; +} diff --git a/src/components/rich-text-editor/rich-text-editor.html b/src/components/rich-text-editor/rich-text-editor.html index 72eee6e14..e4a71afd3 100644 --- a/src/components/rich-text-editor/rich-text-editor.html +++ b/src/components/rich-text-editor/rich-text-editor.html @@ -20,7 +20,7 @@
- +
diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts index 97a6a6530..c0a9ed6c4 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -42,6 +42,7 @@ export class CoreRichTextEditorComponent { @Input() placeholder = ''; // Placeholder to set in textarea. @Input() control: FormControl; // Form control. + @Input() name = 'core-rich-text-editor'; // Name to set to the textarea. @Output() contentChanged: EventEmitter; @ViewChild('editor') editor: ElementRef; // WYSIWYG editor. @@ -109,6 +110,7 @@ export class CoreRichTextEditorComponent { this.clearText(); } else { this.control.setValue(this.editorElement.innerHTML); + this.textarea.value = this.editorElement.innerHTML; } } else { if (this.isNullOrWhiteSpace(this.textarea.value)) { @@ -117,6 +119,7 @@ export class CoreRichTextEditorComponent { this.control.setValue(this.textarea.value); } } + this.contentChanged.emit(this.control.value); } diff --git a/src/core/question/classes/base-question-component.ts b/src/core/question/classes/base-question-component.ts index 5befc6391..53af54d5b 100644 --- a/src/core/question/classes/base-question-component.ts +++ b/src/core/question/classes/base-question-component.ts @@ -83,7 +83,6 @@ export class CoreQuestionBaseComponent { if (optionEl.selected) { selectModel.selected = option.value; - selectModel.selectedLabel = option.label; } selectModel.options.push(option); @@ -92,7 +91,6 @@ export class CoreQuestionBaseComponent { if (!selectModel.selected) { // No selected option, select the first one. selectModel.selected = selectModel.options[0].value; - selectModel.selectedLabel = selectModel.options[0].label; } // Get the accessibility label. @@ -158,7 +156,7 @@ export class CoreQuestionBaseComponent { // Check which one should be displayed first: the options or the input. const input = questionDiv.querySelector('input[type="text"][name*=answer]'); this.question.optionsFirst = - questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(options[0].outerHTML); + questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(radios[0].outerHTML); } } @@ -313,7 +311,7 @@ export class CoreQuestionBaseComponent { if (questionDiv) { // Find rows. - const rows = Array.from(questionDiv.querySelectorAll('tr')); + const rows = Array.from(questionDiv.querySelectorAll('table.answer tr')); if (!rows || !rows.length) { this.logger.warn('Aborting because couldn\'t find any row.', this.question.name); @@ -376,7 +374,7 @@ export class CoreQuestionBaseComponent { }; if (option.selected) { - rowModel.selected = option; + rowModel.selected = option.value; } rowModel.options.push(option); @@ -404,8 +402,6 @@ export class CoreQuestionBaseComponent { const questionDiv = this.initComponent(); if (questionDiv) { - // Create the model for radio buttons. - this.question.singleChoiceModel = {}; // Get the prompt. this.question.prompt = this.domUtils.getContentsOfElement(questionDiv, '.prompt'); @@ -481,6 +477,11 @@ export class CoreQuestionBaseComponent { return this.questionHelper.showComponentError(this.onAbort); } + + if (!this.question.multi && typeof this.question.singleChoiceModel == 'undefined') { + // We couldn't find the option to select, select the first one. + this.question.singleChoiceModel = options[0].value; + } } return questionDiv; diff --git a/src/core/question/components/question/question.ts b/src/core/question/components/question/question.ts index ddfb7ecea..57913acc9 100644 --- a/src/core/question/components/question/question.ts +++ b/src/core/question/components/question/question.ts @@ -123,7 +123,7 @@ export class CoreQuestionComponent implements OnInit { promise.then(() => { // Handle behaviour. - this.behaviourDelegate.handleQuestion(this.question, this.question.preferredBehaviour).then((comps) => { + this.behaviourDelegate.handleQuestion(this.question.preferredBehaviour, this.question).then((comps) => { this.behaviourComponents = comps; }); this.questionHelper.extractQbehaviourRedoButton(this.question); diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts index bd4aec3f2..4d3ca91e4 100644 --- a/src/core/question/providers/helper.ts +++ b/src/core/question/providers/helper.ts @@ -319,7 +319,7 @@ export class CoreQuestionHelperProvider { elements = Array.from(form.elements); elements.forEach((element: HTMLInputElement) => { - const name = element.name || ''; + const name = element.name || element.getAttribute('ng-reflect-name') || ''; // Ignore flag and submit inputs. if (!name || name.match(/_:flagged$/) || element.type == 'submit' || element.tagName == 'BUTTON') { @@ -588,13 +588,11 @@ export class CoreQuestionHelperProvider { * @param {string} [error] Error to show. */ showComponentError(onAbort: EventEmitter, error?: string): void { - error = error || 'Error processing the question. This could be caused by custom modifications in your site.'; - // Prevent consecutive errors. const now = Date.now(); if (now - this.lastErrorShown > 500) { this.lastErrorShown = now; - this.domUtils.showErrorModal(error); + this.domUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorparsequestions', true); } onAbort && onAbort.emit();