Znaczniki użytkownika

Technologia JSP umożliwia tworzenie własnych bibliotek znaczników, wykonujących jakieś akcje. Warto zauważyć, że programista może przejąć całkowitą kontrolę nad wykonaniem akcji, to znaczy:

Aby dołączyć do strony JSP obsługę bibioteki znaczników, należy użyć dyrektywy taglib.

Przykład:

<%@ taglib prefix="moje" uri="taglib.tld" %>

<moje:terminarz miesiac="Grudzien" >
  <moje:termin dzien="2">
    Spotkanie z premierem.
  </moje:termin>
  <moje:termin dzien="1">
    Wycieczka do Brukseli.
  </moje:termin>
</moje:terminarz>

Wynik działania takich akcji może być na przykład taki:

Grudzień

1, Niedziela


Wycieczka do Brukseli

2, Poniedziałek


Spotkanie z premierem

Każdemu zdefiniowanemu znacznikowi (czyli akcji) odpowiada klasa w Javie, która opisuje jego zachowanie. O tym, która klasa jest związana z daną akcją, mówi deskryptor biblioteki znaczników, którego położenie podaje się w dyrektywie taglib. Deskryptor to plik w formacie XML, podający także atrybuty znacznika i inne jego cechy. Deskryptor dla powyższego przykładu to:

<?xml version = '1.0' encoding = 'UTF-8'?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
   <tlibversion>1</tlibversion>
   <jspversion>1.1</jspversion>
   <shortname>terminarze</shortname>
   <tag>
      <name>termin</name>
      <tagclass>terminarz.TerminTag</tagclass>
      <attribute>
        <name>dzien</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
      </attribute>
   </tag>
   <tag>
      <name>terminarz</name>
      <tagclass>terminarz.TerminarzTag</tagclass>
   </tag>
</taglib>

Jak można zauważyć powyżej, przy opisie znacznika podajemy:

Klasy obsługujące znaczniki

Klasy te muszą implementować interfejs javax.servlet.jsp.tagext.Tag. Na ogół jednak dziedziczą z klas TagSupport lub BodyTagSupport. Specyfikacja JSP 1.2 wyróżnia trzy typy znaczników:

Znacznik prosty

Dzialanie prostego znacznika

Powyższy rysunek przedstawia przepływ sterowania podczas wykonywania się akcji związanej ze znacznikiem prostym. Kontener JSP najpierw odnajduje, przy użyciu deskryptora biblioteki znaczników, klasę odpowiedzialną za obsługę danego znacznika i tworzy obiekt tej klasy. Następnie wykonuje jej metody setPageContext() oraz setParent(). Pierwsza z nich ustawia atrybut pageContext obiektu, zawierający kontekst strony, na której znajduje się znacznik. Druga ustawia atrybut parent obiektu wskazujący obiekt, który odpowiada znacznikowi nadrzędnemu, którego ciało zawiera nasz znacznik.

Następnie kontener JSP wywołuje metodę doStartTag() obiektu znacznika. Metoda ta może zwrócić dwie wartości EVAL_BODY_INCLUDE lub SKIP_BODY. Jeżeli zwrócona zostanie pierwsza wartość, wykonane zostanie ciało znacznika (akcji). W przeciwnym przypadku ciało znacznika zostanie pominięte.

W kolejnym kroku kontener wywołuje metodę doEndTag() obiektu znacznika, która może zwrócić wartość SKIP_PAGE lub EVAL_PAGE. Wykonanie strony jest odpowiednio: wstrzymywane lub kontynuowane.

Przykład

Przypuśćmy, że chcemy stworzyć prosty znacznik, wstawiający na stronę tekst pozdrowienia. Taka strona JSP miałaby postać:

<%@ taglib prefix="moje" uri="przywitania.tld" %>

<moje:czesc jezyk="polski"/> użytkowniku.

Pokażę, jak napisać ten prosty znacznik na dwa sposoby.

Dziedziczenie z klasy TagSupport
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class CzescTag extends TagSupport 
{
  String jezyk;

  public void setJezyk (String jezyk) 
  {
    this.jezyk = jezyk;
  }
  
  public int doStartTag () throws JspException 
  {
    try {
      JspWriter out = pageContext.getOut ();
      if (this.jezyk.compareTo ("polski")) out.print ("Czesc");
      else out.print ("Hello");
    } catch (IOException e) {
      throw new JspException ("Wystapil wyjatek IOException");
    }
    return SKIP_BODY;
  }

  public int doEndTag () throws JspException 
  {
    return EVAL_PAGE;
  }
}
Implementacja interfejsu Tag
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class CzescTag implements Tag 
{
  private PageContext pageContext;
  private Tag parent;
  String jezyk;

  public CzescTag () 
  {
    super ();
  }

  public setJezyk (String jezyk)
  {
    this.jezyk = jezyk;
  }

  public int doStartTag () throws JspException 
  {
    try {
      JspWriter out = pageContext.getOut ();
      if (this.jezyk.compareTo ("polski")) out.print ("Czesc");
      else out.print ("Hello");
    } catch (IOException e) {
      throw new JspException ("Wystapil wyjatek IOException");
    }
  }

  public int doEndTag () throws JspException 
  {
    return EVAL_PAGE;
  }

  public void release ()
  {
  }
  
  public void setPageContext (PageContext pageContext)
  {
    this.pageContext = pageContext;
  }

  public void setParent (Tag parent)
  {
    this.parent = parent;
  }

  public Tag getParent ()
  {
    return this.parent;
  }
}
Deskryptor biblioteki znaczników (przywitania.tld)
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//
        Sun Microsystems, Inc.//
        DTD JSP Tag Library 1.1//EN"
        "http://java.sun.com/j2ee/dtds/
        web-jsptaglibrary_1_1.dtd">

<taglib>
  <tlibversion>1.0</tlibversion>
  <jspversion>1.1</jspversion<
  <shortname>first</shortname>

  <tag>
    <name>hello</name>
    <tagclass>tags.HelloTag</tagclass>
    <bodycontent>empty</bodycontent>
    <attribute>
      <name>jezyk</name>
      <required>true</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
  </tag>         
</taglib>
Wpis w deskryptorze serwisu
<taglib>
  <taglib-uri>przywitania.tld</taglib-uri>
  <taglib-location>/WEB-INF/jsp/przywitania.tld</taglib-location>
</taglib>

Znacznik obsługujący ciało akcji

Znacznik ten pozwala na wielokrotne przetwarzanie ciała akcji, które dodatkowo można modyfikować. Przepływ sterowania wygląda tak samo, jak w przypadku prostego znacznika, z tym wyjątkiem, że po wykonaniu ciała akcji wywoływana jest metoda doAfterBody(), która pozwala na manipulację tymże ciałem. Dzięki niej możemy także zmusić kontener do kolejnego przetworzenia ciała akcji.

Przykład: Manipulacja ciałem akcji

Klasa obsługująca znacznik:

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class MaleLiteryTag extends BodyTagSupport {

   public int doAfterBody() throws JspException {
      try {
         BodyContent bc = getBodyContent();         
         String body = bc.getString();
         JspWriter out = bc.getEnclosingWriter();
         if (body != null) {
            out.print(body.toLowerCase());
         }
      } catch(IOException e) {
         throw new JspException("Wystapil wyjatek IOException");
      }
      return SKIP_BODY;
   }
}

Strona JSP korzystająca ze znacznika:

<%@ taglib uri="taglib.tld" prefix="moje" %>
<html>
<head>
<title>Manipulacja</title>
</head>

<body>

<moje:malelitery>
Witamy w swiecie technologii JSP.
</moje:malelitery>

</body>
</html>

Wpis w deskryptorze biblioteki znaczników:

<tag>
  <name>malelitery</name>
  <tagclass>MaleLiteryTag</tagclass>
  <bodycontent>JSP</bodycontent>
</tag>
Wynik działania

Przykład: Wielokrotne przetwarzanie ciała akcji

Klasa obsługująca znacznik:

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class IterowanieTag extends BodyTagSupport {

   int ile = 0;

   BodyContent bodyContent;

   public void setIle(int ile) {
      this.ile = ile;
   }

   public int doStartTag() throws JspException {
      if (ile > 0) {
        return EVAL_BODY_TAG;
      } else {
         return SKIP_BODY;
      }
   }

   public void setBodyContent(BodyContent bodyContent) {
      this.bodyContent = bodyContent;
   }

   public int doAfterBody() throws JspException {
      if (ile >1) {
        ile--;
        return EVAL_BODY_TAG;
      } else {
         return SKIP_BODY;
      }
   }

   public int doEndTag() throws JspException {
      try {
         if (bodyContent != null) {
           bodyContent.writeOut(bodyContent.getEnclosingWriter());
         }
      } catch (IOException e) {
        throw new JspException("Wystapil wyjatek IOException");
      }
      return EVAL_PAGE;
   }
}

Strona JSP korzystająca ze znacznika:

<%@ taglib uri="taglib.tld" prefix="moje" %>
<html>
<head>
<title>Iterowanie</title>
</head>

<body>

<moje:iterowanie ile="3">
Witamy w swiecie technologii JSP.<br>
</moje:iterowanie>

</body>
</html>

Wpis w deskryptorze biblioteki znaczników:

<tag>
  <name>iterowanie</name>
  <tagclass>IterowanieTag</tagclass>
  <bodycontent>JSP</bodycontent>
  <attribute>
     <name>ile</name>
     <required>true</required>
     <rtexprvalue>true</rtexprvalue>
  </attribute>
</tag>
Wynik działania

Przykład: Zagnieżdżone znaczniki

Klasa obsługująca znacznik:

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.Writer;
import java.io.IOException;
import java.util.ArrayList;

public class TerminarzTag extends BodyTagSupport 
{
  String terminy[];
 
  public void dodajTermin (int dzien, String opis) {
    if (opis != null)
     terminy[dzien-1] = opis;
  }

  public int doStartTag () throws JspException {
    terminy = new String[31];
    return EVAL_BODY_TAG;
  }

  public int doEndTag () throws JspException {
    for (int i = 0; i < 31; i++) {
      String opis = terminy[i];
      if (opis != null) {
        JspWriter out = pageContext.getOut();
        try {
          out.println("<h2>" + (i+1) + "</h2>");
          out.println(opis);
          out.newLine ();
        } catch (IOException e) {
          throw new JspException ();
        }
      }
    }    
    return super.doEndTag ();
  }
}


import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.Writer;
import java.io.IOException;
import java.util.ArrayList;

public class TerminTag extends BodyTagSupport
{
  int dzien;

  public void setDzien (int d) {
    this.dzien = d;
  }

  public int doStartTag () throws JspException {
    return EVAL_BODY_TAG;
  }

  public int doEndTag () throws JspException {
    TerminarzTag trm = (TerminarzTag)getParent ();
    BodyContent body = getBodyContent ();    
    String s = body.getString ();
    if (dzien != 0 && s != null) 
      trm.dodajTermin (dzien, s);
    return super.doEndTag ();
  }
}

Strona JSP korzystająca ze znacznika:

<%@ page contentType="text/html; charset=Windows-1250" %>

<%@ taglib prefix="moje" uri="/WEB-INF/terminlib.tld" %>

Terminarz na nadchodzący miesiąc

<moje:terminarz>
  <moje:termin dzien="2">
    Spotkanie z ministrem łšcznoœci.
  </moje:termin>
  <moje:termin dzien="1">
    Spotkanie z prezydentem.
  </moje:termin>
</moje:terminarz>

Wpis w deskryptorze biblioteki znaczników:

  <tag>
    <name>terminarz</name>
    <tagclass>TerminarzTag</tagclass>
    <bodycontent>JSP</bodycontent>
    <info>terminarz</info>
  </tag>
  <tag>
    <name>termin</name>
    <tagclass>TerminTag</tagclass>
    <bodycontent>JSP</bodycontent>
    <info>termin</info>
    <attribute>
      <name>dzien</name>
      <required>true</required>
      <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>
Wynik działania