JSTL ใน JSF2 Facelets …เข้าท่าไหม?


163

ฉันต้องการส่งออกโค้ด Facelets เล็กน้อยตามเงื่อนไข

เพื่อจุดประสงค์นั้นแท็ก JSTL ดูเหมือนจะทำงานได้ดี:

<c:if test="${lpc.verbose}">
    ...
</c:if>

อย่างไรก็ตามฉันไม่แน่ใจว่านี่เป็นการปฏิบัติที่ดีที่สุดหรือไม่? มีวิธีอื่นในการบรรลุเป้าหมายของฉันหรือไม่

คำตอบ:


320

บทนำ

JSTL <c:xxx>แท็กทั้งหมดtaghandlersและพวกเขาจะดำเนินการในช่วงมุมมองสร้างเวลาในขณะ JSF <h:xxx>แท็กทั้งหมดUI ส่วนประกอบและพวกเขาจะดำเนินการในช่วงมุมมองการแสดงผลเวลามุมมองการแสดงผลเวลา

โปรดทราบว่าจาก JSF ของตัวเอง<f:xxx>และ<ui:xxx>แท็กเฉพาะผู้ที่ไม่ได้ขยายจากUIComponentนี้ยังมี taghandlers เช่น<f:validator>, <ui:include>, <ui:define>ฯลฯ คนซึ่งเพิ่มขึ้นจากUIComponentนี้ยังมีส่วนประกอบ JSF UI เช่น<f:param>, <ui:fragment>, <ui:repeat>ฯลฯ จากส่วนประกอบ JSF UI เท่านั้นidและbindingแอตทริบิวต์ ยังประเมินระหว่างเวลาสร้างมุมมอง ดังนั้นคำตอบด้านล่างเกี่ยวกับวงจรชีวิตของ JSTL ก็ใช้กับidและbindingคุณสมบัติของส่วนประกอบ JSF

มุมมองเวลาในการสร้างเป็นช่วงเวลาที่ว่าเมื่อไฟล์ XHTML / JSP การที่จะถูกแยกและแปลงเป็นต้นไม้องค์ประกอบ JSF ที่ถูกเก็บไว้แล้วเป็นของUIViewRoot FacesContextเวลาในการเรนเดอร์มุมมองนั้นเป็นช่วงเวลาที่โครงสร้างของ JSF กำลังจะสร้าง HTML ขึ้นต้นด้วยUIViewRoot#encodeAll()เริ่มต้นด้วย ดังนั้น: องค์ประกอบ JSF UI และแท็ก JSTL จะไม่ทำงานในการซิงค์อย่างที่คุณคาดหวังจากการเข้ารหัส คุณสามารถเห็นภาพได้ดังนี้: JSTL รันจากบนลงล่างก่อนสร้างแผนผังองค์ประกอบ JSF จากนั้นก็ถึงคราวที่จะเรียกใช้ JSF จากบนลงล่างอีกครั้งสร้างเอาต์พุต HTML

<c:forEach> VS <ui:repeat>

ตัวอย่างเช่นมาร์กอัป Facelets นี้ทำการวนซ้ำมากกว่า 3 รายการโดยใช้<c:forEach>:

<c:forEach items="#{bean.items}" var="item">
    <h:outputText id="item_#{item.id}" value="#{item.value}" />
</c:forEach>

... สร้างในช่วงเวลาสร้างมุมมองสาม<h:outputText>องค์ประกอบที่แยกจากกันในโครงสร้างองค์ประกอบ JSF ซึ่งแสดงโดยประมาณดังนี้:

<h:outputText id="item_1" value="#{bean.items[0].value}" />
<h:outputText id="item_2" value="#{bean.items[1].value}" />
<h:outputText id="item_3" value="#{bean.items[2].value}" />

... ซึ่งในทางกลับกันสร้างเอาต์พุต HTML ของพวกเขาระหว่างการแสดงผลเวลาดู:

<span id="item_1">value1</span>
<span id="item_2">value2</span>
<span id="item_3">value3</span>

โปรดทราบว่าคุณจำเป็นต้องตรวจสอบความไม่ซ้ำของ ID ส่วนประกอบด้วยตนเองและตรวจสอบว่ามีการประเมินในระหว่างการสร้างมุมมอง

ในขณะนี้ Facelets ทำเครื่องหมายซ้ำมากกว่า 3 รายการโดยใช้<ui:repeat>ซึ่งเป็นองค์ประกอบของ JSF UI:

<ui:repeat id="items" value="#{bean.items}" var="item">
    <h:outputText id="item" value="#{item.value}" />
</ui:repeat>

... สิ้นสุดลงตามที่เป็นอยู่ในโครงสร้างองค์ประกอบ JSF โดยที่<h:outputText>ส่วนประกอบเดียวกันนั้นอยู่ในช่วงเวลาที่แสดงภาพถูกนำมาใช้ซ้ำเพื่อสร้างเอาต์พุต HTML ตามรอบการวนรอบปัจจุบัน:

<span id="items:0:item">value1</span>
<span id="items:1:item">value2</span>
<span id="items:2:item">value3</span>

โปรดทราบว่าใน<ui:repeat>ฐานะที่เป็นNamingContainerองค์ประกอบทำให้มั่นใจได้แล้วว่าเอกลักษณ์ของรหัสลูกค้าตามดัชนีการวนซ้ำ มันเป็นไปไม่ได้ที่จะใช้ EL ในแอidททริบิวต์ขององค์ประกอบลูกด้วยวิธีนี้เพราะมันจะถูกประเมินในช่วงเวลาที่สร้างภาพในขณะที่#{item}ใช้ได้เฉพาะในช่วงเวลาที่แสดงภาพเท่านั้น Same เป็นจริงสำหรับh:dataTableส่วนประกอบที่คล้ายคลึงกัน

<c:if>/ <c:choose>vsrendered

เป็นอีกตัวอย่างหนึ่งมาร์คอัพ Facelets นี้เพิ่มแท็กที่แตกต่างกันโดยใช้<c:if>(คุณสามารถใช้<c:choose><c:when><c:otherwise>สำหรับสิ่งนี้):

<c:if test="#{field.type eq 'TEXT'}">
    <h:inputText ... />
</c:if>
<c:if test="#{field.type eq 'PASSWORD'}">
    <h:inputSecret ... />
</c:if>
<c:if test="#{field.type eq 'SELECTONE'}">
    <h:selectOneMenu ... />
</c:if>

... ในกรณีที่type = TEXTจะเพิ่มเพียง<h:inputText>องค์ประกอบในโครงสร้างองค์ประกอบ JSF:

<h:inputText ... />

ในขณะที่มาร์คอัป Facelets นี้:

<h:inputText ... rendered="#{field.type eq 'TEXT'}" />
<h:inputSecret ... rendered="#{field.type eq 'PASSWORD'}" />
<h:selectOneMenu ... rendered="#{field.type eq 'SELECTONE'}" />

... จะจบลงอย่างที่กล่าวไว้ข้างต้นในแผนผังองค์ประกอบ JSF โดยไม่คำนึงถึงเงื่อนไข นี่อาจจบลงในทรีคอมโพเนนต์ "bloated" เมื่อคุณมีหลายองค์ประกอบและจริง ๆ แล้วอิงตามโมเดล "สแตติก" (เช่นที่fieldไม่เปลี่ยนแปลงตลอดเวลาอย่างน้อยขอบเขตการดู) นอกจากนี้คุณอาจพบปัญหา EL เมื่อคุณจัดการกับคลาสย่อยที่มีคุณสมบัติเพิ่มเติมในเวอร์ชัน Mojarra ก่อน 2.2.7

<c:set> VS <ui:param>

พวกเขาไม่สามารถใช้แทนกันได้ การ<c:set>ตั้งค่าตัวแปรในขอบเขต EL ซึ่งสามารถเข้าถึงได้หลังจากตำแหน่งแท็กระหว่างเวลาสร้างมุมมอง แต่ที่ใดก็ได้ในมุมมองระหว่างเวลาแสดงผลมุมมอง <ui:param>ผ่านตัวแปร EL กับแม่แบบ Facelet รวมผ่านทาง<ui:include>, หรือ<ui:decorate template> <ui:composition template>เวอร์ชัน JSF ที่เก่ากว่านั้นมีข้อบกพร่องโดยที่<ui:param>ตัวแปรยังมีอยู่นอกเทมเพลต Facelet ที่เป็นปัญหาสิ่งนี้ไม่ควรเชื่อถือ

<c:set>โดยไม่ต้องมีscopeคุณลักษณะที่จะทำตัวเหมือนนามแฝง มันไม่ได้แคชผลลัพธ์ของนิพจน์ EL ในขอบเขตใด ๆ มันสามารถปรับใช้ได้อย่างสมบูรณ์แบบภายในตัวอย่างเช่นวนองค์ประกอบ JSF ดังนั้นเช่นด้านล่างจะทำงานได้ดี:

<ui:repeat value="#{bean.products}" var="product">
    <c:set var="price" value="#{product.price}" />
    <h:outputText value="#{price}" />
</ui:repeat>

มันไม่เหมาะสำหรับเช่นการคำนวณผลรวมในวง สำหรับสิ่งนั้นใช้สตรีม EL 3.0แทน:

<ui:repeat value="#{bean.products}" var="product">
    ...
</ui:repeat>
<p>Total price: #{bean.products.stream().map(product->product.price).sum()}</p>

เฉพาะเมื่อคุณตั้งค่าscopeแอตทริบิวต์ที่มีค่าใดค่าหนึ่งที่อนุญาตrequest, view, sessionหรือapplicationแล้วมันจะได้รับการประเมินทันทีในช่วงเวลาในการสร้างมุมมองและเก็บไว้ในขอบเขตที่กำหนด

<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />

สิ่งนี้จะถูกประเมินเพียงครั้งเดียวและใช้ได้#{dev}ตลอดทั้งแอปพลิเคชันทั้งหมด

ใช้ JSTL เพื่อควบคุมการสร้างแผนผังองค์ประกอบ JSF

ใช้ JSTL อาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดเมื่อมีการใช้ภายใน JSF ส่วนประกอบ iterating เช่น<h:dataTable>, <ui:repeat>ฯลฯ หรือเมื่อคุณลักษณะแท็ก JSTL ขึ้นอยู่กับผลของเหตุการณ์ JSF เช่นpreRenderViewหรือส่งค่ารูปแบบในรูปแบบที่ไม่สามารถใช้ได้ในช่วงเวลาในการสร้างมุมมอง . ดังนั้นให้ใช้แท็ก JSTL เพื่อควบคุมการไหลของการสร้างแผนผังองค์ประกอบ JSF เท่านั้น ใช้คอมโพเนนต์ JSF UI เพื่อควบคุมโฟลว์ของการสร้างเอาต์พุต HTML อย่าผูกvarคอมโพเนนต์การวนซ้ำ JSF กับแอ็ตทริบิวต์แท็ก JSTL อย่าพึ่งพาเหตุการณ์ JSF ในแอตทริบิวต์แท็ก JSTL

เมื่อใดก็ตามที่คุณคิดว่าคุณต้องผูกส่วนประกอบเข้ากับแบ็คเอนด์บีนbindingหรือคว้าผ่านfindComponent()และสร้าง / จัดการกับลูกโดยใช้โค้ด Java ในแบ็คสเปคด้วยnew SomeComponent()และไม่ควรทำเช่นนั้นคุณควรหยุดและพิจารณาใช้ JSTL แทนทันที เนื่องจาก JSTL ยังใช้ XML รหัสที่จำเป็นในการสร้างส่วนประกอบ JSF แบบไดนามิกจะสามารถอ่านและบำรุงรักษาได้ดีขึ้นมาก

สิ่งสำคัญที่ควรทราบคือ Mojarra เวอร์ชันเก่ากว่า 2.1.18 มีข้อผิดพลาดในการบันทึกสถานะบางส่วนเมื่ออ้างอิงถั่วมุมมองที่กำหนดในแอตทริบิวต์แท็ก JSTL มุมมองทั้งขอบเขตถั่วจะเพิ่งสร้างขึ้นแทนการดึงออกมาจากต้นไม้มุมมอง (เพียงเพราะต้นไม้มุมมองที่สมบูรณ์คือยังไม่พร้อมที่จุด JSTL วิ่ง) หากคุณคาดหวังหรือเก็บสถานะไว้ในถั่วที่กำหนดมุมมองด้วยแอตทริบิวต์แท็ก JSTL มันจะไม่ส่งคืนค่าที่คุณคาดหวังหรือจะ "หายไป" ในมุมมองที่ถูกกำหนดขอบเขตจริงซึ่งเรียกคืนหลังจากดู ต้นไม้ถูกสร้างขึ้น ในกรณีที่คุณไม่สามารถอัปเกรดเป็น Mojarra 2.1.18 หรือใหม่กว่าการแก้ไขคือการปิดสถานะการประหยัดบางส่วนweb.xmlดังนี้:

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

ดูสิ่งนี้ด้วย:

หากต้องการดูตัวอย่างโลกแห่งความจริงที่มีแท็ก JSTL ที่เป็นประโยชน์ (เช่นเมื่อใช้อย่างถูกต้องระหว่างการสร้างมุมมอง) ให้ดูคำถาม / คำตอบต่อไปนี้:


โดยสังเขป

ในฐานะที่เป็นความต้องการของคุณทำงานคอนกรีตถ้าคุณต้องการที่จะทำให้ส่วนประกอบ JSF เงื่อนไขใช้renderedแอตทริบิวต์ในองค์ประกอบ JSF HTML แทนโดยเฉพาะอย่างยิ่งถ้า#{lpc}หมายถึงรายการที่ซ้ำปัจจุบันของ JSF วนองค์ประกอบเช่นหรือ<h:dataTable><ui:repeat>

<h:someComponent rendered="#{lpc.verbose}">
    ...
</h:someComponent>

หรือถ้าคุณต้องการสร้าง (สร้าง / เพิ่ม) ส่วนประกอบ JSF ตามเงื่อนไขให้ใช้ JSTL ต่อไป เป็นวิธีที่ดีกว่าการใช้ verbosely new SomeComponent()ใน java

<c:if test="#{lpc.verbose}">
    <h:someComponent>
        ...
    </h:someComponent>
</c:if>

ดูสิ่งนี้ด้วย:


3
@Aklin: ไม่ วิธีการเกี่ยวกับตัวอย่างนี้ ?
BalusC

1
ฉันไม่สามารถตีความวรรคหนึ่งได้อย่างเหมาะสมเป็นเวลานาน (ตัวอย่างที่ให้มานั้นชัดเจนมาก) ดังนั้นฉันจะออกความคิดเห็นนี้เป็นวิธีเดียว ตามวรรคนั้นฉันอยู่ในการแสดงผลที่<ui:repeat>เป็นตัวจัดการแท็ก (เพราะบรรทัดนี้ " โปรดทราบว่า JSF ของตัวเอง<f:xxx>และ<ui:xxx>... ") เหมือน<c:forEach>และดังนั้นจึงมีการประเมินเวลาสร้างมุมมอง (อีกครั้งเหมือน<c:forEach>) . ถ้าเป็นแล้วไม่ควรจะมองเห็นความแตกต่างใด ๆ ระหว่างการทำงาน<ui:repeat>และ<c:forEach>? ฉันไม่เข้าใจความหมายของย่อหน้าที่แน่นอน :)
จิ๋ว

1
ขออภัยฉันจะไม่สร้างมลภาวะให้กับโพสต์นี้อีก ผมเอาความคิดเห็นก่อนหน้าของคุณลงในความสนใจของฉัน แต่ไม่ได้ประโยคนี้ " โปรดทราบว่า JSF ของตัวเอง<f:xxx>และ<ui:xxx>แท็กที่ไม่ขยายUIComponentนอกจากนี้ยังมีรถขนแท็ก. " ความพยายามที่จะบ่งบอกว่า<ui:repeat>ยังเป็นตัวจัดการแท็กเพราะ<ui:xxx>ยังมี<ui:repeat>? นี้แล้วควรจะหมายถึงว่า<ui:repeat>เป็นหนึ่งในองค์ประกอบในที่ขยาย<ui:xxx> UIComponentดังนั้นจึงไม่ใช่ตัวจัดการแท็ก (บางรายการอาจไม่ขยายUIComponentดังนั้นจึงเป็นตัวจัดการแท็ก) ใช่หรือไม่
Tiny

2
@Shirgill: <c:set>ไม่scopeสร้างนามแฝงของนิพจน์ EL แทนการตั้งค่าที่ประเมินในขอบเขตเป้าหมาย ลองscope="request"แทนซึ่งจะประเมินค่าทันที (ในช่วงเวลาที่สร้างมุมมองแน่นอน) และตั้งค่าเป็นแอตทริบิวต์คำขอ (ซึ่งจะไม่ถูก "เขียนทับ" ในระหว่างการทำซ้ำ) ภายใต้ฝาปิดมันสร้างและกำหนดValueExpressionวัตถุ
BalusC

1
@ K.Nicholas: ClassNotFoundExceptionมันภายใต้ผ้าห่ม การพึ่งพารันไทม์ของโครงการของคุณเสีย เป็นไปได้ว่าคุณกำลังใช้เซิร์ฟเวอร์ที่ไม่ใช่ JavaEE เช่น Tomcat และคุณลืมติดตั้ง JSTL หรือคุณรวมทั้ง JSTL 1.0 และ JSTL 1.1+ โดยไม่ตั้งใจ เพราะใน JSTL 1.0 แพคเกจjavax.servlet.jstl.core.*และตั้งแต่ JSTL 1.1 javax.servlet.jsp.jstl.core.*นี้ได้กลายเป็น พบเบาะแสสำหรับการติดตั้ง JSTL ได้ที่นี่: stackoverflow.com/a/4928309
BalusC

13

ใช้

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>

ขอบคุณคำตอบที่ดี โดยทั่วไปเพิ่มเติม: แท็ก JSTL ยังคงสมเหตุสมผลหรือเราควรพิจารณาว่าเลิกใช้ตั้งแต่ JSF 2.0 หรือไม่
ม.ค.

ในกรณีส่วนใหญ่ใช่ แต่บางครั้งก็เหมาะสมที่จะใช้มัน
Bozho

3
การใช้ h: panelGroup เป็นวิธีที่สกปรกเพราะมันจะสร้างแท็ก <span> ในขณะที่ c: ถ้าไม่มีอะไรเพิ่มในรหัส html h: panelGroup ยังเป็นปัญหาภายใน panelGrids เนื่องจากจัดกลุ่มองค์ประกอบ
Rober2D2

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.