Im ersten Teil des Tutorials konnte man ja gut erkennen, dass Grails in einem Portlet lauffähig ist. Da das generierte WAR so an die 23 MB umfasst, sollte es alle relevanten Grails-Bibliotheken enthalten.
Beispiel
Nun ist es an der Zeit, eine kleine Anwendung zu erstellen. Dafür erstellen wir eine kleine Verwaltungssoftware für Fluglinien.
Plugins
Domainklassen
Zunächst sollen die folgenden drei Domainklassen verwaltet werden können: Flughafen, Flugzeuge und Flüge.
package de.ronnyfriedland class Airport { String name String code static constraints = { } }
package de.ronnyfriedland class Plane { String type static constraints = { } }
package de.ronnyfriedland import java.util.Date; class Flight { static belongsTo = [plane:de.ronnyfriedland.Plane, start:de.ronnyfriedland.Airport, destination:de.ronnyfriedland.Airport] String number Date date static constraints = { number(nullable: false, blank: false, maxSize: 250) date(nullable: false) plane(nullable: false) start(nullable: false) destination(nullable: false) } }
Service
So richtig Sinn macht es zwar für dieses kleine Beispiel nicht - aber die so können wir überprüfen, ob Services richtig aus der Portletklasse heraus aufgerufen werden können.
package de.ronnyfriedland class AirlineService { boolean transactional = true def listFlights() { return Flight.list() } def listAirports() { return Airport.list() } def listPlanes() { return Plane.list() } }
Portletklasse
Für diesen Teil erstellen wir eine neues Portlet SecondPortlet, welches den VIEW-Mode und den HELP-Mode unterstützen soll.
Im VIEW-Mode können neue Flüge über ein Formular hinzugefügt werden. Der HELP-Mode enthält eine Auflistung aller Flughäfen, Flugzeuge und Flüge.
import java.text.SimpleDateFormat; import javax.portlet.* import de.ronnyfriedland.AirlineService import de.ronnyfriedland.Airport import de.ronnyfriedland.Flight import de.ronnyfriedland.Plane class SecondPortlet { def title = 'Flight Administration' def description = ''' Description about the portlet goes here. ''' def displayName = 'Display Name' def supports = ['text/html':['view', 'help']] // Liferay server specific configurations def liferay_display_category = 'MyCategory' def airlineService def actionView = { if(params.save) { Flight.withTransaction { status -> Flight flight = new Flight() if(params.date) { SimpleDateFormat format = new SimpleDateFormat("M/d/y", Locale.ENGLISH); Date date = format.parse(params.date_value); flight.date = date } if(params.number) { flight.number = params.number } if(params.plane) { def plane = Plane.findById(params.plane) flight.plane = plane } if(params.start) { def start = Airport.findById(params.start) flight.start = start } if(params.destination) { def destination = Airport.findById(params.destination) flight.destination = destination } if(!flight.validate()) { flash.error = "Angaben ungültig. Flug konnte nicht gespeichert werden: " + flight.errors return } flight.save(flush:true) flash.message = "Flug erfolgerich gespeichert." return } } } def renderView = { def airportList = airlineService.listAirports() def planeList = airlineService.listPlanes() ['airportList':airportList, 'planeList':planeList] } def actionHelp = { portletResponse.setPortletMode(PortletMode.VIEW) } def renderHelp = { def airportList = airlineService.listAirports() def planeList = airlineService.listPlanes() def flightList = airlineService.listFlights() ['airportList':airportList, 'planeList':planeList, 'flightList':flightList] } }
Views
view.gsp
Dieser View soll das Formular enthalten, um neue Flüge speichern zu können. Jetzt kommt auch das calendar-Plugin zum Einsatz, um das Datum des Flugs über diesen JS-DatePicker auszuwählen.
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %> <calendar:resources/> <g:if test="${flash.message}"> <div class="message"> ${flash.message} ${flash.message = null} </div> </g:if> <g:if test="${flash.error}"> <div class="errors"> <ul> <li> ${flash.error} ${flash.error = null} </li> </ul> </div> </g:if> <div> <h1>Add new Flight</h1> <form action="${portletResponse.createActionURL()}"> <p> <label for="number">Flight Number:</label><g:textField id="number" name="number" value="" /> </p> <p> <label for="date">Date:</label><calendar:datePicker name="date" defaultValue="${new Date()}"/> </p> <p> <label for="start">Start Airport:</label> <g:select name="start" id="start" from="${airportList}" value="${id}" optionKey="id" optionValue="name" /> </p> <p> <label for="destination">Destination Airport:</label> <g:select name="destination" id="destination" from="${airportList}" value="${name}" optionKey="id" optionValue="name" /> </p> <p> <label for="plane">Plane:</label> <g:select name="plane" id="plane" from="${planeList}" value="${id}" optionKey="id" optionValue="type" /> </p> <input type="submit" name="save" value="Save"/> </form> </div>
help.gsp
Dieser View soll eigentlich nur alle Daten auflisten, die im System gespeichert sind.
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %> <div> <form action="${portletResponse.createActionURL()}"> <h1>Available Airports</h1> <table border="1"> <tr> <th>ID</th> <th>Code</th> <th>Name</th> </tr> <g:each in="${airportList}" var="airport" status="airportState"> <tr> <td>${airport.id}</td> <td>${airport.code}</td> <td>${airport.name}</td> </tr> </g:each> </table> <hr/> <h1>Available Planes</h1> <table border="1"> <tr> <th>ID</th> <th>Type</th> </tr> <g:each in="${planeList}" var="plane" status="planeState"> <tr> <td>${plane.id}</td> <td>${plane.type}</td> </tr> </g:each> </table> <hr/> <h1>Available Flights</h1> <table border="1"> <tr> <th>ID</th> <th>Number</th> <th>Date</th> <th>Plane</th> <th>Start</th> <th>Destination</th> </tr> <g:each in="${flightList}" var="flight" status="flightState"> <tr> <td>${flight.id}</td> <td>${flight.number}</td> <td>${flight.date}</td> <td>${flight.plane.type}</td> <td>${flight.start.name}</td> <td>${flight.destination.name}</td> </tr> </g:each> </table> <br/> <input type="submit" value="Back"/> </form> </div>
Beispieldaten
Das Beispiel hier sieht nur vor, Flüge hinzufügen zu können. Um das Beispiel etwas einfacher zu halten, habe ich mich entschlossen, Flugzeuge und Flughäfen bereits im Bootstrap zu erzeugen.
import de.ronnyfriedland.Airport; import de.ronnyfriedland.Flight; import de.ronnyfriedland.Plane; import java.util.Date; class BootStrap { def init = { servletContext -> Airport airport1 = new Airport() airport1.name = "Dresden" airport1.code = "DRS" airport1.save(flush:true) Airport airport2 = new Airport() airport2.name = "Rom" airport2.code = "CIA" airport2.save(flush:true) Airport airport3 = new Airport() airport3.name = "Dubai" airport3.code = "DXB" airport3.save(flush:true) Plane plane1 = new Plane() plane1.type = "Airbus A380" plane1.save(flush:true) Plane plane2 = new Plane() plane2.type = "Boing 737" plane2.save(flush:true) Flight flight = new Flight() flight.date = new Date() flight.number = "IA-123" flight.start = airport1 flight.destination = airport2 flight.plane = plane1 flight.save(flush:true) } def destroy = { } }
Probleme
Leider habe ich es nicht geschafft, ein Command Object für die Formulardaten zum laufen zu bekommen. Irgendwie war das Objekt immer null. Daher die etwas unschöne Lösung beim Speichern der Daten.
... Flight.withTransaction { status -> ... } ...
Download
Das gesamte Beispiel gibt es hier als Download.