MOBILE-2389 qtype: Implement essay type
parent
38184308a3
commit
7041485359
|
@ -12,10 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
|
@ -27,8 +25,8 @@ import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-
|
|||
})
|
||||
export class AddonQtypeCalculatedComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeCalculatedComponent', questionHelper, domUtils);
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeCalculatedComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,10 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
|
@ -27,8 +25,8 @@ import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-
|
|||
})
|
||||
export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeDescriptionComponent', questionHelper, domUtils);
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeDescriptionComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<section ion-list *ngIf="question.text || question.text === ''">
|
||||
<!-- Question text. -->
|
||||
<ion-item text-wrap>
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
|
||||
</ion-item>
|
||||
|
||||
<!-- Textarea. -->
|
||||
<ion-item *ngIf="question.textarea && !question.hasDraftFiles">
|
||||
<!-- "Format" hidden input -->
|
||||
<input *ngIf="question.formatInput" type="hidden" [name]="question.formatInput.name" [value]="question.formatInput.value" >
|
||||
<!-- Plain text textarea. -->
|
||||
<ion-textarea *ngIf="question.isPlainText" class="core-question-textarea" [ngClass]='{"core-monospaced": question.isMonospaced}' placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.textarea.name" aria-multiline="true">{{question.textarea.text}}</ion-textarea>
|
||||
<!-- Rich text editor. -->
|
||||
<core-rich-text-editor *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}"></core-rich-text-editor>
|
||||
<!-- @todo: Attributes that were passed to RTE in Ionic 1 but now they aren't supported yet:
|
||||
model="textarea" [name]="textarea.name" [component]="component" [componentId]="componentId" -->
|
||||
</ion-item>
|
||||
|
||||
<!-- Draft files not supported. -->
|
||||
<ng-container *ngIf="question.textarea && question.hasDraftFiles">
|
||||
<ion-item text-wrap class="core-error-item">
|
||||
<p class="core-question-warning">{{ 'core.question.errorinlinefilesnotsupported' | translate }}</p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.textarea.text"></core-format-text></p>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Attachments not supported in the app yet. -->
|
||||
<ion-item text-wrap *ngIf="question.allowsAttachments" class="core-error-item">
|
||||
<p class="core-question-warning">{{ 'core.question.errorattachmentsnotsupported' | translate }}</p>
|
||||
</ion-item>
|
||||
|
||||
<!-- Answer to the question and attachments (reviewing). -->
|
||||
<ion-item text-wrap *ngIf="!question.textarea && (question.answer || (!question.attachments.length && !question.allowsAttachments))">
|
||||
<p><core-format-text [ngClass]='{"core-monospaced": question.isMonospaced}' [component]="component" [componentId]="componentId" [text]="question.answer"></core-format-text></p>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="!question.textarea && question.attachments && question.attachments.length">
|
||||
<core-file *ngFor="let attachment of question.attachments" [file]="attachment" [component]="component" [componentId]="componentId"></core-file>
|
||||
</ion-item>
|
||||
</section>
|
|
@ -0,0 +1,38 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
* Component to render an essay question.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-qtype-essay',
|
||||
templateUrl: 'essay.html'
|
||||
})
|
||||
export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeEssayComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.initEssayComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonQtypeEssayHandler } from './providers/handler';
|
||||
import { AddonQtypeEssayComponent } from './component/essay';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonQtypeEssayComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeEssayHandler
|
||||
],
|
||||
exports: [
|
||||
AddonQtypeEssayComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonQtypeEssayComponent
|
||||
]
|
||||
})
|
||||
export class AddonQtypeEssayModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeEssayHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
|
||||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { AddonQtypeEssayComponent } from '../component/essay';
|
||||
|
||||
/**
|
||||
* Handler to support essay question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeEssay';
|
||||
type = 'qtype_essay';
|
||||
|
||||
protected div = document.createElement('div'); // A div element to search in HTML code.
|
||||
|
||||
constructor(private utils: CoreUtilsProvider, private questionHelper: CoreQuestionHelperProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Return the name of the behaviour to use for the question.
|
||||
* If the question should use the default behaviour you shouldn't implement this function.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {string} behaviour The default behaviour.
|
||||
* @return {string} The behaviour to use.
|
||||
*/
|
||||
getBehaviour(question: any, behaviour: string): string {
|
||||
return 'manualgraded';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the question.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param {Injector} injector Injector.
|
||||
* @param {any} question The question to render.
|
||||
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(injector: Injector, question: any): any | Promise<any> {
|
||||
return AddonQtypeEssayComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a question can be submitted.
|
||||
* If a question cannot be submitted it should return a message explaining why (translated or not).
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @return {string} Prevent submit message. Undefined or empty if can be submitted.
|
||||
*/
|
||||
getPreventSubmitMessage(question: any): string {
|
||||
this.div.innerHTML = question.html;
|
||||
|
||||
if (this.div.querySelector('div[id*=filemanager]')) {
|
||||
// The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
|
||||
return 'core.question.errorattachmentsnotsupported';
|
||||
}
|
||||
|
||||
if (this.questionHelper.hasDraftFileUrls(this.div.innerHTML)) {
|
||||
return 'core.question.errorinlinefilesnotsupported';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a response is complete.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {any} answers Object with the question answers (without prefix).
|
||||
* @return {number} 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||
*/
|
||||
isCompleteResponse(question: any, answers: any): number {
|
||||
this.div.innerHTML = question.html;
|
||||
|
||||
const hasInlineText = answers['answer'] && answers['answer'] !== '',
|
||||
allowsAttachments = !!this.div.querySelector('div[id*=filemanager]');
|
||||
|
||||
if (!allowsAttachments) {
|
||||
return hasInlineText ? 1 : 0;
|
||||
}
|
||||
|
||||
// We can't know if the attachments are required or if the user added any in web.
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
||||
* or whether it must be considered aborted.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {any} answers Object with the question answers (without prefix).
|
||||
* @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||
*/
|
||||
isGradableResponse(question: any, answers: any): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two responses are the same.
|
||||
*
|
||||
* @param {any} question Question.
|
||||
* @param {any} prevAnswers Object with the previous question answers.
|
||||
* @param {any} newAnswers Object with the new question answers.
|
||||
* @return {boolean} Whether they're the same.
|
||||
*/
|
||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and add to answers the data to send to server based in the input. Return promise if async.
|
||||
*
|
||||
* @param {any} question Question.
|
||||
* @param {any} answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||
* @param {boolean} [offline] Whether the data should be saved in offline.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {void|Promise<any>} Return a promise resolved when done if async, void if sync.
|
||||
*/
|
||||
prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> {
|
||||
this.div.innerHTML = question.html;
|
||||
|
||||
// Search the textarea to get its name.
|
||||
const textarea = <HTMLTextAreaElement> this.div.querySelector('textarea[name*=_answer]');
|
||||
|
||||
if (textarea && typeof answers[textarea.name] != 'undefined') {
|
||||
return this.domUtils.isRichTextEditorEnabled().then((enabled) => {
|
||||
if (!enabled) {
|
||||
// Rich text editor not enabled, add some HTML to the text if needed.
|
||||
answers[textarea.name] = this.textUtils.formatHtmlLines(answers[textarea.name]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,10 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
|
@ -27,8 +25,8 @@ import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-
|
|||
})
|
||||
export class AddonQtypeMatchComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeMatchComponent', questionHelper, domUtils);
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeMatchComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,10 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
|
@ -27,8 +25,8 @@ import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-
|
|||
})
|
||||
export class AddonQtypeMultichoiceComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeMultichoiceComponent', questionHelper, domUtils);
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeMultichoiceComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@ import { AddonQtypeCalculatedModule } from './calculated/calculated.module';
|
|||
import { AddonQtypeCalculatedMultiModule } from './calculatedmulti/calculatedmulti.module';
|
||||
import { AddonQtypeCalculatedSimpleModule } from './calculatedsimple/calculatedsimple.module';
|
||||
import { AddonQtypeDescriptionModule } from './description/description.module';
|
||||
import { AddonQtypeEssayModule } from './essay/essay.module';
|
||||
import { AddonQtypeMatchModule } from './match/match.module';
|
||||
import { AddonQtypeMultichoiceModule } from './multichoice/multichoice.module';
|
||||
import { AddonQtypeNumericalModule } from './numerical/numerical.module';
|
||||
|
@ -31,6 +32,7 @@ import { AddonQtypeTrueFalseModule } from './truefalse/truefalse.module';
|
|||
AddonQtypeCalculatedMultiModule,
|
||||
AddonQtypeCalculatedSimpleModule,
|
||||
AddonQtypeDescriptionModule,
|
||||
AddonQtypeEssayModule,
|
||||
AddonQtypeMatchModule,
|
||||
AddonQtypeMultichoiceModule,
|
||||
AddonQtypeNumericalModule,
|
||||
|
|
|
@ -12,10 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
|
@ -27,8 +25,8 @@ import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-
|
|||
})
|
||||
export class AddonQtypeShortAnswerComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeShortAnswerComponent', questionHelper, domUtils);
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeShortAnswerComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Input, EventEmitter } from '@angular/core';
|
||||
import { Input, EventEmitter, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
|
||||
/**
|
||||
|
@ -30,10 +31,17 @@ export class CoreQuestionBaseComponent {
|
|||
@Input() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
|
||||
|
||||
protected logger;
|
||||
protected questionHelper: CoreQuestionHelperProvider;
|
||||
protected domUtils: CoreDomUtilsProvider;
|
||||
protected textUtils: CoreTextUtilsProvider;
|
||||
|
||||
constructor(logger: CoreLoggerProvider, logName: string, protected questionHelper: CoreQuestionHelperProvider,
|
||||
protected domUtils: CoreDomUtilsProvider) {
|
||||
constructor(logger: CoreLoggerProvider, logName: string, protected injector: Injector) {
|
||||
this.logger = logger.getInstance(logName);
|
||||
|
||||
// Use an injector to get the providers to prevent having to modify all subclasses if a new provider is needed.
|
||||
this.questionHelper = injector.get(CoreQuestionHelperProvider);
|
||||
this.domUtils = injector.get(CoreDomUtilsProvider);
|
||||
this.textUtils = injector.get(CoreTextUtilsProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,6 +188,48 @@ export class CoreQuestionBaseComponent {
|
|||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component of type essay.
|
||||
*
|
||||
* @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid.
|
||||
*/
|
||||
initEssayComponent(): void | HTMLElement {
|
||||
const questionDiv = this.initComponent();
|
||||
|
||||
if (questionDiv) {
|
||||
// First search the textarea.
|
||||
const textarea = <HTMLTextAreaElement> questionDiv.querySelector('textarea[name*=_answer]');
|
||||
this.question.allowsAttachments = !!questionDiv.querySelector('div[id*=filemanager]');
|
||||
this.question.isMonospaced = !!questionDiv.querySelector('.qtype_essay_monospaced');
|
||||
this.question.isPlainText = this.question.isMonospaced || !!questionDiv.querySelector('.qtype_essay_plain');
|
||||
this.question.hasDraftFiles = this.questionHelper.hasDraftFileUrls(questionDiv.innerHTML);
|
||||
|
||||
if (!textarea) {
|
||||
// Textarea not found, we might be in review. Search the answer and the attachments.
|
||||
this.question.answer = this.domUtils.getContentsOfElement(questionDiv, '.qtype_essay_response');
|
||||
this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml(
|
||||
this.domUtils.getContentsOfElement(questionDiv, '.attachments'));
|
||||
} else {
|
||||
// Textarea found.
|
||||
const input = <HTMLInputElement> questionDiv.querySelector('input[type="hidden"][name*=answerformat]'),
|
||||
content = textarea.innerHTML;
|
||||
|
||||
this.question.textarea = {
|
||||
id: textarea.id,
|
||||
name: textarea.name,
|
||||
text: content ? this.textUtils.decodeHTML(content) : ''
|
||||
};
|
||||
|
||||
if (input) {
|
||||
this.question.formatInput = {
|
||||
name: input.name,
|
||||
value: input.value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component that has an input of type "text".
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable, EventEmitter } from '@angular/core';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreQuestionProvider } from './question';
|
||||
|
@ -26,7 +27,7 @@ export class CoreQuestionHelperProvider {
|
|||
protected div = document.createElement('div'); // A div element to search in HTML code.
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider,
|
||||
private questionProvider: CoreQuestionProvider) { }
|
||||
private questionProvider: CoreQuestionProvider, private sitesProvider: CoreSitesProvider) { }
|
||||
|
||||
/**
|
||||
* Add a behaviour button to the question's "behaviourButtons" property.
|
||||
|
@ -266,6 +267,40 @@ export class CoreQuestionHelperProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an HTML code with list of attachments, returns the list of attached files (filename and fileurl).
|
||||
* Please take into account that this function will treat all the anchors in the HTML, you should provide
|
||||
* an HTML containing only the attachments anchors.
|
||||
*
|
||||
* @param {String} html HTML code to search in.
|
||||
* @return {Object[]} Attachments.
|
||||
*/
|
||||
getQuestionAttachmentsFromHtml(html: string): any[] {
|
||||
this.div.innerHTML = html;
|
||||
|
||||
// Remove the filemanager (area to attach files to a question).
|
||||
this.domUtils.removeElement(this.div, 'div[id*=filemanager]');
|
||||
|
||||
// Search the anchors.
|
||||
const anchors = Array.from(this.div.querySelectorAll('a')),
|
||||
attachments = [];
|
||||
|
||||
anchors.forEach((anchor) => {
|
||||
let content = anchor.innerHTML;
|
||||
|
||||
// Check anchor is valid.
|
||||
if (anchor.href && content) {
|
||||
content = this.textUtils.cleanTags(content, true).trim();
|
||||
attachments.push({
|
||||
filename: content,
|
||||
fileurl: anchor.href
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sequence check from a question HTML.
|
||||
*
|
||||
|
@ -299,6 +334,22 @@ export class CoreQuestionHelperProvider {
|
|||
return this.domUtils.getContentsOfElement(this.div, '.validationerror');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if some HTML contains draft file URLs for the current site.
|
||||
*
|
||||
* @param {string} html Question's HTML.
|
||||
* @return {boolean} Whether it contains draft files URLs.
|
||||
*/
|
||||
hasDraftFileUrls(html: string): boolean {
|
||||
let url = this.sitesProvider.getCurrentSite().getURL();
|
||||
if (url.slice(-1) != '/') {
|
||||
url = url += '/';
|
||||
}
|
||||
url += 'draftfile.php';
|
||||
|
||||
return html.indexOf(url) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each input element found in the HTML, search if there's a local answer stored and
|
||||
* override the HTML's value with the local one.
|
||||
|
|
Loading…
Reference in New Issue