/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.view.descriptor.basic;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jspresso.framework.model.descriptor.ICollectionDescriptor;
import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptorProvider;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IReferencePropertyDescriptor;
import org.jspresso.framework.view.descriptor.EHorizontalPosition;
import org.jspresso.framework.view.descriptor.IComponentViewDescriptor;
/**
* Component view descriptors are surely one of the most commonly used view
* descriptors in Jspresso. It allows to implement advanced form-like views to
* interact with a single component model. Component properties that are
* displayed in the view are organized in an invisible grid. Each field
* component is labelled with the property name it displays and labels can be
* configured to be displayed aside or above their peer field. Property fields
* can be configured to span multiple form columns. Component view offer various
* straightforward customizations, but the most advanced and powerful one is
* definitely the {@code propertyViewDescriptors} property tat allows to
* fine-tune each component UI field individually.
* <p>
* The description property is used to compute view tooltips and support the
* following rules :
* <ol>
* <li>if the description is a property name of the underlying model, this
* property will be used to compute the (dynamic) tooltip (depending on the
* actual model).</li>
* <li>if the description is not a property name of the underlying model, the
* the tooltip is considered static and the translation will searched in the
* application resource bundles.</li>
* <li>if the description is the empty string (''), the tooltip is de-activated.
* </li>
* <li>if the description is not set, then the toHtml property (see toHtml
* property on entities / components definition) is used as dynamic property.
* And the toHtml falls back to the toString if not set, which falls back to the
* 1st string rendered property if not set.</li>
* </ol>
* Note that on every case above, HTML is supported. This way, you can have
* really useful tooltips (event multi-line), in order to detail some synthetic
* data. Moreover, this rule is available for the form tooltip, but also for
* each individual field (property view) in the form.
*
* @author Vincent Vandenschrick
*/
public class BasicComponentViewDescriptor extends AbstractComponentViewDescriptor {
private int columnCount;
private EHorizontalPosition labelsHorizontalPosition;
private Map<String, Integer> propertyWidths;
private Map<String, List<String>> renderedChildProperties;
private Boolean verticallyScrollable;
private Boolean horizontallyScrollable;
private Boolean widthResizeable;
/**
* Constructs a new {@code BasicComponentViewDescriptor} instance.
*/
public BasicComponentViewDescriptor() {
super();
columnCount = 1;
verticallyScrollable = false;
horizontallyScrollable = false;
labelsHorizontalPosition = EHorizontalPosition.LEFT;
}
/**
* {@inheritDoc}
*/
@Override
public int getColumnCount() {
return columnCount;
}
/**
* Configures the number of columns on this component view. Property fields
* that are to be displayed in the view are spread across columns and rows
* following their defined order. Whenever a row does not contain enough empty
* cells to receive the next field (either because the last column has been
* reached or the next field has been configured to span multiple columns and
* there is not enough cells left in the current row to satisfy the span), a
* new row is created and the next field is added to the first column on the
* new row.
* <p>
* Note that column count and span are defined in fields coordinates (the
* <i>field</i> including the property UI component + its label). The
* underlying grid is actually finer since it has to cope with the labels; but
* this is internal implementation details and Jspresso takes care of it,
* without the developer having to cope with labels placements.
* <p>
* Default value is 1, meaning that all rendered fields will be stacked in a
* single column.
*
* @param columnCount
* the columnCount to set.
*/
public void setColumnCount(int columnCount) {
this.columnCount = columnCount;
}
/**
* This property allows to simply define property spans in the underlying grid
* without having to extensively define the
* {@code propertyViewDescriptors} property. It must be configured with a
* {@code Map} containing only the properties that need to span more than
* 1 column. The other properties will follow the default span of 1.
* <p>
* The {@code Map} is :
* <ul>
* <li>keyed by the name of the property</li>
* <li>valued by the number of columns of the property span</li>
* </ul>
* Default value is {@code null}, meaning all property fields have a span
* of 1.
*
* @param propertyWidths
* the propertyWidths to set.
*/
public void setPropertyWidths(Map<String, Object> propertyWidths) {
this.propertyWidths = new HashMap<>();
for (Map.Entry<String, Object> propertyWidth : propertyWidths.entrySet()) {
if (propertyWidth.getValue() instanceof String) {
this.propertyWidths.put(propertyWidth.getKey(),
Integer.valueOf((String) propertyWidth.getValue()));
} else {
this.propertyWidths.put(propertyWidth.getKey(),
((Number) propertyWidth.getValue()).intValue());
}
}
}
/**
* Whenever a rendered property is not scalar, this property allows to
* override which of the referenced component fields should be displayed :
* <ul>
* <li>as columns when the rendered property is a collection property</li>
* <li>as fields when the rendered property is a reference property</li>
* </ul>
* The property must be configured with a {@code Map} which is :
* <ul>
* <li>keyed by the non-scalar property name</li>
* <li>valued by the list of the property names to render for the child
* element(s)</li>
* </ul>
* <p>
* A {@code null} value (default), means that all non-scalar properties
* will be rendered using default rendered properties as specified in their
* referenced model descriptor.
* <p>
* Please note that this is quite unusual to embed non-scalar properties
* directly in a component view. Although permitted, you won't have as much
* flexibility in the content layouting as you would have when using composite
* views; so the latter is by far recommended.
*
* @param renderedChildProperties
* the renderedChildProperties to set.
*/
public void setRenderedChildProperties(
Map<String, List<String>> renderedChildProperties) {
this.renderedChildProperties = renderedChildProperties;
}
@Override
protected Integer getPropertyWidth(String propertyName) {
if (propertyWidths != null) {
return propertyWidths.get(propertyName);
}
return 1;
}
@Override
protected List<String> computeDefaultRenderedChildProperties(String propertyName) {
List<String> childProperties = null;
if (renderedChildProperties != null) {
childProperties = renderedChildProperties.get(propertyName);
}
if (childProperties == null) {
IPropertyDescriptor childPropertyDescriptor = ((IComponentDescriptorProvider<?>) getModelDescriptor())
.getComponentDescriptor().getPropertyDescriptor(propertyName);
if (childPropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
return ((ICollectionDescriptor<?>) ((ICollectionPropertyDescriptor<?>) childPropertyDescriptor)
.getCollectionDescriptor()).getElementDescriptor()
.getRenderedProperties();
} else if (childPropertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
// return the toString property
return Collections
.singletonList(((IReferencePropertyDescriptor<?>) childPropertyDescriptor)
.getReferencedDescriptor().getToStringProperty());
}
}
return childProperties;
}
/**
* Gets the verticallyScrollable.
*
* @return the verticallyScrollable.
*/
@Override
public boolean isVerticallyScrollable() {
return verticallyScrollable;
}
/**
* This property allows to define the form vertical scrolling behaviour.
* Whenever it is set to true, the corresponding UI component will install a
* vertical scroll bar when the available vertical space is not enough.
* <p>
* Default value is {@code false}.
*
* @param verticallyScrollable
* the verticallyScrollable to set.
*/
public void setVerticallyScrollable(boolean verticallyScrollable) {
this.verticallyScrollable = verticallyScrollable;
}
/**
* Gets the horizontallyScrollable.
*
* @return the horizontallyScrollable.
*/
@Override
public boolean isHorizontallyScrollable() {
return horizontallyScrollable;
}
/**
* This property allows to define the form horizontal scrolling behaviour.
* Whenever it is set to true, the corresponding UI component will install a
* horizontal scroll bar when the available horizontal space is not enough.
* <p>
* Default value is {@code false}.
*
* @param horizontallyScrollable
* the horizontallyScrollable to set.
*/
public void setHorizontallyScrollable(boolean horizontallyScrollable) {
this.horizontallyScrollable = horizontallyScrollable;
}
/**
* This property allows to define the form horizontal fill behaviour.
* Whenever it is set to true, the corresponding UI component will fill all its available horizontal space.
* <p/>
* Default value is {@code true}.
*
* @param widthResizeable
* the widthResizeable to set.
*/
public void setWidthResizeable(boolean widthResizeable) {
this.widthResizeable = widthResizeable;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWidthResizeable() {
if (widthResizeable != null) {
return widthResizeable;
}
return getColumnCount() > 1;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isScrollable() {
return isVerticallyScrollable() || isHorizontallyScrollable();
}
/**
* Gets label horizontal position.
*
* @return the label horizontal position
*/
@Override
public EHorizontalPosition getLabelsHorizontalPosition() {
return labelsHorizontalPosition;
}
/**
* Configures the label horizontal position. There are special cases when the default label position has to be
* overridden. This is either a value of the {@code EHorizontalPosition}
* enum or its equivalent string representation :
* <ul>
* <li>{@code LEFT} for left position</li>
* <li>{@code RIGHT} for right position</li>
* </ul>
* <p>
* Default value is {@code LEFT}.
*
* @param labelsHorizontalPosition the label horizontal position
*/
public void setLabelsHorizontalPosition(EHorizontalPosition labelsHorizontalPosition) {
this.labelsHorizontalPosition = labelsHorizontalPosition;
}
}