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 { docStructure(slot: number): AddonQtypeDdImageOrTextQuestionDocStructure {
const topNode = <HTMLElement> this.container.querySelector('.addon-qtype-ddimageortext-container'), const topNode = <HTMLElement> this.container.querySelector('.addon-qtype-ddimageortext-container'),
dragItemsArea = <HTMLElement> topNode.querySelector('div.dragitems'),
doc: AddonQtypeDdImageOrTextQuestionDocStructure = {}; 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 => { doc.topNode = (): HTMLElement => {
return topNode; return topNode;
}; };

View File

@ -64,12 +64,21 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent
this.question.readOnly = false; this.question.readOnly = false;
if (this.question.initObjects) { if (this.question.initObjects) {
// Moodle version <= 3.5.
if (typeof this.question.initObjects.drops != 'undefined') { if (typeof this.question.initObjects.drops != 'undefined') {
this.drops = this.question.initObjects.drops; this.drops = this.question.initObjects.drops;
} }
if (typeof this.question.initObjects.readonly != 'undefined') { if (typeof this.question.initObjects.readonly != 'undefined') {
this.question.readOnly = this.question.initObjects.readonly; 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; this.question.loaded = false;

View File

@ -65,9 +65,11 @@ export class AddonQtypeDdMarkerQuestion {
* @param {any} question The question instance. * @param {any} question The question instance.
* @param {boolean} readOnly Whether it's read only. * @param {boolean} readOnly Whether it's read only.
* @param {any[]} dropZones The drop zones received in the init object of the question. * @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, 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.logger = logger.getInstance('AddonQtypeDdMarkerQuestion');
this.graphics = new AddonQtypeDdMarkerGraphicsApi(this, this.domUtils); this.graphics = new AddonQtypeDdMarkerGraphicsApi(this, this.domUtils);
@ -645,7 +647,7 @@ export class AddonQtypeDdMarkerQuestion {
// Wait the DOM to be rendered. // Wait the DOM to be rendered.
setTimeout(() => { setTimeout(() => {
this.pollForImageLoad(question.scriptsCode, question.slot); this.pollForImageLoad();
}); });
this.resizeFunction = this.redrawDragsAndDrops.bind(this); this.resizeFunction = this.redrawDragsAndDrops.bind(this);
@ -706,24 +708,16 @@ export class AddonQtypeDdMarkerQuestion {
/** /**
* Wait for the background image to be loaded. * 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) { if (this.afterImageLoadDone) {
// Already treated. // Already treated.
return; return;
} }
const bgImg = this.doc.bgImg(); const bgImg = this.doc.bgImg();
if (!bgImg.src && this.imgSrc) {
if (scriptsCode && !bgImg.src) { bgImg.src = this.imgSrc;
const regExp = RegExp('amd\\.init\\("q' + slot + '",[ ]*"([^"]*)"', 'g'),
match = regExp.exec(scriptsCode);
if (match && match.length > 1) {
bgImg.src = match[1];
}
} }
const imgLoaded = (): void => { const imgLoaded = (): void => {

View File

@ -15,6 +15,9 @@
import { Component, OnInit, OnDestroy, Injector, ElementRef, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, Injector, ElementRef, ViewChild } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; 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'; import { AddonQtypeDdMarkerQuestion } from '../classes/ddmarker';
/** /**
@ -30,9 +33,12 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
protected element: HTMLElement; protected element: HTMLElement;
protected questionInstance: AddonQtypeDdMarkerQuestion; protected questionInstance: AddonQtypeDdMarkerQuestion;
protected dropZones: any[]; // The drop zones received in the init object of the question. protected dropZones: any[]; // The drop zones received in the init object of the question.
protected imgSrc: string; // Background image URL.
protected destroyed = false; 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); super(loggerProvider, 'AddonQtypeDdMarkerComponent', injector);
this.element = element.nativeElement; this.element = element.nativeElement;
@ -72,12 +78,24 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
this.question.readOnly = false; this.question.readOnly = false;
if (this.question.initObjects) { if (this.question.initObjects) {
// Moodle version <= 3.5.
if (typeof this.question.initObjects.dropzones != 'undefined') { if (typeof this.question.initObjects.dropzones != 'undefined') {
this.dropZones = this.question.initObjects.dropzones; this.dropZones = this.question.initObjects.dropzones;
} }
if (typeof this.question.initObjects.readonly != 'undefined') { if (typeof this.question.initObjects.readonly != 'undefined') {
this.question.readOnly = this.question.initObjects.readonly; 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; this.question.loaded = false;
@ -88,10 +106,21 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
*/ */
questionRendered(): void { questionRendered(): void {
if (!this.destroyed) { if (!this.destroyed) {
// 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(() => { this.domUtils.waitForImages(this.questionTextEl.nativeElement).then(() => {
// Create the instance. // Create the instance.
this.questionInstance = new AddonQtypeDdMarkerQuestion(this.loggerProvider, this.domUtils, this.textUtils, this.questionInstance = new AddonQtypeDdMarkerQuestion(this.loggerProvider, this.domUtils, this.textUtils,
this.element, this.question, this.question.readOnly, this.dropZones); this.element, this.question, this.question.readOnly, this.dropZones, imgSrc);
});
}); });
} }
} }

View File

@ -16,6 +16,7 @@
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreQuestionProvider } from '@core/question/providers/question';
import { CoreQuestionHandler } from '@core/question/providers/delegate'; import { CoreQuestionHandler } from '@core/question/providers/delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { AddonQtypeDdMarkerComponent } from '../component/ddmarker'; import { AddonQtypeDdMarkerComponent } from '../component/ddmarker';
/** /**
@ -26,7 +27,7 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
name = 'AddonQtypeDdMarker'; name = 'AddonQtypeDdMarker';
type = 'qtype_ddmarker'; 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. * 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 { isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); 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. * @return {boolean} Whether sequencecheck is valid.
*/ */
validateSequenceCheck?(question: any, offlineSequenceCheck: string): boolean; 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]); 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 { extractQuestionScripts(question: any): void {
question.scriptsCode = ''; question.scriptsCode = '';
question.initObjects = []; question.initObjects = null;
question.amdArgs = null;
if (question.html) { if (question.html) {
// Search the scripts. // Search the scripts.
@ -267,7 +268,15 @@ export class CoreQuestionHelperProvider {
initMatch = initMatch.substr(0, initMatch.length - 2); initMatch = initMatch.substr(0, initMatch.length - 2);
// Try to convert it to an object and add it to the question. // 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; componentId = question.id;
} }
urls.push(...this.questionDelegate.getAdditionalDownloadableFiles(question));
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const promises = []; const promises = [];