2012年9月26日 星期三

第三個範例:ZK 與 Session

第三個範例:ZK 與 Session

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

ZK 跟所有 Web-based 的資訊系統一樣,網頁跟網頁之間都是互為獨立的 (稱之為 stateless),可是在實務上,往往網頁與網頁之間是有順序關係的; 例如如果使用者沒有登入,那麼使用者就不能修改使用者的註冊資料。 為了能夠達到這個目的,一般 Web-based 的資訊系統都利用 Session 的作法, 讓網頁與網頁之間可以藉由 Session 物件的一些共享資料來達成。在 ZK 中, 它也提供了類似的 package,這個 package 的名稱為 org.zkoss.zk.ui.Session; 除了這個範例之外,讀者可以參考該 package 的其他類別以及用法。 在這個範例中,我們假設設計一個系統,該系統有多個網頁,而要使用該系統, 使用者必須先執行 login.zul 網頁;一旦登入成功之後,使用者就能使用 其他網頁;為了簡化起見,我們只設計了一個 zul 網頁 todo1.zul(該網頁 是 todo.zul 的簡化版),其他網頁可以比照辦理。如果使用者未登入便想 直接存取 todo1.zul,則使用者會被導向 login.zul。
首先,讓我們設計 login.zul 的畫面,其畫面如下:

在畫面,請留意左上角的紅色框框,這是設定網頁標題(也就是類似 HTML 的 <title> 標籤的用法)的名稱。這個畫面的定義如下:
01  <?xml version="1.0" encoding="Big5"?>
02  <?page title="待辦事項系統"?>
03  <window title="登入畫面" border="normal" width="220px" mode="modal"
04          xmlns:h="http://www.w3.org/1999/xhtml">
05  <h:table>
06  <h:tr>
07  <h:td>帳號:</h:td><h:td><textbox id="user"/></h:td>
08  </h:tr><h:tr>
09  <h:td>密碼:</h:td><h:td><textbox type="password" id="pwd"/></h:td>
10  </h:tr><h:tr>
11  <h:td colspan="2" align="center"><button label="登入"/></h:td>
12  </h:tr>
13  </h:table>
14  </window>
網頁標題是由第 02 行的 page 標籤(其實是一個 PI)所設定的。另外,為了 能夠將帳號、密碼、以及登入按鈕依照表格的方式(也就是 HTML 的 <table>) 來完成,我們必須宣告一個名稱空間來分開 ZK 的標籤(也就是預設名稱空間) 以及 XHTML 的標籤。(請注意: 本範例故意使用 ZK 與 XHTML 在同一個 zul 網頁的方式來說明;否則,同樣的排版也可以利用 ZK 的 <grid> 來 完成)。 從原始碼的綠色部分,讀者應該可以看得出來,我們定義了一個簡稱為 h 的名稱 空間,而該空間為 http://www.w3.org/1999/xhtml(即 XHTML)。然後,所有的 XHTML 標籤名稱之前都需要加上 h:;以本範例來說,需要加上 h: 包含 h:table、h:tr、h:td 等。
在使用者輸入帳號和密碼之後,使用者會點一下"登入"按鈕;一旦"登入"按鈕 被點選,login() 方法會被執行。該事件的註冊方式如下:
<button label="登入" onClick="login()"/>
login() 方法的工作包含:從輸入欄位取得資料,並將其資料與系統 內使用者的身分做驗證;如果驗證成功,程式會在 Session 物件設定包含 必要的資料(例如帳號、密碼等),然後執行下一個網頁 todo1.zul;否則, 程式會將使用者再度導向 login.zul。登入的程式碼如下:
01  import org.zkoss.zk.ui.*;
02  
03  void login() { 
04    // 也可以連資料庫驗證
05    if(user.value.equals("123") &amp;&amp; pwd.value.equals("123")) {
06      Session s = Sessions.getCurrent();
07      s.setAttribute("user", user.value);
08      s.setAttribute("pwd", pwd.value);
09      Executions.sendRedirect("todo1.zul");
10    } else {
11      Executions.sendRedirect("login.zul");
12    }
13  }
由於我們需要 ZK 的 Session 物件,因此需要第 01 行。在驗證使用者的 身分資料部分,雖然可以使用資料庫,但是為了簡化起見,我們利用第 05 行 的方式來檢查;如果驗證失敗,程式會將使用者導向 login.zul 網頁,如 第 11 行所示,其中 Executions.sendRedirect("URL"); 會將 網頁導向 URL;反之,如果驗證成功,程式首先在第 06 行取得目前的 Session 物件,並且在第 07 和 08 行利用該物件的 setAttribute() 方法 來設定兩個 Session 共享的資訊,其分別是帳號(屬性名稱為 user;屬性值 由 user 欄位取得)和密碼(屬性名稱為 pwd; 屬性值由 pwd 欄位取得)。 最後,將網頁導向 todo1.zul。 在結束說明 login() 之前,還有一個很重要的用法要說明一下。在第 05 行 程式必須判斷帳號以及密碼都等於 123,因此 if 敘述內必須是"且"的運算子, 在 Java 中它應該是 &&,但是因為該程式碼在 zul(或者更明確的 說,XML)網頁內,所以該運算子不能寫成 && 而必須寫成 &amp;&amp;,這樣一來,ZK 處理器在剖析 login.zul 的時候,會將它轉成 && 然後再進行編譯。
todo1.zul 是第二個範例的簡易版,其畫面的定義部分如下:
<?xml version="1.0" encoding="Big5"?>
<window title="待辦事項清單" width="640px" border="normal" closable="true"
        onClose="self.visible=false;logout();">
  <listbox id="box" multiple="true" rows="4">
    <listhead>
      <listheader label="待辦事項" />
      <listheader label="重要性" width="50px" />
      <listheader label="日期" width="90px" />
    </listhead>
  </listbox> 
</window>
有了這樣的定義,其畫面如下:
絕大部分的原始碼之前已經說明了,就不再多說。其中比較不同是視窗的右上角 出現了一個像一般視窗的 "X" 按鈕。我們希望在使用者點選 "X" 之後,該視窗 會不見,而且就像登出一樣,它會將使用者導向 login.zul;因此,我們幫 <window> 的 closable 屬性設定為 true(使得出現 "X");若使用者 在 "X" 點一下,則會呼叫 onClose 事件的處理程式,這包含 self.visible=false; (這會讓視窗隱藏起來),並呼叫 logout() 方法。
另外,除了 login.zul,所有同一個系統中的其他網頁應該在載入的時候,就應該檢查 Session 中的共用資料(在本範例中即屬性名稱為 user 以及 pwd 是否存在, 必要的話,還可以檢查屬性的值是否存在)。為了做這兩件事,程式碼如下:
01  import org.zkoss.zk.ui.*;
02  
03  Session s = Sessions.getCurrent();
04  if(s.getAttribute("user") == null)
05    Executions.sendRedirect("login.zul");
06  
07  void logout() {
08    s.invalidate();
09    Executions.sendRedirect("login.zul");
10  }
從程式碼中可以看出:第 03 行取得目前的 Session 物件,然後在第 04 行 檢查是否定義了 user 屬性(也可以在加上 pwd 屬性的檢查);如果未定義, 導向 login.zul;反之,則繼續執行本網頁。 logout() 除了會將使用者導向 login.zul 之外,並且會再重新導向之前 先把 Session 物件解除掉(利用 invalidate() 方法)。除了利用 "X" 之外, 當然也可以使用其他的方式登出;但是不論使用哪一種方式,記得一定要 把 Session 物件 invalidate 掉,該物件才不會被不當的使用。
我們鼓勵讀者試著從各種方式來使用 Session。例如:開啟一個新的網頁之後, 馬上試著連 todo1.zul;或者 logout 之外,直接更改 URL 試圖連接 todo1.zul。 有了良好的 Session 控制,這些方式都不應該可以連上 todo1.zul;除了 正確的登入。
在測試的過程中,你或許會發現雖然在無法順利使用 todo1.zul 的情形下, 使用者還是有一個短暫的時間可以看到 todo1.zul 的畫面,如果該畫面有 一些機密內容,那麼 todo1.zul 的設計還是不妥。為了解決這個問題,我們 可以在一開始讓 <window> 是看不到的,只有使用者正確的登入後 才讓 <window> 能夠顯示出來。由於程式碼還蠻簡單的,我們就不再說明, 而把整個程式碼的內容列示出來:
<?xml version="1.0" encoding="Big5"?>
<window id="win" title="待辦事項清單" width="640px" border="normal" closable="true" visible="false"
        onClose="self.visible=false;logout();">
<zscript>
import org.zkoss.zk.ui.*;

Session s = Sessions.getCurrent();
if(s.getAttribute("user") == null)
  Executions.sendRedirect("login.zul");
else
  win.setVisible(true);

void logout() {
  s.invalidate();
  Executions.sendRedirect("login.zul");
}
</zscript>
  <listbox id="box" multiple="true" rows="4">
    <listhead>
      <listheader label="待辦事項" />
      <listheader label="重要性" width="50px" />
      <listheader label="日期" width="90px" />
    </listhead>
  </listbox> 
</window>
練習題: 請修改 login.zul 使得使用者在輸入完密碼之後,能夠直接按 "Enter" 來執行登入,而不必一定要用滑鼠來點選"登入"按鈕來登入。
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu















沒有留言:

張貼留言