使用动态表功能将一个hasMany属性在页面上分类显示?
李小翔
2014-06-17 17:23

内容列表

考虑下面的需求：在HR系统中，需要录入员工的家庭成员信息，而家庭成员分为配偶、子女、其他三大类，配偶需要填写较多的信息，而子女、其他类只需要填写较少的信息，如下图所示。
将家庭成员设计为一个Domain类，使用 relationship（关系）来区分其类别，Domain类如下所示：
/**
 * 员工
 */
class Employee { 
    String name
   /** 家庭成员 */
   static hasMany = [familyMembers: FamilyMember]
} 
/**
 * 家庭成员
 */
class FamilyMember { 
   /** 员工 */
    Employee employee
   static belongsTo = [employee: Employee]
   
   /** 关系：配偶、子女、其他（父亲、母亲...） */
    String relationship
   public static final String REL_MATE = ”mate”            // 配偶
    public static final String REL_CHILDREN = ”children”    // 子女
    
   /** 姓名 */
    String name
   /** 出生日期 */
    Date birthdate
   /** 民族 */
    String nation
   /** 户籍 */
    String hukou
   /** 政治面貌 */
    String politicalStatus
   /** 工作时间 */
    Date workingDate
   /** 学历 */
    String education
   /** 联系电话 */
    String phone
   /** 毕业院校及专业 */
    String school
   /** 工作单位 */
    String company
   /** 职务 */
    String position
   
   /** 性别 */
    String gender
} 
视图（或控制器）中，先根据 relationship 属性将所有家庭成员分类为配偶、子女和其他：
FamilyMember mate = employeeInstance.familyMembers.find{ it.relationship == FamilyMember.REL_MATE}   // 配偶
List<FamilyMember> children = employeeInstance.familyMembers.findAll{ it.relationship == FamilyMember.REL_CHILDREN} .toList() // 子女
List<FamilyMember> others = (employeeInstance.familyMembers - children - mate).toList() // 其他
然后，视图开发时，只需要按照下面的步骤处理，即可使用动态表功能实现分类显示与自动保存：
将视图分为三段：配偶、子女、其他，每段显示和处理不同的数据。如配偶段处理对象 FamilyMamber mate，子女段处理列表 children、其他段处理列表 others
只有子女和其他两类使用了动态表标签 g:dynamicTable，其前缀均设为 fm（通过checkboxField属性），各个字段均以 fm 开头，如 fm.name、fm.gender 表示家庭成员的名称、性别；而配偶段同样采用 fm 前缀来命名HTML元素，以便后台自动解析、合并
两个动态表中，为了全选的 checkbox 和 增/删行 的功能运行正常，动态表名称设为不一样，一个为 familyMembersChildren、familyMembersOthers
为了保证提交的顺序、数据对应关系正确，每段数据中都包含全部属性（即便没有使用）的HTML元素：如配偶段，使用 g:hiddenField 将 fm.relationship 设置为 FamilyMember.REL_MATE
如子女段，将没有使用的 fm.nation、fm.hukou 等字段均设为空
完整的视图代码如下：
<tr class=”prop”>
   <td class=”name” colspan=”4”>配偶:</td>
</tr>
<tr class=”prop”>
   <td class=”name”><label for=”fm.name”><g:message code=”bropen.erp.hr.employee.FamilyMember.name” default=”Name” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.name} ”>
           <g:hiddenField name=”fm.id” value=”${ mate?.id ?: -1} ” />
           <g:hiddenField name=”fm.relationship” value=”${ FamilyMember.REL_MATE} ” />
           <g:hiddenField name=”fm.gender” value=”” />
           <g:textField name=”fm.name” value=”${ mate?.name} ” maxlength=”${ FamilyMember.constraints.name.maxSize} ” />
       </g:formField>
   </td>
   <td class=”name”><label for=”fm.birthdate”><g:message code=”bropen.erp.hr.employee.FamilyMember.birthdate” default=”Birthdate” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ DateUtils.formatDate(mate?.birthdate)} ”>
           <g:datePicker2 name=”fm.birthdate” value=”${ birthdate?.name} ” />
       </g:formField>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name”><label for=”fm.nation”><g:message code=”bropen.erp.hr.employee.FamilyMember.nation” default=”Nation” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.nation} ”>
           <g:textField name=”fm.nation” value=”${ mate?.nation} ” maxlength=”${ FamilyMember.constraints.nation.maxSize} ” />
       </g:formField>
   </td>
   <td class=”name”><label for=”fm.hukou”><g:message code=”bropen.erp.hr.employee.FamilyMember.hukou” default=”Hukou” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.hukou} ”>
           <g:textField name=”fm.hukou” value=”${ mate?.hukou} ” maxlength=”${ FamilyMember.constraints.hukou.maxSize} ” />
       </g:formField>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name”><label for=”fm.politicalStatus”><g:message code=”bropen.erp.hr.employee.FamilyMember.politicalStatus” default=”Political Status” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.politicalStatus} ”>
           <g:textField name=”fm.politicalStatus” value=”${ mate?.politicalStatus} ” maxlength=”${ FamilyMember.constraints.politicalStatus.maxSize} ” />
       </g:formField>
   </td>
   <td class=”name”><label for=”fm.workingDate”><g:message code=”bropen.erp.hr.employee.FamilyMember.workingDate” default=”WorkingDate” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ DateUtils.formatDate(mate?.workingDate)} ”>
           <g:datePicker2 name=”fm.workingDate” value=”${ mate?.workingDate} ” />
       </g:formField>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name”><label for=”fm.education”><g:message code=”bropen.erp.hr.employee.FamilyMember.education” default=”Education” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.education} ”>
           <g:textField name=”fm.education” value=”${ mate?.education} ” maxlength=”${ FamilyMember.constraints.education.maxSize} ” />
       </g:formField>
   </td>
   <td class=”name”><label for=”fm.phone”><g:message code=”bropen.erp.hr.employee.FamilyMember.phone” default=”Phone” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.phone} ”>
           <g:textField name=”fm.phone” value=”${ mate?.phone} ” maxlength=”${ FamilyMember.constraints.phone.maxSize} ” />
       </g:formField>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name”><label for=”fm.school”><g:message code=”bropen.erp.hr.employee.FamilyMember.school” default=”School” />:</label></td>
   <td class=”value” colspan=”3”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.school} ”>
           <g:textField name=”fm.school” value=”${ mate?.school} ” maxlength=”${ FamilyMember.constraints.school.maxSize} ” class=”max” />
       </g:formField>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name”><label for=”fm.company”><g:message code=”bropen.erp.hr.employee.FamilyMember.company” default=”Company” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.company} ”>
           <g:textField name=”fm.company” value=”${ mate?.company} ” maxlength=”${ FamilyMember.constraints.company.maxSize} ” class=”max” />
       </g:formField>
   </td>
   <td class=”name”><label for=”fm.position”><g:message code=”bropen.erp.hr.employee.FamilyMember.position” default=”Position” />:</label></td>
   <td class=”value”>
       <g:formField bean=”${ employeeInstance} ” name=”familyMembers” value=”${ mate?.position} ”>
           <g:textField name=”fm.position” value=”${ mate?.position} ” maxlength=”${ FamilyMember.constraints.position.maxSize} ” />
       </g:formField>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name” colspan=”4”>子女:</td>
</tr>
<tr class=”prop”>
   <td class=”value” colspan=”4”>
       <g:if test=”${ isFormFieldEditable(bean: employeeInstance, name: ‘familyMembers’) || children} ”>
       <g:dynamicTable name=”familyMembersChildren” class=”innerTable” border=”1” button=”top” checkboxField=”fm.id”
           childProperty=”familyMembers” requiredField=”name”
           editable=”${ isFormFieldEditable(bean: employeeInstance, name: ‘familyMembers’)} ”>
       <thead>
           <tr>
               <th width=”15” class=”editable”><input type=”checkbox” onclick=”familyMembersChildren.selectAll(this.checked)”/></th>
               <th width=”100”><g:message code=”bropen.erp.hr.employee.FamilyMember.name” default=”Name” /></th>
               <th width=”50”><g:message code=”bropen.erp.hr.employee.FamilyMember.gender” default=”Gender” /></th>
               <th width=”100”><g:message code=”bropen.erp.hr.employee.FamilyMember.birthdate” default=”Birthdate” /></th>
               <th width=”300”><g:message code=”bropen.erp.hr.employee.FamilyMember.company2” default=”Company” /></th>
           </tr>
       </thead>
       <tbody class=”template”>
           <tr>
               <td class=”center”>
                   <input name=”fm.id” type=”checkbox” value=”-1” />
                   <input name=”fm.relationship” type=”hidden” value=”${ FamilyMember.REL_CHILDREN} ” />
                   <input name=”fm.nation” type=”hidden” value=”” />
                   <input name=”fm.hukou” type=”hidden” value=”” />
                   <input name=”fm.politicalStatus” type=”hidden” value=”” />
                   <input name=”fm.workingDate” type=”hidden” value=”” />
                   <input name=”fm.education” type=”hidden” value=”” />
                   <input name=”fm.phone” type=”hidden” value=”” />
                   <input name=”fm.school” type=”hidden” value=”” />
                   <input name=”fm.position” type=”hidden” value=”” />
               </td>
               <td><g:textField name=”fm.name” /></td>
               <td><g:select name=”fm.gender” from=”${ FamilyMember.constraints.gender.inList} ” valueMessagePrefix=”bropen.erp.hr.employee.FamilyMember.gender” noSelection=”[’’:’’]” /></td>
               <td><input type=”text” name=”fm.birthdate” class=”date” /></td>
               <td><g:textField name=”fm.company” style=”width: 300px” /></td>
           </tr>
       </tbody>
       <tbody>
           <g:each in=”${ CollectionUtils.sort(children, ‘asc’, ‘birthdate’)} ” var=”fm”>
           <tr>
               <td class=”center editable”>
                   <input name=”fm.id” type=”checkbox” value=”${ fm.id} ”/>
                   <input name=”fm.relationship” type=”hidden” value=”${ FamilyMember.REL_CHILDREN} ” />
                   <input name=”fm.nation” type=”hidden” value=”” />
                   <input name=”fm.hukou” type=”hidden” value=”” />
                   <input name=”fm.politicalStatus” type=”hidden” value=”” />
                   <input name=”fm.workingDate” type=”hidden” value=”” />
                   <input name=”fm.education” type=”hidden” value=”” />
                   <input name=”fm.phone” type=”hidden” value=”” />
                   <input name=”fm.school” type=”hidden” value=”” />
                   <input name=”fm.position” type=”hidden” value=”” />
               </td>
               <td><g:formField bean=”${ fm} ” name=”name”>
                   <g:textField name=”fm.name” value=”${ fm.name} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”gender” valueI18n=”true”>
                   <g:select name=”fm.gender” from=”${ FamilyMember.constraints.gender.inList} ” valueMessagePrefix=”bropen.erp.hr.employee.FamilyMember.gender” noSelection=”[’’:’’]” value=”${ fm.gender} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”birthdate”>
                   <input type=”text” name=”fm.birthdate” class=”date” value=”${ DateUtils.formatDate(fm.birthdate)} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”company”>
                   <g:textField name=”fm.company” style=”width:300px” value=”${ fm.company} ”/>
               </g:formField></td>
           </tr>
           </g:each>
       <tbody>
       </g:dynamicTable>
       </g:if><g:else>无</g:else>
   </td>
</tr>
<tr class=”prop”>
   <td class=”name” colspan=”4”>其他成员:</td>
</tr>
<tr class=”prop”>
   <td class=”value” colspan=”4”>
       <g:if test=”${ isFormFieldEditable(bean: employeeInstance, name: ‘familyMembers’) || others} ”>
       <g:dynamicTable name=”familyMembersOthers” class=”innerTable” border=”1” button=”top” checkboxField=”fm.id”
           childProperty=”familyMembers” requiredField=”name”
           editable=”${ isFormFieldEditable(bean: employeeInstance, name: ‘familyMembers’)} ”>
       <thead>
           <tr>
               <th width=”15” class=”editable”><input type=”checkbox” onclick=”familyMembersOthers.selectAll(this.checked)”/></th>
               <th width=”50”><g:message code=”bropen.erp.hr.employee.FamilyMember.relationship” default=”Relationship” /></th>
               <th width=”100”><g:message code=”bropen.erp.hr.employee.FamilyMember.name” default=”Name” /></th>
               <th width=”100”><g:message code=”bropen.erp.hr.employee.FamilyMember.birthdate” default=”Birthdate” /></th>
               <th width=”50”><g:message code=”bropen.erp.hr.employee.FamilyMember.politicalStatus” default=”Political Status” /></th>
               <th width=”200”><g:message code=”bropen.erp.hr.employee.FamilyMember.company” default=”Company” /></th>
               <th width=”50”><g:message code=”bropen.erp.hr.employee.FamilyMember.position” default=”Position” /></th>
           </tr>
       </thead>
       <tbody class=”template”>
           <tr>
               <td class=”center”>
                   <input name=”fm.id” type=”checkbox” value=”-1” />
                   <input name=”fm.nation” type=”hidden” value=”” />
                   <input name=”fm.hukou” type=”hidden” value=”” />
                   <input name=”fm.workingDate” type=”hidden” value=”” />
                   <input name=”fm.education” type=”hidden” value=”” />
                   <input name=”fm.phone” type=”hidden” value=”” />
                   <input name=”fm.school” type=”hidden” value=”” />
                   <input name=”fm.gender” type=”hidden” value=”” />
               </td>
               <td><g:textField name=”fm.relationship” style=”width: 50px” /></td>
               <td><g:textField name=”fm.name” /></td>
               <td><input type=”text” name=”fm.birthdate” class=”date” /></td>
               <td><g:textField name=”fm.politicalStatus” style=”width: 50px” /></td>
               <td><g:textField name=”fm.company” style=”width: 200px” /></td>
               <td><g:textField name=”fm.position” style=”width: 50px” /></td>
           </tr>
       </tbody>
       <tbody>
           <g:each in=”${ CollectionUtils.sort(others, ‘asc’, ‘name’)} ” var=”fm”>
           <tr>
               <td class=”center editable”>
                   <input name=”fm.id” type=”checkbox” value=”${ fm.id} ”/>
                   <input name=”fm.nation” type=”hidden” value=”” />
                   <input name=”fm.hukou” type=”hidden” value=”” />
                   <input name=”fm.workingDate” type=”hidden” value=”” />
                   <input name=”fm.education” type=”hidden” value=”” />
                   <input name=”fm.phone” type=”hidden” value=”” />
                   <input name=”fm.school” type=”hidden” value=”” />
                   <input name=”fm.gender” type=”hidden” value=”” />
               </td>
               <td><g:formField bean=”${ fm} ” name=”relationship”>
                   <g:textField name=”fm.relationship” style=”width: 50px” value=”${ fm.relationship} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”name”>
                   <g:textField name=”fm.name” value=”${ fm.name} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”birthdate”>
                   <input type=”text” name=”fm.birthdate” class=”date” value=”${ DateUtils.formatDate(fm.birthdate)} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”politicalStatus”>
                   <g:textField name=”fm.politicalStatus” style=”width: 50px” value=”${ fm.politicalStatus} ” />
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”company”>
                   <g:textField name=”fm.company” style=”width: 200px” value=”${ fm.company} ”/>
               </g:formField></td>
               <td><g:formField bean=”${ fm} ” name=”position”>
                   <g:textField name=”fm.position” style=”width: 50px” value=”${ fm.position} ” />
               </g:formField></td>
           </tr>
           </g:each>
       <tbody>
       </g:dynamicTable>
       </g:if><g:else>无</g:else>
   </td>
</tr>
最终效果如下图所示：