/*
* Copyright (C) 2015 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.workflow;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.DSCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.types.TitleOrientation;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.FormItemIfFunction;
import com.smartgwt.client.widgets.form.events.ItemChangedEvent;
import com.smartgwt.client.widgets.form.events.ItemChangedHandler;
import com.smartgwt.client.widgets.form.fields.CheckboxItem;
import com.smartgwt.client.widgets.form.fields.ComboBoxItem;
import com.smartgwt.client.widgets.form.fields.DateTimeItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.FormItemIcon;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.TextAreaItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.validator.IsFloatValidator;
import com.smartgwt.client.widgets.form.validator.RequiredIfFunction;
import com.smartgwt.client.widgets.form.validator.RequiredIfValidator;
import com.smartgwt.client.widgets.form.validator.Validator;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
import cz.cas.lib.proarc.common.workflow.model.Task.State;
import cz.cas.lib.proarc.common.workflow.model.ValueType;
import cz.cas.lib.proarc.common.workflow.model.WorkflowModelConsts;
import cz.cas.lib.proarc.common.workflow.profile.DisplayType;
import cz.cas.lib.proarc.webapp.client.ClientMessages;
import cz.cas.lib.proarc.webapp.client.Editor;
import cz.cas.lib.proarc.webapp.client.ErrorHandler;
import cz.cas.lib.proarc.webapp.client.action.AbstractAction;
import cz.cas.lib.proarc.webapp.client.action.ActionEvent;
import cz.cas.lib.proarc.webapp.client.action.Actions;
import cz.cas.lib.proarc.webapp.client.action.Actions.ActionSource;
import cz.cas.lib.proarc.webapp.client.action.RefreshAction;
import cz.cas.lib.proarc.webapp.client.action.RefreshAction.Refreshable;
import cz.cas.lib.proarc.webapp.client.action.SaveAction;
import cz.cas.lib.proarc.webapp.client.ds.RestConfig;
import cz.cas.lib.proarc.webapp.client.ds.UserDataSource;
import cz.cas.lib.proarc.webapp.client.ds.ValueMapDataSource;
import cz.cas.lib.proarc.webapp.client.ds.WorkflowParameterDataSource;
import cz.cas.lib.proarc.webapp.client.ds.WorkflowTaskDataSource;
import cz.cas.lib.proarc.webapp.client.presenter.WorkflowTasksEditor;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*
* @author Jan Pokorsky
*/
public class WorkflowTaskFormView implements Refreshable {
private final ClientMessages i18n;
private final Canvas widget;
private DynamicForm taskForm;
private SelectItem stateItem;
private WorkflowMaterialView materialView;
private final WorkflowTasksEditor handler;
private DynamicForm paramForm;
private final RequiredIfFunction requiredFunc;
private VLayout paramContainer;
private ItemChangedHandler itemChangedHandler;
private final ActionSource actionSource = new ActionSource(this);
public WorkflowTaskFormView(ClientMessages i18n, WorkflowTasksEditor handler) {
this.i18n = i18n;
this.handler = handler;
// params are required just in case of the finished state of the task
this.requiredFunc = new RequiredIfFunction() {
@Override
public boolean execute(FormItem formItem, Object value) {
return State.FINISHED.name().equals(
taskForm.getValueAsString(WorkflowTaskDataSource.FIELD_STATE));
}
};
this.widget = createMainLayout();
setItemChangedHandler();
}
public Canvas getWidget() {
return widget;
}
public boolean isChanged() {
return taskForm.valuesHaveChanged() || paramForm.valuesHaveChanged();
}
public DynamicForm getTask() {
return taskForm;
}
@Override
public void refresh() {
Record task = taskForm.getValuesAsRecord();
if (task.getAttribute(WorkflowTaskDataSource.FIELD_ID) != null) {
setTask(task);
}
}
public void refreshState() {
actionSource.fireEvent();
}
public boolean validate() {
if (taskForm.validate() && paramForm.validate()) {
Map<?,?> params = paramForm.getValues();
for (Iterator<?> it = params.values().iterator(); it.hasNext();) {
Object paramValue = it.next();
if (paramValue instanceof Map) {
it.remove();
}
}
if (params.isEmpty()) {
taskForm.clearValue(WorkflowTaskDataSource.FIELD_PARAMETERS);
} else {
taskForm.setValue(WorkflowTaskDataSource.FIELD_PARAMETERS, params);
}
return true;
} else {
return false;
}
}
public void setTask(Record task) {
if (task != null) {
String taskId = task.getAttribute(WorkflowTaskDataSource.FIELD_ID);
taskForm.clearErrors(true);
taskForm.fetchData(new Criteria(WorkflowTaskDataSource.FIELD_ID, taskId), new DSCallback() {
@Override
public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
Record dataRecord = null;
if (RestConfig.isStatusOk(dsResponse)) {
Record[] records = dsResponse.getData();
if (records.length > 0) {
dataRecord = records[0];
}
}
setPermitedTaskStates(dataRecord);
refreshState();
}
});
setParameters(taskId);
materialView.setTaskMaterials(taskId);
} else {
taskForm.clearValues();
setParameters(null);
materialView.setEmptyMaterials();
}
widget.setDisabled(task == null);
refreshState();
}
private void setPermitedTaskStates(Record task) {
State state = State.WAITING;
try {
if (task != null) {
state = State.valueOf(task.getAttribute(WorkflowTaskDataSource.FIELD_STATE));
}
} catch (IllegalArgumentException e) {
state = State.WAITING;
}
EnumSet<State> permitted;
switch(state) {
case READY:
case STARTED:
permitted = EnumSet.of(State.READY, State.STARTED, State.FINISHED, State.CANCELED);
break;
case CANCELED:
case FINISHED:
permitted = EnumSet.of(State.FINISHED, State.CANCELED);
break;
case WAITING:
default:
permitted = EnumSet.of(State.WAITING, State.READY, State.STARTED, State.FINISHED, State.CANCELED);
break;
}
stateItem.setValueMap(fillTaskStates(permitted));
}
private LinkedHashMap<String, String> fillTaskStates(EnumSet<State> permitted) {
LinkedHashMap<String, String> vm = new LinkedHashMap<String, String>();
LinkedHashMap<String, String> allTaskStates = WorkflowTaskDataSource.getInstance().getAllTaskStates();
for (State s : permitted) {
String label = allTaskStates.get(s.name());
label = label != null ? label : s.name();
vm.put(s.name(), label);
}
return vm;
}
public void setParameters(String taskId) {
if (taskId != null) {
DSRequest dsRequest = new DSRequest();
dsRequest.setWillHandleError(true);
WorkflowParameterDataSource.getInstance().fetchData(
new Criteria(WorkflowModelConsts.PARAMETERPROFILE_TASKID, taskId),
new DSCallback() {
@Override
public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
if (RestConfig.isStatusOk(dsResponse)) {
setParameterRecords(dsResponse.getData());
} else {
setParameterRecords(new Record[0]);
ErrorHandler.warn(dsResponse, dsRequest);
}
}
}, dsRequest);
} else {
setParameterRecords(new Record[0]);
}
}
/** Notifies about form changes. */
private void setItemChangedHandler() {
this.itemChangedHandler = new ItemChangedHandler() {
@Override
public void onItemChanged(ItemChangedEvent event) {
refreshState();
}
};
taskForm.addItemChangedHandler(itemChangedHandler);
}
private Canvas createMainLayout() {
VLayout forms = new VLayout();
forms.setOverflow(Overflow.AUTO);
forms.addMember(createForm());
forms.addMember(createParameterList());
forms.setShowResizeBar(true);
forms.setResizeBarTarget("next");
VLayout main = new VLayout();
main.addMember(createTaskToolbar());
main.addMember(forms);
main.addMember(createMaterialList());
return main;
}
private ToolStrip createTaskToolbar() {
ToolStrip toolbar = Actions.createToolStrip();
RefreshAction refreshAction = new RefreshAction(i18n);
SaveAction saveAction = new SaveAction(i18n) {
@Override
public boolean accept(ActionEvent event) {
return handler != null
&& taskForm.getValue(WorkflowTaskDataSource.FIELD_ID) != null
&& isChanged();
}
@Override
public void performAction(ActionEvent event) {
handler.onSave(WorkflowTaskFormView.this);
}
};
AbstractAction openJobAction = new AbstractAction(
i18n.WorkflowTask_View_OpenJobAction_Title(),
"[SKIN]/actions/edit.png",
i18n.WorkflowTask_View_OpenJobAction_Hint()) {
@Override
public boolean accept(ActionEvent event) {
return handler != null
&& taskForm.getValue(WorkflowTaskDataSource.FIELD_ID) != null;
}
@Override
public void performAction(ActionEvent event) {
String jobId = taskForm.getValueAsString(WorkflowTaskDataSource.FIELD_JOB_ID);
handler.onOpenJob(jobId);
}
};
toolbar.addMember(Actions.asIconButton(refreshAction, this));
toolbar.addMember(Actions.asIconButton(openJobAction, actionSource));
toolbar.addMember(Actions.asIconButton(saveAction, actionSource));
return toolbar;
}
private Widget createForm() {
taskForm = new DynamicForm();
taskForm.setDataSource(WorkflowTaskDataSource.getInstance());
taskForm.setNumCols(3);
taskForm.setColWidths("*", "*", "*");
taskForm.setTitleOrientation(TitleOrientation.TOP);
taskForm.setItemHoverWidth(300);
StaticTextItem jobLabel = new StaticTextItem(WorkflowTaskDataSource.FIELD_JOB_LABEL);
jobLabel.setColSpan("*");
jobLabel.setWidth("*");
jobLabel.setShowTitle(false);
jobLabel.setReadOnlyTextBoxStyle(Editor.CSS_HEADER_INSIDE_FORM);
jobLabel.setTextBoxStyle(Editor.CSS_HEADER_INSIDE_FORM);
final SelectItem owner = new SelectItem(WorkflowTaskDataSource.FIELD_OWNER);
owner.setOptionDataSource(UserDataSource.getInstance());
owner.setValueField(UserDataSource.FIELD_ID);
owner.setDisplayField(UserDataSource.FIELD_USERNAME);
owner.setWidth("*");
owner.setValidators(new RequiredIfValidator(requiredFunc));
TextAreaItem note = new TextAreaItem(WorkflowTaskDataSource.FIELD_NOTE);
note.setStartRow(true);
note.setColSpan("*");
note.setWidth("*");
// title tooltip is broken in SmartGWT 4.0
final FormItemIcon taskHelpIcon = new FormItemIcon();
taskHelpIcon.setSrc("[SKIN]/actions/help.png");
taskHelpIcon.setTabIndex(-1);
taskHelpIcon.setNeverDisable(true);
taskHelpIcon.setShowIfCondition(new FormItemIfFunction() {
@Override
public boolean execute(FormItem item, Object value, DynamicForm form) {
String hint = taskForm.getValueAsString(WorkflowTaskDataSource.FIELD_HINT);
taskHelpIcon.setPrompt(hint);
return hint != null;
}
});
TextItem label = new TextItem(WorkflowTaskDataSource.FIELD_LABEL);
label.setWidth("*");
label.setIcons(taskHelpIcon);
stateItem = new SelectItem(WorkflowTaskDataSource.FIELD_STATE);
stateItem.addChangedHandler(new ChangedHandler() {
@Override
public void onChanged(ChangedEvent event) {
owner.redraw();
paramForm.markForRedraw();
}
});
taskForm.setFields(jobLabel,
label,
owner,
stateItem,
new TextItem(WorkflowTaskDataSource.FIELD_CREATED),
new TextItem(WorkflowTaskDataSource.FIELD_MODIFIED),
new SelectItem(WorkflowTaskDataSource.FIELD_PRIORITY),
note);
return taskForm;
}
private DynamicForm createDefaultParameterForm() {
DynamicForm df = new DynamicForm();
// StaticTextItem msg = new StaticTextItem();
// msg.setShowTitle(false);
// msg.setValue("No parameter!");
// df.setItems(msg);
return df;
}
private DynamicForm createParameterForm(Record[] records) {
if (records == null || records.length == 0) {
return createDefaultParameterForm();
}
DynamicForm df = new DynamicForm();
df.setUseFlatFields(true);
df.setWrapItemTitles(false);
df.setTitleOrientation(TitleOrientation.TOP);
df.setNumCols(3);
df.setColWidths("*", "*", "*");
df.setItemHoverWidth(300);
FormItem[] items = new FormItem[records.length];
Record values = new Record();
for (int i = 0; i < records.length; i++) {
Record record = records[i];
ValueType valueType = ValueType.fromString(
record.getAttribute(WorkflowModelConsts.PARAMETER_VALUETYPE));
DisplayType displayType = DisplayType.fromString(
record.getAttribute(WorkflowModelConsts.PARAMETER_DISPLAYTYPE));
displayType = valueType == ValueType.DATETIME ? DisplayType.DATETIME : displayType;
String paramName = record.getAttribute(WorkflowParameterDataSource.FIELD_NAME);
String fieldName = "f" + i;
items[i] = createFormItem(record, valueType, displayType);
items[i].setName(fieldName);
// use dataPath to solve cases where the valid JSON name is not a valid javascript ID (param.id).
items[i].setDataPath("/" + paramName);
items[i].setTitle(record.getAttribute(WorkflowModelConsts.PARAMETER_PROFILELABEL));
items[i].setTooltip(record.getAttribute(WorkflowModelConsts.PARAMETER_PROFILEHINT));
Object val = getParameterValue(record, valueType, displayType);
if (val != null) {
values.setAttribute(paramName, val);
}
}
df.setItems(items);
df.editRecord(values);
df.addItemChangedHandler(itemChangedHandler);
return df;
}
private Object getParameterValue(Record record, ValueType valueType, DisplayType displayType) {
Object val = record.getAttributeAsObject(WorkflowParameterDataSource.FIELD_VALUE);
if (valueType == ValueType.DATETIME && val instanceof String) {
DateTimeFormat format = DateTimeFormat.getFormat(PredefinedFormat.ISO_8601);
val = format.parse((String) val);
} else if (displayType == DisplayType.CHECKBOX && val instanceof String) {
if (Boolean.TRUE.toString().equalsIgnoreCase((String) val)) {
val = true;
} else if (Boolean.FALSE.toString().equalsIgnoreCase((String) val)) {
val = false;
} else {
try {
val = new BigDecimal((String) val).compareTo(BigDecimal.ZERO) > 0;
} catch (NumberFormatException e) {
// ignore
}
}
} else if (displayType == DisplayType.CHECKBOX && val instanceof Number) {
val = ((Number) val).doubleValue() > 0;
}
return val;
}
private Widget createParameterList() {
paramContainer = new VLayout();
paramContainer.setAutoHeight();
setParameterRecords(null);
return paramContainer;
}
private void setParameterRecords(Record[] records) {
if (paramForm != null) {
paramForm.markForDestroy();
}
paramForm = createParameterForm(records);
paramContainer.setMembers(paramForm);
}
private FormItem createFormItem(Record editedRecord, ValueType valueType, DisplayType displayType) {
FormItem fi = createFormItem(displayType, editedRecord);
Boolean required = editedRecord.getAttributeAsBoolean(WorkflowModelConsts.PARAMETER_REQUIRED);
ArrayList<Validator> validators = new ArrayList<Validator>();
if (required != null && required) {
validators.add(new RequiredIfValidator(requiredFunc));
}
if (valueType == ValueType.NUMBER && displayType != DisplayType.CHECKBOX) {
validators.add(new IsFloatValidator());
}
if (!validators.isEmpty()) {
fi.setValidators(validators.toArray(new Validator[validators.size()]));
}
return fi;
}
private FormItem createFormItem(DisplayType displayType, Record profile) {
String name = profile.getAttribute(WorkflowParameterDataSource.FIELD_NAME);
switch (displayType) {
case SELECT:
SelectItem si = new SelectItem();
setOptions(si, profile);
si.setWidth("*");
return si;
case COMBOBOX:
ComboBoxItem cbi = new ComboBoxItem();
setOptions(cbi, profile);
cbi.setLength(2000);
cbi.setWidth("*");
return cbi;
case CHECKBOX:
CheckboxItem ci = new CheckboxItem();
// the width must be set otherwise it overflows the form
ci.setWidth(150);
ci.setAllowEmptyValue(true);
return ci;
case TEXTAREA:
TextAreaItem tai = new TextAreaItem();
tai.setStartRow(true);
tai.setEndRow(true);
tai.setLength(2000);
tai.setColSpan("*");
tai.setWidth("*");
tai.setHeight(30);
return tai;
case DATETIME:
DateTimeItem di = new DateTimeItem();
return di;
case TEXT:
default:
TextItem ti = new TextItem(name);
ti.setLength(2000);
ti.setWidth("*");
return ti;
}
}
private void setOptions(FormItem item, Record profile) {
String dataSourceId = profile.getAttribute(WorkflowModelConsts.PARAMETER_VALUEMAPID);
if (dataSourceId != null) {
DataSource ds = ValueMapDataSource.getInstance().getOptionDataSource(dataSourceId);
item.setValueField(profile.getAttribute(WorkflowModelConsts.PARAMETER_OPTION_VALUE_FIELD));
item.setOptionDataSource(ds);
item.setDisplayField(profile.getAttribute(WorkflowModelConsts.PARAMETER_OPTION_DISPLAY_FIELD));
}
}
private Widget createMaterialList() {
materialView = new WorkflowMaterialView(i18n);
return materialView.getWidget();
}
}