/** * <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.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.olat.core.commons.persistence.SortKey; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; 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.FlexiTableSortOptions; import org.olat.core.gui.components.form.flexible.elements.FormLink; 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.table.FlexiTableRenderEvent; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableRendererType; import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.components.stack.TooledStackedPanel.Align; 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.id.OLATResourceable; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; import org.olat.modules.portfolio.AssessmentSection; import org.olat.modules.portfolio.Assignment; import org.olat.modules.portfolio.Binder; import org.olat.modules.portfolio.BinderConfiguration; import org.olat.modules.portfolio.BinderSecurityCallback; import org.olat.modules.portfolio.Category; import org.olat.modules.portfolio.CategoryToElement; import org.olat.modules.portfolio.Page; import org.olat.modules.portfolio.PortfolioLoggingAction; import org.olat.modules.portfolio.PortfolioRoles; import org.olat.modules.portfolio.Section; import org.olat.modules.portfolio.ui.component.TimelinePoint; import org.olat.modules.portfolio.ui.model.PortfolioElementRow; import org.olat.user.UserManager; import org.olat.util.logging.activity.LoggingResourceable; import org.springframework.beans.factory.annotation.Autowired; /** * * Initial date: 07.06.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class BinderPageListController extends AbstractPageListController { private Link newSectionLink, newEntryLink, newAssignmentLink; private FormLink newSectionButton, previousSectionLink, nextSectionLink, showAllSectionsLink; private CloseableModalController cmc; private SectionEditController newSectionCtrl; private PageMetadataEditController newPageCtrl; private AssignmentEditController newAssignmentCtrl; private final Binder binder; private final List<Identity> owners; private Section filteringSection; @Autowired private UserManager userManager; public BinderPageListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, BinderSecurityCallback secCallback, Binder binder, BinderConfiguration config) { super(ureq, wControl, stackPanel, secCallback, config, "binder_pages", true); this.binder = binder; owners = portfolioService.getMembers(binder, PortfolioRoles.owner.name()); initForm(ureq); loadModel(ureq, null); if (secCallback.canNewAssignment()) { // in template mode, add editor class to toolbar initialPanel.setCssClass("o_edit_mode"); } } @Override public void initTools() { if(secCallback.canAddSection()) { newSectionLink = LinkFactory.createToolLink("new.section", translate("create.new.section"), this); newSectionLink.setIconLeftCSS("o_icon o_icon-lg o_icon_new_portfolio"); newSectionLink.setElementCssClass("o_sel_pf_new_section"); stackPanel.addTool(newSectionLink, Align.right); } if(secCallback.canAddPage(null)) { newEntryLink = LinkFactory.createToolLink("new.page", translate("create.new.page"), this); newEntryLink.setIconLeftCSS("o_icon o_icon-lg o_icon_new_portfolio"); newEntryLink.setElementCssClass("o_sel_pf_new_entry"); newEntryLink.setVisible(model.getRowCount() > 0); stackPanel.addTool(newEntryLink, Align.right); } if(secCallback.canNewAssignment()) { newAssignmentLink = LinkFactory.createToolLink("new.assignment", translate("create.new.assignment"), this); newAssignmentLink.setIconLeftCSS("o_icon o_icon-lg o_icon_new_portfolio"); newAssignmentLink.setElementCssClass("o_sel_pf_new_assignment"); newAssignmentLink.setVisible(model.getRowCount() > 0); stackPanel.addTool(newAssignmentLink, Align.right); } } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { super.initForm(formLayout, listener, ureq); if(formLayout instanceof FormLayoutContainer) { FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout; StringBuilder ownerSb = new StringBuilder(); for(Identity owner:owners) { if(ownerSb.length() > 0) ownerSb.append(", "); ownerSb.append(userManager.getUserDisplayName(owner)); } layoutCont.contextPut("owners", ownerSb.toString()); layoutCont.contextPut("binderKey", binder.getKey()); layoutCont.contextPut("binderTitle", StringHelper.escapeHtml(binder.getTitle())); } FlexiTableSortOptions options = new FlexiTableSortOptions(); options.setFromColumnModel(false); options.setDefaultOrderBy(new SortKey(null, false)); tableEl.setSortSettings(options); previousSectionLink = uifactory.addFormLink("section.paging.previous", formLayout, Link.BUTTON | Link.NONTRANSLATED); previousSectionLink.setVisible(false); previousSectionLink.setIconLeftCSS("o_icon o_icon_move_left"); nextSectionLink = uifactory.addFormLink("section.paging.next", formLayout, Link.BUTTON | Link.NONTRANSLATED); nextSectionLink.setVisible(false); nextSectionLink.setIconRightCSS("o_icon o_icon_move_right"); showAllSectionsLink = uifactory.addFormLink("section.paging.all", formLayout, Link.BUTTON); showAllSectionsLink.setVisible(false); if(secCallback.canAddSection()) { newSectionButton = uifactory.addFormLink("create.new.section", formLayout, Link.BUTTON); newSectionButton.setCustomEnabledLinkCSS("btn btn-primary o_sel_pf_new_section"); } } @Override public int getNumOfPages() { int countPages = 0; if(model != null) { List<PortfolioElementRow> rows = model.getObjects(); for(PortfolioElementRow row:rows) { if(row.isPage()) { countPages++; } } } return countPages; } @Override protected void loadModel(UserRequest ureq, String searchString) { List<Section> sections = portfolioService.getSections(binder); List<CategoryToElement> categorizedElements = portfolioService.getCategorizedSectionsAndPages(binder); Map<OLATResourceable,List<Category>> categorizedElementMap = new HashMap<>(); Map<Section,Set<String>> sectionAggregatedCategoriesMap = new HashMap<>(); for(CategoryToElement categorizedElement:categorizedElements) { List<Category> categories = categorizedElementMap.get(categorizedElement.getCategorizedResource()); if(categories == null) { categories = new ArrayList<>(); categorizedElementMap.put(categorizedElement.getCategorizedResource(), categories); } categories.add(categorizedElement.getCategory()); } //comments Map<Long,Long> numberOfCommentsMap = portfolioService.getNumberOfComments(binder); //assessment sections List<AssessmentSection> assessmentSections = portfolioService.getAssessmentSections(binder, getIdentity()); Map<Section,AssessmentSection> sectionToAssessmentSectionMap = assessmentSections.stream() .collect(Collectors.toMap(as -> as.getSection(), as -> as)); List<PortfolioElementRow> rows = new ArrayList<>(); //assignments List<Assignment> assignments = portfolioService.getAssignments(binder, searchString); Map<Section,List<Assignment>> sectionToAssignmentMap = new HashMap<>(); for(Assignment assignment:assignments) { List<Assignment> assignmentList; Section section = assignment.getSection(); if(sectionToAssignmentMap.containsKey(section)) { assignmentList = sectionToAssignmentMap.get(section); } else { assignmentList = new ArrayList<>(); sectionToAssignmentMap.put(section, assignmentList); } assignmentList.add(assignment); } for(Assignment assignment:assignments) { Section section = assignment.getSection(); if(assignment.getPage() == null && secCallback.canViewPendingAssignments(section)) { List<Assignment> sectionAssignments = sectionToAssignmentMap.get(section); PortfolioElementRow row = forgePendingAssignmentRow(assignment, section, sectionAssignments); rows.add(row); if(secCallback.canAddPage(section)) { FormLink newEntryButton = uifactory.addFormLink("new.entry." + (++counter), "new.entry", "create.new.page", null, flc, Link.BUTTON); newEntryButton.setCustomEnabledLinkCSS("btn btn-primary o_sel_pf_new_entry"); newEntryButton.setUserObject(row); row.setNewEntryLink(newEntryButton); } } } List<Page> pages = portfolioService.getPages(binder, searchString); for (Page page : pages) { if(!secCallback.canViewElement(page)) { continue; } Section section = page.getSection(); PortfolioElementRow pageRow = forgePageRow(ureq, page, sectionToAssessmentSectionMap.get(section), sectionToAssignmentMap.get(section), categorizedElementMap, numberOfCommentsMap); rows.add(pageRow); if(secCallback.canAddPage(section)) { FormLink newEntryButton = uifactory.addFormLink("new.entry." + (++counter), "new.entry", "create.new.page", null, flc, Link.BUTTON); newEntryButton.setCustomEnabledLinkCSS("btn btn-primary o_sel_pf_new_entry"); newEntryButton.setUserObject(pageRow); pageRow.setNewEntryLink(newEntryButton); } if(secCallback.canNewAssignment() && section != null) { FormLink newAssignmentButton = uifactory.addFormLink("new.assignment." + (++counter), "new.assignment", "create.new.assignment", null, flc, Link.BUTTON); newAssignmentButton.setCustomEnabledLinkCSS("btn btn-primary o_sel_pf_new_assignment"); newAssignmentButton.setUserObject(pageRow); pageRow.setNewAssignmentLink(newAssignmentButton); } if(section != null) { Set<String> categories = sectionAggregatedCategoriesMap.get(section); if(categories == null) { categories = new HashSet<>(); sectionAggregatedCategoriesMap.put(section, categories); } if(pageRow.getPageCategories() != null && pageRow.getPageCategories().size() > 0) { categories.addAll(pageRow.getPageCategories()); } pageRow.setSectionCategories(categories); } } //sections without pages if(!StringHelper.containsNonWhitespace(searchString)) { for(Section section:sections) { if(!secCallback.canViewElement(section)) { continue; } PortfolioElementRow sectionRow = forgeSectionRow(section, sectionToAssessmentSectionMap.get(section), sectionToAssignmentMap.get(section), categorizedElementMap); rows.add(sectionRow); if(secCallback.canAddPage(section)) { FormLink newEntryButton = uifactory.addFormLink("new.entry." + (++counter), "new.entry", "create.new.page", null, flc, Link.BUTTON); newEntryButton.setCustomEnabledLinkCSS("btn btn-primary o_sel_pf_new_entry"); newEntryButton.setUserObject(sectionRow); sectionRow.setNewEntryLink(newEntryButton); } if(secCallback.canNewAssignment() && section != null) { FormLink newAssignmentButton = uifactory.addFormLink("new.assignment." + (++counter), "new.assignment", "create.new.assignment", null, flc, Link.BUTTON); newAssignmentButton.setCustomEnabledLinkCSS("btn btn-primary o_sel_pf_new_assignment"); newAssignmentButton.setUserObject(sectionRow); sectionRow.setNewAssignmentLink(newAssignmentButton); } } } if(newSectionButton != null && rows.isEmpty()) { flc.add("create.new.section", newSectionButton); } else if (newSectionButton != null) { flc.remove(newSectionButton); } if(newEntryLink != null && !newEntryLink.isVisible()) { newEntryLink.setVisible(rows.size() > 0); stackPanel.setDirty(true); } if(newAssignmentLink != null && !newAssignmentLink.isVisible()) { newAssignmentLink.setVisible(rows.size() > 0); stackPanel.setDirty(true); } disposeRows();//clean up the posters model.setObjects(rows); if(filteringSection != null) { doFilterSection(filteringSection); } else { tableEl.reloadData(); updateTimeline(); } } private void updateTimeline() { List<PortfolioElementRow> pages = model.getObjects(); List<TimelinePoint> points = new ArrayList<>(pages.size()); for(PortfolioElementRow page:pages) { if(page.isPage()) { String s = page.getPageStatus() == null ? "draft" : page.getPageStatus().name(); points.add(new TimelinePoint(page.getKey().toString(), page.getTitle(), page.getCreationDate(), s)); } } timelineEl.setPoints(points); } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(tableEl == source) { if(event instanceof FlexiTableRenderEvent) { FlexiTableRenderEvent re = (FlexiTableRenderEvent)event; if(re.getRendererType() == FlexiTableRendererType.custom) { tableEl.sort(new SortKey(null, false)); } } else if(event instanceof SelectionEvent) { SelectionEvent se = (SelectionEvent)event; String cmd = se.getCommand(); if("select-page".equals(cmd)) { PortfolioElementRow row = model.getObject(se.getIndex()); if(row.isPendingAssignment()) { doStartAssignment(ureq, row); } else { doOpenRow(ureq, row, false); } } } } else if(previousSectionLink == source) { Section previousSection = (Section)previousSectionLink.getUserObject(); doFilterSection(previousSection); } else if(nextSectionLink == source) { Section nextSection = (Section)nextSectionLink.getUserObject(); doFilterSection(nextSection); } else if(showAllSectionsLink == source) { doShowAll(); } else if(newSectionButton == source) { doCreateNewSection(ureq); } else if(source instanceof FormLink) { FormLink link = (FormLink)source; String cmd = link.getCmd(); if("new.entry".equals(cmd)) { PortfolioElementRow row = (PortfolioElementRow)link.getUserObject(); doCreateNewPage(ureq, row.getSection()); } else if("new.assignment".equals(cmd)) { PortfolioElementRow row = (PortfolioElementRow)link.getUserObject(); doCreateNewAssignment(ureq, row.getSection()); } } super.formInnerEvent(ureq, source, event); } @Override public void event(UserRequest ureq, Component source, Event event) { if(newEntryLink == source) { doCreateNewPage(ureq, filteringSection); } else if(newSectionLink == source) { doCreateNewSection(ureq); } else if(newAssignmentLink == source) { if(filteringSection == null) { doCreateNewAssignment(ureq); } else { doCreateNewAssignment(ureq, filteringSection); } } super.event(ureq, source, event); } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { super.activate(ureq, entries, state); if(entries == null || entries.isEmpty()) return; String resName = entries.get(0).getOLATResourceable().getResourceableTypeName(); if("Section".equalsIgnoreCase(resName)) { Long resId = entries.get(0).getOLATResourceable().getResourceableId(); PortfolioElementRow activatedRow = null; for(PortfolioElementRow row :model.getObjects()) { if(row.getSection() != null && row.getSection().getKey().equals(resId)) { activatedRow = row; break; } } if(activatedRow != null) { doFilterSection(activatedRow.getSection()); } } } @Override public void event(UserRequest ureq, Controller source, Event event) { if(newSectionCtrl == source) { if(event == Event.DONE_EVENT) { filteringSection = newSectionCtrl.getSection(); loadModel(ureq, null); fireEvent(ureq, Event.CHANGED_EVENT); } cmc.deactivate(); cleanUp(); } else if(newPageCtrl == source) { if(event == Event.DONE_EVENT) { loadModel(ureq, null); doOpenPage(ureq, newPageCtrl.getPage(), true); fireEvent(ureq, Event.CHANGED_EVENT); } cmc.deactivate(); cleanUp(); } else if(newAssignmentCtrl == source) { if(event == Event.DONE_EVENT) { loadModel(ureq, null); fireEvent(ureq, Event.CHANGED_EVENT); } cmc.deactivate(); cleanUp(); } else if(cmc == source) { cleanUp(); } super.event(ureq, source, event); } private void cleanUp() { removeAsListenerAndDispose(newAssignmentCtrl); removeAsListenerAndDispose(newSectionCtrl); removeAsListenerAndDispose(newPageCtrl); removeAsListenerAndDispose(cmc); newAssignmentCtrl = null; newSectionCtrl = null; newPageCtrl = null; cmc = null; } private void doShowAll() { this.filteringSection = null; model.filter(null); tableEl.reloadData(); updateTimeline(); previousSectionLink.setVisible(false); nextSectionLink.setVisible(false); showAllSectionsLink.setVisible(false); } protected void doFilterSection(Section section) { this.filteringSection = section; List<Section> currentSections = model.filter(section); tableEl.reloadData(); updateTimeline(); int index = currentSections.indexOf(section); previousSectionLink.setEnabled(index > 0); if(index > 0) { String previousTitle = currentSections.get(index - 1).getTitle(); previousSectionLink.setI18nKey(translate("section.paging.with.title", new String[]{ previousTitle })); previousSectionLink.setUserObject(currentSections.get(index - 1)); } else { previousSectionLink.setI18nKey(translate("section.paging.previous")); } if(index >= 0 && index + 1 < currentSections.size()) { String nextTitle = currentSections.get(index + 1).getTitle(); nextSectionLink.setI18nKey(translate("section.paging.with.title", new String[]{ nextTitle })); nextSectionLink.setEnabled(true); nextSectionLink.setUserObject(currentSections.get(index + 1)); } else { nextSectionLink.setI18nKey(translate("section.paging.next")); nextSectionLink.setEnabled(false); } boolean visible = currentSections.size() > 1; previousSectionLink.setVisible(visible); nextSectionLink.setVisible(visible); showAllSectionsLink.setVisible(visible); flc.setDirty(true); } private void doCreateNewSection(UserRequest ureq) { if(newSectionCtrl != null) return; newSectionCtrl = new SectionEditController(ureq, getWindowControl(), binder, secCallback); listenTo(newSectionCtrl); String title = translate("create.new.section"); cmc = new CloseableModalController(getWindowControl(), null, newSectionCtrl.getInitialComponent(), true, title, true); listenTo(cmc); cmc.activate(); } private void doCreateNewPage(UserRequest ureq, Section preSelectedSection) { if(newPageCtrl != null) return; newPageCtrl = new PageMetadataEditController(ureq, getWindowControl(), binder, false, preSelectedSection, true); listenTo(newPageCtrl); String title = translate("create.new.page"); cmc = new CloseableModalController(getWindowControl(), null, newPageCtrl.getInitialComponent(), true, title, true); listenTo(cmc); cmc.activate(); } private void doCreateNewAssignment(UserRequest ureq) { if(newAssignmentCtrl != null) return; newAssignmentCtrl = new AssignmentEditController(ureq, getWindowControl(), binder); listenTo(newAssignmentCtrl); String title = translate("create.new.assignment"); cmc = new CloseableModalController(getWindowControl(), null, newAssignmentCtrl.getInitialComponent(), true, title, true); listenTo(cmc); cmc.activate(); } private void doCreateNewAssignment(UserRequest ureq, Section section) { if(newAssignmentCtrl != null) return; newAssignmentCtrl = new AssignmentEditController(ureq, getWindowControl(), section); listenTo(newAssignmentCtrl); String title = translate("create.new.assignment"); cmc = new CloseableModalController(getWindowControl(), null, newAssignmentCtrl.getInitialComponent(), true, title, true); listenTo(cmc); cmc.activate(); } @Override protected void doOpenRow(UserRequest ureq, PortfolioElementRow row, boolean newElement) { if(row.isSection()) { doFilterSection(row.getSection()); } else { super.doOpenRow(ureq, row, newElement); } } @Override protected Assignment doStartAssignment(UserRequest ureq, PortfolioElementRow row) { if(secCallback.canInstantiateAssignment()) { Assignment startedAssigment = super.doStartAssignment(ureq, row); ThreadLocalUserActivityLogger.log(PortfolioLoggingAction.PORTFOLIO_ASSIGNMENT_STARTED, getClass(), LoggingResourceable.wrap(row.getSection()), LoggingResourceable.wrap(startedAssigment)); return startedAssigment; } else if(secCallback.canNewAssignment()) { doEditAssignment(ureq, row); } return null; } }