10. gyakorlat¶
JSP folytatás¶
Fragment-ek¶
A nem teljes értékű, csak töredék JSP oldalakat JSP fragment-eknek nevezzük.
Feladat
A person-administrator alkalmazásunk jsp/servlet megvalósításában a menu
és a közös header
elemek nem önálló JSP oldalak.
Viszont a böngészőből direktben elérhetőek ezek az oldalak. Javítsuk ki, hogy erre ne legyen lehetőség!
Arról már volt szó, hogy amit nem akarunk, hogy a felhasználó elérjen, azt a WEB-INF könyvtárba kell pakolni.
Tegyünk is így: mozgassuk a teljes common mappát a WEB-INF alá!
Ezután állítsuk át a többi oldalon szereplő elérési útvonalakat (../WEB-INF/common/... kell legyen)!
Felhasználók kezelése¶
Feladat
Adjunk hozzá az alkalmazásunkhoz valódi felhasználó kezelést! Jelen esetben a login alkalmával csak beletesszük a session-be és kész. Legyen lehetőségünk úgy belépni, hogy ténylegesen ellenőrizzük az alkalmazásban, hogy helyes felhasználó jelszó párost adott-e meg a felhasználó. A jelszavakat titkosítsuk bcrypt algoritmussal! Ehhez konstruáljuk egy felhasználó táblát, illetve az adott felhasználó külön tudja menedzselni a saját kontaktjait, tehát minden felhasználó csak az általa hozzáadott embereket látja. Legyen egy regisztráció menüpont is a login-ról elérhető!
-
lépés: Dolgozzuk át az adatbázist! Az valahogy így kell, hogy kinézzen az átalakítás után:
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 41 42 43
create table CONTACT ( id INTEGER not null constraint CONTACT_pk primary key autoincrement, name text not null, email text not null, address text, dateOfBirth text not null, company text, position text, user_id int ); create unique index CONTACT_email_uindex on CONTACT (email); create table PHONE ( id integer not null constraint PHONE_pk primary key autoincrement, number text not null, phoneType integer not null, contact_id int references CONTACT ); create table USER ( id INTEGER not null constraint USER_pk primary key autoincrement, username text not null, password text not null, email text not null ); create unique index USER_email_uindex on USER (email); create unique index USER_username_uindex on USER (username);
-
lépés:
User
model hozzáadása acontacts-core
-hoz ésContact
model aktualizálása.Contact.java
1 2 3 4 5 6
public class Contact{ // other properties private ObjectProperty<User> user = new SimpleObjectProperty<>(this, "user"); // getters, setters }
User.java
1 2 3 4 5 6 7 8
public class User { private IntegerProperty id = new SimpleIntegerProperty(); private StringProperty username = new SimpleStringProperty(); private StringProperty password = new SimpleStringProperty(); private StringProperty email = new SimpleStringProperty(); // getters, setters }
-
lépés:
UserDAO
ésUserDAOImpl
elkészítése1 2 3 4 5 6
public interface UserDAO { User getUserById(int id); void addUser(User user); User login(String username, String password); }
A
UserDAO
-ban semmilyen különleges nincs. Lehetőséget ad lekérdezésre, hozzáadásra (regisztráció), illetve bejelentkezésre.A konkrét megvalósítás:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
public class UserDAOImpl implements UserDAO{ private static UserDAOImpl instance; private static final String DB_CONN_STR = ContactConfiguration.getValue("db.url"); public static UserDAOImpl getInstance() { if (instance == null) { try { Class.forName("org.sqlite.JDBC"); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } instance = new UserDAOImpl(); } return instance; } private UserDAOImpl() { } @Override public User getUserById(int id) { try (Connection conn = DriverManager.getConnection(DB_CONN_STR); PreparedStatement pst = conn.prepareStatement("SELECT * FROM USER WHERE id = ?") ) { pst.setInt(1, id); ResultSet rs = pst.executeQuery(); if (rs.next()) { User u = new User(); u.setId(rs.getInt(1)); u.setUsername(rs.getString(2)); u.setEmail(rs.getString(3)); u.setPassword(rs.getString(4)); return u; } } catch (SQLException e) { e.printStackTrace(); } return null; } @Override public void addUser(User user) { try (Connection conn = DriverManager.getConnection(DB_CONN_STR); PreparedStatement pst = conn.prepareStatement("INSERT INTO USER (username, password, email) VALUES (?,?,?)") ) { String newPwd = BCrypt.withDefaults().hashToString(12, user.getPassword().toCharArray()); user.setPassword(newPwd); pst.setString(1, user.getUsername()); pst.setString(2, user.getPassword()); pst.setString(3, user.getEmail()); pst.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } @Override public User login(String username, String password) { try (Connection conn = DriverManager.getConnection(DB_CONN_STR); PreparedStatement pst = conn.prepareStatement("SELECT * FROM USER WHERE username = ?") ) { pst.setString(1, username); ResultSet rs = pst.executeQuery(); if (rs.next()) { String dbPass = rs.getString("password"); BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), dbPass); if(result.verified){ User user = new User(); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); user.setEmail(rs.getString("email")); user.setId(rs.getInt("id")); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; } }
Érdemes megfigyelni, hogy a
UserDAOImpl
példányosítását a Singleton tervezési minta mentén készítettük el, így agetInstance
hívással kérhetünk majd egy ilyen példányt (private konstruktor miatt kívülről nem példányosítható). A megismert mintát alkalmazhatjuk a többi DAO osztályon is.Egy további érdekes rész, amikor az
addUser
metódusban a jelszó hash-elt változatát állítjuk elő, tesszük ezt azért, mert nem szeretnénk a natúr szöveges jelszavunkat menteni a DB-be. Ehhez a bcrypt algoritmust használjuk, mely manapság egy javasolt hash-elési algoritmus. A JDK nem tartalmaz erre a célre beépített osztályokat, így egy külön függőséget használnuk.1 2 3 4 5
<dependency> <groupId>at.favre.lib</groupId> <artifactId>bcrypt</artifactId> <version>0.9.0</version> </dependency>
Jelen esetben a az alapbeállításokat használjuk (
withDefaults
), illetve így állítjuk elő a hash string-et a password alapján. További konfigurációkat is megadhatunk, melyről bővebben a lib github oldalán találunk leírást. Miután megvan a hash, ezt beállítjuk a user password-jének és így ezt mentjük az adatbázisba. Amikor bejelentkezünk, akkor pedig a megadott jelszót veti össze a library az adatbázisban szereplővel:BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), dbPass);
-
lépés: Contact DAO updatelése
ContactDAO.java
1 2 3 4 5 6 7 8 9
public interface ContactDAO { List<Contact> findAll(); List<Contact> findAll(User user); Contact save(Contact contact); void delete(Contact contact); }
A
ContactDAO
úgy változik meg, hogy afindAll
paraméterében egyUser
-t is átadunk. A mentéskor szeretnénk jelölni, hogy az újönnan létrehozott kontakt melyik felhasználóhoz tartozik (ezt most már aContact
modellben eltároljuk), illetve, amikor lekérdezünk, akkor egy felhasználóhoz tartozó kontaktot listáját adjuk vissza. Mivel az asztali alkalmazásunkban nem kezeltünk felhasználókat és nem is fogunk már visszamenni arra a pontra, hogy ezt implementáljunk, ezért az eredeti függvénylenyomatokat meghagyjuk és biztosítunk olyan lehetőséget, melyben aUser
is megadható. Ennek tükrében aContactDAOImpl
a következőképpen néz ki: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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
public class ContactDAOImpl implements ContactDAO{ private static final String SELECT_ALL_CONTACTS = "SELECT * FROM CONTACT"; private static final String SELECT_ALL_CONTACTS_BY_USER = "SELECT * FROM CONTACT WHERE user_id = ?"; private static final String INSERT_CONTACT = "INSERT INTO CONTACT (name, email, address, dateOfBirth, company, position, user_id) VALUES (?,?,?,?,?,?,?)"; private static final String UPDATE_CONTACT = "UPDATE CONTACT SET name=?, email = ?, address = ?, dateOfBirth=?, company=?, position = ? WHERE id=?"; private static final String DELETE_CONTACT = "DELETE FROM CONTACT WHERE id = ?"; private String connectionURL; private PhoneDAO phoneDAO = new PhoneDAOImpl(); private UserDAO userDAO = UserDAOImpl.getInstance(); public ContactDAOImpl(){ try { Class.forName("org.sqlite.JDBC"); } catch (ClassNotFoundException e) { e.printStackTrace(); } connectionURL = ContactConfiguration.getValue("db.url"); } @Override public List<Contact> findAll() { return findAll(null); } @Override public List<Contact> findAll(User user) { List<Contact> result = new ArrayList<>(); try(Connection c = DriverManager.getConnection(connectionURL); ){ ResultSet rs; if(user == null){ Statement stmt = c.createStatement(); rs = stmt.executeQuery(SELECT_ALL_CONTACTS); } else{ PreparedStatement stmt = c.prepareStatement(SELECT_ALL_CONTACTS_BY_USER); stmt.setInt(1, user.getId()); rs = stmt.executeQuery(); } while(rs.next()){ Contact contact = new Contact(); contact.setId(rs.getInt("id")); contact.setName(rs.getString("name")); contact.setEmail(rs.getString("email")); contact.setAddress(rs.getString("address")); Date date = Date.valueOf(rs.getString("dateOfBirth")); contact.setDateOfBirth(date == null ? LocalDate.now() : date.toLocalDate()); contact.setCompany(rs.getString("company")); contact.setPosition(rs.getString("position")); contact.setPhones(phoneDAO.findAllByContactId(contact.getId())); contact.setUser(userDAO.getUserById(rs.getInt("user_id"))); result.add(contact); } } catch (SQLException throwables) { throwables.printStackTrace(); } return result; } @Override public Contact save(Contact contact) { try(Connection c = DriverManager.getConnection(connectionURL); PreparedStatement stmt = contact.getId() <= 0 ? c.prepareStatement(INSERT_CONTACT, Statement.RETURN_GENERATED_KEYS) : c.prepareStatement(UPDATE_CONTACT) ){ if(contact.getId() > 0){ // UPDATE stmt.setInt(7, contact.getId()); } else{ if(contact.getUser() != null){ stmt.setInt(7, contact.getUser().getId()); } } stmt.setString(1, contact.getName()); stmt.setString(2, contact.getEmail()); stmt.setString(3, contact.getAddress()); stmt.setString(4, contact.getDateOfBirth().toString()); stmt.setString(5, contact.getCompany()); stmt.setString(6, contact.getPosition()); int affectedRows = stmt.executeUpdate(); if(affectedRows == 0){ return null; } if(contact.getId() <= 0){ // INSERT ResultSet genKeys = stmt.getGeneratedKeys(); if(genKeys.next()){ contact.setId(genKeys.getInt(1)); } } } catch (SQLException throwables) { throwables.printStackTrace(); return null; } return contact; } // delete is unmodified }
-
lépés: login updatelése
A login oldalon alapvetően marad minden úgy, ahogy volt de egy linket hozzáadunk, hogy a regisztrálás oldalra át tudjunk navigálni abban az esetben ha még nem volt létrehozott fiókunk.
1
<span><a href="register.jsp">Register</a></span>
-
lépés: LoginController updatelése:
Ahhoz, hogy a bejentkezés az új DAO-val együttműködjön, a LoginController-ben is kell néhány módosítást eszközölnünk. Itt már felhasználjuk a DAO nyújtotta
login
metódust.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); UserDAO userDAO = UserDAOImpl.getInstance(); String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDAO.login(username, password); if( user == null){ response.sendRedirect("pages/login.jsp"); return; } request.getSession().setAttribute("currentUser", user); response.sendRedirect("pages/list-contact.jsp"); }
-
lépés: regisztrációs form
A regisztrációs form valahogy így nézhet ki:
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
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <jsp:include page="../WEB-INF/common/common-header.jsp"/> <title>Register</title> </head> <body> <div class="container"> <form action="../RegisterController" 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> <div class="form-group"> <label for="email">Email</label> <input required name="email" type="email" class="form-control" id="email" placeholder="Email"/> </div> <button id="submit" type="submit" class="btn btn-primary">Submit</button> </form> </div> </body> </html>
A regisztrációs formban beérjük a felhasználónevet, a jelszót és az email címet. A regisztrációs form az adatokat továbbküldi a
RegisterController
-nek, mely az adatrétegnek továbbküldi a regisztrációs kérelmet.Jelen esetben semmilyen jellegű ellenőrzés nincs, amivel a regisztráció sikerességét ellenőrizzük.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
@WebServlet("/RegisterController") public class RegisterController extends HttpServlet { UserDAO dao = UserDAOImpl.getInstance(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); User user = new User(); user.setUsername(req.getParameter("username")); user.setEmail(req.getParameter("email")); user.setPassword(req.getParameter("password")); dao.addUser(user); resp.sendRedirect("pages/login.jsp"); } }
-
lépés: Alakítsunk a hozzáadáson és a listázáson az újfajta DAO-nak megfelelően (kell egy user, akihez tartozó kontaktokat szeretnénk listázni, illetve akinek szeretnénk új kontaktot létrehozni).
Az egyik igen fontos pont, hogy a
list-contact.jsp
-ben behúzzuk aContactController
-t, melynek ilyen módon adoGet
metódusa fog lefutni, mely az alábbit teszi:1 2 3 4 5 6 7 8 9 10
... private ContactDAO dao = new ContactDAOImpl(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User currentUser = (User) req.getSession().getAttribute("currentUser"); List<Contact> all = dao.findAll(currentUser); req.setAttribute("contactList", all); } ...
A session alapján lekérjük az aktuális felhasználót és csak a hozzá tartozó kontaktokat adjuk vissza.
A hozzáadás form-ja nem változott, csak a common header rész miatt.
Elküldjük a
ContactController
-nek az összes szükséges paramétert. A doPost a következőképpen módosul:1 2 3 4 5 6 7 8 9 10 11 12
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); // setup params ... User currentUser = (User) request.getSession().getAttribute("currentUser"); c.setUser(currentUser); c = dao.save(c); ... // saving phones }
Fontos, hogy a request encoding-ját is UTF-8-ra állítsuk be, máskülönben az adatbázisba is rossz encoding-al kerül be az új kontakt (
request.setCharacterEncoding("utf-8");
). -
lépés: Lényegében kész vagyunk, de egy-két dolog még kimaradt, mint például a logout átalakítása.
Eddig a cookie-ban volt csak az aktuális user, most viszont a sessionben tároljuk, így ezt is át kell alakítanunk kicsit.
1 2 3 4 5
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getSession().removeAttribute("username"); response.sendRedirect("pages/login.jsp"); }
Szimplán töröljük a sessionből a
currentUser
attribútumot, illetve a login oldalra kalauzoljuk a felhasználót. -
lépés: Menü update:
A menüsáv jobb oldalán kiírjuk az aktuális felhasználó nevét, illetve az itteni lenyíló menüben biztosítjuk, hogy a felhasználó ki tudjon jelentkezni. A menüsáv szintén a cookie-t használta eddig, így ezt is át kell alakítani. Az idevágó kódrészlet:
1 2 3 4 5 6 7 8 9 10
<c:if test="${sessionScope.currentUser.username != null}"> <li class="nav-item dropdown ml-auto"> <a class='nav-link dropdown-toggle' href='#' id='navbarDropdownMenuLink' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'> ${sessionScope.currentUser.username} </a> <div class='dropdown-menu dropdown-menu-right' aria-labelledby='navbarDropdownMenuLink'> <a class='dropdown-item' href='../LogoutController'>Kijelentkezés</a> </div> </li> </c:if>
-
lépés: Filterezés
A login megoldásunk már egészen tűrhető, azonban jelen helyzetben, ha a valamelyik oldalra navigálunk direktben, akkor az oldalt megpróbálja betölteni a rendszer. A megfelelő működés az volna, hogy legalább a session-t ellenőrizzük, hogy be van-e jelentkezve a felhasználó. Amennyiben igen, akkor az adott oldalra engedjük, máskülönben visszhaírányítjuk a login oldalra. Mivel a filterezést minden lehetséges oldalra szeretnénk végrehajtani, így az a következőképpen néz ki:
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
@WebFilter("/*") public class AuthFilter implements Filter { private List<String> exclusions; @Override public void init(FilterConfig filterConfig) throws ServletException { this.exclusions = Arrays.asList(filterConfig.getServletContext().getInitParameter("login-filter-exclusion").split(",")); this.exclusions.replaceAll(String::trim); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String path = ((HttpServletRequest) request).getServletPath(); if (exclusions.stream().anyMatch(path::equals)) { chain.doFilter(request, response); return; } User currentUser = (User) ((HttpServletRequest) request).getSession().getAttribute("currentUser"); if (currentUser == null) { ((HttpServletResponse)response).sendRedirect(((HttpServletRequest) request).getContextPath() + "/pages/login.jsp"); } else{ chain.doFilter(request, response); } } }
A filterezésnél a
/*
-al mondjuk meg, hogy minden kérésre szeretnénk futtatni a filtert. Mivel van több oldal is, amin azonban nem szeretnénk, ha a filter lefutna, így ezeket valamilyen módon meg kell adnunk. Sajnos kivételeket nem tudunk megadni a filter url-patter résznél, ezért aweb.xml
-ben adjuk meg azokat az url-eket, melyekre nem szeretnénk filterezést végrehajtani. Még egy technikai problém, hogy egy param-hoz nem tudunk több értéket megadni, így vesszővel elválasztva adjuk ezt meg. A Filter-ben pedig a vesszők mentén feldaraboljuk a string-et (init-ben).1 2 3 4 5 6 7 8 9 10 11
<context-param> <param-name>login-filter-exclusion</param-name> <param-value> /index.jsp, /pages/login.jsp, /LoginController, /pages/register.jsp, /RegisterController, /css/style.css </param-value> </context-param>
A doFilter metódusban megvizsgáljuk, hogy az aktuális kérés url-je (context url nélkül) megegyezik-e a megadott lista bármely elemével. Amennyiben találunk ilyet, akkor továbbengedjük a kérést, máskülönben megvizsgáljuk, hogy be vagyunk-e jelentkezve. Ha a session-ben találunk bejelentkezett felhasználót, akkor továbbengedjük a kérést, máskülönben a login-ra irányítjuk.
Ez egy komplexebb feladat volt, de lépésenként szépen fel lehetett építeni az alkalmazás megfelelő működését.
A megoldás megtalálható a pub-on 01-contacts-user
mappában.
Profil hozzáadása¶
Feladat
Bővítsük az alkalmazást egy profil oldallal, ahol megadhatunk profilképet, leírást is. Az oldalon lehessen updatelni a felhasználó tulajdonságait (jelszót nem kell).
Elsőként bővítsük a User
osztályt a kép és a leírás opciókkal!
1 2 3 4 |
|
A UserDAO
-t módosítsuk úgy, hogy az az update
-re is képes legyen.
Ehhez az addUser
-t refaktoráljuk, hogy save
-nek hívják!
1 |
|
Az implementációt pedig a következőképpen módosítsuk:
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 41 42 43 44 |
|
Ahhoz, hogy megfelelően működjenek az adatbázis műveletek módosítanunk kell a DB sémát is:
1 2 3 4 5 6 7 8 9 10 11 |
|
A regisztrálási oldalon most nem változtatunk, így nézzük a profil oldalt!
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|
A fontosabb sorokat kiemeltük a könnyebbség végett.
Mivel szeretnénk egy olyan működést, melynek során biztosítjuk, hogy új kép kiválasztása esetén a profilképet egyből meg is jelenítjük, így ezt jQuery segítségével oldjuk meg, melyet az első kiemelt sor húz be.
A profil frissítésekor a UserController
-hez küldjük a kérést, ahol az aktuálisan bejelentkezett felhasználó adatait módosítjuk (kivéve a jelszót).
Először lássuk a profile.js
állományt:
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 |
|
Minden esetben, amikor változik a kiválasztott kép, akkor beolvassuk a képet egy FileReader
segítségével, majd ezt állítjuk be a profilkép src
attribútumának.
Amikor a form-ot submit-eljük, akkor egy rejtett input field-ben adjuk át a profilePic
nevű attribútumot a formData-ban, hogy ezt könnyen feldolgozhassuk a szerver oldalon.
Az elküldött információk feldolgozását a UserController
doPost
metódusa végzi:
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 |
|
Elsőként a session-ből kiszedjük az aktuális felhasználót, majd frissítjük az attribútumait és elmentjük az adatbázisban, majd frissítjük a sessionben lévő currentUser
objektumot.
Miután ezekkel megvagyunk, helyezzük el a menüben is a profil menüpontot:
1 2 3 4 5 6 |
|
Törlés és módosítás¶
Feladat
Bővítsük az alkalmazást törlés és módosítás elemekkel
Elsőként helyezzük el a törlés és módosítás ikonokat a listában!
Ezekhez az ikonokhoz font-awesome-t használunk, így azt a common-header.jsp
-ben be kell húznunk!
common-header.jsp
:
1 2 3 |
|
Ezután a list-contact.jsp
oldalon adjunk hozzá egy új oszlopot, melyben ezek az ikonok lesznek megjelenítve:
1 2 3 4 5 6 7 8 |
|
Kezdjünk az UpdateContactController
megvalósításával:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Maga az update nem egy bonyolult konstrukció.
A queryString-ben elküldött paraméter alapján lekérjük a megfelelő kontaktot, majd ezt a kérésben, mint attribútum elhelyezzük.
Ezek után a a kérést továbbítjuk a add-contact.jsp
oldalnak (mely ebben a formában lehet, hogy megérett egy átnevezésre, de most ezzel nem foglalkozunk).
Itt pedig ki fogjuk olvasni a kérésben elhelyezett attribútum összes értékét és ezzel töltjük fel a felületi vezérlőket.
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
|
A fenti kódrészletben kiemeltük a fontosabb változtatásokat.
Először is a forward miatt az add-contact.js
elérése nem biztos lesz megfelelő, így a pageContext.request.contextPath
URL-t használjuk, mely megadja a context útvonalát (pl.: http://localhost:8080/contacts_web_war
).
Ezután az előzetesen a request-ben elhelyezett contact
attribútumot kiolvassuk a useBean
segítségével, majd ezt fogjuk felhasználni az oldalon, hogy feltöltsük a field-ek értékeit.
A meglévő input field-ek mellett hozzáadunk egy rejtett mezőt az id
számára is, mivel err a feldolgozás során szükségünk lesz, hiszen frissíteni szeretnénk.
Ezen felül az összes field-hez megadjuk a value="${contact.property}
attribútumot, hogy ki is töltse az input-okat az aktuális értékekkel.
A telefonszámok esetében azonban összetettebb feladatunk van és némi átszervezést is igényel a feladat.
Amennyiben a kontakthoz tartoznak telefonszámok, akkor azokat ki kell írnunk, illetve mindegyik mellett a Delete
opciót adjuk meg, kivéve az utolsónál, ahol a New
opciót.
Az első c:if
, akkor fut le, amikor vannak telefonszámok.
Ebben az esetben végigiterálunk a már megadott telefonszámokon és mindegyikhez megadunk egy div
-et (row
class-al).
Kiírjuk magát a telefonszámot (40. sor) egy vezérlőben, majd egy lenyílóba belerakjuk az összes phoneType
opciót és közben figyeljük, hogy ha az adott phoneType
megegyezik azzal, ami ki volt választva, akkor rákerül a selected
attribútum is, azaz ő lesz kiválasztva a felületen.
A gombok megadásakor (54-61. sor) figyeljük, hogy az utolsó elemnél vagyunk-e.
Az utolsó elemnél a New
szöveg lesz a gombon, illetve a newRow(this)
eseménykezelőt adjuk meg.
Ellenkező esetben a Delete
szöveget alkalmazzuk és a deleteRow(this)
eseménykezelőt.
A newRow
és a deleteRow
függvényeket az add-contact.js
-ben adjuk meg, melyet szintén módosítanunk kellett a kontakt frissítés érdekében.
A 65-82. sorig tartó részben szerepel az, amikor nincs megadva telefonszám a kontakhoz, amely alapján csak egy üres beviteli sort adunk meg.
Ezután lássuk a módosított JavaScript részt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
A JS kód funkcionalitása lényegében megmaradt, csupán a kiszerveztük azokat két külön függvénybe.
A fentiek eddig a pontig teljesen jól működnek, azonban a kontakt frissítésekor egy új rekord fog megjelenni, mivel a ContactController
-ben is hozzá kell igazítanunk a működést a most megvalósított elemekhez.
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
A legfontosabb az id
kiolvasása, mely alapján lekérdezzük a meglévő kontaktot.
Később a save
hatására mentés vagy beillesztés is történhet, így ezt nem kell majd igazítanunk.
Ezután feldolgozzuk a telefonszámokat, azonban szükségünk lesz a meglévő id-kra, melyeket a phoneIds
-ban küldünk el, így ezt a jsp-ben is el kell helyeznünk egy hidden field-be:
1 |
|
Miután kiolvastuk a megfelelő telefonokat (Phone
objektumok létrehozása) elmentjük a kontaktot.
Ezen a ponton egy nagyobb refaktorálást is tegyünk meg, amelyet kicsit halogattunk.
Ennek eredményeképpen a DAO réteget átalakítottuk, hogy a egy kontakt mentése esetén a hozzá tartozó telefonszámokat is frissítse az adatelérési réteg.
A módosításokat ezen a ponton nem fogjuk kifejteni, a módosított DAO elérhető a pub-ban.
Az update mellett a delete-hez tartozó servlet a következőképpen néz ki:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Itt is érdemes megjegyezni, hogy a kontakt kitörlése az összes hozzátartozó telefonszámot is kitörli. További feladatként hozzáadhatunk egy megerősítést a törléshez, de ezen a ponton ezzel most nem foglalkozunk.