SEBSERV-29 new multi selection with combo supporter selection

This commit is contained in:
anhefti 2019-03-21 16:55:11 +01:00
parent 94858b757a
commit 584a900529
24 changed files with 535 additions and 181 deletions

View file

@ -14,6 +14,9 @@ import org.joda.time.format.DateTimeFormatter;
/** Global Constants used in SEB Server web-service as well as in web-gui component */
public final class Constants {
public static final String TRUE_STRING = Boolean.TRUE.toString();
public static final String FALSE_STRING = Boolean.FALSE.toString();
public static final long SECOND_IN_MILLIS = 1000;
public static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
public static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;

View file

@ -48,6 +48,7 @@ public final class UserInfo implements UserAccount, Activatable, Serializable {
public static final String FILTER_ATTR_USER_NAME = "username";
public static final String FILTER_ATTR_EMAIL = "email";
public static final String FILTER_ATTR_LANGUAGE = "language";
public static final String FILTER_ATTR_ROLE = "role";
/** The user's UUID */
@JsonProperty(USER.ATTR_UUID)

View file

@ -10,7 +10,11 @@ package ch.ethz.seb.sebserver.gui.content;
import java.util.function.Function;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@ -28,6 +32,7 @@ import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
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.page.ModalInputDialog;
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.page.action.Action;
@ -131,6 +136,10 @@ public class ExamList implements TemplateComposer {
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
pageContext.clearEntityKeys()
.createAction(ActionDefinition.TEST_ACTION)
.withExec(this::testModalInput)
.publish()
.createAction(ActionDefinition.EXAM_IMPORT)
.publishIf(userGrant::im)
@ -163,4 +172,29 @@ public class ExamList implements TemplateComposer {
.getText("sebserver.exam.type." + exam.type.name());
}
private Action testModalInput(final Action action) {
final ModalInputDialog<String> dialog = new ModalInputDialog<>(
action.pageContext().getParent().getShell(),
this.widgetFactory);
dialog.open(
"Test Input Dialog",
action.pageContext(),
value -> {
System.out.println("********************** value: " + value);
},
pc -> {
final Composite parent = pc.getParent();
final Label label = new Label(parent, SWT.NONE);
label.setText("Please Enter:");
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Text text = new Text(parent, SWT.LEFT | SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
return () -> text.getText();
});
return action;
}
}

View file

@ -319,7 +319,7 @@ public enum ActionDefinition {
EXAM_INDICATOR_NEW(
new LocTextKey("sebserver.exam.indicator.action.list.new"),
ImageIcon.NEW,
LmsSetupForm.class,
IndicatorForm.class,
EXAM_VIEW_FROM_LIST,
ActionCategory.FORM,
false),
@ -350,6 +350,12 @@ public enum ActionDefinition {
EXAM_VIEW_FROM_LIST,
ActionCategory.FORM,
true),
TEST_ACTION(
new LocTextKey("TEST"),
ImageIcon.TEST,
ExamList.class,
EXAM_VIEW_LIST,
ActionCategory.FORM),
;

View file

@ -8,12 +8,12 @@
package ch.ethz.seb.sebserver.gui.form;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
@ -30,10 +30,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
import ch.ethz.seb.sebserver.gui.widget.ImageUpload;
import ch.ethz.seb.sebserver.gui.widget.MultiSelection;
import ch.ethz.seb.sebserver.gui.widget.SingleSelection;
import ch.ethz.seb.sebserver.gui.widget.Selection;
public final class Form implements FormBinding {
@ -42,8 +42,6 @@ public final class Form implements FormBinding {
private final Map<String, String> staticValues = new LinkedHashMap<>();
private final MultiValueMap<String, FormFieldAccessor> formFields = new LinkedMultiValueMap<>();
private final Map<String, Form> subForms = new LinkedHashMap<>();
private final Map<String, List<Form>> subLists = new LinkedHashMap<>();
private final Map<String, Set<String>> groups = new LinkedHashMap<>();
Form(final JSONMapper jsonMapper) {
@ -104,40 +102,23 @@ public final class Form implements FormBinding {
return this;
}
public void putField(final String name, final Label label, final SingleSelection field) {
public void putField(final String name, final Label label, final Selection field) {
this.formFields.add(name, createAccessor(label, field));
}
public void putField(final String name, final Label label, final MultiSelection field) {
this.formFields.add(name, createAccessor(label, field));
public void putField(
final String name,
final Label label,
final Selection field,
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) {
this.formFields.add(name, createAccessor(label, field, jsonValueAdapter));
}
public void putField(final String name, final Label label, final ImageUpload imageUpload) {
this.formFields.add(name, createAccessor(label, imageUpload));
}
public void putSubForm(final String name, final Form form) {
this.subForms.put(name, form);
}
public Form getSubForm(final String name) {
return this.subForms.get(name);
}
public void addSubForm(final String arrayName, final Form form) {
final List<Form> array = this.subLists.computeIfAbsent(arrayName, k -> new ArrayList<>());
array.add(form);
}
public Form getSubForm(final String arrayName, final int index) {
final List<Form> array = this.subLists.get(arrayName);
if (array == null) {
return null;
}
return array.get(index);
}
public void allVisible() {
process(
name -> true,
@ -196,30 +177,12 @@ public final class Form implements FormBinding {
.filter(ffa -> StringUtils.isNoneBlank(ffa.getValue()))
.forEach(ffa -> ffa.putJsonValue(entry.getKey(), this.objectRoot));
}
for (final Map.Entry<String, Form> entry : this.subForms.entrySet()) {
final Form subForm = entry.getValue();
subForm.flush();
final ObjectNode objectNode = this.jsonMapper.createObjectNode();
this.objectRoot.set(entry.getKey(), objectNode);
}
for (final Map.Entry<String, List<Form>> entry : this.subLists.entrySet()) {
final List<Form> value = entry.getValue();
final ArrayNode arrayNode = this.jsonMapper.createArrayNode();
final int index = 0;
for (final Form arrayForm : value) {
arrayForm.flush();
arrayNode.insert(index, arrayForm.objectRoot);
}
this.objectRoot.set(entry.getKey(), arrayNode);
}
}
// following are FormFieldAccessor implementations for all field types
//@formatter:off
private FormFieldAccessor createAccessor(final Label label, final Label field) {
return new FormFieldAccessor(label, field) {
return new FormFieldAccessor(label, field) {
@Override public String getValue() { return null; }
@Override public void setValue(final String value) { field.setText(value); }
};
@ -230,26 +193,20 @@ public final class Form implements FormBinding {
@Override public void setValue(final String value) { text.setText(value); }
};
}
private FormFieldAccessor createAccessor(final Label label, final SingleSelection singleSelection) {
return new FormFieldAccessor(label, singleSelection) {
@Override public String getValue() { return singleSelection.getSelectionValue(); }
@Override public void setValue(final String value) { singleSelection.select(value); }
};
private FormFieldAccessor createAccessor(final Label label, final Selection selection) {
switch (selection.type()) {
case MULTI : return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray);
default : return createAccessor(label, selection, null);
}
}
private FormFieldAccessor createAccessor(final Label label,final MultiSelection multiSelection) {
return new FormFieldAccessor(label, multiSelection) {
@Override public String getValue() { return multiSelection.getSelectionValue(); }
@Override public void setValue(final String value) { multiSelection.select(value); }
@Override public void putJsonValue(final String key, final ObjectNode objectRoot) {
final String value = getValue();
if (StringUtils.isNoneBlank(value)) {
final ArrayNode arrayNode = objectRoot.putArray(key);
final String[] split = StringUtils.split(value, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
arrayNode.add(split[i]);
}
}
}
private FormFieldAccessor createAccessor(
final Label label,
final Selection selection,
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) {
return new FormFieldAccessor(label, selection.adaptToControl(), jsonValueAdapter) {
@Override public String getValue() { return selection.getSelectionValue(); }
@Override public void setValue(final String value) { selection.select(value); }
};
}
private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) {
@ -281,15 +238,44 @@ public final class Form implements FormBinding {
}
}
private static final void adaptCommaSeparatedStringToJsonArray(final Tuple<String> tuple,
final ObjectNode jsonNode) {
if (StringUtils.isNoneBlank(tuple._2)) {
final ArrayNode arrayNode = jsonNode.putArray(tuple._1);
final String[] split = StringUtils.split(tuple._2, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
arrayNode.add(split[i]);
}
}
}
public static abstract class FormFieldAccessor {
public final Label label;
public final Control control;
private final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter;
private boolean hasError;
public FormFieldAccessor(final Label label, final Control control) {
FormFieldAccessor(final Label label, final Control control) {
this(label, control, null);
}
FormFieldAccessor(
final Label label,
final Control control,
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) {
this.label = label;
this.control = control;
if (jsonValueAdapter != null) {
this.jsonValueAdapter = jsonValueAdapter;
} else {
this.jsonValueAdapter = (tuple, jsonObject) -> {
if (StringUtils.isNoneBlank(tuple._2)) {
jsonObject.put(tuple._1, tuple._2);
}
};
}
}
public abstract String getValue();
@ -301,11 +287,8 @@ public final class Form implements FormBinding {
this.control.setVisible(visible);
}
public void putJsonValue(final String key, final ObjectNode objectRoot) {
final String value = getValue();
if (StringUtils.isNoneBlank(value)) {
objectRoot.put(key, value);
}
public final void putJsonValue(final String key, final ObjectNode objectRoot) {
this.jsonValueAdapter.accept(new Tuple<>(key, getValue()), objectRoot);
}
public void setError(final String errorTooltip) {

View file

@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple;
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.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@ -194,7 +195,7 @@ public class FormBuilder {
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(name, label, value, itemsSupplier);
return new SelectionFieldBuilder(Selection.Type.SINGLE, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiSelection(
@ -203,8 +204,16 @@ public class FormBuilder {
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(name, label, value, itemsSupplier)
.asMultiSelection();
return new SelectionFieldBuilder(Selection.Type.MULTI, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiComboSelection(
final String name,
final String label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier);
}
public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) {

View file

@ -26,24 +26,25 @@ import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.widget.MultiSelection;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.SingleSelection;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public final class SelectionFieldBuilder extends FieldBuilder {
final Supplier<List<Tuple<String>>> itemsSupplier;
Consumer<Form> selectionListener = null;
boolean multi = false;
final Selection.Type type;
SelectionFieldBuilder(
final Selection.Type type,
final String name,
final String label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
super(name, label, value);
this.type = type;
this.itemsSupplier = itemsSupplier;
}
@ -52,11 +53,6 @@ public final class SelectionFieldBuilder extends FieldBuilder {
return this;
}
public SelectionFieldBuilder asMultiSelection() {
this.multi = true;
return this;
}
@Override
void build(final FormBuilder builder) {
final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel);
@ -68,29 +64,29 @@ public final class SelectionFieldBuilder extends FieldBuilder {
}
private void buildInput(final FormBuilder builder, final Label lab) {
final Selection selection = (this.multi)
? builder.widgetFactory.multiSelectionLocalized(builder.formParent, this.itemsSupplier)
: builder.widgetFactory.singleSelectionLocalized(builder.formParent, this.itemsSupplier);
final Selection selection = builder.widgetFactory.selectionLocalized(
this.type,
builder.formParent,
this.itemsSupplier);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
((Control) selection).setLayoutData(gridData);
selection.select(this.value);
if (this.multi) {
builder.form.putField(this.name, lab, selection.<MultiSelection> getTypeInstance());
} else {
builder.form.putField(this.name, lab, selection.<SingleSelection> getTypeInstance());
}
builder.form.putField(this.name, lab, selection);
if (this.selectionListener != null) {
((Control) selection).addListener(SWT.Selection, e -> {
this.selectionListener.accept(builder.form);
});
}
builder.setFieldVisible(this.visible, this.name);
}
/* Build the read-only representation of the selection field */
private void buildReadOnly(final FormBuilder builder, final Label lab) {
if (this.multi) {
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO) {
final Composite composite = new Composite(builder.formParent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.horizontalSpacing = 0;
@ -99,7 +95,11 @@ public final class SelectionFieldBuilder extends FieldBuilder {
gridLayout.marginWidth = 0;
composite.setLayout(gridLayout);
if (StringUtils.isBlank(this.value)) {
createMuliSelectionReadonlyLabel(composite, Constants.EMPTY_NOTE);
final Label label = new Label(composite, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION_READONLY.key);
label.setText(this.value);
} else {
final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR));
this.itemsSupplier.get()
@ -137,12 +137,4 @@ public final class SelectionFieldBuilder extends FieldBuilder {
return label;
}
private void createMuliSelectionReadonlyLabel(final Composite composite, final String value) {
final Label label = new Label(composite, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION_READONLY.key);
label.setText(value);
}
}

View file

@ -23,7 +23,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
@ -104,7 +104,7 @@ public class ResourceService {
public List<Tuple<String>> institutionResource() {
return this.restService.getBuilder(GetInstitutionNames.class)
.withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true")
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
.call()
.getOr(Collections.emptyList())
.stream()
@ -115,7 +115,7 @@ public class ResourceService {
public Function<String, String> getInstitutionNameFunction() {
final Map<String, String> idNameMap = this.restService.getBuilder(GetInstitutionNames.class)
.withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true")
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
.call()
.getOr(Collections.emptyList())
.stream()
@ -133,8 +133,8 @@ public class ResourceService {
final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
final String institutionId = (isSEBAdmin) ? "" : String.valueOf(this.currentUser.get().institutionId);
return this.restService.getBuilder(GetLmsSetupNames.class)
.withQueryParam(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, institutionId)
.withQueryParam(Domain.LMS_SETUP.ATTR_ACTIVE, "true")
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, institutionId)
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
.call()
.getOr(Collections.emptyList())
.stream()
@ -146,8 +146,8 @@ public class ResourceService {
final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
final String institutionId = (isSEBAdmin) ? "" : String.valueOf(this.currentUser.get().institutionId);
final Map<String, String> idNameMap = this.restService.getBuilder(GetLmsSetupNames.class)
.withQueryParam(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, institutionId)
.withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true")
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, institutionId)
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
.call()
.getOr(Collections.emptyList())
.stream()
@ -196,11 +196,24 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> examSupporterResources() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetUserAccountNames.class)
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.institutionId))
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
.withQueryParam(UserInfo.FILTER_ATTR_ROLE, UserRole.EXAM_SUPPORTER.name())
.call()
.getOr(Collections.emptyList())
.stream()
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
.collect(Collectors.toList());
}
public List<Tuple<String>> userResources() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetUserAccountNames.class)
.withQueryParam(Domain.USER.ATTR_INSTITUTION_ID, String.valueOf(userInfo.institutionId))
.withQueryParam(Domain.USER.ATTR_ACTIVE, "true")
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.institutionId))
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
.call()
.getOr(Collections.emptyList())
.stream()

View file

@ -0,0 +1,101 @@
/*
* 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;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Shell;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
public class ModalInputDialog<T> extends Dialog {
private static final long serialVersionUID = -3448614119078234374L;
private final WidgetFactory widgetFactory;
private T returnValue = null;
public ModalInputDialog(
final Shell parent,
final WidgetFactory widgetFactory) {
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL);
this.widgetFactory = widgetFactory;
}
public void open(
final String title,
final PageContext pageContext,
final Consumer<T> callback,
final ModalInputDialogComposer<T> contentComposer) {
// Create the dialog window
final Shell shell = new Shell(getParent(), getStyle());
shell.setText(getText());
shell.setData(RWT.CUSTOM_VARIANT, "message");
shell.setText(title);
shell.setLayout(new GridLayout(2, true));
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Composite main = new Composite(shell, SWT.NONE);
main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.horizontalSpan = 2;
main.setLayoutData(gridData);
final PageContext internalPageContext = pageContext.copyOf(main);
final Supplier<T> valueSuppier = contentComposer.compose(internalPageContext);
final Button ok = this.widgetFactory.buttonLocalized(
shell,
new LocTextKey("sebserver.overall.action.ok"));
GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
data.widthHint = 100;
ok.setLayoutData(data);
ok.addListener(SWT.Selection, event -> {
callback.accept(valueSuppier.get());
this.returnValue = valueSuppier.get();
shell.close();
});
shell.setDefaultButton(ok);
final Button cancel = this.widgetFactory.buttonLocalized(
shell,
new LocTextKey("sebserver.overall.action.cancel"));
data = new GridData(GridData.CENTER);
data.widthHint = 100;
cancel.setLayoutData(data);
cancel.addListener(SWT.Selection, event -> {
shell.close();
});
shell.pack();
final Rectangle bounds = shell.getBounds();
final Rectangle bounds2 = super.getParent().getDisplay().getBounds();
bounds.width = 400;
bounds.x = (bounds2.width - bounds.width) / 2;
bounds.y = (bounds2.height - bounds.height) / 2;
shell.setBounds(bounds);
shell.open();
}
}

View file

@ -0,0 +1,18 @@
/*
* 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;
import java.util.function.Supplier;
@FunctionalInterface
public interface ModalInputDialogComposer<T> {
Supplier<T> compose(PageContext pageContext);
}

View file

@ -325,10 +325,6 @@ public class EntityTable<ROW extends Entity> {
index++;
}
// NOTE this.layout() triggers the navigation to disappear unexpectedly!?
//this.table.layout(true, true);
//this.composite.layout(true, true);
} catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);
}

View file

@ -30,7 +30,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.widget.SingleSelection;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
public class TableFilter<ROW extends Entity> {
@ -105,7 +105,7 @@ public class TableFilter<ROW extends Entity> {
case TEXT:
return new TextFilter(attribute);
case SINGLE_SELECTION:
return new Selection(attribute);
return new SelectionFilter(attribute);
case DATE:
return new Date(attribute);
default:
@ -245,21 +245,24 @@ public class TableFilter<ROW extends Entity> {
}
private class Selection extends FilterComponent {
private class SelectionFilter extends FilterComponent {
protected SingleSelection selector;
protected Selection selector;
Selection(final TableFilterAttribute attribute) {
SelectionFilter(final TableFilterAttribute attribute) {
super(attribute);
}
@Override
FilterComponent build(final Composite parent) {
this.selector = TableFilter.this.entityTable.widgetFactory
.singleSelectionLocalized(
.selectionLocalized(
ch.ethz.seb.sebserver.gui.widget.Selection.Type.SINGLE,
parent,
this.attribute.resourceSupplier);
this.selector.setLayoutData(this.rowData);
this.selector
.adaptToControl()
.setLayoutData(this.rowData);
return this;
}

View file

@ -34,7 +34,7 @@ public class MultiSelection extends Composite implements Selection {
private final List<Label> labels = new ArrayList<>();
private final List<Label> selected = new ArrayList<>();
public MultiSelection(final Composite parent) {
MultiSelection(final Composite parent) {
super(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.verticalSpacing = 1;
@ -44,6 +44,11 @@ public class MultiSelection extends Composite implements Selection {
setLayout(gridLayout);
}
@Override
public Type type() {
return Type.MULTI;
}
@Override
public void applyNewMapping(final List<Tuple<String>> mapping) {
final String selectionValue = getSelectionValue();
@ -134,10 +139,4 @@ public class MultiSelection extends Composite implements Selection {
deselectAll();
}
@SuppressWarnings("unchecked")
@Override
public MultiSelection getTypeInstance() {
return this;
}
}

View file

@ -0,0 +1,131 @@
/*
* 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.widget;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
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.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
public class MultiSelectionCombo extends Composite implements Selection {
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
private static final long serialVersionUID = -7787134114963647332L;
private static final int ACTION_COLUMN_WIDTH = 20;
private final WidgetFactory widgetFactory;
private final Table table;
private final Combo combo;
private final List<String> selected = new ArrayList<>();
private final List<Tuple<String>> mapping = new ArrayList<>();
MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
setLayout(gridLayout);
this.table = new Table(this, SWT.NONE);
TableColumn column = new TableColumn(this.table, SWT.NONE);
column = new TableColumn(this.table, SWT.NONE);
column.setWidth(ACTION_COLUMN_WIDTH);
this.table.setHeaderVisible(false);
this.table.addListener(SWT.Resize, this::adaptColumnWidth);
final TableItem header = new TableItem(this.table, SWT.NONE);
final TableEditor editor = new TableEditor(this.table);
this.combo = new Combo(this.table, SWT.NONE);
editor.grabHorizontal = true;
editor.setEditor(this.combo, header, 0);
widgetFactory.imageButton(
ImageIcon.ADD,
this.table,
new LocTextKey("Add"),
this::addComboSelection);
}
@Override
public Type type() {
return Type.MULTI_COMBO;
}
@Override
public void applyNewMapping(final List<Tuple<String>> mapping) {
this.selected.clear();
this.mapping.clear();
this.mapping.addAll(mapping);
}
@Override
public void select(final String keys) {
clear();
}
@Override
public String getSelectionValue() {
if (this.selected.isEmpty()) {
return null;
}
return this.mapping
.stream()
.filter(t -> this.selected.contains(t._2))
.map(t -> t._1)
.reduce("", (s1, s2) -> s1.concat(Constants.LIST_SEPARATOR).concat(s2));
}
@Override
public void clear() {
this.selected.clear();
this.table.remove(1, this.table.getItemCount());
final List<String> names = this.mapping
.stream()
.map(t -> t._2)
.collect(Collectors.toList());
this.combo.setItems(names.toArray(new String[names.size()]));
}
private void addComboSelection(final Event event) {
}
private void adaptColumnWidth(final Event event) {
try {
final int currentTableWidth = this.table.getParent().getClientArea().width;
this.table.getColumn(0).setWidth(currentTableWidth - ACTION_COLUMN_WIDTH);
} catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);
}
}
}

View file

@ -10,10 +10,20 @@ package ch.ethz.seb.sebserver.gui.widget;
import java.util.List;
import org.eclipse.swt.widgets.Control;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
public interface Selection {
enum Type {
SINGLE,
MULTI,
MULTI_COMBO
}
Type type();
void applyNewMapping(final List<Tuple<String>> mapping);
void select(final String keys);
@ -24,6 +34,10 @@ public interface Selection {
void setVisible(boolean visible);
<T extends Selection> T getTypeInstance();
default Control adaptToControl() {
return (Control) this;
}
//<T extends Selection> T getTypeInstance();
}

View file

@ -25,7 +25,7 @@ public class SingleSelection extends Combo implements Selection {
final List<String> valueMapping;
final List<String> keyMapping;
public SingleSelection(final Composite parent) {
SingleSelection(final Composite parent) {
super(parent, SWT.READ_ONLY);
this.valueMapping = new ArrayList<>();
this.keyMapping = new ArrayList<>();
@ -72,10 +72,9 @@ public class SingleSelection extends Combo implements Selection {
super.setItems(this.valueMapping.toArray(new String[this.valueMapping.size()]));
}
@SuppressWarnings("unchecked")
@Override
public SingleSelection getTypeInstance() {
return this;
public Type type() {
return Type.SINGLE;
}
}

View file

@ -60,6 +60,8 @@ public class WidgetFactory {
public enum ImageIcon {
MAXIMIZE("maximize.png"),
MINIMIZE("minimize.png"),
ADD("add.png"),
REMOVE("remove.png"),
EDIT("edit.png"),
TEST("test.png"),
IMPORT("import.png"),
@ -307,27 +309,64 @@ public class WidgetFactory {
return imageButton;
}
public SingleSelection singleSelectionLocalized(
public Selection selectionLocalized(
final Selection.Type type,
final Composite parent,
final Supplier<List<Tuple<String>>> itemsSupplier) {
final SingleSelection singleSelection = new SingleSelection(parent);
final Consumer<SingleSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
updateFunction.accept(singleSelection);
return singleSelection;
final Selection selection;
switch (type) {
case SINGLE:
selection = new SingleSelection(parent);
break;
case MULTI:
selection = new MultiSelection(parent);
break;
case MULTI_COMBO:
selection = new MultiSelectionCombo(parent, this);
break;
default:
throw new IllegalArgumentException("Unsupported Selection.Type: " + type);
}
final Consumer<Selection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
updateFunction.accept(selection);
return selection;
}
public MultiSelection multiSelectionLocalized(
final Composite parent,
final Supplier<List<Tuple<String>>> itemsSupplier) {
final MultiSelection multiSelection = new MultiSelection(parent);
final Consumer<MultiSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
updateFunction.accept(multiSelection);
return multiSelection;
}
// public SingleSelection singleSelectionLocalized(
// final Composite parent,
// final Supplier<List<Tuple<String>>> itemsSupplier) {
//
// final SingleSelection singleSelection = new SingleSelection(parent);
// final Consumer<SingleSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
// singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
// updateFunction.accept(singleSelection);
// return singleSelection;
// }
//
// public MultiSelection multiSelectionLocalized(
// final Composite parent,
// final Supplier<List<Tuple<String>>> itemsSupplier) {
//
// final MultiSelection multiSelection = new MultiSelection(parent);
// final Consumer<MultiSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
// updateFunction.accept(multiSelection);
// return multiSelection;
// }
//
// public MultiSelectionCombo multiSelectionComboLocalized(
// final Composite parent,
// final Supplier<List<Tuple<String>>> itemsSupplier) {
//
// final MultiSelectionCombo multiSelection = new MultiSelectionCombo(parent, this);
// final Consumer<MultiSelectionCombo> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
// updateFunction.accept(multiSelection);
// return multiSelection;
// }
public ImageUpload imageUploadLocalized(
final Composite parent,

View file

@ -170,7 +170,7 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
}
private final static char[] possibleCharacters = (new String(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}?"))
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?"))
.toCharArray();
private CharSequence getSalt(final CharSequence saltPlain) throws UnsupportedEncodingException {

View file

@ -67,6 +67,10 @@ public class FilterMap extends POSTMapper {
return getString(UserInfo.FILTER_ATTR_LANGUAGE);
}
public String getUserRole() {
return getString(UserInfo.FILTER_ATTR_ROLE);
}
public String getLmsSetupType() {
return getString(LmsSetup.FILTER_ATTR_LMS_TYPE);
}

View file

@ -150,33 +150,40 @@ public class UserDAOImpl implements UserDAO {
@Override
@Transactional(readOnly = true)
public Result<Collection<UserInfo>> allMatching(final FilterMap filterMap, final Predicate<UserInfo> predicate) {
return Result.tryCatch(() -> this.userRecordMapper
.selectByExample()
.where(
UserRecordDynamicSqlSupport.active,
isEqualToWhenPresent(filterMap.getActiveAsInt()))
.and(
UserRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
UserRecordDynamicSqlSupport.name,
isLikeWhenPresent(filterMap.getName()))
.and(
UserRecordDynamicSqlSupport.username,
isLikeWhenPresent(filterMap.getUserUsername()))
.and(
UserRecordDynamicSqlSupport.email,
isLikeWhenPresent(filterMap.getUserEmail()))
.and(
UserRecordDynamicSqlSupport.language,
isLikeWhenPresent(filterMap.getUserLanguage()))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.filter(predicate)
.collect(Collectors.toList()));
return Result.tryCatch(() -> {
final String userRole = filterMap.getUserRole();
final Predicate<UserInfo> _predicate = (StringUtils.isNotBlank(userRole))
? predicate.and(ui -> ui.roles.contains(userRole))
: predicate;
return this.userRecordMapper
.selectByExample()
.where(
UserRecordDynamicSqlSupport.active,
isEqualToWhenPresent(filterMap.getActiveAsInt()))
.and(
UserRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
UserRecordDynamicSqlSupport.name,
isLikeWhenPresent(filterMap.getName()))
.and(
UserRecordDynamicSqlSupport.username,
isLikeWhenPresent(filterMap.getUserUsername()))
.and(
UserRecordDynamicSqlSupport.email,
isLikeWhenPresent(filterMap.getUserEmail()))
.and(
UserRecordDynamicSqlSupport.language,
isLikeWhenPresent(filterMap.getUserLanguage()))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.filter(_predicate)
.collect(Collectors.toList());
});
}
@Override

View file

@ -12,6 +12,8 @@ sebserver.overall.action.modify.cancel=Cancel
sebserver.overall.action.modify.cancel.confirm=Are you sure to cancel? Modifications will be lost.
sebserver.overall.action.filter=Apply Filter
sebserver.overall.action.filter.clear=Clear Filter Criteria
sebserver.overall.action.ok=OK
sebserver.overall.action.cancel=Cancel
sebserver.overall.action.category.varia=Varia

View file

@ -96,7 +96,7 @@ Label.selected {
Label-SeparatorLine {
background-image: none;
background-color: transparent;
border: 1px solid #595959;
border: 1px solid #bdbdbd;
border-radius: 0px;
height: 1px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B