Spring MVC + Hibernate:一个带有@manytomany关系复选框的表单

时间:2014-07-10 10:07:41

标签: java hibernate spring-mvc spring-webflow

这是一个星期,我正在尝试在网页开发中做一件非常简单的事情:一个带有复选框的表单,我可以勾选哪些代理商在比赛中被收集。

代理商 - > ContestAgency< - Contest

比赛课程:

// i tried also EAGER
@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.contest", cascade=CascadeType.ALL) 
private Set<ContestAgency> contestAgencies = new TreeSet<ContestAgency>();

代理商类:

@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.agency", cascade=CascadeType.ALL)
     private Set<ContestAgency> contestAgencies = new TreeSet<ContestAgency>(); 

如何在WebFlow或控制器中使用复选框创建表单?

感谢

1 个答案:

答案 0 :(得分:2)

我找到了解决方案。我认为不是最好的,但它有效:

  • 在视图中,我给所有复选框命名(chk_services,chk_agencies)

  • 转换到下一步,我将POST生成的逗号分隔字符串与已检查的ID(id1,id2,...)一起传递给方法 拆分字符串,对于每个id,从db获取相关实体并将它们放入我的多对多实体集(ContestService)

  

&LT;评估   表达=“contestFlow.converterCheckboxToSetContestService(flowScope.contest,   requestParameters.chk_services)”   结果= “flowScope.contest.ContestServices” &GT; &LT; / evaluate&gt;

(当我进入下一步和前一步时,我这样做)

  • 在进入视图时,我想再次检查复选框。要做到这一点,我需要将我的Set转换为id列表:
  < set name="viewScope.checked_services" value="contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices)" / >
  • 在视图中,我看是否选中或不选中每个复选框:

    th:checked =“$ {#lists.contains(checked_services,''+ service_el.id)}”

注意:''+ service_el.id需要,因为没有''+它不起作用



我发布了完整的代码。我希望有人会发现它有用。

欢迎任何改进和建议。

Webapp配置(我正在使用java注释): 使用流程中使用的方法配置bean

public class WebAppConfig extends WebMvcConfigurerAdapter {

    //...

    @Bean
    public ContestFlow contestFlow()
    {
        return new ContestFlow();       
    }

}

使用我的方法参加比赛流程:

@Component
public class ContestFlow {  

    static Logger logger = LoggerFactory.getLogger(ContestFlow.class);

    @Autowired
    private ServiceService ServiceService;

    @Autowired
    private AgencyService AgencyService;

    /**
     * input: a comma separated string with all ids checked from the POST
     * 
     * @param contest:      contest object that i will add at the end of the webflow
     * @param ids_string:   comma separated string with checked ids
     * @return
     */
    public Set<ContestService> converterCheckboxToSetContestService(Contest contest, String ids_string)
    {

        Set<ContestService> contestServices = new HashSet<ContestService>(0);   

        if (ids_string != null)
        {
            String[] arr_ids = ids_string.split(",");

            /*
             * for each record i get the Service
             */
            for (int i = 0; i < arr_ids.length; i++)
            {
                try
                {
                    //get the Service
                    Service service = ServiceService.getService(Integer.parseInt(arr_ids[i]));

                    logger.info("Aggiungo il service id [" + arr_ids[i] + "]");

                    //creation of the Id object
                    ContestServiceId contestServiceId = new ContestServiceId();             
                    contestServiceId.setService(service);
                    contestServiceId.setContest(contest);

                    //record population
                    ContestService contestService = new ContestService();
                    contestService.setService(service);
                    contestService.setContest(contest);
                    contestService.setPk(contestServiceId);

                    //add the record
                    contestServices.add(contestService);

                }
                catch(Exception ex)
                {
                    ex.printStackTrace();
                    logger.info("Service id [" + arr_ids[i] + "] not found!");

                }       

            }       
        }           
        return contestServices;
    }


    /**
     * input: Set of ContestAgency (many-to-many) checked 
     * and returns a List<String> of ids to be used to select checkboxes 
     * in thymeleaf view with th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}"
     * 
     * i can't return a List<Integer> because it doesn't check the checkboxes
     * 
     * @param contestAgencies
     * @return
     */
    public List<String> converterSetContestServiceToCheckbox(Set<ContestService> contestServices)
    {
        List<String> result = new ArrayList<String>();

        if (contestServices != null)
        {
            Iterator<ContestService> iterator = contestServices.iterator();
            while(iterator.hasNext()) 
            {           
                ContestService contestService = iterator.next();            

                Integer id = contestService.getService().getId();

                result.add(id.toString());
            }  
        }

        return result;
    }




    //same as above, for the Agencies:





    /**
     * input: a comma separated string with all ids checked from the POST
     * 
     * @param contest:      contest object that i will add at the end of the webflow
     * @param ids_string:   comma separated string with checked ids
     * @return
     */
    public Set<ContestAgency> converterCheckboxToSetContestAgency(Contest contest, String ids_string)
    {

        Set<ContestAgency> contestAgencies = new HashSet<ContestAgency>(0); 

        if (ids_string != null)
        {
            String[] arr_ids = ids_string.split(",");

            /*
             * for each record i get the Agency
             */
            for (int i = 0; i < arr_ids.length; i++)
            {
                try
                {
                    //get the Agency
                    Agency agency = AgencyService.getAgency(Integer.parseInt(arr_ids[i]));

                    logger.info("Adding agency id [" + arr_ids[i] + "]");

                    //creation of the Id object
                    ContestAgencyId contestAgencyId = new ContestAgencyId();                
                    contestAgencyId.setAgency(agency);
                    contestAgencyId.setContest(contest);

                    //record population
                    ContestAgency contestAgency = new ContestAgency();
                    contestAgency.setAgency(agency);
                    contestAgency.setContest(contest);
                    contestAgency.setPk(contestAgencyId);
                    contestAgency.setContractCount(0);  //my many-to-many relationship has an additional field

                    //add the record
                    contestAgencies.add(contestAgency);

                }
                catch(RecordNotFoundException ex)
                {
                    ex.printStackTrace();
                    logger.info("Agency id [" + arr_ids[i] + "] not found!");

                }       

            }       
        }       
        return contestAgencies;
    }


    /**
     * input: Set of ContestAgency (many-to-many) checked 
     * and returns a List<String> of ids to be used to select checkboxes 
     * in thymeleaf view with th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}"
     * 
     * i can't return a List<Integer> because it doesn't check the checkboxes
     * 
     * @param contestAgencies
     * @return
     */
    public List<String> converterSetContestAgencyToCheckbox(Set<ContestAgency> contestAgencies)
    {
        List<String> result = new ArrayList<String>();

        if (contestAgencies != null)
        {
            Iterator<ContestAgency> iterator = contestAgencies.iterator();
            while(iterator.hasNext()) 
            {
                ContestAgency contestAgency = iterator.next();

                Integer id = contestAgency.getAgency().getId();

                result.add(id.toString());
            }       
        }   

        return result;
    }

}

第2步的视图:带有服务复选框的表单:

<ul class="list-unstyled">
        <!-- 
        - parent and children are saved in the same table, so i'm not worried about ids overlapping
        -->

        <li th:each="service_el : ${services_list}" >
            <input type="checkbox" name="chk_services" th:value="${service_el.id}" th:checked="${#lists.contains(checked_services, '' + service_el.id)}"/>
            <label th:text="${service_el.title}" th:for="'chk_services' + ${service_el.id}">service</label>

            <ul class="list-unstyled-padding">
                <li th:each="subservice_el : ${service_el.children}">
                    <input type="checkbox" name="chk_services" th:value="${subservice.id}" th:checked="${#lists.contains(checked_services, '' + subservice.id)}"/>
                    <label  th:text="${subservice.title}" th:for="'chk_services' + ${service_el.id}">subservice</label>
                </li>
            </ul>              
        </li>
    </ul>

查看第3步:表单与代理商复选框:

<ul class="list-unstyled">
    <li  th:each="agency_el : ${agencies_list}">
        <input name="chk_agencies" type="checkbox" th:id="'chk_agencies' + ${agency_el.id}" th:value="${agency_el.id}" th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}" />
        <label th:text="${agency_el.name}" th:for="'chk_agencies' + ${agency_el.id}">agency</label>            
    </li>
</ul>

最后:流xml配置:

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

    <secured attributes="ROLE_USER" />

    <!-- creation of an empty object i will insert in db in the last step -->
    <on-start>
        <evaluate expression="ContestService.createContest()" result="flowScope.contest" />
    </on-start>

    <!-- 
        step 1: contest details
     -->
    <view-state id="contest-details" model="contest"> 
        <binder>
            <binding property="startDate" required="true" />
            <binding property="endDate" required="true"/>
            <binding property="bonus" required="true"/>
            <binding property="goal" required="true"/>
            <binding property="title" required="true"/>
        </binder>
        <transition on="proceed" to="contest-services">
        </transition>
        <transition on="cancel" to="cancel" bind="false" />
    </view-state>

    <!-- 
        step 2: i select which services are involved
    -->
    <view-state id="contest-services" model="contest">
        <on-entry>      
            <!--
            - in case i'm coming here from the step 3
            - injection of the list of ids previously checked
            -->
            <set name="viewScope.checked_services" value="contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices)" />

            <!-- 
             - i get the list of the Main Services
             - subservices will be scanned with getChildren method  
             -->
            <set name="viewScope.services_list" value="ServiceService.getMainServices()" />
        </on-entry>         

        <transition on="proceed" to="contest-agencies" >
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestService>
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_services)" result="flowScope.contest.ContestServices"></evaluate>  
        </transition>

        <transition on="cancel" to="contest-details">
            <!-- 
            - also if i go back in the flow, to the first step,
            - i need to remember which checkboxes were selected
            - 
            - and i need to save the checked services to the Contest entity, 
            - else, when i will call addContest method, 
            - it will not save the checked Services
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_services)" result="flowScope.contest.ContestServices"></evaluate>  
        </transition>
    </view-state>




    <!-- 
        step 3: i select which agencies are involved in contest.
        only agencies enabled for previously checked services are shown
    -->     
    <view-state id="contest-agencies" model="agencies">
        <on-entry>      
            <!--
            - in case i'm coming here from the step 3
            - injection of the list of ids previously checked
            -->
            <set name="viewScope.checked_agencies" value="contestFlow.converterSetContestAgencyToCheckbox(flowScope.contest.ContestAgencies)" />

            <!-- 
             - only agencies enabled for the step 2 checked services are shown
             -->
            <set name="viewScope.agencies_list" value="AgencyService.getEnabledAgenciesForServices(contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices))" />
        </on-entry>     

        <transition on="proceed" to="contest-confirm" >
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestAgency>
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestAgency(flowScope.contest, requestParameters.chk_agencies)" result="flowScope.contest.ContestAgencies"></evaluate>  
        </transition>

        <transition on="cancel" to="contest-services">
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestAgency>
            - 
            - and i need to save the checked Agencies to the Contest entity, 
            - else, when i will call addContest method, 
            - it will not save the checked Agencies
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestAgency(flowScope.contest, requestParameters.chk_agencies)" result="flowScope.contest.ContestAgencies"></evaluate>  
        </transition>
    </view-state>


    <!-- 
    - data confirmation before insert in db
     -->    
    <view-state id="contest-confirm" model="contest">       
        <transition on="proceed" to="contest-end" >
            <evaluate expression="ContestService.addContest(contest)" />
        </transition>
        <transition on="cancel" to="contest-agencies" />
    </view-state>   



    <!-- 
    end: redirect to list
     -->
    <end-state id="contest-end" view="externalRedirect:contextRelative:/contest/list"/>

    <!-- 
    cancella
     -->
    <end-state id="cancel"/>

</flow>