Merge pull request #1717 from albertgasset/MOBILE-2795

MOBILE-2795 qtype: Fix drag and drop questions in 3.6 sites
main
Juan Leyva 2019-01-10 11:10:36 +01:00 committed by GitHub
commit 49615641af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 22 deletions

View File

@ -197,9 +197,34 @@ export class AddonQtypeDdImageOrTextQuestion {
*/
docStructure(slot: number): AddonQtypeDdImageOrTextQuestionDocStructure {
const topNode = <HTMLElement> this.container.querySelector('.addon-qtype-ddimageortext-container'),
dragItemsArea = <HTMLElement> topNode.querySelector('div.dragitems'),
doc: AddonQtypeDdImageOrTextQuestionDocStructure = {};
let dragItemsArea = <HTMLElement> topNode.querySelector('div.draghomes');
if (dragItemsArea) {
// 3.6+ site, transform HTML so it has the same structure as in Moodle 3.5.
// Remove empty div.dragitems.
topNode.querySelector('div.dragitems').remove();
const ddArea = topNode.querySelector('div.ddarea');
// Move div.dropzones to div.ddarea.
ddArea.appendChild(topNode.querySelector('div.dropzones'));
// Move div.draghomes to div.ddarea and rename the class to .dragitems.
ddArea.appendChild(dragItemsArea);
dragItemsArea.classList.remove('draghomes');
dragItemsArea.classList.add('dragitems');
// Add .dragitemhomesNNN class to drag items.
Array.from(dragItemsArea.querySelectorAll('.draghome')).forEach((draghome, index) => {
draghome.classList.add('dragitemhomes' + index);
});
} else {
dragItemsArea = <HTMLElement> topNode.querySelector('div.dragitems');
}
doc.topNode = (): HTMLElement => {
return topNode;
};

View File

@ -64,12 +64,21 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent
this.question.readOnly = false;
if (this.question.initObjects) {
// Moodle version <= 3.5.
if (typeof this.question.initObjects.drops != 'undefined') {
this.drops = this.question.initObjects.drops;
}
if (typeof this.question.initObjects.readonly != 'undefined') {
this.question.readOnly = this.question.initObjects.readonly;
}
} else if (this.question.amdArgs) {
// Moodle version >= 3.6.
if (typeof this.question.amdArgs[1] != 'undefined') {
this.question.readOnly = this.question.amdArgs[1];
}
if (typeof this.question.amdArgs[2] != 'undefined') {
this.drops = this.question.amdArgs[2];
}
}
this.question.loaded = false;

View File

@ -65,9 +65,11 @@ export class AddonQtypeDdMarkerQuestion {
* @param {any} question The question instance.
* @param {boolean} readOnly Whether it's read only.
* @param {any[]} dropZones The drop zones received in the init object of the question.
* @param {string} [imgSrc] Background image source (3.6+ sites).
*/
constructor(logger: CoreLoggerProvider, protected domUtils: CoreDomUtilsProvider, protected textUtils: CoreTextUtilsProvider,
protected container: HTMLElement, protected question: any, protected readOnly: boolean, protected dropZones: any[]) {
protected container: HTMLElement, protected question: any, protected readOnly: boolean, protected dropZones: any[],
protected imgSrc?: string) {
this.logger = logger.getInstance('AddonQtypeDdMarkerQuestion');
this.graphics = new AddonQtypeDdMarkerGraphicsApi(this, this.domUtils);
@ -645,7 +647,7 @@ export class AddonQtypeDdMarkerQuestion {
// Wait the DOM to be rendered.
setTimeout(() => {
this.pollForImageLoad(question.scriptsCode, question.slot);
this.pollForImageLoad();
});
this.resizeFunction = this.redrawDragsAndDrops.bind(this);
@ -706,24 +708,16 @@ export class AddonQtypeDdMarkerQuestion {
/**
* Wait for the background image to be loaded.
*
* @param {string} scriptsCode Scripts code to fetch background image.
* @param {number} slot Question number also named slot.
*/
pollForImageLoad(scriptsCode?: string, slot?: number): void {
pollForImageLoad(): void {
if (this.afterImageLoadDone) {
// Already treated.
return;
}
const bgImg = this.doc.bgImg();
if (scriptsCode && !bgImg.src) {
const regExp = RegExp('amd\\.init\\("q' + slot + '",[ ]*"([^"]*)"', 'g'),
match = regExp.exec(scriptsCode);
if (match && match.length > 1) {
bgImg.src = match[1];
}
if (!bgImg.src && this.imgSrc) {
bgImg.src = this.imgSrc;
}
const imgLoaded = (): void => {

View File

@ -15,6 +15,9 @@
import { Component, OnInit, OnDestroy, Injector, ElementRef, ViewChild } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { AddonQtypeDdMarkerQuestion } from '../classes/ddmarker';
/**
@ -30,9 +33,12 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
protected element: HTMLElement;
protected questionInstance: AddonQtypeDdMarkerQuestion;
protected dropZones: any[]; // The drop zones received in the init object of the question.
protected imgSrc: string; // Background image URL.
protected destroyed = false;
constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef) {
constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef,
protected sitesProvider: CoreSitesProvider, protected urlUtils: CoreUrlUtilsProvider,
protected filepoolProvider: CoreFilepoolProvider) {
super(loggerProvider, 'AddonQtypeDdMarkerComponent', injector);
this.element = element.nativeElement;
@ -72,12 +78,24 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
this.question.readOnly = false;
if (this.question.initObjects) {
// Moodle version <= 3.5.
if (typeof this.question.initObjects.dropzones != 'undefined') {
this.dropZones = this.question.initObjects.dropzones;
}
if (typeof this.question.initObjects.readonly != 'undefined') {
this.question.readOnly = this.question.initObjects.readonly;
}
} else if (this.question.amdArgs) {
// Moodle version >= 3.6.
if (typeof this.question.amdArgs[1] != 'undefined') {
this.imgSrc = this.question.amdArgs[1];
}
if (typeof this.question.amdArgs[2] != 'undefined') {
this.question.readOnly = this.question.amdArgs[2];
}
if (typeof this.question.amdArgs[3] != 'undefined') {
this.dropZones = this.question.amdArgs[3];
}
}
this.question.loaded = false;
@ -88,10 +106,21 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
*/
questionRendered(): void {
if (!this.destroyed) {
this.domUtils.waitForImages(this.questionTextEl.nativeElement).then(() => {
// Create the instance.
this.questionInstance = new AddonQtypeDdMarkerQuestion(this.loggerProvider, this.domUtils, this.textUtils,
this.element, this.question, this.question.readOnly, this.dropZones);
// Download background image (3.6+ sites).
let promise = null;
const site = this.sitesProvider.getCurrentSite();
if (this.imgSrc && site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(this.imgSrc)) {
promise = this.filepoolProvider.getSrcByUrl(site.id, this.imgSrc, this.component, this.componentId, 0, true, true);
} else {
promise = Promise.resolve(this.imgSrc);
}
promise.then((imgSrc) => {
this.domUtils.waitForImages(this.questionTextEl.nativeElement).then(() => {
// Create the instance.
this.questionInstance = new AddonQtypeDdMarkerQuestion(this.loggerProvider, this.domUtils, this.textUtils,
this.element, this.question, this.question.readOnly, this.dropZones, imgSrc);
});
});
}
}

View File

@ -16,6 +16,7 @@
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionProvider } from '@core/question/providers/question';
import { CoreQuestionHandler } from '@core/question/providers/delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { AddonQtypeDdMarkerComponent } from '../component/ddmarker';
/**
@ -26,7 +27,7 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
name = 'AddonQtypeDdMarker';
type = 'qtype_ddmarker';
constructor(private questionProvider: CoreQuestionProvider) { }
constructor(private questionProvider: CoreQuestionProvider, private questionHelper: CoreQuestionHelperProvider) { }
/**
* Return the name of the behaviour to use for the question.
@ -106,4 +107,21 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
}
/**
* Get the list of files that needs to be downloaded in addition to the files embedded in the HTML.
*
* @param {any} question Question.
* @return {string[]} List of URLs.
*/
getAdditionalDownloadableFiles(question: any): string[] {
this.questionHelper.extractQuestionScripts(question);
if (question.amdArgs && typeof question.amdArgs[1] !== 'undefined') {
// Moodle 3.6+.
return [question.amdArgs[1]];
}
return [];
}
}

View File

@ -107,6 +107,14 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
* @return {boolean} Whether sequencecheck is valid.
*/
validateSequenceCheck?(question: any, offlineSequenceCheck: string): boolean;
/**
* Get the list of files that needs to be downloaded in addition to the files embedded in the HTML.
*
* @param {any} question Question.
* @return {string[]} List of URLs.
*/
getAdditionalDownloadableFiles?(question: any): string[];
}
/**
@ -261,4 +269,16 @@ export class CoreQuestionDelegate extends CoreDelegate {
return this.executeFunctionOnEnabled(type, 'validateSequenceCheck', [question, offlineSequenceCheck]);
}
/**
* Get the list of files that needs to be downloaded in addition to the files embedded in the HTML.
*
* @param {any} question Question.
* @return {string[]} List of URLs.
*/
getAdditionalDownloadableFiles(question: any): string[] {
const type = this.getTypeName(question);
return this.executeFunctionOnEnabled(type, 'getAdditionalDownloadableFiles', [question]) || [];
}
}

View File

@ -242,7 +242,8 @@ export class CoreQuestionHelperProvider {
*/
extractQuestionScripts(question: any): void {
question.scriptsCode = '';
question.initObjects = [];
question.initObjects = null;
question.amdArgs = null;
if (question.html) {
// Search the scripts.
@ -267,7 +268,15 @@ export class CoreQuestionHelperProvider {
initMatch = initMatch.substr(0, initMatch.length - 2);
// Try to convert it to an object and add it to the question.
question.initObjects = this.textUtils.parseJSON(initMatch);
question.initObjects = this.textUtils.parseJSON(initMatch, null);
}
const amdRegExp = new RegExp('require\\(\\["qtype_' + question.type + '/question"\\], ' +
'function\\(amd\\) \\{ amd\.init\\(("q' + question.slot + '".*?)\\); \\}\\);;', 'm');
const amdMatch = match.match(amdRegExp);
if (amdMatch) {
// Try to convert the arguments to an array and add them to the question.
question.amdArgs = this.textUtils.parseJSON('[' + amdMatch[1] + ']', null);
}
});
}
@ -507,6 +516,8 @@ export class CoreQuestionHelperProvider {
componentId = question.id;
}
urls.push(...this.questionDelegate.getAdditionalDownloadableFiles(question));
return this.sitesProvider.getSite(siteId).then((site) => {
const promises = [];