package org.activityinfo.ui.client.page.config.design;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* 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/gpl-3.0.html>.
* #L%
*/
import com.extjs.gxt.ui.client.binding.FieldBinding;
import com.extjs.gxt.ui.client.binding.FormBinding;
import com.extjs.gxt.ui.client.event.*;
import com.extjs.gxt.ui.client.widget.form.*;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gwt.core.client.GWT;
import org.activityinfo.model.expr.ExprLexer;
import org.activityinfo.model.expr.ExprNode;
import org.activityinfo.model.expr.ExprParser;
import org.activityinfo.model.expr.ExprSyntaxException;
import org.activityinfo.model.expr.FunctionCallNode;
import org.activityinfo.model.expr.SymbolExpr;
import org.activityinfo.i18n.shared.I18N;
import org.activityinfo.i18n.shared.UiConstants;
import org.activityinfo.model.legacy.CuidAdapter;
import org.activityinfo.legacy.shared.model.IndicatorDTO;
import org.activityinfo.model.type.FieldTypeClass;
import org.activityinfo.model.type.TypeRegistry;
import org.activityinfo.model.type.barcode.BarcodeType;
import org.activityinfo.model.type.primitive.BooleanType;
import org.activityinfo.ui.client.widget.legacy.MappingComboBox;
import org.activityinfo.ui.client.widget.legacy.MappingComboBoxBinding;
import org.activityinfo.ui.client.widget.legacy.OnlyValidFieldBinding;
import java.util.List;
class IndicatorForm extends AbstractDesignForm {
private final UiConstants constants = GWT.create(UiConstants.class);
private final FormBinding binding;
private final MappingComboBox<String> typeCombo;
private final TextField<String> unitsField;
private final MappingComboBox aggregationCombo;
private final TextField<String> expressionField;
private final TextField<String> codeField;
private final NumberField idField;
private final LabelField calculatedExpressionLabelDesc;
private final CheckBox calculateAutomatically;
public IndicatorForm() {
super();
binding = new FormBinding(this);
this.setLabelWidth(150);
this.setFieldWidth(200);
idField = new NumberField();
idField.setFieldLabel("ID");
idField.setReadOnly(true);
binding.addFieldBinding(new FieldBinding(idField, "id"));
add(idField);
codeField = new TextField<>();
codeField.setFieldLabel(constants.codeFieldLabel());
codeField.setToolTip(constants.codeFieldLabel());
binding.addFieldBinding(new OnlyValidFieldBinding(codeField, "nameInExpression"));
this.add(codeField);
this.add(new LabelField(constants.nameInExpressionTooltip()));
TextField<String> nameField = new TextField<>();
nameField.setFieldLabel(constants.name());
nameField.setAllowBlank(false);
binding.addFieldBinding(new OnlyValidFieldBinding(nameField, "name"));
this.add(nameField);
typeCombo = new MappingComboBox<>();
typeCombo.setFieldLabel(constants.type());
typeCombo.add(FieldTypeClass.QUANTITY.getId(), I18N.CONSTANTS.fieldTypeQuantity());
typeCombo.add(FieldTypeClass.FREE_TEXT.getId(), I18N.CONSTANTS.fieldTypeText());
typeCombo.add(FieldTypeClass.NARRATIVE.getId(), I18N.CONSTANTS.fieldTypeNarrative());
binding.addFieldBinding(new MappingComboBoxBinding(typeCombo, "type"));
this.add(typeCombo);
unitsField = new TextField<>();
unitsField.setName("units");
unitsField.setFieldLabel(constants.units());
unitsField.setAllowBlank(false);
unitsField.setMaxLength(IndicatorDTO.UNITS_MAX_LENGTH);
binding.addFieldBinding(new OnlyValidFieldBinding(unitsField, "units"));
this.add(unitsField);
aggregationCombo = new MappingComboBox();
aggregationCombo.setFieldLabel(constants.aggregationMethod());
aggregationCombo.add(IndicatorDTO.AGGREGATE_SUM, constants.sum());
aggregationCombo.add(IndicatorDTO.AGGREGATE_AVG, constants.average());
aggregationCombo.add(IndicatorDTO.AGGREGATE_SITE_COUNT, constants.siteCount());
binding.addFieldBinding(new MappingComboBoxBinding(aggregationCombo, "aggregation"));
this.add(aggregationCombo);
this.add(new LabelField("Please note: text and narrative indicators are not yet " +
"available for activities with monthly reporting. " +
"(We're working on it!)"));
typeCombo.addSelectionChangedListener(new SelectionChangedListener<MappingComboBox.Wrapper<String>>() {
@Override
public void selectionChanged(SelectionChangedEvent<MappingComboBox.Wrapper<String>> wrapperSelectionChangedEvent) {
setState();
}
});
calculateAutomatically = new CheckBox();
calculateAutomatically.setFieldLabel(constants.calculateAutomatically());
calculateAutomatically.addListener(Events.Change, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
expressionField.setEnabled(calculateAutomatically.getValue());
}
});
binding.addFieldBinding(new FieldBinding(calculateAutomatically, "calculatedAutomatically"));
this.add(calculateAutomatically);
expressionField = new TextField<>();
expressionField.addKeyListener(new KeyListener() {
@Override
public void componentKeyUp(ComponentEvent event) {
expressionField.validate();
}
});
expressionField.setFieldLabel(constants.calculation());
expressionField.setToolTip(constants.calculatedIndicatorExplanation());
expressionField.setValidator(new Validator() {
@Override
public String validate(Field<?> field, String value) {
return validateExpression(value);
}
});
binding.addFieldBinding(new OnlyValidFieldBinding(expressionField, "expression"));
this.add(expressionField);
calculatedExpressionLabelDesc = new LabelField(constants.calculatedIndicatorExplanation());
this.add(calculatedExpressionLabelDesc);
TextField<String> categoryField = new TextField<>();
categoryField.setName("category");
categoryField.setFieldLabel(constants.category());
categoryField.setMaxLength(IndicatorDTO.MAX_CATEGORY_LENGTH);
binding.addFieldBinding(new OnlyValidFieldBinding(categoryField, "category"));
this.add(categoryField);
TextField<String> listHeaderField = new TextField<>();
listHeaderField.setFieldLabel(constants.listHeader());
listHeaderField.setMaxLength(IndicatorDTO.MAX_LIST_HEADER_LENGTH);
binding.addFieldBinding(new OnlyValidFieldBinding(listHeaderField, "listHeader"));
this.add(listHeaderField);
TextArea descField = new TextArea();
descField.setFieldLabel(constants.description());
binding.addFieldBinding(new OnlyValidFieldBinding(descField, "description"));
this.add(descField);
CheckBox mandatoryCB = new CheckBox();
mandatoryCB.setFieldLabel(constants.mandatory());
binding.addFieldBinding(new FieldBinding(mandatoryCB, "mandatory"));
this.add(mandatoryCB);
hideFieldWhenNull(idField);
binding.addListener(Events.Bind, new Listener<BindingEvent>() {
@Override
public void handleEvent(BindingEvent be) {
setState();
}
});
}
private String validateExpression(String value) {
try {
ExprLexer lexer = new ExprLexer(value);
ExprParser parser = new ExprParser(lexer);
ExprNode expr = parser.parse();
// expr node is created, expression is parsable
// try to check variable names
List<SymbolExpr> placeholderExprList = Lists.newArrayList();
gatherPlaceholderExprs(expr, placeholderExprList);
List<String> existingIndicatorCodes = existingIndicatorCodes();
for (SymbolExpr placeholderExpr : placeholderExprList) {
if (!existingIndicatorCodes.contains(placeholderExpr.getName())) {
return I18N.MESSAGES.doesNotExist(placeholderExpr.getName());
}
}
return null;
} catch (ExprSyntaxException e) {
return e.getMessage();
} catch (Exception e) {
e.printStackTrace();
// ignore : expression is invalid
}
return constants.calculationExpressionIsInvalid();
}
private void gatherPlaceholderExprs(ExprNode node, List<SymbolExpr> placeholderExprList) {
if (node instanceof SymbolExpr) {
placeholderExprList.add((SymbolExpr) node);
} else if (node instanceof FunctionCallNode) {
FunctionCallNode functionCallNode = (FunctionCallNode) node;
List<ExprNode> arguments = functionCallNode.getArguments();
for (ExprNode arg : arguments) {
gatherPlaceholderExprs(arg, placeholderExprList);
}
}
}
private List<String> existingIndicatorCodes() {
final List<String> result = Lists.newArrayList();
List models = IndicatorForm.this.getBinding().getStore().getModels();
for (Object model : models) {
if (model instanceof IndicatorDTO) {
IndicatorDTO indicatorDTO = (IndicatorDTO) model;
result.add(CuidAdapter.indicatorField(indicatorDTO.getId()).asString());
String nameInExpression = indicatorDTO.getNameInExpression();
if (!Strings.isNullOrEmpty(nameInExpression)) {
result.add(nameInExpression);
}
}
}
return result;
}
private void setState() {
if (typeCombo.getValue() != null) {
FieldTypeClass selectedType = TypeRegistry.get().get().getTypeClass(typeCombo.getValue().getWrappedValue());
unitsField.setVisible(selectedType == FieldTypeClass.QUANTITY);
unitsField.setAllowBlank(selectedType != FieldTypeClass.QUANTITY);
if (selectedType != FieldTypeClass.QUANTITY) {
unitsField.setValue("");
}
calculatedExpressionLabelDesc.setEnabled(selectedType == FieldTypeClass.QUANTITY);
calculateAutomatically.setEnabled(selectedType == FieldTypeClass.QUANTITY);
expressionField.setEnabled(selectedType == FieldTypeClass.QUANTITY && calculateAutomatically.getValue());
aggregationCombo.setVisible(selectedType == FieldTypeClass.QUANTITY);
if (idField.getValue() != null && Strings.isNullOrEmpty(codeField.getValue())) {
codeField.setValue(CuidAdapter.indicatorField(idField.getValue().intValue()).asString());
}
}
}
@Override
public FormBinding getBinding() {
return binding;
}
}