Thứ Bảy, 4 tháng 2, 2012

JSF - Bài 7: Doanh thu trong tuần

    Center of Excellence


    Bài 7
    Doanh thu trong tuần
I. Bài toán

Hãy phát triển một web application để tính tổng doanh thu trong tuần, doanh thu bình quân ngày, doanh thu ngày cao nhất, và doanh thu ngày thấp nhất. Ví dụ về doanh thu của một tuần như sau:

1000.00, 500.00, 3000.00, 4000.00, 9000.00, 6000.00, 7000.00

Dưới đây là một UI mẫu.

Hình 1


II. Giải pháp

Project: WeeklySalesJSF

1. Thiết kế UI screen

index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <head>
        <title>Weekly Sales</title>
        <script src="resources/clearForm.js"/>
        <style>
            table input,
            fieldset input {
                width: 100px;
            }
            fieldset {
                margin: 0 60% 0 0;
            }
            fieldset label {
                width: 140px;
                float: left;
                text-align: right;
            }
        </style>
    </head>
    <body>
        <h2>Weekly Sales</h2>
        
        <table>
            <thead>
                <tr>
                    <th>Monday</th>
                    <th>Tuesday</th>
                    <th>Wednesday</th>
                    <th>Thursday</th>
                    <th>Friday</th>
                    <th>Saturday</th>
                    <th>Sunday</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><input/></td>
                    <td><input/></td>
                    <td><input/></td>
                    <td><input/></td>
                    <td><input/></td>
                    <td><input/></td>
                    <td><input/></td>
                </tr>
            </tbody>
        </table>
        
        <fieldset>
            <legend>Statistics</legend>
            
            <label>Total weekly sales $</label>
            <input disabled="true"/><br/>
            
            <label>Average daily sales $</label>
            <input disabled="true"/><br/>
            
            <label>Highest daily sales $</label>
            <input disabled="true"/><br/>
            
            <label>Lowest daily sales $</label>
            <input disabled="true"/>
        </fieldset>
        
        <div>
            <input type="submit" value="Compute Statistics"/>
            <input type="button" value="Clear" onclick="clearForm(this.form)"/>
        </div>
    </body>
</html>

2. Bổ sung phần động cho UI screen

2.1. Thiết kế managed bean

Thay vì phải dùng bảy fields trong managed bean để lưu trữ bảy doanh số (sales) nhập vào từ user, ta tập hợp chúng vào một array, gọi là salesFigures.

Hình 2

Tuy nhiên, data type của salesFigures không thể là Double[], bởi vì đây là input component, ta phải có setter cho từng thành phần của Double[], tức phải có setter cho từng Double, nhưng Double đã được qui định sẵn là không có setter. Để giải quyết vấn đề này, ta có thể dùng wrapper class, lấy tên là SalesFigure, để bọc một Double lại, rồi bổ sung getter và setter cho wrapper đó.

Hình 3

2.2. Tạo fields và methods cho managed bean

Tạo SalesFigure class

package controller;

public class SalesFigure {
    private Double value;

    public SalesFigure() {
    }

    public Double getValue() {
        return value;
    }

    public void setValue(Double value) {
        this.value = value;
    }
}


Tạo managed bean

Lưu ý rằng ta cần khởi tạo SalesFigure[]. Dưới đây, ta đặt code khởi tạo bên trong makeSalesFigures() method, và method này được gọi thi hành bên trong constructor.

package controller;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named(value = "index")
@RequestScoped
public class Index {
    private SalesFigure[] salesFigures;
    private Double sum;
    private Double average;
    private Double max;
    private Double min;

    public Index() {
        salesFigures = makeSalesFigures();
    }

     private SalesFigure[] makeSalesFigures() {
        SalesFigure[] figures = new SalesFigure[7];
        for (int i = 0; i < figures.length; i++)
            figures[i] = new Figure();
        return figures;
    }
    
    public void computeStatistics() {
        // TODO
    }

    public SalesFigure[] getSalesFigures() {
        return salesFigures;
    }

    public Double getAverage() {
        return average;
    }

    public Double getMax() {
        return max;
    }

    public Double getMin() {
        return min;
    }

    public Double getSum() {
        return sum;
    }
}


2.3. Ràng buộc UI screen với managed bean

Khi ràng buộc <table> ở UI screen với salesFigures[] trong managed bean, ta dùng một cấu trúc lặp của JSF, tương tự như for-each, đó là <ui:repeat>. Cần lưu ý value attribute trong <h:inputText>#{figure.value}.

Đối với các components khác, ta thực hiện việc ràng buộc như đã làm trong những bài trước.

Hình 4


3. Phát triển model

3.1. Design

Hình 5

Lưu ý là ta không cần dùng wrapper class cho salesFigures bên trong model. Tất nhiên là sau này, khi kết nối giữa managed bean và model, ta cần phải chuyển đổi từ SalesFigure[] về double[].


3.2. Test class

package model;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class WeeklySalesTest {
    private WeeklySales weeklySales = new WeeklySales(
            1_000.0, 500.0, 3_000.0, 4_000.0, 9_000.0, 6_000.0, 7_000.0);
    
    @Test public void sum() {
        assertEquals(30_500.0, weeklySales.sum(), 0.0);
    }
    
    @Test public void average() {
        assertEquals(4_357.14, weeklySales.average(), 0.009);
    }
    
    @Test public void max() {
        assertEquals(9_000.0, weeklySales.max(), 0.0);
    }
    
    @Test public void min() {
        assertEquals(500.0, weeklySales.min(), 0.0);
    }
}

3.3. Main class

package model;

public class WeeklySales {
    private final double[] salesFigures;

    public WeeklySales(double... salesFigures) {
        this.salesFigures = salesFigures;
    }
    
    public double sum() {
        double acc = 0.0;
        for (double saleFigure : this.salesFigures)
            acc += salesFigure;
        return acc;
    }
    
    public double average() {
        return sum() / salesFigures.length;
    }
    
    public double max() {
        double currentMax = Double.NEGATIVE_INFINITY;
        for (double salesFigure : this.salesFigures)
            if (salesFigure > currentMax)
                currentMax = salesFigure;
        return currentMax;
    }
    
    public double min() {
        double currentMin = Double.POSITIVE_INFINITY;
        for (double salesFigure : this.salesFigures)
            if (salesFigure < currentMin)
                currentMin = salesFigure;
        return currentMin;
    }
}


4. Kết nối managed bean với model

Như đã đề cập ở trên, trước khi gọi model từ managed bean, ta cần chuyển đổi Figure[] về double[]. Điều này thể hiện trong toDoubleArrayOfFigures().

    public void computeStatistics() {
        WeeklySales weeklySales = new WeeklySales(salesFiguresToDoubles());
        sum = weeklySales.sum();
        average = weeklySales.average();
        max = weeklySales.max();
        min = weeklySales.min();
    }
    
    private double[] salesFiguresToDoubles() {
        double[] a = new double[figures.length];
        for (int i = 0; i < a.length; i++)
            a[i] = figures[i].getValue();
        return a;
    }


5. Định dạng số

Bây giờ ta muốn hiển thị kết quả tính toán theo các điều kiện sau
  • Áp dụng dấu phân cách hàng ngàn (thousands separator) cho những số lớn
  • Nếu không có chữ số khác 0 nào đứng trước dấu chấm thập phân (decimal point) thì sẽ hiển thị số 0 trước decimal point
  • Có hai chữ số sau decimal point

    Ta điều chỉnh lại index.xhtml như sau

            <fieldset>
                <legend>Statistics</legend>

                <label>Total weekly sales: $</label>
                <h:inputText disabled="true" value="#{index.sum}">
                    <f:convertNumber pattern="#,##0.00"/>
                </h:inputText><br/>

                <label>Average daily sales: $</label>
                <h:inputText disabled="true" value="#{index.average}">
                    <f:convertNumber pattern="#,##0.00"/>
                </h:inputText><br/>

                <label>Highest daily sales: $</label>
                <h:inputText disabled="true" value="#{index.max}">
                    <f:convertNumber pattern="#,##0.00"/>
                </h:inputText><br/>

                <label>Lowest daily sales: $</label>
                <h:inputText disabled="true" value="#{index.min}">
                    <f:convertNumber pattern="#,##0.00"/>
                </h:inputText>
            </fieldset>


6. Kiểm tra tính hợp lệ của input

Hình 6


III. Tổng kết

Bài viết đã đề cập các khái niệm và kỹ thuật sau
  • Cấu trúc bảng với <table>
  • Wrapper class cùng getter và setter để ràng buộc web page với managed bean
  • Định dạng với <f:convertNumber>


IV. Tài liệu tham khảo
  1. Gaddis T. (2010) Starting Out with Java From Control Structures Through Objects (4th edition), Pearson Education, Boston. Chương 8.
  2. Ed Burns, Chris Schalk (2010) JavaServer Faces 2.0: The Complete Reference, McGraw-Hill.


V. Bài tập

Hãy điều chỉnh để application có thể tiếp nhận input từ user theo các điều kiện đã được đề cập trong phần định dạng output. Ví dụ, application có thể tiếp nhận input có giá trị là 1,234.56.

Không có nhận xét nào:

Đăng nhận xét