Thứ Năm, 1 tháng 3, 2012

JSF - Bài 8: Điểm trung bình toàn môn học



Center of Excellence


    Bài 8
    Điểm trung bình toàn môn học
I. Bài toán

Một giảng viên cho học viên làm nhiều bài kiểm tra trong suốt một học kỳ. Đến cuối kỳ, khi tính điểm trung bình cho một học viên, giảng viên đó sẽ loại ra điểm kiểm tra thấp nhất. Chẳng hạn, nếu tập các điểm kiểm tra của một học viên là 85, 70, 40, và 95, thì khi tính trung bình, điểm thấp nhất 40 sẽ được loại ra, và lúc đó điểm trung bình môn sẽ là (85 + 70 + 95) / 3 = 83,3. Hãy phát triển một web application nhằm giúp giảng viên thực hiện thao tác tính trung bình cho một học viên.

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

Hình 1

II. Giải pháp

Project: AverageScoreJSF

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"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Average Score</title>
        <script src="resources/clearInputTexts.js"/>
        <style>
            body {
                margin: 0 20% 0 20%;
            }
            fieldset label {
                width: 190px;
                float: left;
                text-align: right;
            }
            div.buttons {
                margin-left: 205px;
            }
        </style>
    </h:head>
    <h:body>
        <h2>Average Score</h2>
        <h:form prependId="false">
            <fieldset>
                <label>List of scores:</label>
                <h:inputText disabled="true"/><br/>
                
                <label>Score to add:</label>
                <h:inputText id="Score" required="true">
                    <f:validateLongRange minimum="0" maximum="100"/>
                </h:inputText>
                <h:message for="Score" style="color:red"/><br/>
                
                <label>Average without min:</label>
                <h:inputText disabled="true"/>
            </fieldset>
            <div class="buttons">
                <h:commandButton value="Add"/>
                <h:commandButton value="Average"/>
                <h:commandButton value="Clear" action="#{index.clear()}"
                                 immediate="true" onclick="clearInputTexts(this.form)"/>
            </div>
        </h:form>
    </h:body>
</html>


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

2.1. Thiết kế managed bean

Hình 2

Hành vi của application này có điểm khác biệt với applications ở các bài trước ở chỗ: Sau khi user nhập một score, application cần nhớ lại score này để có thể dùng cho thao tác tính trung bình sau này. Khi đó, ta không thể đặt managed vào request scope vì scope này chỉ có khả năng nhớ ngắn hạn, trong khoảng thời gian từ lúc user nhập data, clicks button để truyền yêu cầu về server, cho đến khi server hồi đáp về browser là kết thúc. Để duy trì managed bean dài hạn hơn, ta cần đặt nó vào session scope. Scope này sẽ duy trì managed bean cho đến khi kết thúc phiên làm việc (session).

Hình 3


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

package controller;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named(value = "index")
@SessionScoped
public class Index implements Serializable {
private List<Integer> scores = new ArrayList();
    private Integer scoreToAdd;
    private Double averageWithoutMin;

    public Index() {
    }
    
    public void averageWithoutMin() {
        // TODO
    }
    
    public void add() {
        scores.add(scoreToAdd);
        scoreToAdd = null;
    }
    
    public void clear() {
        scores.clear();
        scoreToAdd = null;
        averageWithoutMin = null;
    }

    public List<Integer> getScores() {
        return scores;
    }

    public Integer getScoreToAdd() {
        return scoreToAdd;
    }

    public void setScoreToAdd(Integer scoreToAdd) {
        this.scoreToAdd = scoreToAdd;
    }

    public Double getAverageWithoutMin() {
        return averageWithoutMin;
    }
}


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

<?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"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Average Score</title>
        <script src="resources/clearInputTexts.js"/>
        <style>
            body {
                margin: 0 20% 0 20%;
            }
            fieldset label {
                width: 190px;
                float: left;
                text-align: right;
            }
            div.buttons {
                margin-left: 205px;
            }
        </style>
    </h:head>
    <h:body>
        <h2>Average Score</h2>
        <h:form prependId="false">
            <fieldset>
                <label>List of scores:</label>
                <h:inputText disabled="true" value="#{index.scores}"/><br/>
                
                <label>Score to add:</label>
                <h:inputText id="Score" required="true" value="#{index.scoreToAdd}">
                    <f:validateLongRange minimum="0" maximum="100"/>
                </h:inputText>
                <h:message for="Score" style="color:red"/><br/>
                
                <label>Average without min:</label>
                <h:inputText disabled="true" value="#{index.averageWithoutMin}"/>
            </fieldset>
            <div class="buttons">
                <h:commandButton value="Add" action="#{index.add()}"/>
                <h:commandButton value="Average" action="#{index.averageWithoutMin()}"/>
                <h:commandButton value="Clear" action="#{index.clear()}"
                                 immediate="true" onclick="clearInputTexts(this.form)"/>
            </div>
        </h:form>
    </h:body>
</html>


3. Phát triển model

3.1. Design

Hình 4


3.2. Test class

package model;

import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class ScoreCollectionTest {
    @Test
    public void averageWithoutMin() {
        List<Integer> scores = Arrays.asList(85, 70, 40, 95);
        assertEquals(83.3, new ScoreCollection(scores).averageWithoutMin(), 0.04);
    }
}


3.3. Main class

package model;

import java.util.Collections;
import java.util.List;

public class ScoreCollection {
    private List<Integer> scores;

    public ScoreCollection(List<Integer> scores) {
        this.scores = scores;
    }

    public double averageWithoutMin() {
        return (double)this.sumWithoutMin() / (scores.size() - 1);
    }

    private int sumWithoutMin() {
        return sum() - Collections.min(scores);
    }

    private int sum() {
        int acc = 0;
        for (int score : this.scores)
            acc += score;
        return acc;
    }
}


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

@Named(value = "index")
@SessionScoped
public class Index implements Serializable {
private List<Integer> scores = new ArrayList();
    private Integer scoreToAdd;
    private Double averageWithoutMin;

    public Index() {
    }
    
    public void averageWithoutMin() {
        averageWithoutMin = new ScoreCollection(scores).averageWithoutMin();
    }
    
    public void add() {
        scores.add(scoreToAdd);
        scoreToAdd = null;
    }
    
    public void clear() {
        scores.clear();
        scoreToAdd = null;
        averageWithoutMin = null;
    }

    // getter, setter
}

Khi thi hành application, nhập vào một số giá trị, rồi click Average, ta nhận được thông báo yêu cầu không được để trống ô Score to add (!) Ta xử lý trường hợp này bằng cách thêm attribute immediate=”true” vào Average button.

<div class="buttons">
    <h:commandButton value="Add" action="#{index.add()}"/>
    <h:commandButton value="Average" action="#{index.averageWithoutMin()}"
                     immediate="true"/>
    <h:commandButton value="Clear" action="#{index.clear()}"
                     immediate="true" onclick="clearInputTexts(this.form)"/>

Ngoài ra, ta có thể qui định là điểm trung bình được hiển thị dưới dạng có một chữ số sau dấu chấm thập phân.

<label>Average without min:</label>
<h:inputText disabled="true" value="#{index.averageWithoutMin}">
    <f:convertNumber pattern="#0.0"/>
</h:inputText>


5. Kiểm tra tính hợp lệ của input từ managed bean

Trong index.xhtml ta đã qui định rằng user chỉ có thể nhập các điểm số từ 1 đến 100. Tuy nhiên, nếu user không nhập điểm mà clicks ngay Average, ta sẽ gặp ngoại lệ NoSuchElementException. Lý do là application không có gì để tính trung bình. Như vậy, ta cần phát ra một thông báo lỗi trong trường hợp này hoặc trường hợp tập hợp điểm chỉ chứa một phần tử.

Trong averageWithoutMin() thuộc managed bean, trước khi tính trung bình, ta bổ sung code kiểm tra. Nếu input không hợp lệ, ta trả về một thông báo lỗi.

public void averageWithoutMin() {
    if (scores.size() <= 1) {
        String msg = "A score is expected.";
        FacesContext.getCurrentInstance().addMessage("Score",
                new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
        return;
    }
    
    averageWithoutMin = new ScoreCollection(scores).averageWithoutMin();
}


III. Tổng kết

Bài viết đã vận dụng session scope để tính điểm trung bình của toàn môn học. Ngoài ra, bài viết còn trình bày cách xử lý trường hợp data không hợp lệ trong managed bean.


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.

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

Đăng nhận xét