/** * <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.modules.forms.ui; import static org.olat.modules.forms.handler.EvaluationFormResource.FORM_XML_FILE; import java.io.File; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.olat.core.commons.persistence.DB; import org.olat.core.gui.UserRequest; 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.FormLink; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.elements.SliderElement; 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.elements.FormSubmit; import org.olat.core.gui.components.link.Link; 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.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.util.StringHelper; import org.olat.core.util.xml.XStreamHelper; import org.olat.fileresource.FileResourceManager; import org.olat.modules.forms.EvaluationFormManager; import org.olat.modules.forms.EvaluationFormResponse; import org.olat.modules.forms.EvaluationFormResponseDataTypes; import org.olat.modules.forms.EvaluationFormSession; import org.olat.modules.forms.EvaluationFormSessionStatus; import org.olat.modules.forms.model.xml.AbstractElement; import org.olat.modules.forms.model.xml.Form; import org.olat.modules.forms.model.xml.FormXStream; import org.olat.modules.forms.model.xml.Rubric; import org.olat.modules.forms.model.xml.Rubric.SliderType; import org.olat.modules.forms.model.xml.Slider; import org.olat.modules.forms.model.xml.TextInput; import org.olat.modules.forms.ui.model.EvaluationFormElementWrapper; import org.olat.modules.forms.ui.model.SliderWrapper; import org.olat.modules.forms.ui.model.TextInputWrapper; import org.olat.modules.portfolio.PageBody; import org.olat.modules.portfolio.ui.editor.ValidatingController; import org.olat.modules.portfolio.ui.editor.ValidationMessage; import org.olat.modules.portfolio.ui.editor.ValidationMessage.Level; import org.olat.repository.RepositoryEntry; import org.springframework.beans.factory.annotation.Autowired; /** * * Initial date: 6 déc. 2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class EvaluationFormController extends FormBasicController implements ValidatingController { private int count = 0; private final Form form; private PageBody anchor; private boolean readOnly; private final boolean doneButton; private final Identity evaluator; private final RepositoryEntry formEntry; private EvaluationFormSession session; private List<EvaluationFormElementWrapper> elementWrapperList = new ArrayList<>(); private final Map<String, EvaluationFormResponse> identifierToResponses = new HashMap<>(); private FormSubmit saveAsDoneButton; private DialogBoxController confirmDoneCtrl; @Autowired private DB dbInstance; @Autowired private EvaluationFormManager evaluationFormManager; /** * The responses are saved, it's aimed at the binder where the assignment was deleted. * * @param ureq * @param wControl * @param xmlForm */ public EvaluationFormController(UserRequest ureq, WindowControl wControl, Identity evaluator, PageBody pageBody, String xmlForm, boolean readOnly) { super(ureq, wControl, "run"); form = (Form)XStreamHelper.readObject(FormXStream.getXStream(), xmlForm); this.evaluator = evaluator; this.readOnly = readOnly; this.anchor = pageBody; formEntry = null; doneButton = false; loadResponses(); initForm(ureq); } /** * The responses are not saved, it's only a preview. * * @param ureq * @param wControl * @param formFile */ public EvaluationFormController(UserRequest ureq, WindowControl wControl, File formFile) { super(ureq, wControl, "run"); form = (Form)XStreamHelper.readObject(FormXStream.getXStream(), formFile); evaluator = null; readOnly = false; formEntry = null; doneButton = false; initForm(ureq); } /** * The responses are saved and linked to the anchor. * * @param ureq * @param wControl * @param form * @param anchor The database object which hold the evaluation. */ public EvaluationFormController(UserRequest ureq, WindowControl wControl, Identity evaluator, PageBody anchor, RepositoryEntry formEntry, boolean readOnly, boolean doneButton) { super(ureq, wControl, "run"); this.anchor = anchor; this.readOnly = readOnly; this.evaluator = evaluator; this.formEntry = formEntry; this.doneButton = doneButton; File repositoryDir = new File(FileResourceManager.getInstance().getFileResourceRoot(formEntry.getOlatResource()), FileResourceManager.ZIPDIR); File formFile = new File(repositoryDir, FORM_XML_FILE); form = (Form)XStreamHelper.readObject(FormXStream.getXStream(), formFile); loadResponses(); initForm(ureq); } public EvaluationFormSession getSession() { return session; } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { updateElements(); if(doneButton && !readOnly) { saveAsDoneButton = uifactory.addFormSubmitButton("save.as.done", formLayout); } } private void updateElements() { List<EvaluationFormElementWrapper> elementWrappers = new ArrayList<>(); for(AbstractElement element:form.getElements()) { EvaluationFormElementWrapper wrapper = forgeElement(element); if(wrapper != null) { elementWrappers.add(wrapper); } } elementWrapperList = elementWrappers; flc.contextPut("elements", elementWrappers); } private void loadResponses() { if(evaluator == null) return; flc.contextPut("messageNotDone", Boolean.FALSE); session = evaluationFormManager.getSessionForPortfolioEvaluation(evaluator, anchor); if(session == null) { session = evaluationFormManager.createSessionForPortfolioEvaluation(evaluator, anchor, formEntry); } if(session.getEvaluationFormSessionStatus() == EvaluationFormSessionStatus.done) { readOnly = true; } else if(!evaluator.equals(getIdentity())) { flc.contextPut("messageNotDone", Boolean.TRUE); } List<EvaluationFormResponse> responses = evaluationFormManager.getResponsesFromPortfolioEvaluation(evaluator, anchor); for(EvaluationFormResponse response:responses) { identifierToResponses.put(response.getResponseIdentifier(), response); } } private EvaluationFormElementWrapper forgeElement(AbstractElement element) { EvaluationFormElementWrapper wrapper = null; String type = element.getType(); switch(type) { case "formhtitle": case "formhr": case "formhtmlraw": wrapper = new EvaluationFormElementWrapper(element); break; case "formrubric": wrapper = forgeRubric((Rubric)element); break; case "formtextinput": wrapper = forgeTextInput((TextInput)element); break; } return wrapper; } private EvaluationFormElementWrapper forgeTextInput(TextInput element) { String initialValue = ""; EvaluationFormResponse response = identifierToResponses.get(element.getId()); if(response != null && StringHelper.containsNonWhitespace(response.getStringuifiedResponse())) { initialValue = response.getStringuifiedResponse(); } int rows = 12; if(element.getRows() > 0) { rows = element.getRows(); } TextElement textEl = uifactory.addTextAreaElement("textinput_" + (count++), null, Integer.MAX_VALUE, rows, 72, false, initialValue, flc); textEl.setEnabled(!readOnly); FormLink saveButton = uifactory.addFormLink("save_" + (count++), "save", null, flc, Link.BUTTON); saveButton.setVisible(!readOnly); TextInputWrapper textInputWrapper = new TextInputWrapper(element, textEl, saveButton); saveButton.setUserObject(textInputWrapper); textEl.setUserObject(textInputWrapper); EvaluationFormElementWrapper wrapper = new EvaluationFormElementWrapper(element); wrapper.setTextInputWrapper(textInputWrapper); return wrapper; } private EvaluationFormElementWrapper forgeRubric(Rubric element) { EvaluationFormElementWrapper wrapper = new EvaluationFormElementWrapper(element); List<Slider> sliders = element.getSliders(); List<SliderWrapper> sliderWrappers = new ArrayList<>(sliders.size()); for(Slider slider:sliders) { String responseIdentifier = slider.getId(); EvaluationFormResponse response = identifierToResponses.get(responseIdentifier); SliderType type = element.getSliderType(); SliderWrapper sliderWrapper = null; if(type == SliderType.discrete) { sliderWrapper = forgeDiscreteRadioButtons(slider, element, response); } else if(type == SliderType.discrete_slider) { sliderWrapper = forgeDiscreteSlider(slider, element, response); } else if(type == SliderType.continuous) { sliderWrapper = forgeContinuousSlider(slider, element, response); } if(sliderWrapper != null) { sliderWrappers.add(sliderWrapper); } } wrapper.setSliders(sliderWrappers); return wrapper; } private SliderWrapper forgeContinuousSlider(Slider slider, Rubric element, EvaluationFormResponse response) { SliderElement sliderEl = uifactory.addSliderElement("slider_" + (count++), null, flc); sliderEl.setDomReplacementWrapperRequired(false); sliderEl.addActionListener(FormEvent.ONCHANGE); sliderEl.setEnabled(!readOnly); if(response != null && response.getNumericalResponse() != null) { double val = response.getNumericalResponse().doubleValue(); sliderEl.setInitialValue(val); } sliderEl.setMinValue(element.getStart()); sliderEl.setMaxValue(element.getEnd()); SliderWrapper sliderWrapper = new SliderWrapper(slider, sliderEl); sliderEl.setUserObject(sliderWrapper); return sliderWrapper; } private SliderWrapper forgeDiscreteSlider(Slider slider, Rubric element, EvaluationFormResponse response) { SliderElement sliderEl = uifactory.addSliderElement("slider_" + (count++), null, flc); sliderEl.setDomReplacementWrapperRequired(false); sliderEl.addActionListener(FormEvent.ONCHANGE); sliderEl.setEnabled(!readOnly); if(response != null && response.getNumericalResponse() != null) { double val = response.getNumericalResponse().doubleValue(); sliderEl.setInitialValue(val); } sliderEl.setMinValue(element.getStart()); sliderEl.setMaxValue(element.getEnd()); sliderEl.setStep(1); SliderWrapper sliderWrapper = new SliderWrapper(slider, sliderEl); sliderEl.setUserObject(sliderWrapper); return sliderWrapper; } private SliderWrapper forgeDiscreteRadioButtons(Slider slider, Rubric element, EvaluationFormResponse response) { int start = element.getStart(); int end = element.getEnd(); int steps = element.getSteps(); double[] theSteps = new double[steps]; String[] theKeys = new String[steps]; String[] theValues = new String[steps]; double step = (end - start + 1) / (double)steps; for(int i=0; i<steps; i++) { theSteps[i] = start + (i * step); theKeys[i] = Double.toString(theSteps[i]); theValues[i] = ""; } SingleSelection radioEl = uifactory.addRadiosVertical("slider_" + (count++), null, flc, theKeys, theValues); radioEl.setDomReplacementWrapperRequired(false); radioEl.addActionListener(FormEvent.ONCHANGE); radioEl.setEnabled(!readOnly); radioEl.setAllowNoSelection(true); int widthInPercent = EvaluationFormElementWrapper.getWidthInPercent(element); radioEl.setWidthInPercent(widthInPercent, true); if(response != null && response.getNumericalResponse() != null) { double val = response.getNumericalResponse().doubleValue(); double error = step / 10.0d; for(int i=0; i<theSteps.length; i++) { double theStep = theSteps[i]; double margin = Math.abs(theStep - val); if(margin < error) { radioEl.select(theKeys[i], true); } } } SliderWrapper sliderWrapper = new SliderWrapper(slider, radioEl); radioEl.setUserObject(sliderWrapper); return sliderWrapper; } @Override protected void propagateDirtinessToContainer(FormItem fiSrc, FormEvent fe) { //super.propagateDirtinessToContainer(fiSrc, fe); } @Override public boolean validate(UserRequest ureq, List<ValidationMessage> messages) { boolean allFiled = true; for(EvaluationFormElementWrapper elementWrapper:elementWrapperList) { if(elementWrapper.isTextInput()) { TextInputWrapper wrapper = elementWrapper.getTextInputWrapper(); if(wrapper != null && !hasResponse(wrapper.getId())) { allFiled &= false; } } else if(elementWrapper.getSliders() != null && elementWrapper.getSliders().size() > 0) { for(SliderWrapper slider:elementWrapper.getSliders()) { if(slider != null && !hasResponse(slider.getId())) { allFiled &= false; } } } } if(!allFiled) { String msg = translate("warning.form.not.completed"); messages.add(new ValidationMessage(Level.warning, msg)); } return validateFormLogic(ureq); } private boolean hasResponse(String id) { if(id == null) return true;//not a field if(!identifierToResponses.containsKey(id)) { return false; } EvaluationFormResponse response = identifierToResponses.get(id); if(response == null || (response.getNumericalResponse() == null && !StringHelper.containsNonWhitespace(response.getStringuifiedResponse()))) { return false; } return true; } @Override protected boolean validateFormLogic(UserRequest ureq) { boolean allOk = true; return allOk & super.validateFormLogic(ureq); } @Override protected void event(UserRequest ureq, Controller source, Event event) { if(confirmDoneCtrl == source) { if(DialogBoxUIFactory.isYesEvent(event)) { saveAsDone(ureq); } } super.event(ureq, source, event); } @Override protected void formOK(UserRequest ureq) { doConfirmDone(ureq); } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(saveAsDoneButton == source) { doConfirmDone(ureq); } else if(source instanceof SingleSelection) { SingleSelection radioEl = (SingleSelection)source; Object uobject = radioEl.getUserObject(); if(uobject instanceof SliderWrapper) { String selectedKey = radioEl.getSelectedKey(); SliderWrapper sliderWrapper = (SliderWrapper)uobject; saveResponse(new BigDecimal(selectedKey), selectedKey, sliderWrapper.getId()); } } else if(source instanceof SliderElement) { SliderElement slider = (SliderElement)source; Object uobject = slider.getUserObject(); if(uobject instanceof SliderWrapper) { double value = slider.getValue(); SliderWrapper sliderWrapper = (SliderWrapper)uobject; saveResponse(new BigDecimal(value), Double.toString(value), sliderWrapper.getId()); } } else if(source instanceof FormLink) { FormLink link = (FormLink)source; Object uobject = link.getUserObject(); if(uobject instanceof TextInputWrapper) { TextInputWrapper wrapper = (TextInputWrapper)uobject; String value = wrapper.getTextEl().getValue(); saveResponse(null, value, wrapper.getId()); } } super.formInnerEvent(ureq, source, event); } private void saveResponse(BigDecimal numericalValue, String stringuifiedReponse, String responseIdentifier) { if(evaluator == null || readOnly) return; EvaluationFormResponse response = identifierToResponses.get(responseIdentifier); if(response == null) { response = evaluationFormManager.createResponseForPortfolioEvaluation(responseIdentifier, numericalValue, stringuifiedReponse, EvaluationFormResponseDataTypes.numerical, session); } else { response = evaluationFormManager.updateResponseForPortfolioEvaluation(numericalValue, stringuifiedReponse, response); } //update cache if(response != null) { identifierToResponses.put(responseIdentifier, response); } } private void doConfirmDone(UserRequest ureq) { for(EvaluationFormElementWrapper elementWrapper:elementWrapperList) { if(elementWrapper.isTextInput()) { TextInputWrapper wrapper = elementWrapper.getTextInputWrapper(); String value = wrapper.getTextEl().getValue(); saveResponse(null, value, wrapper.getId()); } } StringBuilder sb = new StringBuilder(); sb.append("<p>").append(translate("confirm.done")).append("</p>"); List<ValidationMessage> messages = new ArrayList<>(); validate(ureq, messages); if(messages.size() > 0) { for(ValidationMessage message:messages) { sb.append("<p class='o_warning'>").append(message.getMessage()).append("</p>"); } } confirmDoneCtrl = activateYesNoDialog(ureq, null, sb.toString(), confirmDoneCtrl); } private void saveAsDone(UserRequest ureq) { //save text inputs session = evaluationFormManager.changeSessionStatus(session, EvaluationFormSessionStatus.done); readOnly = true; dbInstance.commit(); loadResponses(); updateElements(); if (saveAsDoneButton != null) { saveAsDoneButton.setVisible(false); } dbInstance.commit(); fireEvent(ureq, Event.DONE_EVENT); } @Override protected void doDispose() { // } }