Reference » Segments

Segments

Last modified by Andreas Hahn on 2011/06/09 10:19

Overview

The term 'segment' refers to a portion of the web page that typically contains editable user interface elements. Segments can be combined in a way representing the user workflow in your application. Its best explained when you have a look at the Online Demos. In shept.xml there are the following configuration templates predefined:

  • sheptSegmentTemplate
  • sheptDataGridTemplate

Segments shall be defined by convention in segments.xml.

Let's have a look at a segments minimal configuration:

<bean name="addresses" parent="sheptDataGridTemplate">
<property name="entityClass" value="org.shept.apps.demo.orm.Address" />
</bean>

We have defined segment named addresses. It is of type sheptSegmentListTemplate which is the default type for table based segments. It's class is defined with all the other defaults in shept.xml. We have also define a property entityClass with an address entity class meaning that the resulting table is populated with instances of this class.

sheptSegment configurable properties

name

The name identifies the segment and is used to make a match between a segments configuration, the segment layout and as a reference for chaining.
You can use any valid bean name that is a unique spring bean name accross the bean configuration. It is recommended that the name of the based entity is part of the segments name (e.g. your entity is Person then a good name for a datagrid segment would be persons.

The name property takes part in the default debug features - you can append any shept url with a ?debug=true suffix to visualize your subform names.

Base entityClass and filterClass

In a data grid segment your row objects are instances of the base entityClass. When new rows are created these new rows will initially be populated with an empty entity class instance (the actual initialization depends on your initializing code). If you specify a filterClass (of FilterDefinition interface) you control which results show up in your result list.

Note that that a fully qualified className is required - not a bean definition. As a consequence a filter instance (and an entity) does not take part in springs dependency injection configuration - you cannot inject other beans into the filter.

componentBindingInitializers

ComponentBindingInitializers serve the purpose to apply individual formatters for your data - segment by segment. A common use for binding initializers are internationalization of number / date and time formats.

<property name="componentBindingInitializer">
 <bean parent="sheptBindingComponentInitializerTemplate" >
   <property name="dateEditors">
     <map>
<entry key="timeFrom" value="TIME_FORMAT_SHORT" />
<entry key="timeTill" value="TIME_FORMAT_SHORT" />
     </map>
   </property>
 </bean>
</property>

The example shows a time configuration of TIME_FORMAT_SHORT for your entites members timeFrom and timeTill. When you have loaded the default project you will have a configuration file WEB-INF/i18n.xml that will contain a couple of locale specific definitions that you can extend to your needs.

validators

You attach one or more validators to a segment by specifying a validator class in the segment configuration:

<property name="validator" >
 <bean class="your.fully.qualified.ValidatorClass" />
</property>

The validator must implement the ComponentValidator interface.

void validate(Object target, Errors errors, String componentPath);

Validation works the usual spring style way and there is an additional componentPath parameter to retrieve your object to be validated. The target object is the form backing object of the whole page so you have to retrieve the segment backing object. In this example it is a pageHolder with its page data. 

public void validate(Object target, Errors errors, String path) {
  String prefix = ComponentUtils.getPropertyPathPrefix(path);
  PageableList pageHolder = (PageableList) ComponentUtils.getComponent(target, path);
  List<Timesheet> timesheets = (List<Timesheet>)pageHolder.getSource();

See the full example attached. It checks for valid time entry and overlapping periods in a time based data grid. Note that the prefix is used to fully qualify the names of the erroneous fields no matter how complex your form gets. Errors will be easily identified with a field error marker in your application.
There is also a simple validation example from a custom project checking some simple configurations of a custom backing object that is not a data grid.

transaction

Transactions are configured by default. If you push the save-button on a data grid segment the visible content of the data grid is saved transactional.
However there are situations where you need to customize this behavior. You can add initialization code to all your row objects or you might perform additional checks to exclude individual rows from saving or you can modify the viewport (only visible = default or even the full list of objects).

<property name="transaction" >
 <bean parent="sheptTransactionTemplate" p:save="saveSchedule" />
</property>

An individual transaction needs to be implemented by the DataAccessObject you have configured in your WEB-INF/applicationContext.xml as the backing data access object. Let's have a look on an example implementation where we save all the visible rows of a data grid by the saveSchedule method.

public void saveSchedule(FilteredListHolder pageable) {
  Schedule newSchedule = (Schedule) pageable.getNewModelTemplate();
 @SuppressWarnings("unchecked")
  List<Schedule> schedules = (List<Schedule>) pageable.getPageList();
 for (Schedule schedule : schedules) {
   if (schedule.getId() == null && ! newSchedule.isCreationAllowed(schedule)) {
     continue; // do nothing
   }
    schedule.setCustomer(SessionUtils.getCurrentCustomer());
    schedule.setName(scheduleHelper.getDescription(schedule));
    getHibernateTemplate().saveOrUpdate(schedule);
 }
}

Our method needs to take exactly one argument - that is the segments backing object (not the forms backing object !). In this case it is a FilteredListHolder which is the default object for data grids. We might have implemented this method in a more generic way using interfaces but lets keep it simple here for the purpose of this example. With the #getNewModelTemplate() we check a template model against all new (yet unsaved) rows if we are allowed to instantiate the new row. In this example we also set the sessions customer as the schedules owner. Note that we iterate over all visible elements with pageable.getPageList() we might also iterate over all elements of the list whether they are visible or not by pageable.getSource().

componentPostprocessors

ComponentPostprocessors will provide additional information that is needed for view rendering. Typical use cases are population of choice options (drop down boxes, ...). See the ChoiceListPostprocessor in the SheptDemo example. This is a simple postprocessor providing fix strings as address type names. This simple example can easily be extended. However its simple to extend this example for localization. This is an improved version providing local specific messages:

package org.shept.apps.demo.web.controller.postprocessors;
/**
 *
 */


import java.util.LinkedHashMap;
import java.util.Map;

import org.shept.org.springframework.web.bind.support.ComponentPostprocessor;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.ModelAndView;

/**
 * @version $$Id: $$
 *
 * @author Andi
 *
 */

public class CommonPostProcessor extends WebApplicationObjectSupport implements
ComponentPostprocessor {

/* (non-Javadoc)
* @see org.shept.org.springframework.web.bind.support.ComponentPostprocessor#postHandle(org.springframework.web.context.request.WebRequest, org.springframework.web.servlet.ModelAndView, java.lang.String)
*/

@Override
public void postHandle(WebRequest request, ModelAndView mv,
String componentPath) {

if (mv != null) {
mv.addObject("addressTypes", getAddressTypes(getMessageSourceAccessor()));
}
}

private Map<Integer, String> getAddressTypes(MessageSourceAccessor accessor ) {
Map <Integer, String> modes = new LinkedHashMap <Integer, String>();
modes.put(0, accessor.getMessage("addressType.default"));
modes.put(1, accessor.getMessage("addressType.shipping"));
modes.put(2, accessor.getMessage("addressType.legal"));
return modes;
}
}

Of course you need to define the localized messages "addressType.default, ..." in your messages.properties configuration. ComponentPostprocessors might also easily be populated from another entity object from the database. If we had an AddressType entity class our code for populating the address list (and injecting the dao resource) might look like this

private YourDao dao;

@Resource
public setDao(YourDao dao) {
 this.dao = dao
}

private Map<Integer, String> getAddressTypes(MessageSourceAccessor accessor ) {
  Map <Integer, String> modes = new LinkedHashMap <Integer, String>();
  List<types> types = dao.getHibernateTemplate().loadAll(AddressType.class);
 for (AddressType type : types) {
   modes.put(type.getId(), type.getName());
 }
 return modes;
}

sheptDataGrid configurable properties

For data grids there a few additional configuration properties.

newModelSize

A value specifies the number of rows for new objects. The default is 1. Note that empty rows will only be shown if your filter supports new row creation (#getNewModelTemplate() may not return null).

pageSize

A value specifies the number of lines that a data grid shows initially. The default is 5.

Tags:
Created by Andreas Hahn on 2010/12/14 14:21

© 2011 shept.org - Andreas Hahn