/** * <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.Date; import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.olat.core.commons.modules.bc.meta.MetaInfo; import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.services.image.Size; import org.olat.core.dispatcher.mapper.Mapper; 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.FlexiTableElement; import org.olat.core.gui.components.form.flexible.elements.FormLink; 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.table.DefaultFlexiColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableCssDelegate; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponentDelegate; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; 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.TooledController; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.components.stack.TooledStackedPanel.Align; 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.closablewrapper.CalloutSettings; import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.media.MediaResource; 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.core.util.resource.OresHelper; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; import org.olat.course.nodes.PortfolioCourseNode; import org.olat.modules.portfolio.Binder; import org.olat.modules.portfolio.BinderConfiguration; import org.olat.modules.portfolio.BinderRef; import org.olat.modules.portfolio.BinderSecurityCallback; import org.olat.modules.portfolio.BinderSecurityCallbackFactory; import org.olat.modules.portfolio.PortfolioLoggingAction; import org.olat.modules.portfolio.PortfolioService; import org.olat.modules.portfolio.PortfolioV2Module; import org.olat.modules.portfolio.handler.BinderTemplateResource; import org.olat.modules.portfolio.model.BinderRefImpl; import org.olat.modules.portfolio.model.BinderStatistics; import org.olat.modules.portfolio.model.SynchedBinder; import org.olat.modules.portfolio.ui.BindersDataModel.PortfolioCols; import org.olat.modules.portfolio.ui.event.DeleteBinderEvent; import org.olat.modules.portfolio.ui.event.NewBinderEvent; import org.olat.modules.portfolio.ui.model.BinderRow; import org.olat.modules.portfolio.ui.model.CourseTemplateRow; import org.olat.repository.RepositoryEntry; import org.olat.repository.controllers.ReferencableEntriesSearchController; import org.olat.repository.controllers.RepositorySearchController; import org.olat.repository.controllers.RepositorySearchController.Can; import org.olat.repository.ui.RepositoryTableModel; import org.olat.util.logging.activity.LoggingResourceable; import org.springframework.beans.factory.annotation.Autowired; /** * This is the list of the binders owned by the user. * * Initial date: 07.06.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class BinderListController extends FormBasicController implements Activateable2, TooledController, FlexiTableComponentDelegate { private static final Size BACKGROUND_SIZE = new Size(400, 230, false); private int counter = 1; private Link newBinderLink; private String mapperThumbnailUrl; protected FlexiTableElement tableEl; protected BindersDataModel model; protected final TooledStackedPanel stackPanel; private FormLink newBinderDropdown, newBinderFromCourseButton; protected CloseableModalController cmc; protected BinderController binderCtrl; private BinderMetadataEditController newBinderCtrl; private RepositorySearchController searchTemplateCtrl; private CourseTemplateSearchController searchCourseTemplateCtrl; private NewBinderCalloutController chooseNewBinderTypeCtrl; private CloseableCalloutWindowController newBinderCalloutCtrl; @Autowired private PortfolioV2Module portfolioModule; @Autowired protected PortfolioService portfolioService; public BinderListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel) { super(ureq, wControl, "binder_list"); this.stackPanel = stackPanel; initForm(ureq); loadModel(); } public int getNumOfBinders() { int count = model.getRowCount(); if(portfolioModule.isLearnerCanCreateBinders() || portfolioModule.isCanCreateBindersFromTemplate() || portfolioModule.isCanCreateBindersFromCourse()) { count--; } return count; } public BinderRow getFirstBinder() { if(model.getRowCount() > 0) { return model.getObject(0); } return null; } protected String getTableId() { return "portfolio-list"; } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, PortfolioCols.key, "select")); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(PortfolioCols.title, "select")); model = new BindersDataModel(columnsModel); tableEl = uifactory.addTableElement(getWindowControl(), "table", model, 20, false, getTranslator(), formLayout); tableEl.setAvailableRendererTypes(FlexiTableRendererType.custom); tableEl.setRendererType(FlexiTableRendererType.custom); tableEl.setSearchEnabled(false); tableEl.setCustomizeColumns(false); tableEl.setElementCssClass("o_portfolio_listing"); tableEl.setEmtpyTableMessageKey("table.sEmptyTable"); tableEl.setNumOfRowsEnabled(false); tableEl.setPageSize(24); VelocityContainer row = createVelocityContainer("binder_row"); row.setDomReplacementWrapperRequired(false); // sets its own DOM id in velocity container tableEl.setRowRenderer(row, this); tableEl.setCssDelegate(new BinderCssDelegate()); tableEl.setAndLoadPersistedPreferences(ureq, getTableId()); mapperThumbnailUrl = registerCacheableMapper(ureq, "binder-list", new ImageMapper(model)); row.contextPut("mapperThumbnailUrl", mapperThumbnailUrl); if(!portfolioModule.isLearnerCanCreateBinders() && !portfolioModule.isCanCreateBindersFromTemplate() && portfolioModule.isCanCreateBindersFromCourse()) { newBinderFromCourseButton = uifactory.addFormLink("create.binder.from.course", "create.empty.binder.from.course", null, formLayout, Link.BUTTON); row.put("createBinderFromCourse", newBinderFromCourseButton.getComponent()); } else if(portfolioModule.isLearnerCanCreateBinders() || portfolioModule.isCanCreateBindersFromTemplate() || portfolioModule.isCanCreateBindersFromCourse()) { newBinderDropdown = uifactory.addFormLink("create.binders", "create.new.binder", null, formLayout, Link.BUTTON); int count = (portfolioModule.isLearnerCanCreateBinders() ? 1 : 0) + (portfolioModule.isCanCreateBindersFromTemplate() ? 1 : 0) + (portfolioModule.isCanCreateBindersFromCourse() ? 1 :0); if(count > 1) { newBinderDropdown.setIconRightCSS("o_icon o_icon_caret"); } row.put("createDropdown", newBinderDropdown.getComponent()); } } @Override public Iterable<Component> getComponents(int row, Object rowObject) { BinderRow elRow = model.getObject(row); List<Component> components = new ArrayList<>(2); if(elRow.getOpenLink() != null) { components.add(elRow.getOpenLink().getComponent()); } return components; } @Override public void initTools() { if(portfolioModule.isLearnerCanCreateBinders() || portfolioModule.isCanCreateBindersFromTemplate() || portfolioModule.isCanCreateBindersFromCourse()) { newBinderLink = LinkFactory.createToolLink("create.new.binder", translate("create.new.binder"), this); newBinderLink.setIconLeftCSS("o_icon o_icon-lg o_icon_new_portfolio"); newBinderLink.setElementCssClass("o_sel_pf_new_binder"); stackPanel.addTool(newBinderLink, Align.right); } } @Override protected void doDispose() { // } protected void loadModel() { List<BinderStatistics> binderRows = portfolioService.searchOwnedBinders(getIdentity()); List<BinderRow> rows = new ArrayList<>(binderRows.size()); for(BinderStatistics binderRow:binderRows) { rows.add(forgePortfolioRow(binderRow)); } if(portfolioModule.isLearnerCanCreateBinders() || portfolioModule.isCanCreateBindersFromTemplate() || portfolioModule.isCanCreateBindersFromCourse()) { rows.add(new BinderRow()); } model.setObjects(rows); tableEl.reset(); tableEl.reloadData(); } protected BinderRow forgePortfolioRow(BinderStatistics binderRow) { String openLinkId = "open_" + (++counter); FormLink openLink = uifactory.addFormLink(openLinkId, "open", "open", null, flc, Link.LINK); openLink.setIconRightCSS("o_icon o_icon_start"); VFSLeaf image = portfolioService.getPosterImageLeaf(binderRow); BinderRow row = new BinderRow(binderRow, image, openLink); openLink.setUserObject(row); return row; } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { if(entries == null || entries.isEmpty()) return; String resName = entries.get(0).getOLATResourceable().getResourceableTypeName(); if("Binder".equalsIgnoreCase(resName)) { Long portfolioKey = entries.get(0).getOLATResourceable().getResourceableId(); Activateable2 activateable = doOpenBinder(ureq, new BinderRefImpl(portfolioKey)); if(activateable != null) { List<ContextEntry> subEntries = entries.subList(1, entries.size()); activateable.activate(ureq, subEntries, entries.get(0).getTransientState()); } } } @Override public void event(UserRequest ureq, Component source, Event event) { if(newBinderLink == source) { if(portfolioModule.isLearnerCanCreateBinders() || portfolioModule.isCanCreateBindersFromTemplate() || portfolioModule.isCanCreateBindersFromCourse()) { doNewBinder(ureq); } } super.event(ureq, source, event); } @Override protected void event(UserRequest ureq, Controller source, Event event) { if(newBinderCtrl == source) { if(event == Event.DONE_EVENT) { loadModel(); doOpenBinder(ureq, newBinderCtrl.getBinder()).activate(ureq, null, null); } cmc.deactivate(); cleanUp(); } else if(chooseNewBinderTypeCtrl == source) { newBinderCalloutCtrl.deactivate(); cleanUp(); if(event instanceof NewBinderEvent) { String cmd = event.getCommand(); if(NewBinderEvent.NEW_EMPTY.equals(cmd)) { doNewBinder(ureq); } else if(NewBinderEvent.NEW_EMPTY_FROM_TEMPLATE.equals(cmd)) { doNewBinderFromTemplate(ureq); } else if(NewBinderEvent.NEW_EMPTY_FROM_COURSE.equals(cmd)) { doNewBinderFromCourse(ureq); } } } else if(searchTemplateCtrl == source) { if(RepositoryTableModel.TABLE_ACTION_SELECT_LINK.equals(event.getCommand())) { RepositoryEntry repoEntry = searchTemplateCtrl.getSelectedEntry(); doCreateBinderFromTemplate(ureq, repoEntry); } cmc.deactivate(); cleanUp(); } else if(searchCourseTemplateCtrl == source) { if(event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { CourseTemplateRow templateRow = searchCourseTemplateCtrl.getSelectedEntry(); doCreateBinderFromCourseTemplate(ureq, templateRow); } cmc.deactivate(); cleanUp(); } else if(newBinderCalloutCtrl == source) { cleanUp(); } else if(binderCtrl == source) { if(event instanceof DeleteBinderEvent) { stackPanel.popUpToController(this); loadModel(); } } else if(cmc == source) { cleanUp(); } super.event(ureq, source, event); } protected void cleanUp() { removeAsListenerAndDispose(searchCourseTemplateCtrl); removeAsListenerAndDispose(chooseNewBinderTypeCtrl); removeAsListenerAndDispose(newBinderCalloutCtrl); removeAsListenerAndDispose(searchTemplateCtrl); removeAsListenerAndDispose(newBinderCtrl); removeAsListenerAndDispose(cmc); searchCourseTemplateCtrl = null; chooseNewBinderTypeCtrl = null; newBinderCalloutCtrl = null; searchTemplateCtrl = null; newBinderCtrl = null; cmc = null; } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(newBinderDropdown == source) { doNewBinderCallout(ureq); } else if(newBinderFromCourseButton == source) { doNewBinderFromCourse(ureq); } else if(tableEl == source) { if(event instanceof SelectionEvent) { SelectionEvent se = (SelectionEvent)event; String cmd = se.getCommand(); if("select".equals(cmd)) { BinderRow row = model.getObject(se.getIndex()); Activateable2 activateable = doOpenBinder(ureq, row); if(activateable != null) { activateable.activate(ureq, null, null); } } } } else if(source instanceof FormLink) { FormLink link = (FormLink)source; String cmd = link.getCmd(); if("open".equals(cmd)) { BinderRow row = (BinderRow)link.getUserObject(); Activateable2 activateable = doOpenBinder(ureq, row); if(activateable != null) { activateable.activate(ureq, null, null); } } } super.formInnerEvent(ureq, source, event); } @Override protected void formOK(UserRequest ureq) { // } protected List<BinderRow> getSelectedRows() { Set<Integer> indexes = tableEl.getMultiSelectedIndex(); List<BinderRow> selectedRows = new ArrayList<>(indexes.size()); for(Integer index:indexes) { BinderRow row = model.getObject(index.intValue()); selectedRows.add(row); } return selectedRows; } protected BinderController doOpenBinder(UserRequest ureq, BinderRef row) { SynchedBinder binder = portfolioService.loadAndSyncBinder(row); if(binder.isChanged()) { showInfo("warning.binder.synched"); } BinderController binderCtrl = doOpenBinder(ureq, binder.getBinder()); if(row instanceof BinderRow) { VFSLeaf image = portfolioService.getPosterImageLeaf(binder.getBinder()); ((BinderRow)row).setBackgroundImage(image); } return binderCtrl; } protected BinderController doOpenBinder(UserRequest ureq, Binder binder) { if(binder == null) { showWarning("warning.portfolio.not.found"); return null; } else { removeAsListenerAndDispose(binderCtrl); portfolioService.updateBinderUserInformations(binder, getIdentity()); OLATResourceable binderOres = OresHelper.createOLATResourceableInstance("Binder", binder.getKey()); WindowControl swControl = addToHistory(ureq, binderOres, null); BinderSecurityCallback secCallback = BinderSecurityCallbackFactory.getCallbackForOwnedBinder(binder); BinderConfiguration config = BinderConfiguration.createConfig(binder); binderCtrl = new BinderController(ureq, swControl, stackPanel, secCallback, binder, config); listenTo(binderCtrl); stackPanel.pushController(binder.getTitle(), binderCtrl); return binderCtrl; } } private void doNewBinderCallout(UserRequest ureq) { // short cut if only one option is selected if(portfolioModule.isLearnerCanCreateBinders() && !portfolioModule.isCanCreateBindersFromTemplate() && !portfolioModule.isCanCreateBindersFromCourse()) { doNewBinder(ureq); } else if(!portfolioModule.isLearnerCanCreateBinders() && portfolioModule.isCanCreateBindersFromTemplate() && !portfolioModule.isCanCreateBindersFromCourse()) { doNewBinderFromTemplate(ureq); } else if(!portfolioModule.isLearnerCanCreateBinders() && !portfolioModule.isCanCreateBindersFromTemplate() && portfolioModule.isCanCreateBindersFromCourse()) { doNewBinderFromCourse(ureq); } else if(chooseNewBinderTypeCtrl == null) { chooseNewBinderTypeCtrl = new NewBinderCalloutController(ureq, getWindowControl()); listenTo(chooseNewBinderTypeCtrl); newBinderCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), chooseNewBinderTypeCtrl.getInitialComponent(), newBinderDropdown.getFormDispatchId(), "", true, "", new CalloutSettings(false)); listenTo(newBinderCalloutCtrl); newBinderCalloutCtrl.activate(); } } private void doNewBinder(UserRequest ureq) { if(newBinderCtrl != null) return; newBinderCtrl = new BinderMetadataEditController(ureq, getWindowControl(), null); listenTo(newBinderCtrl); String title = translate("create.new.binder"); cmc = new CloseableModalController(getWindowControl(), null, newBinderCtrl.getInitialComponent(), true, title, true); listenTo(cmc); cmc.activate(); } private void doNewBinderFromTemplate(UserRequest ureq) { if(searchTemplateCtrl != null) return; String title = translate("create.empty.binder.from.template"); String commandLabel = translate("create.binder.selectTemplate"); removeAsListenerAndDispose(searchTemplateCtrl); searchTemplateCtrl = new RepositorySearchController(commandLabel, ureq, getWindowControl(), false, false, new String[]{ BinderTemplateResource.TYPE_NAME }, null); searchTemplateCtrl.enableSearchforAllXXAbleInSearchForm(Can.all); searchTemplateCtrl.doSearchByTypeLimitAccess(new String[]{ BinderTemplateResource.TYPE_NAME }, ureq); listenTo(searchTemplateCtrl); cmc = new CloseableModalController(getWindowControl(), title, searchTemplateCtrl.getInitialComponent(), true, title); listenTo(cmc); cmc.activate(); } private void doCreateBinderFromTemplate(UserRequest ureq, RepositoryEntry entry) { Binder templateBinder = portfolioService.getBinderByResource(entry.getOlatResource()); Binder newBinder = portfolioService.assignBinder(getIdentity(), templateBinder, null, null, null); DBFactory.getInstance().commit(); SynchedBinder synchedBinder = portfolioService.loadAndSyncBinder(newBinder); newBinder = synchedBinder.getBinder(); doOpenBinder(ureq, newBinder).activate(ureq, null, null); } private void doNewBinderFromCourse(UserRequest ureq) { if(searchCourseTemplateCtrl != null) return; removeAsListenerAndDispose(searchCourseTemplateCtrl); searchCourseTemplateCtrl = new CourseTemplateSearchController(ureq, getWindowControl()); listenTo(searchCourseTemplateCtrl); String title = translate("create.empty.binder.from.template"); cmc = new CloseableModalController(getWindowControl(), title, searchCourseTemplateCtrl.getInitialComponent(), true, title); listenTo(cmc); cmc.activate(); } private void doCreateBinderFromCourseTemplate(UserRequest ureq, CourseTemplateRow row) { RepositoryEntry courseEntry = row.getCourseEntry(); RepositoryEntry templateEntry = row.getTemplateEntry(); PortfolioCourseNode courseNode = row.getCourseNode(); Binder templateBinder = portfolioService.getBinderByResource(templateEntry.getOlatResource()); Binder copyBinder = portfolioService.getBinder(getIdentity(), templateBinder, courseEntry, courseNode.getIdent()); if(copyBinder == null) { Date deadline = courseNode.getDeadline(); copyBinder = portfolioService.assignBinder(getIdentity(), templateBinder, courseEntry, courseNode.getIdent(), deadline); DBFactory.getInstance().commit(); SynchedBinder synchedBinder = portfolioService.loadAndSyncBinder(copyBinder); copyBinder = synchedBinder.getBinder(); if(copyBinder != null) { showInfo("map.copied", StringHelper.escapeHtml(templateBinder.getTitle())); ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(copyBinder)); ThreadLocalUserActivityLogger.log(PortfolioLoggingAction.PORTFOLIO_TASK_STARTED, getClass()); } } doOpenBinder(ureq, copyBinder).activate(ureq, null, null); } public static class ImageMapper implements Mapper { private final BindersDataModel binderModel; public ImageMapper(BindersDataModel model) { this.binderModel = model; } @Override public MediaResource handle(String relPath, HttpServletRequest request) { String row = relPath; if(row.startsWith("/")) { row = row.substring(1, row.length()); } int index = row.indexOf("/"); if(index > 0) { row = row.substring(0, index); Long key = new Long(row); List<BinderRow> rows = binderModel.getObjects(); for(BinderRow prow:rows) { if(key.equals(prow.getKey())) { VFSLeaf image = prow.getBackgroundImage(); if(image instanceof MetaTagged) { MetaInfo info = ((MetaTagged)image).getMetaInfo(); VFSLeaf thumbnail = info.getThumbnail(BACKGROUND_SIZE.getWidth(), BACKGROUND_SIZE.getHeight(), true); if(thumbnail != null) { image = thumbnail; } } return new VFSMediaResource(image); } } } return null; } } private static class BinderCssDelegate extends DefaultFlexiTableCssDelegate { @Override public String getRowCssClass(FlexiTableRendererType type, int pos) { return "o_portfolio_entry"; } } }