SEBSERV-50 adapt normal form error handling to exam form error handling

This commit is contained in:
anhefti 2019-07-22 12:36:54 +02:00
parent ba49611c1c
commit da0d56baee
15 changed files with 172 additions and 82 deletions

View file

@ -107,8 +107,10 @@ public class ActivitiesPane implements TemplateComposer {
}
// User Account
// if current user has base or institutional read privilege for User Account, show list
if (this.currentUser.hasInstitutionalPrivilege(PrivilegeType.READ, EntityType.USER)) {
// if current user has role seb-server admin or institutional-admin, show list
if (this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN) ||
this.currentUser.get().hasRole(UserRole.INSTITUTIONAL_ADMIN)) {
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
navigation,
ActivityDefinition.USER_ACCOUNT.displayName);

View file

@ -20,6 +20,10 @@ import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
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.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
@ -39,6 +43,7 @@ import ch.ethz.seb.sebserver.gui.widget.ImageUpload;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public final class Form implements FormBinding {
@ -112,35 +117,28 @@ public final class Form implements FormBinding {
return !this.formFields.isEmpty();
}
public Form putField(final String name, final Label label, final Label field) {
Form putField(final String name, final Label label, final Label field) {
this.formFields.add(name, createAccessor(label, field));
return this;
}
public Form putField(final String name, final Label label, final Text field) {
this.formFields.add(name, createAccessor(label, field));
Form putField(final String name, final Label label, final Text field, final Label errorLabel) {
this.formFields.add(name, createAccessor(label, field, errorLabel));
return this;
}
public void putField(final String name, final Label label, final Selection field) {
this.formFields.add(name, createAccessor(label, field));
void putField(final String name, final Label label, final Selection field, final Label errorLabel) {
this.formFields.add(name, createAccessor(label, field, errorLabel));
}
public void putField(final String name, final Label label, final ThresholdList field) {
this.formFields.add(name, createAccessor(label, field));
void putField(final String name, final Label label, final ThresholdList field, final Label errorLabel) {
this.formFields.add(name, createAccessor(label, field, errorLabel));
}
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));
void putField(final String name, final Label label, final ImageUpload imageUpload, final Label errorLabel) {
final FormFieldAccessor createAccessor = createAccessor(label, imageUpload, errorLabel);
imageUpload.setErrorHandler(createAccessor::setError);
this.formFields.add(name, createAccessor);
}
public String getFieldValue(final String attributeName) {
@ -224,41 +222,43 @@ public final class Form implements FormBinding {
// 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, null) {
@Override public String getStringValue() { return null; }
@Override public void setStringValue(final String value) { field.setText( (value == null) ? StringUtils.EMPTY : value); }
};
}
private FormFieldAccessor createAccessor(final Label label, final Text text) {
return new FormFieldAccessor(label, text) {
private FormFieldAccessor createAccessor(final Label label, final Text text, final Label errorLabel) {
return new FormFieldAccessor(label, text, errorLabel) {
@Override public String getStringValue() { return text.getText(); }
@Override public void setStringValue(final String value) { text.setText( (value == null) ? StringUtils.EMPTY : value); }
};
}
private FormFieldAccessor createAccessor(final Label label, final Selection selection) {
private FormFieldAccessor createAccessor(final Label label, final Selection selection, final Label errorLabel) {
switch (selection.type()) {
case MULTI:
case MULTI_COMBO:
return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray);
default : return createAccessor(label, selection, null);
return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray, errorLabel);
default : return createAccessor(label, selection, null, null);
}
}
private FormFieldAccessor createAccessor(
final Label label,
final Selection selection,
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) {
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter,
final Label errorLabel) {
return new FormFieldAccessor(
label,
selection.adaptToControl(),
jsonValueAdapter,
selection.type() != Type.SINGLE) {
selection.type() != Type.SINGLE,
errorLabel) {
@Override public String getStringValue() { return selection.getSelectionValue(); }
@Override public void setStringValue(final String value) { selection.select(value); }
};
}
private FormFieldAccessor createAccessor(final Label label, final ThresholdList thresholdList) {
return new FormFieldAccessor(label, thresholdList, null, true) {
private FormFieldAccessor createAccessor(final Label label, final ThresholdList thresholdList, final Label errorLabel) {
return new FormFieldAccessor(label, thresholdList, null, true, errorLabel) {
@Override public String getStringValue() {
return ThresholdListBuilder
.thresholdsToFormURLEncodedStringValue(thresholdList.getThresholds());
@ -275,8 +275,8 @@ public final class Form implements FormBinding {
}
};
}
private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) {
return new FormFieldAccessor(label, imageUpload) {
private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload, final Label errorLabel) {
return new FormFieldAccessor(label, imageUpload, errorLabel) {
@Override public String getStringValue() { return imageUpload.getImageBase64(); }
};
}
@ -347,22 +347,25 @@ public final class Form implements FormBinding {
public final Label label;
public final Control control;
private final Label errorLabel;
private final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter;
private boolean hasError;
private final boolean listValue;
FormFieldAccessor(final Label label, final Control control) {
this(label, control, null, false);
FormFieldAccessor(final Label label, final Control control, final Label errorLabel) {
this(label, control, null, false, errorLabel);
}
FormFieldAccessor(
final Label label,
final Control control,
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter,
final boolean listValue) {
final boolean listValue,
final Label errorLabel) {
this.label = label;
this.control = control;
this.errorLabel = errorLabel;
if (jsonValueAdapter != null) {
this.jsonValueAdapter = jsonValueAdapter;
} else {
@ -390,21 +393,51 @@ public final class Form implements FormBinding {
this.jsonValueAdapter.accept(new Tuple<>(key, getStringValue()), objectRoot);
}
public void setError(final String errorTooltip) {
public void setError(final String errorMessage) {
if (this.errorLabel == null) {
return;
}
if (!this.hasError) {
this.control.setData(RWT.CUSTOM_VARIANT, "error");
this.control.setToolTipText(errorTooltip);
this.hasError = true;
this.errorLabel.setText(errorMessage);
this.errorLabel.setVisible(true);
}
}
public void resetError() {
if (this.errorLabel == null) {
return;
}
if (this.hasError) {
this.control.setData(RWT.CUSTOM_VARIANT, null);
this.control.setToolTipText(null);
this.hasError = false;
this.errorLabel.setVisible(false);
this.errorLabel.setText("");
}
}
}
public static Composite createFieldGrid(final Composite parent, final int hspan) {
final Composite fieldGrid = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout();
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 1;
gridLayout.marginWidth = 0;
gridLayout.marginRight = 5;
fieldGrid.setLayout(gridLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
gridData.horizontalSpan = hspan;
fieldGrid.setLayoutData(gridData);
return fieldGrid;
}
public static Label createErrorLabel(final Composite innerGrid) {
final Label errorLabel = new Label(innerGrid, SWT.NONE);
errorLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
errorLabel.setVisible(false);
errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
return errorLabel;
}
}

View file

@ -248,6 +248,7 @@ public class FormBuilder {
final Label label = this.widgetFactory.labelLocalized(parent, locTextKey);
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, true, false, hspan, 1);
gridData.verticalIndent = 4;
gridData.heightHint = 20;
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
return label;

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.form;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
@ -23,15 +24,19 @@ public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
@Override
void build(final FormBuilder builder) {
final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel);
final Label lab = builder.labelLocalized(builder.formParent, this.label, 1);
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
final ImageUpload imageUpload = builder.widgetFactory.imageUploadLocalized(
builder.formParent,
fieldGrid,
new LocTextKey("sebserver.overall.upload"),
builder.readonly || this.readonly);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
imageUpload.setLayoutData(gridData);
imageUpload.setImageBase64(this.value);
builder.form.putField(this.name, lab, imageUpload);
final Label errorLabel = Form.createErrorLabel(fieldGrid);
builder.form.putField(this.name, lab, imageUpload, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}

View file

@ -65,19 +65,23 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
}
private void buildInput(final FormBuilder builder, final Label lab) {
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
final String actionKey = (this.label != null) ? this.label.name + ".action" : null;
final Selection selection = builder.widgetFactory.selectionLocalized(
this.type,
builder.formParent,
fieldGrid,
this.itemsSupplier,
null,
null,
actionKey);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
((Control) selection).setLayoutData(gridData);
selection.select(this.value);
builder.form.putField(this.name, lab, selection);
final Label errorLabel = Form.createErrorLabel(fieldGrid);
builder.form.putField(this.name, lab, selection, errorLabel);
if (this.selectionListener != null) {
((Control) selection).addListener(SWT.Selection, e -> {

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.form;
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;
@ -52,13 +53,15 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
builder.valueLabel(builder.formParent, this.value, this.spanInput));
builder.setFieldVisible(this.visible, this.name);
} else {
final Text textInput = (this.isNumber)
? builder.widgetFactory.numberInput(builder.formParent, null)
: (this.isArea)
? builder.widgetFactory.textAreaInput(builder.formParent)
: builder.widgetFactory.textInput(builder.formParent, this.isPassword);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
final Text textInput = (this.isNumber)
? builder.widgetFactory.numberInput(fieldGrid, null)
: (this.isArea)
? builder.widgetFactory.textAreaInput(fieldGrid)
: builder.widgetFactory.textInput(fieldGrid, this.isPassword);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
if (this.isArea) {
gridData.heightHint = 50;
}
@ -66,7 +69,9 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
if (this.value != null) {
textInput.setText(this.value);
}
builder.form.putField(this.name, lab, textInput);
final Label errorLabel = Form.createErrorLabel(fieldGrid);
builder.form.putField(this.name, lab, textInput, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
}

View file

@ -14,6 +14,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.Constants;
@ -39,13 +40,17 @@ public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
// TODO do we need a read-only view for this?
return;
} else {
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
builder.formParent,
fieldGrid,
this.value);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
thresholdList.setLayoutData(gridData);
builder.form.putField(this.name, lab, thresholdList);
final Label errorLabel = Form.createErrorLabel(fieldGrid);
builder.form.putField(this.name, lab, thresholdList, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}

View file

@ -9,18 +9,15 @@
package ch.ethz.seb.sebserver.gui.service.examconfig;
import org.apache.commons.lang3.BooleanUtils;
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.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gui.service.examconfig.impl.InputFieldBuilderSupplier;
import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public interface InputFieldBuilder {
@ -84,12 +81,4 @@ public interface InputFieldBuilder {
attribute));
}
static Label createErrorLabel(final Composite innerGrid) {
final Label errorLabel = new Label(innerGrid, SWT.NONE);
errorLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
errorLabel.setVisible(false);
errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
return errorLabel;
}
}

View file

@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputField;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
@ -77,7 +78,7 @@ public class PassworFieldBuilder implements InputFieldBuilder {
orientation,
passwordInput,
confirmInput,
InputFieldBuilder.createErrorLabel(innerGrid));
Form.createErrorLabel(innerGrid));
final Listener valueChangeEventListener = event -> {
passwordInputField.clearError();

View file

@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputField;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
@ -73,7 +74,7 @@ public class SingleSelectionFieldBuilder extends SelectionFieldBuilder implement
attribute,
orientation,
selection,
InputFieldBuilder.createErrorLabel(innerGrid));
Form.createErrorLabel(innerGrid));
selection.setSelectionListener(event -> {
singleSelectionInputField.clearError();

View file

@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputField;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
@ -97,7 +98,7 @@ public class TextFieldBuilder implements InputFieldBuilder {
attribute,
orientation,
text,
InputFieldBuilder.createErrorLabel(innerGrid));
Form.createErrorLabel(innerGrid));
final Listener valueChangeEventListener = event -> {
textInputField.clearError();

View file

@ -12,7 +12,12 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.commons.codec.binary.Base64InputStream;
import org.apache.commons.io.IOUtils;
@ -33,24 +38,33 @@ import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
public final class ImageUpload extends Composite {
private static final long serialVersionUID = 368264811155804533L;
private static final Logger log = LoggerFactory.getLogger(ImageUpload.class);
private transient final ServerPushService serverPushService;
public static final Set<String> SUPPORTED_IMAGE_FILES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
".png")));
private final ServerPushService serverPushService;
private final Composite imageCanvas;
private final FileUpload fileUpload;
private Consumer<String> errorHandler;
private String imageBase64 = null;
private boolean loadNewImage = false;
private boolean imageLoaded = false;
ImageUpload(final Composite parent, final ServerPushService serverPushService, final boolean readonly) {
ImageUpload(
final Composite parent,
final ServerPushService serverPushService,
final I18nSupport i18nSupport,
final boolean readonly) {
super(parent, SWT.NONE);
super.setLayout(new GridLayout(1, false));
@ -87,6 +101,19 @@ public final class ImageUpload extends Composite {
@Override
public void widgetSelected(final SelectionEvent event) {
final String fileName = ImageUpload.this.fileUpload.getFileName();
if (fileName == null || !fileSupported(fileName)) {
if (ImageUpload.this.errorHandler != null) {
final String text = i18nSupport.getText(
"sebserver.institution.form.logoImage.unsupportedFileType",
"Unsupported image file type selected");
ImageUpload.this.errorHandler.accept(text);
}
log.warn("Unsupported image file selected: {}", fileName);
return;
}
ImageUpload.this.loadNewImage = true;
ImageUpload.this.imageLoaded = false;
ImageUpload.this.fileUpload.submit(uploadHandler.getUploadUrl());
@ -96,6 +123,7 @@ public final class ImageUpload extends Composite {
ImageUpload::wait,
ImageUpload::update);
}
});
} else {
this.fileUpload = null;
@ -107,6 +135,10 @@ public final class ImageUpload extends Composite {
}
public void setErrorHandler(final Consumer<String> errorHandler) {
this.errorHandler = errorHandler;
}
public void setSelectionText(final String text) {
if (this.fileUpload != null) {
this.fileUpload.setToolTipText(text);
@ -158,6 +190,7 @@ public final class ImageUpload extends Composite {
context.layout();
imageUpload.layout();
imageUpload.loadNewImage = false;
imageUpload.errorHandler.accept("");
}
}
@ -170,4 +203,12 @@ public final class ImageUpload extends Composite {
imageUpload.imageCanvas.setBackgroundImage(new Image(imageUpload.imageCanvas.getDisplay(), imageData));
}
private static boolean fileSupported(final String fileName) {
return SUPPORTED_IMAGE_FILES
.stream()
.filter(fileType -> fileName.endsWith(fileType))
.findFirst()
.isPresent();
}
}

View file

@ -183,7 +183,7 @@ public class WidgetFactory {
final Composite grid = new Composite(parent, SWT.NONE);
final GridLayout layout = new GridLayout(rows, true);
layout.horizontalSpacing = 10;
layout.verticalSpacing = 10;
//layout.verticalSpacing = 10;
layout.marginBottom = 50;
layout.marginLeft = 10;
layout.marginTop = 0;
@ -534,7 +534,12 @@ public class WidgetFactory {
final LocTextKey locTextKey,
final boolean readonly) {
final ImageUpload imageUpload = new ImageUpload(parent, this.serverPushService, readonly);
final ImageUpload imageUpload = new ImageUpload(
parent,
this.serverPushService,
this.i18nSupport,
readonly);
this.polyglotPageService.injectI18n(imageUpload, locTextKey);
return imageUpload;
}

View file

@ -146,8 +146,4 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
EntityType.CLIENT_EVENT);
}
private void noModifyAccess() {
}
}

View file

@ -101,6 +101,7 @@ sebserver.institution.form.title=Institution
sebserver.institution.form.name=Name
sebserver.institution.form.urlSuffix=URL Suffix
sebserver.institution.form.logoImage=Logo Image
sebserver.institution.form.logoImage.unsupportedFileType=The selected file is not or an unsupported image type
################################