Friday, May 31, 2013

ZK - Paginating Large Results sets in ZK with MyBatis

Benefit of this type of paging is that it doesn't load all the results up front. Useful for very large result sets. I'm a fan of MyBatis, so this example below demonstrates the use with MyBatis.



ZUL


 
 <paging id="executionsPaging"
  onCreate="executionsList.setPaginal(self)"
  totalSize="@bind(vm.executionsTotalSize)"
  pageSize="@bind(vm.executionsPageSize)"
  activePage="@bind(vm.currentPageNumber)"
  onPaging="@command('pageExecutions', pageNum=event.activePage)"
  /> 


 <listbox id="executionsList" model="@load(vm.executions)"
       selectedItem="@bind(vm.selectedExecution)".... />


Executions ViewModel


public class ExecutionsVM {
 private final static Logger logger = LoggerFactory.getLogger(ExecutionsVM.class);

 private List executions = new ArrayList();
 private ExecutionSearchFilter searchFilter = new ExecutionSearchFilter();
 private long executionsTotalSize = 0l;
 private int executionsPageSize = 25;
 private int currentPageNumber = 0;

 @WireVariable
 private ExecutionService executionService;

 @NotifyChange({"executions","executionsTotalSize"})
 @Command
 public void pageExecutions(@BindingParam("pageNum") int pageNum) {
  logger.debug("pageExecutions for pagNum {}", pageNum);
  this.currentPageNumber = pageNum;
  populateSearchFilter();
  executions = executionService.getExecutions(searchFilter);
  executionsTotalSize = executionService.getExecutionsSize(searchFilter);
 }

 private void populateSearchFilter() {
  int base = this.currentPageNumber * executionsPageSize;
  int firstRow = base + 1;
  int lastRow = base + executionsPageSize;
  searchFilter.setLowerRowLimit(firstRow);
  searchFilter.setUpperRowLimit(lastRow);
 }

}

Executions Service (wraps Mapper)


@Service
public class ExecutionService {

 @Resource
 private ExecutionMapper executionMapper;

 public List featchExecutions(ExecutionSearchFilter searchFilter) {
  return executionMapper.getExecutions(searchFilter);
 }
}

Executions Mapper


public interface ExecutionMapper {
 List featchExecutions(ExecutionSearchFilter searchFilter) 
}

Search Filter POJO


public class ExecutionSearchFilter {
 private int upperRowLimit;
 private int lowerRowLimit;
    //setters/getters
}

Executions Mapper MXL


<select id="getExecutions" resultMap="executionResultMap" parameterType="ExecutionSearchFilter">
  select
   EXECUTION_ID,
   EXECUTION_NM,
   ROW_NUM
  FROM
   (
    SELECT
     tempRow.EXECUTION_ID,
     tempRow.EXECUTION_NM,
     rownum ROW_NUM
    from
    (
     SELECT
      e.EXECUTION_ID,
      e.EXECUTION_NM
     FROM
      EXECUTION_T e
    ) tempRow
    WHERE
     rownum <![CDATA[ <= ]]> #{upperRowLimit}
   )
  WHERE
   ROW_NUM >= #{lowerRowLimit}
</select>

Friday, May 24, 2013

ZK Tips

Figured I'd list some of the tips here as I work with ZK. A lot of things aren't immediately apparent even using the online docs.

NotifyChange from your Base ViewModels


In annoying thing in ZK is that if your ViewModel extends another ViewModel and your base ViewModel decides that it needs to delegate a call to its subclass, the subclass @NotifyChange({"props}") will not automatically be triggered. As an example take:
public abstract class BaseVM {

 public void doSomething() {
  //do Stuff
  //delegate to implementing class
  doSomeStuff();
 }
 public abstract void doSomeStuff();
}

public ChildVM extends BaseVM {
 private String someProperty;

 @NotifyChange("someProperty")
 public void doSomeStuff() {
  someProperty = "blah";
 }

 public String getSomeProperty() {
  return someProperty;
    }
}
It would be nice if when "doSomeStuff" is called that "someProperty" would be able to be notified so it could display properly in your zul. Matze2 on the forums helped provide the work around http://forum.zkoss.org/question/86637/notifychange-in-subclass-methods-called-from-parent-will-not-fire/ Solution is to use the "Binder" object. Illustrated as so...
public abstract class BaseVM {
 
 protected Binder binder;

 @Init
 public final void init(@ContextParam(ContextType.BINDER) Binder _binder) {
  binder = _binder;
 }

 public void doSomething() {
  //do Stuff
  //delegate to implementing class
  doSomeStuff();
  binder.notifyChange(this, "someProperty");
 }
 public abstract void doSomeStuff();
}

public ChildVM extends BaseVM {
 private String someProperty;

 @Init(superclass=true)
 public void init() {
 }

 @NotifyChange("someProperty")
 public void doSomeStuff() {
  someProperty = "blah";
 }

 public String getSomeProperty() {
  return someProperty;
    }
}
Don't forget the @Init(superclass=true) in your subclass (I think you can add it to the Class annotation if you aren't providing an init method in your subclass.) The above is clunky but it works.

Use Constants for your GlobalCommands


Most of the documentation and examples demonstrate declaring GlobalCommmand annotations like:
@GlobalCommand
public void someMethod() { ... }
and calling it from some other ViewModel like so:
BindUtils.postGlobalCommand(null, null, "someMethod", null);
The problem with this is it's extremely fragile. If you decide to refactor your method name, you end up then having to do a search and replace for everywhere you've called your Global command with "someMethod." Possibly more annoying is you do not get any IDE help when you want to post to this GlobalCommand from another ViewModel... you need to recall the exact name of the method and if you can't recall the name you have to remember where that GlobalCommand was defined which could take time in a large project. Instead, I recommend always getting in the habit of using a GlobalCommandValues class that declares the GlobalCommand as String constant and then using it in your annotation and of course in your postGlobalCommand calls. This seems obvious, but I wasn't even aware it was an option.
//GlobalCommandValues class
public static final String SOME_METHOD = "someMethod";

//ViewModel
@GlobalCommand(GlobalCommandValues.SOME_METHOD)
public void someMethodDoesntHaveToMatchYourConstant() { ... }

//Usage
BindUtils.postGlobalCommand(null, null, GlobalCommandValues.SOME_METHOD, null);

Blogger Syntax Highliter