From 94576c157dd68b007db072b96f8c02f0f6186f1e Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Tue, 11 Jul 2017 08:47:48 +0200 Subject: [PATCH] OO-2865: show the full answers if we cannot extract a summary of it --- .../qti21/ui/components/FlowComponent.java | 101 ++++++++++++++++++ .../ui/components/FlowComponentRenderer.java | 56 ++++++++++ .../ims/qti21/ui/components/FlowFormItem.java | 68 ++++++++++++ .../AssessmentItemEditorController.java | 10 +- .../interactions/ChoiceScoreController.java | 42 +++++++- .../interactions/MatchScoreController.java | 30 +++++- .../interactions/_content/choices_score.html | 6 +- .../interactions/_content/match_score.html | 2 +- 8 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/olat/ims/qti21/ui/components/FlowComponent.java create mode 100644 src/main/java/org/olat/ims/qti21/ui/components/FlowComponentRenderer.java create mode 100644 src/main/java/org/olat/ims/qti21/ui/components/FlowFormItem.java diff --git a/src/main/java/org/olat/ims/qti21/ui/components/FlowComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/FlowComponent.java new file mode 100644 index 00000000000..7559eae7154 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/components/FlowComponent.java @@ -0,0 +1,101 @@ +package org.olat.ims.qti21.ui.components; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; +import java.util.List; + +import uk.ac.ed.ph.jqtiplus.node.content.basic.FlowStatic; +import uk.ac.ed.ph.jqtiplus.node.content.basic.InlineStatic; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; +import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; + +/** + * + * Initial date: 10 juil. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class FlowComponent extends AssessmentObjectComponent { + + private final static FlowComponentRenderer RENDERER = new FlowComponentRenderer(); + + private ResolvedAssessmentTest resolvedAssessmentTest; + private final File assessmentItemFile; + + private final FlowFormItem qtiItem; + private List<FlowStatic> flowStatics; + private List<InlineStatic> inlineStatics; + + public FlowComponent(String name, File assessmentItemFile, FlowFormItem qtiItem) { + super(name); + this.qtiItem = qtiItem; + this.assessmentItemFile = assessmentItemFile; + setDomReplacementWrapperRequired(false); + } + + public List<FlowStatic> getFlowStatics() { + return flowStatics; + } + + public void setFlowStatics(List<FlowStatic> flowStatics) { + this.flowStatics = flowStatics; + } + + public List<InlineStatic> getInlineStatics() { + return inlineStatics; + } + + public void setInlineStatics(List<InlineStatic> inlineStatics) { + this.inlineStatics = inlineStatics; + } + + public ResolvedAssessmentTest getResolvedAssessmentTest() { + return resolvedAssessmentTest; + } + + public void setResolvedAssessmentTest(ResolvedAssessmentTest resolvedAssessmentTest) { + this.resolvedAssessmentTest = resolvedAssessmentTest; + } + + @Override + public FlowFormItem getQtiItem() { + return qtiItem; + } + + @Override + public String getResponseUniqueIdentifier(ItemSessionState itemSessionState, Interaction interaction) { + return null; + } + + @Override + public Interaction getInteractionOfResponseUniqueIdentifier(String responseUniqueId) { + return null; + } + + @Override + public String relativePathTo(ResolvedAssessmentItem rAssessmentItem) { + + String relativePathString = ""; + if(resolvedAssessmentTest != null) { + URI testUri = resolvedAssessmentTest.getTestLookup().getSystemId(); + File testFile = new File(testUri); + Path relativePath = testFile.toPath().getParent().relativize(assessmentItemFile.toPath().getParent()); + relativePathString = relativePath.toString(); + } + + if(relativePathString.isEmpty()) { + return relativePathString; + } else if(relativePathString.endsWith("/")) { + return relativePathString; + } + return relativePathString.concat("/"); + } + + @Override + public FlowComponentRenderer getHTMLRendererSingleton() { + return RENDERER; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/components/FlowComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/FlowComponentRenderer.java new file mode 100644 index 00000000000..c7faaa0dd26 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/components/FlowComponentRenderer.java @@ -0,0 +1,56 @@ +package org.olat.ims.qti21.ui.components; + +import org.olat.core.gui.components.Component; +import org.olat.core.gui.render.RenderResult; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.render.URLBuilder; +import org.olat.core.gui.translator.Translator; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; + +import uk.ac.ed.ph.jqtiplus.node.content.variable.PrintedVariable; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; +import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; + +/** + * + * Initial date: 10 juil. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class FlowComponentRenderer extends AssessmentObjectComponentRenderer { + + private static final OLog log = Tracing.createLoggerFor(FlowComponentRenderer.class); + + @Override + public void render(Renderer renderer, StringOutput target, Component source, URLBuilder ubu, Translator translator, + RenderResult renderResult, String[] args) { + FlowComponent avc = (FlowComponent)source; + AssessmentRenderer aRenderer = new AssessmentRenderer(renderer); + if(avc.getFlowStatics() != null) { + try { + avc.getFlowStatics().forEach((flow) + -> avc.getHTMLRendererSingleton().renderFlow(aRenderer, target, avc, (ResolvedAssessmentItem)null, (ItemSessionState)null, flow, ubu, translator)); + } catch (Exception e) { + log.error("", e); + } + } + if(avc.getInlineStatics() != null) { + try { + avc.getInlineStatics().forEach((inline) + -> avc.getHTMLRendererSingleton().renderInline(aRenderer, target, avc, (ResolvedAssessmentItem)null, (ItemSessionState)null, inline, ubu, translator)); + } catch (Exception e) { + log.error("", e); + } + } + } + + @Override + protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, + AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, + ItemSessionState itemSessionState, PrintedVariable printedVar) { + //do nothing + } + +} diff --git a/src/main/java/org/olat/ims/qti21/ui/components/FlowFormItem.java b/src/main/java/org/olat/ims/qti21/ui/components/FlowFormItem.java new file mode 100644 index 00000000000..74c376b280a --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/components/FlowFormItem.java @@ -0,0 +1,68 @@ +package org.olat.ims.qti21.ui.components; + +import java.io.File; +import java.util.List; + +import org.olat.core.gui.UserRequest; + +import uk.ac.ed.ph.jqtiplus.node.content.basic.FlowStatic; +import uk.ac.ed.ph.jqtiplus.node.content.basic.InlineStatic; + +/** + * + * Initial date: 10 juil. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class FlowFormItem extends AssessmentObjectFormItem { + + private final FlowComponent component; + + public FlowFormItem(String name, File assessmentItemFile) { + super(name, null); + component = new FlowComponent(name, assessmentItemFile, this); + } + + public List<FlowStatic> getFlowStatics() { + return component.getFlowStatics(); + } + + public void setFlowStatics(List<FlowStatic> flowStatics) { + component.setFlowStatics(flowStatics); + } + + public List<InlineStatic> getInlineStatics() { + return component.getInlineStatics(); + } + + public void setInlineStatics(List<InlineStatic> inlineStatics) { + component.setInlineStatics(inlineStatics); + } + + @Override + public FlowComponent getComponent() { + return component; + } + + @Override + protected FlowComponent getFormItemComponent() { + return component; + } + + @Override + protected void rootFormAvailable() { + // + } + + @Override + public void evalFormRequest(UserRequest ureq) { + // + } + + @Override + public void reset() { + // + } + + +} diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java index 05d5eae1279..a028278f3f7 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java @@ -235,7 +235,7 @@ public class AssessmentItemEditorController extends BasicController { itemEditor = new SingleChoiceEditorController(ureq, getWindowControl(), scItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); listenTo(itemEditor); - scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), scItemBuilder, itemRef, restrictedEdit, + scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), scItemBuilder, itemRef, itemFile, restrictedEdit, "Test editor QTI 2.1 in detail#details_testeditor_score"); listenTo(scoreEditor); feedbackEditor = new FeedbackEditorController(ureq, getWindowControl(), scItemBuilder, @@ -253,7 +253,7 @@ public class AssessmentItemEditorController extends BasicController { itemEditor = new MultipleChoiceEditorController(ureq, getWindowControl(), mcItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); listenTo(itemEditor); - scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), mcItemBuilder, itemRef, restrictedEdit, + scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), mcItemBuilder, itemRef, itemFile, restrictedEdit, "Test editor QTI 2.1 in detail#details_testeditor_score"); listenTo(scoreEditor); feedbackEditor = new FeedbackEditorController(ureq, getWindowControl(), mcItemBuilder, @@ -289,7 +289,7 @@ public class AssessmentItemEditorController extends BasicController { itemEditor = new MatchEditorController(ureq, getWindowControl(), matchItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); listenTo(itemEditor); - scoreEditor = new MatchScoreController(ureq, getWindowControl(), matchItemBuilder, itemRef, restrictedEdit); + scoreEditor = new MatchScoreController(ureq, getWindowControl(), matchItemBuilder, itemRef, itemFile, restrictedEdit); listenTo(scoreEditor); feedbackEditor = new FeedbackEditorController(ureq, getWindowControl(), matchItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); @@ -306,7 +306,7 @@ public class AssessmentItemEditorController extends BasicController { itemEditor = new MatchEditorController(ureq, getWindowControl(), matchItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); listenTo(itemEditor); - scoreEditor = new MatchScoreController(ureq, getWindowControl(), matchItemBuilder, itemRef, restrictedEdit); + scoreEditor = new MatchScoreController(ureq, getWindowControl(), matchItemBuilder, itemRef, itemFile, restrictedEdit); listenTo(scoreEditor); feedbackEditor = new FeedbackEditorController(ureq, getWindowControl(), matchItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); @@ -411,7 +411,7 @@ public class AssessmentItemEditorController extends BasicController { itemEditor = new HottextEditorController(ureq, getWindowControl(), hottextItemBuilder, rootDirectory, rootContainer, itemFile, restrictedEdit); listenTo(itemEditor); - scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), hottextItemBuilder, itemRef, restrictedEdit, + scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), hottextItemBuilder, itemRef, itemFile, restrictedEdit, "Test editor QTI 2.1 in detail#details_testeditor_score"); listenTo(scoreEditor); feedbackEditor = new FeedbackEditorController(ureq, getWindowControl(), hottextItemBuilder, diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java index ac1f8b799c9..00f33d343ed 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java @@ -19,6 +19,8 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; +import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -32,7 +34,9 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.filter.FilterFactory; import org.olat.ims.qti21.model.xml.AssessmentHtmlBuilder; @@ -40,6 +44,8 @@ import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; import org.olat.ims.qti21.model.xml.ScoreBuilder; import org.olat.ims.qti21.model.xml.interactions.ChoiceAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.SimpleChoiceAssessmentItemBuilder.ScoreEvaluation; +import org.olat.ims.qti21.ui.ResourcesMapper; +import org.olat.ims.qti21.ui.components.FlowFormItem; import org.olat.ims.qti21.ui.editor.AssessmentTestEditorController; import org.olat.ims.qti21.ui.editor.SyncAssessmentItem; import org.olat.ims.qti21.ui.editor.events.AssessmentItemEvent; @@ -68,18 +74,27 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp private FormLayoutContainer scoreCont; private final List<ChoiceWrapper> wrappers = new ArrayList<>(); + private int count = 0; + private final File itemFileRef; + private final String mapperUri; private final ChoiceAssessmentItemBuilder itemBuilder; private int counter = 0; private final String contextHelpUrl; public ChoiceScoreController(UserRequest ureq, WindowControl wControl, - ChoiceAssessmentItemBuilder itemBuilder, AssessmentItemRef itemRef, boolean restrictedEdit, - String contextHelpUrl) { + ChoiceAssessmentItemBuilder itemBuilder, AssessmentItemRef itemRef, File itemFileRef, + boolean restrictedEdit, String contextHelpUrl) { super(ureq, wControl, itemRef, restrictedEdit); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); this.itemBuilder = itemBuilder; + this.itemFileRef = itemFileRef; this.contextHelpUrl = contextHelpUrl; + + URI assessmentObjectUri = itemFileRef.toURI(); + mapperUri = registerCacheableMapper(null, "ChoiceScoreController::" + CodeHelper.getRAMUniqueID(), + new ResourcesMapper(assessmentObjectUri)); + initForm(ureq); } @@ -233,6 +248,7 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp private String summary; private Choice choice; private final TextElement pointsEl; + private FlowFormItem summaryEl; public ChoiceWrapper(Choice choice, TextElement pointsEl) { setChoice(choice); @@ -248,6 +264,10 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp return summary; } + public FlowFormItem getSummaryEl() { + return summaryEl; + } + public TextElement getPointsEl() { return pointsEl; } @@ -259,15 +279,29 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp public void setChoice(Choice choice) { this.choice = choice; if(choice instanceof SimpleChoice) { - String answer = new AssessmentHtmlBuilder().flowStaticString(((SimpleChoice)choice).getFlowStatics()); + SimpleChoice simpleChoice = (SimpleChoice)choice; + String answer = new AssessmentHtmlBuilder().flowStaticString(simpleChoice.getFlowStatics()); answer = FilterFactory.getHtmlTagAndDescapingFilter().filter(answer); answer = answer.trim(); summary = Formatter.truncate(answer, 128); + if(!StringHelper.containsNonWhitespace(summary)) { + summaryEl = new FlowFormItem("summary" + count++, itemFileRef); + summaryEl.setFlowStatics(simpleChoice.getFlowStatics()); + summaryEl.setMapperUri(mapperUri); + scoreCont.add(summaryEl); + } } else if(choice instanceof Hottext) { - String answer = new AssessmentHtmlBuilder().inlineStaticString(((Hottext)choice).getInlineStatics()); + Hottext hottext = (Hottext)choice; + String answer = new AssessmentHtmlBuilder().inlineStaticString(hottext.getInlineStatics()); answer = FilterFactory.getHtmlTagAndDescapingFilter().filter(answer); answer = answer.trim(); summary = Formatter.truncate(answer, 128); + if(!StringHelper.containsNonWhitespace(summary)) { + summaryEl = new FlowFormItem("summary" + count++, itemFileRef); + summaryEl.setInlineStatics(hottext.getInlineStatics()); + summaryEl.setMapperUri(mapperUri); + scoreCont.add(summaryEl); + } } else { summary = ""; } diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java index 41645f6dc62..260799f1655 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java @@ -19,6 +19,8 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -34,6 +36,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.CodeHelper; import org.olat.core.util.Util; import org.olat.course.assessment.AssessmentHelper; import org.olat.ims.qti21.model.xml.AssessmentHtmlBuilder; @@ -41,6 +44,8 @@ import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; import org.olat.ims.qti21.model.xml.ScoreBuilder; import org.olat.ims.qti21.model.xml.interactions.MatchAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.SimpleChoiceAssessmentItemBuilder.ScoreEvaluation; +import org.olat.ims.qti21.ui.ResourcesMapper; +import org.olat.ims.qti21.ui.components.FlowFormItem; import org.olat.ims.qti21.ui.editor.AssessmentTestEditorController; import org.olat.ims.qti21.ui.editor.SyncAssessmentItem; import org.olat.ims.qti21.ui.editor.events.AssessmentItemEvent; @@ -70,15 +75,24 @@ public class MatchScoreController extends AssessmentItemRefEditorController impl private MatchAssessmentItemBuilder itemBuilder; + private int count = 0; + private final String mapperUri; + private final File itemFileRef; private List<MatchWrapper> sourceWrappers = new ArrayList<>(); private List<MatchWrapper> targetWrappers = new ArrayList<>(); private Map<DirectedPairValue, MatchScoreWrapper> scoreWrappers = new HashMap<>(); public MatchScoreController(UserRequest ureq, WindowControl wControl, MatchAssessmentItemBuilder itemBuilder, - AssessmentItemRef itemRef, boolean restrictedEdit) { + AssessmentItemRef itemRef, File itemFileRef, boolean restrictedEdit) { super(ureq, wControl, itemRef, restrictedEdit); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); this.itemBuilder = itemBuilder; + this.itemFileRef = itemFileRef; + + URI assessmentObjectUri = itemFileRef.toURI(); + mapperUri = registerCacheableMapper(null, "MatchScoreController::" + CodeHelper.getRAMUniqueID(), + new ResourcesMapper(assessmentObjectUri)); + initForm(ureq); } @@ -250,22 +264,32 @@ public class MatchScoreController extends AssessmentItemRefEditorController impl } private MatchWrapper createMatchWrapper(SimpleAssociableChoice choice) { - return new MatchWrapper(choice.getIdentifier(), choice); + FlowFormItem summaryEl = new FlowFormItem("summary_" + count++, itemFileRef); + summaryEl.setFlowStatics(choice.getFlowStatics()); + summaryEl.setMapperUri(mapperUri); + scoreCont.add(summaryEl); + return new MatchWrapper(choice.getIdentifier(), choice, summaryEl); } public static class MatchWrapper { private final Identifier choiceIdentifier; private SimpleAssociableChoice choice; + private final FlowFormItem summaryEl; - public MatchWrapper(Identifier choiceIdentifier, SimpleAssociableChoice choice) { + public MatchWrapper(Identifier choiceIdentifier, SimpleAssociableChoice choice, FlowFormItem summaryEl) { this.choiceIdentifier = choiceIdentifier; this.choice = choice; + this.summaryEl = summaryEl; } public String getSummary() { return new AssessmentHtmlBuilder().flowStaticString(choice.getFlowStatics()); } + + public FlowFormItem getSummaryEl() { + return summaryEl; + } public Identifier getChoiceIdentifier() { return choiceIdentifier; diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/choices_score.html b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/choices_score.html index ecf08603563..8adeebdc235 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/choices_score.html +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/choices_score.html @@ -8,7 +8,11 @@ #foreach($choice in $choices) <tr> <td>#if(${choice.isCorrect()}) <i class="o_icon o_icon-lg o_icon_check_on"> </i> #end</td> - <td>$choice.summary</td> + <td>#if($r.isNotNull($choice.summaryEl)) + $r.render($choice.summaryEl) + #else + $choice.summary + #end</td> <td>$r.render($choice.getPointsEl().getComponent().getComponentName()) #if($f.hasError($choice.getPointsEl().getComponent().getComponentName())) <div>$r.render("${choice.getPointsEl().getComponent().getComponentName()}_ERROR")</div> diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html index 03cbbdb6b0d..060ff2ad87f 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_score.html @@ -3,7 +3,7 @@ <tr> <th></th> #foreach($targetChoice in $targetChoices) - <th>${targetChoice.getSummary()} + <th>${r.render($targetChoice.getSummaryEl())} #end </tr> </thead> -- GitLab