Unit 07 - 頁面樣板(JSF Templates)

4 minute read

目標

  • 瞭解如何使用 JSF 樣板製作頁面, 使得每個頁面可以有相同的頁首, 頁尾或共同的顯示外觀。
  • 如何在樣板中使用 JS

知識: 使用 JSF 樣板所需呀的 Facelets

樣板原理

  • 樣板檔案(Template File): 做為頁面樣板的 Facelets Page. 頁面內預留數個區域, 供套用樣板時, 放置特定頁面的不同內容。
  • 樣板客戶頁面(Template Client): 套用樣板頁面製作特定的頁面。只需在樣板內的預留區域, 填入特定內容, 產生一個特定的頁面。

Source: Figure 4-1 in [1]

Facelets 標籤

ui:composition

有兩個用途: 1) 在樣板客戶頁面中套用指定的樣板; 或者 2) 用來定義組合區塊。

組合區塊(Composition Block)讓我們模組化頁面中的元素, 以便在樣板頁面或者樣板客戶頁面中使用. 如此, 我們可以以需求組合不同的組合區塊。

當要套用樣板時, 使用 template 屬性指定樣板檔案。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h:body>
        <ui:composition template="/templates/masterLayout.xhtml">
            <ui:define name="contentArea">
                <h3>Customer List</h3>
                <ul>
                    <li>Spider Man</li>
                    <li>Captain Marvel</li>
                    <li>Hulk</li>
                </ul>
            </ui:define>
            <ui:define name="navbar_script">
                <script>
                    $(".nav").find(".active").removeClass("active");
                    $(".nav #customerItem").addClass("active");
                 </script>
            </ui:define>
        </ui:composition>
</h:body>

注意, ui:composition facelet 外的全部內容會被忽略。 此例中, ui:define facelet 定義樣板預留區域中的內容。

如果用於定義組合區塊, 則不需使用 template 屬性。 例如, 底下在 sideBarLeft.xml 定義一個 composition block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <h:head>
        <title>Facelet Title</title>
</h:head>
<h:body>
    <ui:composition>
        <ul class="nav nav-stacked nav-pills">
            <li><h:link class="btn btn-primary" 
                        outcome="/customerList"> 
                    Customer List
                </h:link> </li>
            <li><h:link class="btn btn-primary" 
                        value="Show Emails" 
                        outcome="/emailList" /></li>
        </ul>
    </ui:composition>
</h:body>

我們可以在樣板頁面中引入此組合區塊:

1
2
3
4
5
6
 <div id="sideBarLeft" class="col-xs-12 col-sm-12 col-md-3 col-lg-3">
    <p>Left Side Bar</p>
    <ui:insert name="sideBarLeft">
        <ui:include src="/templates/sideBarLeft.xhtml"/>
    </ui:insert>
</div>

ui:include facelet 讓我們引入組合區塊, 製作頁面.

ui:decorate:

使用樣板來裝飾頁面上特定的元素。

例如, 有一樣版檔案 content_title.xml 定義如下:

1
2
3
4
5
6
7
8
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
                xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h3>
        <h:outputText value="#{title}"/>
    </h3>
    <ui:insert />
</ui:composition>

假若在頁面中有一個元素要被裝飾, 這個元素是一段文字, 則使用方式如下:

1
2
3
4
5
 <ui:decorate template="templates/contentTitle.xhtml">
    <ui:param name="title" value="內容標題"/>
        點選左側的按鈕, 切換不同的頁面. 這些頁面有相同的: 上端導覽列, 左側導覽列.
        只有在內容區域顯示不同的內容. (這些內容會被放到沒有名稱的 <code>ui:insert</code> facelet 中。
</ui:decorate>

如此, 這個元素上方會有一個 <h3> 的標籤, 內容由 ui:param facelet 傳遞參數進行設定。

參考: decorate(JSF 2.2 View Declaration Language: Facelets Variant)

ui:insert

在樣版檔案上標示預留區, 供樣版客戶使用 ui:define facelet 定義要顯示的特定內容。每個預留區上使用 name 屬性指定預留區的名稱。

使用 ui:define 定義內容時利用其 name 屬性指定要放置的預留區域。

例如, 底下的 ui:define 內的內容會放到樣板檔案的 ui:insert name='contentArea' 的預留區域中。

1
2
3
4
5
6
7
8
9
<ui:composition template="/templates/masterLayout.xhtml">
    <ui:define name="contentArea">
        <ui:decorate template="templates/contentTitle.xhtml">
            <ui:param name="title" value="內容標題"/>
            點選左側的按鈕, 切換不同的頁面. 這些頁面有相同的: 上端導覽列, 左側導覽列.
            只有在內容區域顯示不同的內容. (這些內容會被放到沒有名稱的 <code>ui:insert</code> facelet 中。
        </ui:decorate>
    </ui:define>
</ui:composition>

ui:insert 可以用在以下的 facelet 內:

  • ui:composition
  • ui:component
  • ui:decorate

參考: insert (JSF 2.2 View Declaration Language: Facelets Variant)

ui:define

用來定義樣板內預留區的內容。

The ui:define tag can be used inside <ui:composition template="template_name.xml"> and <ui:decorate template="template_name.xml">

參考: define (JSF 2.2 View Declaration Language: Facelets Variant)

ui:include and ui:param

使用 ui:include 在標籤出現的地方, 引入外部的 Facelet Page 檔案 (xhtml)。檔案內使用 ui:composition 定義組合區塊。

組合區塊內的內容可以進行參數化, 在 ui:include 標籤內使用 ui:param 傳遞參數。在組合區塊中使用 EL 語法取得特定參數名稱的值。

Embed ui:param tags in either ui:include, ui:composition, or ui:decorate to pass the parameters.

參考:

User Story

  1. 使用者點選上方導覽列的選單項目, 切換不同的頁面. 頁面的內容顯示在右下方的區域。
  2. 使用者也可以點選左側的導覽列上的按鈕, 切換不同的頁面.
  3. 系統高亮度顯示選單項目, 指示目前所在的頁面名稱.

實作程序

Step 建立側邊欄的組合區塊。 在專案中建立目錄 /templates, 並建立 sideBarLeft.xhtml facelets page. 使用 ui:composition facelet 建立如下的內容:

<?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://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:composition>
            <ul class="nav nav-stacked nav-pills">
                <li><h:link class="btn btn-primary" 
                            outcome="/customerList"> 
                        Customer List
                    </h:link> </li>
                <li><h:link class="btn btn-primary" 
                            value="Show Emails" 
                            outcome="/emailList" /></li>
            </ul>
        </ui:composition>
    </h:body>
</html>
  
  

這個 sidebar 內有兩個連結, 皆套用 bootstrap 的 btn CSS 樣式。我們會在樣板頁面中使用此組合區塊, bootstrap 的樣式也會在樣板頁面中引入。

Step 建立內容標題的組合區塊。 建立 /templates/contentTitle.xhtml facelet page, 用來顯示內容區域的標題, 檔案內容如下:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
                xmlns:h="http://xmlns.jcp.org/jsf/html">
  
    <h3>
        <h:outputText value="#{title}"/>
    </h3>
    <ui:insert />
</ui:composition>

此 facelet page, 只有使用 ui:composition facelet, 將 namespace 的宣告放在起始標籤內。這是合法的使用, 因為 JSF 會捨棄 ui:composition 標籤以外的內容。

此組合區塊有一個參數 title, 使用此組合區塊時, 使用 ui:param facelet 傳遞參數。

Step 建立樣板檔案。 建立 /templates/masterLayout.xhtml 做為主要的樣板檔案。在樣板檔案中會包括以下類型的內容:

  • 樣板內容: 會在每個樣板使用頁面出現的內容. 樣板內容可以是一般的 HTML 元素、Facelet 標籤、或者組合區塊。
  • 預留區塊: 使用 ui:insert facelet 標示的預留區塊。預留區塊內也可以先放置一般內容或組合區塊。樣板使用頁面可以再定義預留區塊內的內容, 覆蓋原有的內容。

masterLayout.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://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>Facelet Title</title>
        <link rel="stylesheet" 
              href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" 
              integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" 
              crossorigin="anonymous"></link>
        <style>
            body { padding-top: 70px; }
        </style>
    </h:head>
    <h:body>
        <nav class="navbar navbar-default navbar-fixed-top">
            <div class="container-fluid" >
                <div class="navbar-header">
                    <h:link styleClass="navbar-brand" outcome="/index">Unit07 ${siteName} </h:link>
                </div>
                <div>
                    <ul class="nav navbar-nav">
                        <li id="customerItem" class="active"><h:link outcome="/customerList">Customer List</h:link> </li>
                        <li id="emailItem"><h:link outcome="/emailList" >Email List</h:link> </li>
                    </ul>
                </div>
            </div>
        </nav>
        <div id="heading" class="container">
            <div class="jumbotron">
                <h1>JSF Templates</h1>
                <p>
                    使用 JSF 樣板製作頁面, 使得每個頁面可以有相同的頁首, 頁尾或共同的顯示外觀. 
                </p>
            </div>
        </div>
  
        <div class="container">
  
            <div class="row">
                <div id="sideBarLeft" class="col-xs-12 col-sm-12 col-md-3 col-lg-3">
                    <p>Left Side Bar</p>
                    <ui:insert name="sideBarLeft">
                        <ui:include src="/templates/sideBarLeft.xhtml"/>
                    </ui:insert>
                </div>
  
                <div id="content" class="col-xs-12 col-sm-12 col-md-9 col-lg-9">
                    <ui:insert name="contentArea">
                        <p>Content Area</p>
                    </ui:insert>
  
                </div>
            </div>
            <div class="row">
                <ui:remove>
                    <ui:decorate template="footer.xhtml" />
                </ui:remove>
  
            </div>
        </div>
        <!--javascripts-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
        <ui:insert name="navbar_script" />
  
    </h:body>
</html>
  
  

樣板檔案中, 使用 <ui:insert name="insert_area_name"> 定義預留區塊。樣板檔案中也引入了所需的 CSS 及 JS。

Step 製作 index.xhtml 頁面。

此頁面使用<ui:composition template="/templates/masterLayout.xhtml"> 標籤 套用 /templates/masterLayout.xhtml 樣板檔案建立頁面。

在樣板客戶頁面中, <ui:define name="contentArea"> 定義樣板內預留區塊 <ui:insert name="contentArea"> 內的內容。另外, 使用 <ui:decorate template=/templates/contentTitle.xhtml> 裝飾內容區域內的文字, 讓區域文字上方顯示標題。標題的內容透過 <ui:param name="title" value="內容標題"> 設定組合區塊中的參數。

<ui:define name="navbar_script"> 定義樣板檔案內的 ` ` 內容, 放入 JS code, 取消上方導覽列選單項目的高亮度顯示, 表示目前尚未切換到其他頁面。

<?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://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:composition template="/templates/masterLayout.xhtml">
            <ui:define name="contentArea">
                <ui:decorate template="templates/contentTitle.xhtml">
                    <ui:param name="title" value="內容標題"/>
                    點選左側的按鈕, 切換不同的頁面. 這些頁面有相同的: 上端導覽列, 左側導覽列.
                    只有在內容區域顯示不同的內容. (這些內容會被放到沒有名稱的 <code>ui:insert</code> facelet 中。
                </ui:decorate>
  
  
            </ui:define>
            <ui:define name="navbar_script">
                <script>
                    $(".nav").find(".active").removeClass("active");
                </script>
            </ui:define>
        </ui:composition>
    </h:body>
</html>
  
  

Step 製作 customerList.xhtml 頁面。

此頁面同樣<ui:composition template="/templates/masterLayout.xhtml"> 標籤 套用樣板檔案建立頁面。 <ui:define name="contentArea"> 定義樣板內預留區塊 <ui:insert name="contentArea"> 的內容, 顯示顧客清單。<ui:define name="navbar_script"> 內放入 JS code, 更新上方導覽列的高亮度顯示選單項目, 表示目前在 customerList.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://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:composition template="/templates/masterLayout.xhtml">
            <ui:define name="contentArea">
                <h3>Customer List</h3>
                <ul>
                    <li>Spider Man</li>
                    <li>Captain Marvel</li>
                    <li>Hulk</li>
                </ul>
            </ui:define>
            <ui:define name="navbar_script">
                <script>
                    $(".nav").find(".active").removeClass("active");
                    $(".nav #customerItem").addClass("active");
                 </script>
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Step 製作 emailList.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://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:composition template="/templates/masterLayout.xhtml">
            <ui:define name="contentArea">
                <h3>Email List</h3>
                <ul>
                    <li>spider-man@gmail.com</li>
                    <li>caption-marvel@gmail.com</li>
                    <li>hulk@gmail.com</li>
                </ul>
            </ui:define>
            <ui:define name="navbar_script">
                <script>
                    $(".nav").find(".active").removeClass("active");
                    $(".nav #emailItem").addClass("active");
                </script>
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Step 測試你的程式。

複習問題

  1. JSF 頁面樣板的工作原理為何?
  2. ui:composition facelat 有那兩個用途?
  3. 組合區塊(composition) comblock中 留有一參數 title, 要如何設定此參數呢?
  4. ui:compositionui:decorate 兩個 facelet 有什麼差異呢?

技術挑戰

參考資料

[1] Burns, E. and Schalk, C., JavaServer Faces 2.0: the complete reference. McGraw Hill, 2010.

[2] Overview (JSF 2.2 View Declaration Language: Facelets Variant) , accessed on 2018/10/29

Updated: