Skip to content
Snippets Groups Projects
Commit 6c1f4891 authored by uhensler's avatar uhensler
Browse files

OO-4206: GUI for the configuration of the learning path options

parent 83f8e2d6
No related branches found
No related tags found
No related merge requests found
Showing
with 449 additions and 16 deletions
......@@ -50,6 +50,14 @@ public class KeyValues {
remove(keyValue.getKey());
keyValues.add(keyValue);
}
public void addAll(KeyValues additionalKeyValues) {
if (additionalKeyValues == null) return;
for (KeyValue additionalKeyValue : additionalKeyValues.keyValues) {
add(additionalKeyValue);
}
}
/**
* If a key / value pair with the key exists, the existing pair is replaced. If
......@@ -177,4 +185,4 @@ public class KeyValues {
}
}
}
}
\ No newline at end of file
/**
* <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.course.assessment;
/**
*
* Initial date: 27 Aug 2019<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
public enum AssessmentAction {
nodeClicked,
confirmed
}
......@@ -21,9 +21,13 @@ package org.olat.course.condition;
import java.util.Locale;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.Util;
import org.olat.course.nodeaccess.NodeAccessProvider;
import org.olat.course.nodes.CourseNode;
import org.springframework.stereotype.Service;
/**
......@@ -48,4 +52,9 @@ public class ConditionNodeAccessProvider implements NodeAccessProvider {
return translator.translate("access.provider.name");
}
@Override
public TabbableController createEditController(UserRequest ureq, WindowControl wControl, CourseNode courseNode) {
return null;
}
}
......@@ -42,15 +42,16 @@ import org.olat.course.ICourse;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.course.condition.Condition;
import org.olat.course.condition.ConditionEditController;
import org.olat.course.condition.ConditionNodeAccessProvider;
import org.olat.course.nodeaccess.NodeAccessService;
import org.olat.course.nodes.CourseNode;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.course.tree.CourseEditorTreeModel;
import org.olat.repository.RepositoryManager;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Description:<br>
* is the controller for
*
* @author Felix Jost
*/
......@@ -82,11 +83,15 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
private ConditionEditController visibilityCondContr;
private NoAccessExplEditController noAccessContr;
private TabbedPane myTabbedPane;
private TabbableController nodeAccessCtrl;
private TabbableController childTabsCntrllr;
/** Event that signals that the node configuration has been changed * */
public static final Event NODECONFIG_CHANGED_EVENT = new Event("nodeconfigchanged");
private static final String[] paneKeys = { PANE_TAB_VISIBILITY, PANE_TAB_GENERAL };
@Autowired
private NodeAccessService nodeAccessService;
public NodeEditController(UserRequest ureq, WindowControl wControl, CourseEditorTreeModel editorModel, ICourse course, CourseNode luNode,
UserCourseEnvironment euce, TabbableController childTabsController) {
......@@ -127,13 +132,21 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
// Visibility and no-access explanation component
visibilityVc = createVelocityContainer("visibilityedit");
// Visibility precondition
Condition visibCondition = luNode.getPreConditionVisibility();
visibilityCondContr = new ConditionEditController(ureq, getWindowControl(), euce, visibCondition,
AssessmentHelper.getAssessableNodes(editorModel, luNode));
//set this useractivity logger for the visibility condition controller
listenTo(visibilityCondContr);
visibilityVc.put("visibilityCondition", visibilityCondContr.getInitialComponent());
String nodeAccessType = course.getCourseConfig().getNodeAccessType();
if (!ConditionNodeAccessProvider.TYPE.equals(nodeAccessType)) {
TabbableController nodeAccessCtrl = nodeAccessService.createEditController(ureq, getWindowControl(),
nodeAccessType, courseNode);
this.nodeAccessCtrl = nodeAccessCtrl;
listenTo(nodeAccessCtrl);
} else {
// Visibility precondition
Condition visibCondition = luNode.getPreConditionVisibility();
visibilityCondContr = new ConditionEditController(ureq, getWindowControl(), euce, visibCondition,
AssessmentHelper.getAssessableNodes(editorModel, luNode));
//set this useractivity logger for the visibility condition controller
listenTo(visibilityCondContr);
visibilityVc.put("visibilityCondition", visibilityCondContr.getInitialComponent());
}
// No-Access-Explanation
String noAccessExplanation = luNode.getNoAccessExplanation();
......@@ -149,7 +162,6 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
@Override
public void event(UserRequest urequest, Controller source, Event event) {
if (source == visibilityCondContr) {
if (event == Event.CHANGED_EVENT) {
Condition cond = visibilityCondContr.getCondition();
......@@ -162,6 +174,10 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
courseNode.setNoAccessExplanation(noAccessExplanation);
fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT);
}
} else if (source == nodeAccessCtrl) {
if (event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT);
}
} else if (source == childTabsCntrllr) {
if (event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
//fire child controller request further
......@@ -217,9 +233,14 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
@Override
public void addTabs(TabbedPane tabbedPane) {
myTabbedPane = tabbedPane;
myTabbedPane = tabbedPane;
tabbedPane.addTab(translate(PANE_TAB_GENERAL), descriptionVc);
tabbedPane.addTab(translate(PANE_TAB_VISIBILITY), visibilityVc);
if (nodeAccessCtrl!= null) {
nodeAccessCtrl.addTabs(tabbedPane);
}
if (visibilityCondContr !=null) {
tabbedPane.addTab(translate(PANE_TAB_VISIBILITY), visibilityVc);
}
if (childTabsCntrllr != null) {
childTabsCntrllr.addTabs(tabbedPane);
}
......
......@@ -21,10 +21,17 @@ package org.olat.course.learningpath;
import java.util.Locale;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.Util;
import org.olat.course.assessment.AssessmentAction;
import org.olat.course.learningpath.ui.LeaningPathNodeConfigController;
import org.olat.course.learningpath.ui.LeaningPathNodeConfigController.LearningPathControllerConfig;
import org.olat.course.learningpath.ui.TabbableLeaningPathNodeConfigController;
import org.olat.course.nodeaccess.NodeAccessProvider;
import org.olat.course.nodes.CourseNode;
import org.springframework.stereotype.Service;
/**
......@@ -47,4 +54,12 @@ public class LearningPathNodeAccessProvider implements NodeAccessProvider {
return translator.translate("access.provider.name");
}
@Override
public TabbableController createEditController(UserRequest ureq, WindowControl wControl, CourseNode courseNode) {
LearningPathControllerConfig ctrlConfig = LeaningPathNodeConfigController.builder()
.addAssessmentAction(AssessmentAction.nodeClicked)
.build();
return new TabbableLeaningPathNodeConfigController(ureq, wControl, courseNode.getModuleConfiguration(), ctrlConfig);
}
}
......@@ -19,12 +19,198 @@
*/
package org.olat.course.learningpath.ui;
import static org.olat.core.gui.components.util.KeyValues.entry;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
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.util.KeyValues;
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.assessment.AssessmentAction;
import org.olat.modules.ModuleConfiguration;
/**
*
* Initial date: 27 Aug 2019<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
public class LeaningPathNodeConfigController {
public class LeaningPathNodeConfigController extends FormBasicController {
public static final String CONFIG_KEY_ESTIMATED_DURATION = "learning.path.estimated.duration";
public static final String CONFIG_KEY_OBLIGATION = "learning.path.obligation";
public static final String CONFIG_VALUE_OBLIGATION_MANDATORY = "obligation.mandatory";
public static final String CONFIG_VALUE_OBLIGATION_OPTIONAL = "obligation.optional";
public static final String CONFIG_DEFAULT_OBLIGATION = CONFIG_VALUE_OBLIGATION_MANDATORY;
public static final String CONFIG_KEY_DONE_TRIGGER = "learning.path.done.trigger";
private static final String CONFIG_VALUE_DONE_TRIGGER_NONE = "done.trigger.none";
public static final String CONFIG_DEFAULT_DONE_TRIGGER = CONFIG_VALUE_DONE_TRIGGER_NONE;
private TextElement estimatedDurationEl;
private SingleSelection obligationEl;
private SingleSelection doneTriggerEl;
private final ModuleConfiguration configs;
private final LearningPathControllerConfig ctrlConfig;
public LeaningPathNodeConfigController(UserRequest ureq, WindowControl wControl,
ModuleConfiguration configs, LearningPathControllerConfig ctrlConfig) {
super(ureq, wControl);
this.configs = configs;
this.ctrlConfig = ctrlConfig;
initForm(ureq);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
String estimatedTime = configs.getStringValue(CONFIG_KEY_ESTIMATED_DURATION);
estimatedDurationEl = uifactory.addTextElement("config.estimated.duration", 128, estimatedTime , formLayout);
KeyValues obligationKV = new KeyValues();
obligationKV.add(entry(CONFIG_VALUE_OBLIGATION_MANDATORY, translate("config.obligation.mandatory")));
obligationKV.add(entry(CONFIG_VALUE_OBLIGATION_OPTIONAL, translate("config.obligation.optional")));
obligationEl = uifactory.addRadiosHorizontal("config.obligation", formLayout, obligationKV.keys(), obligationKV.values());
String obligationKey = configs.getStringValue(CONFIG_KEY_OBLIGATION, CONFIG_DEFAULT_OBLIGATION);
if (Arrays.asList(obligationEl.getKeys()).contains(obligationKey)) {
obligationEl.select(obligationKey, true);
}
KeyValues doneTriggerKV = getDoneTriggerKV();
doneTriggerEl = uifactory.addDropdownSingleselect("config.done.trigger", formLayout,
doneTriggerKV.keys(), doneTriggerKV.values());
doneTriggerEl.addActionListener(FormEvent.ONCHANGE);
String doneTriggerKey = configs.getStringValue(CONFIG_KEY_DONE_TRIGGER, CONFIG_DEFAULT_DONE_TRIGGER);
if (Arrays.asList(doneTriggerEl.getKeys()).contains(doneTriggerKey)) {
doneTriggerEl.select(doneTriggerKey, true);
}
uifactory.addFormSubmitButton("save", formLayout);
}
private KeyValues getDoneTriggerKV() {
KeyValues doneTriggerKV = new KeyValues();
if (ctrlConfig.getAssessmentActions().contains(AssessmentAction.nodeClicked)) {
doneTriggerKV.add(entry(AssessmentAction.nodeClicked.name(), translate("config.done.trigger.started")));
}
if (ctrlConfig.getAssessmentActions().contains(AssessmentAction.confirmed)) {
doneTriggerKV.add(entry(AssessmentAction.confirmed.name(), translate("config.done.trigger.confirmed")));
}
return doneTriggerKV;
}
@Override
protected boolean validateFormLogic(UserRequest ureq) {
boolean allOk = true;
allOk = validateInteger(estimatedDurationEl, 1, 10000, "error.positiv.int");
return allOk & super.validateFormLogic(ureq);
}
public static boolean validateInteger(TextElement el, int min, int max, String i18nKey) {
boolean allOk = true;
el.clearError();
if(el.isEnabled() && el.isVisible()) {
String val = el.getValue();
if(StringHelper.containsNonWhitespace(val)) {
try {
int value = Integer.parseInt(val);
if(min > value) {
el.setErrorKey(i18nKey, null);
allOk = false;
} else if(max < value) {
el.setErrorKey(i18nKey, null);
allOk = false;
}
} catch (NumberFormatException e) {
el.setErrorKey(i18nKey, null);
allOk = false;
}
}
}
return allOk;
}
@Override
protected void formOK(UserRequest ureq) {
fireEvent(ureq, Event.DONE_EVENT);
}
protected ModuleConfiguration getUpdatedConfig() {
String estimatedTime = estimatedDurationEl.getValue();
configs.setStringValue(CONFIG_KEY_ESTIMATED_DURATION, estimatedTime);
String obligation = obligationEl.isOneSelected()
? obligationEl.getSelectedKey()
: CONFIG_DEFAULT_OBLIGATION;
configs.setStringValue(CONFIG_KEY_OBLIGATION, obligation);
String doneTrigger = doneTriggerEl.isOneSelected()
? doneTriggerEl.getSelectedKey()
: CONFIG_DEFAULT_DONE_TRIGGER;
configs.setStringValue(CONFIG_KEY_DONE_TRIGGER, doneTrigger);
return configs;
}
@Override
protected void doDispose() {
//
}
public interface LearningPathControllerConfig {
public Set<AssessmentAction> getAssessmentActions();
}
public static ControllerConfigBuilder builder() {
return new ControllerConfigBuilder();
}
public static class ControllerConfigBuilder {
private final Set<AssessmentAction> assessmentActions = new HashSet<>();
private ControllerConfigBuilder() {
}
public ControllerConfigBuilder addAssessmentAction(AssessmentAction action) {
assessmentActions.add(action);
return this;
}
public LearningPathControllerConfig build() {
return new ControllerConfigImpl(this);
}
private final static class ControllerConfigImpl implements LearningPathControllerConfig {
private final Set<AssessmentAction> assessmentActions;
public ControllerConfigImpl(ControllerConfigBuilder builder) {
this.assessmentActions = new HashSet<>(builder.assessmentActions);
}
@Override
public Set<AssessmentAction> getAssessmentActions() {
return assessmentActions;
}
}
}
}
/**
* <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.course.learningpath.ui;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.tabbedpane.TabbedPane;
import org.olat.core.gui.components.velocity.VelocityContainer;
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.gui.control.generic.tabbable.ActivateableTabbableDefaultController;
import org.olat.course.editor.NodeEditController;
import org.olat.course.learningpath.ui.LeaningPathNodeConfigController.LearningPathControllerConfig;
import org.olat.modules.ModuleConfiguration;
/**
*
* Initial date: 27 Aug 2019<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
public class TabbableLeaningPathNodeConfigController extends ActivateableTabbableDefaultController {
private static final String PANE_TAB_LEARNIN_PATH = "pane.tab.learning.path";
private final static String[] paneKeys = { PANE_TAB_LEARNIN_PATH };
private final VelocityContainer configVC;
private final LeaningPathNodeConfigController configController;
private TabbedPane tabPane;
public TabbableLeaningPathNodeConfigController(UserRequest ureq, WindowControl wControl,
ModuleConfiguration configs, LearningPathControllerConfig ctrlConfig) {
super(ureq, wControl);
configController = new LeaningPathNodeConfigController(ureq, wControl, configs, ctrlConfig);
listenTo(configController);
configVC = createVelocityContainer("config");
configVC.put("config", configController.getInitialComponent());
}
@Override
public void addTabs(TabbedPane tabbedPane) {
tabPane = tabbedPane;
tabbedPane.addTab(translate(PANE_TAB_LEARNIN_PATH), configVC);
}
@Override
public String[] getPaneKeys() {
return paneKeys;
}
@Override
public TabbedPane getTabbedPane() {
return tabPane;
}
@Override
public void event(UserRequest ureq, Controller source, Event event) {
if (source == configController && event.equals(Event.DONE_EVENT)) {
configController.getUpdatedConfig();
fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
}
}
@Override
protected void event(UserRequest ureq, Component source, Event event) {
//
}
@Override
protected void doDispose() {
//
}
}
$r.render("config")
\ No newline at end of file
access.provider.name=Lernpfad
\ No newline at end of file
access.provider.name=Lernpfad
error.positiv.int=Geben Sie eine positive Zahl ein.
config.done.trigger=Lernfortschritt
config.done.trigger.confirmed=Best\u00E4tigt
config.done.trigger.started=Ge\u00F6ffnet
config.estimated.duration=Zeitvorgabe (Minuten)
congig.obligation=Pflicht
config.obligation.mandatory=Obligatorisch
config.obligation.optional=Freiwillig
pane.tab.learning.path=Lernpfad
\ No newline at end of file
access.provider.name=Learning path
\ No newline at end of file
access.provider.name=Learning path
error.positiv.int=Enter a positive number.
config.done.trigger=Done trigger
config.done.trigger.confirmed=Confirmed
config.done.trigger.started=Opened
config.estimated.duration=Estimated duration (minutes)
config.obligation=Obligation
config.obligation.mandatory=Mandatory
config.obligation.optional=Optional
pane.tab.learning.path=Learning path
\ No newline at end of file
......@@ -19,6 +19,11 @@
*/
package org.olat.course.nodeaccess;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.course.nodes.CourseNode;
/**
*
* Initial date: 27 Aug 2019<br>
......@@ -27,4 +32,6 @@ package org.olat.course.nodeaccess;
*/
public interface NodeAccessProvider extends NodeAccessProviderIdentifier {
public TabbableController createEditController(UserRequest ureq, WindowControl wControl, CourseNode courseNode);
}
......@@ -21,6 +21,11 @@ package org.olat.course.nodeaccess;
import java.util.List;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.course.nodes.CourseNode;
/**
*
* Initial date: 27 Aug 2019<br>
......@@ -31,4 +36,16 @@ public interface NodeAccessService {
public List<? extends NodeAccessProviderIdentifier> getNodeAccessProviderIdentifer();
/**
* Creates the controller to edit the configurations of the node.
*
* @param ureq
* @param windowControl
* @param nodeAccessType
* @param courseNode
* @return
*/
public TabbableController createEditController(UserRequest ureq, WindowControl wControl, String nodeAccessType,
CourseNode courseNode);
}
......@@ -21,9 +21,15 @@ package org.olat.course.nodeaccess.manager;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.logging.Tracing;
import org.olat.course.nodeaccess.NodeAccessProvider;
import org.olat.course.nodeaccess.NodeAccessProviderIdentifier;
import org.olat.course.nodeaccess.NodeAccessService;
import org.olat.course.nodes.CourseNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -36,12 +42,30 @@ import org.springframework.stereotype.Service;
@Service
public class NodeAccessServiceImpl implements NodeAccessService {
private static final Logger log = Tracing.createLoggerFor(NodeAccessServiceImpl.class);
@Autowired
private List<NodeAccessProvider> nodeAccessProviders;
private NodeAccessProvider getNodeAccessProvider(String nodeAccessType) {
for (NodeAccessProvider provider : nodeAccessProviders) {
if (provider.getType().equals(nodeAccessType)) {
return provider;
}
}
log.error("No node access provider found for type '{}'!", nodeAccessType);
return null;
}
@Override
public List<? extends NodeAccessProviderIdentifier> getNodeAccessProviderIdentifer() {
return nodeAccessProviders;
}
@Override
public TabbableController createEditController(UserRequest ureq, WindowControl wControl, String nodeAccessType,
CourseNode courseNode) {
return getNodeAccessProvider(nodeAccessType).createEditController(ureq, wControl, courseNode);
}
}
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