Grails: Tutorial 1 – Anwendung inkl. Bild-Upload
Mar 2021 Grails
In diesem Tutorial wird beschrieben, wie man mit Grails eine Web-Anwendung erstellt, die auf Daten einer MySQL-Datenbank zugreift.
Inhalt:
Es folgt eine Schritt-für-Schritt-Anleitung. Die fertigen Dateien des Projektes kann man hier herunterladen: Grails-Anwendung inkl. Bild-Upload.
Der Fehler
kann ignoriert werden. Er taucht andauernd auf.
Die gerade erzeugten Dateien werden jetzt mit Inhalt gefüllt.
Der Hersteller wird durch Name, Ort, Land und URL gekennzeichnet und bekommt ein Feld für Bemerkungen. Zu jedem Hersteller können mehrere Teddies eingetragen werden. Das Feld
Der Teddy bekommt je ein Feld für Name, Kaufdatum und -preis, Wert, Gesamtauflage, Artikelnummer, Nummer, Geburtsdatum, Größe, Schätze und Beschreibung und es kann ein Foto hochgeladen werden. Zu jedem Teddy gibt es genau einen Hersteller.
Die Reihenfolge unter
Alle Feldnamen werden klein geschrieben.
sorgt dafür, daß im Hersteller-View die Bezeichnung des Teddies anstelle der ID angezeigt wird und umgekehrt.
(Ganz oben in
Der Connector wird von dort geladen.)
Als nächstes muß die Datenquelle auf MySQL geändert werden, sowie der Name der Datenbank, der Datenbankbenutzer und sein Passwort hinterlegt werden.
Vor dem ersten Aufruf von Grails muß hier der Benutzer
Es lohnt sich, sich mit den verschiedenen Bedeutungen von
Ich stelle während der Entwicklungsphase
Die Datenbank
Jetzt kann man die Applikation testen:
Im Browser http://localhost:8080 aufrufen:
Grails mit
Jetzt wird der Datenbankbenutzer
Grails starten:
Jetzt sieht man die zwei Controller:

Ein Klick auf

Hier kann man einen Hersteller anlegen:

Jetzt können ein paar Testdatensätze angelegt werden: zuerst ein Hersteller, dann Teddies.
Die Datei
So sieht die Einstiegsseite jetzt aus:
Die Bezeichnungen für die Felder können hier angepaßt werden:
Syntax:
So sieht jetzt die Listenansicht der Teddies aus:
Oberhalb der Liste befindet sich der neue Link auf die Herstellerliste.
Im Moment werden nur die ersten sieben Felder zum Teddy angezeigt, das wird später noch geändert.
Die Datei
Jetzt die Listenansicht so ändern, daß das Bild angezeigt wird, sofern eines vorhanden ist (den Inhalt der Datei vollständig ersetzen).
(Das ist kein schöner Code, aber ich habe keine bessere Möglichkeit gefunden.)
In der Liste werden jetzt alle Felder angezeigt:
In den Ändern-, Anzeigen- und Anlegen-Sichten werden nun die Felder
In der Anzeige-Sicht wird zusätzlich eine Schaltfläche ergänzt, die auf eine Seite zum Hochladen des Bildes führt:
Jetzt ist eine neue Schaltfläche
Jetzt muß noch die Seite zum Hochladen des Bildes angelegt werden:
und modifiziert werden:
Sie sieht so aus:
Als nächstes wird der Inhalt von
Jetzt kann man ein Foto hochladen und bekommt es in der Liste angezeigt.
Wenn ein Bild vorhanden ist, soll es auch in der Detailsicht des Teddies gezeigt werden:
Die
Und so gestoppt werden:
angepaßt werden (Zeilen ggf. einfügen).
hilft das Löschen der Datei
https://docs.grails.org/4.0.8/guide/single.html
https://guides.grails.org/grails-upload-file/guide/index.html
https://de.slideshare.net/ashishkirpan/grails-connecting-tomysql-13327214
macOS Catalina
Grails Version: 4.0.8
JVM Version: 11.0.10
MySQL Community Server 8.0.23
Inhalt:
- Domain-Klassen anlegen
- MySQL-Datenquelle konfigurieren
- Controller und Views erzeugen
- Die Optik der Weboberfläche anpassen
- Bilder hochladen
- Produktivsetzung
- Lose Enden
- Grails wurde installiert und
create-app
ausgeführt (siehe: Grails: Installation) - MySQL wurde installiert, die Datenbank
teddies
und ein Benutzerpieper
angelegt, und der MySQL-Server ist gestartet (siehe: Installation von MySQL)
Es folgt eine Schritt-für-Schritt-Anleitung. Die fertigen Dateien des Projektes kann man hier herunterladen: Grails-Anwendung inkl. Bild-Upload.
Domain-Klassen für Teddies und Hersteller anlegen
grails create-app
wurde bereits ausgeführt (Anleitung hier), die Grails-Anwendung liegt im Verzeichnis ~/Grails/Teddy-DB
.Teddy-DB % cd ~/Grails/Teddy-DB
Teddy-DB % grails create-domain-class Teddies
| Created grails-app/domain/teddies/Teddies.groovy
| Created src/test/groovy/teddies/TeddiesSpec.groovy
Teddy-DB % grails create-domain-class Hersteller
| Created grails-app/domain/teddies/Hersteller.groovy
| Created src/test/groovy/teddies/HerstellerSpec.groovy
Der Fehler
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/Users/pieper/.sdkman/candidates/grails/4.0.8/lib/org.codehaus.groovy/groovy/jars/groovy-2.5.14.jar) to method java.lang.Object.finalize()
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.reflection.CachedClass
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
kann ignoriert werden. Er taucht andauernd auf.
Die gerade erzeugten Dateien werden jetzt mit Inhalt gefüllt.
Der Hersteller wird durch Name, Ort, Land und URL gekennzeichnet und bekommt ein Feld für Bemerkungen. Zu jedem Hersteller können mehrere Teddies eingetragen werden. Das Feld
name
muß gefüllt werden.Teddy-DB % vi grails-app/domain/teddies/Hersteller.groovy
package teddies
class Hersteller {
String name
String ort
String land
String url
String bemerkung
static hasMany = [ teddies:Teddies]
static constraints = {
name(unique:true, blank:false)
ort(nullable:true)
land(nullable:true)
url(nullable:true)
bemerkung(nullable:true)
}
String toString() {
name
}
}
Der Teddy bekommt je ein Feld für Name, Kaufdatum und -preis, Wert, Gesamtauflage, Artikelnummer, Nummer, Geburtsdatum, Größe, Schätze und Beschreibung und es kann ein Foto hochgeladen werden. Zu jedem Teddy gibt es genau einen Hersteller.
Teddy-DB % vi grails-app/domain/teddies/Teddies.groovy
package teddies
class Teddies {
String name
String kaufdatum
Float kaufpreis
Float wert
Integer gesamtauflage
String artikelnummer
Integer nummer
String beschreibung
String geburtsdatum
Float groesse
byte[] featuredImageBytes
String featuredImageContentType
String schaetze
static belongsTo = [ hersteller:Hersteller]
static constraints = {
name(nullable:true)
beschreibung(nullable:true)
groesse(nullable:true)
schaetze(nullable:true)
hersteller(nullable:false)
geburtsdatum(nullable:true)
wert(nullable:true)
artikelnummer(nullable:true)
gesamtauflage(nullable: true)
nummer(nullable:true)
kaufpreis(nullable: true)
kaufdatum(nullable: true)
featuredImageBytes nullable: true
featuredImageContentType nullable: true
}
static mapping = {
featuredImageBytes column: 'featured_image_bytes', sqlType: 'longblob'
}
String toString() {
name
}
}
Die Reihenfolge unter
static constraints
bestimmt die Reihenfolge der Felder in der Weboberfläche.Alle Feldnamen werden klein geschrieben.
String toString() {
name
sorgt dafür, daß im Hersteller-View die Bezeichnung des Teddies anstelle der ID angezeigt wird und umgekehrt.
MySQL-Datenquelle konfigurieren
Damit Grails die MySQL-Datenbank nutzt, muß der Java-Connector in die Dateibuild.gradle
im Bereich dependencies { }
im unteren Teil der Datei eingefügt werden. Teddy-DB % vi build.gradle
einfügen:
runtime "com.bertramlabs.plugins:asset-pipeline-grails:3.2.4"
runtime "mysql:mysql-connector-java:8.0.23"
testCompile "io.micronaut:micronaut-inject-groovy"
(Ganz oben in
build.gradle
steht die Zeile repositories {
maven { url "https://repo.grails.org/grails/core" }
Der Connector wird von dort geladen.)
Als nächstes muß die Datenquelle auf MySQL geändert werden, sowie der Name der Datenbank, der Datenbankbenutzer und sein Passwort hinterlegt werden.
Teddy-DB % vi grails-app/conf/application.yml
ändern:
dataSource:
pooled: true
jmxExport: true
driverClassName: com.mysql.jdbc.Driver
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
username: root
password: 01020304
environments:
development:
dataSource:
dbCreate: create
url: jdbc:mysql://localhost/teddies
test:
dataSource:
dbCreate: none
url: jdbc:mysql://localhost/teddies
production:
dataSource:
dbCreate: none
url: jdbc:mysql://localhost/teddies
Vor dem ersten Aufruf von Grails muß hier der Benutzer
root
hinterlegt werden, damit die Datenbanktabellen angelegt werden können. Es lohnt sich, sich mit den verschiedenen Bedeutungen von
dbCreate
vertraut zu machen, da je nach Einstellung alle Daten gelöscht werden (s. Grails-Dokumentation).Ich stelle während der Entwicklungsphase
dbCreate
auf create
, danach auf none
.Die Datenbank
teddies
und der Benutzer pieper
müssen in mysql
angelegt werden (siehe MacOS: Installation von MySQL)Jetzt kann man die Applikation testen:
Teddy-DB % grails run-app
Im Browser http://localhost:8080 aufrufen:
Grails mit
Ctrl-C
beenden.Jetzt wird der Datenbankbenutzer
pieper
eingetragen, der keine Rechte zum Anlegen und Löschen von Tabellen hat, und dbCreate
auf none
gesetzt. Damit verhindert man, daß Grails die Datenbanktabellen beim Starten und/oder Stoppen der Anwendung löscht. Wenn man nachträglich das Datenmodell ändert, muß man die Einstellungen entsprechend anpassen.Teddy-DB % vi grails-app/conf/application.yml
ändern:
dataSource:
pooled: true
jmxExport: true
driverClassName: com.mysql.jdbc.Driver
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
username: pieper
password: 010203
environments:
development:
dataSource:
dbCreate: none
Controller und Views erzeugen
Grails erzeugt automatisch Controller und Views zum Anlegen, Ändern etc. von Daten. Die Views werden später noch angepaßt.Teddy-DB % grails generate-all Teddies
| Resolving Dependencies. Please wait...
CONFIGURE SUCCESSFUL in 6s
| Rendered template Controller.groovy to destination grails-app/controllers/teddies/TeddiesController.groovy
| Rendered template Service.groovy to destination grails-app/services/teddies/TeddiesService.groovy
| Rendered template Spec.groovy to destination src/test/groovy/teddies/TeddiesControllerSpec.groovy
| Rendered template ServiceSpec.groovy to destination src/integration-test/groovy/teddies/TeddiesServiceSpec.groovy
| Scaffolding completed for grails-app/domain/teddies/Teddies.groovy
| Rendered template show.gsp to destination grails-app/views/teddies/show.gsp
| Rendered template index.gsp to destination grails-app/views/teddies/index.gsp
| Rendered template create.gsp to destination grails-app/views/teddies/create.gsp
| Rendered template edit.gsp to destination grails-app/views/teddies/edit.gsp
| Views generated for grails-app/domain/teddies/Teddies.groovy
Teddy-DB % grails generate-all Hersteller
| Rendered template Controller.groovy to destination grails-app/controllers/teddies/HerstellerController.groovy
| Rendered template Service.groovy to destination grails-app/services/teddies/HerstellerService.groovy
| Rendered template Spec.groovy to destination src/test/groovy/teddies/HerstellerControllerSpec.groovy
| Rendered template ServiceSpec.groovy to destination src/integration-test/groovy/teddies/HerstellerServiceSpec.groovy
| Scaffolding completed for grails-app/domain/teddies/Hersteller.groovy
| Rendered template show.gsp to destination grails-app/views/hersteller/show.gsp
| Rendered template index.gsp to destination grails-app/views/hersteller/index.gsp
| Rendered template create.gsp to destination grails-app/views/hersteller/create.gsp
| Rendered template edit.gsp to destination grails-app/views/hersteller/edit.gsp
| Views generated for grails-app/domain/teddies/Hersteller.groovy
Grails starten:
Teddy-DB % grails run-app
Jetzt sieht man die zwei Controller:

Ein Klick auf
teddies.HerstellerController
zeigt die Herstellerliste:
Hier kann man einen Hersteller anlegen:

Jetzt können ein paar Testdatensätze angelegt werden: zuerst ein Hersteller, dann Teddies.
Die Optik der Weboberfläche anpassen
Jetzt ist es an der Zeit, das Aussehen der Weboberfläche anzupassen. Man kann Grails laufen lassen, während man die Änderungen vornimmt. Es reicht aus, den Browser aufzufrischen.Grails-Logo ersetzen, Überschrift hinzufügen und die Fußzeile löschen
Zuerst wird das Grails-Logo ersetzt, ein Text in der Kopfzeile hinzugefügt und der Inhalt der Fußzeile gelöscht:Teddy-DB % vi grails-app/views/layouts/main.gsp
anpassen:
<a class="navbar-brand" href="/#"><asset:image src="teddy_logo.jpg" alt="süßer Teddy"/></a>
einfügen:
</button>
<p style="color: #fbdbff; font-size: 3.5em"> Piepers Däddi gontrola </p>
<div class="collapse navbar-collapse" aria-expanded="false" style="height: 0.8px;" id="navbarContent">
alle 24 Zeilen innerhalb der footer row löschen:
<div class="footer row" role="contentinfo">
</div>
Die Datei
teddy_logo.jpg
wird in das Verzeichnis grails-app/assets/images/
kopiert.Einstiegsseite anpassen
Der weiße Grails-Kelch soll weg und es soll ein eigener Text angezeigt werden.Teddy-DB % vi grails-app/views/index.gsp
löschen:
<asset:image src="grails-cupsonly-logo-white.svg" class="grails-logo"/>
ändern:
<section class="row colset-2-its">
<h1>Willkommen bei Piepers Däddi gontrola!</h1>
<p style="width:75%">
Hier geht es zu den Teddies und Herstellern:
</p>
<div id="controllers" role="navigation">
<h2>Controller:</h2>
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
<li class="controller">
<g:link controller="${c.logicalPropertyName}">${c.shortName}</g:link>
</li>
</g:each>
</ul>
</div>
</section>
shortName
statt fullName
sorgt dafür, daß der Name der Datenbank im Controllernamen nicht mit angezeigt wird: HerstellerController
statt teddies.HerstellerController
.So sieht die Einstiegsseite jetzt aus:
Bezeichnungen der Felder anpassen
Den Feldbezeichnungen fehlt noch die richtige Schreibweise (Größe statt Groesse):Die Bezeichnungen für die Felder können hier angepaßt werden:
Teddy-DB % vi grails-app/i18n/messages_de.properties
am Ende einfügen:
teddies.groesse.label=Größe
teddies.schaetze.label=Schätze
Syntax:
<full packagePath>.<domain name>.<propertyName>.<attribute>'= <message>
Schaltfläche zum Absprung von der Hersteller-Liste in die Teddy-Liste hinzufügen
Teddy-DB % vi grails-app/views/hersteller/index.gsp
einfügen:
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><a class="list" href="${createLink(uri: '/teddies')}"><g:message code="Teddies"/></a></li>
</ul>
Schaltfläche zum Absprung von der Teddy-Liste in die Hersteller-Liste hinzufügen
Teddy-DB % vi grails-app/views/teddies/index.gsp
einfügen:
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><a class="list" href="${createLink(uri: '/hersteller')}"><g:message code="Hersteller"/></a></li>
</ul>
So sieht jetzt die Listenansicht der Teddies aus:
Oberhalb der Liste befindet sich der neue Link auf die Herstellerliste.
Im Moment werden nur die ersten sieben Felder zum Teddy angezeigt, das wird später noch geändert.
Löschen-Schaltfläche für Hersteller deaktivieren
Wenn ein Hersteller gelöscht wird, werden automatisch alle dazugehörigen Teddies mit gelöscht. Das kann schmerzhaft sein, deshalb wird der Löschen-Knopf beim Hersteller entfernt.Teddy-DB % vi grails-app/views/hersteller/show.gsp
auskommentieren:
<!— <input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" /> —>
Bilder hochladen
Die Anwendung funktioniert soweit schon ganz gut. Um Fotos hochzuladen, muß man noch ein paar Änderungen vornehmen. Zuerst den Controller um den Bildupload erweitern:Teddy-DB % vi grails-app/controllers/teddies/TeddiesController.groovy
ersetzen:
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
def index() {
params.max = Math.min( params.max ? params.max.toInteger() : 10, 100)
[ teddiesInstanceList: Teddies.list( params ), teddiesInstanceTotal: Teddies.count() ]
}
def show(Long id) {
einfügen:
}
def featuredImage(Long id) {
Teddies teddies = teddiesService.get(id)
if (!teddies || teddies.featuredImageBytes == null) {
notFound()
return
}
render file: teddies.featuredImageBytes,
contentType: teddies.featuredImageContentType
}
def editFeaturedImage(Long id) {
Teddies teddies = teddiesService.get(id)
if (!teddies) {
notFound()
}
[teddies: teddies]
}
def uploadFeaturedImage(FeaturedImageCommand cmd) {
if (cmd == null) {
notFound()
return
}
if (cmd.hasErrors()) {
respond(cmd.errors, model: [teddies: cmd], view: 'editFeaturedImage')
return
}
Teddies teddies = teddiesService.update(cmd.id,
cmd.version,
cmd.featuredImageFile.bytes,
cmd.featuredImageFile.contentType)
if (teddies == null) {
notFound()
return
}
if (teddies.hasErrors()) {
respond(teddies.errors, model: [teddies: teddies], view: 'editFeaturedImage')
return
}
Locale locale = request.locale
// flash.message = crudMessageService.message(CRUD.UPDATE, domainName(locale), cmd.id, locale)
redirect teddies
}
protected void notFound() {
Die Datei
FeaturedImageCoommand.groovy
erzeugen:Teddy-DB % vi grails-app/controllers/teddies/FeaturedImageCommand.groovy
package teddies
import grails.validation.Validateable
import org.springframework.web.multipart.MultipartFile
class FeaturedImageCommand implements Validateable {
MultipartFile featuredImageFile
Long id
Integer version
static constraints = {
id nullable: false
version nullable: false
featuredImageFile validator: { val, obj ->
if ( val == null ) {
return false
}
if ( val.empty ) {
return false
}
['jpeg', 'jpg', 'png'].any { extension -> // <1>
val.originalFilename?.toLowerCase()?.endsWith(extension)
}
}
}
}
Jetzt die Listenansicht so ändern, daß das Bild angezeigt wird, sofern eines vorhanden ist (den Inhalt der Datei vollständig ersetzen).
Teddy-DB % vi grails-app/views/teddies/index.gsp
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<g:set var="entityName" value="${message(code: 'teddies.label', default: 'Teddies')}" />
<title><g:message code="default.list.label" args="[entityName]" /></title>
</head>
<body>
<div class="nav" role="navigation">
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><a class="list" href="${createLink(uri: '/hersteller')}"><g:message code="Hersteller"/></a></li>
</ul>
</div>
<div id="list-hersteller" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<div class="list">
<table>
<thead>
<tr>
<g:sortableColumn property="id" title="ID" />
<g:sortableColumn property="name" title="Name" />
<g:sortableColumn property="beschreibung" title="Beschreibung" />
<g:sortableColumn property="groesse" title="Größe" />
<g:sortableColumn property="schaetze" title="Schätze" />
<g:sortableColumn property="hersteller" title="Hersteller" />
<g:sortableColumn property="artikelnummer" title="Artikelnummer" />
<g:sortableColumn property="kaufdatum" title="Kaufdatum" />
<g:sortableColumn property="kaufpreis" title="Kaufpreis" />
<g:sortableColumn property="nummer" title="Nummer" />
<g:sortableColumn property="gesamtauflage" title="Gesamtauflage" />
<g:sortableColumn property="geburtsdatum" title="Geburtsdatum" />
<g:sortableColumn property="wert" title="Wert" />
<g:sortableColumn property="featuredImageBytes" title="Bild" />
</tr>
</thead>
<tbody>
<g:each in="${teddiesInstanceList}" status="i" var="teddiesInstance">
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td><g:link action="show" id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'id')}</g:link></td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'name')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'beschreibung')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'groesse')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'schaetze')}</td>
<td valign="top" class="value"><g:link controller="hersteller" action="show" id="${teddiesInstance?.hersteller?.id}">${teddiesInstance?.hersteller?.name?.encodeAsHTML()}</g:link></td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'artikelnummer')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'kaufdatum')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'kaufpreis')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'nummer')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'gesamtauflage')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'geburtsdatum')}</td>
<td><id="${teddiesInstance.id}">${fieldValue(bean:teddiesInstance, field:'wert')}</td>
<td> <g:if test="${teddiesInstance?.featuredImageBytes}">
<img width="50" class="featuredImageBytes" src="${createLink(controller:'teddies', action:'featuredImage', id:teddiesInstance?.id)}" />
</g:if>
</td>
</tr>
</g:each>
</tbody>
</table>
</div>
<div class="pagination">
<g:paginate total="${teddiesInstanceTotal}" />
</div>
</div>
</body>
</html>
(Das ist kein schöner Code, aber ich habe keine bessere Möglichkeit gefunden.)
In der Liste werden jetzt alle Felder angezeigt:
In den Ändern-, Anzeigen- und Anlegen-Sichten werden nun die Felder
featuredImageBytes
und featuredImageContentType
ausgeblendet:Teddy-DB % vi grails-app/views/teddies/edit.gsp
ergänzen:
<f:all bean="teddies" except="featuredImageBytes,featuredImageContentType"/>
Teddy-DB % vi grails-app/views/teddies/create.gsp
ergänzen:
<f:all bean="teddies" except="featuredImageBytes,featuredImageContentType"/>
In der Anzeige-Sicht wird zusätzlich eine Schaltfläche ergänzt, die auf eine Seite zum Hochladen des Bildes führt:
Teddy-DB% vi grails-app/views/teddies/show.gsp
ergänzen:
<f:all bean="teddies" except="featuredImageBytes,featuredImageContentType"/>
einfügen:
<g:link class="edit" action="edit" resource="${this.teddies}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
<g:link class="edit" action="editFeaturedImage" resource="${this.teddies}"><g:message code="teddies.featuredImageUrl.edit.label" default="Bild hochladen" /></g:link>
Jetzt ist eine neue Schaltfläche
Bild bearbeiten
zu sehen:Jetzt muß noch die Seite zum Hochladen des Bildes angelegt werden:
Teddy-DB % cp grails-app/views/teddies/edit.gsp grails-app/views/teddies/editFeaturedImage.gsp
und modifiziert werden:
Teddy-DB% vi grails-app/views/teddies/editFeaturedImage.gsp
<g:form bis </g:form> durch <g:uploadForm ersetzen:
<g:uploadForm name="uploadFeaturedImage" action="uploadFeaturedImage">
<g:hiddenField name="id" value="${this.teddies?.id}" />
<g:hiddenField name="version" value="${this.teddies?.version}" />
<input type="file" name="featuredImageFile" />
<fieldset class="buttons">
<input class="save" type="submit" value="${message(code: 'teddies.featuredImage.upload.label', default: 'Hochladen')}" />
</fieldset>
</g:uploadForm>
Sie sieht so aus:
Als nächstes wird der Inhalt von
TeddiesService.groovy
verändert:Teddy-DB % vi grails-app/services/teddies/TeddiesService.groovy
ändern:
package teddies
import grails.gorm.services.Service
@Service(Teddies)
interface TeddiesService {
Teddies get(Long id)
List<Teddies> list(Map args)
Number count()
void delete(Serializable id)
Teddies update(Serializable id, Long version, byte[] featuredImageBytes, String featuredImageContentType)
Teddies save(Teddies teddies)
}
Jetzt kann man ein Foto hochladen und bekommt es in der Liste angezeigt.
Wenn ein Bild vorhanden ist, soll es auch in der Detailsicht des Teddies gezeigt werden:
Teddy-DB % vi grails-app/views/teddies/show.gsp
einfügen:
<g:if test="${this.teddies.featuredImageBytes}">
<img src="<g:createLink controller="teddies" action="featuredImage" id="${this.teddies.id}"/>" width="200"/>
</g:if>
<f:display bean="teddies" except="featuredImageBytes,featuredImageContentType"/>
Fertig!
Produktivsetzung
Wenn man fertig ist, kann man ein lauffähigesWAR
erzeugen:Teddy-DB % grails package
> Task :compileGroovyPages
BUILD SUCCESSFUL in 20s
7 actionable tasks: 6 executed, 1 up-to-date
<-------------> 0% WAITING
> IDLE
> IDLE
| Built application to build/libs using environment: production
Die
.war
-Datei kann so aufgerufen werden:% ~/.sdkman/candidates/java/current/bin/java -Dgrails.env=prod -jar ~/Grails/Teddy-DB/build/libs/Teddy-DB-0.1.war
Und so gestoppt werden:
% pkill -f Dgrails
Lose Enden
Maximale Größe der Bilddateien
Die Standardgröße für Dateien beträgt 128 KB. Sie kann in der Dateigrails-app/conf/application.yml
unter grails:
controllers:
upload:
maxFileSize: 2000000
maxRequestSize: 2000000
angepaßt werden (Zeilen ggf. einfügen).
Kopiertes Grails-Projekt läuft nicht
Wenn man ein Grails-Projekt an einen anderen Ort kopiert hat und es dann nicht läuft| Error Command [run-app] error: Profile [org.grails.profiles:base:4.0.0] declares an invalid dependency on parent profile [org.grails.profiles:base:4.0.0] (Use --stacktrace to see the full trace)
hilft das Löschen der Datei
grails-app/build/.dependencies
Dokumentationen
Bei der Erstellung dieses Projekts waren folgende Anleitungen besonders hilfreich:https://docs.grails.org/4.0.8/guide/single.html
https://guides.grails.org/grails-upload-file/guide/index.html
https://de.slideshare.net/ashishkirpan/grails-connecting-tomysql-13327214
macOS Catalina
Grails Version: 4.0.8
JVM Version: 11.0.10
MySQL Community Server 8.0.23