Kihagyás

11. gyakorlat

Saját tagek (Custom tags)

A felhasználó saját maga hozhat létre új tagaket. Ezáltal segít abban, hogy ne kelljen scriptleteket használni, illetve segít szeparálni az üzleti logikát a megjelenítéstől. Ezek mellett talán a legnagyobb előnye, hogy ezeket a tageket újra fel tudjuk használni több helyen is. Kétféleképpen használhatjuk a saját tagjeinket:

1
2
3
4
5
6
7
<prefix:tagname attr1=value1....attrn=valuen />

vagy

<prefix:tagname attr1=value1....attrn=valuen >  
  body code  
</prefix:tagname>  

A saját tagek definíciójához nagyobb szabadságot ad, ha TLD-ket (Tag Library Descriptor-okat) adunk meg, de ez túlmutat a céljainkon, mivel kicsit nehézkes és több helyen kell ezeket megadni.

Egy másik, egyszerűbb módja, hogy a tag definíciókat a WEB-INF/tags mappában helyezzük el. Készítsük el a current-datetime taget!

WEB-INF/tags/current-datetime.tag

1
2
3
4
5
6
<%@ tag import="java.time.LocalDateTime"%>
<%@ tag language="java" pageEncoding="ISO-8859-1"%>

<%
  out.println(LocalDateTime.now());
%>

Figyeljük meg, hogy a page direktíva helyett azt írtjuk, hogy tag, ezzel is jelezvén, hogy egy saját tag-et definiálunk. A tag egyszerűen kiírja az aktuális dátumot és időt.

Használata, index.jsp

1
2
3
4
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib tagdir="/WEB-INF/tags" prefix="t"%>

<t:current-datetime />

A taglib direktívában nem az uri-t kell megadnunk, hanem a tagdir attribútumban a saját tagjeink könyvtárát. Innentől már használhatjuk a tags könyvtárban lévő tageket mint például a current-datetime-ot.

Custom tagek attribútumai

Az előző példában csak egy tag-et csináltunk, de annak nem voltak attribútumai, ami viszont egy elég általános elvárás egy saját tag-től.

Készítsük el azt a saját tag-et amelyet a következőképpen tudunk használni:

1
<t:cube value="4" />

Ehhez készítünk egy cube.tag állományt a WEB-INF/tags mappába, melynek tartalma:

1
2
3
4
5
6
7
<%@ tag language="java" pageEncoding="ISO-8859-1"%>

<%@ attribute name="value" required="true" type="java.lang.Double" %>

<%
  out.print(value * value * value);
%>
Mint az kitűnik, attribútumot a <%@ attribute %> direktívával adhatunk meg. Ez a direktíva csak tag definícióknál használható, jsp oldalaknál nem. Az attribútumnak meg lehet adni a nevét, hogy kötelező-e, mint azt a fenti példában is láthatjuk. Nagyon fontos a type attribútum. A példában a 3. hatványra emelő tagünknek egy számot kell várnia, azonban az attribútumok alaptípusa java.util.String. A mi esetünkben ez egy nagy exception-t eredményezne, mivel arra nem tudja értelmezni a value * value kifejezést, így a type-ot megadtuk, hogy Double-t várjon és így működik is a tag. Ezen felül az attribútumnak adhatunk leírást is a description megadásával.

Amennyiben nem attribútumban, hanem a tagek között akarjuk megadni az értéket a cube-nak, akkor a következőt kell tennünk:

1
2
3
4
5
6
7
8
<%@ tag language="java" pageEncoding="ISO-8859-1"%>

<jsp:doBody scope="request" var="value" />

<%
  int value = Integer.parseInt(((String)request.getAttribute("value")).trim());
  out.print(value * value * value);
%>

A jsp:doBody segítségével a custom tagünk közötti body részt értékelhetjük ki. A kiértékelés eredményét egyből nyomhatjuk az outba, ekkor nem kell sem a scope sem a var attribútumot használnunk. Ha viszont szeretnénk felhasználni az értékét később, akkor érdemes lehet eltárolni, mint ahogyan most is ki akarjuk számolni a köbét. Ehhez kiolvassuk a tartalmat, majd string-é alakítjuk és trimmeljük, hogy tudjunk parsolni olyat is ha különböző sorban van a nyitó és záró tag, illetve közte az adat. Használni egyszerűen a következőképpen lehet:

1
<t:cube>3</t:cube>

Az attribute direktívának van egy további paramétere, mégpedig, hogy az attribútum az fragment-e. Ez azt jelenti, hogy a tag paraméterül jsp kódrészletet kap. Ezek nagyon jól használhatóak, ha például template-et kell készítenünk az oldalainkhoz.

A cube tag-et bővítsük, hogy benne még további jsp-t adhassunk meg! A tag definícióban az alábbiakat kell írnunk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<%@ tag language="java" pageEncoding="UTF-8"%>

<%@ attribute name="value" required="true" type="java.lang.Double"  %>
<%@ attribute name="content" fragment="true"%>

<%
  out.print(value * value * value);
%>

<jsp:invoke fragment="content" />
A kapott jsp fragment-et az oldalon belül a <jsp:invoke> használatával illeszthetjük be. Ezt akár több ponton is megtehetjük. Ezután a használatát tekintve a következőket kell tennünk:

1
2
3
4
5
<t:cube value="3" >
  <jsp:attribute name="content">
    asdqwe
  </jsp:attribute>
</t:cube>

Most, hogy az alapokat megnéztük ideje, hogy átalakítsuk az alkalmazásunkat úgy, hogy az page template-eket használjon. Az esetleges újdonságokat menet közben még tisztázzuk.

Feladat

Alakítsuk át úgy a contacts alkalmazásunkat, hogy page template-eket használjon!

Először is különböztessük meg azt a különböző eseteket, amikre szükségünk lehet:

  • Common header szükséges az oldalhoz
  • Common header és menü is szükséges az oldalon

Ez alapján kétféle page template-et fogunk elkészíteni. Vegyük az elsőt, melyben csak a common header lesz benne. Ehhez a WEB-INF alatt hozzuk létre a tags könyvtárat, ahol létrehozzuk a basic-layout.tag állományt, melynek a következő tartalma lesz:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<%@ tag description="Basic Page template" pageEncoding="UTF-8" %>
<%@ attribute name="title" required="false" type="java.lang.String" %>
<%@ attribute name="header" fragment="true" %>
<%@ attribute name="footer" fragment="true" %>


<html>
    <head>
        <title>${title}</title>
        <link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css">
        <link rel="stylesheet"
              href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
              integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
              crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
                integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
                crossorigin="anonymous"></script>
        <script
                src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
                integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
                crossorigin="anonymous"></script>
        <script
                src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
                integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
                crossorigin="anonymous"></script>

        <jsp:invoke fragment="header"/>
    </head>

    <body>
        <div id="body" class="container">
            <jsp:doBody/>
        </div>
        <jsp:invoke fragment="footer"/>
    </body>
</html>

A fájl elején megadjuk, hogy egy custom tag-ről van szó (aminek neve megegyezik a fájl nevével: basic-layout), majd megadjuk, hogy milyen attribútumokat fogunk használni ebben a tag-ben. Az első attribútum a title, mely az oldal címét adja meg és annak típusú String. Ha megnézzük, hogy hol használjuk fel a title attribútumot, akkor láthatjuk, hogy a <title>${title}</title> megadásban szerepel, ami az oldal címét adja meg. Ilyenkor a tag-et a következőképpen fogjuk használni: <basic-layout title="Login">...</basic-layout>, melynek eredményeképpen a megadott oldal címe Login lesz.

A <head> részben a korábban common-header.jsp oldalon megadott elemeket helyezzük el. Ugyanakkor itt van még egy fontos pont, amikor meghívjuk a header attribútumot, amely viszont már egy fragment a title-el ellentétben, így az tartalmazhat HTML részfát is akár. Erre azért van szükség, mert az egyes oldalak esetleg tovább szeretnék bővíteni a <head>-ben megadott elemeket, mint például az add-contact.jsp-ben szeretnénk használni az add-contact.js állományt (máshol viszont nem). Ugyanezt végezzük el a footer-re, melyet jelenleg ugyan nem használunk, de később jól jöhet.

A használat helyén a <basic-layout title="xxx">...</basic-layout> között megadott tartalmat a <jsp:doBody/> hívással írhatjuk ki a template-ben. Ezt a tartalmat egy div-ben helyezzük el <div id="body" class="container">.

Miután megvan a template, nézzük, hogy hogyan használhatjuk fel azt például a login.jsp oldalon:

1
2
3
4
5
6
7
8
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:basic-layout title="Login">
  <form action="../LoginController" method="post">
    ...
  </form>
</t:basic-layout>

A fenti példában a behúzzuk a használni kívánt tagek mappáját: tagdir és adunk neki egy prefixet, mely most a t. Ezután egyszerűen használjuk a saját basic-layout tag-ünket, ahol megadjuk a title attribútumot. Most a megadott <form> elem kerül a template-ben megadott <jsp:doBody/> helyre.

A teljesség kedvéért alul látható a böngészőben elérhető HTML, ahol már a saját tag- feloldását láthatjuk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<html>
    <head>
        <title>Login</title>
        <link rel="stylesheet" href="/contacts_web_war/css/style.css">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css">
        <link rel="stylesheet"
              href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
              integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
              crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
                integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
                crossorigin="anonymous"></script>
        <script
                src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
                integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
                crossorigin="anonymous"></script>
        <script
                src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
                integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
                crossorigin="anonymous"></script>
    </head>
    <body>
        <div id="body" class="container">
          <form action="../LoginController" method="post">
              <div class="form-group">
                  <label for="username">Username</label>
                  <input required name="username" type="text" class="form-control" id="username"
                        placeholder="Username"/>
              </div>
              <div class="form-group">
                  <label for="password">Password</label>
                  <input required name="password" type="password" class="form-control" id="password"
                        placeholder="Password"/>
              </div>
              <button id="submit" type="submit" class="btn btn-primary">Submit</button>
              <span><a href="register.jsp">Register</a></span>
          </form>
        </div>
    </body>
</html>

Ezt az alap template-et a login mellett a register.jsp is használja, mely nagyon hasonló lesz a login-hoz:

1
2
3
4
5
6
7
8
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:basic-layout title="Register">
    <form action="../RegisterController" method="post">
        ...
    </form>
</t:basic-layout>

Az eddigi form-ot átmozgattuk a basic-layout tag body részébe, illetve más title-t adtunk meg, de más különbség nincs.

Ezután nézzük meg azt, hogy hogyan használhatjuk fel a basic-layout tag-et egy másik tag definíciójában, azaz hogyan bővíthetjük ezt a tag-et (a menüvel). Nyilvánvalóan a basic-layout-ban szereplő elemeket szeretnénk a basic-layout-menu tag-ben is alkalmazni, viszont kódot nem szeretnénk duplikálni.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ tag description="Basic Page template" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@ attribute name="header" fragment="true" %>
<%@ attribute name="footer" fragment="true" %>
<%@ attribute name="title" required="false" type="java.lang.String" %>

<t:basic-layout title="${title}">
    <jsp:attribute name="header">
        <jsp:invoke fragment="header" />
    </jsp:attribute>
    <jsp:attribute name="footer">
        <jsp:invoke fragment="footer" />
    </jsp:attribute>

    <jsp:body>
        <nav class="navbar fixed-top navbar-expand-lg navbar-light bg-light">
            ...
        </nav>
        <jsp:doBody/>
    </jsp:body>
</t:basic-layout>

Mivel hasonlóan meg szeretnénk adni egy basic-layout-menu tag-nek is a title-t, az esetleges header-t és footer-t, így ezeket az attribútumokat itt is "kivezetjük", melyeket majd továbbadjuk a basic-layout-nak. Ez látszódik például a <t:basic-layout title="${title}"> sorban is. A header és a footer megadása kicsit másképpen működik. A header beillesztéséhez a <jsp:invoke fragment="header" />-ot kell meghívnunk, melyet "kívülről" kapunk. Mivel ezt a <t:basic-layout>-nak szeretnénk továbbadni, így itt meg kell adni, hogy a basic-layout header attribútumát szeretnénk beállítani, ezért kell a <jsp:attribute name="header"> megadás. Hasonlóképpen a kívülről kapott basic-layout-menu tartalmát tovább szeretnénk adni a basic-layout body-jaként, így ezt <jsp:body> tag-ek között kell megadnunk. Annyi a különbség, hogy a kívülről kapott body HTML tartalom elé berakjuk a nav-ot, azaz a menüt.

Nézzük, hogy hogyan használja fel a list-contact.jsp oldal a template-et:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags"%>

<jsp:include page="/ContactController"/>

<t:basic-layout-menu title="List Contact">
    <table class="table">
        ...
    </table>
</t:basic-layout-menu>

A tag felhasználása a megszokott módon történik, nincs semmi mágia. A különbség annyi, hogy most már a <basic-layout-menu> tag-et használjuk.

Viszont van egy fontos különbség, akkor amikor a header-be vagy a footer-be is bele szeretnénk valamit rakni. Ez igazából hasonlóan megy, mint a basic-layout-menu-nél. Lássuk az add-contact.jsp oldalt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<%@ page import="hu.alkfejl.model.Phone" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:basic-layout-menu title="Add Contact">
    <jsp:attribute name="header">
            <script src="${pageContext.request.contextPath}/js/add-contact.js"></script>
    </jsp:attribute>
    <jsp:body>
        <jsp:useBean id="contact" class="hu.alkfejl.model.Contact" scope="request"/>
        <form action="${pageContext.request.contextPath}/ContactController" method="post">
          ...
        </form>
    </jsp:body>
</t:basic-layout-menu>

Szokásos módon behúzzuk a használni kívánt tag-ek könyvtárát: <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>. Ezután a <basic-layout-menu>-on belül a header-t szeretnénk megadni, ahol a <script src="${pageContext.request.contextPath}/js/add-contact.js"></script> bővítést szeretnénk eszközölni. Ezután pedig a body-ban megadni a useBean-nel a megfelelő Contact objektumot és a form-ot. Amennyiben nem jeleznénk, hogy mi a header attribútum és mi a basic-layout-menu body-ja, akkor a rendszer mindent body-nak venne, így megadjuk a header attribútum értékét a <jsp:attribute name="header"> tag használatával, illetve a <jsp:body>-val magát basic-layout-menu body-ját.

A profile.jsp oldalon hasonló módosításokat kell tennünk, mint az add-contact.jsp-n, így azt itt már nem mutatjuk be.

Ezzel elkészítettük a kontakt alkalmazásunk webes változatát is. A teljes projektet lásd a pub-on!


Utolsó frissítés: 2021-04-13 13:36:13