JSP file: /WEB-INF/jsp/mortgages/list.jsp

<%-- 

  An example of a paged list with multiple checkbox bound to a Set property 
  using the selected primary keys.

--%>
<tags:layout>

  <jsp:attribute name="js">
    <script>
    
      // the table may not be present if there are no results
      var table = $("mortgages");
      if (table) { 
          // create table checkbox selector
          new loom.ui.tables.MultiCheckbox(table);
          
          // make table rows behave as links
          new loom.ui.tables.LinkTable(table);
      }
      
    </script>
  </jsp:attribute>
  
  <jsp:body>
    <l:form action="Mortgages" event="delete">
    
        <div class="buttonBar">
            <l:button type="submit" value="delete"/>
        </div>

        <l:pagedTable data="${action.mortgages}">
          <l:column sortable="false">
                <l:inputCheckbox name="selectedRows[${row.id}]" class="selectRow checkbox" renderLabel="false"/>
          </l:column>
          <l:column property="id" />
          <l:column property="name" action="Mortgages" event="edit">
             <l:param name="mortgage.id" value="${row.id}"/>
          </l:column>
          <l:column property="address" />
          <l:column property="principalLoanBalance" title="Loan" />
          <l:column property="creationDate" class="date" />
        </l:pagedTable>
            
    </l:form>
  </jsp:body>
    
</tags:layout>

Class: Mortgage.java

/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.loom.demo.model;

import java.math.BigDecimal;
import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

import org.loom.annotation.validation.DateValidation;
import org.loom.annotation.validation.NumberValidation;

@Entity
public class Mortgage extends AbstractProduct {

    @Basic(optional=false)
    @Column(length=80)
    private String address;
    
    @Column(nullable=false, length=2)
    private String country;
    
    @Basic(optional=false)
    @Column(scale=2, precision=11)
    @NumberValidation(minValue="10000", scale=2, precision=11)
    private BigDecimal principalLoanBalance;
    
    @Basic(optional=false)
    @NumberValidation(maxValue="100", excludeMax=true)
    private BigDecimal interestRate;
    
    @Basic(optional=false)
    @NumberValidation(minValue="1", maxValue="50")
    private int lengthYears;
    
    @Basic(optional=false)
    @Temporal(TemporalType.TIMESTAMP)
    @DateValidation(minValue="today + 1d", maxValue="today + 1y")
    private Date start;
    
    /** optimistic locking */
    @Version
    private Integer version;
    
    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }

    public int getLengthYears() {
        return lengthYears;
    }

    public void setLengthYears(int lengthYears) {
        this.lengthYears = lengthYears;
    }

    public BigDecimal getPrincipalLoanBalance() {
        return principalLoanBalance;
    }

    public void setPrincipalLoanBalance(BigDecimal principalLoanBalance) {
        this.principalLoanBalance = principalLoanBalance;
    }

    public Date getStart() {
        return start;
    }

    public void setStart(Date start) {
        this.start = start;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

}

Class: MortgagesAction.java

/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.loom.demo.action;

import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.loom.action.AbstractAction;
import org.loom.annotation.Event;
import org.loom.annotation.RetrieveEntity;
import org.loom.annotation.validation.NestedAnnotations;
import org.loom.demo.model.Mortgage;
import org.loom.exception.EntityNotFoundException;
import org.loom.log.Log;
import org.loom.paged.PagedListCriteria;
import org.loom.paged.PagedListCriteriaFactory;
import org.loom.paged.PagedListData;
import org.loom.persistence.ExtendedEntityManager;
import org.loom.resolution.Resolution;
import org.loom.tags.core.LabelComparator;
import org.loom.tags.core.Option;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Example of a typical CRUD interface
 * 
 * @author icoloma
 */
public class MortgagesAction extends AbstractAction {

    /** controller that handles the CRUD stuff */
    @Autowired
    private ExtendedEntityManager transactionalService;
    
    /** the paged list of Mortgages */
    private PagedListData<Mortgage> mortgages;
    
    /** the instance that is being currently edited */
    @NestedAnnotations(on="save")
    @RetrieveEntity(on={"save", "edit"})
    private Mortgage mortgage;
    
    /** list of rows selected for removal */
    private Set<Integer> selectedRows;
    
    private static Log log = Log.getLog();
    
    /**
     * Show a paged list of Mortgage instances
     */
    @GET @Path("/")
    @Event(defaultEvent=true)
    public Resolution list() {
        PagedListCriteria criteria = PagedListCriteriaFactory.create(getRequest(), "mortgages");
        criteria.setQuery("from Mortgage m order by m.id"); 
        criteria.setPageSize(5); // small value to test pagination
        mortgages = transactionalService.query(criteria);
        return forward();
    }
    
    /**
     * Show a form to introduce a new Mortgage
     */
    @GET @Path("create")
    public Resolution create() {
        return forward("edit.jsp");
    }
    
    /**
     * Show a form to edit an existing Mortgage
     */
    @GET @Path("/{mortgage.id}")
    public Resolution edit() {
        return forward();
    }
    
    /**
     * Save a new/existing Mortgage instance
     */
    @POST @Path("/{mortgage.id?}")
    public Resolution save() {
        mortgage = transactionalService.merge(mortgage);
        return redirect(MortgagesAction.class, "list");
    }
    
    /**
     * Delete a selected list of Mortage instances
     */
    @DELETE @Path("/{mortgage.id?}")
    public Resolution delete() {
        if (mortgage !=  null  && mortgage.getId() != null) {
            // a single mortgage selected for removal 
            transactionalService.remove(mortgage);
        } if (selectedRows != null) {
            // multiple rows selected
            for (Integer id : selectedRows) {
                try {
                    transactionalService.remove(Mortgage.class, id);
                } catch (EntityNotFoundException e) {
                    log.debug("Mortgage instance " + id + " could not be deleted because it was not found: " + e.getMessage());
                }
            }
        }
        return redirect(MortgagesAction.class, "list");
    }
    
    /**
     * Retrieve the list of countries to display in the <select> tag
     * @return a list of valid countries
     */
    public Set<Option> getCountries() {
        LabelComparator comparator = new LabelComparator();
        comparator.setRepository(getMessagesRepository());
        Set<Option> countries = new TreeSet<Option>(comparator);
        Locale currentLocale = getMessagesRepository().getLocale();
        Locale[] locales = Locale.getAvailableLocales();
        for (Locale locale : locales) { 
            // options are translated manually by locale.getDisplayCountry()
            countries.add(new Option(locale.getCountry(), locale.getDisplayCountry(currentLocale), false)); 
        }
        return countries;
    }
    
    public boolean isReadOnly() {
        return mortgage != null && mortgage.isReadOnly() && getContext().getMessages().isEmpty();
    }
    
    public Mortgage getMortgage() {
        return mortgage;
    }

    public PagedListData<Mortgage> getMortgages() {
        return mortgages;
    }

    public void setMortgage(Mortgage mortgage) {
        this.mortgage = mortgage;
    }

}