JSP وJSTL وطبقة العرض

تنسيق JSTL والدوال

18 دقيقة الدرس 6 من 13

تنسيق JSTL والدوال

تناول الدرس السابق وسوم JSTL Core — العمود الفقري لتدفق التحكم في أي صفحة JSP. يتناول هذا الدرس مكتبتين بالغتي الأهمية: مكتبة وسوم fmt لتنسيق الأرقام والتواريخ بما يتوافق مع الإعدادات المحلية، ومكتبة دوال fn لمعالجة النصوص داخل تعبيرات EL. تُلغي هاتان المكتبتان كود Java القبيح الذي اضطر المطوّرون تاريخيًا لحشوه في صفحات JSP لتنسيق المخرجات.

الإعداد: إضافة مكتبتَي fmt و fn

كلتا المكتبتين تأتيان في نفس ملف JAR الخاص بـ jakarta.servlet.jsp.jstl (JSTL 3.x لـ Jakarta EE 10+). لديك هذا الملف بالفعل من درس Core. أعلن عنهما في أعلى كل صفحة JSP تحتاجهما:

<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %> <%@ taglib prefix="fn" uri="jakarta.tags.functions" %>
JSTL 3 مقابل JSTL 1.2: أعادت Jakarta EE 10 تسمية جميع عناوين URI من http://java.sun.com/jsp/jstl/... إلى jakarta.tags.*. إن كنت تعمل على Tomcat 9 القديم أو Java EE فلا تزال تستخدم عناوين URI القديمة. سلوك الوسوم متطابق في الحالتين.

تحديد نطاق اللغة والمنطقة الزمنية بـ fmt:setLocale و fmt:setTimeZone

تحترم جميع وسوم تنسيق fmt اللغة المحلية والمنطقة الزمنية الحالية، اللتين تُحددان مرة واحدة لكل صفحة (أو لكل طلب) بدلًا من تكرارهما في كل وسم:

<!-- تثبيت اللغة لكامل الصفحة --> <fmt:setLocale value="${sessionScope.userLocale}" /> <!-- تثبيت المنطقة الزمنية لكامل الصفحة --> <fmt:setTimeZone value="${sessionScope.userTimeZone}" />

حين لا يظهر أيٌّ من الوسمين، يتراجع محرك JSP إلى لغة JVM للخادم وUTC، وهو ما لا يريده المستخدمون النهائيون في الغالب. احرص دائمًا على ضبطهما صراحةً في صفحات الإنتاج.

تنسيق الأرقام بـ fmt:formatNumber

يُحوّل <fmt:formatNumber> أي قيمة رقمية إلى سلسلة نصية صحيحة وفق الإعدادات المحلية. تتحكم سمة type في أسلوب المخرجات:

  • type="number" — عشري عادي مع فواصل تجميع (القيمة الافتراضية)
  • type="currency" — رمز العملة + التجميع + المنازل العشرية وفق الإعدادات المحلية
  • type="percent" — يضرب في 100 ويُلحق علامة النسبة المئوية
<fmt:setLocale value="en_US" /> <p>السعر: <fmt:formatNumber value="${product.price}" type="currency" /></p> <p>الخصم: <fmt:formatNumber value="${product.discount}" type="percent" /></p> <p>المخزون: <fmt:formatNumber value="${product.stock}" type="number" groupingUsed="true" /></p> <p>الوزن: <fmt:formatNumber value="${product.weightKg}" type="number" minFractionDigits="2" maxFractionDigits="2" /> كجم</p>

مع الإعدادات المحلية en_US وقيمة price = 1299.9، ينتج وسم العملة $1,299.90. بالتبديل إلى de_DE ينتج 1.299,90 € — يعالج الوسم تلقائيًا رمز العملة وعلامات الترقيم المناسبين.

استخدم currencyCode لتجاوز رمز العملة. حين تحفظ بياناتك مبلغًا بعملة معينة لكن الإعدادات المحلية للمستخدم مختلفة، أضف currencyCode="EUR" (أو أي رمز ISO 4217) لعرض الرمز الصحيح بصرف النظر عن الإعدادات المحلية.

لالتقاط السلسلة المُنسَّقة في متغير بدلًا من كتابتها مباشرةً، استخدم سمتَي var وscope — نمط تشترك فيه معظم وسوم JSTL:

<fmt:formatNumber value="${order.total}" type="currency" var="formattedTotal" scope="request" /> <p>مجموعك: ${formattedTotal}</p>

تحليل الأرقام بـ fmt:parseNumber

العملية العكسية — تحويل سلسلة نصية مُنسَّقة وفق إعدادات محلية إلى رقم — يتولّاها <fmt:parseNumber>. نادرًا ما تحتاجه في طبقة العرض، لكنه مفيد في الفلاتر أو ملفات الوسوم التي تستقبل معاملات نصية من سلاسل الاستعلام:

<fmt:parseNumber value="1.299,90" type="number" parseLocale="de_DE" var="amount" /> <!-- ${amount} الآن هو double بقيمة 1299.9 في Java -->

تنسيق التواريخ والأوقات بـ fmt:formatDate

يقبل <fmt:formatDate> كائنًا من نوع java.util.Date (أو قيمة يُرجعها EL بعد التحويل) وينسّقه وفق اللغة المحلية والمنطقة الزمنية المُعرَّفتين. تتحكم سمة type في الجزء المعروض من الطابع الزمني:

<fmt:setLocale value="en_US" /> <fmt:setTimeZone value="America/New_York" /> <p>التاريخ فقط: <fmt:formatDate value="${order.createdAt}" type="date" dateStyle="long" /></p> <p>الوقت فقط: <fmt:formatDate value="${order.createdAt}" type="time" timeStyle="short" /></p> <p>التاريخ والوقت: <fmt:formatDate value="${order.createdAt}" type="both" dateStyle="medium" timeStyle="medium" /></p> <p>نمط مخصص: <fmt:formatDate value="${order.createdAt}" pattern="yyyy-MM-dd HH:mm" /></p>

تقبل سمتا dateStyle وtimeStyle القيم short وmedium وlong وfull — نفس ثوابت java.text.DateFormat. تأخذ سمة pattern نمط SimpleDateFormat وتُلغي سمات الأسلوب حين يجتمعان.

java.util.Date موروث من الإصدارات القديمة. يستخدم كود Java الحديث java.time.* (LocalDate وZonedDateTime وInstant). لا يفهم fmt:formatDate إلا java.util.Date، فيلزمك التحويل: Date.from(instant) أو Date.from(localDate.atStartOfDay(ZoneId.of("UTC")).toInstant()). في Spring MVC يمكنك تسجيل محوّل مخصص لكي يُحلّل EL كائنات java.time مباشرةً، لكن fmt:formatDate ذاته لا يزال يحتاج النوع القديم.

تحليل التواريخ بـ fmt:parseDate

يُحوّل الوسم المُصاحب <fmt:parseDate> سلسلة نصية إلى java.util.Date:

<fmt:parseDate value="${param.dob}" pattern="yyyy-MM-dd" var="dobDate" /> <!-- ${dobDate} الآن كائن java.util.Date -->

تدويل النصوص بـ fmt:message

تمتلك مكتبة fmt أيضًا وسم رسائل i18n. حمّل حزمة موارد وابحث عن المفاتيح بالاسم:

<fmt:setBundle basename="messages" /> <fmt:message key="welcome.title" /> <fmt:message key="order.confirmation"> <fmt:param value="${order.id}" /> </fmt:message>

سيحتوي ملف الحزمة messages_en_US.properties على أسطر مثل order.confirmation=Your order #{0} has been placed.. يتكامل هذا مع عناصر نائبة خاصة بـ MessageFormat في Java.

مكتبة دوال fn

توفّر مكتبة fn دوال مساعدة للنصوص والمجموعات يمكن استدعاؤها مباشرةً داخل تعبيرات EL. لا يوجد وسم منفصل — كل دالة تُستدعى بالشكل ${fn:functionName(arg1, arg2)}.

أكثر الدوال استخدامًا:

  • fn:length(collection) — طول سلسلة نصية أو مصفوفة أو مجموعة
  • fn:toUpperCase(str) / fn:toLowerCase(str)
  • fn:trim(str)
  • fn:substring(str, begin, end) — صفري الأساس، النهاية غير شاملة (يشابه String.substring)
  • fn:substringBefore(str, delimiter) / fn:substringAfter(str, delimiter)
  • fn:replace(str, before, after)
  • fn:split(str, delimiter) — ترجع String[]
  • fn:join(array, separator) — عكس split
  • fn:contains(str, substring) — حساس لحالة الأحرف؛ يرجع قيمة منطقية
  • fn:containsIgnoreCase(str, substring)
  • fn:startsWith(str, prefix) / fn:endsWith(str, suffix)
  • fn:indexOf(str, substring)
  • fn:escapeXml(str) — يُشفّر الكيانات < و> و& و" و'

دوال fn في التطبيق العملي

مقتطف عملي: عرض وصف منتج مُختصر مع نقاط إضافية، والحراسة من القيم الخالية، وإظهار شارة فقط حين تكون قائمة الوسوم غير فارغة:

<c:if test="${fn:length(product.description) gt 120}"> <p>${fn:substring(product.description, 0, 120)}...</p> </c:if> <c:if test="${fn:length(product.description) le 120}"> <p>${product.description}</p> </c:if> <c:if test="${fn:length(product.tags) gt 0}"> <ul class="tags"> <c:forEach items="${product.tags}" var="tag"> <li>${fn:toLowerCase(fn:trim(tag))}</li> </c:forEach> </ul> </c:if> <!-- عرض آمن للنص الذي أدخله المستخدم --> <p>${fn:escapeXml(review.body)}</p>
استخدم fn:escapeXml دائمًا على المحتوى الذي يُدخله المستخدم. عرض مُدخلات المستخدم الخام عبر ${review.body} دون تشفير يُشكّل ثغرة XSS مُخزَّنة. يُعدّ fn:escapeXml خط الدفاع الأخير في طبقة العرض.

الجمع بين fmt و fn معًا

تتكامل دوال fn مع وسوم fmt عبر سمة var. نمط شائع هو بناء سلسلة نصية بـ fn، تخزينها في متغير، ثم تمريرها معاملًا لوسم رسالة:

<fmt:formatNumber value="${item.unitPrice}" type="currency" var="priceStr" /> <fmt:formatDate value="${item.shipDate}" pattern="MMM d, yyyy" var="dateStr" /> <fmt:message key="item.summary"> <fmt:param value="${fn:toUpperCase(item.sku)}" /> <fmt:param value="${priceStr}" /> <fmt:param value="${dateStr}" /> </fmt:message>

المقايضات ومتى تنقل المنطق إلى المتحكم

تعالج مكتبتا fmt و fn تحويلات العرض النقي بشكل أنيق. غير أن ثمة حالات ينبغي فيها نقل التنسيق إلى طبقة Java:

  • إعادة الاستخدام عبر صفحات متعددة — إن عرضت ست صفحات JSP مختلفة السعر بنفس الأسلوب، نسّقه مرة واحدة في المتحكم ومرّر سلسلة نصية جاهزة، أو استخدم ملف وسم مشتركًا.
  • منطق أعمال في التنسيق — اختيار السعر شاملًا أو مستبعدًا الضريبة بناءً على موقع المستخدم الجغرافي ليس تقديمًا بل ينتمي إلى فئة خدمة.
  • أنواع java.time — نسّق بـ DateTimeFormatter في المتحكم ومرّر سلسلة نصية بسيطة للعرض لتجنّب الحاجة لتحويل java.util.Date.
ابقِ صفحات JSP بسيطة. مكتبتا fmt و fn مخصصتان لـ كيفية عرض شيء ما لا لـ ما تعنيه. التنسيق الشرطي المبني على قواعد أعمال (حد الشحن المجاني، لون الفاتورة المتأخرة) يُعبَّر عنه أفضل في مساعد Java أو حبة ViewHelper في Spring حيث يمكن اختبارها.

الخلاصة

تمنحك مكتبة fmt تنسيقًا يراعي الإعدادات المحلية والمنطقة الزمنية للأرقام والعملات والنسب المئوية والتواريخ دون سطر Java واحد في الصفحة. وتجلب مكتبة fn أدوات النصوص — الطول والمقطع والتنظيف والاستبدال والتقسيم والدمج وتشفير XML — مباشرةً إلى تعبيرات EL. يُغطيان معًا تقريبًا كل احتياجات تنسيق المخرجات في طبقة عرض JSP دون أي scriptlet. يتناول الدرس القادم نمط MVC الذي يحكم كيفية تعاون Servlets وJSP.