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

OO-2732: implement a matchInteraction with drag and drop (fallback to click...

OO-2732: implement a matchInteraction with drag and drop (fallback to click possible) with slightly enhanced editor and word export
parent 212b898b
No related branches found
No related tags found
No related merge requests found
Showing
with 675 additions and 33 deletions
......@@ -121,6 +121,7 @@ This produce uses software based on the MIT License
* SCORM player project: reload (MIT License) [http://www.reload.ac.uk]
* jQuery JavaScript DOM library (MIT License) [http://jquery.com]
* jQuery-UI JavaScript UI library (MIT License) [http://jqueryui.com]
* jQuery ui touch punch JavaScript UI library (MIT License) [http://touchpunch.furf.com]
* jQuery transit library (MIT License) [http://ricostacruz.com/jquery.transit]
* slf4j (MIT License) [http://www.slf4j.org]
* fullcalendar (MIT License) [http://arshaw.com/fullcalendar/]
......
......@@ -219,6 +219,25 @@
<include>${basedir}/target/bootstrap/bootstrap-openolat.min.js</include>
</includes>
</aggregation>
<aggregation>
<output>${basedir}/src/main/webapp/static/js/jquery/qti/jquery.qti.min.js</output>
<removeIncluded>false</removeIncluded>
<includes>
<include>${basedir}/target/jquery/jquery/qti/jquery.associate.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.gapMatch.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.graphicAssociate.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.graphicGap.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.graphicOrder.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.hotspot.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.match.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.order.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.positionObject.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.qtiAutosave.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.qtiTimer.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.selectPoint.min.js</include>
<include>${basedir}/target/jquery/jquery/qti/jquery.slider.min.js</include>
</includes>
</aggregation>
<aggregation>
<output>${basedir}/src/main/webapp/static/js/tinymce4/tinymce/plugins/olatmatheditor/plugin.min.js</output>
<removeIncluded>false</removeIncluded>
......@@ -274,6 +293,45 @@
</excludes>
</configuration>
</execution>
<execution>
<id>compressqti</id>
<phase>process-resources</phase>
<goals>
<goal>compress</goal>
</goals>
<configuration>
<suffix>.min</suffix>
<force>true</force>
<encoding>UTF-8</encoding>
<nocompress>false</nocompress>
<outputDirectory>${basedir}/target/bootstrap</outputDirectory>
<sourceDirectory>${basedir}/src/main/webapp/static/bootstrap</sourceDirectory>
<excludeResources>true</excludeResources>
<aggregations>
<aggregation>
<output>${basedir}/target/bootstrap/bootstrap-openolat.min.js</output>
<removeIncluded>false</removeIncluded>
<includes>
<!-- <include>${basedir}/target/bootstrap/javascripts/bootstrap/affix.min.js</include> -->
<include>${basedir}/target/bootstrap/javascripts/bootstrap/alert.min.js</include>
<include>${basedir}/target/bootstrap/javascripts/bootstrap/button.min.js</include>
<!-- <include>${basedir}/target/bootstrap/javascripts/bootstrap/carousel.min.js</include> -->
<include>${basedir}/target/bootstrap/javascripts/bootstrap/collapse.min.js</include>
<include>${basedir}/target/bootstrap/javascripts/bootstrap/dropdown.min.js</include>
<include>${basedir}/target/bootstrap/javascripts/bootstrap/tab.min.js</include>
<include>${basedir}/target/bootstrap/javascripts/bootstrap/transition.min.js</include>
<!-- <include>${basedir}/target/bootstrap/javascripts/bootstrap/scrollspy.min.js</include> -->
<include>${basedir}/target/bootstrap/javascripts/bootstrap/modal.min.js</include>
<include>${basedir}/target/bootstrap/javascripts/bootstrap/tooltip.min.js</include>
<include>${basedir}/target/bootstrap/javascripts/bootstrap/popover.min.js</include>
</includes>
</aggregation>
</aggregations>
<excludes>
<exclude>**/sliderpips/jquery-ui-slider-pips.js</exclude>
</excludes>
</configuration>
</execution>
<execution>
<id>compresscss</id>
<phase>process-resources</phase>
......
......@@ -66,6 +66,8 @@ public class HTMLToOpenXMLHandler extends DefaultHandler {
protected ListParagraph currentListParagraph;
protected boolean pNeedNewParagraph = true;
protected double maxWidthCm = OpenXMLConstants.PAGE_FULL_WIDTH_CM;
public HTMLToOpenXMLHandler(OpenXMLDocument document) {
this.factory = document;
}
......@@ -87,6 +89,10 @@ public class HTMLToOpenXMLHandler extends DefaultHandler {
this.startSpacing = spacing;
}
public void setMaxWidthCm(double width) {
this.maxWidthCm = width;
}
public void setInitialParagraph(Element paragraph) {
this.currentParagraph = paragraph;
}
......@@ -327,7 +333,7 @@ public class HTMLToOpenXMLHandler extends DefaultHandler {
}
protected void setImage(String path) {
Element imgEl = factory.createImageEl(path);
Element imgEl = factory.createImageEl(path, maxWidthCm);
if(imgEl != null) {
PredefinedStyle style = getCurrentPredefinedStyle();
Element runEl = factory.createRunEl(Collections.singletonList(imgEl), style);
......@@ -337,7 +343,7 @@ public class HTMLToOpenXMLHandler extends DefaultHandler {
}
protected void setImage(File file) {
Element imgEl = factory.createImageEl(file);
Element imgEl = factory.createImageEl(file, maxWidthCm);
if(imgEl != null) {
PredefinedStyle style = getCurrentPredefinedStyle();
Element runEl = factory.createRunEl(Collections.singletonList(imgEl), style);
......@@ -355,21 +361,29 @@ public class HTMLToOpenXMLHandler extends DefaultHandler {
closeParagraph();
}
protected void startTable() {
public void startTable() {
closeParagraph();
currentTable = new Table();
}
protected void startTable(Integer... width) {
public void startTable(Integer... width) {
closeParagraph();
currentTable = new Table(width);
}
protected void startCurrentTableRow() {
public void startCurrentTableRow() {
currentTable.addRowEl();
}
protected void closeCurrentTableRow() {
public Node addCell(int colSpan, int rowSpan) {
return currentTable.addCellEl(colSpan, rowSpan);
}
public Node addCell(Element cellEl) {
return currentTable.addCellEl(cellEl, 1);
}
public void closeCurrentTableRow() {
if(currentTable != null) {
currentTable.closeRow();
}
......@@ -378,7 +392,7 @@ public class HTMLToOpenXMLHandler extends DefaultHandler {
currentParagraph = null;
}
protected void endTable() {
public void endTable() {
if(currentTable != null) {
content.add(currentTable.getTableEl());
}
......
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.util.openxml;
/**
*
* Initial date: 23 mai 2017<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class OpenXMLConstants {
public static final double PAGE_FULL_WIDTH_CM = 15.9;
public static final int TABLE_FULL_WIDTH_DXA = 11294;
public static final int TABLE_FULL_WIDTH_PCT = 4858;
}
......@@ -784,19 +784,28 @@ public class OpenXMLDocument {
<w:shd w:val="solid" w:color="E9EAF2" w:fill="auto" />
</w:tcPr>
*/
/**
* The border are the same color as the background.
*
* @param background
* @param width
* @param unit
* @return
*/
public Element createTableCell(String background, Integer width, Unit unit) {
Element cellEl = document.createElement("w:tc");
Node prefEl = null;
if(unit != null) {
prefEl = cellEl.appendChild(document.createElement("w:tcPr"));
createWidthEl("w:tcW", width, unit, cellEl);
createWidthEl("w:tcW", width, unit, prefEl);
}
if(StringHelper.containsNonWhitespace(background)) {
if(prefEl == null) {
prefEl = cellEl.appendChild(document.createElement("w:tcPr"));
}
Node borderEl = prefEl.appendChild(document.createElement("w:tcBorders"));
createBorder("w:top", background, borderEl);
createBorder("w:left", background, borderEl);
......@@ -808,6 +817,38 @@ public class OpenXMLDocument {
return cellEl;
}
public Element createTableCell(String background, Border border, Integer width, Unit unit) {
Element cellEl = document.createElement("w:tc");
Node prefEl = null;
if(unit != null) {
prefEl = cellEl.appendChild(document.createElement("w:tcPr"));
createWidthEl("w:tcW", width, unit, prefEl);
}
if(StringHelper.containsNonWhitespace(background)) {
if(prefEl == null) {
prefEl = cellEl.appendChild(document.createElement("w:tcPr"));
}
createShadow(background, prefEl);
}
if(border != null) {
if(prefEl == null) {
prefEl = cellEl.appendChild(document.createElement("w:tcPr"));
}
Node borderEl = prefEl.appendChild(document.createElement("w:tcBorders"));
createBorder("w:top", border, borderEl);
createBorder("w:left", border, borderEl);
createBorder("w:bottom", border, borderEl);
createBorder("w:right", border, borderEl);
}
return cellEl;
}
public ListParagraph createListParagraph() {
int abstractNumberingId = currentNumberingId++;
int numberingId = currentNumberingId++;
......@@ -929,6 +970,15 @@ public class OpenXMLDocument {
return borderEl;
}
private Element createBorder(String name, Border border, Node parent) {
Element borderEl = (Element)parent.appendChild(document.createElement(name));
borderEl.setAttribute("w:val", border.getVal());
borderEl.setAttribute("w:sz", Integer.toString(border.getSize()));
borderEl.setAttribute("w:space", Integer.toString(border.getSpace()));
borderEl.setAttribute("w:color", border.getColor());
return borderEl;
}
private Element createGridCol(Integer width, Node parent) {
Element colEl = (Element)parent.appendChild(document.createElement("w:gridCol"));
colEl.setAttribute("w:w", width.toString());
......@@ -1008,13 +1058,13 @@ public class OpenXMLDocument {
}
}
public Element createImageEl(String path) {
public Element createImageEl(String path, double maxWidthCm) {
if(mediaContainer == null) return null;
VFSItem media = mediaContainer.resolve(path);
if(media instanceof LocalFileImpl) {
LocalFileImpl file = (LocalFileImpl)media;
return createImageEl(file.getBasefile());
return createImageEl(file.getBasefile(), maxWidthCm);
}
return null;
}
......@@ -1077,7 +1127,11 @@ public class OpenXMLDocument {
* @return
*/
public Element createImageEl(File image) {
DocReference ref = registerImage(image);
return createImageEl(image, OpenXMLConstants.PAGE_FULL_WIDTH_CM/* cm */);
}
public Element createImageEl(File image, double widthCm) {
DocReference ref = registerImage(image, widthCm);
String id = ref.getId();
OpenXMLSize emuSize = ref.getEmuSize();
String filename = ref.getFilename();
......@@ -1169,14 +1223,14 @@ public class OpenXMLDocument {
return drawingEl;
}
private DocReference registerImage(File image) {
private DocReference registerImage(File image, double widthCm) {
DocReference ref;
if(fileToImagesMap.containsKey(image)) {
ref = fileToImagesMap.get(image);
} else {
String id = generateId();
Size size = ImageUtils.getImageSize(image);
OpenXMLSize emuSize = OpenXMLUtils.convertPixelToEMUs(size, DPI, 15.9/* cm */);
OpenXMLSize emuSize = OpenXMLUtils.convertPixelToEMUs(size, DPI, widthCm/* cm */);
String filename = getUniqueFilename(image);
ref = new DocReference(id, filename, emuSize, image);
fileToImagesMap.put(image, ref);
......@@ -1290,7 +1344,7 @@ public class OpenXMLDocument {
</w:drawing>
*/
public Element createGraphicEl(File backgroundImage, List<OpenXMLGraphic> elements) {
DocReference backgroundImageRef = registerImage(backgroundImage);
DocReference backgroundImageRef = registerImage(backgroundImage, OpenXMLConstants.PAGE_FULL_WIDTH_CM/**/);
OpenXMLSize emuSize = backgroundImageRef.getEmuSize();
Element alternateContentEl = document.createElement("mc:AlternateContent");
......
......@@ -132,5 +132,20 @@ public class QTI21Constants {
public static final Identifier CORRECT_SOLUTION_IDENTIFIER = Identifier.parseString(CORRECT_SOLUTION);
public static final String CSS_MATCH_MATRIX = "match_matrix";
public static final String CSS_MATCH_DRAG_AND_DROP = "match_dnd";
public static final String CSS_MATCH_SOURCE_TOP = "source-top";
public static final String CSS_MATCH_SOURCE_LEFT = "source-left";
public static final String CSS_MATCH_SOURCE_RIGHT = "source-right";
public static final String CSS_MATCH_SOURCE_BOTTOM = "source-bottom";
}
......@@ -48,7 +48,9 @@ import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.openxml.HTMLToOpenXMLHandler;
import org.olat.core.util.openxml.OpenXMLConstants;
import org.olat.core.util.openxml.OpenXMLDocument;
import org.olat.core.util.openxml.OpenXMLDocument.Border;
import org.olat.core.util.openxml.OpenXMLDocument.Style;
import org.olat.core.util.openxml.OpenXMLDocument.Unit;
import org.olat.core.util.openxml.OpenXMLDocumentWriter;
......@@ -663,14 +665,280 @@ public class QTI21WordExport implements MediaResource {
}
private void startMatch(MatchInteraction matchInteraction) {
List<String> cssClasses = matchInteraction.getClassAttr();
if(cssClasses.contains(QTI21Constants.CSS_MATCH_DRAG_AND_DROP)) {
if(hasClass(matchInteraction, QTI21Constants.CSS_MATCH_SOURCE_TOP)
|| hasClass(matchInteraction, QTI21Constants.CSS_MATCH_SOURCE_BOTTOM)) {
startMatchDragAndDropHorizontal(matchInteraction);
} else {
startMatchDragAndDropVertical(matchInteraction);
}
} else {
startMatchMatrix(matchInteraction);
}
}
private void startMatchDragAndDropHorizontal(MatchInteraction matchInteraction) {
SimpleMatchSet sourcesMatchSet = matchInteraction.getSimpleMatchSets().get(0);
SimpleMatchSet targetsMatchSet = matchInteraction.getSimpleMatchSets().get(1);
List<SimpleAssociableChoice> sourcesAssociableChoices = sourcesMatchSet.getSimpleAssociableChoices();
List<SimpleAssociableChoice> targetsAssociableChoices = targetsMatchSet.getSimpleAssociableChoices();
if(hasClass(matchInteraction, QTI21Constants.CSS_MATCH_SOURCE_BOTTOM)) {
appendMatchTargetsHorizontal(targetsAssociableChoices, sourcesAssociableChoices, matchInteraction);
appendMatchSourcesHorizontal(sourcesAssociableChoices);
} else {
appendMatchSourcesHorizontal(sourcesAssociableChoices);
appendMatchTargetsHorizontal(targetsAssociableChoices, sourcesAssociableChoices, matchInteraction);
}
}
private void appendMatchSourcesHorizontal(List<SimpleAssociableChoice> choices) {
String backgroundColor = "FFFFFF";
Border sourceBorder = new Border(0, 6, "E9EAF2");
startTable(OpenXMLConstants.TABLE_FULL_WIDTH_DXA);
for(SimpleAssociableChoice choice:choices) {
currentTable.addRowEl();
Element cell = factory.createTableCell(backgroundColor, sourceBorder, OpenXMLConstants.TABLE_FULL_WIDTH_PCT - 10, Unit.pct);
Node contentCell = currentTable.addCellEl(cell, 1);
String html = htmlBuilder.flowStaticString(choice.getFlowStatics());
List<Node> nodes = appendHtmlText(html, factory.createParagraphEl(), 7.5);
if(nodes.size() == 0) {
contentCell.appendChild(factory.createParagraphEl());
} else {
for(Node node:nodes) {
contentCell.appendChild(node);
}
contentCell.appendChild(factory.createParagraphEl());
}
currentTable.closeRow();
}
endTable();
}
private void appendMatchTargetsHorizontal(List<SimpleAssociableChoice> targetChoices, List<SimpleAssociableChoice> sourceChoices,
MatchInteraction matchInteraction) {
String backgroundColor = "E9EAF2";
String dropBackgroundColor = "FFFFFF";
Border targetBorder = new Border(0, 6, "E9EAF2");
Border dropBorder = new Border(0, 6, "EEEEEE");
int columnWidthPct = OpenXMLConstants.TABLE_FULL_WIDTH_PCT;
startTable(OpenXMLConstants.TABLE_FULL_WIDTH_DXA);
for(SimpleAssociableChoice choice:targetChoices) {
currentTable.addRowEl();
Node contentCell = currentTable.addCellEl(factory.createTableCell(backgroundColor, targetBorder, columnWidthPct - 10, Unit.pct), 1);
String html = htmlBuilder.flowStaticString(choice.getFlowStatics());
List<Node> nodes = appendHtmlText(html, factory.createParagraphEl(), 7.5);
for(Node node:nodes) {
contentCell.appendChild(node);
}
// add the drop panels
Element wrapEl = factory.createParagraphEl();
HTMLToOpenXMLHandler dropTable = new HTMLToOpenXMLHandler(factory, wrapEl, false);
dropTable.setMaxWidthCm(7);
dropTable.startTable(columnWidthPct - 50);
if(withResponses) {
for(SimpleAssociableChoice sourceChoice:sourceChoices) {
boolean correct = isCorrectMatchResponse(sourceChoice.getIdentifier(), choice.getIdentifier(), matchInteraction);
if(correct) {
dropTable.startCurrentTableRow();
Node answerCell = dropTable.addCell(factory.createTableCell(dropBackgroundColor, targetBorder, columnWidthPct - 10, Unit.pct));
String answerHtml = htmlBuilder.flowStaticString(sourceChoice.getFlowStatics());
List<Node> answerNodes = appendHtmlText(answerHtml, factory.createParagraphEl(), 7.5);
if(answerNodes.size() == 0) {
answerCell.appendChild(factory.createParagraphEl());
} else {
for(Node answerNode:answerNodes) {
answerCell.appendChild(answerNode);
}
}
dropTable.closeCurrentTableRow();
}
}
} else {
dropTable.startCurrentTableRow();
Node dropCell = dropTable.addCell(factory.createTableCell(dropBackgroundColor, dropBorder, columnWidthPct - 10, Unit.pct));
dropCell.appendChild(factory.createParagraphEl());
dropTable.closeCurrentTableRow();
}
dropTable.endTable();
List<Node> dropNodes = dropTable.getContent();
for(Node dropNode:dropNodes) {
contentCell.appendChild(dropNode);
}
contentCell.appendChild(factory.createParagraphEl());
currentTable.closeRow();
}
endTable();
}
/**
* The sources are in one column, the targets are in the second.
* @param matchInteraction
*/
private void startMatchDragAndDropVertical(MatchInteraction matchInteraction) {
SimpleMatchSet sourcesMatchSet = matchInteraction.getSimpleMatchSets().get(0);
SimpleMatchSet targetsMatchSet = matchInteraction.getSimpleMatchSets().get(1);
List<SimpleAssociableChoice> sourcesAssociableChoices = sourcesMatchSet.getSimpleAssociableChoices();
List<SimpleAssociableChoice> targetsAssociableChoices = targetsMatchSet.getSimpleAssociableChoices();
// calculate the width of the table () and of its columns
int tableWidthDxa = OpenXMLConstants.TABLE_FULL_WIDTH_DXA;
int tableWidthPct = OpenXMLConstants.TABLE_FULL_WIDTH_PCT;
int numOfColumns = 2;
int columnWidthDxa = tableWidthDxa / numOfColumns;
int columnWidthPct = tableWidthPct / numOfColumns;
Integer[] columnsWidth = new Integer[numOfColumns];
for(int i=numOfColumns; i-->0; ) {
columnsWidth[i] = columnWidthDxa;
}
startTable(columnsWidth);
if(hasClass(matchInteraction, QTI21Constants.CSS_MATCH_SOURCE_RIGHT)) {
currentTable.addRowEl();
Element targetsCell = currentTable.addCellEl(factory.createTableCell(null, columnWidthPct, Unit.pct), 1);
appendMatchTargetsVertical(targetsAssociableChoices, sourcesAssociableChoices, matchInteraction, columnWidthPct, targetsCell);
Element sourcesCell = currentTable.addCellEl(factory.createTableCell(null, columnWidthPct, Unit.pct), 1);
appendMatchSourcesVertical(sourcesAssociableChoices, columnWidthPct, sourcesCell);
currentTable.closeRow();
} else {
currentTable.addRowEl();
Element sourcesCell = currentTable.addCellEl(factory.createTableCell(null, columnWidthPct, Unit.pct), 1);
appendMatchSourcesVertical(sourcesAssociableChoices, columnWidthPct, sourcesCell);
Element targetsCell = currentTable.addCellEl(factory.createTableCell(null, columnWidthPct, Unit.pct), 1);
appendMatchTargetsVertical(targetsAssociableChoices, sourcesAssociableChoices, matchInteraction, columnWidthPct, targetsCell);
currentTable.closeRow();
}
endTable();
}
private boolean hasClass(Interaction interaction, String cssClass) {
List<String> cssClassses = interaction.getClassAttr();
return cssClassses != null && cssClassses.contains(cssClass);
}
private void appendMatchSourcesVertical(List<SimpleAssociableChoice> choices, int columnWidthPct, Element sourcesCell) {
Element wrapEl = factory.createParagraphEl();
sourcesCell.appendChild(wrapEl);
String backgroundColor = "FFFFFF";
Border sourceBorder = new Border(0, 6, "E9EAF2");
HTMLToOpenXMLHandler innerTable = new HTMLToOpenXMLHandler(factory, wrapEl, false);
innerTable.setMaxWidthCm(7.5);
innerTable.startTable(columnWidthPct);
for(SimpleAssociableChoice choice:choices) {
innerTable.startCurrentTableRow();
Node contentCell = innerTable.addCell(factory.createTableCell(backgroundColor, sourceBorder, columnWidthPct - 10, Unit.pct));
String html = htmlBuilder.flowStaticString(choice.getFlowStatics());
List<Node> nodes = appendHtmlText(html, factory.createParagraphEl(), 7.5);
if(nodes.size() == 0) {
contentCell.appendChild(factory.createParagraphEl());
} else {
for(Node node:nodes) {
contentCell.appendChild(node);
}
contentCell.appendChild(factory.createParagraphEl());
}
innerTable.closeCurrentTableRow();
}
innerTable.endTable();
List<Node> innerNodes = innerTable.getContent();
for(Node innerNode:innerNodes) {
sourcesCell.appendChild(innerNode);
}
sourcesCell.appendChild(factory.createParagraphEl());
}
private void appendMatchTargetsVertical(List<SimpleAssociableChoice> targetChoices, List<SimpleAssociableChoice> sourceChoices,
MatchInteraction matchInteraction, int columnWidthPct, Element sourcesCell) {
String backgroundColor = "E9EAF2";
String dropBackgroundColor = "FFFFFF";
Border targetBorder = new Border(0, 6, "E9EAF2");
Border dropBorder = new Border(0, 6, "EEEEEE");
Element wrapEl = factory.createParagraphEl();
sourcesCell.appendChild(wrapEl);
HTMLToOpenXMLHandler innerTable = new HTMLToOpenXMLHandler(factory, wrapEl, false);
innerTable.setMaxWidthCm(7.5);
innerTable.startTable(columnWidthPct);
for(SimpleAssociableChoice choice:targetChoices) {
innerTable.startCurrentTableRow();
Node contentCell = innerTable.addCell(factory.createTableCell(backgroundColor, targetBorder, columnWidthPct - 10, Unit.pct));
String html = htmlBuilder.flowStaticString(choice.getFlowStatics());
List<Node> nodes = appendHtmlText(html, factory.createParagraphEl(), 7.5);
for(Node node:nodes) {
contentCell.appendChild(node);
}
// add the drop panels
HTMLToOpenXMLHandler dropTable = new HTMLToOpenXMLHandler(factory, wrapEl, false);
dropTable.setMaxWidthCm(7);
dropTable.startTable(columnWidthPct - 50);
if(withResponses) {
for(SimpleAssociableChoice sourceChoice:sourceChoices) {
boolean correct = isCorrectMatchResponse(sourceChoice.getIdentifier(), choice.getIdentifier(), matchInteraction);
if(correct) {
dropTable.startCurrentTableRow();
Node answerCell = dropTable.addCell(factory.createTableCell(dropBackgroundColor, targetBorder, columnWidthPct - 10, Unit.pct));
String answerHtml = htmlBuilder.flowStaticString(sourceChoice.getFlowStatics());
List<Node> answerNodes = appendHtmlText(answerHtml, factory.createParagraphEl(), 7.5);
if(answerNodes.size() == 0) {
answerCell.appendChild(factory.createParagraphEl());
} else {
for(Node answerNode:answerNodes) {
answerCell.appendChild(answerNode);
}
}
dropTable.closeCurrentTableRow();
}
}
} else {
dropTable.startCurrentTableRow();
Node dropCell = dropTable.addCell(factory.createTableCell(dropBackgroundColor, dropBorder, columnWidthPct - 10, Unit.pct));
dropCell.appendChild(factory.createParagraphEl());
dropTable.closeCurrentTableRow();
}
dropTable.endTable();
List<Node> dropNodes = dropTable.getContent();
for(Node dropNode:dropNodes) {
contentCell.appendChild(dropNode);
}
contentCell.appendChild(factory.createParagraphEl());
innerTable.closeCurrentTableRow();
}
innerTable.endTable();
List<Node> innerNodes = innerTable.getContent();
for(Node innerNode:innerNodes) {
sourcesCell.appendChild(innerNode);
}
sourcesCell.appendChild(factory.createParagraphEl());
}
private void startMatchMatrix(MatchInteraction matchInteraction) {
SimpleMatchSet questionMatchSetVertical = matchInteraction.getSimpleMatchSets().get(0);
SimpleMatchSet questionMatchSetHorizontal = matchInteraction.getSimpleMatchSets().get(1);
List<SimpleAssociableChoice> horizontalAssociableChoices = questionMatchSetHorizontal.getSimpleAssociableChoices();
List<SimpleAssociableChoice> verticalAssociableChoices = questionMatchSetVertical.getSimpleAssociableChoices();
// calculate the width of the table () and of its columns
int tableWidthDxa = 11294;
int tableWidthPct = 4858;
int tableWidthDxa = OpenXMLConstants.TABLE_FULL_WIDTH_DXA;
int tableWidthPct = OpenXMLConstants.TABLE_FULL_WIDTH_PCT;
int numOfColumns = horizontalAssociableChoices.size() + 1;
int columnWidthDxa = tableWidthDxa / numOfColumns;
int columnWidthPct = tableWidthPct / numOfColumns;
......@@ -720,13 +988,19 @@ public class QTI21WordExport implements MediaResource {
}
}
public List<Node> appendHtmlText(String html, Element wrapEl) {
return appendHtmlText(html, wrapEl, OpenXMLConstants.PAGE_FULL_WIDTH_CM);
}
public List<Node> appendHtmlText(String html, Element wrapEl, double widthCm) {
if(!StringHelper.containsNonWhitespace(html)) {
return Collections.emptyList();
}
try {
SAXParser parser = new SAXParser();
HTMLToOpenXMLHandler handler = new HTMLToOpenXMLHandler(factory, wrapEl, false);
handler.setMaxWidthCm(widthCm);
parser.setContentHandler(handler);
parser.parse(new InputSource(new StringReader(html)));
return handler.getContent();
......
......@@ -50,6 +50,7 @@ public enum QTI21QuestionType {
mc(true, "mc", "o_mi_qtimc", QuestionType.MC),
kprim(true, "kprim", "o_mi_qtikprim", QuestionType.KPRIM),
match(true, "match", "o_mi_qtimatch", QuestionType.MATCH),
matchdraganddrop(true, "matchdraganddrop", "o_mi_qtimatch_draganddrop", QuestionType.MATCHDRAGANDDROP),
fib(true, "fib", "o_mi_qtifib", QuestionType.FIB),
numerical(true, "numerical", "o_mi_qtinumerical", QuestionType.NUMERICAL),
hotspot(true, "hotspot", "o_mi_qtihotspot", QuestionType.HOTSPOT),
......@@ -209,7 +210,9 @@ public enum QTI21QuestionType {
ResponseDeclaration responseDeclaration = item.getResponseDeclaration(interaction.getResponseIdentifier());
String responseIdentifier = responseDeclaration.getIdentifier().toString();
Cardinality cardinalty = responseDeclaration.getCardinality();
if(cardinalty.isMultiple()) {
if(hasClass(interaction, "match_dnd")) {
return QTI21QuestionType.matchdraganddrop;
} else if(cardinalty.isMultiple()) {
if(responseIdentifier.startsWith("KPRIM_")) {
return QTI21QuestionType.kprim;
} else {
......@@ -239,4 +242,9 @@ public enum QTI21QuestionType {
return QTI21QuestionType.unkown;
}
}
private static final boolean hasClass(Interaction interaction, String cssClass) {
List<String> cssClasses = interaction.getClassAttr();
return cssClasses != null && cssClasses.size() > 0 && cssClasses.contains(cssClass);
}
}
......@@ -34,6 +34,7 @@ import java.util.Map;
import javax.xml.transform.stream.StreamResult;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.util.StringHelper;
import org.olat.ims.qti21.QTI21Constants;
import org.olat.ims.qti21.model.QTI21QuestionType;
import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
......@@ -91,16 +92,17 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder {
private MatchInteraction matchInteraction;
private Map<Identifier, List<Identifier>> associations;
private Map<DirectedPairValue, Double> scoreMapping;
public MatchAssessmentItemBuilder(String title, QtiSerializer qtiSerializer) {
super(createAssessmentItem(title), qtiSerializer);
public MatchAssessmentItemBuilder(String title, String matchCssClass, QtiSerializer qtiSerializer) {
super(createAssessmentItem(title, matchCssClass), qtiSerializer);
}
public MatchAssessmentItemBuilder(AssessmentItem assessmentItem, QtiSerializer qtiSerializer) {
super(assessmentItem, qtiSerializer);
}
private static AssessmentItem createAssessmentItem(String title) {
private static AssessmentItem createAssessmentItem(String title, String matchCssClass) {
AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem(QTI21QuestionType.match, title);
NodeGroupList nodeGroups = assessmentItem.getNodeGroups();
......@@ -115,7 +117,11 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder {
//the single choice interaction
ItemBody itemBody = appendDefaultItemBody(assessmentItem);
appendMatchInteraction(itemBody, responseDeclarationId);
MatchInteraction interaction = appendMatchInteraction(itemBody, responseDeclarationId);
List<String> cssClasses = new ArrayList<>();
cssClasses.add(matchCssClass);
interaction.setClassAttr(cssClasses);
Map<Identifier, List<Identifier>> associations = new HashMap<>();
appendAssociationMatchResponseDeclaration(responseDeclaration, associations, 1.0);
......@@ -208,6 +214,7 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder {
qtiSerializer.serializeJqtiObject(block, new StreamResult(sb));
}
}
question = sb.toString();
}
......@@ -238,9 +245,38 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder {
@Override
public QTI21QuestionType getQuestionType() {
if(matchInteraction != null) {
List<?> cssClassses = matchInteraction.getClassAttr();
if(cssClassses != null && cssClassses.contains(QTI21Constants.CSS_MATCH_DRAG_AND_DROP)) {
return QTI21QuestionType.matchdraganddrop;
}
}
return QTI21QuestionType.match;
}
public boolean hasMatchInteractionClass(String cssClass) {
List<String> cssClassses = matchInteraction.getClassAttr();
return cssClassses != null && cssClassses.contains(cssClass);
}
public void addMatchInteractionClass(String cssClass) {
if(!StringHelper.containsNonWhitespace(cssClass)) return;
List<String> cssClassses = new ArrayList<>(matchInteraction.getClassAttr());
cssClassses.add(cssClass);
matchInteraction.setClassAttr(cssClassses);
}
public void removeMatchInteractionClass(String... cssClasses) {
if(cssClasses == null || cssClasses.length == 0 || cssClasses[0] == null) return;
List<String> cssClassses = new ArrayList<>(matchInteraction.getClassAttr());
for(String cssClass:cssClasses) {
cssClassses.remove(cssClass);
}
matchInteraction.setClassAttr(cssClassses);
}
public boolean isShuffle() {
return shuffle;
}
......
......@@ -52,6 +52,7 @@ public class QTI21AssessmentItemFactory implements QItemFactory {
case mc: return "QTI 2.1 " + trans.translate("new.mc");
case kprim: return "QTI 2.1 " + trans.translate("new.kprim");
case match: return "QTI 2.1 " + trans.translate("new.match");
case matchdraganddrop: return "QTI 2.1 " + trans.translate("new.matchdraganddrop");
case fib: return "QTI 2.1 " + trans.translate("new.fib");
case numerical: return "QTI 2.1 " + trans.translate("new.fib.numerical");
case essay: return "QTI 2.1 " + trans.translate("new.essay");
......
......@@ -283,6 +283,7 @@ public class QTI21ImportProcessor {
case mc: return qItemTypeDao.loadByType(QuestionType.MC.name());
case kprim: return qItemTypeDao.loadByType(QuestionType.KPRIM.name());
case match: return qItemTypeDao.loadByType(QuestionType.MATCH.name());
case matchdraganddrop: return qItemTypeDao.loadByType(QuestionType.MATCHDRAGANDDROP.name());
case fib: return qItemTypeDao.loadByType(QuestionType.FIB.name());
case numerical: return qItemTypeDao.loadByType(QuestionType.NUMERICAL.name());
case hotspot: return qItemTypeDao.loadByType(QuestionType.HOTSPOT.name());
......
......@@ -343,7 +343,8 @@ public class QTI21QPoolServiceProvider implements QPoolSPI {
case sc: itemBuilder = new SingleChoiceAssessmentItemBuilder(translator.translate("new.sc"), translator.translate("new.answer"), qtiService.qtiSerializer()); break;
case mc: itemBuilder = new MultipleChoiceAssessmentItemBuilder(translator.translate("new.mc"), translator.translate("new.answer"), qtiService.qtiSerializer()); break;
case kprim: itemBuilder = new KPrimAssessmentItemBuilder(translator.translate("new.kprim"), translator.translate("new.answer"), qtiService.qtiSerializer()); break;
case match: itemBuilder = new MatchAssessmentItemBuilder(translator.translate("new.match"), qtiService.qtiSerializer()); break;
case match: itemBuilder = new MatchAssessmentItemBuilder(translator.translate("new.match"), QTI21Constants.CSS_MATCH_MATRIX, qtiService.qtiSerializer()); break;
case matchdraganddrop: itemBuilder = new MatchAssessmentItemBuilder(translator.translate("new.match"), QTI21Constants.CSS_MATCH_DRAG_AND_DROP, qtiService.qtiSerializer()); break;
case fib: itemBuilder = new FIBAssessmentItemBuilder(translator.translate("new.fib"), EntryType.text, qtiService.qtiSerializer()); break;
case numerical: itemBuilder = new FIBAssessmentItemBuilder(translator.translate("new.fib.numerical"), EntryType.numerical, qtiService.qtiSerializer()); break;
case essay: itemBuilder = new EssayAssessmentItemBuilder(translator.translate("new.essay"), qtiService.qtiSerializer()); break;
......
......@@ -184,7 +184,6 @@ public abstract class AssessmentObjectComponent extends AbstractComponent implem
jsa.addRequiredStaticJsFile("js/jquery/maphilight/jquery.maphilight.js");
jsa.addRequiredStaticJsFile("js/jquery/ui/jquery-ui-1.11.4.custom.qti.min.js");
jsa.addRequiredStaticJsFile("js/jquery/openolat/jquery.paint.js");
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.associate.js");
......@@ -196,8 +195,10 @@ public abstract class AssessmentObjectComponent extends AbstractComponent implem
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.slider.js");
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.order.js");
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.match.js");
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.match_dnd.js");
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.gapMatch.js");
jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.hotspot.js");
}
@Override
......
......@@ -153,6 +153,7 @@ 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;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.MatchInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.MediaInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.OrderInteraction;
......@@ -1010,6 +1011,8 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
interactionName = interaction.getQtiClassName();
if(matchInteraction.getResponseIdentifier().toString().startsWith("KPRIM_")) {
interactionName += "_kprim";
} else if(hasClass(matchInteraction, QTI21Constants.CSS_MATCH_DRAG_AND_DROP)) {
interactionName += "_dnd";
}
break;
}
......@@ -1026,6 +1029,13 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
return velocity_root + "/" + templateName + ".html";
}
private final boolean hasClass(Interaction interaction, String cssClass) {
if(interaction == null || cssClass == null) return false;
List<String> cssClasses = interaction.getClassAttr();
return cssClasses != null && cssClasses.size() > 0 && cssClasses.contains(cssClass);
}
/*
<xsl:template match="qti:gap">
<xsl:variable name="gmi" select="ancestor::qti:gapMatchInteraction" as="element(qti:gapMatchInteraction)"/>
......
......@@ -512,6 +512,14 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor
return new SliderOptions(discrete, reverse, min, max, step);
}
public boolean hasCssClass(Interaction interaction, String cssClass) {
if(StringHelper.containsNonWhitespace(cssClass)) {
List<String> cssClasses = interaction.getClassAttr();
return cssClasses != null && cssClasses.contains(cssClass);
}
return false;
}
public boolean isVisible(Choice choice, ItemSessionState iSessionState) {
return AssessmentRenderFunctions.isVisible(choice, iSessionState);
}
......
#set($responseIdentifier = $r.responseUniqueId($interaction))
#set($responseValue = $r.getResponseValue($interaction.responseIdentifier))
#set($orderedSet1 = $r.getVisibleOrderedChoices($interaction,0))
#set($orderedSet2 = $r.getVisibleOrderedChoices($interaction,1))
<input name="qtiworks_presented_${responseIdentifier}" type="hidden" value="1"/>
<div class="$localName">
#if($interaction.getPrompt())
<div class="prompt">$r.renderPrompt($interaction.getPrompt())</div>
#end
#if($r.isInvalidResponse($interaction.responseIdentifier))
<div class="o_error badResponse">$r.translate("error.as.directed")</div>
#end
<div id="qti_container_${responseIdentifier}" class="clearfix">
#if($r.hasCssClass($interaction, "source-top"))
<ul class="list-unstyled o_match_dnd_sources source-top clearfix">
#foreach($choice in $orderedSet1)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_source" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())</li>
#end
</ul>
<ul class="list-unstyled o_match_dnd_targets target-bottom clearfix">
#foreach($choice in $orderedSet2)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_target" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())
<ul class="o_match_dnd_target_drop_zone list-unstyled clearfix" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}"></ul>
</li>
#end
</ul>
#elseif($r.hasCssClass($interaction, "source-right"))
<ul class="list-unstyled o_match_dnd_targets target-right">
#foreach($choice in $orderedSet2)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_target" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())
<ul class="o_match_dnd_target_drop_zone list-unstyled" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}"></ul>
</li>
#end
</ul>
<ul class="list-unstyled o_match_dnd_sources source-left">
#foreach($choice in $orderedSet1)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_source" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())</li>
#end
</ul>
#elseif($r.hasCssClass($interaction, "source-bottom"))
<ul class="list-unstyled o_match_dnd_targets target-top clearfix">
#foreach($choice in $orderedSet2)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_target" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())
<ul class="o_match_dnd_target_drop_zone list-unstyled clearfix" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}"></ul>
</li>
#end
</ul>
<ul class="list-unstyled o_match_dnd_sources source-bottom clearfix">
#foreach($choice in $orderedSet1)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_source" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())</li>
#end
</ul>
#else
<ul class="list-unstyled o_match_dnd_sources source-left">
#foreach($choice in $orderedSet1)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_source" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())</li>
#end
</ul>
<ul class="list-unstyled o_match_dnd_targets target-right">
#foreach($choice in $orderedSet2)
#set($choiceIdentifier = $r.toString($choice.identifier))
<li class="o_match_dnd_target" data-qti-match-max="${choice.matchMax}">$r.renderFlowStatics($choice.getFlowStatics())
<ul class="o_match_dnd_target_drop_zone list-unstyled" data-qti-id="${choiceIdentifier}" data-qti-match-max="${choice.matchMax}"></ul>
</li>
#end
</ul>
#end
</div>
<script type='text/javascript'>
jQuery(function() {
jQuery('#qti_container_${responseIdentifier}').matchInteractionDnd({
responseIdentifier: '$responseIdentifier',
formDispatchFieldId: '$r.formDispatchFieldId',
maxAssociations: $interaction.maxAssociations,
responseValue: '$r.toString($responseValue,",")',
opened: $r.isItemSessionOpen()
});
});
</script>
</div>
......@@ -207,6 +207,7 @@ public class AssessmentItemEditorController extends BasicController {
case numerical: itemBuilder = initFIBEditors(ureq, type, item); break;
case kprim: itemBuilder = initKPrimChoiceEditors(ureq, item); break;
case match: itemBuilder = initMatchChoiceEditors(ureq, item); break;
case matchdraganddrop: itemBuilder = initMatchDragAndDropEditors(ureq, item); break;
case hotspot: itemBuilder = initHotspotEditors(ureq, item); break;
case essay: itemBuilder = initEssayEditors(ureq, item); break;
case upload: itemBuilder = initUploadEditors(ureq, item); break;
......@@ -300,6 +301,23 @@ public class AssessmentItemEditorController extends BasicController {
return matchItemBuilder;
}
private AssessmentItemBuilder initMatchDragAndDropEditors(UserRequest ureq, AssessmentItem item) {
MatchAssessmentItemBuilder matchItemBuilder = new MatchAssessmentItemBuilder(item, qtiService.qtiSerializer());
itemEditor = new MatchEditorController(ureq, getWindowControl(), matchItemBuilder,
rootDirectory, rootContainer, itemFile, restrictedEdit);
listenTo(itemEditor);
scoreEditor = new MatchScoreController(ureq, getWindowControl(), matchItemBuilder, itemRef, restrictedEdit);
listenTo(scoreEditor);
feedbackEditor = new FeedbackEditorController(ureq, getWindowControl(), matchItemBuilder,
rootDirectory, rootContainer, itemFile, restrictedEdit);
listenTo(feedbackEditor);
tabbedPane.addTab(translate("form.match"), itemEditor);
tabbedPane.addTab(translate("form.score"), scoreEditor);
tabbedPane.addTab(translate("form.feedback"), feedbackEditor);
return matchItemBuilder;
}
private AssessmentItemBuilder initFIBEditors(UserRequest ureq, QTI21QuestionType preferedType, AssessmentItem item) {
FIBAssessmentItemBuilder fibItemBuilder = new FIBAssessmentItemBuilder(item, qtiService.qtiSerializer());
itemEditor = new FIBEditorController(ureq, getWindowControl(), preferedType, fibItemBuilder,
......
......@@ -149,8 +149,10 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
private MenuTree menuTree;
private Dropdown exportItemTools, addItemTools, changeItemTools;
private Link newTestPartLink, newSectionLink, newSingleChoiceLink, newMultipleChoiceLink, newKPrimLink, newMatchLink,
newFIBLink, newNumericalLink, newHotspotLink, newHottextLink, newEssayLink, newUploadLink, newDrawingLink;
private Link newTestPartLink, newSectionLink, newSingleChoiceLink, newMultipleChoiceLink,
newKPrimLink, newMatchLink, newMatchDragAndDropLink,
newFIBLink, newNumericalLink, newHotspotLink, newHottextLink,
newEssayLink, newUploadLink, newDrawingLink;
private Link importFromPoolLink, importFromTableLink, exportToPoolLink, exportToDocxLink;
private Link reloadInCacheLink, deleteLink, copyLink;
private final TooledStackedPanel toolbar;
......@@ -260,6 +262,9 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
newMatchLink = LinkFactory.createToolLink("new.match", translate("new.match"), this, "o_mi_qtimatch");
newMatchLink.setDomReplacementWrapperRequired(false);
addItemTools.addComponent(newMatchLink);
newMatchDragAndDropLink = LinkFactory.createToolLink("new.matchdraganddrop", translate("new.matchdraganddrop"), this, "o_mi_qtimatch_draganddrop");
newMatchDragAndDropLink.setDomReplacementWrapperRequired(false);
addItemTools.addComponent(newMatchDragAndDropLink);
newFIBLink = LinkFactory.createToolLink("new.fib", translate("new.fib"), this, "o_mi_qtifib");
newFIBLink.setDomReplacementWrapperRequired(false);
......@@ -469,7 +474,9 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
} else if(newKPrimLink == source) {
doNewAssessmentItem(ureq, menuTree.getSelectedNode(), new KPrimAssessmentItemBuilder(translate("new.kprim"), translate("new.answer"), qtiService.qtiSerializer()));
} else if(newMatchLink == source) {
doNewAssessmentItem(ureq, menuTree.getSelectedNode(), new MatchAssessmentItemBuilder(translate("new.match"), qtiService.qtiSerializer()));
doNewAssessmentItem(ureq, menuTree.getSelectedNode(), new MatchAssessmentItemBuilder(translate("new.match"), QTI21Constants.CSS_MATCH_MATRIX, qtiService.qtiSerializer()));
} else if(newMatchDragAndDropLink == source) {
doNewAssessmentItem(ureq, menuTree.getSelectedNode(), new MatchAssessmentItemBuilder(translate("new.matchdraganddrop"), QTI21Constants.CSS_MATCH_DRAG_AND_DROP, qtiService.qtiSerializer()));
} else if(newFIBLink == source) {
doNewAssessmentItem(ureq, menuTree.getSelectedNode(), new FIBAssessmentItemBuilder(translate("new.fib"), EntryType.text, qtiService.qtiSerializer()));
} else if(newNumericalLink == source) {
......
......@@ -120,13 +120,12 @@ public class UnkownItemEditorController extends FormBasicController {
itemSessionControllerSettings.setMaxAttempts(10);
/* Create controller and wire up notification recorder */
final ItemSessionController itemSessionController = new ItemSessionController(qtiService.jqtiExtensionManager(),
final ItemSessionController sessionController = new ItemSessionController(qtiService.jqtiExtensionManager(),
itemSessionControllerSettings, itemProcessingMap, itemSessionState);
itemSessionController.addNotificationListener(new NotificationRecorder(NotificationLevel.ERROR));
itemSessionController.initialize(new Date());
return itemSessionController;
sessionController.addNotificationListener(new NotificationRecorder(NotificationLevel.ERROR));
sessionController.initialize(new Date());
return sessionController;
}
@Override
......
......@@ -80,6 +80,11 @@ form.imd.incorrect.title=Titel
form.imd.layout=$org.olat.ims.qti.editor\:form.imd.layout
form.imd.layout.horizontal=$org.olat.ims.qti.editor\:form.imd.layout.horizontal
form.imd.layout.vertical=$org.olat.ims.qti.editor\:form.imd.layout.vertical
form.imd.layout.match=Position of the drag elements
form.imd.layout.left=Left
form.imd.layout.top=Top
form.imd.layout.right=Right
form.imd.layout.bottom=Bottom
form.imd.limittries=$org.olat.ims.qti.editor\:form.imd.limittries
form.imd.match.single.multiple=Typ
form.imd.match.single.choice=Single choice
......@@ -144,6 +149,7 @@ new.hottext.start=Das ist ein
new.hottext.text=Hottext
new.kprim=Kprim
new.match=Matrix
new.matchdraganddrop=Drag and Drop
new.mc=Multiple Choice
new.rectangle=Viereck
new.sc=Single Choice
......
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