$ brew install openjdk@25
$ brew install git
- Guide d’installation
- Première application
- Comment fonctionne l’application avec le framework Taack ?
- Créer un nouveau plugin d’application
- Gérer les futures mises à jour
- Construire un menu pour l’application
- Créer des classes
- Créer un tableau
- Ajouter des boutons à un bloc de tableau
- Créer un formulaire et enregistrer des objets
- Afficher un objet
- Supprimer un objet
- Nommer les en-têtes
- Filtrage
- Ajouter un second menu
- Lier les auteurs aux livres
- Dépannage et erreurs courantes
Guide d’installation
Prérequis
Pour commencer, assurez-vous que les outils suivants sont installés sur votre machine :
-
Java (JDK 25 …)
-
Git
Installer les prérequis
macOS (Homebrew)
Windows (Winget)
winget install --id Microsoft.OpenJDK.25
winget install --id Git.Git
Cloner l’intranet prêt pour le développement
$ git clone https://github.com/Taack/intranet.git
Ce dépôt inclut un squelette d’intranet minimal (gestion des utilisateurs Crew + plugin Spring Security) prêt pour le développement.
Démarrer votre intranet
$ cd intranet
$ ./gradlew server:bootRun
|
La commande |
Grails application running at http://localhost:9442
TODO : Accédez à l’intranet en ouvrant cette adresse dans votre navigateur. Utilisez les identifiants de connexion par défaut :
-
Nom d’utilisateur :
admin -
Mot de passe :
ChangeIt
Pour modifier le mot de passe administrateur par défaut, éditez les fichiers suivants :
-
server/grails-app/conf/application.yml -
server/grails-app/conf/application-MACOSX.yml
Recherchez la ligne :
taack.admin.password: ChangeIt
Après avoir effectué la modification, supprimez le fichier intranetDb.mv.db pour appliquer le nouveau mot de passe.
|
|
|
La suppression du fichier |
Configurer une base de données persistante
Si vous souhaitez que les données soient persistantes, modifiez les fichiers server/grails-app/conf/application.yml et
server/grails-app/conf/application-MACOSX.yml pour utiliser une base de données persistante.
environments:
development:
dataSource:
dbCreate: update (1)
url: jdbc:h2:./intranetDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE (2)
| 1 | Mode de création (voir la documentation Grails GORM) |
| 2 | intranetDb : racine du nom de fichier contenant les données |
Produire un fichier JAR pour Tomcat
$ ./gradlew server:assemble
Le JAR assemblé (par ex. server-0.6.jar) se trouvera dans server/build/libs.
cd server/build/libs
java -jar server-[version].jar (1)
| 1 | Le numéro de version se trouve dans build.gradle. La valeur par défaut est 0.6 |
|
Assurez-vous que l’application n’est pas déjà en cours d’exécution lorsque vous démarrez une nouvelle instance. |
Configurer votre IDE
Nous recommandons vivement d’utiliser la dernière version d'IntelliJ Ultimate Edition pour sa prise en charge complète de Groovy et Grails.
IntelliJ Ultimate Edition
Nous recommandons d’installer le plugin IntelliJ Taack, que vous pouvez installer depuis l’IntelliJ Marketplace TaackUiASTAutocomplete ou directement depuis le dépôt de code source.
Ouvrez le projet, allez dans File > Open… et sélectionnez intranet/settings.gradle.
Assurez-vous que Gradle utilise le JDK 25 :

| 1 | Ouvrir les paramètres Gradle |
| 2 | Vérifier que la version de la JVM Gradle est 25 (le fournisseur peut être quelconque) |
Visual Studio Code
Si vous préférez Visual Studio Code, nous recommandons les extensions suivantes pour utiliser ce framework :
|
La plupart des fonctionnalités de Grails et du framework Taack ne seront pas reconnues par VS Code. Les imports, la navigation et l’assistance au code peuvent être limités. IntelliJ reste l’IDE privilégié pour une prise en charge complète. |
Première application
Comment fonctionne l’application avec le framework Taack ?
Votre projet d’intranet comporte deux parties :
-
Les plugins d’application, situés dans le dossier
app, sont des micro-projets, chacun ayant ses propres dépendances, sa propre compilation, etc. (par ex., Crew). Pour une explication plus détaillée de la déclaration des plugins, consultez À propos des plugins. -
Le serveur principal utilise tous les plugins d’application déclarés dans son fichier
build.gradleet les affiche sur la page principale de l’intranet.
Créer un nouveau plugin d’application
Pour créer un nouveau plugin d’application (un module Gradle), assurez-vous d’avoir récupéré la dernière version de votre intranet depuis notre dépôt GitHub (voir installation).
$ ./gradlew -DmodName=myBooks server:generateTaackAppTask
Cela crée un nouveau dossier app/myBooks.
|
Sous |
Pour que votre IDE reconnaisse le module nouvellement créé, rechargez Gradle en cliquant sur l’icône ci-dessous :
Gérer les futures mises à jour
La plupart du temps, la mise à jour de votre intranet implique de mettre à jour le dossier buildSrc, le fichier gradle.properties et, plus rarement, les fichiers build.gradle des applications.
|
En réalité, ce processus est courant dans le développement logiciel à long terme et ne devrait pas intimider les nouveaux venus. |
Construire un menu pour l’application
TODO : Nous allons construire deux menus, un pour les livres et un pour les auteurs. Dans chaque menu, vous pourrez ajouter de nouveaux éléments, les filtrer et voir chaque entrée dans un tableau.
Concentrons-nous sur la navigation de votre nouvelle application, myBooks.
La première étape consiste à définir la manière dont le menu sera construit. Allez dans grails-app/services/my/books/MyBooksUiService.
static UiMenuSpecifier buildMenu(String q = null) { (1)
new UiMenuSpecifier().ui {
menu MyBooksController.&index as MC
}
}
| 1 | Ajoutez static s’il n’est pas déjà présent. |
Nous créons un objet UiMenuSpecifier pour définir la structure du menu, puis nous spécifions le contenu à afficher.
menu MyBooksController.&index as MC
Dans ce cas, menu crée un simple lien dans la barre de navigation. index est le nom affiché pour le lien, et MyBooksController.&index (utilisant une MethodClosure) définit l’action vers laquelle le lien redirigera.
Créer des classes
Créez une classe Groovy appelée Author dans grails-app/domain/my/books.
import grails.compiler.GrailsCompileStatic
import taack.ast.annotation.TaackFieldEnum
@GrailsCompileStatic
@TaackFieldEnum
class Author {
String firstName
String lastName
Date dateOfBirth
}
Créez une autre classe Groovy appelée Book dans grails-app/domain/my/books.
import grails.compiler.GrailsCompileStatic
import taack.ast.annotation.TaackFieldEnum
@GrailsCompileStatic
@TaackFieldEnum
class Book {
String title
String authorName
int numberOfPages
}
|
Utilisez toujours |
Créer un tableau
Affichons maintenant un tableau dans notre page listBook. Allez dans grails-app/services/my/books/MyBooksUiService.
buildBookTable :UiTableSpecifier buildBookTable() {
Book book = new Book()
UiTableSpecifier bookTableSpecifier = new UiTableSpecifier()
bookTableSpecifier.ui {
//Add table content inside the closure here
}
}
Ici, nous définissons un nouveau tableau qui listera les instances de Book. Utilisez import taack.ui.dsl.UiTableSpecifier.
bookTableSpecifier.ui {
// -- Header --
header {
column {
sortableFieldHeader book.title_ (1)
}
column {
label book.authorName_
}
column {
label book.numberOfPages_
}
column {
label "Delete book"
}
}
}
| 1 | Notez qu’ici nous utilisons sortableFieldHeader, ce qui rend la colonne triable : vous pouvez trier les livres par ordre alphabétique en cliquant sur l’en-tête book.title.
Maintenant, nous allons remplir notre tableau : nous allons parcourir les instances de Book dans la base de données à l’aide de la méthode DSL de tableau iterate. |
bookTableSpecifier.ui {
//table headers...
iterate(taackFilterService.getBuilder(Book) (1)
.setMaxNumberOfLine(8) (2)
.setSortOrder(TaackFilter.Order.DESC, book.title_) (3)
.build()) { Book bookIterator ->
rowColumn {
rowField bookIterator.title_ (4)
}
rowColumn {
rowField bookIterator.authorName_
}
rowColumn {
rowField bookIterator.numberOfPages_
}
rowColumn {
rowAction ActionIcon.DELETE * IconStyle.SCALE_DOWN, (5)
MyBooksController.&deleteBook as MethodClosure, bookIterator.id
}
}
}
| 1 | Utilisez import taack.domain.TaackFilterService. |
| 2 | Seuls les huit premiers livres seront affichés. |
| 3 | Définit l’ordre d’affichage selon le titre des livres par ordre décroissant (utilisez import taack.domain.TaackFilter). |
| 4 | Le tiret bas (underscore) est nécessaire ici. |
| 5 | Utilisez import taack.ui.dsl.common.ActionIcon
import taack.ui.dsl.common.IconStyle. |
|
Nous devons créer l’action |
Allez dans grails-app/controllers/my/books/MyBooksController.groovy.
index :def deleteBook () {}
Pour chaque livre de notre liste, nous créons une nouvelle ligne avec le titre du livre dans la première colonne, suivi de l’auteur, du nombre de pages et d’un bouton Delete dans la quatrième colonne.
Votre tableau est maintenant complet ; il ne nous reste plus qu’à l’afficher sur la page.
Pour afficher les UiSpecifier précédemment construits, nous devons utiliser taackUiService, qui devrait déjà être importé dans le contrôleur créé par la commande create-taack-app.
Nous ferons cela dans une action appelée listBook : dans la méthode index, remplacez son contenu par une redirection vers l’action listBook.
listBook :def index() {
redirect action: 'listBook'
}
listBook et ajoutez le code suivant :UiTableSpecifier tableBookSpecifier = myBooksUiService.buildBookTable() (1)
taackUiService.show(new UiBlockSpecifier().ui {
table tableBookSpecifier
}, MyBooksUiService.buildMenu())
| 1 | Récupère le tableSpecifier que nous avons créé depuis MyBooksUiService. |
Utilisez import taack.ui.dsl.UiTableSpecifier.
taackUiService.show(UiBlockSpecifier block, UiMenuSpecifier menu) se chargera du rendu de la spécification que nous lui passons.
Nous utilisons la méthode statique buildMenu() créée précédemment dans MyBooksUiService comme second argument de show() pour afficher le menu avec la page.
Vous pouvez maintenant démarrer le serveur et naviguer vers votre nouvelle application (myBooks.app).
Le tableau devrait être fonctionnel, mais pour l’instant vous ne verrez que les en-têtes du tableau puisqu’il n’y a aucun livre dans votre base de données.
Passons donc à la création d’un formulaire et à l’enregistrement d’objets dans la base de données.
Ajouter des boutons à un bloc de tableau
Ajoutons au tableau des livres un bouton qui ouvre une fenêtre modale via AJAX pour créer un nouveau livre. Pour ce faire, nous devons ajouter une closure au tableau.
taackUiService.show(new UiBlockSpecifier().ui {
table tableBookSpecifier, {
//Added Closure here
menuIcon ActionIcon.CREATE, this.&bookForm as MethodClosure (1)
}
}, MyBooksUiService.buildMenu())
| 1 | Vous devrez définir l’action bookForm, tout comme nous l’avons fait avec deleteBook. |
N’oubliez pas d’utiliser import taack.ui.dsl.common.ActionIcon.
Démarrez maintenant le serveur et naviguez à nouveau vers votre nouvelle application : vous verrez un bouton Create en haut à droite du tableau.
La méthode menuIcon est composée des paramètres suivants :
-
L’icône utilisée par le bouton doit être une valeur de l’énumération
ActionIcon. -
L’action vers laquelle le bouton redirigera.
Créer un formulaire et enregistrer des objets
Nous allons maintenant créer le formulaire qui servira à créer et mettre à jour des livres.
Pour gérer les deux cas, nous allons initialiser un nouveau Book ou lire le livre si un identifiant de Book a été passé dans les paramètres de la requête dans MyBooksUiService.
buildBookForm :UiFormSpecifier buildBookForm(Book book) {
book ?= new Book(params) (1)
}
| 1 | Cela signifie que si book est null, on lui assigne une nouvelle instance à l’aide des paramètres de la requête. |
Définissons maintenant son formulaire et son contenu.
buildBookForm (importez les classes nécessaires) :UiFormSpecifier bookFormSpecifier = new UiFormSpecifier()
bookFormSpecifier.ui book, {
//Section of fields
section "Book details", {
field book.title_
field book.authorName_
field book.numberOfPages_
}
//Save button
formAction MyBooksController.&saveBook as MethodClosure (1)
}
| 1 | Vous devrez définir l’action saveBook (comme pour deleteBook) dans MyBooksController. |
Utilisez import taack.ui.dsl.UiFormSpecifier.
Une fois votre formulaire défini, vous pouvez l’afficher à l’aide de la méthode taackUiService.show().
bookForm de MyBooksController :def bookForm(Book book) {
UiFormSpecifier tableFormSpecifier = myBooksUiService.buildBookForm book
taackUiService.show new UiBlockSpecifier().ui {
modal {
form tableFormSpecifier
}
}
}
Utilisez import taack.ui.dsl.UiFormSpecifier.
Cette fois, nous ne spécifions pas buildMenu dans notre appel à show, car nous ne voulons pas que le menu soit affiché à l’intérieur de la fenêtre modale.
saveBook (importez et initialisez les classes nécessaires) :@Transactional
def saveBook() {
taackSaveService.saveThenReloadOrRenderErrors(Book)
}
Utilisez import taack.render.TaackSaveService et import grails.gorm.transactions.Transactional.
|
Consultez Fermer la modale et recharger la page pour savoir comment éviter l’utilisation de |
Redémarrez le serveur : vous devriez maintenant pouvoir cliquer sur le bouton et remplir le formulaire.
|
Dans le coin supérieur gauche, vous pouvez basculer entre les pages de livres à l’aide des numéros de page (1 ou 2). Cette navigation apparaît car la liste est limitée à 8 livres par page. |
Afficher un objet
Maintenant que nous pouvons créer des livres et en voir la liste dans un tableau, affichons chaque livre plus en détail à l’aide d’une fenêtre modale.
Une fois encore, nous définissons le spécificateur et nous l’affichons dans un bloc modal avec taackUiService.show().
showBook (importez la classe nécessaire) :def showBook(Book book) {
// Define the show displayed fields
UiShowSpecifier showSpec = new UiShowSpecifier().ui({
fieldLabeled book.title_
fieldLabeled book.authorName_
})
taackUiService.show(new UiBlockSpecifier().ui {
modal {
show showSpec
}
})
}
Utilisez import taack.ui.dsl.UiShowSpecifier.
Nous devons également afficher un lien vers cette fenêtre modale dans le tableau. Pour cela, ajoutez la ligne suivante dans la même rowColumn (sous le champ du titre du livre) où vous souhaitez voir apparaître le bouton dans MyBooksUiService :
buildBookTable :UiTableSpecifier buildBookTable() {
//Section of code
rowColumn {
//Add these 2 lines
rowAction ActionIcon.SHOW * IconStyle.SCALE_DOWN, (1)
MyBooksController.&showBook as MethodClosure, bookIterator.id
rowField bookIterator.title_
}
//Section of code
}
| 1 | Ici, nous réduisons la taille de l’icône avec l’opérateur de multiplication. |
Cela créera un petit bouton icône dans la cellule du tableau qui ouvrira une fenêtre modale contenant les informations du livre correspondant.
Notez que ActionIcon a été multiplié par un IconStyle pour modifier sa taille dans ce cas.
Cliquez sur l’icône en forme d’œil pour afficher le livre.
Supprimer un objet
Vous vous souvenez du bouton Delete que nous avons ajouté à notre tableau ?
Rendons ce bouton Delete fonctionnel en définissant l’action deleteBook qu’il appelle.
@Transactional
def deleteBook(Book book) {
book.delete()
redirect action: 'index'
}
|
Dans certains cas, il est préférable d’ajouter un champ |
Nous utilisons la méthode delete de Grails pour supprimer le livre de la base de données, puis nous redirigeons vers l’action index où se trouve le tableau des livres.
Notez que les noms des champs ne sont pas des libellés directs, mais des clés i18n telles que book.numberOfPages.label,default.numberOfPages.label (faisant référence aux clés i18n dans messages.properties). Changeons cela !
Nommer les en-têtes
Pour définir le nom des en-têtes, allez dans app/myBooks/grails-app/i18n/messages.properties.
default.numberOfPages.label et les autres libellés avec les noms de votre choix :default.authorName.label=Author Name
default.title.label=Title
default.numberOfPages.label=Number of Pages
myBooks.index.label=Books
myBooks.app=Book
myBooks.desc=Book storage
Lancez le serveur pour voir les noms des libellés mis à jour.
Filtrage
Si nous ajoutons trop de livres, nous risquons de perdre du temps à chercher un livre précis ; filtrer les livres permet de rendre la recherche plus efficace. Implémentons cela dans MyBooksUiService.
buildBookFilter :UiFilterSpecifier buildBookFilter(Book book) {
UiFilterSpecifier bookFilterSpecifier = new UiFilterSpecifier() (1)
bookFilterSpecifier.ui Book, { (2)
section "Book Filter", { (3)
filterField book.title_ (4)
}
}
}
| 1 | Créez le filtre. Utilisez import taack.ui.dsl.UiFilterSpecifier. |
| 2 | Passez la classe de l’objet et la closure contenant les spécifications du filtre via la méthode ui. |
| 3 | Créez une section intitulée "Book Filter". |
| 4 | Ajoutez un champ au filtre, notez le tiret bas à la fin du nom du champ. |
L’étape suivante consiste à afficher ce filtre nouvellement créé.
taackUiService.show dans listBook (situé dans MyBooksController) :def listBook() {
Book book = new Book()
UiTableSpecifier tableBookSpecifier = myBooksUiService.buildBookTable()
UiFilterSpecifier filterBookSpecifier = myBooksUiService.buildBookFilter book
taackUiService.show(new UiBlockSpecifier().ui {
tableFilter filterBookSpecifier, tableBookSpecifier, {
menuIcon ActionIcon.CREATE, this.&bookForm as MethodClosure
}
}, MyBooksUiService.buildMenu())
}
Utilisez import taack.ui.dsl.UiFilterSpecifier.
Pour plus d’informations détaillées sur le filtrage, consultez la documentation des filtres et tableaux.
Ajouter un second menu
Vous vous souvenez de la classe Author que nous avons créée au début ? Il est temps de l’utiliser ! Nous allons créer un second menu dans lequel nous pourrons ajouter des auteurs et les filtrer.
buildMenu dans MyBooksUiService :menu MyBooksController.&listAuthor as MC
Cela crée un second menu dans le coin supérieur gauche pour accéder à la page des auteurs.
|
Vous pouvez également créer des sous-menus imbriqués dans le menu principal.
Cela crée un sous-menu intitulé "Menu" contenant un lien vers l’action |
MyBooksUiService :UiTableSpecifier buildAuthorTable(Boolean isSelect = false ) {
Author author = new Author()
UiTableSpecifier authorTableSpecifier = new UiTableSpecifier()
authorTableSpecifier.ui {
header {
column {
sortableFieldHeader author.firstName_
}
column {
sortableFieldHeader author.lastName_
}
column {
label author.dateOfBirth_
}
column {
label "Delete Author"
}
}
iterate(taackFilterService.getBuilder(Author)
.setMaxNumberOfLine(8)
.setSortOrder(TaackFilter.Order.DESC, author.lastName_)
.build()) { Author authorIterator ->
rowColumn {
rowField authorIterator.firstName_
rowAction ActionIcon.SHOW * IconStyle.SCALE_DOWN,
MyBooksController.&showAuthor as MethodClosure, authorIterator.id
}
rowColumn {
rowField authorIterator.lastName_
}
rowColumn {
rowField authorIterator.dateOfBirth_
}
rowColumn {
rowAction ActionIcon.DELETE * IconStyle.SCALE_DOWN,
MyBooksController.&deleteAuthor as MC, authorIterator.id
}
}
}
}
UiFormSpecifier buildAuthorForm(Author author) {
author ?= new Author(params)
UiFormSpecifier authorFormSpecifier = new UiFormSpecifier()
authorFormSpecifier.ui author, {
section "Author details", {
field author.firstName_
field author.lastName_
field author.dateOfBirth_
}
formAction MyBooksController.&saveAuthor as MC
}
}
UiFilterSpecifier buildAuthorFilter() {
Author author = new Author()
UiFilterSpecifier authorFilterSpecifier = new UiFilterSpecifier()
authorFilterSpecifier.ui Author, {
section "Author Filter", {
filterField author.lastName_
}
}
}
Notez que le paramètre isSelect dans buildAuthorTable sera utilisé plus tard.
listAuthor dans MyBooksController :def listAuthor() {
UiTableSpecifier tableAuthorSpecifier = myBooksUiService.buildAuthorTable()
UiFilterSpecifier filterAuthorSpecifier = myBooksUiService.buildAuthorFilter()
taackUiService.show(new UiBlockSpecifier().ui {
tableFilter filterAuthorSpecifier, tableAuthorSpecifier, {
menuIcon ActionIcon.CREATE, this.&authorForm as MethodClosure
}
}, MyBooksUiService.buildMenu())
}
deleteAuthor, saveAuthor, authorForm et showAuthor :def authorForm(Author author) {
UiFormSpecifier tableFormSpecifier = myBooksUiService.buildAuthorForm author
taackUiService.show new UiBlockSpecifier().ui {
modal {
form tableFormSpecifier
}
}
}
@Transactional
def deleteAuthor(Author author) {
author.delete()
redirect action: 'index'
}
@Transactional
def saveAuthor() {
taackSaveService.saveThenReloadOrRenderErrors(Author)
}
def showAuthor(Author author) {
UiShowSpecifier showSpec = new UiShowSpecifier().ui(author, {
fieldLabeled author.firstName_
fieldLabeled author.lastName_
})
taackUiService.show(new UiBlockSpecifier().ui {
modal {
show showSpec
}
})
}
default.firstName.label=First Name
default.lastName.label=Last Name
default.dateOfBirth.label=Date of Birth
myBooks.listAuthor.label= Authors
Vous disposez maintenant de menus distincts pour les livres et les auteurs, mais ils fonctionnent actuellement de manière indépendante. Et si vous pouviez sélectionner un auteur directement dans la liste des auteurs enregistrés lors de la création d’un livre ? Implémentons cette fonctionnalité.
Lier les auteurs aux livres
Author :String toString() {
return firstName + ' ' + lastName
}
Book :Author author
|
Remplacez toutes les occurrences de |
|
Modifier le nom ou le champ d’une classe est fortement déconseillé, car cela oblige à effacer les fichiers contenant les données précédemment ajoutées.
Vous devez supprimer |
messages.properties :default.author.label=Author Name
buildAuthorTable situé dans MyBooksUiService :rowColumn {
rowField authorIterator.firstName_
rowAction ActionIcon.SHOW * IconStyle.SCALE_DOWN,
MyBooksController.&showAuthor as MethodClosure, authorIterator.id
// Add the following if statement
if (isSelect)
rowAction tr('default.role.label'), ActionIcon.SELECT * IconStyle.SCALE_DOWN, authorIterator.id, authorIterator.toString()
}
Cela ajoutera un bouton de sélection permettant de choisir l’auteur lors du remplissage de bookForm. Utilisez import static taack.render.TaackUiService.tr.
buildBookForm situé dans MyBooksUiService et remplacez field book.author_ :ajaxField book.author_, MyBooksController.&selectAuthor as MC
Cette ligne appellera selectAuthor lors de la sélection d’un auteur dans bookForm.
Implémentons maintenant selectAuthor.
selectAuthor dans MyBooksController :def selectAuthor() {
UiTableSpecifier t = myBooksUiService.buildAuthorTable true (1)
UiFilterSpecifier f = myBooksUiService.buildAuthorFilter()
taackUiService.show new UiBlockSpecifier().ui {
modal {
tableFilter f, t
}
}
}
| 1 | Notez que isSelect vaut true.
Cela affichera une version compacte du tableau Author et de son filtre lors de la sélection d’un auteur pour le formulaire de livre. |
Vous disposez désormais d’une interface CRUD entièrement fonctionnelle pour vos entités Book et Author à l’aide du framework DSL Taack, sans avoir besoin d’écrire manuellement des vues HTML ou GSP.
Vous êtes maintenant prêt à explorer les fonctionnalités plus avancées du framework Taack UI.
Bienvenue !
Dépannage et erreurs courantes
ERROR: Cannot resolve symbol 'MyBooksUiService'
Vous avez oublié de recharger Gradle après avoir créé le plugin. Cliquez sur le bouton de rechargement de Gradle dans votre IDE.
ERROR: Field 'authorName_' not found
Vous avez renommé le champ authorName en author (une référence à Author). Assurez-vous d’avoir remplacé tous les authorName_ par author_ à la fois dans le formulaire et dans le tableau.
ERROR: No matching method for action...
Assurez-vous d’avoir déclaré la méthode correspondante dans MyBooksController et d’avoir utilisé correctement la syntaxe MethodClosure : MyBooksController.&myMethod as MethodClosure.
Il vous manque peut-être l’annotation @Transactional sur la méthode saveBook ou vous avez oublié d’importer taackSaveService.
Si vous avez modifié des champs dans des classes du domaine, vous devez supprimer les fichiers de base de données :
intranet/server/intranetDb.mv.db
intranet/server/intranetDb.trace.db. Attention, cela effacera toutes les données enregistrées précédemment.
Vous utilisez peut-être la mauvaise annotation @Transactional. Veillez à utiliser celle du package GORM et non celle de Javax.
Vous souhaiterez peut-être vérifier l’intégrité des données et les DDL générés par la base de données embarquée H2.
Pour ce faire, vous devez ajuster les paramètres de Spring Security.
Naviguez vers Root → /server → /grails-app → /conf
Vous y trouverez un fichier application.groovy.
Ajoutez
[pattern: '/h2-console/**', access: ['permitAll']],
à ce bloc de configuration :
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/', access: ['permitAll']],
[pattern: '/error', access: ['permitAll']],
[pattern: '/index', access: ['permitAll']],
[pattern: '/index.gsp', access: ['permitAll']],
[pattern: '/shutdown', access: ['permitAll']],
[pattern: '/assets/**', access: ['permitAll']],
[pattern: '/**/js/**', access: ['permitAll']],
[pattern: '/**/css/**', access: ['permitAll']],
[pattern: '/**/images/**', access: ['permitAll']],
[pattern: '/**/favicon.ico', access: ['permitAll']]
]