org.sigmah.shared.dto.element.TextAreaElementDTO.java Source code

Java tutorial

Introduction

Here is the source code for org.sigmah.shared.dto.element.TextAreaElementDTO.java

Source

package org.sigmah.shared.dto.element;

/*
 * #%L
 * Sigmah
 * %%
 * Copyright (C) 2010 - 2016 URD
 * %%
 * 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.allen_sauer.gwt.log.client.Log;
import java.util.Date;

import org.sigmah.client.i18n.I18N;
import org.sigmah.client.ui.widget.HistoryTokenText;
import org.sigmah.client.util.DateUtils;
import org.sigmah.client.util.ToStringBuilder;
import org.sigmah.shared.command.result.ValueResult;
import org.sigmah.shared.dto.element.event.RequiredValueEvent;
import org.sigmah.shared.dto.element.event.ValueEvent;
import org.sigmah.shared.dto.history.HistoryTokenListDTO;

import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.DatePickerEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.form.DateField;
import com.extjs.gxt.ui.client.widget.form.NumberField;
import com.extjs.gxt.ui.client.widget.form.TextArea;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import org.sigmah.shared.dto.referential.TextAreaType;

/**
 * TextAreaElementDTO.
 * 
 * @author Denis Colliot (dcolliot@ideia.fr)
 */
public class TextAreaElementDTO extends FlexibleElementDTO {

    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = 8520711106031085130L;

    public static final String ENTITY_NAME = "element.TextAreaElement";

    /**
     * {@inheritDoc}
     */
    @Override
    public String getEntityName() {
        // Gets the entity name mapped by the current DTO starting from the "server.domain" package name.
        return ENTITY_NAME;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void appendToString(final ToStringBuilder builder) {
        builder.append("type", getType());
        builder.append("minValue", getMinValue());
        builder.append("maxValue", getMaxValue());
        builder.append("length", getLength());
        builder.append("isDecimal", getIsDecimal());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Component getComponent(ValueResult valueResult, boolean enabled) {

        final TextField<?> field;

        // Checks the type of the expected value to build the corrected
        // component.
        final TextAreaType type = TextAreaType.fromCode(getType());
        if (type != null)
            switch (type) {
            case DATE:
                field = createDateField(valueResult);
                break;
            case NUMBER:
                field = createNumberField(valueResult);
                break;
            case PARAGRAPH:
                field = createParagraphField(valueResult);
                break;
            case TEXT:
                field = createTextField(valueResult);
                break;
            default:
                throw new UnsupportedOperationException("Given type '" + type + "' is not supported yet.");
            }
        else {
            // A case where type is null exists in production but is the result
            // of a bug. Until the cause is found and fixed, null is handled
            // the same as PARAGRAPH.
            // TODO: Should throw an exception instead of silently ignoring the null value.
            Log.warn("No textarea type is specified for the textarea element '" + getLabel()
                    + "'. Using paragraph instead.");
            field = createParagraphField(valueResult);
        }

        // Sets the global properties.
        field.setAllowBlank(true);
        field.setFieldLabel(getLabel());

        field.setEnabled(enabled);

        return field;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isCorrectRequiredValue(ValueResult result) {

        if (result == null || !result.isValueDefined()) {
            return false;
        }

        final String value = result.getValueObject();
        final boolean correct;

        final TextAreaType type = TextAreaType.fromCode(getType());
        if (type != null)
            switch (type) {
            case DATE:
                correct = isCorrectRequiredDateValue(value);
                break;
            case NUMBER:
                correct = isCorrectRequiredNumberValue(value);
                break;
            case PARAGRAPH:
            case TEXT:
                correct = isCorrectRequiredStringValue(value);
                break;
            default:
                throw new UnsupportedOperationException("Given type '" + type + "' is not supported yet.");
            }
        else {
            // A case where type is null exists in production but is the result
            // of a bug. Until the cause is found and fixed, null is handled
            // the same as PARAGRAPH.
            // TODO: Should throw an exception instead of silently ignoring the null value.
            Log.warn("No textarea type is specified for the textarea element '" + getLabel()
                    + "'. Using paragraph instead.");
            correct = isCorrectRequiredStringValue(value);
        }

        return correct;
    }

    /**
     * Method in charge of firing value events.
     * 
     * @param value
     *          The raw value which is serialized to the server and saved to the data layer.
     * @param isValueOn
     *          If the value is correct.
     */
    private void fireEvents(String value, boolean isValueOn) {

        handlerManager.fireEvent(new ValueEvent(TextAreaElementDTO.this, value));

        // Required element ?
        if (getValidates()) {
            handlerManager.fireEvent(new RequiredValueEvent(isValueOn));
        }
    }

    private String formatDate(String value) {
        if (value != null) {
            try {
                final DateTimeFormat formatter = DateUtils.DATE_SHORT;
                return formatter.format(new Date(Long.parseLong(value)));
            } catch (NumberFormatException e) {
                return "";
            }
        } else {
            return "";
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object renderHistoryToken(HistoryTokenListDTO token) {

        if (getType() != null && getType() == 'D') {
            return new HistoryTokenText(formatDate(token.getTokens().get(0).getValue()));
        } else {
            return super.renderHistoryToken(token);
        }
    }

    @Override
    public String toHTML(String value) {
        if (value == null || value.length() == 0) {
            return "";
        }

        if (getType() != null && getType() == 'D') {
            return formatDate(value);
        } else {
            return value.replace("\n", "<br>");
        }
    }

    // --
    // Utility methods.
    // --

    /**
     * Creates a new <code>NumberField</code> for this element.
     * 
     * @param valueResult
     *          Initial value to set.
     * @return A new <code>NumberField</code>.
     */
    private TextField<Number> createNumberField(final ValueResult valueResult) {

        final NumberField numberField = new NumberField();
        final boolean isDecimal = Boolean.TRUE.equals(getIsDecimal());

        numberField.setAllowDecimals(isDecimal);
        numberField.setAllowNegative(true);
        preferredWidth = FlexibleElementDTO.NUMBER_FIELD_WIDTH;

        // Decimal value
        if (isDecimal) {
            numberField.setFormat(NumberFormat.getDecimalFormat());

            // Sets the value to the field.
            if (valueResult != null && valueResult.isValueDefined()) {
                numberField.setValue(Double.parseDouble(valueResult.getValueObject()));
            }
        }
        // Non-decimal value
        else {
            numberField.setFormat(NumberFormat.getFormat("#"));

            // Sets the value to the field.
            if (valueResult != null && valueResult.isValueDefined()) {
                numberField.setValue(Long.parseLong(valueResult.getValueObject()));
            }
        }

        // Sets the min value.
        final Long minValue = getMinValue();
        if (minValue != null) {
            numberField.setMinValue(minValue);
        }

        // Sets the min value.
        final Long maxValue = getMaxValue();
        if (maxValue != null) {
            numberField.setMaxValue(maxValue);
        }

        // Sets tooltip.
        numberField.setToolTip(I18N.MESSAGES.flexibleElementTextAreaNumberRange(
                isDecimal ? I18N.CONSTANTS.flexibleElementDecimalValue()
                        : I18N.CONSTANTS.flexibleElementNonDecimalValue(),
                minValue != null ? String.valueOf(minValue) : "-",
                maxValue != null ? String.valueOf(maxValue) : "-"));

        // Adds listeners.
        numberField.addListener(Events.OnKeyUp, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                onNumberFieldChange(numberField, isDecimal);
            }

        });

        numberField.addListener(Events.OnBlur, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                onNumberFieldChange(numberField, isDecimal);
            }
        });

        return numberField;
    }

    /**
     * Creates a new <code>DateField</code> for this element.
     * 
     * @param valueResult
     *          Initial value to set.
     * @return A new <code>DateField</code>.
     */
    private TextField<Date> createDateField(final ValueResult valueResult) {

        // Creates a date field which manages date picker selections and
        // manual selections.
        final DateField dateField = new DateField();
        final DateTimeFormat dateFormat = DateUtils.DATE_SHORT;
        dateField.getPropertyEditor().setFormat(dateFormat);
        dateField.setEditable(true);
        dateField.setAllowBlank(true);
        preferredWidth = FlexibleElementDTO.NUMBER_FIELD_WIDTH;

        // Sets the min date value.
        final Date minDate;
        if (getMinValue() != null) {
            minDate = new Date(getMinValue());
            dateField.setMinValue(minDate);
        } else {
            minDate = null;
        }

        // Sets the max date value.
        final Date maxDate;
        if (getMaxValue() != null) {
            maxDate = new Date(getMaxValue());
            dateField.setMaxValue(maxDate);
        } else {
            maxDate = null;
        }

        // Sets tooltip.
        dateField.setToolTip(
                I18N.MESSAGES.flexibleElementTextAreaDateRange(minDate != null ? dateFormat.format(minDate) : "-",
                        maxDate != null ? dateFormat.format(maxDate) : "-"));

        // Adds the listeners.

        dateField.getDatePicker().addListener(Events.Select, new Listener<DatePickerEvent>() {

            @Override
            public void handleEvent(DatePickerEvent be) {

                // The date is saved as a timestamp.
                final String rawValue = String.valueOf(be.getDate().getTime());
                // The date picker always returns a valid date.
                final boolean isValueOn = true;

                fireEvents(rawValue, isValueOn);
            }
        });

        dateField.addListener(Events.OnKeyUp, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {

                final Date date = dateField.getValue();

                // The date is invalid, fires only a required event to
                // invalidate some previously valid date.
                if (date == null || (minDate != null && date.before(minDate))
                        || (maxDate != null && date.after(maxDate))) {

                    // Required element ?
                    if (getValidates()) {
                        handlerManager.fireEvent(new RequiredValueEvent(false));
                    }

                    return;
                }

                // The date is saved as a timestamp.
                final String rawValue = String.valueOf(date.getTime());
                // The date is valid here.
                final boolean isValueOn = true;

                fireEvents(rawValue, isValueOn);
            }
        });

        // Sets the value to the field.
        if (valueResult != null && valueResult.isValueDefined()) {
            dateField.setValue(new Date(Long.parseLong(valueResult.getValueObject())));
        }

        return dateField;
    }

    /**
     * Creates a new <code>TextArea</code> for this element.
     * 
     * @param valueResult
     *          Initial value to set.
     * @return A new <code>TextArea</code>.
     */
    private TextField<String> createParagraphField(final ValueResult valueResult) {

        final TextArea textArea = new TextArea();
        textArea.addStyleName("flexibility-textarea");

        final Integer length = getLength();
        // Sets the max length.
        if (length != null) {
            textArea.setMaxLength(length);
            textArea.setToolTip(I18N.MESSAGES.flexibleElementTextAreaTextLength(String.valueOf(length)));
        }

        // Adds the listeners.
        textArea.addListener(Events.OnKeyUp, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {

                String rawValue = textArea.getValue();

                if (rawValue == null) {
                    rawValue = "";
                }

                // The value is valid if it contains at least one
                // non-blank character.
                final boolean isValueOn = !rawValue.trim().equals("")
                        && !(length != null && rawValue.length() > length);

                fireEvents(rawValue, isValueOn);
            }
        });

        // Sets the value to the field.
        if (valueResult != null && valueResult.isValueDefined()) {
            textArea.setValue(valueResult.getValueObject());
        }

        return textArea;
    }

    /**
     * Creates a new <code>TextField</code> for this element.
     * 
     * @param valueResult
     *          Initial value to set.
     * @return A new <code>TextField</code>.
     */
    private TextField<String> createTextField(final ValueResult valueResult) {

        final TextField<String> textField = new TextField<String>();

        // Sets the max length.
        final Integer length = getLength();
        if (length != null) {
            textField.setMaxLength(length);
            textField.setToolTip(I18N.MESSAGES.flexibleElementTextAreaTextLength(String.valueOf(length)));
        }

        // Adds the listeners.
        textField.addListener(Events.OnKeyUp, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {

                String rawValue = textField.getValue();

                if (rawValue == null) {
                    rawValue = "";
                }

                // The value is valid if it contains at least one
                // non-blank character.
                final boolean isValueOn = !rawValue.trim().equals("")
                        && !(length != null && rawValue.length() > length);

                fireEvents(rawValue, isValueOn);
            }
        });

        // Sets the value to the field.
        if (valueResult != null && valueResult.isValueDefined()) {
            textField.setValue(valueResult.getValueObject());
        }

        return textField;
    }

    /**
     * Verify if the given <code>String</code> is a number and that it matches
     * the minimum and maximum values.
     * 
     * @param value
     *          Value to verify.
     * @return <code>true</code> if the given value is correct,
     * <code>false</code> otherwise.
     */
    private boolean isCorrectRequiredNumberValue(final String value) {

        final boolean decimal = Boolean.TRUE.equals(getIsDecimal());
        final Long minValue = getMinValue();
        final Long maxValue = getMaxValue();

        // Checks the number range.
        if (decimal) {
            final double d = Double.parseDouble(value);
            return (minValue == null || d >= minValue) && (maxValue == null || d <= maxValue);
        } else {
            final long l = Long.parseLong(value);
            return (minValue == null || l >= minValue) && (maxValue == null || l <= maxValue);
        }
    }

    /**
     * Verify if the given <code>String</code> is a date and that it matches
     * the minimum and maximum values.
     * 
     * @param value
     *          Value to verify.
     * @return <code>true</code> if the given value is correct,
     * <code>false</code> otherwise.
     */
    private boolean isCorrectRequiredDateValue(final String value) {

        // Gets the min date value.
        final Date minDate;
        if (getMinValue() != null) {
            minDate = new Date(getMinValue());
        } else {
            minDate = null;
        }

        // Gets the max date value.
        final Date maxDate;
        if (getMaxValue() != null) {
            maxDate = new Date(getMaxValue());
        } else {
            maxDate = null;
        }

        final Date date = new Date(Long.parseLong(value));
        return !((minDate != null && date.before(minDate)) || (maxDate != null && date.after(maxDate)));
    }

    /**
     * Verify if the given <code>String</code> matches the maximum length
     * constraint.
     * 
     * @param value
     *          Value to verify.
     * @return <code>true</code> if the given value is correct,
     * <code>false</code> otherwise.
     */
    private boolean isCorrectRequiredStringValue(final String value) {
        final Integer length = getLength();
        return !value.trim().isEmpty() && (length == null || value.length() <= length);
    }

    /**
     * Propagate the change if the current value is valid.
     * Called when the value of a number field change.
     * 
     * @param numberField
     *          Field whose value changed.
     * @param decimal 
     *          <code>true</code> if the value can be decimal,
     *          <code>false</code> otherwise.
     */
    private void onNumberFieldChange(final TextField<Number> numberField, final boolean decimal) {

        final Number number = numberField.getValue();
        final Double asDouble = number != null ? number.doubleValue() : null;

        // The number is invalid, fires only a required event to invalidate some previously valid number.
        if (asDouble == null) {
            // Required element ?
            if (getValidates()) {
                handlerManager.fireEvent(new RequiredValueEvent(false));
            }
            return;
        }

        // The number is saved as a double (decimal) or a long (integer).
        final String rawValue = decimal ? String.valueOf(asDouble) : String.valueOf(asDouble.longValue());
        fireEvents(rawValue, isCorrectRequiredNumberValue(rawValue));
    }

    // --
    // GETTERS & SETTERS
    // --

    // Expected value type
    public Character getType() {
        return get("type");
    }

    public void setType(Character type) {
        set("type", type);
    }

    // Expected min value
    public Long getMinValue() {
        return get("minValue");
    }

    public void setMinValue(Long minValue) {
        set("minValue", minValue);
    }

    // Expected max value
    public Long getMaxValue() {
        return get("maxValue");
    }

    public void setMaxValue(Long maxValue) {
        set("maxValue", maxValue);
    }

    // Expected decimal value ?
    public Boolean getIsDecimal() {
        return get("isDecimal");
    }

    public void setIsDecimal(Boolean isDecimal) {
        set("isDecimal", isDecimal);
    }

    // Expected value's length
    public Integer getLength() {
        return get("length");
    }

    public void setLength(Integer length) {
        set("length", length);
    }

}