/**
* <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.portfolio.ui;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.FlexiTableElement;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
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.elements.FormCancel;
import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit;
import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
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.closablewrapper.CloseableModalController;
import org.olat.core.id.Identity;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.StringHelper;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.modules.assessment.model.AssessmentEntryStatus;
import org.olat.modules.assessment.ui.ScoreCellRenderer;
import org.olat.modules.portfolio.AssessmentSection;
import org.olat.modules.portfolio.Binder;
import org.olat.modules.portfolio.BinderConfiguration;
import org.olat.modules.portfolio.BinderSecurityCallback;
import org.olat.modules.portfolio.Page;
import org.olat.modules.portfolio.PortfolioLoggingAction;
import org.olat.modules.portfolio.PortfolioRoles;
import org.olat.modules.portfolio.PortfolioService;
import org.olat.modules.portfolio.Section;
import org.olat.modules.portfolio.SectionStatus;
import org.olat.modules.portfolio.model.AssessmentSectionChange;
import org.olat.modules.portfolio.ui.BinderAssessmentDataModel.AssessmentSectionCols;
import org.olat.modules.portfolio.ui.component.SectionStatusCellRenderer;
import org.olat.modules.portfolio.ui.renderer.PassedCellRenderer;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 22.06.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class BinderAssessmentController extends FormBasicController {
private static final String[] onKeys = new String[] { "on" };
private static final String[] onValues = new String[] { "" };
private Binder binder;
private final BinderSecurityCallback secCallback;
private int counter = 0;
private FormSubmit saveButton;
private FormCancel cancelButton;
private FlexiTableElement tableEl;
private FormLink saveAndDoneLink, reopenLink;
private BinderAssessmentDataModel model;
private CloseableModalController cmc;
private SectionDatesEditController editSectionDatesCtrl;
private boolean withScore;
private boolean withPassed;
@Autowired
private PortfolioService portfolioService;
public BinderAssessmentController(UserRequest ureq, WindowControl wControl,
BinderSecurityCallback secCallback, Binder binder, BinderConfiguration config) {
super(ureq, wControl, "section_assessment");
this.binder = binder;
this.secCallback = secCallback;
this.withPassed = config.isWithPassed();
this.withScore = config.isWithScore();
initForm(ureq);
loadModel();
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AssessmentSectionCols.sectionName));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AssessmentSectionCols.numOfPages));
if(withPassed) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AssessmentSectionCols.passed, new PassedCellRenderer()));
}
if(withScore) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AssessmentSectionCols.score, new ScoreCellRenderer()));
}
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AssessmentSectionCols.changeStatus, new SectionStatusCellRenderer(getTranslator())));
model = new BinderAssessmentDataModel(columnsModel);
tableEl = uifactory.addTableElement(getWindowControl(), "section-list", model, getTranslator(), formLayout);
tableEl.setCustomizeColumns(true);
tableEl.setEditMode(true);
tableEl.setAndLoadPersistedPreferences(ureq, "section-assessment");
FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
formLayout.add(buttonsCont);
saveButton = uifactory.addFormSubmitButton("save", buttonsCont);
saveButton.setElementCssClass("o_sel_pf_assessment_save");
saveAndDoneLink = uifactory.addFormLink("save.done", buttonsCont, Link.BUTTON);
saveAndDoneLink.setElementCssClass("o_sel_pf_assessment_save_done");
reopenLink = uifactory.addFormLink("reopen.binder", buttonsCont, Link.BUTTON);
reopenLink.setElementCssClass("o_sel_pf_assessment_reopen");
cancelButton = uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
}
private void loadModel() {
List<AssessmentSection> aSections = portfolioService.getAssessmentSections(binder, null);
Map<Section,AssessmentSection> aSectionsMap = new HashMap<>();
for(AssessmentSection aSection:aSections) {
aSectionsMap.put(aSection.getSection(), aSection);
}
// binder done only is an owner is present
// and all owners have done the binder
boolean binderDone = false;
List<Identity> assessedIdentities = portfolioService.getMembers(binder, PortfolioRoles.owner.name());
int countDone = 0;
for(Identity assessedIdentity:assessedIdentities) {
AssessmentEntryStatus status = portfolioService.getAssessmentStatus(assessedIdentity, binder);
if(status == AssessmentEntryStatus.done) {
countDone++;
}
}
binderDone = (countDone > 0 && countDone == assessedIdentities.size());
boolean allClosed = true;
List<Section> sections = portfolioService.getSections(binder);
List<AssessmentSectionWrapper> rows = new ArrayList<>();
Map<Section,AssessmentSectionWrapper> sectionToRows = new HashMap<>();
for(Section section:sections) {
AssessmentSection assessmentSection = aSectionsMap.get(section);
AssessmentSectionWrapper row = new AssessmentSectionWrapper(section, assessmentSection);
sectionToRows.put(section, row);
if(secCallback.canViewAssess(section) || secCallback.canAssess(section)) {
rows.add(row);
}
allClosed &= section.getSectionStatus() == SectionStatus.closed;
}
List<Page> pages = portfolioService.getPages(binder, null);
for(Page page:pages) {
AssessmentSectionWrapper row = sectionToRows.get(page.getSection());
if(row != null) {
row.setNumOfPages(row.getNumOfPages() + 1);
}
}
boolean allowedToAssess = false;
boolean allowedToAssessBinder = secCallback.canAssess(binder);
for(AssessmentSectionWrapper row:rows) {
boolean canAssess = secCallback.canAssess(row.getSection());
if(canAssess && !binderDone) {
forgeAssessmentSection(row);
allowedToAssess = true;
}
}
reopenLink.setVisible(allowedToAssessBinder && binderDone);
saveAndDoneLink.setVisible(allowedToAssessBinder && !binderDone && allClosed);
saveButton.setVisible(allowedToAssess);
cancelButton.setVisible(allowedToAssess);
model.setObjects(rows);
tableEl.reset();
tableEl.reloadData();
}
private void forgeAssessmentSection(AssessmentSectionWrapper row) {
AssessmentSection assessmentSection = row.getAssessmentSection();
Section section = row.getSection();
if(!SectionStatus.isClosed(section)) {
//score
String pointVal = null;
if(assessmentSection != null && assessmentSection.getScore() != null) {
BigDecimal score = assessmentSection.getScore();
pointVal = AssessmentHelper.getRoundedScore(score);
}
TextElement pointEl = uifactory.addTextElement("point" + (++counter), null, 5, pointVal, flc);
pointEl.setDisplaySize(5);
row.setScoreEl(pointEl);
//passed
Boolean passed = assessmentSection == null ? null : assessmentSection.getPassed();
MultipleSelectionElement passedEl = uifactory.addCheckboxesHorizontal("check" + (++counter), null, flc, onKeys, onValues);
if(passed != null && passed.booleanValue()) {
passedEl.select(onKeys[0], passed.booleanValue());
}
row.setPassedEl(passedEl);
}
if(SectionStatus.isClosed(section)) {
FormLink reopenButton = uifactory.addFormLink("reopen" + (++counter), "reopen", "reopen", null, flc, Link.BUTTON);
reopenButton.setElementCssClass("o_sel_pf_reopen_section");
reopenButton.setUserObject(row);
row.setButton(reopenButton);
} else {
FormLink closeButton = uifactory.addFormLink("close" + (++counter), "close", "close.section", null, flc, Link.BUTTON);
closeButton.setElementCssClass("o_sel_pf_close_section");
closeButton.setUserObject(row);
row.setButton(closeButton);
}
}
@Override
protected void doDispose() {
//
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if(editSectionDatesCtrl == source) {
if(event == Event.DONE_EVENT) {
loadModel();
}
cmc.deactivate();
cleanUp();
} else if(cmc == source) {
cleanUp();
}
super.event(ureq, source, event);
}
private void cleanUp() {
removeAsListenerAndDispose(editSectionDatesCtrl);
removeAsListenerAndDispose(cmc);
editSectionDatesCtrl = null;
cmc = null;
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(reopenLink == source) {
doReopenBinder();
loadModel();
fireEvent(ureq, Event.CHANGED_EVENT);
} else if(saveAndDoneLink == source) {
commitChanges();
doSetBinderDone();
loadModel();
fireEvent(ureq, Event.CHANGED_EVENT);
} else if(source instanceof FormLink) {
FormLink button = (FormLink)source;
String cmd = button.getCmd();
if("close".equals(cmd)) {
AssessmentSectionWrapper row = (AssessmentSectionWrapper)button.getUserObject();
doClose(row.getSection());
loadModel();
fireEvent(ureq, Event.CHANGED_EVENT);
} else if("reopen".equals(cmd)) {
AssessmentSectionWrapper row = (AssessmentSectionWrapper)button.getUserObject();
doReopen(ureq, row.getSection());
loadModel();
fireEvent(ureq, Event.CHANGED_EVENT);
}
}
super.formInnerEvent(ureq, source, event);
}
@Override
protected void formOK(UserRequest ureq) {
commitChanges();
fireEvent(ureq, Event.CHANGED_EVENT);
}
private void commitChanges() {
List<AssessmentSectionWrapper> rows = model.getObjects();
List<Identity> assessedIdentities = portfolioService.getMembers(binder, PortfolioRoles.owner.name());
List<AssessmentSectionChange> changes = new ArrayList<>();
for(AssessmentSectionWrapper row:rows) {
Section section = row.getSection();
if(secCallback.canAssess(section) && !SectionStatus.isClosed(section)) {
BigDecimal score = null;
if(withScore) {
String value = row.getScoreEl().getValue();
if(StringHelper.containsNonWhitespace(value)) {
score = new BigDecimal(value);
}
}
Boolean passed = null;
if(withPassed) {
passed = row.getPassedEl().isSelected(0);
}
for(Identity assessedIdentity:assessedIdentities) {
changes.add(new AssessmentSectionChange(assessedIdentity, row.getSection(), row.getAssessmentSection(), score, passed));
}
}
}
portfolioService.updateAssessmentSections(binder, changes, getIdentity());
}
private void doClose(Section section) {
portfolioService.changeSectionStatus(section, SectionStatus.closed, getIdentity());
ThreadLocalUserActivityLogger.log(PortfolioLoggingAction.PORTFOLIO_SECTION_CLOSE, getClass(),
LoggingResourceable.wrap(section));
}
private void doReopen(UserRequest ureq, Section section) {
if(section.getSectionStatus() != null && section.getSectionStatus().equals(SectionStatus.closed)) {
portfolioService.changeSectionStatus(section, SectionStatus.inProgress, getIdentity());
ThreadLocalUserActivityLogger.log(PortfolioLoggingAction.PORTFOLIO_SECTION_REOPEN, getClass(),
LoggingResourceable.wrap(section));
} else if(section.getEndDate() != null && section.getEndDate().compareTo(new Date()) < 0) {
editSectionDatesCtrl = new SectionDatesEditController(ureq, getWindowControl(), section);
listenTo(editSectionDatesCtrl);
String title = translate("override.dates.section");
cmc = new CloseableModalController(getWindowControl(), null, editSectionDatesCtrl.getInitialComponent(), true, title, true);
listenTo(cmc);
cmc.activate();
}
}
private void doReopenBinder() {
List<Identity> assessedIdentities = portfolioService.getMembers(binder, PortfolioRoles.owner.name());
for(Identity assessedIdentity:assessedIdentities) {
portfolioService.setAssessmentStatus(assessedIdentity, binder, AssessmentEntryStatus.inProgress, getIdentity());
}
}
private void doSetBinderDone() {
List<Identity> assessedIdentities = portfolioService.getMembers(binder, PortfolioRoles.owner.name());
for(Identity assessedIdentity:assessedIdentities) {
portfolioService.setAssessmentStatus(assessedIdentity, binder, AssessmentEntryStatus.done, getIdentity());
}
}
public static class AssessmentSectionWrapper {
private int numOfPages = 0;
private Section section;
private AssessmentSection assessmentSection;
private FormLink button;
private TextElement scoreEl;
private MultipleSelectionElement passedEl;
public AssessmentSectionWrapper(Section section, AssessmentSection assessmentSection) {
this.section = section;
this.assessmentSection = assessmentSection;
}
public int getNumOfPages() {
return numOfPages;
}
public void setNumOfPages(int numOfPages) {
this.numOfPages = numOfPages;
}
public String getSectionTitle() {
return section.getTitle();
}
public Boolean getPassed() {
return assessmentSection == null ? null : assessmentSection.getPassed();
}
public BigDecimal getScore() {
return assessmentSection == null ? null : assessmentSection.getScore();
}
public Section getSection() {
return section;
}
public void setSection(Section section) {
this.section = section;
}
public AssessmentSection getAssessmentSection() {
return assessmentSection;
}
public void setAssessmentSection(AssessmentSection assessmentSection) {
this.assessmentSection = assessmentSection;
}
public TextElement getScoreEl() {
return scoreEl;
}
public void setScoreEl(TextElement scoreEl) {
this.scoreEl = scoreEl;
}
public MultipleSelectionElement getPassedEl() {
return passedEl;
}
public void setPassedEl(MultipleSelectionElement passedEl) {
this.passedEl = passedEl;
}
public FormLink getButton() {
return button;
}
public void setButton(FormLink button) {
this.button = button;
}
}
}