Tải bản đầy đủ (.pdf) (31 trang)

Sức mạnh của JSF 2, Phần 3: Xử lý sự kiện, JavaScript và Ajax ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (994.59 KB, 31 trang )

Sức mạnh của JSF 2, Phần 3: Xử lý sự kiện, JavaScript và Ajax
Nâng cao các thành phần phức hợp bằng cách sử dụng các tính năng JSF 2 mới
David Geary, Chủ tịch, Clarity Training, Inc.
Tóm tắt: David Geary, thành viên nhóm chuyên gia Java™Server Faces (JSF) 2,
kết thúc loạt bài ba phần của ông về các tính năng mới của JSF 2. Tìm hiểu cách
sử dụng mô hình sự kiện mới của khung công tác và sự hỗ trợ kèm sẵn cho Ajax
để làm cho tất cả các thành phần tái sử dụng của bạn càng mạnh mẽ hơn.
Một trong các điểm hấp dẫn lớn nhất của JSF là nó là một khung công tác dựa vào
thành phần. Điều đó có nghĩa là bạn hoặc những người khác có thể thực hiện các
thành phần, các thành phần có thể tái sử dụng. Cơ chế tái sử dụng mạnh mẽ đó,
đối với hầu hết các phần, đã biểu hiện không đáng kể trong JSF 1 vì đã rất khó
triển khai thực hiện các thành phần.
Tuy nhiên, như bạn đã thấy trong Phần 2, JSF 2 làm cho dễ dàng triển khai thực
hiện các thành phần — không cần mã Java và không có cấu hình — với một tính
năng mới được gọi là các thành phần phức hợp. Tính năng đó có thể là phần quan
trọng nhất của JSF 2, vì cuối cùng nó thực hiện được tiềm năng của các thành
phần JSF.
Trong bài thứ ba và là bài cuối cùng về JSF 2 này, tôi sẽ cho bạn thấy làm thế nào
để cải thiện tính năng của thành phần phức hợp bằng cách sử dụng Ajax mới và
các khả năng xử lý sự kiện cũng được đưa vào trong JSF 2, với các lời khuyên sau
đây để khai thác tốt nhất JSF 2:
 Lời khuyên 1: Hãy thành phần hóa
 Lời khuyên 2: Hãy Ajax hóa
 Lời khuyên 3: Hãy cho xem tiến độ
Trong lời khuyên đầu tiên, tôi sẽ xem xét lại ngắn gọn hai thành phần mà tôi thảo
luận chi tiết trong Phần 2. Trong các lời khuyên sau đó, tôi sẽ cho bạn thấy làm thế
nào để chuyển đổi các thành phần đó bằng cách sử dụng Ajax và xử lý-sự kiện.
Lời khuyên 1: Hãy thành phần hóa
Ứng dụng các địa điểm, mà tôi đã giới thiệu trong Phần 1, có chứa một số thành
phần phức hợp. Một là thành phần map (bản đồ), hiển thị một bản đồ của một địa
chỉ, bổ sung thêm một trình đơn thả xuống gồm các mức phóng to, như trong Hình


1:

Hình 1. Thành phần map của ứng dụng các địa điểm

Liệt kê mã rút gọn của thành phần map được hiển thị trong Liệt kê 1:

Liệt kê 1. Thành phần map
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="

xmlns:composite="
xmlns:places="

<! INTERFACE >
<composite:interface>
<composite:attribute name="title"/>
</composite:interface>

<! IMPLEMENTATION >
<composite:implementation">
<div class="map">

<h:panelGrid >
<h:panelGrid >
<h:selectOneMenu onchange="submit()"
value="#{cc.parent.attrs.location.zoomIndex}"
valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"
style="font-size:13px;font-family:Palatino">

<f:selectItems value="#{places.zoomLevelItems}"/>


</h:selectOneMenu>
</h:panelGrid>
</h:panelGrid>

<h:graphicImage url="#{cc.parent.attrs.location.mapUrl}"
style="border: thin solid gray"/>


</div>


</composite:implementation>
</html>

Một trong những điều tuyệt vời của các thành phần là bạn có thể thay thế chúng
bằng các lựa chọn thay thế khác, mạnh hơn mà không làm ảnh hưởng bất kỳ các
chức năng xung quanh nào. Ví dụ, trong Hình 2, tôi đã thay thế thành phần image
(hình ảnh) trong Liệt kê 1 bằng thành phần Google Maps, với sự cho phép của
GMaps4JSF (xem Tài nguyên):

Hình 2. Hình ảnh bản đồ của GMaps4JSF

Mã được cập nhật (và được cắt ngắn bớt) cho thành phần map được hiển thị trong
Liệt kê 2:

Liệt kê 2. Thay thế hình ảnh bản đồ bằng một thành phần GMaps4JSF

<h:selectOneMenu onchange="submit()"
value="#{cc.parent.attrs.location.zoomIndex}"

valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"
style="font-size:13px;font-family:Palatino">

<f:selectItems value="#{places.zoomLevelItems}"/>

</h:selectOneMenu>



<m:map id="map" width="420px" height="400px"
address="#{cc.parent.attrs.location.streetAddress}, "
zoom="#{cc.parent.attrs.location.zoomIndex}"
renderOnWindowLoad="false">

<m:mapControl id="smallMapCtrl"
name="GLargeMapControl"
position="G_ANCHOR_TOP_RIGHT"/>

<m:mapControl id="smallMapTypeCtrl" name="GMapTypeControl"/>
<m:marker id="placeMapMarker"/>

</m:map>

Để sử dụng một thành phần GMaps4JSF, tôi đã thay thế thẻ <h:graphicImage>
bằng một thẻ <m:map> từ bộ thành phần GMaps4JSF. Cũng thật đơn giản để móc
nối thành phần GMaps4JSF vào trình đơn thả xuống các mức phóng to, chỉ cần chỉ
rõ thuộc tính bean hậu thuẫn chính xác cho thuộc tính zoom (phóng to) của thẻ
<m:map>.
Khi nói về các mức phóng to, chú ý rằng khi một người dùng thay đổi mức phóng
to, tôi bắt buộc gửi đi một biểu mẫu có thuộc tính onchange của thẻ

<h:selectOneMenu>, như được hiển thị trong dòng đầu tiên được in đậm một phần
trong Liệt kê 1. Việc gửi biểu mẫu đó kích hoạt vòng đời JSF, mà cuối cùng đẩy
giá trị mới cho mức phóng to vào thuộc tính zoomIndex của một bean location (vị
trí) được lưu trong thành phần phức hợp cha mẹ. Thuộc tính bean đó được liên kết
với thành phần đầu vào, trong dòng đầu tiên của Liệt kê 2.
Vì tôi đã không xác định bất kỳ sự chuyển hướng nào để gửi đi biểu mẫu kết hợp
với việc thay đổi mức phóng to, nên JSF làm mới chính trang này sau khi xử lý
các yêu cầu, vẽ lại hình ảnh bản đồ để phản ánh mức phóng to mới. Tuy nhiên,
việc làm mới trang đó cũng vẽ lại toàn bộ trang mặc dù sự thay đổi duy nhất chỉ ở
trong hình ảnh bản đồ. Trong Lời khuyên 2: Hãy Ajax hóa, tôi sẽ chỉ cho bạn cách
sử dụng Ajax để chỉ vẽ lại hình ảnh để đáp ứng một sự thay đổi mức phóng to.
Thành phần login
Một thành phần khác được sử dụng trong ứng dụng các địa điểm là thành phần
login (đăng nhập). Hình 3 cho thấy thành phần login đang hoạt động:

Hình 3. Thành phần login

Liệt kê 3 cho thấy tài liệu đánh dấu siêu văn bản tạo ra thành phần login hiển thị
trong Hình 3:

Liệt kê 3. login (đăng nhập) tối thiểu: Chỉ các thuộc tính cần thiết

<ui:composition xmlns="
xmlns:ui="
xmlns:util="

<util:login loginAction="#{user.login}"
managedBean="#{user}"/>

</ui:composition>


Thành phần login chỉ có hai thuộc tính cần thiết phải có:
 loginAction: Một phương thức hành động đăng nhập.
 managedBean: Một bean được quản lý có các thuộc tính tên và mật khẩu.
Bean được quản lý đã xác định trong Liệt kê 3 được hiển thị trong Liệt kê 4:

Liệt kê 4. User.groovy

package com.clarity

import javax.faces.context.FacesContext
import javax.faces.bean.ManagedBean
import javax.faces.bean.SessionScoped

@ManagedBean()
@SessionScoped

public class User {
private final String VALID_NAME = "Hiro"
private final String VALID_PASSWORD = "jsf"

private String name, password;

public String getName() { name }
public void setName(String newValue) { name = newValue }

public String getPassword() { return password }
public void setPassword(String newValue) { password = newValue }

public String login() {

"/views/places"
}

public String logout() {
name = password = nameError = null
"/views/login"
}
}

Bean được quản lý trong Liệt kê 4 là một bean Groovy. Việc sử dụng Groovy thay
cho ngôn ngữ Java không mang lại gì nhiều cho tôi trong trường hợp này, ngoài
việc giải phóng tôi khỏi công việc cực nhọc và buồn tẻ của các dấu chấm phẩy và
các câu lệnh return. Tuy nhiên, trong phần Xác nhận hợp lệ (Validation) của Lời
khuyên 2, tôi sẽ cho bạn thấy một lý do thuyết phục hơn để sử dụng Groovy đối
với bean được quản lý User.
Hầu hết trường hợp, bạn sẽ muốn cấu hình đầy đủ các thành phần đăng nhập với
các lời nhắc và văn bản kèm theo nút, như trong Hình 4:

Hình 4. Một thành phần login được cấu hình đầy đủ

Liệt kê 5 cho thấy tài liệu đánh dấu siêu văn bản tạo ra thành phần login trong
Hình 4:

Liệt kê 5. Cấu hình thành phần login

<ui:composition xmlns="
xmlns:f="
xmlns:h="
xmlns:ui="
xmlns:util="


<util:login loginPrompt="#{msgs.loginPrompt}"
namePrompt="#{msgs.namePrompt}"
passwordPrompt="#{msgs.passwordPrompt}"
loginButtonText="#{msgs.loginButtonText}"
loginAction="#{user.login}"
managedBean="#{user}"/>

</ui:composition>

Trong Liệt kê 5, tôi nhận được các chuỗi ký tự với lời nhắc và văn bản kèm theo
nút đăng nhập từ một gói tài nguyên.
Liệt kê 6 định nghĩa thành phần login:

Liệt kê 6. Định nghĩa thành phần login

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"

<! Usage:

<util:login loginPrompt="#{msgs.loginPrompt}"
namePrompt="#{msgs.namePrompt}"
passwordPrompt="#{msgs.passwordPrompt}"
loginButtonText="#{msgs.loginButtonText}"
loginAction="#{user.login}"
managedBean="#{user}">

<f:actionListener for="loginButton"
type="com.clarity.LoginActionListener"/>


</util:login>

managedBean must have two properties: name and password.

The loginAction attribute must be an action method that takes no
arguments and returns a string. That string is used to navigate
to the page the user sees after logging in.

This component's loginButton is accessible so that you can
add action listeners to it, as depicted above. The class specified
in f:actionListener's type attribute must implement the
javax.faces.event.ActionListener interface.

>

<html xmlns="
xmlns:f="
xmlns:h="
xmlns:composite="

<! INTERFACE >
<composite:interface>

<! PROMPTS >
<composite:attribute name="loginPrompt"/>
<composite:attribute name="namePrompt"/>
<composite:attribute name="passwordPrompt"/>

<! LOGIN BUTTON >

<composite:attribute name="loginButtonText"/>

<! loginAction is called when the form is submitted >
<composite:attribute name="loginAction"
method-signature="java.lang.String login()"
required="true"/>

<! You can add listeners to this actionSource: >
<composite:actionSource name="loginButton" targets="form:loginButton"/>

<! BACKING BEAN >
<composite:attribute name="managedBean" required="true"/>
</composite:interface>

<! IMPLEMENTATION >
<composite:implementation>
<div class="prompt">
#{cc.attrs.loginPrompt}
</div>

<! FORM >
<h:form id="form">
<h:panelGrid columns="2">

<! NAME AND PASSWORD FIELDS >
#{cc.attrs.namePrompt}
<h:inputText id="name"
value="#{cc.attrs.managedBean.name}"/>

#{cc.attrs.passwordPrompt}

<h:inputSecret id="password" size="8"
value="#{cc.attrs.managedBean.password}"/>

</h:panelGrid>

<p>
<! LOGIN BUTTON >
<h:commandButton id="loginButton"
value="#{cc.attrs.loginButtonText}"
action="#{cc.attrs.loginAction}"/>
</p>
</h:form>
</composite:implementation>
</html>

Cũng giống như thành phần map (bản đồ), thành phần login có thể sử dụng nâng
cấp bằng Ajax. Trong mục Xác nhận hợp lệ của lời khuyên tiếp theo, tôi sẽ cho
bạn thấy làm thế nào để thêm việc xác nhận hợp lệ bằng Ajax cho thành phần đăng
nhập login.


Lời khuyên 2: Hãy Ajax hóa
Ajax điển hình thường đòi hỏi hai bước mà thông thường các yêu cầu HTTP
không Ajax đã không làm như thế: xử lý từng phần các biểu mẫu trên máy chủ và
biểu hiện từng phần kết quả của Mô hình đối tượng tài liệu (Document Object
Model-DOM) trên máy khách.
Xử lý và biểu hiện từng phần
JSF 2 hỗ trợ xử lý và biểu hiện từng phần bằng cách phân tách vòng đời của JSF
thành hai phần lô-gic riêng biệt: thi hành và biểu hiện. Hình 5 nêu bật phần thi
hành:


Hình 5. Phần thi hành của vòng đời JSF

Hình 6 nêu bật phần biểu hiện của vòng đời của JSF:

Hình 6. Phần biểu hiện của vòng đời JSF

Ý tưởng đằng sau các phần thi hành và biểu hiện trong vòng đời là đơn giản: bạn
có thể quy định các thành phần mà JSF thi hành (xử lý) chúng trên máy chủ và các
thành phần mà JSF biểu hiện khi một cuộc gọi Ajax trả về. Bạn làm điều đó với
thẻ <f:ajax>, là thẻ mới cho JSF 2, như thể hiện trong Liệt kê 7:

Liệt kê 7. Trình đơn phóng to của Ajax
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<h:selectOneMenu id="menu"
value="#{cc.parent.attrs.location.zoomIndex}"
style="font-size:13px;font-family:Palatino">

<f:ajax event="change" execute="@this" render="map"/>
<f:selectItems value="#{places.zoomLevelItems}"/>

</h:selectOneMenu>

<m:map id="map" >

Liệt kê 7 là sửa đổi của trình đơn được chỉ ra trong dòng đầu tiên của Liệt kê 2:
Tôi đã gỡ bỏ thuộc tính onchange khỏi Liệt kê 2 và đã thêm một thẻ <f:ajax>. Thẻ
<f:ajax> đó xác định:
 Sự kiện kích hoạt cuộc gọi Ajax.
 Một thành phần để thi hành trên máy chủ.

 Một thành phần để biểu hiện trên máy khách.
Khi người sử dụng chọn một mục từ trình đơn phóng to, JSF bắt đầu một cuộc gọi
Ajax đến máy chủ. Sau đó, JSF chuyển trình đơn đến phần thi hành của vòng đời
(@this có nghĩa là thành phần bao quanh của <f:ajax>) và cập nhật zoomIndex của
trình đơn trong giai đoạn Cập nhật các giá trị mô hình của vòng đời. Khi cuộc gọi
Ajax trả về, JSF biểu hiện thành phần map (bản đồ), sử dụng chỉ số phóng to (vừa
mới được thiết lập) để vẽ lại bản đồ và bây giờ bạn có một trình đơn phóng to
được xử lý bằng Ajax, chỉ thêm một dòng XHTML.
Nhưng còn có thể làm mọi việc đơn giản hơn nữa, bởi vì JSF cung cấp các giá trị
mặc định cho các thuộc tính event (sự kiện) và execute (thi hành).
Mỗi thành phần JSF có một sự kiện mặc định để kích hoạt các cuộc gọi Ajax nếu
bạn nhúng một thẻ <f:ajax> bên trong thẻ thành phần. Đối với các trình đơn, sự
kiện đó là sự kiện change (thay đổi). Điều đó có nghĩa là tôi có thể bỏ thuộc tính
event của <f:ajax> trong Liệt kê 7. Giá trị mặc định cho thuộc tính execute của
<f:ajax> là @this, nghĩa là thành phần bao quanh của thẻ <f:ajax>. Trong ví dụ
này, thành phần đó là trình đơn, vì vậy tôi cũng có thể bỏ thuộc tính execute.
Bằng cách sử dụng các giá trị thuộc tính mặc định cho <f:ajax>, tôi có thể rút gọn
Liệt kê 7 thành Liệt kê 8:

Liệt kê 8. Phiên bản đơn giản hơn của một trình đơn Ajax phóng to
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<h:selectOneMenu id="menu"
value="#{cc.parent.attrs.location.zoomIndex}"
style="font-size:13px;font-family:Palatino">

<f:ajax render="map"/>
<f:selectItems value="#{places.zoomLevelItems}"/>

</h:selectOneMenu>


<m:map id="map" >

Điều này cho thấy việc thêm Ajax cho các thành phần của bạn với JSF 2 dễ dàng
như thế nào. Tất nhiên, ví dụ trên là khá đơn giản: Tôi đơn giản chỉ vẽ lại bản đồ
thay vì toàn bộ cả trang khi người sử dụng chọn một mức phóng to. Một số phép
toán, như là việc xác nhận hợp lệ một trường riêng biệt trong một biểu mẫu, sẽ
phức tạp hơn, do đó, tiếp theo tôi sẽ giải quyết các trường hợp sử dụng đó.
Xác nhận hợp lệ
Xác nhận hợp lệ các trường và cung cấp thông tin phản hồi ngay lập tức khi người
sử dụng nhập xong dữ liệu và ra khỏi một trường là một ý tưởng tốt. Ví dụ, trong
Hình 7, tôi đang sử dụng Ajax để xác nhận hợp lệ trường tên:

Hình 7. Xác nhận hợp lệ của Ajax

Mã đánh dấu siêu văn bản tạo ra trường tên được hiển thị trong Liệt kê 9:

Liệt kê 9. Trường name
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<h:panelGrid columns="2">
#{cc.attrs.namePrompt}
<h:panelGroup>
<h:inputText id="name" value="#{cc.attrs.managedBean.name}"
valueChangeListener="#{cc.attrs.managedBean.validateName}">

<f:ajax event="blur" render="nameError"/>

</h:inputText>

<h:outputText id="nameError"
value="#{cc.attrs.managedBean.nameError}"

style="color: red;font-style: italic;"/>
</h:panelGroup>

</h:panelGrid>

Một lần nữa, tôi sử dụng <f:ajax>, chỉ có điều là lần này sự kiện mặc định cho đầu
vào — change —sẽ không áp dụng được, vì vậy tôi xác định blur (làm mờ đi) là
sự kiện kích hoạt cuộc gọi Ajax. Khi người sử dụng ra khỏi trường tên, JSF bắt
đầu một cuộc gọi Ajax đến máy chủ và chạy thành phần đầu vào name thuộc giai
đoạn thi hành của vòng đời. Điều này có nghĩa rằng JSF sẽ gọi trình nghe thay đổi
giá trị đầu vào của name như đã chỉ rõ trong Liệt kê 9 trong giai đoạn xác nhận
hợp lệ của vòng đời. Liệt kê 10 hiển thị trình nghe thay đổi giá trị ấy:

Liệt kê 10. Phương thức validateName()

package com.clarity

import javax.faces.context.FacesContext
import javax.faces.bean.ManagedBean
import javax.faces.bean.SessionScoped
import javax.faces.event.ValueChangeEvent
import javax.faces.component.UIInput

@ManagedBean()
@SessionScoped

public class User {
private String name, password, nameError;




public void validateName(ValueChangeEvent e) {
UIInput nameInput = e.getComponent()
String name = nameInput.getValue()

if (name.contains("_")) nameError = "Name cannot contain underscores"
else if (name.equals("")) nameError = "Name cannot be blank"
else nameError = ""
}


}

Trình nghe thay đổi-giá trị — Phương thức validateName() của bean được quản lý
user (người sử dụng) — xác nhận hợp lệ trường tên và cập nhật thuộc tính
nameError của bean được quản lý user.
Sau khi cuộc gọi Ajax trả về, nhờ có thuộc tính render của thẻ <f:ajax> trong Liệt
kê 9, JSF biểu hiện kết quả đầu ra nameError. Đầu ra đó hiển thị thuộc tính
nameError của bean được quản lý user.
Xác nhận hợp lệ nhiều trường
Trong tiểu mục trên, tôi đã cho bạn thấy làm thế nào để thực hiện xác nhận hợp lệ
bằng Ajax cho một trường duy nhất. Tuy nhiên, đôi khi bạn cần phải xác nhận hợp
lệ nhiều trường cùng một lúc. Ví dụ, Hình 8 cho thấy ứng dụng các địa điểm xác
nhận hợp lệ các trường tên và mật khẩu cùng nhau:

Hình 8. Xác nhận hợp lệ nhiều trường

Tôi xác nhận hợp lệ trường tên và mật khẩu cùng nhau khi người sử dụng gửi đi
biểu mẫu, vì vậy tôi không cần Ajax cho ví dụ này. Thay vào đó, tôi sẽ sử dụng hệ
thống sự kiện mới của JSF 2, như thể hiện trong Liệt kê 11:


Liệt kê 11. Sử dụng <f:event>

<h:form id="form" prependId="false">

<f:event type="postValidate"
listener="#{cc.attrs.managedBean.validate}"/>

</h:form>

<div class="error" style="padding-top:10px;">
<h:messages layout="table"/>
</div>

Trong Liệt kê 11, tôi sử dụng <f:event>, — giống như <f:ajax> — là điểm mới
của JSF 2. Thẻ <f:event> cũng tương tự như <f:ajax> ở khía cạnh khác, đó là: sử
dụng rất đơn giản.
Bạn đặt một thẻ <f:event> bên trong một thẻ thành phần và khi sự kiện đã xác
định (bằng thuộc tính type) xảy ra đối với thành phần đó, JSF gọi một phương
thức, chỉ rõ bởi thuộc tính listener (trình nghe). Vì vậy, theo tiếng Anh, thẻ
<f:event> trong Liệt kê 11 có nghĩa là: Sau khi xác nhận hợp lệ biểu mẫu, gọi
phương thức validate() của bean được quản lý mà người sử dụng đã chuyển cho
thành phần phức hợp này. Đó là phương thức được chỉ ra trong Liệt kê 12:

Liệt kê 12. Phương thức validate()
package com.clarity

import javax.faces.context.FacesContext
import javax.faces.bean.ManagedBean
import javax.faces.bean.SessionScoped

import javax.faces.event.ValueChangeEvent
import javax.faces.component.UIInput

@ManagedBean()
@SessionScoped

public class User {
private final String VALID_NAME = "Hiro";
private final String VALID_PASSWORD = "jsf";



public void validate(ComponentSystemEvent e) {
UIForm form = e.getComponent()
UIInput nameInput = form.findComponent("name")
UIInput pwdInput = form.findComponent("password")

if ( ! (nameInput.getValue().equals(VALID_NAME) &&

×