Demo: Persons and Adresses » Create Person Subform

Create Person Subform

Last modified by Andreas Hahn on 2011/05/30 18:58

In this step we will create the editable person subform and show both the person and address subform on one web page.

The resulting page

  • contains 2 subForms: persons and their addresses
  • contains a search filter for person wildcard searches
  • supports navigation from person to address through a navigation link

Both subForms contain the full editing / sorting / paging facilities. The resulting page will look like this:

SheptDemoPersons.png

Let's have a look step by step.

Creating the person subform

We have already created the address subform and this one comes pretty close. Have a look at the final /WEB-INF/tags/segments/tables/persons.tagx here. This extract shows the segment definition for persons target subform:

<bean name="persons" parent="baseDataGridConfig">
<property name="entityClass" value="org.shept.apps.demo.orm.Person" />
<property name="filterClass" value="org.shept.apps.demo.web.controller.filters.PersonFilter" />
</bean>

We already know that the entityClass property denotes the class of the objects in the resultset. The filterClass specifies a filter for populating the persons data grid with the users selection. Note that if we need individual formatting for rows in our data grid we can easily override the default bindings to achieve for example a different nationalized time date granularity.

Specifying a filter

Definition

Shept supports all kinds of Hibernate filters e.g. the criteria API, the query API as well as entity examples and relationships from your generated or defined relationships. In this case we use the criteria API. The PersonFilter inherits from the HibernateCriteriaFilter which by default takes the entityClass definition to create a new empty row instance. (If we don't want to allow creation of a new Person we need to override the #getNewModelTemplate to return null). This is the interesting part from the PersonFilter.java:


/* (non-Javadoc)
  * @see org.shept.apps.demo.web.controller.filter.DefaultCriteriaFilter#getCriteria(org.springframework.beans.support.SortDefinition)
  */

@Override
public DetachedCriteria getCriteria(SortDefinition sortDefinition) {
  DetachedCriteria crit = super.getCriteria(sortDefinition);
 if (StringUtils.hasText(name)) {
   crit.add(Restrictions.ilike("name", "%"+name+"%"));
 }
 if (StringUtils.hasText(firstName)) {
   crit.add(Restrictions.ilike("firstName", "%"+firstName+"%"));
 }
 return crit;
}

The full PersonFilter.java just contains the filters fields and property accessors. 

View

The filter corresponds with the view fragment in WEB-INF/tags/segments/filters/person.tagx:

<jsp:root
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:spring="http://www.springframework.org/tags"
xmlns:form="http://www.springframework.org/tags/form"
xmlns:shept="http://www.shept.org/tags/shept"
version="2.0"
>

<shept:filter >
 <table >
<tr>
<th>
<spring:message code="person.name" />
</th>
<th>
<spring:message code="person.firstName" />
</th>
</tr>
<tr>
<td>
<form:input path="name" cssStyle="margin-right: 8px" cssErrorClass="sheptFieldError" />
</td>
<td>
<form:input path="firstName" cssStyle="margin-right: 8px" cssErrorClass="sheptFieldError" />
</td>
</tr>
 </table>
 <shept:submitSearch />
</shept:filter>

</jsp:root>

Note that the path specification matches the property accessors of the underlying PersonFilter.java class.

Registration

Again we need to register the person subform to bind the person segment configuration to its view.
The following snippet from /WEB-INF/tags/segments/segments.tagx binds the person data grid and the filter:

<c:when test="${subFormName eq 'persons'}">
 <shept:subForm headerCode="persons" >
<filter:persons />
<table:persons />
 </shept:subForm>
</c:when>

Navigation to address subform

For navigating from person to their addresses we have included a navigational statement.
The chainRow tag from /WEB-INF/tags/segments/tables/persons.tagx adds a navigation link linkgo.png:

 <td>
<table:chainRow chainName="addresses" defaultText="addresses" />
 </td>

The defaultText is an international code from your message definitions.
The chainName="addresses" refers to the chain definition.

We also need to configure the navigation chain in WEB-INF/chains.xml.
Let's see how to configure the link from person to addresses:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
>

<context:annotation-config />

<util:list id="sheptChains" >

<!--  chains from unitsFiltered (same as targets but also a source)-->
<bean parent="sheptChain" >
<property name="from" ref="persons" />
<property name="to" ref="addresses" />
<property name="info">
<bean parent="sheptInfoItem" p:code="info.addresses" p:selector="name" />
</property>
</bean>

</util:list>

</beans>

The from and to properties refer to the corresponding segment definitions in segments.xml. Two things are important to note:
1) The specified chain name addresses refers to the target segment named addresses. If the target would be used in more than one context resulting in ambigous names we need to name the <bean parent="sheptChain" name="myChainName" > and use myChainName in <table:chainRow chainName="myChainName"> instead of addresses as the chain name.
2) The targets segments name serves as the default accessor method name for traversing the relation from Person to Address. If thats not suitable or you just want to have your naming conventions depending on each other you can specify a relation name as a property: <property name="relation" value"addresses" />

The info property binds an informal message code to a selector of the referring entity so that the result will show like this info.png

Full CRUD support

The dependent addresses data grid supports the full CRUD lifecycle. While read / update / delete are not surprising the creation of new entity instances is also supported. 2 conditions need to be met:

The class of the target entity Address needs to support initialization from the depending entity. For this we will implement an Address#initialize(Person) method:

public void initialize(Person person) {
this.person = person;
}

So when you click on the chain in one Person row a new template of Address will be created and initialized with that Person.
Now we have created a template object that can be used as a new instance provided the users enters more specific data into that row.
We need to define under which circumstances it should be allowed to save this new entity. For this purpose we need to define the ModelCreation interface on the Address object:

@Override
public boolean isCreationAllowed(Object arg0) {
Address address = (Address) arg0;
if ( ! StringUtils.hasText(address.getCity())) return false;
if (StringUtils.hasText(city) && city.equalsIgnoreCase(address.getCity())) {
return false;
}
return true;
}

@Override
@Transient
public boolean isTransient() {
return getId() == null;
}

The #isCreationAllowed(Object) method is to compare an empty template (=this) with the edited candidate row (argument object) for saving if the users has entered enough additional information to allow saving. You need to define your own rules on each entity to allow saving.

The #isTransient() serves as a simple to use indicator if the underlying entity should be treated as a new row candidate primarily for decorating a new row in the view.

<< General setup   Fact check >>

Tags:
Created by Andreas Hahn on 2010/12/17 09:24

© 2011 shept.org - Andreas Hahn