Gwiazdka nieaktywnaGwiazdka nieaktywnaGwiazdka nieaktywnaGwiazdka nieaktywnaGwiazdka nieaktywna
 

W artykule przedstawiono pełny moduł odpowiedzialny za dodawanie nowych kategorii. Wykorzystano framework RichFaces (AJAX), JavaServer Faces i bazę danych MySQL. Mechanizm opisany w przykładzie można zastosować w systemach pisanych również w innych językach jak PHP, Python, C#. Na samym końcu artykułu można zapoznać się z wybranymi książkami, artykułami umożliwiającymi konfigurację środowiska pracy w celu prawidłowego uruchomienia przykładu. Obecnie projektowanie stron w języku Java jest wspierane przez firmę Google za pomocą „Google App Engine” – chmura obliczeniowa. W części „polecane książki” umieszczono również linki do publikacji wspierających wspomnianą technologię w języku polskim.

Formularz korzysta z tabel sekcje i kategorie zaprezentowanych na rysunku 1.

 

Prezentacja relacji pomiędzy tabelami w bazie danych.

Rysunek 1. Prezentacja relacji pomiędzy tabelami w bazie danych

 

Jednym z wielu modułów zaprojektowanych dla systemu „Piksel-Net” jest moduł odpowiedzialny za „Dodawanie nowej kategorii”. Został zaimplementowany przy wykorzystaniu technologii AJAX wbudowanej w znaczniki frameworka RichFaces. Opisywany formularz jest zaprezentowany na rysunku 2., a kod formularza przedstawiono na listingu 1. Formularz zawiera trzy pola wymagane: wybierz sekcję, nazwa kategorii, publikacja.

 

Dodawanie nowej kategorii w systemie „Piksel-Net”.
Rys. 2. Dodawanie nowej kategorii w systemie „Piksel-Net"

 

Wypełnianie formularza należy zacząć od wybrania z listy rozwijanej nazwy sekcji. Dzięki zastosowaniu w znaczniku h:selectOneMenu atrybutu required z parametrem „true”, wybór z listy nazwy sekcji jest obowiązkowy. W przeciwnym razie zostanie wyświetlony komunikat błędu. W ciele znacznika h:selectOneMenu został umieszczony drugi znacznik f:selectItems odpowiedzialny za wyświetlenie wszystkich sekcji pobranych z bazy danych, za pomocą metody getSectionItems() (Listing 2) przypisanej do atrybutu value. Po wybraniu sekcji wprowadzamy w pole tekstowe nazwę kategorii. Automatycznie po przejściu do następnego pola AJAX sprawdza, czy podana nazwa kategorii już nie istnieje w wybranej sekcji. Jeśli nazwa kategorii zostanie znaleziona program wygeneruje komunikat błędu. Błąd również zostanie wygenerowany, kiedy pole nie zostanie wypełnione. Do zrealizowania opisanej funkcjonalności pola tekstowego należało w ciele znacznika h:inputText zastosować znaczniki f:validator, f:attribute, rich:ajaxValidator.

Znacznik f:attribute tworzy identyfikator „sectionId”, któremu przypisywany jest klucz główny z wybranej sekcji z listy rozwijanej. Znacznik rich:ajaxValidator włącza język JavaScript. Najważniejszy znacznik to f:validator do którego programista podpina klasę odpowiedzialną za weryfikację pola tekstowego. Klasę podłączono w pliku konfiguracyjnym faces-config.xml w elemencie validator (Listing 3). Klasę odpowiedzialną za weryfikację nazwy kategorii przedstawiono na listingu 4.

Znacznikowi h:selectOneRadio przypisano ułożenie w poziomie za pomocą atrybutu layout i parametru „lineDirection”. W ciele umieszczono znacznik f:selectItems za pomocą którego uruchomiono metodę getPublicationItems() zawierającą listę odpowiedzialną za wygenerowanie odpowiedniej ilości przycisków typu „radio” (Listing 6). Jedna opcja musi zostać zaznaczona, inaczej zostanie wyświetlony komunikat o błędzie. Jedynym polem opcjonalnym w formularzu jest opis kategorii, zastosowano znacznik rich:editor. Po zatwierdzeniu prawidłowo wypełnionego formularza kategoria zostanie dodana do systemu. Istnieje również możliwość anulowania wprowadzonych danych do formularza dzięki przyciskowi „Anuluj” (Tabela 1.).

 

Tabela 1. Opis funkcji przycisków w formularzu "Dodaj kategorię"
Przycisk Funkcja przycisku
Zapisz Zapisanie nowej kategorii w bazie danych za pomocą metody addCategoryArticle() przedstawionej na listingu 8, przypisanej do znacznika h:commandButton (Listing 1).
Anuluj Anulowanie wprowadzonych danych do formularza i przejście do strony wyświetlającej listę wszystkich dodanych kategorii. Metoda cancelCategoryArticle() jest odpowiedzialna za opisywaną funkcjonalność i została przedstawiona na listingu 8. Do opuszczenia formularza w trybie natychmiastowym należy zastosować w znaczniku h:commandButton atrybut immediate z parametrem „true” (Listing 1).

 

Pliki potrzebne w formularzu „Dodawanie nowej kategorii” to:

  • add_category_article.jsp – odpowiada za wyświetlenie formularza (Listing 1);
  • DataBase.java – zawiera dwie metody, z których korzysta formularz: getSectionItems() i getConnection() (Listing 2);
  • faces-config.xml – plik konfiguracyjny aplikacji (Listing 3);
  • CategoryValidator.java – klasa odpowiedzialna za weryfikację pola tekstowego „nazwa kategorii” (Listing 4);
  • CategoryArticleFactory.java – klasa zawiera metody operujące na bazie danych przeznaczone tylko dla modułu kategorie (Listing 5);
  • DataDefault.java – klasa zawiera listę przechowującą nazwy przycisków (Listing 6);
  • message.properties – plik komunikatów, „znaki Unicode spoza zbioru pierwszych 127 znaków są kodowane za pomocą sekwencji specjalnej \uxxx (Listing 7). Eclipse automatycznie wykrywa m. in. ą, ę i koduje w podanym formacie;
  • CategoryArticle.java – komponent udostępnia właściwości i zdarzenia potrzebne do prawidłowego działania formularza „Dodaj nową kategorię” (Listing 8);
  • Messages.java – klasa umożliwia wyświetlanie komunikatów na stronie internetowej za pośrednictwem odpowiednich znaczników. Klasa została pobrana z książki „JavaServer Faces. Wydanie II”, rozdział 6 „Konwersja i weryfikacja poprawności danych”.

Rysunek 3 przedstawia ułożenie plików po wdrożeniu programu „Piksel-Net” na serwerze potrzebnych do prawidłowego działania formularza „Dodawanie nowej kategorii”. Diagram układu katalogów w projekcie został zrealizowany za pomocą programu „Microsoft Visio Professional 2010”.

 

Ułożenie plików po wdrożeniu aplikacji dla formularza - Dodaj nową kategorię.
Rys. 3. Ułożenie plików po wdrożeniu aplikacji dla formularza „Dodaj nową kategorię"

 

Listing 1. Fragment kodu strony admin/templates/add_category_article.jsp


<h:form id="add_category"><rich:panel style="padding: 0px 0px 20px 0px; margin:0px; float:left; width: 100%;">
<rich:toolBar height="30px">
<rich:toolBarGroup location="left">
<h:outputText style="padding:0px; margin:0px; font-size: 11pt;" value="#{msgs.toolBar_add_category}" />
</rich:toolBarGroup>
<rich:toolBarGroup location="right">
<h:commandButton action="#{category_article.addCategoryArticle}" value="#{msgs.button_insert}" />
<h:commandButton action="#{category_article.cancelCategoryArticle}" value="#{msgs.button_cancel}" immediate="true" />
</rich:toolBarGroup>
</rich:toolBar>
<p />
<rich:panel style="width: 100%; height: 300px;">
<h:panelGrid columns="3">
<h:outputLabel value="#{msgs.categoryLabel_section}" />
<!-- Program pobiera z bazy danych wszystkie sekcje, które są wyświetlane w postaci listy rozwijanej. -->
<h:selectOneMenu value="#{category_article.sectionID}" id="section" required="true" validatorMessage="#{msgs.selectSection_errorMessage}">
<f:selectItems value="#{category_article.sectionItems}" />
<f:validateLongRange minimum="1" />
<rich:ajaxValidator event="onblur" />
</h:selectOneMenu>
<rich:message for="section" styleClass="errorMessage" />
<!-- Fragment kodu odpowiedzialny za podanie nazwy kategorii -->
<h:outputLabel value="#{msgs.categoryLabel_category}" />
<h:inputText value="#{category_article.title}" id="category" required="true" size="50" maxlength="200" requiredMessage="#{msgs.category_errorMessage}">
<f:validator validatorId="pl.wiedzanaplus.validator.CategoryValidator" />
<f:attribute name="sectionId" value="add_category:section" />
<rich:ajaxValidator event="onblur" />
</h:inputText>
<rich:message for="category" styleClass="errorMessage" />
<h:outputLabel value="#{msgs.publication}" />
<h:selectOneRadio id="publication" layout="lineDirection" value="#{category_article.publication}" required="true" requiredMessage="#{msgs.selectOneRadio_errorMessage}">
<f:selectItems value="#{data_default.publicationItems}" />
<rich:ajaxValidator event="onblur" />
</h:selectOneRadio>
<rich:message for="publication" styleClass="errorMessage" />
</h:panelGrid>
<h:panelGrid columns="1">
<h:outputLabel value="#{msgs.category_description}" />
<rich:editor height="100" width="950" value="#{category_article.description}">
</rich:editor>
</h:panelGrid>

 

Listing 2. Fragment klasy pl/wiedzanaplus/database/DataBase.java


protected List<SelectItem> sectionItems = new ArrayList<SelectItem>();
// Metoda odpowiedzialna za pobranie z bazy danych wszystkich sekcji i zapisanie
// wyniku w formie listy.
public List<SelectItem> getSectionItems() throws SQLException {
// Tworzymy instrukcję.
Statement stmt = conn.createStatement();
// Polecenie pobierające wszystkie sekcje z bazy danych.
String query = "SELECT id_sekcja, nazwa_sekcji FROM sekcje ORDER BY kolejnosc ASC";
// Wykonuje zapytanie przygotowane pod zmienną query i zwraca obiekt ResultSet.
ResultSet result = stmt.executeQuery(query);
// Czyścimy listę sectionItems
sectionItems.clear();
// Dodajemy do pustej listy nowe wartości pobranie z bazy danych.
sectionItems.add(new SelectItem("0", "Wybierz sekcję: "));
while (result.next()) {
int id = result.getInt(1);
String section = result.getString(2);
sectionItems.add(new SelectItem(id, section));
} // koniec while.
return sectionItems;
} // Koniec metody pobierającej sekcje z bazy danych.

// Metoda odpowiedzialna za nawiązanie połączenia z bazą danych.
protected static Connection getConnection() throws SQLException, IOException, ClassNotFoundException {
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/praca_mgr";
String username = "root";
String password = "piotrklimek";
return DriverManager.getConnection(url, username, password);
} // Koniec metody odpowiedzialnej za połączenie z bazą danych.

 

Listing 3. Fragment pliku konfiguracyjnego WEB-INF/faces-config.xml


<managed-bean>
<managed-bean-name>category_article</managed-bean-name>
<managed-bean-class>pl.wiedzanaplus.CategoryArticle</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<managed-bean>
<managed-bean-name>data_default</managed-bean-name>
<managed-bean-class>pl.wiedzanaplus.DataDefault</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<validator>
<validator-id>pl.wiedzanaplus.validator.CategoryValidator</validator-id>
<validator-class>pl.wiedzanaplus.validator.CategoryValidator</validator-class>
</validator>

<navigation-rule>
<from-view-id>/admin/add_category_article.jsp</from-view-id>
<navigation-case>
<from-outcome>cancel_category_article</from-outcome>
<to-view-id>/admin/category_article.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>insert_category_article</from-outcome>
<to-view-id>/admin/category_article.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>

<application>
<resource-bundle>
<base-name>pl.wiedzanaplus.messages</base-name>
<var>msgs</var>
</resource-bundle>

</application>

 

Listing 4. Kod klasy pl/wiedzanaplus/validator/CategoryValidator.java

package pl.wiedzanaplus.validator;
import java.io.IOException;
import java.sql.SQLException;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import pl.wiedzanaplus.database.CategoryArticleFactory;
/**
* Klasa odpowiada za sprawdzenie, czy wprowadzona kategoria do formularza
* istnieje w wybranej sekcji. Jeśli tak przekazuje komunikat błędu do formularza.
*/

public class CategoryValidator extends CategoryArticleFactory implements Validator {
public CategoryValidator() throws SQLException, IOException, ClassNotFoundException {}
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
// Kod pobiera wartość atrybutu sectionId z formularza add_category_article.jsp
String sectionId = (String) component.getAttributes().get("sectionId");
UIInput sectionInput = (UIInput) context.getViewRoot().findComponent(sectionId);
Integer section = (Integer) sectionInput.getValue();
// Metoda trim() – odpowiada za usunięcie znaków odstępu na początku i końcu
// nowego obiektu typu String.
String category = ((String) value).trim();
Boolean result = null;
try {
result = getResultCategory(category, section);
// Instrukcja warunkowa zostanie wykonana jeśli nazwa kategorii zostanie
// znaleziona w bazie danych.

if (result == false) {
// Metoda odpowiedzialna za przekazanie komunikatu błędu do formularza z pliku
// pl/wiedzanaplus/messages.properties

FacesMessage message = pl.wiedzanaplus.messages.Messages.getMessage("pl.wiedzanaplus.messages", "categoryExist_errorMessage", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
} catch (SQLException e) {
e.printStackTrace();
} } // Koniec metody.
} // Koniec klasy.

 

Listing 5. Fragment klasy pl/wiedzanaplus/database/CategoryArticleFactory.java


private PreparedStatement addCategoryArticleStmt;

// Metoda odpowiedzialna za dodanie nowej kategorii do bazy danych.
public void addCategoryArticleSQL(int sectionID, String title, String publication, String description) throws SQLException {
// Tworzymy instrukcję z wcześniej przygotowanego polecenia.
addCategoryArticleStmt = conn.prepareStatement(SQL_INSERT_CATEGORY);
addCategoryArticleStmt.setInt(1, sectionID);
addCategoryArticleStmt.setString(2, title);
addCategoryArticleStmt.setString(3, description);
if (publication.equals("yes")) {
java.sql.Timestamp sqlDate = new java.sql.Timestamp(new java.util.Date().getTime());
addCategoryArticleStmt.setTimestamp(4, sqlDate);
} else {
addCategoryArticleStmt.setDate(4, null);
}
// Zapisujemy polecenie w bazie danych.
addCategoryArticleStmt.executeUpdate();
// Część zapytania sql odpowiedzialnego za ustawienie kolejności wyświetlania
// kategorii. Przy tworzeniu kategorii kolejność jest przypisywana automatycznie,
// numer klucza głównego kategorii.

String query = "UPDATE kategorie SET kolejnosc = id_kategoria WHERE id_kategoria = (SELECT last_insert_id() AS last_id)";
PreparedStatement stat = conn.prepareStatement(query);
stat.executeUpdate();
} // Koniec metody odpowiedzialnej za dodanie nowej kategorii.


// Metoda sprawdza, czy kategoria istnieje w wybranej sekcji,
// jeśli tak zwraca false.

protected boolean getResultCategory(String CategoryName, Integer sectionID) throws SQLException {
// Zapytanie SQL sprawdza, czy kategoria istnieje w wybranej sekcji.
String query = "SELECT k.id_kategoria FROM `kategorie` k, `sekcje` s WHERE s.id_sekcja = k.id_sekcja AND k.nazwa_kategorii = '"+CategoryName+"' AND s.id_sekcja = '"+sectionID+"'";
// Aby wykonać instrukcję musimy utworzyć obiekt Statement.
Statement stmt = conn.createStatement();
// Wykonujemy zapytanie języka SQL.
ResultSet result = stmt.executeQuery(query);
try {
// Odpowiada za przesunięcie o jeden element w zbiorze wyników zapytania.
result.next();
// Zwraca wartość kolumny rekordu o podanym indeksie.
int id = result.getInt(1);
if (id > 0)
return false;
} catch (SQLException ex) {
System.err.println("Wyjątek zadziała, kiedy nazwa kategorii nie istnieje w wybranej sekcji: " + ex.getMessage());
}
result.close();
stmt.close();
return true;
} // Koniec metody sprawdzającej, czy kategoria już istnieje w bazie danych.


// Polecenie SQL odpowiedzialne za dodanie nowej kategorii dla działu artykuły.
private static final String SQL_INSERT_CATEGORY = "INSERT INTO kategorie (`id_sekcja`,`nazwa_kategorii`, `opis_kategorii`, `data_utworzenia`, `data_opublikowania`) VALUES (?, ?, ?, CURRENT_TIMESTAMP,?);";

 

Listing 6. Fragment klasy pl/wiedzanaplus/DataDefault.java


public SelectItem[] getPublicationItems() { return publicationItems; }
private static SelectItem[] publicationItems = {
new SelectItem("yes", "Tak"),
new SelectItem("no", "Nie")
};

 

Listing 7. Fragment pliku pl/wiedzanaplus/messages.properties


toolBar_add_category = Dodaj nowa kategori\u0119 dla artyku\u0142\u00F3w
button_insert = Zapisz
button_cancel = Anuluj
categoryLabel_section = Wybierz sekcj\u0119\:
selectSection_errorMessage = Wybierz sekcj\u0119 dla kategorii.
categoryLabel_category = Nazwa kategorii:
selectCategory_errorMessage = Wybierz kategori\u0119 dla wybranej sekcji.
categoryExist_errorMessage = Podana nazwa kategorii ju\u017C istnieje w wybranej sekcji.
category_dataTable_publication=Publikacja
selectOneRadio_errorMessage = Jedna z odpowiedzi musi zosta\u0107 zaznaczona
category_description = Opis: (Pole opcjonalne)

 

Listing 8. Fragment klasy pl.wiedzanaplus.CategoryArticle.java


// Zmienna przechowuje identyfikator sekcji.
private int sectionID;
// Zmienna przechowuje nazwę kategorii.
private String title = null;

// Zmienna przechowuje informację czy kategoria została opublikowana.
private String publication = null;

// Zmienna przechowuje opis dla kategorii.
private String description = null;

// Metody dostępowe.
public int getSectionID() { return sectionID; }
public void setSectionID(int sectionID) { this.sectionID = sectionID; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getPublication() { return publication; }
public void setPublication(String publication) { this.publication = publication;}
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description;}

// Metoda odpowiedzialna za dodanie nowej kategorii do działu artykuły.
public String addCategoryArticle() throws SQLException {
int sectionID = this.sectionID;
String title = this.title.trim();
String description = this.description.trim();
if (description.length() == 0)
description = null;
// Metoda odpowiedzialna za wykonanie polecenia INSERT na bazie danych.
addCategoryArticleSQL(sectionID, title, this.publication, description);
this.sectionID = 0;
this.message_insert_category = 1;
return "insert_category_article";
} // Koniec metody.

// Metoda odpowiedzialna za anulowanie wprowadzonych danych w formularzu i powrót
// do głównej strony wyświetlającej wszystkie kategorie.

public String cancelCategoryArticle() {
this.sectionID = 0;
this.categoryID = 0;
this.title = null;
this.description = null;
this.added = null;
this.activation = null;
this.publication = null;
return "cancel_category_article";
}

 

Lista książek, do których odwołano się w artykule:

Warto przeczytać również:

Wybrane książki:

Strony internetowe:

Książki Helion