Skip to content
Snippets Groups Projects
Commit ea9fd2ea authored by srosse's avatar srosse
Browse files

OO-2926: choose cardinality of hotspot interaction in editor

parent db46be24
No related branches found
No related tags found
No related merge requests found
Showing
with 185 additions and 116 deletions
......@@ -325,10 +325,13 @@ public class AssessmentItemFactory {
return responseDeclaration;
}
public static ResponseDeclaration createHotspotCorrectResponseDeclaration(AssessmentItem assessmentItem, Identifier declarationId, List<Identifier> correctResponseIds) {
public static ResponseDeclaration createHotspotCorrectResponseDeclaration(AssessmentItem assessmentItem, Identifier declarationId,
List<Identifier> correctResponseIds, Cardinality cardinality) {
ResponseDeclaration responseDeclaration = new ResponseDeclaration(assessmentItem);
responseDeclaration.setIdentifier(declarationId);
if(correctResponseIds == null || correctResponseIds.size() == 0 || correctResponseIds.size() > 1) {
if(cardinality != null && (cardinality == Cardinality.SINGLE || cardinality == Cardinality.MULTIPLE)) {
responseDeclaration.setCardinality(cardinality);
} else if(correctResponseIds == null || correctResponseIds.size() == 0 || correctResponseIds.size() > 1) {
responseDeclaration.setCardinality(Cardinality.MULTIPLE);
} else {
responseDeclaration.setCardinality(Cardinality.SINGLE);
......
......@@ -74,6 +74,7 @@ import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
import uk.ac.ed.ph.jqtiplus.types.ComplexReferenceIdentifier;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.value.BaseType;
import uk.ac.ed.ph.jqtiplus.value.Cardinality;
import uk.ac.ed.ph.jqtiplus.value.IdentifierValue;
import uk.ac.ed.ph.jqtiplus.value.SingleValue;
......@@ -86,6 +87,7 @@ import uk.ac.ed.ph.jqtiplus.value.SingleValue;
public class HotspotAssessmentItemBuilder extends AssessmentItemBuilder implements ResponseIdentifierForFeedback {
private String question;
private Cardinality cardinality;
private Identifier responseIdentifier;
private List<Identifier> correctAnswers;
protected ScoreEvaluation scoreEvaluation;
......@@ -152,9 +154,12 @@ public class HotspotAssessmentItemBuilder extends AssessmentItemBuilder implemen
if(hotspotInteraction != null) {
ResponseDeclaration responseDeclaration = assessmentItem
.getResponseDeclaration(hotspotInteraction.getResponseIdentifier());
if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
extractIdentifiersFromCorrectResponse(correctResponse, correctAnswers);
if(responseDeclaration != null) {
if(responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
extractIdentifiersFromCorrectResponse(correctResponse, correctAnswers);
}
cardinality = responseDeclaration.getCardinality();
}
}
}
......@@ -188,6 +193,14 @@ public class HotspotAssessmentItemBuilder extends AssessmentItemBuilder implemen
return responseIdentifier;
}
public boolean isSingleChoice() {
return cardinality == Cardinality.SINGLE;
}
public void setCardinality(Cardinality cardinality) {
this.cardinality = cardinality;
}
@Override
public List<Answer> getAnswers() {
List<HotspotChoice> hotspotChoices = getHotspotChoices();
......@@ -345,7 +358,7 @@ public class HotspotAssessmentItemBuilder extends AssessmentItemBuilder implemen
@Override
protected void buildResponseAndOutcomeDeclarations() {
ResponseDeclaration responseDeclaration = AssessmentItemFactory
.createHotspotCorrectResponseDeclaration(assessmentItem, responseIdentifier, correctAnswers);
.createHotspotCorrectResponseDeclaration(assessmentItem, responseIdentifier, correctAnswers, cardinality);
if(scoreEvaluation == ScoreEvaluation.perAnswer) {
AssessmentItemFactory.appendMapping(responseDeclaration, scoreMapping);
}
......
......@@ -55,6 +55,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.GapMatchInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicAssociateInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicGapMatchInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicOrderInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.HotspotInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.HottextInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.InlineChoiceInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction;
......@@ -249,6 +250,13 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor
return false;
}
return sc;
} else if(interaction instanceof HotspotInteraction) {
HotspotInteraction hotspotInteraction = (HotspotInteraction)interaction;
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(hotspotInteraction.getResponseIdentifier());
if(responseDeclaration != null && responseDeclaration.hasCardinality(Cardinality.SINGLE)) {
return true;
}
return false;
}
return false;
}
......
......@@ -2,6 +2,7 @@
#set($qtiContainerId = "oc_" + $responseIdentifier)
#set($responseValue = $r.getResponseValue($interaction.responseIdentifier))
#set($isResponsive = $r.hasCssClass($interaction, "interaction-responsive"))
#set($singleChoice = $r.isSingleChoice($interaction))
<input name="qtiworks_presented_${responseIdentifier}" type="hidden" value="1"/>
<div class="$localName">
......@@ -44,6 +45,7 @@
responseIdentifier: '$responseIdentifier',
formDispatchFieldId: '$r.formDispatchFieldId',
maxChoices: $interaction.maxChoices,
singleChoice: $singleChoice,
responseValue: '$r.toString($responseValue,",")',
opened: $isItemSessionOpen
});
......
......@@ -26,6 +26,7 @@ editor.unkown.title=Unbekanntes interaction
error.assessment.test=Die Datei konnte nicht gelesen werden. Sie ist entweder korrupt oder mit dem falschen Format gespeichert.
error.cannot.create.section=Sie k\u00F6nnen hier keine Sektion erstellen.
error.cannot.delete=Sie d\u00FCrfen diese Ressource nicht l\u00F6schen.
error.cardinality.answer=Single choice erlaubt nur eine korrekte Antwort.
error.double=$org.olat.ims.qti21.ui\:error.double
error.import.question=Die Frage konnte wegen eine unerwartete Fehler nicht importiert werden
error.integer=$org.olat.ims.qti21.ui\:error.integer
......@@ -87,6 +88,7 @@ form.imd.answered.title=Titel
form.imd.background=Hintergrund
form.imd.background.resize=Bildgr\u00F6sse f\u00FCr das Web optimieren
form.imd.background.resize.no=Nicht anpassen
form.imd.cardinality=Typ
form.imd.condition=Bedingung(en)
form.imd.correct.kprim=Richtig
form.imd.correct.spots=Korrekte Spots
......@@ -177,6 +179,7 @@ min.choices=Min. Anzahl von m
min.choices.unlimited=Nicht begrenzet
min.score=Minimal erreichbare Punktzahl
minute.short=m
MULTIPLE=Multiple choice
new.answer=Neue Antwort
new.circle=Kreis
new.drawing=Zeichnen
......@@ -200,6 +203,7 @@ new.testpart=Test-Part
new.upload=Datei hochladen
preview=Vorschau
preview.solution=Vorschau L\u00F6sung
SINGLE=Single choice
time.limit.max=Zeitbeschr\u00E4nkung
title.add=$org.olat.ims.qti.editor\:title.add
tools.change.copy=$org.olat.ims.qti.editor\:tools.change.copy
......
......@@ -27,6 +27,7 @@ editor.unkown.title=Unkown interaction
error.assessment.test=The file cannot be interpreted. It seems corrupted or with the wrong format.
error.cannot.create.section=A section cannot be created everywhere\!
error.cannot.delete=You cannot delete this object.
error.cardinality.answer=Single choice allow only one correct answer.
error.double=$org.olat.ims.qti21.ui\:error.double
error.import.question=An unexpected error happens during import of a question.
error.integer=$org.olat.ims.qti21.ui\:error.integer
......@@ -88,6 +89,7 @@ form.imd.answered.title=Title
form.imd.background=Background
form.imd.background.resize=Optimize an image size for the Web
form.imd.background.resize.no=Don't change
form.imd.cardinality=Type
form.imd.condition=Condition(s)
form.imd.correct.kprim=True
form.imd.correct.spots=Correct spots
......@@ -178,6 +180,7 @@ min.choices=Min. number of possible answers
min.choices.unlimited=Not limited
min.score=Min. score
minute.short=m
MULTIPLE=Multiple choice
new.answer=New answer
new.circle=Circle
new.drawing=Drawing
......@@ -203,6 +206,7 @@ preview=Preview
preview.solution=Preview solution
time.limit.max=Time limit
title.add=$org.olat.ims.qti.editor\:title.add
SINGLE=Single choice
tools.change.copy=$org.olat.ims.qti.editor\:tools.change.copy
tools.change.delete=$org.olat.ims.qti.editor\:tools.change.delete
tools.export.docx=$org.olat.ims.qti.editor\:tools.export.docx
......
......@@ -66,6 +66,7 @@ form.imd.answered.title=Titre
form.imd.background=Image de fond
form.imd.background.resize=Optimiser la taille de l'image pour le web
form.imd.background.resize.no=Laisser inchang\u00E9
form.imd.cardinality=Type
form.imd.correct.kprim=Vrai
form.imd.correctSolution.text=Solution correcte
form.imd.correctSolution.text.word=$\:form.imd.correctSolution.text (seulement pour export Word)
......
......@@ -69,6 +69,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import uk.ac.ed.ph.jqtiplus.node.expression.operator.Shape;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.graphic.HotspotChoice;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.value.Cardinality;
/**
*
......@@ -92,6 +93,7 @@ public class HotspotEditorController extends FormBasicController {
private RichTextElement textEl;
private FileElement backgroundEl;
private SingleSelection resizeEl;
private SingleSelection cardinalityEl;
private FormLayoutContainer hotspotsCont;
private MultipleSelectionElement responsiveEl;
private FormLink newCircleButton, newRectButton;
......@@ -143,6 +145,15 @@ public class HotspotEditorController extends FormBasicController {
formLayout, ureq.getUserSession(), getWindowControl());
textEl.addActionListener(FormEvent.ONCLICK);
String[] cardinalityKeys = new String[] { Cardinality.SINGLE.name(), Cardinality.MULTIPLE.name() };
String[] cardinalityValues = new String[] { translate(Cardinality.SINGLE.name()), translate(Cardinality.MULTIPLE.name()) };
cardinalityEl = uifactory.addRadiosHorizontal("form.imd.cardinality", formLayout, cardinalityKeys, cardinalityValues);
if(itemBuilder.isSingleChoice()) {
cardinalityEl.select(cardinalityKeys[0], true);
} else {
cardinalityEl.select(cardinalityKeys[1], true);
}
responsiveEl = uifactory.addCheckboxesHorizontal("form.imd.responsive", formLayout, onKeys, new String[] {""});
responsiveEl.setHelpText(translate("form.imd.responsive.hint"));
if(itemBuilder.isResponsive()) {
......@@ -240,6 +251,12 @@ public class HotspotEditorController extends FormBasicController {
}
}
cardinalityEl.clearError();
if(cardinalityEl.isSelected(0) && correctHotspotsEl.getSelectedKeys().size() > 1) {
cardinalityEl.setErrorKey("error.cardinality.answer", null);
allOk &= false;
}
return allOk & super.validateFormLogic(ureq);
}
......@@ -418,6 +435,11 @@ public class HotspotEditorController extends FormBasicController {
objectImg = initialBackgroundImage;
}
if(cardinalityEl.isOneSelected()) {
String selectedCardinality = cardinalityEl.getSelectedKey();
itemBuilder.setCardinality(Cardinality.valueOf(selectedCardinality));
}
boolean updateHotspot = true;
if(objectImg != null) {
......
(function ($) {
$.fn.hotspotInteraction = function(options) {
var settings = $.extend({
responseIdentifier: null,
formDispatchFieldId: null,
maxChoices: 1,
responseValue: null,
opened: false
}, options );
try {
if(!(typeof settings.responseValue === "undefined") && settings.responseValue.length > 0) {
drawHotspotAreas(this, settings);
}
if(settings.opened) {
hotspots(this, settings);
}
} catch(e) {
if(window.console) console.log(e);
}
return this;
};
function drawHotspotAreas($obj, settings) {
var containerId = $obj.attr('id');
var divContainer = jQuery('#' + containerId);
var areaIds = settings.responseValue.split(',');
for(i=areaIds.length; i-->0; ) {
var areaEl = jQuery('#ac_' + settings.responseIdentifier + '_' + areaIds[i]);
var data = areaEl.data('maphilight') || {};
data.selectedOn = true;
colorData(data);
areaEl.data('maphilight', data).trigger('alwaysOn.maphilight');
var inputElement = jQuery('<input type="hidden"/>')
$.fn.hotspotInteraction = function(options) {
var settings = $.extend({
responseIdentifier: null,
formDispatchFieldId: null,
maxChoices: 1,
singleChoice: false,
responseValue: null,
opened: false
}, options );
try {
if(!(typeof settings.responseValue === "undefined") && settings.responseValue.length > 0) {
drawHotspotAreas(this, settings);
}
if(settings.opened) {
hotspots(this, settings);
}
} catch(e) {
if(window.console) console.log(e);
}
return this;
};
function drawHotspotAreas($obj, settings) {
var containerId = $obj.attr('id');
var divContainer = jQuery('#' + containerId);
var areaIds = settings.responseValue.split(',');
for(i=areaIds.length; i-->0; ) {
var areaEl = jQuery('#ac_' + settings.responseIdentifier + '_' + areaIds[i]);
var data = areaEl.data('maphilight') || {};
data.selectedOn = true;
colorData(data);
areaEl.data('maphilight', data).trigger('alwaysOn.maphilight');
var inputElement = jQuery('<input type="hidden"/>')
.attr('name', 'qtiworks_response_' + settings.responseIdentifier)
.attr('value', areaEl.data('qti-id'));
divContainer.append(inputElement);
}
}
function hotspots($obj, settings) {
var containerId = $obj.attr('id');
jQuery('#' + containerId + " map area").each(function(index, el) {
jQuery(el).on('click', function() {
clickHotspotArea(this, containerId, settings.responseIdentifier, settings.maxChoices);
});
})
};
divContainer.append(inputElement);
}
}
function hotspots($obj, settings) {
var containerId = $obj.attr('id');
jQuery('#' + containerId + " map area").each(function(index, el) {
jQuery(el).on('click', function() {
clickHotspotArea(this, containerId, settings.responseIdentifier, settings.maxChoices, settings.singleChoice);
});
})
};
function clickHotspotArea(spot, containerId, responseIdentifier, maxChoices) {
var areaEl = jQuery(spot);
var data = areaEl.data('maphilight') || {};
if((typeof data.selectedOn === "undefined") || !data.selectedOn) {
var numOfChoices = maxChoices;
if(numOfChoices > 0) {
var countChoices = 0;
jQuery("area", "map[name='" + containerId + "_map']").each(function(index, el) {
var cData = jQuery(el).data('maphilight') || {};
if(cData.selectedOn) {
countChoices++;
}
});
if(countChoices >= numOfChoices) {
return false;
}
}
}
if(typeof data.selectedOn === "undefined") {
data.selectedOn = true;
} else {
data.selectedOn = !data.selectedOn;
}
colorData(data);
areaEl.data('maphilight', data).trigger('alwaysOn.maphilight');
function clickHotspotArea(spot, containerId, responseIdentifier, maxChoices, singleChoice) {
var areaEl = jQuery(spot);
var data = areaEl.data('maphilight') || {};
if((typeof data.selectedOn === "undefined") || !data.selectedOn) {
if(singleChoice) {
jQuery("area", "map[name='" + containerId + "_map']").each(function(index, el) {
var cData = jQuery(el).data('maphilight') || {};
if(cData.selectedOn) {
cData.selectedOn = false;
colorData(cData);
jQuery(el).data('maphilight', cData).trigger('alwaysOn.maphilight');
}
});
}
var numOfChoices = maxChoices;
if(numOfChoices > 0) {
var countChoices = 0;
jQuery("area", "map[name='" + containerId + "_map']").each(function(index, el) {
var cData = jQuery(el).data('maphilight') || {};
if(cData.selectedOn) {
countChoices++;
}
});
if(countChoices >= numOfChoices) {
return false;
}
}
}
if(typeof data.selectedOn === "undefined") {
data.selectedOn = true;
} else {
data.selectedOn = !data.selectedOn;
}
colorData(data);
areaEl.data('maphilight', data).trigger('alwaysOn.maphilight');
var divContainer = jQuery('#' + containerId);
divContainer.find("input[type='hidden']").remove();
jQuery("area", "map[name='" + containerId + "_map']").each(function(index, el) {
var cAreaEl = jQuery(el);
var cData = cAreaEl.data('maphilight') || {};
if(cData.selectedOn) {
var inputElement = jQuery('<input type="hidden"/>')
.attr('name', 'qtiworks_response_' + responseIdentifier)
.attr('value', cAreaEl.data('qti-id'));
divContainer.append(inputElement);
}
});
};
/*
* Color the data based on the selectedOn flag
*/
function colorData(data) {
if(data.selectedOn) {
data.fillColor = '0000ff';
data.fillOpacity = 0.5;
data.strokeColor = '0000ff';
data.strokeOpacity = 1;
data.shadow = true;
data.shadowX = 0;
data.shadowY = 0;
data.shadowRadius = 7;
data.shadowColor = '000000';
data.shadowOpacity = 0.8;
data.shadowPosition = 'outside';
} else {
var divContainer = jQuery('#' + containerId);
divContainer.find("input[type='hidden']").remove();
jQuery("area", "map[name='" + containerId + "_map']").each(function(index, el) {
var cAreaEl = jQuery(el);
var cData = cAreaEl.data('maphilight') || {};
if(cData.selectedOn) {
var inputElement = jQuery('<input type="hidden"/>')
.attr('name', 'qtiworks_response_' + responseIdentifier)
.attr('value', cAreaEl.data('qti-id'));
divContainer.append(inputElement);
}
});
};
/*
* Color the data based on the selectedOn flag
*/
function colorData(data) {
if(data.selectedOn) {
data.fillColor = '0000ff';
data.fillOpacity = 0.5;
data.strokeColor = '0000ff';
data.strokeOpacity = 1;
data.shadow = true;
data.shadowX = 0;
data.shadowY = 0;
data.shadowRadius = 7;
data.shadowColor = '000000';
data.shadowOpacity = 0.8;
data.shadowPosition = 'outside';
} else {
data.fillColor = 'bbbbbb';
data.fillOpacity = 0.5;
data.strokeColor = '666666';
data.strokeOpacity = 0.8;
data.shadow = false;
}
}
data.fillOpacity = 0.5;
data.strokeColor = '666666';
data.strokeOpacity = 0.8;
data.shadow = false;
}
}
}( jQuery ));
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment