/*
* Copyright (C) 2012 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.webapp.client.widget.mods;
import com.google.gwt.event.shared.HandlerRegistration;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RecordList;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Img;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.ValuesManager;
import com.smartgwt.client.widgets.form.events.ItemChangedEvent;
import com.smartgwt.client.widgets.form.events.ItemChangedHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;
import cz.cas.lib.proarc.webapp.client.widget.mods.RepeatableFormItem.CustomFormFactory;
import cz.cas.lib.proarc.webapp.client.widget.mods.RepeatableFormItem.FormWidget;
import cz.cas.lib.proarc.webapp.client.widget.mods.RepeatableFormItem.FormWidgetFactory;
import cz.cas.lib.proarc.webapp.client.widget.mods.event.HasListChangedHandlers;
import cz.cas.lib.proarc.webapp.client.widget.mods.event.ListChangedEvent;
import cz.cas.lib.proarc.webapp.client.widget.mods.event.ListChangedHandler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Widget to display {@link RepeatableFormItem}.
*
* @author Jan Pokorsky
*/
public final class RepeatableForm extends VLayout implements HasListChangedHandlers {
private static final Logger LOG = Logger.getLogger(RepeatableForm.class.getName());
/** Holds origin records with special fields. */
private RecordList dataModel = new RecordList();
private final CustomFormFactory formFactory;
/** pool of available form widgets */
private ArrayList<Row> pool = new ArrayList<Row>();
/** active form rows */
private ArrayList<Row> activeRows = new ArrayList<Row>();
private final RepeatableFormItem formItem;
/** The widget to present validation errors related to all item repetitions. */
private final Img formError;
public static final class Row {
private FormWidget formWidget;
/** form + buttons */
private Canvas view;
private IButton buttonAdd;
private IButton buttonRemove;
public ValuesManager getForm() {
return formWidget.getValues();
}
public FormWidget getFormWidget() {
return formWidget;
}
public void setFormWidget(FormWidget formWidget) {
this.formWidget = formWidget;
}
public Canvas getView() {
return view;
}
public void setView(Canvas view) {
this.view = view;
}
public IButton getButtonAdd() {
return buttonAdd;
}
public void setButtonAdd(IButton buttonAdd) {
this.buttonAdd = buttonAdd;
}
public IButton getButtonRemove() {
return buttonRemove;
}
public void setButtonRemove(IButton buttonRemove) {
this.buttonRemove = buttonRemove;
}
/**
* Fixed {@link ValuesManager#clearErrors(boolean) }.
*/
public void clearErrors(boolean show) {
ValuesManager vm = getForm();
// vm.clearErrors() broken in SmartGWT 3.0
for (DynamicForm form : vm.getMembers()) {
form.clearErrors(show);
}
}
}
RepeatableForm(RepeatableFormItem item) {
this.formItem = item;
setAutoHeight();
this.formFactory = item.getFormFactory();
if (formItem.getMaxOccurrences() > 1 || formItem.getTitle() != null) {
setGroupTitle(formItem.getTitle());
setIsGroup(true);
setLayoutTopMargin(6);
setLayoutLeftMargin(4);
}
if (formItem.isAutoWidth()) {
setAutoWidth();
} else if (formItem.isWidth100()) {
setWidth100();
} else {
setWidth(formItem.getWidthAsString());
}
formError = new Img("[SKIN]/actions/exclamation.png", 16, 16);
formError.setHoverWidth(item.getHoverWidth());
formError.setVisible(false);
addMember(formError);
// ClientUtils.info(LOG, "init.RForm, name: %s, autoWidth: %s, width100: %s, width: %s",
// formItem.getName(), formItem.isAutoWidth(), formItem.isWidth100(), formItem.getWidthAsString());
}
@Override
public HandlerRegistration addListChangedHandler(ListChangedHandler handler) {
return doAddHandler(handler, ListChangedEvent.TYPE);
}
public List<Row> getRows() {
return activeRows;
}
public void setData(Record... data) {
if (data == null || data.length == 0) {
data = new Record[] {new Record()};
}
RecordList recordList = new RecordList(data);
setData(recordList);
}
public boolean validate(boolean showError) {
boolean valid = true;
for (Row row : activeRows) {
ValuesManager vm = row.getForm();
for (DynamicForm df : vm.getMembers()) {
valid &= showError ? df.validate() : df.valuesAreValid(false);
}
}
return valid;
}
public void showErrors(Collection<String> errors) {
if (errors != null) {
StringBuilder sb = new StringBuilder();
for (String error : errors) {
sb.append(error).append("<br/>");
}
formError.setVisible(true);
formError.setPrompt(sb.toString());
} else {
formError.setVisible(false);
}
}
public void clearErrors(boolean show) {
formError.setVisible(false);
for (Row row : activeRows) {
row.clearErrors(show);
}
}
public List<Map<Object, Object>> getErrors() {
ArrayList<Map<Object, Object>> result = new ArrayList<Map<Object, Object>>();
for (Row row : activeRows) {
ValuesManager vm = row.getForm();
@SuppressWarnings("unchecked")
Map<Object, Object> errors = vm.getErrors();
if (errors != null && !errors.isEmpty()) {
result.add(errors);
}
}
return result;
}
/**
* for now it uses ResultSet as a plain static array of records
*/
public void setData(RecordList data) {
if (data.isEmpty()) {
dataModel = new RecordList();
dataModel.add(new Record());
} else {
// issue 123: make own copy to ensure modified data are not propagated
// to RepeatableFormItem before firing an event
dataModel = new RecordList(data.duplicate());
}
int rowIndex = 0;
for (; rowIndex < dataModel.getLength(); rowIndex++) {
Record record = dataModel.get(rowIndex);
ValuesManager form;
Row row;
if (rowIndex < activeRows.size()) {
// reuse active row
row = activeRows.get(rowIndex);
form = row.getForm();
} else {
// get new row
row = getRow();
form = row.getForm();
addMember(row.getView());
activeRows.add(row);
}
showRow(row);
form.editRecord(record);
row.getFormWidget().fireDataLoad();
}
// release unused rows
while (rowIndex < activeRows.size()) {
Row remove = activeRows.remove(rowIndex);
pool.add(remove);
removeMember(remove.view);
}
setAddDisabled(activeRows.size() >= formItem.getMaxOccurrences());
}
/**
* Gets available item from the pool or creates new one.
*/
private Row getRow() {
Row row;
if (pool.isEmpty()) {
row = new Row();
createRowForm(row);
Canvas listItem = createRowWidget(row);
row.setView(listItem);
} else {
row = pool.remove(0);
row.clearErrors(true);
row.getForm().clearValues();
}
return row;
}
public Record[] getData() {
return getDataAsRecordList().toArray();
}
public RecordList getDataAsRecordList() {
return dataModel;
}
private void createRowForm(final Row row) {
ValuesManager vm;
if (formFactory instanceof FormWidgetFactory) {
FormWidgetFactory ff = (FormWidgetFactory) formFactory;
FormWidget formWidget = ff.createFormWidget(formItem.getProfile());
row.setFormWidget(formWidget);
vm = formWidget.getValues();
} else {
DynamicForm form = formFactory.create();
vm = form.getValuesManager();
if (vm == null) {
vm = new ValuesManager();
DataSource dataSource = form.getDataSource();
if (dataSource != null) {
// SmartGWT requires existing form DataSource to be set to ValuesManager
vm.setDataSource(dataSource);
}
vm.addMember(form);
}
FormWidget formWidget = new FormWidget(form, vm);
row.setFormWidget(formWidget);
}
for (DynamicForm form : vm.getMembers()) {
form.setShowInlineErrors(true);
form.setShowErrorStyle(true);
form.addItemChangedHandler(new ItemChangedHandler() {
@Override
public void onItemChanged(ItemChangedEvent event) {
// get original record and replace it with all form attributes
Map values = row.getForm().getValues();
dataModel.set(activeRows.indexOf(row), new Record(values));
RepeatableForm.this.fireEvent(new ListChangedEvent());
}
});
}
}
private Canvas createRowWidget(Row row) {
HLayout hLayout = new HLayout();
Canvas buttons = createItemButtons(row);
buttons.setLayoutAlign(VerticalAlignment.BOTTOM);
hLayout.addMember(row.getFormWidget().getWidget());
hLayout.addMember(buttons);
return hLayout;
}
private void showRow(Row row) {
boolean repeatable = formItem.getMaxOccurrences() != 1;
row.getButtonAdd().setVisible(repeatable);
row.getButtonRemove().setVisible(repeatable);
}
private void onAddRowClick(int eventRowIndex) {
if (activeRows.size() >= formItem.getMaxOccurrences()) {
return ;
}
Row row = getRow();
Canvas newListItem = row.getView();
// 1. member is formError
RepeatableForm.this.addMember(newListItem, eventRowIndex + 2);
Record newRecord = new Record();
dataModel.addAt(newRecord, eventRowIndex + 1);
activeRows.add(eventRowIndex + 1, row);
// disable Add buttons
if (activeRows.size() >= formItem.getMaxOccurrences()) {
setAddDisabled(true);
}
RepeatableForm.this.fireEvent(new ListChangedEvent());
}
private void onRemoveRowClick(int eventRowIndex) {
if (activeRows.size() == 1) {
// do not remove last item
Row row = activeRows.get(0);
row.getForm().clearValues();
row.clearErrors(true);
dataModel.set(0, new Record());
} else {
Row row = activeRows.remove(eventRowIndex);
pool.add(row);
dataModel.removeAt(eventRowIndex);
// 1. member is formError
RepeatableForm.this.removeMember(row.getView());
setAddDisabled(false);
}
RepeatableForm.this.fireEvent(new ListChangedEvent());
}
private void setAddDisabled(boolean disable) {
for (Row row : activeRows) {
row.getButtonAdd().setDisabled(disable);
}
}
private Canvas createItemButtons(final Row row) {
HLayout hLayout = new HLayout(2);
hLayout.setLayoutMargin(2);
IButton btnAdd = new IButton("+", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
int rowIndex = activeRows.indexOf(row);
onAddRowClick(rowIndex);
}
});
btnAdd.setAutoFit(true);
hLayout.addMember(btnAdd);
row.setButtonAdd(btnAdd);
IButton btnRemove = new IButton("-", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
int rowIndex = activeRows.indexOf(row);
onRemoveRowClick(rowIndex);
}
});
btnRemove.setAutoFit(true);
hLayout.addMember(btnRemove);
row.setButtonRemove(btnRemove);
return hLayout;
}
}