org.obiba.onyx.quartz.editor.openAnswer.OpenAnswerPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.obiba.onyx.quartz.editor.openAnswer.OpenAnswerPanel.java

Source

/*******************************************************************************
 * Copyright 2008(c) The OBiBa Consortium. All rights reserved.
 *
 * This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0.
 *
 * 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 org.obiba.onyx.quartz.editor.openAnswer;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.extensions.ajax.markup.html.tabs.AjaxTabbedPanel;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.HeaderlessColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
import org.apache.wicket.extensions.markup.html.tabs.ITab;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.markup.html.form.SimpleFormComponentLabel;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.validator.AbstractValidator;
import org.apache.wicket.validation.validator.MaximumValidator;
import org.apache.wicket.validation.validator.MinimumValidator;
import org.apache.wicket.validation.validator.PatternValidator;
import org.apache.wicket.validation.validator.RangeValidator;
import org.apache.wicket.validation.validator.StringValidator;
import org.obiba.magma.type.TextType;
import org.obiba.onyx.core.data.ComparingDataSource;
import org.obiba.onyx.core.data.IDataSource;
import org.obiba.onyx.core.data.JavascriptDataSource;
import org.obiba.onyx.core.data.VariableDataSource;
import org.obiba.onyx.quartz.core.engine.questionnaire.question.Category;
import org.obiba.onyx.quartz.core.engine.questionnaire.question.OpenAnswerDefinition;
import org.obiba.onyx.quartz.core.engine.questionnaire.question.OpenAnswerDefinitionSuggestion;
import org.obiba.onyx.quartz.core.engine.questionnaire.question.Question;
import org.obiba.onyx.quartz.core.engine.questionnaire.question.Questionnaire;
import org.obiba.onyx.quartz.core.engine.questionnaire.util.QuestionnaireFinder;
import org.obiba.onyx.quartz.editor.OnyxSettings;
import org.obiba.onyx.quartz.editor.QuartzEditorPanel;
import org.obiba.onyx.quartz.editor.behavior.VariableNameBehavior;
import org.obiba.onyx.quartz.editor.behavior.tooltip.HelpTooltipPanel;
import org.obiba.onyx.quartz.editor.locale.LabelsPanel;
import org.obiba.onyx.quartz.editor.locale.LocaleProperties;
import org.obiba.onyx.quartz.editor.locale.LocaleProperties.KeyValue;
import org.obiba.onyx.quartz.editor.locale.LocalePropertiesUtils;
import org.obiba.onyx.quartz.editor.openAnswer.validation.ValidationDataSourceWindow;
import org.obiba.onyx.quartz.editor.utils.MapModel;
import org.obiba.onyx.quartz.editor.utils.SaveablePanel;
import org.obiba.onyx.quartz.editor.variable.VariableUtils;
import org.obiba.onyx.quartz.editor.widget.attributes.AttributesPanel;
import org.obiba.onyx.quartz.editor.widget.sortable.SortableList;
import org.obiba.onyx.util.data.ComparisonOperator;
import org.obiba.onyx.util.data.Data;
import org.obiba.onyx.util.data.DataType;
import org.obiba.onyx.wicket.Images;
import org.obiba.onyx.wicket.behavior.RequiredFormFieldBehavior;
import org.obiba.onyx.wicket.data.DataValidator;
import org.obiba.onyx.wicket.data.IDataValidator;
import org.obiba.onyx.wicket.panel.OnyxEntityList;
import org.obiba.onyx.wicket.reusable.FeedbackWindow;
import org.obiba.wicket.markup.html.table.IColumnProvider;

/**
 *
 */
@SuppressWarnings("serial")
public class OpenAnswerPanel extends Panel implements SaveablePanel {

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "SE_BAD_FIELD", justification = "Need to be be re-initialized upon deserialization")
    @SpringBean
    private LocalePropertiesUtils localePropertiesUtils;

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "SE_BAD_FIELD", justification = "Need to be be re-initialized upon deserialization")
    @SpringBean
    private OnyxSettings onyxSettings;

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "SE_BAD_FIELD", justification = "Need to be be re-initialized upon deserialization")
    @SpringBean
    private OpenAnswerUtils openAnswerUtils;

    private final DropDownChoice<DataType> dataTypeDropDown;

    private final WebMarkupContainer minMaxContainer;

    private final TextField<String> minLength;

    private final TextField<String> maxLength;

    private final TextField<String> minNumeric;

    private final TextField<String> maxNumeric;

    private final TextField<String> minDecimal;

    private final TextField<String> maxDecimal;

    private final TextField<String> beforeDate;

    private final TextField<String> afterDate;

    private SimpleFormComponentLabel minimumLabel;

    private SimpleFormComponentLabel maximumLabel;

    private final OnyxEntityList<ComparingDataSource> validators;

    private final ModalWindow validatorWindow;

    private final IModel<Questionnaire> questionnaireModel;

    private final IModel<Question> questionModel;

    private SortableList<Data> defaultValuesList;

    private final FeedbackPanel feedbackPanel;

    private final FeedbackWindow feedbackWindow;

    private final IModel<LocaleProperties> localePropertiesModel;

    private LabelsPanel labelsPanel;

    @SuppressWarnings("rawtypes")
    private TextField defaultValue;

    private final VariableNameBehavior variableNameBehavior;

    private final TextField<String> variable;

    private final String initialName;

    private final TextField<String> patternField;

    private final TextField<String> sizeField;

    private final TextField<String> rowsField;

    public OpenAnswerPanel(String id, IModel<OpenAnswerDefinition> model, IModel<Category> categoryModel,
            final IModel<Question> questionModel, final IModel<Questionnaire> questionnaireModel,
            IModel<LocaleProperties> localePropertiesModel, final FeedbackPanel feedbackPanel,
            final FeedbackWindow feedbackWindow) {
        super(id, model);
        this.questionModel = questionModel;
        this.questionnaireModel = questionnaireModel;
        this.localePropertiesModel = localePropertiesModel;
        this.feedbackPanel = feedbackPanel;
        this.feedbackWindow = feedbackWindow;

        final Question question = questionModel.getObject();
        final Category category = categoryModel.getObject();
        final OpenAnswerDefinition openAnswer = model.getObject();

        validatorWindow = new ModalWindow("validatorWindow");
        validatorWindow.setCssClassName("onyx");
        validatorWindow.setInitialWidth(850);
        validatorWindow.setInitialHeight(300);
        validatorWindow.setResizable(true);
        validatorWindow.setTitle(new ResourceModel("Validator"));
        add(validatorWindow);

        initialName = model.getObject().getName();
        final TextField<String> name = new TextField<String>("name", new PropertyModel<String>(model, "name"));
        name.setLabel(new ResourceModel("Name"));
        name.add(new RequiredFormFieldBehavior());
        name.add(new PatternValidator(QuartzEditorPanel.ELEMENT_NAME_PATTERN));
        name.add(new AbstractValidator<String>() {
            @Override
            protected void onValidate(IValidatable<String> validatable) {
                if (!StringUtils.equals(initialName, validatable.getValue())) {
                    boolean alreadyContains = false;
                    if (category != null) {
                        Map<String, OpenAnswerDefinition> openAnswerDefinitionsByName = category
                                .getOpenAnswerDefinitionsByName();
                        alreadyContains = openAnswerDefinitionsByName.containsKey(validatable.getValue())
                                && openAnswerDefinitionsByName.get(validatable.getValue()) != openAnswer;
                    }
                    QuestionnaireFinder questionnaireFinder = QuestionnaireFinder
                            .getInstance(questionnaireModel.getObject());
                    questionnaireModel.getObject().setQuestionnaireCache(null);
                    OpenAnswerDefinition findOpenAnswerDefinition = questionnaireFinder
                            .findOpenAnswerDefinition(validatable.getValue());
                    if (alreadyContains
                            || findOpenAnswerDefinition != null && findOpenAnswerDefinition != openAnswer) {
                        error(validatable, "OpenAnswerAlreadyExists");
                    }
                }
            }
        });
        add(name).add(new SimpleFormComponentLabel("nameLabel", name));
        add(new HelpTooltipPanel("nameHelp", new ResourceModel("Name.Tooltip")));

        variable = new TextField<String>("variable", new MapModel<String>(
                new PropertyModel<Map<String, String>>(model, "variableNames"), question.getName()));
        variable.setLabel(new ResourceModel("Variable"));
        add(variable).add(new SimpleFormComponentLabel("variableLabel", variable));
        add(new HelpTooltipPanel("variableHelp", new ResourceModel("Variable.Tooltip")));

        if (category == null) {
            variableNameBehavior = new VariableNameBehavior(name, variable, question.getParentQuestion(), question,
                    null) {
                @Override
                @SuppressWarnings("hiding")
                protected String generateVariableName(Question parentQuestion, Question question, Category category,
                        String name) {
                    if (StringUtils.isBlank(name))
                        return "";
                    if (category != null) {
                        return super.generateVariableName(parentQuestion, question, category, name);
                    }
                    String variableName = parentQuestion == null ? "" : parentQuestion.getName() + ".";
                    if (question != null) {
                        variableName += question.getName() + "." + question.getName() + ".";
                    }
                    return variableName + StringUtils.trimToEmpty(name);
                }
            };
        } else {
            variableNameBehavior = new VariableNameBehavior(name, variable, question.getParentQuestion(), question,
                    category);
        }

        add(variableNameBehavior);

        List<DataType> typeChoices = new ArrayList<DataType>(Arrays.asList(DataType.values()));
        typeChoices.remove(DataType.BOOLEAN);
        typeChoices.remove(DataType.DATA);
        dataTypeDropDown = new DropDownChoice<DataType>("dataType", new PropertyModel<DataType>(model, "dataType"),
                typeChoices, new IChoiceRenderer<DataType>() {
                    @Override
                    public Object getDisplayValue(DataType type) {
                        return new StringResourceModel("DataType." + type, OpenAnswerPanel.this, null).getString();
                    }

                    @Override
                    public String getIdValue(DataType type, int index) {
                        return type.name();
                    }
                });

        dataTypeDropDown.setLabel(new ResourceModel("DataType"));
        dataTypeDropDown.add(new RequiredFormFieldBehavior());
        dataTypeDropDown.setNullValid(false);

        add(dataTypeDropDown).add(new SimpleFormComponentLabel("dataTypeLabel", dataTypeDropDown));
        // add(new HelpTooltipPanel("dataTypeHelp", new ResourceModel("DataType.Tooltip")));

        TextField<String> unit = new TextField<String>("unit", new PropertyModel<String>(model, "unit"));
        unit.setLabel(new ResourceModel("Unit"));
        add(unit).add(new SimpleFormComponentLabel("unitLabel", unit));
        add(new HelpTooltipPanel("unitHelp", new ResourceModel("Unit.Tooltip")));

        PatternValidator numericPatternValidator = new PatternValidator("\\d*");
        // ui Arguments
        Integer size = openAnswer.getInputSize();
        sizeField = new TextField<String>("size", new Model<String>(size == null ? null : String.valueOf(size)));
        sizeField.add(numericPatternValidator);
        sizeField.setLabel(new ResourceModel("SizeLabel"));
        add(new SimpleFormComponentLabel("sizeLabel", sizeField));
        add(sizeField);

        Integer rows = openAnswer.getInputNbRows();
        rowsField = new TextField<String>("rows", new Model<String>(rows == null ? null : String.valueOf(rows)));
        rowsField.add(numericPatternValidator);
        rowsField.setLabel(new ResourceModel("RowsLabel"));
        add(new SimpleFormComponentLabel("rowsLabel", rowsField));
        add(rowsField);

        localePropertiesUtils.load(localePropertiesModel.getObject(), questionnaireModel.getObject(), openAnswer);

        Map<String, Boolean> visibleStates = new HashMap<String, Boolean>();
        if (openAnswer.isSuggestionAnswer()) {
            for (String item : new OpenAnswerDefinitionSuggestion(openAnswer).getSuggestionItems()) {
                visibleStates.put(item, false);
            }
        }
        add(labelsPanel = new LabelsPanel("labels", localePropertiesModel, model, feedbackPanel, feedbackWindow,
                null, visibleStates));

        CheckBox requiredCheckBox = new CheckBox("required", new PropertyModel<Boolean>(model, "required"));
        requiredCheckBox.setLabel(new ResourceModel("AnswerRequired"));
        add(requiredCheckBox);
        add(new SimpleFormComponentLabel("requiredLabel", requiredCheckBox));

        // min/max validators
        String maxValue = null, minValue = null, patternValue = null;
        for (IDataValidator<?> dataValidator : openAnswer.getDataValidators()) {
            IValidator<?> validator = dataValidator.getValidator();
            if (validator instanceof RangeValidator<?>) {
                RangeValidator<?> rangeValidator = (RangeValidator<?>) validator;
                Object minimum = rangeValidator.getMinimum();
                Object maximum = rangeValidator.getMaximum();
                if (dataValidator.getDataType() == DataType.DATE) {
                    if (minimum != null)
                        minValue = onyxSettings.getDateFormat().format((Date) minimum);
                    if (maximum != null)
                        maxValue = onyxSettings.getDateFormat().format((Date) maximum);
                } else {
                    if (minimum != null)
                        minValue = String.valueOf(minimum);
                    if (maximum != null)
                        maxValue = String.valueOf(maximum);
                }
            } else if (validator instanceof StringValidator.MaximumLengthValidator) {
                int maximum = ((StringValidator.MaximumLengthValidator) validator).getMaximum();
                if (maximum > 0)
                    maxValue = String.valueOf(maximum);
            } else if (validator instanceof MaximumValidator<?>) {
                Object maximum = ((MaximumValidator<?>) validator).getMaximum();
                if (dataValidator.getDataType() == DataType.DATE) {
                    if (maximum != null)
                        maxValue = onyxSettings.getDateFormat().format((Date) maximum);
                } else {
                    if (maximum != null)
                        maxValue = String.valueOf(maximum);
                }
            } else if (validator instanceof StringValidator.MinimumLengthValidator) {
                int minimum = ((StringValidator.MinimumLengthValidator) validator).getMinimum();
                if (minimum > 0)
                    minValue = String.valueOf(minimum);
            } else if (validator instanceof MinimumValidator<?>) {
                Object minimum = ((MinimumValidator<?>) validator).getMinimum();
                if (dataValidator.getDataType() == DataType.DATE) {
                    if (minimum != null)
                        minValue = onyxSettings.getDateFormat().format((Date) minimum);
                } else {
                    if (minimum != null)
                        minValue = String.valueOf(minimum);
                }
            } else if (validator instanceof PatternValidator) {
                patternValue = ((PatternValidator) validator).getPattern().toString();
            }
        }

        patternField = new TextField<String>("patternValidator", new Model<String>(patternValue));
        patternField.setLabel(new ResourceModel("PatternLabel"));
        patternField.setOutputMarkupId(true);
        add(new SimpleFormComponentLabel("patternLabel", patternField));
        add(patternField);

        minMaxContainer = new WebMarkupContainer("minMaxContainer");
        minMaxContainer.setOutputMarkupId(true);
        add(minMaxContainer);

        minLength = new TextField<String>("minLength", new Model<String>(minValue), String.class);
        minLength.setLabel(new ResourceModel("Minimum.length"));
        minMaxContainer.add(minLength);

        maxLength = new TextField<String>("maxLength", new Model<String>(maxValue), String.class);
        maxLength.setLabel(new ResourceModel("Maximum.length"));
        minMaxContainer.add(maxLength);

        minNumeric = new TextField<String>("minNumeric", new Model<String>(minValue), String.class);
        minNumeric.setLabel(new ResourceModel("Minimum"));
        minNumeric.add(numericPatternValidator);
        minMaxContainer.add(minNumeric);

        maxNumeric = new TextField<String>("maxNumeric", new Model<String>(maxValue), String.class);
        maxNumeric.setLabel(new ResourceModel("Maximum"));
        maxNumeric.add(numericPatternValidator);
        minMaxContainer.add(maxNumeric);

        PatternValidator decimalPatternValidator = new PatternValidator("\\d*(\\.\\d+)?");
        minDecimal = new TextField<String>("minDecimal", new Model<String>(minValue), String.class);
        minDecimal.setLabel(new ResourceModel("Minimum"));
        minDecimal.add(decimalPatternValidator);
        minMaxContainer.add(minDecimal);

        maxDecimal = new TextField<String>("maxDecimal", new Model<String>(maxValue), String.class);
        maxDecimal.setLabel(new ResourceModel("Maximum"));
        maxDecimal.add(decimalPatternValidator);
        minMaxContainer.add(maxDecimal);

        String patternStr = onyxSettings.getDateFormat().toPattern();

        beforeDate = new TextField<String>("beforeDate", new Model<String>(maxValue), String.class);
        beforeDate.setLabel(new Model<String>(
                new StringResourceModel("Before", this, null).getObject() + " (" + patternStr + ")"));
        minMaxContainer.add(beforeDate);

        afterDate = new TextField<String>("afterDate", new Model<String>(minValue), String.class);
        afterDate.setLabel(new Model<String>(
                new StringResourceModel("After", this, null).getObject() + " (" + patternStr + ")"));
        minMaxContainer.add(afterDate);

        minMaxContainer.add(minimumLabel = new SimpleFormComponentLabel("minimumLabel", minLength));
        minMaxContainer.add(maximumLabel = new SimpleFormComponentLabel("maximumLabel", maxLength));

        setMinMaxLabels(dataTypeDropDown.getModelObject());

        add(validators = new OnyxEntityList<ComparingDataSource>("validators", new ValidationDataSourcesProvider(),
                new ValidationDataSourcesColumnProvider(), new ResourceModel("Validators")));

        AjaxLink<Void> addValidator = new AjaxLink<Void>("addValidator") {
            @Override
            public void onClick(AjaxRequestTarget target) {
                if (dataTypeDropDown.getModelObject() == null) {
                    info(new StringResourceModel("SelectDataTypeFirst", OpenAnswerPanel.this, null).getString());
                    feedbackWindow.setContent(feedbackPanel);
                    feedbackWindow.show(target);
                } else {
                    validatorWindow.setContent(new ValidationDataSourceWindow("content",
                            new Model<ComparingDataSource>(), questionModel, questionnaireModel,
                            dataTypeDropDown.getModelObject(), validatorWindow) {
                        @Override
                        protected void onSave(@SuppressWarnings("hiding") AjaxRequestTarget target,
                                ComparingDataSource comparingDataSource) {
                            openAnswer.addValidationDataSource(comparingDataSource);
                            target.addComponent(validators);
                        }
                    });
                    validatorWindow.show(target);
                }
            }
        };
        addValidator.setOutputMarkupId(true);
        add(addValidator.add(new Image("img", Images.ADD)));

        dataTypeDropDown.add(new OnChangeAjaxBehavior() {

            @SuppressWarnings("incomplete-switch")
            @Override
            protected void onUpdate(AjaxRequestTarget target) {
                setFieldType();
                String value = dataTypeDropDown.getValue(); // use value because model is not set if validation error
                DataType valueOf = DataType.valueOf(value);
                if (value != null) {
                    OpenAnswerDefinition openAnswerDefinition = (OpenAnswerDefinition) getDefaultModelObject();
                    for (Data data : openAnswerDefinition.getDefaultValues()) {

                        switch (valueOf) {
                        case DATE:
                            try {
                                onyxSettings.getDateFormat().parse(data.getValueAsString());
                            } catch (ParseException nfe) {
                                error(new StringResourceModel("InvalidCastType", OpenAnswerPanel.this, null)
                                        .getObject());
                                showFeedbackErrorAndReset(target);
                                return;
                            }
                            break;
                        case DECIMAL:
                            try {
                                Double.parseDouble(data.getValueAsString());
                            } catch (NumberFormatException nfe) {
                                error(new StringResourceModel("InvalidCastType", OpenAnswerPanel.this, null)
                                        .getObject());
                                showFeedbackErrorAndReset(target);
                                return;
                            }
                            break;
                        case INTEGER:
                            if (data.getType() == DataType.DECIMAL) {
                                Double d = data.getValue();
                                if (d != d.longValue()) {
                                    error(new StringResourceModel("InvalidCastType", OpenAnswerPanel.this, null)
                                            .getObject());
                                    showFeedbackErrorAndReset(target);
                                    return;
                                }
                            } else {
                                try {
                                    Long.parseLong(data.getValueAsString());
                                } catch (NumberFormatException nfe) {
                                    error(new StringResourceModel("InvalidCastType", OpenAnswerPanel.this, null)
                                            .getObject());
                                    showFeedbackErrorAndReset(target);
                                    return;
                                }
                            }
                            break;
                        case TEXT:
                            break;
                        }
                    }
                    for (Data data : openAnswerDefinition.getDefaultValues()) {
                        switch (valueOf) {
                        case DATE:
                            try {
                                data.setTypeAndValue(valueOf,
                                        onyxSettings.getDateFormat().parse(data.getValueAsString()));
                            } catch (ParseException e) {
                                throw new RuntimeException(e);
                            }
                            break;
                        case DECIMAL:
                            data.setTypeAndValue(valueOf, Double.parseDouble(data.getValueAsString()));
                            break;
                        case INTEGER:
                            if (data.getType() == DataType.DECIMAL) {
                                data.setTypeAndValue(valueOf, ((Double) data.getValue()).longValue());
                            } else {
                                data.setTypeAndValue(valueOf, Integer.parseInt(data.getValueAsString()));
                            }
                            break;
                        case TEXT:
                            data.setTypeAndValue(valueOf,
                                    data.getType() == DataType.DATE
                                            ? onyxSettings.getDateFormat().format(data.getValue())
                                            : data.getValueAsString());
                            break;
                        }
                    }
                }
                setMinMaxLabels(value == null ? null : valueOf);
                target.addComponent(minMaxContainer);
                target.addComponent(patternField);
                defaultValuesList.refreshList(target);
            }

            private void showFeedbackErrorAndReset(AjaxRequestTarget target) {
                dataTypeDropDown.setModelObject(openAnswer.getDefaultValues().get(0).getType());
                target.addComponent(dataTypeDropDown);
                OpenAnswerPanel.this.feedbackWindow.setContent(OpenAnswerPanel.this.feedbackPanel);
                OpenAnswerPanel.this.feedbackWindow.show(target);
            }
        });

        final IModel<String> addDefaultValuesModel = new Model<String>();

        List<ITab> tabs = new ArrayList<ITab>();
        tabs.add(new AbstractTab(new ResourceModel("Add.simple")) {
            @Override
            public Panel getPanel(String panelId) {
                return new SimpleAddPanel(panelId, addDefaultValuesModel);
            }
        });
        tabs.add(new AbstractTab(new ResourceModel("Add.bulk")) {
            @Override
            public Panel getPanel(String panelId) {
                return new BulkAddPanel(panelId, addDefaultValuesModel);
            }
        });
        add(new AjaxTabbedPanel("addTabs", tabs));

        defaultValuesList = new SortableList<Data>("defaultValues", openAnswer.getDefaultValues(), true) {

            @Override
            public Component getItemTitle(@SuppressWarnings("hiding") String id, Data data) {
                return new Label(id,
                        data.getType() == DataType.DATE ? onyxSettings.getDateFormat().format(data.getValue())
                                : data.getValueAsString());
            }

            @Override
            public void editItem(Data t, AjaxRequestTarget target) {

            }

            @SuppressWarnings("unchecked")
            @Override
            public void deleteItem(final Data data, AjaxRequestTarget target) {
                ((OpenAnswerDefinition) OpenAnswerPanel.this.getDefaultModelObject()).removeDefaultData(data);
                for (Locale locale : OpenAnswerPanel.this.localePropertiesModel.getObject().getLocales()) {
                    List<KeyValue> list = OpenAnswerPanel.this.localePropertiesModel.getObject()
                            .getElementLabels(openAnswer).get(locale);
                    Collection<KeyValue> toDelete = Collections2.filter(list, new Predicate<KeyValue>() {

                        @Override
                        public boolean apply(KeyValue input) {
                            return input.getKey().equals(data.getValue().toString());
                        }

                    });
                    list.remove(toDelete.iterator().next());
                }
                OpenAnswerPanel.this.addOrReplace(
                        labelsPanel = new LabelsPanel("labels", OpenAnswerPanel.this.localePropertiesModel,
                                (IModel<OpenAnswerDefinition>) OpenAnswerPanel.this.getDefaultModel(),
                                OpenAnswerPanel.this.feedbackPanel, OpenAnswerPanel.this.feedbackWindow));
                target.addComponent(labelsPanel);
                refreshList(target);
            }

            @Override
            public Button[] getButtons() {
                return null;
            }

        };
        add(defaultValuesList);
        add(new HelpTooltipPanel("defaultValuesHelp", new ResourceModel("DefaultValues.Tooltip")));
    }

    private class SimpleAddPanel extends Panel {

        private static final long serialVersionUID = 6783243062332586965L;

        @SuppressWarnings({ "rawtypes", "unchecked" })
        private SimpleAddPanel(String id, IModel<String> model) {
            super(id, model);
            Form<String> form = new Form<String>("form", model);
            form.setMultiPart(false);
            add(form);
            defaultValue = new TextField("defaultValue", model);
            defaultValue.setOutputMarkupId(true);
            defaultValue.setLabel(new ResourceModel("NewDefaultValue"));
            setFieldType();
            form.add(defaultValue);
            form.add(new SimpleFormComponentLabel("defaultValueLabel", defaultValue));
            AjaxButton simpleAddButton = new AjaxButton("addButton", form) {
                @Override
                protected void onSubmit(AjaxRequestTarget target, Form<?> form1) {
                    if (defaultValue.getModelObject() == null)
                        return;
                    OpenAnswerDefinition openAnswerDefinition = (OpenAnswerDefinition) OpenAnswerPanel.this
                            .getDefaultModelObject();
                    String dataTypeValue = dataTypeDropDown.getValue();
                    if (StringUtils.isBlank(dataTypeValue)) {
                        error(new StringResourceModel("ChooseDataType", OpenAnswerPanel.this, null).getObject());
                        return;
                    }
                    DataType dataTypeEnum = DataType.valueOf(dataTypeValue);
                    openAnswerDefinition.setDataType(dataTypeEnum);
                    String strValueOf = String.valueOf(defaultValue.getModelObject());
                    addDefaultValue(openAnswerDefinition, strValueOf);
                    LocaleProperties localeProperties = localePropertiesModel.getObject();
                    localePropertiesUtils.load(localeProperties, questionnaireModel.getObject(),
                            openAnswerDefinition);
                    localeProperties.updateValue(openAnswerDefinition, strValueOf, strValueOf);
                    defaultValue.setModelObject(null);
                    OpenAnswerPanel.this.addOrReplace(labelsPanel = new LabelsPanel("labels", localePropertiesModel,
                            (IModel<OpenAnswerDefinition>) OpenAnswerPanel.this.getDefaultModel(), feedbackPanel,
                            feedbackWindow));
                    target.addComponent(labelsPanel);
                    target.addComponent(defaultValue);
                    defaultValuesList.refreshList(target);
                }

                @Override
                protected void onError(AjaxRequestTarget target, Form<?> form1) {
                    feedbackWindow.setContent(feedbackPanel);
                    feedbackWindow.show(target);
                }
            };
            defaultValue.add(new AttributeAppender("onkeypress", true,
                    new Model<String>(buildPressEnterScript(simpleAddButton)), " "));
            simpleAddButton.add(new Image("img", Images.ADD));
            form.add(simpleAddButton);
        }
    }

    private static String buildPressEnterScript(AjaxButton addButton) {
        return "if (event.keyCode == 13) {document.getElementById('" + addButton.getMarkupId()
                + "').click(); return false;} else {return true;};";
    }

    @SuppressWarnings("unchecked")
    private void setFieldType() {
        String value = dataTypeDropDown.getValue();
        if (!isValidStringEnum(value))
            return;
        defaultValue.setType(getType(DataType.valueOf(value)));
    }

    private static boolean isValidStringEnum(String value) {
        for (DataType data : DataType.values()) {
            if (data.name().equals(value))
                return true;
        }
        return false;
    }

    private class BulkAddPanel extends Panel {

        private static final long serialVersionUID = -5146510588970005742L;

        private BulkAddPanel(String id, IModel<String> model) {
            super(id, model);
            Form<String> form = new Form<String>("form", model);
            form.setMultiPart(false);
            add(form);
            final TextArea<String> defaultValues = new TextArea<String>("defaultValues", model);
            defaultValues.setOutputMarkupId(true);
            defaultValues.setLabel(new ResourceModel("NewDefaultValues"));
            form.add(defaultValues);
            form.add(new SimpleFormComponentLabel("defaultValuesLabel", defaultValues));
            AjaxSubmitLink bulkAddLink = new AjaxSubmitLink("bulkAddLink") {

                @SuppressWarnings("unchecked")
                @Override
                protected void onSubmit(AjaxRequestTarget target, Form<?> form1) {
                    String[] names = StringUtils.split(defaultValues.getModelObject(), ',');
                    if (names == null)
                        return;
                    String dataTypeValue = dataTypeDropDown.getValue();
                    if (StringUtils.isBlank(dataTypeValue)) {
                        error(new StringResourceModel("ChooseDataType", OpenAnswerPanel.this, null).getObject());
                        return;
                    }
                    OpenAnswerDefinition openAnswerDefinition = (OpenAnswerDefinition) OpenAnswerPanel.this
                            .getDefaultModelObject();
                    openAnswerDefinition.setDataType(DataType.valueOf(dataTypeValue));
                    for (String name : names) {
                        name = StringUtils.trimToNull(name);
                        if (name == null)
                            continue;
                        if (!openAnswerUtils.isValidDefaultValue(DataType.valueOf(dataTypeValue), name)) {
                            error(new StringResourceModel("InvalidCastType", OpenAnswerPanel.this, null)
                                    .getObject());
                            return;
                        }
                    }
                    for (String name : new LinkedHashSet<String>(Arrays.asList(names))) {
                        addDefaultValue(openAnswerDefinition, name);
                    }
                    defaultValues.setModelObject(null);
                    LocaleProperties localeProperties = localePropertiesModel.getObject();
                    localePropertiesUtils.load(localeProperties, questionnaireModel.getObject(),
                            openAnswerDefinition);

                    for (String name : new LinkedHashSet<String>(Arrays.asList(names))) {
                        localeProperties.updateValue(openAnswerDefinition, name, name);
                    }

                    labelsPanel = new LabelsPanel("labels", localePropertiesModel,
                            (IModel<OpenAnswerDefinition>) OpenAnswerPanel.this.getDefaultModel(), feedbackPanel,
                            feedbackWindow);
                    OpenAnswerPanel.this.addOrReplace(labelsPanel);
                    target.addComponent(labelsPanel);
                    target.addComponent(defaultValues);
                    defaultValuesList.refreshList(target);
                }

                @Override
                protected void onError(AjaxRequestTarget target, Form<?> form1) {
                    feedbackWindow.setContent(feedbackPanel);
                    feedbackWindow.show(target);
                }
            };

            bulkAddLink.add(new Image("bulkAddImg", Images.ADD)
                    .add(new AttributeModifier("title", true, new ResourceModel("Add"))));
            form.add(bulkAddLink);
        }
    }

    private static void addDefaultValue(OpenAnswerDefinition openAnswerDefinition, final String name) {
        Collection<Data> collectionEqualName = Collections2.filter(openAnswerDefinition.getDefaultValues(),
                new Predicate<Data>() {

                    @Override
                    public boolean apply(Data input) {
                        return input.getValueAsString().equals(name);
                    }
                });
        if (collectionEqualName.isEmpty()) {
            openAnswerDefinition.addDefaultValue(name);
        }
    }

    private static Class<?> getType(DataType dataType) {
        switch (dataType) {
        case DATE:
            return Date.class;
        case DECIMAL:
            return Double.class;
        case INTEGER:
            return Integer.class;
        case TEXT:
            return String.class;
        default:
            throw new RuntimeException("Unknown dataType " + dataType);
        }
    }

    /**
     * @param target
     */
    @Override
    @SuppressWarnings("incomplete-switch")
    public void onSave(AjaxRequestTarget target) {
        if (!variableNameBehavior.isVariableNameDefined()) {
            variable.setModelObject(null);
        }

        OpenAnswerDefinition opa = (OpenAnswerDefinition) getDefaultModelObject();

        // update value type of JavascriptDataSource
        for (ComparingDataSource cds : opa.getValidationDataSources()) {
            if (cds.getDataSourceRight() instanceof JavascriptDataSource) {
                JavascriptDataSource dataSourceRight = (JavascriptDataSource) cds.getDataSourceRight();
                String name = VariableUtils.convertToValueType(opa.getDataType()).getName();
                if (name.equals(TextType.get().getName())) {
                    List<ComparisonOperator> comparisonOperatorAsList = Arrays.asList(ComparisonOperator.eq,
                            ComparisonOperator.ne, ComparisonOperator.in);
                    if (!comparisonOperatorAsList.contains(cds.getComparisonOperator())) {
                        error(new StringResourceModel("InvalidType", this, null).getObject());
                    }
                }
                dataSourceRight.setValueType(name);
            }
        }

        // TODO use a specific model instead of use onSave Method
        opa.setInputSize(StringUtils.isBlank(sizeField.getValue()) ? null : Integer.valueOf(sizeField.getValue()));
        opa.setNbRows(StringUtils.isBlank(rowsField.getValue()) ? null : Integer.valueOf(rowsField.getValue()));

        // TODO use a specific model instead of use onSave Method
        opa.clearDataValidators();
        if (StringUtils.isNotBlank(patternField.getValue())) {
            opa.addDataValidator(
                    new DataValidator(new PatternValidator(patternField.getValue()), opa.getDataType()));
        }
        switch (opa.getDataType()) {
        case DATE:
            // TODO incomplete for Date
            try {
                DateFormat dateFormat = onyxSettings.getDateFormat();
                if (StringUtils.isNotBlank(afterDate.getValue())) {
                    MinimumValidator<Date> minimumValidator = new MinimumValidator<Date>(
                            dateFormat.parse(afterDate.getValue()));
                    DataValidator dataValidator = new DataValidator(minimumValidator, DataType.DATE);
                    opa.addDataValidator(dataValidator);
                }
                if (StringUtils.isNotBlank(beforeDate.getValue())) {
                    MaximumValidator<Date> maximumValidator = new MaximumValidator<Date>(
                            dateFormat.parse(beforeDate.getValue()));
                    DataValidator dataValidator = new DataValidator(maximumValidator, DataType.DATE);
                    opa.addDataValidator(dataValidator);
                }
            } catch (ParseException e) {
                error(new StringResourceModel("InvalidDate", this, null).getObject());
                feedbackWindow.setContent(feedbackPanel);
                feedbackWindow.show(target);
                return;
            }
            break;
        case DECIMAL:
            if (StringUtils.isNotBlank(minDecimal.getValue())) {
                MinimumValidator<Double> minimumValidator = new MinimumValidator<Double>(
                        Double.parseDouble(minDecimal.getValue()));
                DataValidator dataValidator = new DataValidator(minimumValidator, DataType.DECIMAL);
                opa.addDataValidator(dataValidator);
            }
            if (StringUtils.isNotBlank(maxDecimal.getValue())) {
                MaximumValidator<Double> maximumValidator = new MaximumValidator<Double>(
                        Double.parseDouble(maxDecimal.getValue()));
                DataValidator dataValidator = new DataValidator(maximumValidator, DataType.DECIMAL);
                opa.addDataValidator(dataValidator);
            }
            break;
        case INTEGER:
            if (StringUtils.isNotBlank(minNumeric.getValue())) {
                MinimumValidator<Long> minimumValidator = new MinimumValidator<Long>(
                        Long.parseLong(minNumeric.getValue()));
                DataValidator dataValidator = new DataValidator(minimumValidator, DataType.INTEGER);
                opa.addDataValidator(dataValidator);
            }
            if (StringUtils.isNotBlank(maxNumeric.getValue())) {
                MaximumValidator<Long> maximumValidator = new MaximumValidator<Long>(
                        Long.parseLong(maxNumeric.getValue()));
                DataValidator dataValidator = new DataValidator(maximumValidator, DataType.INTEGER);
                opa.addDataValidator(dataValidator);
            }
            break;
        case TEXT:
            if (StringUtils.isNotBlank(minLength.getValue())) {
                StringValidator minimumLengthValidator = StringValidator
                        .minimumLength(Integer.parseInt(minLength.getValue()));
                DataValidator dataValidator = new DataValidator(minimumLengthValidator, DataType.TEXT);
                opa.addDataValidator(dataValidator);
            }
            if (StringUtils.isNotBlank(maxLength.getValue())) {
                StringValidator maximumLengthValidator = StringValidator
                        .maximumLength(Integer.parseInt(maxLength.getValue()));
                DataValidator dataValidator = new DataValidator(maximumLengthValidator, DataType.TEXT);
                opa.addDataValidator(dataValidator);
            }
            break;
        }
    }

    @SuppressWarnings("incomplete-switch")
    private void setMinMaxLabels(DataType type) {
        patternField.setEnabled(false);
        if (type == null) {
            setMinimumLabel(minLength);
            setMaximumLabel(maxLength);
            minLength.setVisible(true).setEnabled(false);
            maxLength.setVisible(true).setEnabled(false);
            clearAndHide(minNumeric, maxNumeric, minDecimal, maxDecimal, beforeDate, afterDate);
        } else {
            switch (type) {
            case TEXT:
                setMinimumLabel(minLength);
                setMaximumLabel(maxLength);
                patternField.setEnabled(true);
                minLength.setVisible(true).setEnabled(true);
                maxLength.setVisible(true).setEnabled(true);
                clearAndHide(minNumeric, maxNumeric, minDecimal, maxDecimal, beforeDate, afterDate);
                break;

            case DECIMAL:
                setMinimumLabel(minDecimal);
                setMaximumLabel(maxDecimal);
                minDecimal.setVisible(true);
                maxDecimal.setVisible(true);
                clearAndHide(minNumeric, maxNumeric, minLength, maxLength, beforeDate, afterDate);
                break;
            case INTEGER:
                setMinimumLabel(minNumeric);
                setMaximumLabel(maxNumeric);
                minNumeric.setVisible(true);
                maxNumeric.setVisible(true);
                clearAndHide(minDecimal, maxDecimal, minLength, maxLength, beforeDate, afterDate);
                break;

            case DATE:
                setMinimumLabel(afterDate);
                setMaximumLabel(beforeDate);
                beforeDate.setVisible(true);
                afterDate.setVisible(true);
                clearAndHide(minLength, maxLength, minNumeric, maxNumeric, minDecimal, maxDecimal);
                break;
            }
        }
    }

    private static void clearAndHide(FormComponent<?>... components) {
        if (components != null) {
            for (FormComponent<?> component : components) {
                component.setModelObject(null);
                component.setVisible(false);
            }
        }
    }

    private void setMinimumLabel(FormComponent<?> component) {
        SimpleFormComponentLabel newLabel = new SimpleFormComponentLabel(minimumLabel.getId(), component);
        minimumLabel.replaceWith(newLabel);
        minimumLabel = newLabel;
    }

    private void setMaximumLabel(FormComponent<?> component) {
        SimpleFormComponentLabel newLabel = new SimpleFormComponentLabel(maximumLabel.getId(), component);
        maximumLabel.replaceWith(newLabel);
        maximumLabel = newLabel;
    }

    private class ValidationDataSourcesProvider extends SortableDataProvider<ComparingDataSource> {

        private static final long serialVersionUID = -2817548332176807142L;

        private final List<ComparingDataSource> dataSources;

        private ValidationDataSourcesProvider() {
            dataSources = ((OpenAnswerDefinition) getDefaultModelObject()).getValidationDataSources();
        }

        @Override
        public Iterator<ComparingDataSource> iterator(int first, int count) {
            return dataSources.iterator();
        }

        @Override
        public int size() {
            return dataSources.size();
        }

        @Override
        public IModel<ComparingDataSource> model(ComparingDataSource ds) {
            return new Model<ComparingDataSource>(ds);
        }

    }

    private class ValidationDataSourcesColumnProvider
            implements IColumnProvider<ComparingDataSource>, Serializable {

        private static final long serialVersionUID = 3352730495898474228L;

        private final List<IColumn<ComparingDataSource>> columns = new ArrayList<IColumn<ComparingDataSource>>();

        private ValidationDataSourcesColumnProvider() {
            columns.add(new AbstractColumn<ComparingDataSource>(new ResourceModel("Operator")) {
                @Override
                public void populateItem(Item<ICellPopulator<ComparingDataSource>> cellItem, String componentId,
                        IModel<ComparingDataSource> rowModel) {
                    cellItem.add(new Label(componentId,
                            new ResourceModel("Operator." + rowModel.getObject().getComparisonOperator())));
                }
            });
            columns.add(new AbstractColumn<ComparingDataSource>(new ResourceModel("VariableScript")) {
                @Override
                public void populateItem(Item<ICellPopulator<ComparingDataSource>> cellItem, String componentId,
                        IModel<ComparingDataSource> rowModel) {
                    IDataSource datasource = rowModel.getObject().getDataSourceRight();
                    if (datasource instanceof VariableDataSource) {
                        cellItem.add(new Label(componentId, ((VariableDataSource) datasource).getPath()));
                    } else if (datasource instanceof JavascriptDataSource) {
                        cellItem.add(new Label(componentId,
                                StringUtils.abbreviate(((JavascriptDataSource) datasource).getScript(), 20)));
                    }
                }
            });

            columns.add(new HeaderlessColumn<ComparingDataSource>() {
                @Override
                public void populateItem(Item<ICellPopulator<ComparingDataSource>> cellItem, String componentId,
                        IModel<ComparingDataSource> rowModel) {
                    cellItem.add(new ValidatorsActionFragment(componentId, rowModel));
                }
            });

        }

        @Override
        public List<IColumn<ComparingDataSource>> getAdditionalColumns() {
            return null;
        }

        @Override
        public List<String> getColumnHeaderNames() {
            return null;
        }

        @Override
        public List<IColumn<ComparingDataSource>> getDefaultColumns() {
            return columns;
        }

        @Override
        public List<IColumn<ComparingDataSource>> getRequiredColumns() {
            return columns;
        }

    }

    private class ValidatorsActionFragment extends Fragment {

        private static final long serialVersionUID = 3539962632471597874L;

        private ValidatorsActionFragment(String id, final IModel<ComparingDataSource> rowModel) {
            super(id, "validatorsAction", OpenAnswerPanel.this, rowModel);
            final ComparingDataSource comparingDataSource = rowModel.getObject();

            add(new AjaxLink<ComparingDataSource>("editLink", rowModel) {
                @Override
                public void onClick(AjaxRequestTarget target) {
                    validatorWindow.setContent(new ValidationDataSourceWindow("content", rowModel, questionModel,
                            questionnaireModel, dataTypeDropDown.getModelObject(), validatorWindow) {
                        @Override
                        protected void onSave(@SuppressWarnings("hiding") AjaxRequestTarget target,
                                ComparingDataSource editedComparingDataSource) {
                            OpenAnswerDefinition openAnswer = (OpenAnswerDefinition) OpenAnswerPanel.this
                                    .getDefaultModelObject();
                            openAnswer.removeValidationDataSource(comparingDataSource);
                            openAnswer.addValidationDataSource(editedComparingDataSource);
                            target.addComponent(validators);
                        }
                    });
                    validatorWindow.show(target);
                }
            }.add(new Image("editImg", Images.EDIT)
                    .add(new AttributeModifier("title", true, new ResourceModel("Edit")))));

            add(new AjaxLink<ComparingDataSource>("deleteLink", rowModel) {
                @Override
                public void onClick(AjaxRequestTarget target) {
                    OpenAnswerDefinition openAnswer = (OpenAnswerDefinition) OpenAnswerPanel.this
                            .getDefaultModelObject();
                    openAnswer.removeValidationDataSource(comparingDataSource);
                    target.addComponent(validators);
                }
            }.add(new Image("deleteImg", Images.DELETE)
                    .add(new AttributeModifier("title", true, new ResourceModel("Delete")))));

        }
    }

}