/**
* <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.ims.qti21.ui.components;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.impl.Form;
import org.olat.core.gui.components.form.flexible.impl.FormJSHelper;
import org.olat.core.gui.components.form.flexible.impl.NameValuePair;
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.util.StringHelper;
import org.olat.ims.qti21.AssessmentTestSession;
import org.olat.ims.qti21.model.audit.CandidateEvent;
import org.olat.ims.qti21.model.audit.CandidateTestEventType;
import org.olat.ims.qti21.ui.CandidateSessionContext;
import org.olat.ims.qti21.ui.QTIWorksAssessmentTestEvent.Event;
import uk.ac.ed.ph.jqtiplus.node.content.variable.PrintedVariable;
import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode;
import uk.ac.ed.ph.jqtiplus.node.test.TestPart;
import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
import uk.ac.ed.ph.jqtiplus.running.ItemProcessingContext;
import uk.ac.ed.ph.jqtiplus.running.ItemSessionController;
import uk.ac.ed.ph.jqtiplus.running.TestSessionController;
import uk.ac.ed.ph.jqtiplus.state.ItemSessionState;
import uk.ac.ed.ph.jqtiplus.state.TestPartSessionState;
import uk.ac.ed.ph.jqtiplus.state.TestPlanNode;
import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey;
import uk.ac.ed.ph.jqtiplus.state.TestSessionState;
/**
*
* Initial date: 01.03.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class AssessmentTreeComponentRenderer extends AssessmentObjectComponentRenderer {
@Override
public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu,
Translator translator, RenderResult renderResult, String[] args) {
AssessmentTreeComponent component = (AssessmentTreeComponent)source;
TestSessionController testSessionController = component.getTestSessionController();
if(!testSessionController.getTestSessionState().isEnded()) {
CandidateSessionContext candidateSessionContext = component.getCandidateSessionContext();
final AssessmentTestSession candidateSession = candidateSessionContext.getCandidateSession();
if (!candidateSession.isExploded() && !candidateSessionContext.isTerminated()) {
CandidateEvent candidateEvent = candidateSessionContext.getLastEvent();
CandidateTestEventType testEventType = candidateEvent.getTestEventType();
RenderingRequest options;
if (testEventType == CandidateTestEventType.REVIEW_ITEM) {
options = RenderingRequest.getItemReview();
} else if (testEventType == CandidateTestEventType.SOLUTION_ITEM) {
options = RenderingRequest.getItemSolution();
} else {
options = RenderingRequest.getItem(testSessionController);
}
AssessmentRenderer renderHints = new AssessmentRenderer(renderer);
renderTestEvent(testSessionController, renderHints, sb, component, ubu, translator, options);
}
}
}
private void renderTestEvent(TestSessionController testSessionController, AssessmentRenderer renderer, StringOutput target,
AssessmentTreeComponent component, URLBuilder ubu, Translator translator, RenderingRequest options) {
CandidateSessionContext candidateSessionContext = component.getCandidateSessionContext();
CandidateEvent candidateEvent = candidateSessionContext.getLastEvent();
CandidateTestEventType testEventType = candidateEvent.getTestEventType();
final TestSessionState testSessionState = testSessionController.getTestSessionState();
if(!candidateSessionContext.isTerminated() && !testSessionState.isExited()) {
if (testEventType == CandidateTestEventType.REVIEW_ITEM) {
renderer.setReviewMode(true);
} else if (testEventType == CandidateTestEventType.SOLUTION_ITEM) {
renderer.setSolutionMode(true);
}
renderNavigation(renderer, target, component, ubu, translator, options);
}
}
private void renderNavigation(AssessmentRenderer renderer, StringOutput sb,
AssessmentTreeComponent component, URLBuilder ubu, Translator translator, RenderingRequest options) {
sb.append("<div id='o_qti_menu' class='qtiworks o_assessmenttest o_testpartnavigation o_qti_menu_menustyle'>");
//part, sections and item refs
TestPlanNode currentTestPartNode = component.getCurrentTestPartNode();
if(currentTestPartNode != null) {
sb.append("<ul class='o_testpartnavigation list-unstyled'>");
currentTestPartNode.getChildren().forEach((node)
-> renderNavigation(renderer, sb, component, node, ubu, translator, options));
sb.append("</ul>");
}
sb.append("</div>");
}
private void renderNavigation(AssessmentRenderer renderer, StringOutput sb, AssessmentTreeComponent component, TestPlanNode node,
URLBuilder ubu, Translator translator, RenderingRequest options) {
switch(node.getTestNodeType()) {
case ASSESSMENT_SECTION:
renderNavigationAssessmentSection(renderer, sb, component, node, ubu, translator, options);
break;
case ASSESSMENT_ITEM_REF:
renderNavigationAssessmentItem(sb, component, node, translator, options);
break;
default: break;
}
}
private void renderNavigationAssessmentSection(AssessmentRenderer renderer, StringOutput sb, AssessmentTreeComponent component, TestPlanNode sectionNode,
URLBuilder ubu, Translator translator, RenderingRequest options) {
sb.append("<li class='o_assessmentsection o_qti_menu_item'>")
.append("<header><h4>").append(StringHelper.escapeHtml(sectionNode.getSectionPartTitle())).append("</h4>");
//renderAssessmentSectionRubrickBlock(renderer, sb, component, sectionNode, ubu, translator);
sb.append("</header><ul class='o_testpartnavigation_inner list-unstyled'>");
sectionNode.getChildren().forEach((child)
-> renderNavigation(renderer, sb, component, child, ubu, translator, options));
sb.append("</ul></li>");
}
private void renderNavigationAssessmentItem(StringOutput sb, AssessmentTreeComponent component, TestPlanNode itemNode,
Translator translator, RenderingRequest options) {
// check if currently rendered item is the active item
boolean active = false;
TestSessionController sessionCtr = component.getTestSessionController();
if (sessionCtr != null && itemNode != null) {
TestSessionState sessionState = sessionCtr.getTestSessionState();
if (sessionState != null && sessionState.getCurrentItemKey() != null) {
TestPlanNodeKey testPlanNodeKey = sessionState.getCurrentItemKey();
active = (testPlanNodeKey.getIdentifier().equals(itemNode.getIdentifier()));
}
}
sb.append("<li class='o_assessmentitem").append(" active", active).append("'>");
renderAssessmentItemMark(sb, component, itemNode, translator);
renderAssessmentItemAttempts(sb, component, itemNode, translator);
renderItemStatus(sb, component, itemNode, translator, options);
renderAssessmentItemLink(sb, component, itemNode);
sb.append("</li>");
}
/**
*
* @param sb
* @param component
* @param itemNode
* @return The event used or null
*/
private Event renderAssessmentItemLink(StringOutput sb, AssessmentTreeComponent component, TestPlanNode itemNode) {
String key = itemNode.getKey().toString();
Form form = component.getQtiItem().getRootForm();
String dispatchId = component.getQtiItem().getFormDispatchId();
TestSessionController testSessionController = component.getTestSessionController();
TestSessionState testSessionState = testSessionController.getTestSessionState();
TestPart testPart = testSessionController.getCurrentTestPart();
TestPlanNodeKey currentTestPartKey = testSessionState.getCurrentTestPartKey();
TestPartSessionState currentTestPartSessionState = testSessionState.getTestPartSessionStates().get(currentTestPartKey);
Event event;
if(testPart == null || testPart.getNavigationMode() == NavigationMode.NONLINEAR) {
if(testSessionState.isEnded() || currentTestPartSessionState.isEnded()) {
if(itemNode.getEffectiveItemSessionControl().isAllowReview() || itemNode.getEffectiveItemSessionControl().isShowFeedback()) {
event = Event.reviewItem;
} else {
event = null;
}
} else {
event = Event.selectItem;
}
} else {
event = null;
}
if(event == null) {
sb.append("<span class='o_assessmentitem_nav_disabled'>");
} else {
sb.append("<a href='javascript:;' onclick=\"")
.append(FormJSHelper.getXHRFnCallFor(form, dispatchId, 1, true, true,
new NameValuePair("cid", event.name()), new NameValuePair("item", key)))
.append(";\" class='o_sel_assessmentitem'>");
}
sb.append("<span class='questionTitle'>").append(StringHelper.escapeHtml(itemNode.getSectionPartTitle())).append("</span>");
if(event == null) {
sb.append("</span>");
} else {
sb.append("</a>");
}
return event;
}
private void renderItemStatus(StringOutput sb, AssessmentTreeComponent component, TestPlanNode itemNode,
Translator translator, RenderingRequest options) {
ItemProcessingContext itemProcessingContext = component.getItemSessionState(itemNode);
ItemSessionState itemSessionState = itemProcessingContext.getItemSessionState();
renderItemStatus(sb, itemSessionState, options, translator);
}
private void renderAssessmentItemMark(StringOutput sb, AssessmentTreeComponent component, TestPlanNode itemNode, Translator translator) {
String key = itemNode.getKey().toString();
Form form = component.getQtiItem().getRootForm();
String dispatchId = component.getQtiItem().getFormDispatchId();
boolean mark = component.getCandidateSessionContext().isMarked(key);
sb.append("<a href='javascript:;' onclick=\"")
.append(FormJSHelper.getXHRFnCallFor(form, dispatchId, 1, true, true,
new NameValuePair("cid", Event.mark.name()), new NameValuePair("item", key)))
.append("; o_toggleMark(this); return false;\" class='o_assessmentitem_marks'><i class='o_icon ")
.append("o_icon_bookmark", "o_icon_bookmark_add", mark)
.append("' title='").append(StringHelper.escapeHtml(translator.translate("assessment.item.mark"))).append("'> </i></a>");
}
private void renderAssessmentItemAttempts(StringOutput sb, AssessmentTreeComponent component, TestPlanNode itemNode,
Translator translator) {
ItemProcessingContext itemProcessingContext = component.getItemSessionState(itemNode);
ItemSessionState itemSessionState = itemProcessingContext.getItemSessionState();
//attempts
int numOfAttempts = itemSessionState.getNumAttempts();
int maxAttempts = 0;
if(itemProcessingContext instanceof ItemSessionController) {
ItemSessionController itemSessionController = (ItemSessionController)itemProcessingContext;
maxAttempts = itemSessionController.getItemSessionControllerSettings().getMaxAttempts();
}
sb.append("<span class='o_assessmentitem_attempts ");
if(maxAttempts > 0) {
if (maxAttempts - numOfAttempts > 0) {
sb.append("o_assessmentitem_attempts_limited");
} else {
sb.append("o_assessmentitem_attempts_nomore");
}
String title = translator.translate("attemptsleft", new String[] { Integer.toString((maxAttempts - numOfAttempts)) });
sb.append("' title=\"").append(StringHelper.escapeHtml(title)).append("\">");
sb.append(numOfAttempts).append(" / ").append(Integer.toString(maxAttempts));
} else {
sb.append("'>").append(numOfAttempts);
}
sb.append("</span>");
}
@Override
protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb,
AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState,
PrintedVariable printedVar) {
//
}
}