package de.dhbw.humbuch.view;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import com.vaadin.data.Container.Filter;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.fieldgroup.BeanFieldGroup;
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.data.util.filter.Or;
import com.vaadin.data.util.filter.SimpleStringFilter;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutListener;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.DateField;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.ColumnGenerator;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import de.davherrmann.mvvm.BasicState;
import de.davherrmann.mvvm.State;
import de.davherrmann.mvvm.StateChangeListener;
import de.davherrmann.mvvm.ViewModelComposer;
import de.davherrmann.mvvm.annotations.BindState;
import de.dhbw.humbuch.event.ConfirmEvent;
import de.dhbw.humbuch.event.MessageEvent;
import de.dhbw.humbuch.model.entity.Category;
import de.dhbw.humbuch.model.entity.SchoolYear.Term;
import de.dhbw.humbuch.model.entity.Subject;
import de.dhbw.humbuch.model.entity.TeachingMaterial;
import de.dhbw.humbuch.util.BookLookup;
import de.dhbw.humbuch.util.BookLookup.Book;
import de.dhbw.humbuch.util.BookLookup.BookNotFoundException;
import de.dhbw.humbuch.viewmodel.TeachingMaterialViewModel;
import de.dhbw.humbuch.viewmodel.TeachingMaterialViewModel.Categories;
import de.dhbw.humbuch.viewmodel.TeachingMaterialViewModel.StandardCategory;
import de.dhbw.humbuch.viewmodel.TeachingMaterialViewModel.TeachingMaterials;
/**
* {@link View} to manage the lendable teaching materials.
*
* @author Martin Wentzel
* @author Johannes Idelhauser
*
*/
public class TeachingMaterialView extends VerticalLayout implements View, ViewInformation {
private static final long serialVersionUID = -5063268947544706757L;
private static final String TITLE = "Lehrmittelverwaltung";
private static final String TABLE_CATEGORY = "category";
private static final String TABLE_NAME = "name";
private static final String TABLE_PROFILE = "profile";
private static final String TABLE_FROMGRADE = "fromGrade";
private static final String TABLE_FROMTERM = "fromTerm";
private static final String TABLE_TOGRADE = "toGrade";
private static final String TABLE_TOTERM = "toTerm";
private static final String TABLE_PRODUCER = "producer";
private static final String TABLE_IDENTNR = "identifyingNumber";
private static final String TABLE_COMMENT = "comment";
private static final String TABLE_VALIDFROM = "validFrom";
private static final String TABLE_VALIDUNTIL = "validUntil";
private EventBus eventBus;
private TeachingMaterialViewModel teachingMaterialViewModel;
/**
* Layout components
*/
private HorizontalLayout head;
private TextField filter;
private Button btnEdit;
private Button btnNew;
private Button btnDelete;
private Table materialsTable;
private BeanItemContainer<TeachingMaterial> tableData;
private BeanFieldGroup<TeachingMaterial> binder = new BeanFieldGroup<TeachingMaterial>(TeachingMaterial.class);
@BindState(TeachingMaterials.class)
public final State<Collection<TeachingMaterial>> teachingMaterials = new BasicState<>(Collection.class);
@BindState(Categories.class)
public final State<Collection<Category>> categories = new BasicState<>(Collection.class);
@BindState(StandardCategory.class)
public final State<Category> standardCategory = new BasicState<>(Category.class);
/**
* All popup-window components and the corresponding binded states. The
* popup-window for adding a new teaching material and editing a teaching
* material is the same. Only the caption will be set differently
*/
private Window windowEditTeachingMaterial;
private FormLayout windowContent;
private HorizontalLayout windowButtons;
private TextField txtTmName = new TextField("Titel");
private TextField txtIdentNr = new TextField();
private TextField txtProducer = new TextField("Hersteller/Verlag");
private TextField txtFromGrade = new TextField();
private TextField txtToGrade = new TextField();
private ComboBox cbProfiles = new ComboBox("Profil");
private ComboBox cbFromTerm = new ComboBox();
private ComboBox cbToTerm = new ComboBox();
private ComboBox cbCategory = new ComboBox("Kategorie");
private DateField dfValidFrom = new DateField("Gültig von");
private DateField dfValidUntil = new DateField("Gültig bis");
private TextArea textAreaComment = new TextArea("Kommentar");
private Button btnWindowSave = new Button("Speichern");
private Button btnWindowCancel = new Button("Abbrechen");
private Button btnISBNImport = new Button("Hole Daten");
@Inject
public TeachingMaterialView(ViewModelComposer viewModelComposer, TeachingMaterialViewModel teachingMaterialViewModel, EventBus eventBus) {
this.teachingMaterialViewModel = teachingMaterialViewModel;
this.eventBus = eventBus;
init();
buildLayout();
bindViewModel(viewModelComposer, teachingMaterialViewModel);
}
/**
* Initializes the components and sets attributes.
*
*/
@SuppressWarnings("serial")
private void init() {
head = new HorizontalLayout();
head.setWidth("100%");
head.setSpacing(true);
// Filter
filter = new TextField();
filter.setImmediate(true);
filter.setInputPrompt("Lehrmittel suchen...");
filter.setWidth("50%");
filter.setTextChangeEventMode(TextChangeEventMode.EAGER);
head.addComponent(filter);
head.setExpandRatio(filter, 1);
head.setComponentAlignment(filter, Alignment.MIDDLE_LEFT);
// Buttons
HorizontalLayout buttons = new HorizontalLayout();
buttons.setSpacing(true);
// Add
btnNew = new Button("Hinzufügen");
buttons.addComponent(btnNew);
// Delete
btnDelete = new Button("Löschen");
btnDelete.setEnabled(false);
buttons.addComponent(btnDelete);
// Edit
btnEdit = new Button("Bearbeiten");
btnEdit.setEnabled(false);
btnEdit.setClickShortcut(KeyCode.ENTER);
buttons.addComponent(btnEdit);
head.addComponent(buttons);
head.setComponentAlignment(buttons, Alignment.MIDDLE_RIGHT);
// Instantiate table
materialsTable = new Table();
materialsTable.setSelectable(true);
materialsTable.setImmediate(true);
materialsTable.setSizeFull();
materialsTable.setColumnCollapsingAllowed(true);
tableData = new BeanItemContainer<TeachingMaterial>(
TeachingMaterial.class);
materialsTable.setContainerDataSource(tableData);
materialsTable.setVisibleColumns(new Object[] { TABLE_NAME,
TABLE_PRODUCER, TABLE_PROFILE, TABLE_FROMGRADE, TABLE_TOGRADE,
TABLE_CATEGORY, TABLE_IDENTNR });
materialsTable.setColumnHeader(TABLE_CATEGORY, "Kategorie");
materialsTable.setColumnHeader(TABLE_NAME, "Titel");
materialsTable.setColumnHeader(TABLE_PROFILE, "Profil");
materialsTable.setColumnHeader(TABLE_FROMGRADE, "Von Klasse");
materialsTable.setColumnHeader(TABLE_TOGRADE, "Bis Klasse");
materialsTable.setColumnHeader(TABLE_PRODUCER, "Hersteller/Verlag");
materialsTable.setColumnHeader(TABLE_IDENTNR, "Nummer/ISBN");
materialsTable.setColumnHeader(TABLE_COMMENT, "Kommentar");
materialsTable.setColumnHeader(TABLE_VALIDFROM, "Gültig von");
materialsTable.setColumnHeader(TABLE_VALIDUNTIL, "Gültig bis");
materialsTable.addGeneratedColumn(TABLE_PROFILE, new ColumnGenerator() {
@Override
public Object generateCell(Table source, Object itemId,
Object columnId) {
TeachingMaterial item = (TeachingMaterial) itemId;
String profile = "";
for(Subject subject : item.getProfile()) {
profile = subject.toString();
}
return profile;
}
});
binder.setBuffered(true);
this.createEditWindow();
this.addListener();
}
/**
* Creates the window for editing and creating teaching materials
*
* @return The created Window
*/
@SuppressWarnings("serial")
public void createEditWindow() {
// Create Window and set parameters
windowEditTeachingMaterial = new Window();
windowEditTeachingMaterial.center();
windowEditTeachingMaterial.setModal(true);
windowEditTeachingMaterial.setResizable(false);
windowContent = new FormLayout();
windowContent.setMargin(true);
windowButtons = new HorizontalLayout();
windowButtons.setSpacing(true);
btnWindowSave.addStyleName("default");
// Fill Comboboxes
cbFromTerm.addItem(Term.FIRST);
cbFromTerm.addItem(Term.SECOND);
cbToTerm.addItem(Term.FIRST);
cbToTerm.addItem(Term.SECOND);
for (Subject subject : Subject.values()) {
Set<Subject> subjects = new HashSet<Subject>();
subjects.add(subject);
cbProfiles.addItem(subjects);
cbProfiles.setItemCaption(subjects, subject.toString());
}
// Set Form options
cbCategory.setNullSelectionAllowed(false);
cbProfiles.setNullSelectionAllowed(false);
cbFromTerm.setNullSelectionAllowed(false);
cbToTerm.setNullSelectionAllowed(false);
txtTmName.setRequired(true);
cbCategory.setRequired(true);
cbProfiles.setRequired(true);
dfValidFrom.setRequired(true);
dfValidUntil.setDescription("Leer für unbestimmtes Gültigkeitsdatum");
// Input prompts
txtIdentNr.setInputPrompt("ISBN");
txtTmName.setInputPrompt("Titel oder Name");
txtProducer.setInputPrompt("z.B. Klett");
txtFromGrade.setInputPrompt("z.B. 5");
txtToGrade.setInputPrompt("z.B. 7");
textAreaComment.setInputPrompt("Zusätzliche Informationen");
// NullRepresentation
txtIdentNr.setNullRepresentation("");
txtTmName.setNullRepresentation("");
txtProducer.setNullRepresentation("");
txtFromGrade.setNullRepresentation("");
txtToGrade.setNullRepresentation("");
textAreaComment.setNullRepresentation("");
// Bind to FieldGroup
binder.bind(txtTmName, TABLE_NAME);
binder.bind(txtIdentNr, TABLE_IDENTNR);
binder.bind(cbCategory, TABLE_CATEGORY);
binder.bind(txtProducer, TABLE_PRODUCER);
binder.bind(cbProfiles, TABLE_PROFILE);
binder.bind(txtFromGrade, TABLE_FROMGRADE);
binder.bind(cbFromTerm, TABLE_FROMTERM);
binder.bind(txtToGrade, TABLE_TOGRADE);
binder.bind(cbToTerm, TABLE_TOTERM);
binder.bind(dfValidFrom, TABLE_VALIDFROM);
binder.bind(dfValidUntil, TABLE_VALIDUNTIL);
binder.bind(textAreaComment, TABLE_COMMENT);
// Add all components
windowContent.addComponent(txtTmName);
windowContent.addComponent(new HorizontalLayout(){
{
setSpacing(true);
setCaption("ISBN/Nummer");
setStyleName("required");
addComponent(txtIdentNr);
addComponent(btnISBNImport);
}
});
windowContent.addComponent(cbCategory);
windowContent.addComponent(txtProducer);
windowContent.addComponent(cbProfiles);
windowContent.addComponent(new HorizontalLayout() {
{
setSpacing(true);
setCaption("Von Klassenstufe");
txtFromGrade.setWidth("80px");
addComponent(txtFromGrade);
addComponent(cbFromTerm);
}
});
windowContent.addComponent(new HorizontalLayout() {
{
setSpacing(true);
setCaption("Bis Klassenstufe");
txtToGrade.setWidth("80px");
addComponent(txtToGrade);
addComponent(cbToTerm);
}
});
windowContent.addComponent(dfValidFrom);
windowContent.addComponent(dfValidUntil);
windowContent.addComponent(textAreaComment);
windowButtons.addComponent(btnWindowCancel);
windowButtons.addComponent(btnWindowSave);
windowContent.addComponent(windowButtons);
windowEditTeachingMaterial.setContent(windowContent);
windowEditTeachingMaterial.setCaption("Lehrmittel bearbeiten");
windowEditTeachingMaterial.setCloseShortcut(KeyCode.ESCAPE, null);
}
/**
* Adds all listener to their corresponding components.
*
*/
@SuppressWarnings("serial")
private void addListener() {
/**
* Fetches the book data by using a given ISBN. After fetching the data
* it is inserted into the corresponding fields.
*/
btnISBNImport.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
//Check if empty
if (txtIdentNr.getValue() == null) {
eventBus.post(new MessageEvent("Bitte geben Sie eine ISBN an."));
return;
}
Runnable confirmRunnable = new Runnable() {
@Override
public void run() {
try {
Book book = BookLookup.lookup(txtIdentNr.getValue());
txtTmName.setValue(book.title);
txtProducer.setValue(book.publisher);
textAreaComment.setValue(book.publisher);
} catch (BookNotFoundException e) {
eventBus.post(new MessageEvent("Es konnte kein Buch zu der ISBN gefunden werden."));
}
}
};
eventBus.post(new ConfirmEvent.Builder("Alle bereits eingegebenen Daten des Lehrmittels werden überschrieben.<br>Wollen Sie wirklich fortfahren?")
.caption("Daten werden überschrieben").confirmRunnable(confirmRunnable).build());
}
});
/**
* Listens for changes in the Collection teachingMaterials and adds them
* to the container.
*/
teachingMaterials.addStateChangeListener(new StateChangeListener() {
@Override
public void stateChange(Object value) {
tableData.removeAllItems();
for (TeachingMaterial material : teachingMaterials.get()) {
tableData.addItem(material);
}
}
});
/**
* Enables/disables the edit and delete buttons
*/
materialsTable.addValueChangeListener(new ValueChangeListener() {
@Override
public void valueChange(ValueChangeEvent event) {
TeachingMaterial item = (TeachingMaterial) materialsTable
.getValue();
btnEdit.setEnabled(item != null);
btnDelete.setEnabled(item != null);
}
});
// Double click on a row: make it editable
materialsTable.addItemClickListener(new ItemClickEvent.ItemClickListener() {
@Override
public void itemClick(ItemClickEvent itemClickEvent) {
if (itemClickEvent.isDoubleClick() && !materialsTable.isEditable()) {
materialsTable.setValue(itemClickEvent.getItemId());
btnEdit.click();
}
}
});
/**
* Opens the popup-window for editing a book and inserts the data from
* the teachingMaterialInfo-State.
*/
btnEdit.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
TeachingMaterial item = (TeachingMaterial) materialsTable
.getValue();
binder.setItemDataSource(item);
UI.getCurrent().addWindow(windowEditTeachingMaterial);
txtTmName.focus();
}
});
/**
* Opens a new popup-window with an empty new teaching material
*/
btnNew.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
TeachingMaterial item = new TeachingMaterial.Builder(standardCategory.get(), null, null, new Date()).build();
binder.setItemDataSource(item);
UI.getCurrent().addWindow(windowEditTeachingMaterial);
txtTmName.focus();
}
});
/**
* Closes the popup window
*
*/
btnWindowCancel.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
windowEditTeachingMaterial.close();
clearWindowFields();
}
});
/**
* Saves the teachingMaterial and closes the popup-window
*
*/
btnWindowSave.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
if (FormFieldsValid()) {
try {
binder.commit();
teachingMaterialViewModel.doUpdateTeachingMaterial(binder
.getItemDataSource().getBean());
windowEditTeachingMaterial.close();
eventBus.post(new MessageEvent(
"Lehrmittel gespeichert."));
} catch (CommitException e) {
eventBus.post(new MessageEvent(e.getLocalizedMessage()));
}
}
}
});
/**
* Show a confirm popup and delete the teaching material on confirmation
*/
btnDelete.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
final TeachingMaterial item = (TeachingMaterial) materialsTable
.getValue();
if (item != null) {
Runnable runnable = new Runnable() {
@Override
public void run() {
teachingMaterialViewModel
.doDeleteTeachingMaterial(item);
materialsTable.select(null);
}
};
eventBus.post(new ConfirmEvent.Builder(
"Wollen Sie das Lehrmittel wirklich löschen?")
.caption("Löschen").confirmRunnable(runnable)
.build());
}
}
});
/**
* Provides the live search of the teaching material table by adding a
* filter after every keypress in the search field. Currently the
* publisher and title search are supported.
*/
filter.addTextChangeListener(new TextChangeListener() {
private static final long serialVersionUID = -1684545652234105334L;
@Override
public void textChange(TextChangeEvent event) {
String text = event.getText();
Filter filter = new Or(new SimpleStringFilter(TABLE_NAME, text, true, false),
new SimpleStringFilter(TABLE_PRODUCER, text, true, false),
new SimpleStringFilter(TABLE_CATEGORY,text,true,false),
new SimpleStringFilter(TABLE_FROMGRADE,text,true,false),
new SimpleStringFilter(TABLE_TOGRADE,text,true,false),
new SimpleStringFilter(TABLE_IDENTNR,text,true,false));
tableData.removeAllContainerFilters();
tableData.addContainerFilter(filter);
}
});
/**
* Allows to dismiss the filter by hitting ESCAPE
*/
filter.addShortcutListener(new ShortcutListener("Clear",
KeyCode.ESCAPE, null) {
@Override
public void handleAction(Object sender, Object target) {
filter.setValue("");
tableData.removeAllContainerFilters();
}
});
/**
* Fills the category combobox
*/
categories.addStateChangeListener(new StateChangeListener() {
@Override
public void stateChange(Object arg0) {
cbCategory.removeAllItems();
for (Category cat : categories.get()) {
cbCategory.addItem(cat);
cbCategory.setItemCaption(cat, cat.getName());
}
}
});
}
/**
* Validates the fields in the edit teaching materials window.
*
* @return Whether or not the fields in the editor are valid
*/
private boolean FormFieldsValid() {
// Validate if a field is empty
if (txtIdentNr.getValue() == null || txtIdentNr.getValue().isEmpty()
|| txtTmName.getValue() == null
|| txtTmName.getValue().isEmpty()
|| cbCategory.getValue() == null
|| dfValidFrom.getValue() == null
|| cbProfiles.getValue() == null) {
eventBus.post(new MessageEvent(
"Bitte füllen Sie alle Pflicht-Felder aus."));
return false;
} else {
// No field is empty, validate now for right values and lengths
if (txtTmName.getValue().length() < 2) {
eventBus.post(new MessageEvent(
"Der Titel muss mindestens 2 Zeichen enthalten."));
return false;
}
try {
if (txtToGrade.getValue() != null)
Integer.parseInt(txtToGrade.getValue());
if (txtFromGrade.getValue() != null)
Integer.parseInt(txtFromGrade.getValue());
} catch (NumberFormatException e) {
eventBus.post(new MessageEvent(
"Die Klassenstufen dürfen nur Zahlen enthalten"));
return false;
}
try {
dfValidFrom.validate();
dfValidUntil.validate();
} catch (InvalidValueException e) {
eventBus.post(new MessageEvent(
"Mindestens ein Datumsfeld ist nicht korrekt."));
return false;
}
return true;
}
}
/**
* Builds the layout by adding all components in their specific order.
*/
private void buildLayout() {
setMargin(true);
setSpacing(true);
setSizeFull();
addComponent(head);
addComponent(materialsTable);
setExpandRatio(materialsTable, 1);
}
/**
* Empties the fields of the popup-window to prevent that TextFields already
* have content form previous edits.
*/
private void clearWindowFields() {
txtTmName.setValue("");
txtIdentNr.setValue("");
txtProducer.setValue("");
txtFromGrade.setValue("");
txtToGrade.setValue("");
textAreaComment.setValue("");
txtProducer.setValue("");
cbFromTerm.setValue(Term.FIRST);
cbToTerm.setValue(Term.SECOND);
}
@Override
public void enter(ViewChangeEvent event) {
teachingMaterialViewModel.refresh();
}
private void bindViewModel(ViewModelComposer viewModelComposer,
Object... viewModels) {
try {
viewModelComposer.bind(this, viewModels);
} catch (IllegalAccessException | NoSuchElementException
| UnsupportedOperationException e) {
e.printStackTrace();
}
}
@Override
public String getTitle() {
return TITLE;
}
}