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"; /** * Creates a new text area element DTO. */ public TextAreaElementDTO() { // Empty constructor. } /** * Creates a new text area element DTO with the given type. * * @param type * Type of the text area element DTO to create. */ public TextAreaElementDTO(final TextAreaType type) { setType(type.getCode()); } /** * {@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; Double doubleValue = null; if (valueResult != null && valueResult.isValueDefined()) { try { doubleValue = Double.parseDouble(valueResult.getValueObject()); } catch (IllegalArgumentException e) { // Ignored. } } // Decimal value if (isDecimal) { numberField.setFormat(NumberFormat.getDecimalFormat()); // Sets the value to the field. if (doubleValue != null) { numberField.setValue(doubleValue); } } // Non-decimal value else { numberField.setFormat(NumberFormat.getFormat("#")); // Sets the value to the field. if (doubleValue != null) { numberField.setValue(doubleValue.longValue()); } } // 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(); double doubleValue = 0.0; try { doubleValue = Double.parseDouble(value); } catch (IllegalArgumentException e) { // Ignored. } // Checks the number range. if (decimal) { return (minValue == null || doubleValue >= minValue) && (maxValue == null || doubleValue <= maxValue); } else { final long longValue = (long)doubleValue; return (minValue == null || longValue >= minValue) && (maxValue == null || longValue <= 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); } }