SEBSERV-28 # first implementation with sort and navigation

still left : filtering
This commit is contained in:
anhefti 2019-02-05 16:45:41 +01:00
parent 94a75735ee
commit 8f10dd6163
28 changed files with 876 additions and 59 deletions

View file

@ -16,6 +16,7 @@ public final class Constants {
public static final Character LIST_SEPARATOR_CHAR = ',';
public static final String LIST_SEPARATOR = ",";
public static final String EMPTY_NOTE = "--";
/** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */
public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat

View file

@ -22,4 +22,6 @@ public class SEBServerRestEndpoints {
public static final String ENDPOINT_USER_ACTIVITY_LOG = "/useractivity";
public static final String NAMES_ENDPOINT_SUFFIX = "/names";
}

View file

@ -51,16 +51,16 @@ public final class Page<T> {
this.sort = sort;
}
public Integer getNumberOfPages() {
return this.numberOfPages;
public int getNumberOfPages() {
return (this.numberOfPages != null) ? this.numberOfPages : 1;
}
public Integer getPageNumber() {
return this.pageNumber;
public int getPageNumber() {
return (this.pageNumber != null) ? this.pageNumber : 0;
}
public Integer getPageSize() {
return this.pageSize;
public int getPageSize() {
return (this.pageSize != null) ? this.pageSize : -1;
}
public Collection<T> getContent() {

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
public final class RWTUtils {
public static final String TEXT_NAME_H2 = "h2";
public static void clearComposite(final Composite parent) {
if (parent == null) {
return;
}
for (final Control control : parent.getChildren()) {
control.dispose();
}
}
}

View file

@ -16,21 +16,22 @@ public interface PolyglotPageService {
String POLYGLOT_WIDGET_FUNCTION_KEY = "POLYGLOT_WIDGET_FUNCTION";
String POLYGLOT_TREE_ITEM_TEXT_DATA_KEY = "POLYGLOT_TREE_ITEM_TEXT_DATA";
String POLYGLOT_TREE_ITEM_TOOLTIP_DATA_KEY = "POLYGLOT_TREE_ITEM_TOOLTIP_DATA";
/** Gets the underling I18nSupport
*
*
* @return the underling I18nSupport */
I18nSupport getI18nSupport();
/** The default locale for the page.
* Uses I18nSupport.getCurrentLocale to do so.
*
*
* @param root the root Composite of the page to change the language */
void setDefaultPageLocale(Composite root);
/** Sets the given Locale and if needed, updates the page language according to the
* given Locale
*
*
* @param root root the root Composite of the page to change the language
* @param locale the Locale to set */
void setPageLocale(Composite root, Locale locale);

View file

@ -10,10 +10,10 @@ package ch.ethz.seb.sebserver.gui.service.page;
public interface TemplateComposer {
default boolean validate(final PageContext context) {
default boolean validate(final PageContext pageContext) {
return true;
}
void compose(PageContext composerCtx);
void compose(PageContext pageContext);
}

View file

@ -17,6 +17,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.action.ActionPane;
import ch.ethz.seb.sebserver.gui.service.page.content.InstitutionList;
import ch.ethz.seb.sebserver.gui.service.page.impl.TODOTemplate;
public class ActivitySelection {
@ -31,7 +32,7 @@ public class ActivitySelection {
public enum Activity {
NONE(TODOTemplate.class, TODOTemplate.class, (String) null),
INSTITUTION_ROOT(
TODOTemplate.class,
InstitutionList.class,
ActionPane.class,
new LocTextKey("sebserver.activities.inst")),
INSTITUTION_NODE(

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.page.content;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutions;
import ch.ethz.seb.sebserver.gui.service.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class InstitutionList implements TemplateComposer {
private final WidgetFactory widgetFactory;
private final RestService restService;
protected InstitutionList(
final WidgetFactory widgetFactory,
final RestService restService) {
this.widgetFactory = widgetFactory;
this.restService = restService;
}
@Override
public void compose(final PageContext pageContext) {
final Composite content = new Composite(pageContext.getParent(), SWT.NONE);
final GridLayout contentLayout = new GridLayout();
contentLayout.marginLeft = 10;
content.setLayout(contentLayout);
content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
this.widgetFactory.labelLocalizedTitle(
content,
new LocTextKey("sebserver.institution.list.title"));
this.widgetFactory.entityTableBuilder(this.restService.getRestCall(GetInstitutions.class))
.withPaging(3)
.withColumn(new ColumnDefinition<>(
Domain.INSTITUTION.ATTR_NAME,
new LocTextKey("sebserver.institution.list.column.name"),
null,
0,
entity -> entity.name,
null,
true))
.withColumn(new ColumnDefinition<>(
Domain.INSTITUTION.ATTR_URL_SUFFIX,
new LocTextKey("sebserver.institution.list.column.urlSuffix"),
null,
0,
entity -> entity.urlSuffix,
null,
true))
.withColumn(new ColumnDefinition<>(
Domain.INSTITUTION.ATTR_ACTIVE,
new LocTextKey("sebserver.institution.list.column.active"),
null,
0,
entity -> entity.active,
null,
true))
.compose(content);
}
}

View file

@ -14,13 +14,13 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.RWTUtils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
@ -108,7 +108,7 @@ public class ComposerServiceImpl implements ComposerService {
if (composer.validate(pageContext)) {
clear(pageContext.getParent());
RWTUtils.clearComposite(pageContext.getParent());
try {
composer.compose(pageContext);
@ -173,14 +173,4 @@ public class ComposerServiceImpl implements ComposerService {
this.i18nSupport, this, root, root, null);
}
private void clear(final Composite parent) {
if (parent == null) {
return;
}
for (final Control control : parent.getChildren()) {
control.dispose();
}
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import org.springframework.util.MultiValueMap;
public interface FilterAttributeSupplier {
MultiValueMap<String, String> getAttributes();
}

View file

@ -30,6 +30,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
@ -166,7 +167,21 @@ public abstract class RestCall<T> {
}
public RestCallBuilder withSorting(final String column, final SortOrder order) {
this.queryParams.put(Page.ATTR_SORT, Arrays.asList(order.prefix + column));
if (column != null) {
this.queryParams.put(Page.ATTR_SORT, Arrays.asList(order.encode(column)));
}
return this;
}
public RestCallBuilder withFilterAttributes(final FilterAttributeSupplier filterAttributes) {
if (filterAttributes != null) {
this.queryParams.putAll(filterAttributes.getAttributes());
}
return this;
}
public RestCallBuilder onlyActive(final boolean active) {
this.queryParams.put(Entity.FILTER_ATTR_ACTIVE, Arrays.asList(String.valueOf(active)));
return this;
}
@ -188,6 +203,7 @@ public abstract class RestCall<T> {
return new HttpEntity<>(this.httpHeaders);
}
}
}
}

View file

@ -33,7 +33,13 @@ public class GetInstitutionNames extends RestCall<List<EntityName>> {
},
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
SEBServerRestEndpoints.ENDPOINT_INSTITUTION + "/names");
SEBServerRestEndpoints.ENDPOINT_INSTITUTION + SEBServerRestEndpoints.NAMES_ENDPOINT_SUFFIX);
}
@Override
public RestCall<List<EntityName>>.RestCallBuilder newBuilder() {
return super.newBuilder()
.onlyActive(true);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetInstitutions extends RestCall<Page<Institution>> {
protected GetInstitutions() {
super(
new TypeReference<Page<Institution>>() {
},
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
SEBServerRestEndpoints.ENDPOINT_INSTITUTION);
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
import java.util.function.Function;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
public final class ColumnDefinition<ROW extends Entity> {
final String columnName;
final LocTextKey displayName;
final LocTextKey tooltip;
final int widthPercent;
final Function<ROW, Object> valueSupplier;
final ColumnFilterDefinition filter;
final boolean sortable;
public ColumnDefinition(
final String columnName,
final LocTextKey displayName,
final LocTextKey tooltip,
final int widthPercent) {
this(columnName, displayName, tooltip, widthPercent, null, null, false);
}
public ColumnDefinition(
final String columnName,
final LocTextKey displayName,
final int widthPercent) {
this(columnName, displayName, null, widthPercent, null, null, false);
}
public ColumnDefinition(
final String columnName,
final LocTextKey displayName,
final LocTextKey tooltip,
final int widthPercent,
final Function<ROW, Object> valueSupplier,
final ColumnFilterDefinition filter,
final boolean sortable) {
this.columnName = columnName;
this.displayName = displayName;
this.tooltip = tooltip;
this.widthPercent = widthPercent;
this.valueSupplier = valueSupplier;
this.filter = filter;
this.sortable = sortable;
}
}

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
public class ColumnFilterDefinition {
}

View file

@ -0,0 +1,250 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
public class EntityTable<ROW extends Entity> extends Composite {
private static final Logger log = LoggerFactory.getLogger(EntityTable.class);
private static final long serialVersionUID = -4931198225547108993L;
public static final String TABLE_ROW_DATA = "TABLE_ROW_DATA";
private final WidgetFactory widgetFactory;
private final RestCall<Page<ROW>> restCall;
private final List<ColumnDefinition<ROW>> columns;
private final List<TableRowAction> actions;
private final TableFilter<ROW> filter;
private final Table table;
private final TableNavigator navigator;
private final boolean selectableRows;
private int pageNumber = 1;
private int pageSize;
private String sortColumn = null;
private SortOrder sortOrder = SortOrder.ASCENDING;
private boolean columnsWithSameWidth = true;
EntityTable(
final Composite parent,
final RestCall<Page<ROW>> restCall,
final WidgetFactory widgetFactory,
final List<ColumnDefinition<ROW>> columns,
final List<TableRowAction> actions,
final int pageSize,
final boolean withFilter,
final boolean selectableRows) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
this.restCall = restCall;
this.columns = Utils.immutableListOf(columns);
this.actions = Utils.immutableListOf(actions);
super.setLayout(new GridLayout());
super.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
this.pageSize = pageSize;
this.filter = (withFilter) ? new TableFilter<>(this) : null;
this.selectableRows = selectableRows;
this.table = widgetFactory.tableLocalized(this);
this.table.setLayout(new GridLayout(columns.size(), true));
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
this.table.setLayoutData(gridData);
this.table.addListener(SWT.Resize, this::adaptColumnWidth);
//this.table.setLayoutData(new GridData(GridData.FILL_BOTH));
this.table.setHeaderVisible(true);
this.table.setLinesVisible(true);
this.navigator = new TableNavigator(this);
createTableColumns();
updateTableRows(
this.pageNumber,
this.pageSize,
this.sortColumn,
this.sortOrder);
}
public void setPageSize(final int pageSize) {
this.pageSize = pageSize;
updateTableRows(
this.pageNumber,
this.pageSize,
this.sortColumn,
this.sortOrder);
}
public void selectPage(final int pageSelection) {
// verify input
this.pageNumber = pageSelection;
if (this.pageNumber < 1) {
this.pageNumber = 1;
}
updateTableRows(
this.pageNumber,
this.pageSize,
this.sortColumn,
this.sortOrder);
}
public void applyFilter() {
// TODO remove all rows, set current page to 0, call rest to get entities and build rows and navigation again
}
public void applySort(final String columnName) {
this.sortColumn = columnName;
this.sortOrder = SortOrder.ASCENDING;
updateTableRows(
this.pageNumber,
this.pageSize,
this.sortColumn,
this.sortOrder);
}
public void changeSortOrder() {
this.sortOrder = (this.sortOrder == SortOrder.ASCENDING)
? SortOrder.DESCENDING
: SortOrder.ASCENDING;
updateTableRows(
this.pageNumber,
this.pageSize,
this.sortColumn,
this.sortOrder);
}
private void createTableColumns() {
for (final ColumnDefinition<ROW> column : this.columns) {
final TableColumn tableColumn = this.widgetFactory.tableColumnLocalized(
this.table,
column.displayName,
column.tooltip);
if (column.sortable) {
tableColumn.addListener(SWT.Selection, event -> {
if (!column.columnName.equals(this.sortColumn)) {
applySort(column.columnName);
this.table.setSortColumn(tableColumn);
this.table.setSortDirection(SWT.UP);
} else {
changeSortOrder();
this.table.setSortDirection(
(this.sortOrder == SortOrder.ASCENDING) ? SWT.UP : SWT.DOWN);
}
});
}
if (column.widthPercent > 0) {
this.columnsWithSameWidth = false;
}
}
}
private void updateTableRows(
final int pageNumber,
final int pageSize,
final String sortColumn,
final SortOrder sortOrder) {
// first remove all rows if there are some
this.table.removeAll();
// get page data and create rows
this.restCall.newBuilder()
.withPaging(pageNumber, pageSize)
.withSorting(sortColumn, sortOrder)
.withFilterAttributes(this.filter)
.call()
.map(this::createTableRowsFromPage)
.map(this.navigator::update)
.onErrorDo(t -> {
// TODO error handling
});
this.layout();
}
private Page<ROW> createTableRowsFromPage(final Page<ROW> page) {
for (final ROW row : page.content) {
final TableItem item = new TableItem(this.table, SWT.NONE);
item.setData(TABLE_ROW_DATA, row);
int index = 0;
if (this.selectableRows) {
// TODO
}
for (final ColumnDefinition<ROW> column : this.columns) {
final Object value = column.valueSupplier.apply(row);
if (value instanceof Boolean) {
// TODO set an image or HTML with checkbox
item.setText(index, String.valueOf(value));
} else {
item.setText(index, String.valueOf(value));
}
index++;
}
if (this.actions != null) {
// TODO
}
}
return page;
}
private void adaptColumnWidth(final Event event) {
try {
final int currentTableWidth = this.table.getParent().getClientArea().width;
int index = 0;
for (final ColumnDefinition<ROW> column : this.columns) {
final int percentage = (this.columnsWithSameWidth)
? 100 / this.columns.size()
: column.widthPercent;
final TableColumn tableColumn = this.table.getColumn(index);
tableColumn.setWidth(currentTableWidth / 100 * percentage);
index++;
}
} catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);
}
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.widgets.Composite;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
/** <code>
* new TableBuilder<T>(RestCall)
* .withPaging(pageSize)
* .withColumn(new ColumnDefinition(
* columnName:String,
* displayName:LocTextKey,
* tooltip:LocTextKey,
* width:int,
* valueSupplier:Function<ROW, String>,
* sortable:boolean,
* columnFilter:TableColumnFilter))
* .withAction(action:TableRowAction)
* .withSelectableRows(boolean)
* .compose(parent:Composit, group:Composite);
* </code> */
public class TableBuilder<ROW extends Entity> {
private final WidgetFactory widgetFactory;
final RestCall<Page<ROW>> restCall;
final List<ColumnDefinition<ROW>> columns = new ArrayList<>();
final List<TableRowAction> actions = new ArrayList<>();
private int pageSize = -1;
private boolean selectableRows = false;
public TableBuilder(
final WidgetFactory widgetFactory,
final RestCall<Page<ROW>> restCall) {
this.widgetFactory = widgetFactory;
this.restCall = restCall;
}
public TableBuilder<ROW> withPaging(final int pageSize) {
this.pageSize = pageSize;
return this;
}
public TableBuilder<ROW> withColumn(final ColumnDefinition<ROW> columnDef) {
this.columns.add(columnDef);
return this;
}
public TableBuilder<ROW> withSelectableRows() {
this.selectableRows = true;
return this;
}
public TableBuilder<ROW> withAction(final TableRowAction action) {
this.actions.add(action);
return this;
}
public EntityTable<ROW> compose(final Composite parent) {
final boolean withFilter = this.columns
.stream()
.filter(c -> c.filter != null)
.findFirst()
.isPresent();
return new EntityTable<>(
parent,
this.restCall,
this.widgetFactory,
this.columns,
this.actions,
this.pageSize,
withFilter,
this.selectableRows);
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FilterAttributeSupplier;
public class TableFilter<ROW extends Entity> extends Composite implements FilterAttributeSupplier {
private static final long serialVersionUID = -2460403977147440766L;
TableFilter(final EntityTable<ROW> parent) {
super(parent, SWT.NONE);
}
@Override
public MultiValueMap<String, String> getAttributes() {
// TODO Auto-generated method stub
return null;
}
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gui.service.RWTUtils;
public class TableNavigator extends Composite {
private static final long serialVersionUID = -7349918232061226192L;
private final int pageNavSize = 3;
private final EntityTable<?> entityTable;
TableNavigator(final EntityTable<?> entityTable) {
super(entityTable, SWT.NONE);
super.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
final GridLayout layout = new GridLayout(3, true);
layout.marginLeft = 20;
super.setLayout(layout);
this.entityTable = entityTable;
}
public Page<?> update(final Page<?> pageData) {
// clear all
RWTUtils.clearComposite(this);
final int pageNumber = pageData.getPageNumber();
final int numberOfPages = pageData.getNumberOfPages();
createPagingHeader(pageNumber, numberOfPages);
final Composite numNav = new Composite(this, SWT.NONE);
numNav.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
final RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL);
rowLayout.spacing = 5;
numNav.setLayout(rowLayout);
if (numberOfPages > 1) {
if (pageNumber > 1) {
createRewardLabel(pageNumber, numNav);
}
for (int i = pageNumber - this.pageNavSize; i < pageNumber + this.pageNavSize; i++) {
if (i >= 1 && i <= numberOfPages) {
createPageNumberLabel(i, i != pageNumber, numNav);
}
}
if (pageNumber < numberOfPages) {
createForwardLabel(pageNumber, numNav);
}
}
this.layout();
return pageData;
}
private void createPagingHeader(final int page, final int of) {
final Label pageHeader = new Label(this, SWT.NONE);
pageHeader.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
pageHeader.setText("Page " + page + "/" + of);
}
private void createPageNumberLabel(final int page, final boolean selectable, final Composite parent) {
final Label pageLabel = new Label(parent, SWT.NONE);
pageLabel.setText(" " + String.valueOf(page) + " ");
if (selectable) {
pageLabel.setData(RWT.CUSTOM_VARIANT, "action");
pageLabel.addListener(SWT.MouseDown, event -> {
this.entityTable.selectPage(page);
});
}
}
private void createForwardLabel(final int pageNumber, final Composite parent) {
final Label forward = new Label(parent, SWT.NONE);
forward.setText(">");
forward.setData(RWT.CUSTOM_VARIANT, "action");
forward.addListener(SWT.MouseDown, event -> {
this.entityTable.selectPage(pageNumber + 1);
});
}
private void createRewardLabel(final int pageNumber, final Composite parent) {
final Label reward = new Label(parent, SWT.NONE);
reward.setText("<");
reward.setData(RWT.CUSTOM_VARIANT, "action");
reward.addListener(SWT.MouseDown, event -> {
this.entityTable.selectPage(pageNumber - 1);
});
}
}

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.table;
public class TableRowAction {
}

View file

@ -8,8 +8,7 @@
package ch.ethz.seb.sebserver.gui.service.widget;
import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.POLYGLOT_TREE_ITEM_TEXT_DATA_KEY;
import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY;
import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.*;
import java.io.InputStream;
import java.util.Iterator;
@ -39,12 +38,17 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.RWTUtils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.table.TableBuilder;
@Lazy
@Service
@ -158,6 +162,12 @@ public class WidgetFactory {
return label;
}
public Label labelLocalizedTitle(final Composite content, final LocTextKey locTextKey) {
final Label labelLocalized = labelLocalized(content, RWTUtils.TEXT_NAME_H2, locTextKey);
labelLocalized.setLayoutData(new GridData(SWT.TOP, SWT.LEFT, true, false));
return labelLocalized;
}
public Tree treeLocalized(final Composite parent, final int style) {
final Tree tree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION);
this.injectI18n(tree);
@ -188,15 +198,23 @@ public class WidgetFactory {
return item;
}
public <T extends Entity> TableBuilder<T> entityTableBuilder(final RestCall<Page<T>> apiCall) {
return new TableBuilder<>(this, apiCall);
}
public Table tableLocalized(final Composite parent) {
final Table table = new Table(parent, SWT.NONE);
final Table table = new Table(parent, SWT.SINGLE | SWT.NO_SCROLL);
this.injectI18n(table);
return table;
}
public TableColumn tableColumnLocalized(final Table table, final String locTextKey) {
public TableColumn tableColumnLocalized(
final Table table,
final LocTextKey locTextKey,
final LocTextKey toolTipKey) {
final TableColumn tableColumn = new TableColumn(table, SWT.NONE);
this.injectI18n(tableColumn, new LocTextKey(locTextKey));
this.injectI18n(tableColumn, locTextKey, toolTipKey);
return tableColumn;
}
@ -322,9 +340,14 @@ public class WidgetFactory {
table.setData(POLYGLOT_WIDGET_FUNCTION_KEY, tableFunction(this.i18nSupport));
}
public void injectI18n(final TableColumn tableColumn, final LocTextKey locTextKey) {
public void injectI18n(final TableColumn tableColumn, final LocTextKey locTextKey, final LocTextKey locTooltipKey) {
tableColumn.setData(POLYGLOT_TREE_ITEM_TEXT_DATA_KEY, locTextKey);
tableColumn.setText(this.i18nSupport.getText(locTextKey));
if (locTooltipKey != null) {
tableColumn.setData(POLYGLOT_TREE_ITEM_TOOLTIP_DATA_KEY, locTooltipKey);
tableColumn.setToolTipText(this.i18nSupport.getText(locTooltipKey));
}
}
public void injectI18n(final TableItem tableItem, final LocTextKey... locTextKey) {

View file

@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.InstitutionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
@ -36,29 +37,26 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamic
public class PaginationService {
public enum SortOrder {
ASCENDING("+"),
DESCENDING("-");
ASCENDING,
DESCENDING;
public final String prefix;
public final static String DESCENDING_PREFIX = "-";
private SortOrder(final String prefix) {
this.prefix = prefix;
public String encode(final String sort) {
return (this == DESCENDING) ? DESCENDING_PREFIX + sort : sort;
}
public static SortOrder getSortOrder(final String sort) {
return (sort != null && sort.startsWith(DESCENDING.prefix))
public static String decode(final String sort) {
return (sort != null && sort.startsWith(DESCENDING_PREFIX))
? sort.substring(1)
: sort;
}
public static SortOrder getSortOrder(final String encoded) {
return (encoded != null && encoded.startsWith(DESCENDING_PREFIX))
? SortOrder.DESCENDING
: SortOrder.ASCENDING;
}
public static String getSortColumn(final String sort) {
return (sort == null)
? null
: (sort.startsWith(SortOrder.ASCENDING.prefix) || sort.startsWith(SortOrder.DESCENDING.prefix))
? sort.substring(1)
: sort;
}
}
private final int defaultPageSize;
@ -137,10 +135,11 @@ public class PaginationService {
final com.github.pagehelper.Page<Object> startPage =
PageHelper.startPage(getPageNumber(pageNumber), getPageSize(pageSize), true, true, false);
if (table != null) {
if (table != null && StringUtils.isNoneBlank(sort)) {
final SortOrder sortOrder = SortOrder.getSortOrder(sort);
final String sortColumnName = verifySortColumnName(sort, table);
if (StringUtils.isNoneBlank(sortColumnName)) {
switch (SortOrder.getSortOrder(sort)) {
switch (sortOrder) {
case DESCENDING: {
PageHelper.orderBy(sortColumnName + " DESC");
break;
@ -178,7 +177,7 @@ public class PaginationService {
final Map<String, String> mapping = this.sortColumnMapping.get(table.name());
if (mapping != null) {
final String sortColumn = SortOrder.getSortColumn(sort);
final String sortColumn = SortOrder.decode(sort);
if (StringUtils.isBlank(sortColumn)) {
return this.defaultSortColumn.get(table.name());
}
@ -191,6 +190,24 @@ public class PaginationService {
// TODO is it possible to generate this within MyBatis generator?
private void initSortColumnMapping() {
// Institution Table
final Map<String, String> institutionTableMap = new HashMap<>();
institutionTableMap.put(
Domain.INSTITUTION.ATTR_NAME,
InstitutionRecordDynamicSqlSupport.name.name());
institutionTableMap.put(
Domain.INSTITUTION.ATTR_URL_SUFFIX,
InstitutionRecordDynamicSqlSupport.urlSuffix.name());
institutionTableMap.put(
Domain.INSTITUTION.ATTR_ACTIVE,
InstitutionRecordDynamicSqlSupport.active.name());
this.sortColumnMapping.put(
InstitutionRecordDynamicSqlSupport.institutionRecord.name(),
institutionTableMap);
this.defaultSortColumn.put(
InstitutionRecordDynamicSqlSupport.institutionRecord.name(),
Domain.INSTITUTION.ATTR_ID);
// User Table
final Map<String, String> userTableMap = new HashMap<>();
userTableMap.put(Domain.USER.ATTR_NAME, UserRecordDynamicSqlSupport.name.name());

View file

@ -107,7 +107,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
? -1
: 1;
final String _sort = SortOrder.getSortColumn(sort);
final String _sort = SortOrder.decode(sort);
final Comparator<QuizData> comp = (_sort != null)
? (_sort.equals(QuizData.FILTER_ATTR_START_TIME))
? (q1, q2) -> q1.startTime.compareTo(q2.startTime) * orderFactor

View file

@ -30,9 +30,9 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
@ -137,7 +137,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
this.examDAO.allMatching(new FilterMap(allRequestParams)).getOrThrow());
if (!StringUtils.isBlank(sort)) {
final String sortBy = SortOrder.getSortColumn(sort);
final String sortBy = SortOrder.decode(sort);
if (sortBy.equals(QuizData.QUIZ_ATTR_NAME)) {
Collections.sort(exams, (exam1, exam2) -> exam1.name.compareTo(exam2.name));
}

View file

@ -12,9 +12,7 @@ import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
@ -29,7 +27,6 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Controller
@WebServiceProfile
@Import(DataSourceAutoConfiguration.class)
public class RevokeTokenEndpoint {
private final ConsumerTokenServices tokenServices;

View file

@ -18,8 +18,6 @@ sebserver.webservice.api.exam.accessTokenValiditySeconds=1800
sebserver.webservice.api.exam.refreshTokenValiditySeconds=-1
sebserver.webservice.api.redirect.unauthorized=http://=0.0.0.0:8080/gui
sebserver.webservice.api.redirect.unauthorized=none
server.servlet.session.cookie.http-only=true
server.servlet.session.tracking-modes=cookie

View file

@ -29,3 +29,13 @@ sebserver.actionpane.title=Actions
sebserver.activities.inst=Institution
sebserver.error.unexpected=Unexpected Error
################################
# Institution
################################
sebserver.institution.list.title=Institutions
sebserver.institution.list.column.name=Name
sebserver.institution.list.column.urlSuffix=URL Suffix
sebserver.institution.list.column.active=Active

View file

@ -26,6 +26,21 @@ Label {
text-shadow: none;
}
Label.action {
font: 12px Verdana, "Lucida Sans", Arial, Helvetica, sans-serif;
color: #82BE1E;
background-color: transparent;
background-image: none;
background-repeat: repeat;
background-position: left top;
border: none;
border-radius: 0;
text-decoration: none;
cursor: default;
opacity: 1;
text-shadow: none;
}
Label.h1 {
font: 25px Arial, Helvetica, sans-serif;
height: 28px;