หน้าเว็บ

วันอาทิตย์ที่ 15 มิถุนายน พ.ศ. 2557

Lazy Load Selection Primefaces



ต่อยอดจาก code และ บทความ  custom JSF primefaces lazyLoad with Spring data

บทความนี้เป็นการต่อยอด  จากบทความเดิมที่ผมเขียนไว้ครับ
เป็นการแก้ปัญหา  การเก็บ state ของ primefaces table selection (check box)

คือ ผมเจอปัญหาว่า  เวลาใช้งาน primefaces table selection ที่เขาเตรียมมาให้  ร่วมกับ lazy load (paging)
มันไม่ยอมเก็บ state ให้  เมื่อเราเลือกไปที่ page อื่น แล้วกลับมาที่ page เดิม  อันที่เรา select ไว้จะหายไป

ผมจึงต้อง custom lazy load + การเก็บ state check box เอง  ลองมาดู code กันครับ
ซึ่งมี ขึ้นตอนดังต่อไปนี้

5 ขั้นตอน  การเขียน code
1. เขียน wrapper model สำหรับห่อ ข้อมูล ที่เราต้องการ select ไว้  ในที่นี้เป็น generic คือ สามารถใส่ข้อมูลอะไรลงไปก็ได้  แล้ว เพิ่ม attribute boolean selected เพื่อเอาไว้เก็บ state ของการ select ณ ขณะนั้น

SelectionModel.java
package com.blogspot.na5cent.web.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

/**
 *
 * @author redcrow
 */
public class SelectionModel<T> implements Serializable {

    private T data;
    private boolean selected = false;
    
    private SelectionModel(){
        //
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean getSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }

    public static <T> List<SelectionModel<T>> toSelection(List<T> list) {
        List<SelectionModel<T>> result = new ArrayList<>();
        for (T item : list) {
            result.add(toSelection(item));
        }

        return result;
    }

    public static <T> SelectionModel<T> toSelection(T item) {
        SelectionModel<T> model = new SelectionModel<>();
        model.setData(item);
        return model;
    }

    public static <T> Page<SelectionModel<T>> toSelection(Page<T> page, Pageable pageable) {
        return new PageImpl(toSelection(page.getContent()), pageable, page.getTotalElements());
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 43 * hash + Objects.hashCode(this.data);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final SelectionModel<?> other = (SelectionModel<?>) obj;
        if (!Objects.equals(this.data, other.data)) {
            return false;
        }
        return true;
    }

}

2. เขียน SelectionLazyLoad ซึ่ง extends พฤติกรรม LazyLoad มาจากบทความก่อนหน้านี้ครับ (custom JSF primefaces lazyLoad with Spring data) โดยในที่นี้  จะเพิ่มกระบวนการ  สำหรับการเก็บและคืน state ของ selection model  ขึ้นมา main ของ code ชุดนี้คือการ backup และ return state ของ selection ซึ่งจริงๆ แล้วก็ไม่ได้มีอะไรครับ

SelectionLazyLoad.java
package com.blogspot.na5cent.web.lazyload;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.blogspot.na5cent.model.SelectionModel;
import com.blogspot.na5cent.web.lazyload.LazyLoad;

/**
 *
 * @author redcrow
 */
public abstract class SelectionLazyLoad<T> extends LazyLoad<SelectionModel<T>> {

    private static final Logger LOG = LoggerFactory.getLogger(SelectionLazyLoad.class);

    private Set<T> selected;
    private boolean backup = false;

    public SelectionLazyLoad() {
        this(new HashSet<>());
    }

    public SelectionLazyLoad(List<T> selected) {
        this(selected == null ? new HashSet<>() : new HashSet<>(selected));
    }

    public SelectionLazyLoad(Set<T> selected) {
        if (selected == null) {
            this.selected = new HashSet<>();
        }

        this.selected = selected;
    }

    public abstract Page<T> loadPage(Pageable page);

    @Override
    public Page<SelectionModel<T>> load(Pageable page) {
        backupSelected(); // main *****

        Page<SelectionModel<T>> result = SelectionModel.toSelection(this.loadPage(page), page);

        retureSelected(result); // main *****
        return result;
    }

    private void backupSelected() {
        for (SelectionModel<T> item : this.getContents()) {
            if (item.getSelected()) {
                selected.add(item.getData());
            } else {
                selected.remove(item.getData());
            }
        }

        backup = true;
    }

    private void retureSelected(Page<SelectionModel<T>> result) {
        for (SelectionModel<T> item : result.getContent()) {
            if (selected.contains(item.getData())) {
                item.setSelected(true);
            }
        }

        backup = false;
    }

    public List<T> getSelected() {
        if (!backup) {
            backupSelected();
        }

        return new ArrayList<>(selected);
    }
}

3. สร้าง lazy load ทดสอบ  โดย extends พฤติกรรมจาก SelectionLazyLoad ที่ได้เขียนเอาไว้ ในข้อ 2  ตัวนี้เขียนเป็น LazyLoad ธรรมดาๆ เหมือน LazyLoad ของบทความก่อนหน้า  เพียงแต่ตรง constructor จะให้มีการเรียก super เพื่อ set initial select item ครับ

ForwardLazyLoad.java
package com.blogspot.na5cent.web.lazyload;

import java.util.HashSet;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import com.blogspot.na5cent.web.services.ForwardService;
import com.blogspot.na5cent.web.util.SpringUtils;
import com.blogspot.na5cent.web.model.Hospital;

/**
 *
 * @author redcrow
 */
public class ForwardLazyLoad extends SelectionLazyLoad<Hospital> {

    private static final Logger LOG = LoggerFactory.getLogger(ForwardLazyLoad.class);

    private final ForwardService service;
    private final String keyword;

    public ForwardLazyLoad(List<Hospital> forwards) {
        this(null, forwards);
    }

    public ForwardLazyLoad(String keyword, List<Hospital> forwards) {
        super(forwards);
        this.keyword = keyword;
        service = SpringUtils.getBean(ForwardService.class);
    }

    @Override
    public Page<Hospital> loadPage(Pageable page) {
        if (keyword == null) {
            return service.findAll(page);
        }

        return service.searchByHname(keyword, page);
    }
}

4. ตัวอย่างการใช้งานใน managedbean หรือ controller (อันนี้ผมเขียนโดยใช้ spring component ครับ  จริงๆ แล้วมันก็คือ managedbean นั่นแหล่ะครับ  แต่มันเป็น managedbean ที่ถูกสร้างโดย spring framework http://na5cent.blogspot.com/2013/07/jsf-managedbean-can-autowire-spring.html)
ServiceController.java
...
...
...

@Component
@Scope("session")
public class ServiceController implements Serializable {

    private ForwardLazyLoad forwardLazyLoad;
 
    public void onLoadForward() {
        List<Hospital> hospitals = ...
        forwardLazyLoad = new ForwardLazyLoad(hospitals);
    } 

    public ForwardLazyLoad getForwardLazyLoad() {
        return forwardLazyLoad;
    }
 
    public void onSave() {
        ...
        ...
  
        List<Hospital> hospitals = forwardLazyLoad.getSelected();
  
        ...
        ...
  
    } 
}

5. ฝั่ง xhtml (ตรง check box ใช้ <p:selectBooleanCheckbox/> ครับ)

.xhtml
<p:dataTable value="#{serviceController.forwardLazyLoad}"
    var="item"
    rows="10"
    lazy="true"
    emptyMessage="ไม่มีข้อมูล"
    sortBy="#{item.hcode}"
    sortOrder="ASCENDING"
    rowIndexVar="counter"
    paginator="true"
    paginatorPosition="bottom"
    paginatorTemplate="{CurrentPageReport}  {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
    rowsPerPageTemplate="5,10,15,25,50">

    <p:column headerText="เลือก"
        style="width:2%;">
        <p:selectBooleanCheckbox value="#{item.selected}"/>
    </p:column>
 
    <p:column headerText="ที่.">
        <h:outputText value="#{counter + 1}"/>
    </p:column>
 
    <p:column headerText="หน่วยบริการ">
        <h:outputText value="#{item.data.fullName}"/> <!--- ใช้ .data.fullName แทน .fullName เพราะมันถูก wrap ด้วย SelectionModel -->
    </p:column>

</p:dataTable>
แค่นี้   selection + lazyload ที่เราเขียนขึ้นมา  ก็สามารถเก็บ state ไว้ได้แล้วครับ

1 ความคิดเห็น: