/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <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>
* Copyright (c) 2009 frentix GmbH<br>
* http://www.frentix.com<br>
* <p>
*/
package org.olat.course.nodes.ms;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.DependencyRuleApplayable;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.RichTextElement;
import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
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.components.form.flexible.impl.rules.RulesFactory;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.util.StringHelper;
import org.olat.course.nodes.MSCourseNode;
import org.olat.modules.ModuleConfiguration;
/**
* Provides a FlexiForm for the assesment settings dialog, including custom
* rules for when to show which dialog element.
*
* @author twuersch
*
*/
public class MSEditFormController extends FormBasicController {
/** Configuration this controller will modify. */
private ModuleConfiguration modConfig;
/** Dropdown for choosing whether score will be awarded or not. */
private SingleSelection scoreGranted;
/** Dropdown for choosing whether pass/fail will be displayed or not. */
private SingleSelection displayPassed;
/**
* Dropdown for choosing whether pass and fail will be decided automatically
* or manually.
*/
private SingleSelection displayType;
/** Dropdown for choosing whether results will be commented individually. */
private SingleSelection commentFlag;
/** Text input element for the minimum score. */
private TextElement minVal;
/** Text input element for the maximum score. */
private TextElement maxVal;
/** Text input element for the passing score. */
private TextElement cutVal;
/** Rich text input element for a notice to all users. */
private RichTextElement infotextUser;
/** Rich text input element for a notice to all tutors. */
private RichTextElement infotextCoach;
/** The keys for true / false dropdowns. */
private String[] trueFalseKeys;
/** The keys for yes/no dropdowns. */
private String[] yesNoValues;
/** The keys for manual/automatic scoring dropdown. */
private String[] passedTypeValues;
/**
* Creates this controller.
*
* @param ureq
* @param wControl
* @param modConfig
*/
public MSEditFormController(UserRequest ureq, WindowControl wControl, ModuleConfiguration modConfig) {
super(ureq, wControl, FormBasicController.LAYOUT_DEFAULT);
this.modConfig = modConfig;
this.trueFalseKeys = new String[] { Boolean.TRUE.toString(), Boolean.FALSE.toString() };
this.yesNoValues = new String[] { translate("form.yes"), translate("form.no") };
this.passedTypeValues = new String[] { translate("form.passedtype.cutval"), translate("form.passedtype.manual") };
initForm(ureq);
}
/**
*
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#doDispose
* ()
*/
@Override
protected void doDispose() {
// Don't dispose anything
}
/**
*
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK
* (org.olat.core.gui.UserRequest)
*/
@Override
protected void formOK(UserRequest ureq) {
fireEvent(ureq, Event.DONE_EVENT);
}
/**
*
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formNOK
* (org.olat.core.gui.UserRequest)
*/
@Override
protected void formNOK(UserRequest ureq) {
fireEvent(ureq, Event.FAILED_EVENT);
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formCancelled(org.olat.core.gui.UserRequest)
*/
@Override
protected void formCancelled(UserRequest ureq) {
fireEvent(ureq, Event.CANCELLED_EVENT);
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#initForm
* (org.olat.core.gui.components.form.flexible.FormItemContainer,
* org.olat.core.gui.control.Controller, org.olat.core.gui.UserRequest)
*/
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
// Create the "score granted" field...
scoreGranted = uifactory.addDropdownSingleselect("form.score", formLayout, trueFalseKeys, yesNoValues, null);
scoreGranted.addActionListener(this, FormEvent.ONCHANGE);
Boolean sf = (Boolean) modConfig.get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD);
boolean firstTime = true;
if (sf == null) sf = Boolean.TRUE;
else firstTime = false; // when the score flag is set then this can't be a
// new MS bb
scoreGranted.select(sf.toString(), true);
// ...minimum value...
Float min = (Float) modConfig.get(MSCourseNode.CONFIG_KEY_SCORE_MIN);
if (min == null) {
min = new Float(0.0);
}
minVal = uifactory.addTextElement("form.min", "form.min", 8, min.toString(), formLayout);
minVal.setDisplaySize(5);
Float max = (Float) modConfig.get(MSCourseNode.CONFIG_KEY_SCORE_MAX);
if (max == null) {
max = new Float(0.0);
}
// ...and maximum value input.
maxVal = uifactory.addTextElement("form.max", "form.max", 8, max.toString(), formLayout);
maxVal.setDisplaySize(5);
uifactory.addSpacerElement("spacer1", formLayout, false);
// Create the "display passed / failed" dropdown...
displayPassed = uifactory.addDropdownSingleselect("form.passed", formLayout, trueFalseKeys, yesNoValues, null);
displayPassed.addActionListener(this, FormEvent.ONCHANGE);
Boolean pf = (Boolean) modConfig.get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD);
if (pf == null) pf = Boolean.TRUE;
displayPassed.select(pf.toString(), true);
// ...the automatic / manual dropdown (note that TRUE means automatic and
// FALSE means manually)...
Float cut = (Float) modConfig.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
displayType = uifactory.addDropdownSingleselect("form.passed.type", formLayout, trueFalseKeys, passedTypeValues, null);
displayType.addActionListener(this, FormEvent.ONCHANGE);
if (cut != null || firstTime) {
displayType.select(trueFalseKeys[0], true);
} else {
displayType.select(trueFalseKeys[1], true);
}
// ...and the passing grade input field.
if (cut == null) cut = new Float(0.0);
cutVal = uifactory.addTextElement("form.cut", "form.cut", 8, cut.toString(), formLayout);
cutVal.setDisplaySize(5);
uifactory.addSpacerElement("spacer2", formLayout, false);
// Create the "individual comment" dropdown.
commentFlag = uifactory.addDropdownSingleselect("form.comment", formLayout, trueFalseKeys, yesNoValues, null);
Boolean cf = (Boolean) modConfig.get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD);
if (cf == null) cf = Boolean.TRUE;
commentFlag.select(cf.toString(), true);
uifactory.addSpacerElement("spacer3", formLayout, false);
// Create the rich text fields.
String infoUser = (String) modConfig.get(MSCourseNode.CONFIG_KEY_INFOTEXT_USER);
if (infoUser == null) infoUser = new String("");
infotextUser = uifactory.addRichTextElementForStringDataMinimalistic("infotextUser", "form.infotext.user", infoUser, 10, -1, false,
formLayout, ureq.getUserSession(), getWindowControl());
String infoCoach = (String) modConfig.get(MSCourseNode.CONFIG_KEY_INFOTEXT_COACH);
if (infoCoach == null) infoCoach = new String("");
infotextCoach = uifactory.addRichTextElementForStringDataMinimalistic("infotextCoach", "form.infotext.coach", infoCoach, 10, -1, false,
formLayout, ureq.getUserSession(), getWindowControl());
/*
* The following rules ensure that if score is not granted, there is still
* the possiblity to manually assign a passing or failing grade.
*
* Since the custom rules defined in the following lines are hard to read,
* the first one is annotated:
*/
RulesFactory.createCustomRule(scoreGranted, Boolean.TRUE.toString(), new HashSet<FormItem>(), formLayout).setDependencyRuleApplayable(
new DependencyRuleApplayable() {
/*
* What the line above means is: If the dropdown field "scoreGranted"
* changes its selection key to "true", then execute the statements of
* this rule, defined in the apply(...) method below. If you look into
* the documentation, you see that the third parameter (of type
* HashSet<FormItem>) would be the targets, but this is left
* intentionally empty since we can refer the targets (like minVal,
* maxVal etc.) directly from inside the following apply(...) method.
*/
@SuppressWarnings( { "synthetic-access", "unused" })
public void apply(FormItem triggerElement, Object triggerVal, Set<FormItem> targets) {
minVal.setEnabled(true);
maxVal.setEnabled(true);
if (displayPassed.getSelectedKey().equals(Boolean.TRUE.toString())) {
displayType.setEnabled(true);
if (displayType.getSelectedKey().equals(Boolean.TRUE.toString())) {
cutVal.setEnabled(true);
} else {
cutVal.setEnabled(false);
}
}
}
});
RulesFactory.createCustomRule(scoreGranted, Boolean.FALSE.toString(), new HashSet<FormItem>(), formLayout).setDependencyRuleApplayable(
new DependencyRuleApplayable() {
@SuppressWarnings( { "synthetic-access", "unused" })
public void apply(FormItem triggerElement, Object triggerVal, Set<FormItem> targets) {
minVal.setEnabled(false);
maxVal.setEnabled(false);
if (displayPassed.getSelectedKey().equals(Boolean.TRUE.toString())) {
displayType.select(Boolean.FALSE.toString(), true);
displayType.setEnabled(false);
cutVal.setEnabled(false);
} else {
displayType.setEnabled(false);
cutVal.setEnabled(false);
}
}
});
RulesFactory.createCustomRule(displayPassed, Boolean.TRUE.toString(), new HashSet<FormItem>(), formLayout).setDependencyRuleApplayable(
new DependencyRuleApplayable() {
@SuppressWarnings( { "synthetic-access", "unused" })
public void apply(FormItem triggerElement, Object triggerVal, Set<FormItem> targets) {
if (scoreGranted.getSelectedKey().equals(Boolean.TRUE.toString())) {
displayType.setEnabled(true);
cutVal.setEnabled(true);
} else {
displayType.select(Boolean.FALSE.toString(), true);
displayType.setEnabled(false);
cutVal.setEnabled(false);
}
}
});
RulesFactory.createCustomRule(displayPassed, Boolean.FALSE.toString(), new HashSet<FormItem>(), formLayout)
.setDependencyRuleApplayable(new DependencyRuleApplayable() {
@SuppressWarnings( { "synthetic-access", "unused" })
public void apply(FormItem triggerElement, Object triggerVal, Set<FormItem> targets) {
displayType.setEnabled(false);
cutVal.setEnabled(false);
}
});
RulesFactory.createCustomRule(displayType, Boolean.TRUE.toString(), new HashSet<FormItem>(), formLayout).setDependencyRuleApplayable(
new DependencyRuleApplayable() {
@SuppressWarnings( { "synthetic-access", "unused" })
public void apply(FormItem triggerElement, Object triggerVal, Set<FormItem> targets) {
if (scoreGranted.getSelectedKey().equals(Boolean.TRUE.toString())) {
cutVal.setEnabled(true);
} else {
cutVal.setEnabled(false);
}
}
});
RulesFactory.createCustomRule(displayType, Boolean.FALSE.toString(), new HashSet<FormItem>(), formLayout).setDependencyRuleApplayable(
new DependencyRuleApplayable() {
@SuppressWarnings( { "synthetic-access", "unused" })
public void apply(FormItem triggerElement, Object triggerVal, Set<FormItem> targets) {
cutVal.setEnabled(false);
}
});
// Create submit and cancel buttons
final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator());
formLayout.add(buttonLayout);
uifactory.addFormSubmitButton("submit", buttonLayout);
uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#validateFormLogic(org.olat.core.gui.UserRequest)
*/
@Override
protected boolean validateFormLogic(UserRequest ureq) {
// coach info text
if (infotextCoach.getValue().length() > 4000) {
infotextCoach.setErrorKey("input.toolong", null);
return false;
} else {
infotextCoach.clearError();
}
// user info text
if (infotextUser.getValue().length() > 4000) {
infotextUser.setErrorKey("input.toolong", null);
return false;
} else {
infotextUser.clearError();
}
// score flag
if (scoreGranted.getSelectedKey().equals(Boolean.TRUE.toString())) {
if (!minVal.getValue().matches("^[0-9]+\\.?[0-9]*$")) {
minVal.setErrorKey("form.error.wrongFloat", null);
return false;
} else {
minVal.clearError();
}
if (!maxVal.getValue().matches("^[0-9]+\\.?[0-9]*$")) {
maxVal.setErrorKey("form.error.wrongFloat", null);
return false;
} else if (Float.parseFloat(minVal.getValue()) > Float.parseFloat(maxVal.getValue())) {
maxVal.setErrorKey("form.error.minGreaterThanMax", null);
return false;
} else {
maxVal.clearError();
}
}
// display flag
if (displayPassed.getSelectedKey().equals(Boolean.TRUE.toString()) && displayType.getSelectedKey().equals(Boolean.TRUE.toString())) {
if (Boolean.valueOf(displayType.getSelectedKey()).booleanValue() && scoreGranted.getSelectedKey().equals(Boolean.FALSE.toString())) {
displayType.setErrorKey("form.error.cutButNoScore", null);
} else {
displayType.clearError();
}
if (!cutVal.getValue().matches("^[0-9]+\\.?[0-9]*$")) {
cutVal.setErrorKey("form.error.wrongFloat", null);
return false;
} else if (Float.parseFloat(cutVal.getValue()) < Float.parseFloat(minVal.getValue())
|| Float.parseFloat(cutVal.getValue()) > Float.parseFloat(maxVal.getValue())) {
cutVal.setErrorKey("form.error.cutOutOfRange", null);
return false;
} else {
cutVal.clearError();
}
}
return true && super.validateFormLogic(ureq);
}
/**
* Sets this form to be write-protected.
*
* @param displayOnly
*/
public void setDisplayOnly(boolean displayOnly) {
Map<String, FormItem> formItems = flc.getFormComponents();
for (String formItemName : formItems.keySet()) {
formItems.get(formItemName).setVisible(true);
formItems.get(formItemName).setEnabled(!displayOnly);
}
}
/**
* @param moduleConfiguration
*/
public void updateModuleConfiguration(ModuleConfiguration moduleConfiguration) {
// mandatory score flag
Boolean sf = new Boolean(scoreGranted.getSelectedKey());
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD, sf);
if (sf.booleanValue()) {
// do min/max value
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_SCORE_MIN, new Float(minVal.getValue()));
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_SCORE_MAX, new Float(maxVal.getValue()));
} else {
// remove old config
moduleConfiguration.remove(MSCourseNode.CONFIG_KEY_SCORE_MIN);
moduleConfiguration.remove(MSCourseNode.CONFIG_KEY_SCORE_MAX);
}
// mandatory passed flag
Boolean pf = new Boolean(displayPassed.getSelectedKey());
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD, pf);
if (pf.booleanValue()) {
// do cut value
Boolean cf = new Boolean(displayType.getSelectedKey());
if (cf.booleanValue()) {
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE, new Float(cutVal.getValue()));
} else {
// remove old config
moduleConfiguration.remove(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
}
} else {
// remove old config
moduleConfiguration.remove(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
}
// mandatory comment flag
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD, new Boolean(commentFlag.getSelectedKey()));
// set info text only if something is in there
String iu = infotextUser.getValue();
if (StringHelper.containsNonWhitespace(iu)) {
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_INFOTEXT_USER, iu);
} else {
// remove old config
moduleConfiguration.remove(MSCourseNode.CONFIG_KEY_INFOTEXT_USER);
}
String ic = infotextCoach.getValue();
if (StringHelper.containsNonWhitespace(ic)) {
moduleConfiguration.set(MSCourseNode.CONFIG_KEY_INFOTEXT_COACH, ic);
} else {
// remove old config
moduleConfiguration.remove(MSCourseNode.CONFIG_KEY_INFOTEXT_COACH);
}
}
/**
* @param config the module configuration
* @return true if valid, false otherwhise
*/
public static boolean isConfigValid(ModuleConfiguration config) {
boolean isValid = true;
Object confElement;
// score flag is mandatory
confElement = config.get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD);
if (confElement != null && confElement instanceof Boolean) {
Boolean hasScore = (Boolean) confElement;
if (hasScore.booleanValue()) {
// score min and max are mandatory if score flag is set to true
confElement = config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN);
isValid = (confElement != null && confElement instanceof Float);
confElement = config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX);
isValid = (confElement != null && confElement instanceof Float);
}
} else return false;
// passed flag is mandatory
confElement = config.get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD);
if (confElement != null && confElement instanceof Boolean) {
Boolean hasPassed = (Boolean) confElement;
if (hasPassed.booleanValue()) {
// cut value is optional if passed flag set to true, but type must match
confElement = config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
if (!((confElement == null) || (confElement instanceof Float))) return false;
}
} else return false;
// comment flag is mandatory
confElement = config.get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD);
isValid = (confElement != null && confElement instanceof Boolean);
// infotext is optional
confElement = config.get(MSCourseNode.CONFIG_KEY_INFOTEXT_USER);
if (!((confElement == null) || (confElement instanceof String))) return false;
confElement = config.get(MSCourseNode.CONFIG_KEY_INFOTEXT_COACH);
if (!((confElement == null) || (confElement instanceof String))) return false;
return isValid;
}
}