Guida all’installazione

Requisiti

Per iniziare, assicurati che i seguenti strumenti siano installati sulla tua macchina:

Installare i prerequisiti

macOS (Homebrew)
Installare JDK 24 e Git usando Homebrew
$ brew install openjdk@25
$ brew install git
Windows (Winget)
Installare JDK 25 e Git usando Winget
winget install --id Microsoft.OpenJDK.25
winget install --id Git.Git

Clonare l’intranet pronta per lo sviluppo

Clona il repository iniziale:
$ git clone https://github.com/Taack/intranet.git

Questo repository include uno scheletro minimale di intranet (gestione utenti Crew + plugin Spring Security) pronto per lo sviluppo.

Avviare la tua intranet

Entra nella cartella dell’intranet appena clonata e avvia il server:
$ cd intranet
$ ./gradlew server:bootRun

Il comando ./gradlew utilizza il Gradle Wrapper, quindi non è necessario installare Gradle manualmente.

Una volta completata la build, dovresti vedere:
Grails application running at http://localhost:9442

TODO: Accedi all’intranet aprendo quell’indirizzo nel tuo browser. Utilizza le credenziali di accesso predefinite:

  • Nome utente: admin

  • Password: ChangeIt

Reload
Figure 1. Credenziali

Per modificare la password di amministratore predefinita, modifica i seguenti file:

  • server/grails-app/conf/application.yml

  • server/grails-app/conf/application-MACOSX.yml

Cerca la riga:

taack.admin.password: ChangeIt

Dopo aver effettuato la modifica, elimina il file intranetDb.mv.db per applicare la nuova password.

application-MACOSX.yml viene utilizzato solo quando l’applicazione rileva un ambiente macOS. Se utilizzi Linux o Windows, verrà ignorato.

L’eliminazione del file intranetDb.mv.db cancellerà definitivamente tutti i dati memorizzati in precedenza.

Configurare un database persistente

Se desideri che i dati siano persistenti, modifica i file server/grails-app/conf/application.yml e server/grails-app/conf/application-MACOSX.yml per utilizzare un database persistente.

Qui vogliamo che H2 salvi i dati in un file in modalità di sviluppo:
environments:
    development:
        dataSource:
            dbCreate: update        (1)
            url: jdbc:h2:./intranetDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE (2)
1 Modalità di creazione (vedi la documentazione di Grails GORM)
2 intranetDb: radice del nome del file contenente i dati

Produrre un file JAR per Tomcat

Usa il task assemble invece di bootRun:
$ ./gradlew server:assemble

Il JAR assemblato (ad es., server-0.6.jar) si troverà in server/build/libs.

Puoi distribuirlo sul tuo server di produzione o testarlo localmente
cd server/build/libs
java -jar server-[version].jar   (1)
1 Il numero di versione si trova in build.gradle. Il valore predefinito è 0.6

Assicurati che l’applicazione non sia già in esecuzione quando avvii una nuova istanza.

Configurare il tuo IDE

Consigliamo vivamente di utilizzare l’ultima versione di IntelliJ Ultimate Edition per il suo supporto completo a Groovy e Grails.

IntelliJ Ultimate Edition

Consigliamo di installare l'IntelliJ Taack Plugin, che puoi installare dall’IntelliJ Marketplace TaackUiASTAutocomplete o direttamente dal repository del codice sorgente.

Apri il progetto, vai su File > Open…​ e seleziona intranet/settings.gradle.

Assicurati che Gradle stia utilizzando il JDK 25:

Apri le impostazioni di Gradle e verifica la versione del JDK di Gradle

install java25 gradle select

1 Apri le impostazioni di Gradle
2 Verifica che la versione della JVM di Gradle sia 25 (il fornitore può essere qualsiasi)

Visual Studio Code

Se preferisci Visual Studio Code, consigliamo le seguenti estensioni per utilizzare questo framework:

La maggior parte delle funzionalità di Grails e del Taack Framework non verrà riconosciuta da VS Code. Le importazioni, la navigazione e l’assistenza al codice potrebbero essere limitate. IntelliJ rimane l’IDE preferito per un supporto completo.

Prima applicazione

Come funziona l’applicazione con il Taack Framework?

Il tuo progetto intranet è diviso in due parti:

  • I plugin dell’applicazione, situati nella cartella app, sono micro-progetti, ciascuno con le proprie dipendenze, la propria build, ecc. (ad es., Crew). Per una spiegazione più approfondita su come vengono dichiarati i plugin, consulta Informazioni sui plugin.

  • Il server principale utilizza tutti i plugin dell’applicazione implementati nel suo file build.gradle e li visualizza nella pagina principale dell’intranet.

Creare un nuovo plugin dell’applicazione

Per creare un nuovo plugin dell’applicazione (un modulo Gradle), assicurati di scaricare l’ultima versione della tua intranet dal nostro repository GitHub (vedi installazione).

Esegui il seguente comando dalla cartella principale della tua intranet:
$ ./gradlew -DmodName=myBooks server:generateTaackAppTask

Questo crea una nuova cartella app/myBooks.

Sotto intranet/app/myBooks/grails-app/domain, sostituisci la directory myBooks con my.books, il che garantirà l’accesso alle classi create in domain da controllers e services poiché faranno parte dello stesso package.

Reload
Figure 2. Contenuto di myBooks

Per far riconoscere al tuo IDE il modulo appena creato, ricarica Gradle facendo clic sull’icona mostrata di seguito:

Reload
Figure 3. Ricarica di Gradle

Gestire i futuri aggiornamenti

Nella maggior parte dei casi, l’aggiornamento della tua intranet comporterà l’aggiornamento della cartella buildSrc, del file gradle.properties e, meno frequentemente, dei file build.gradle delle applicazioni.

Infatti, questo processo è comune nello sviluppo software a lungo termine e non dovrebbe intimorire i nuovi arrivati.

Costruire un menu per l’intera applicazione

TODO: Costruiremo due menu, uno per i libri e uno per gli autori. In ciascun menu potrai aggiungere nuovi elementi, filtrarli e visualizzare ogni voce in una tabella.

Concentriamoci sulla navigazione per la tua nuova applicazione, myBooks. Il passo iniziale consiste nel definire come verrà costruito il menu. Vai su grails-app/services/my/books/MyBooksUiService.

Dovresti vedere il seguente codice (che può variare leggermente in base alla versione):
static UiMenuSpecifier buildMenu(String q = null) { (1)
    new UiMenuSpecifier().ui {
        menu MyBooksController.&index as MC
    }
}
1 Aggiungi static se non è già presente.

Creiamo un oggetto UiMenuSpecifier per definire la struttura del menu, quindi specifichiamo il contenuto da visualizzare.

menu MyBooksController.&index as MC

In questo caso, menu crea un semplice link nella barra di navigazione. index è il nome mostrato per il link e MyBooksController.&index (utilizzando MethodClosure) definisce l’azione a cui il link reindirizzerà.

Creare le classi

Crea una classe Groovy chiamata Author in grails-app/domain/my/books.

Reload
Figure 4. Creare una classe
Aggiungi il seguente codice:
import grails.compiler.GrailsCompileStatic
import taack.ast.annotation.TaackFieldEnum

@GrailsCompileStatic
@TaackFieldEnum
class Author {
    String firstName
    String lastName
    Date dateOfBirth
}

Crea un’altra classe Groovy chiamata Book in grails-app/domain/my/books.

Aggiungi il seguente codice:
import grails.compiler.GrailsCompileStatic
import taack.ast.annotation.TaackFieldEnum

@GrailsCompileStatic
@TaackFieldEnum
class Book {
    String title
    String authorName
    int numberOfPages
}

Usa sempre @GrailsCompileStatic per le prestazioni e la sicurezza dei tipi. Usa @TaackFieldEnum, maggiori informazioni qui.

Creare una tabella

Ora visualizziamo una tabella nella nostra pagina listBook. Vai su grails-app/services/my/books/MyBooksUiService.

Crea un nuovo Specifier di tabella chiamato buildBookTable:
UiTableSpecifier buildBookTable() {
    Book book = new Book()
    UiTableSpecifier bookTableSpecifier = new UiTableSpecifier()
    bookTableSpecifier.ui {
    //Add table content inside the closure here
    }
}

Qui stiamo definendo una nuova tabella che elencherà le istanze di Book. Usa import taack.ui.dsl.UiTableSpecifier.

Per definire le intestazioni della tabella aggiungi il seguente codice nella closure (importa le classi necessarie):
bookTableSpecifier.ui {
    // -- Header --
    header {
        column {
            sortableFieldHeader book.title_ (1)
        }
        column {
            label book.authorName_
        }
        column {
            label book.numberOfPages_
        }
        column {
            label "Delete book"
        }
    }
}
1 Nota che qui usiamo sortableFieldHeader, questo rende la colonna ordinabile; puoi ordinare i libri in ordine alfabetico facendo clic sull’intestazione book.title. Ora popoleremo la nostra tabella iterando sulle istanze di Book nel database utilizzando il metodo DSL di tabella iterate.
Aggiungi il seguente codice (inizializza e importa le classi necessarie):
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 Usa import taack.domain.TaackFilterService.
2 Verranno visualizzati solo i primi otto libri.
3 Imposta l’ordine di visualizzazione in base al titolo dei libri in ordine decrescente (usa import taack.domain.TaackFilter).
4 Il trattino basso (underscore) è necessario qui.
5 Usa import taack.ui.dsl.common.ActionIcon import taack.ui.dsl.common.IconStyle.

Dobbiamo creare l’azione deleteBook. Per ora la lasceremo vuota.

Vai su grails-app/controllers/my/books/MyBooksController.groovy.

Aggiungi il seguente metodo dopo l’azione index:
def deleteBook () {}

Per ogni libro nella nostra lista, creiamo una nuova riga con il titolo del libro nella prima colonna, seguito dall’autore, dal numero di pagine e da un pulsante Delete nella quarta colonna.

La tua tabella è ora completa; dobbiamo solo renderizzarla nella pagina. Per renderizzare gli UiSpecifier precedentemente costruiti, dobbiamo usare taackUiService, che dovrebbe essere già importato nel controller creato dal comando create-taack-app.

Lo faremo in un’azione chiamata listBook: all’interno del metodo index, sostituisci il suo contenuto con un reindirizzamento all’azione listBook.

Reindirizza all’azione listBook:
def index() {
    redirect action: 'listBook'
}
Crea un’azione chiamata listBook e aggiungi il seguente codice:
UiTableSpecifier tableBookSpecifier = myBooksUiService.buildBookTable() (1)

        taackUiService.show(new UiBlockSpecifier().ui {
            table tableBookSpecifier
        }, MyBooksUiService.buildMenu())
1 Ottiene il tableSpecifier che abbiamo creato da MyBooksUiService.

Usa import taack.ui.dsl.UiTableSpecifier.

taackUiService.show(UiBlockSpecifier block, UiMenuSpecifier menu) si occuperà di renderizzare la specifica che gli passiamo. Utilizziamo il metodo statico buildMenu() creato in precedenza da MyBooksUiService come secondo argomento di show() per renderizzare il menu insieme alla pagina.

Ora puoi avviare il server e navigare verso la tua nuova applicazione (myBooks.app). La tabella dovrebbe essere funzionante, ma al momento vedrai solo le intestazioni della tabella poiché non ci sono libri nel tuo database. Procediamo quindi con la creazione di un modulo e il salvataggio degli oggetti nel database.

Reload
Figure 5. Tabella dei libri vuota

Aggiungere pulsanti a un blocco tabella

Aggiungiamo un pulsante alla tabella dei libri che apra una finestra modale tramite AJAX per creare un nuovo libro. Per farlo, dobbiamo aggiungere una closure nella tabella.

Aggiungi il seguente codice:
taackUiService.show(new UiBlockSpecifier().ui {
    table tableBookSpecifier, {
        //Added Closure here
        menuIcon ActionIcon.CREATE, this.&bookForm as MethodClosure (1)
    }
}, MyBooksUiService.buildMenu())
1 Dovrai definire l’azione bookForm, proprio come abbiamo fatto con deleteBook.

Non dimenticare di usare import taack.ui.dsl.common.ActionIcon.

Ora avvia il server e naviga di nuovo verso la tua nuova applicazione; vedrai un pulsante Create in alto a destra nella tabella.

Reload
Figure 6. Pulsante

Il metodo menuIcon è composto dai seguenti parametri:

  1. L’icona utilizzata dal pulsante deve essere un valore dell’enum ActionIcon.

  2. L’azione a cui il pulsante reindirizzerà.

Creare un modulo e salvare oggetti

Ora realizzeremo il modulo che verrà utilizzato per creare e aggiornare i libri. Per gestire entrambi i casi, in MyBooksUiService inizializzeremo un nuovo Book oppure lo leggeremo se è stato passato un ID Book nei parametri della richiesta.

Crea un nuovo Specifier di modulo chiamato buildBookForm:
UiFormSpecifier buildBookForm(Book book) {
    book ?= new Book(params) (1)
}
1 Ciò significa che se book è null, gli viene assegnata una nuova istanza utilizzando i parametri della richiesta.

Ora definiamo il suo modulo e il suo contenuto.

Aggiungi quanto segue in buildBookForm (importa le classi necessarie):
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 Dovrai definire l’azione saveBook (come per deleteBook) in MyBooksController.

Usa import taack.ui.dsl.UiFormSpecifier.

Una volta definito il modulo, puoi visualizzarlo utilizzando il metodo taackUiService.show().

Aggiungi quanto segue all’interno del metodo bookForm in MyBooksController:
def bookForm(Book book) {
    UiFormSpecifier tableFormSpecifier = myBooksUiService.buildBookForm book

    taackUiService.show new UiBlockSpecifier().ui {
        modal {
            form tableFormSpecifier
        }
    }
}

Usa import taack.ui.dsl.UiFormSpecifier.

Questa volta non specifichiamo buildMenu nella chiamata a show, poiché non vogliamo che il menu venga renderizzato all’interno della finestra modale.

Modifica l’azione saveBook (importa e inizializza le classi necessarie):
@Transactional
def saveBook() {
    taackSaveService.saveThenReloadOrRenderErrors(Book)
}

Usa import taack.render.TaackSaveService e import grails.gorm.transactions.Transactional.

Vedi Chiudere la modale e ricaricare la pagina per scoprire come evitare l’uso di redirectAction durante il salvataggio…​

Avvia nuovamente il server; ora dovresti essere in grado di fare clic sul pulsante e compilare il modulo.

Reload
Figure 7. Modulo
Reload
Figure 8. Libri salvati

Nell’angolo in alto a sinistra, puoi passare da un gruppo di libri all’altro utilizzando i numeri di pagina (1 o 2). Questa navigazione appare perché l’elenco è limitato a 8 libri per pagina.

Mostrare un oggetto

Ora che possiamo creare libri e vederne un elenco in una tabella, visualizziamo ogni libro più nel dettaglio utilizzando una finestra modale. Ancora una volta definiamo lo specifier e lo renderizziamo all’interno di un blocco modale utilizzando taackUiService.show().

Crea showBook (importa la classe necessaria):
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
        }
    })
}

Usa import taack.ui.dsl.UiShowSpecifier.

Dobbiamo anche visualizzare un link a questa finestra modale nella tabella. Per farlo, aggiungi la seguente riga nella stessa rowColumn (sotto il campo del titolo del libro) in cui desideri che appaia il pulsante in MyBooksUiService:

Aggiungi 2 righe in 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 Qui riduciamo la dimensione dell’icona con l’operatore di moltiplicazione.

Questo creerà un piccolo pulsante icona nella cella della tabella che aprirà una finestra modale con le informazioni del libro corrispondente.

Nota che ActionIcon è stato moltiplicato per un IconStyle per modificarne la dimensione in questo caso.

Reload
Figure 9. Visualizzazione di un oggetto

Fai clic sull’icona dell’occhio per visualizzare il libro.

Eliminare un oggetto

Ricordi il pulsante Delete che abbiamo aggiunto alla nostra tabella? Rendiamo funzionante il pulsante Delete definendo l’azione deleteBook che richiama.

@Transactional
def deleteBook(Book book) {
    book.delete()
    redirect action: 'index'
}

In alcuni casi, è preferibile aggiungere un campo enable per nascondere gli oggetti disabilitati invece di eliminarli.

Utilizziamo il metodo delete di Grails per eliminare il libro dal database e quindi reindirizziamo all’azione index dove si trova la tabella dei libri.

Eliminare un libro

Nota che i nomi dei campi non sono etichette dirette, ma chiavi i18n come book.numberOfPages.label,default.numberOfPages.label (che fanno riferimento alle chiavi i18n in messages.properties). Modifichiamolo!

Assegnare un nome alle intestazioni

Per impostare il nome delle intestazioni, vai su app/myBooks/grails-app/i18n/messages.properties.

Imposta default.numberOfPages.label e le altre etichette con i nomi che preferisci:
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

Avvia il server per visualizzare i nomi delle etichette aggiornati.

Filtro

Se aggiungiamo troppi libri potremmo perdere tempo a cercare un libro specifico; filtrare i libri può rendere la ricerca più efficiente. Implementiamolo in MyBooksUiService.

Crea un nuovo Specifier di filtro chiamato buildBookFilter:
UiFilterSpecifier buildBookFilter(Book book) {
    UiFilterSpecifier bookFilterSpecifier = new UiFilterSpecifier() (1)

    bookFilterSpecifier.ui Book, { (2)
        section "Book Filter", {  (3)
            filterField book.title_ (4)
        }
    }
}
1 Crea il filtro. Usa import taack.ui.dsl.UiFilterSpecifier.
2 Passa la classe dell’oggetto e la closure contenente le specifiche del filtro tramite il metodo ui.
3 Crea una sezione con etichetta "Book Filter".
4 Aggiungi il campo al filtro, nota il trattino basso alla fine del nome del campo.

Il passo successivo consiste nel visualizzare questo filtro appena creato.

Modifica taackUiService.show in listBook (situato in 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())
}

Usa import taack.ui.dsl.UiFilterSpecifier.

Reload
Figure 10. Filtro

Per informazioni più dettagliate sui filtri, fai riferimento alla documentazione di filtri e tabelle.

Aggiungere un secondo menu

Ricordi la classe Author che abbiamo creato all’inizio? È arrivato il momento di usarla! Creeremo un secondo menu in cui potremo aggiungere autori e filtrarli.

Aggiungi questa riga di codice in buildMenu in MyBooksUiService:
menu MyBooksController.&listAuthor as MC

Questo crea un secondo menu nell’angolo in alto a sinistra per accedere alla pagina degli autori.

Puoi anche creare sottomenu annidati all’interno del menu principale.

label("Menu", {
    subMenu(MyBooksController.&index as MC)
    subMenu(MyBooksController.&listAuthor as MC)
})

Questo crea un sottomenu con etichetta "Menu" contenente un link all’azione index (che corrisponde all’azione listBook) e all’azione listAuthor. Ciò è utile se desideri raggruppare link correlati sotto un’unica voce di menu.

Aggiungi i seguenti builder in 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_
        }
    }
}

Nota che isSelect in buildAuthorTable verrà utilizzato più avanti.

Crea il metodo listAuthor in 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())
}
Aggiungi i metodi necessari deleteAuthor, saveAuthor, authorForm e 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
        }
    })
}
Imposta le nuove etichette predefinite con il nome che preferisci:
default.firstName.label=First Name
default.lastName.label=Last Name
default.dateOfBirth.label=Date of Birth
myBooks.listAuthor.label= Authors

Ora hai menu separati per Libri e Autori, ma al momento operano in modo indipendente. E se potessi selezionare un autore direttamente dall’elenco degli autori registrati quando crei un libro? Implementiamo questa funzionalità.

Reload
Figure 11. Tabella degli autori

Collegare gli autori ai libri

Aggiungi il seguente metodo alla classe Author:
String toString() {
    return firstName + ' ' + lastName
}
Modifica la variabile nella classe Book:
Author author

Modifica tutte le occorrenze di book.authorName_ in book.author_ e di bookIterator.authorName_ in bookIterator.author_, situate in MyBooksController e MyBooksUiService.

Modificare il nome o il campo di una classe è fortemente sconsigliato, poiché richiederà di cancellare i file contenenti i dati aggiunti in precedenza. Devi eliminare intranet/server/intranetDb.mv.db e intranet/server/intranetDb.trace.db per evitare errori di integrità. Ciò eliminerà tutti i dati salvati in precedenza.

Aggiungi una riga a messages.properties:
default.author.label=Author Name
Modifica buildAuthorTable situato in 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()
}

Questo aggiungerà un pulsante di selezione per scegliere l’autore durante la compilazione di bookForm. Usa import static taack.render.TaackUiService.tr.

Modifica buildBookForm situato in MyBooksUiService, sostituendo field book.author_:
ajaxField book.author_, MyBooksController.&selectAuthor as MC

Questa riga chiamerà selectAuthor quando si seleziona un autore in bookForm.

Implementiamo selectAuthor.

Implementa selectAuthor in MyBooksController:
def selectAuthor() {
    UiTableSpecifier t = myBooksUiService.buildAuthorTable true (1)
    UiFilterSpecifier f = myBooksUiService.buildAuthorFilter()
    taackUiService.show new UiBlockSpecifier().ui {
        modal {
            tableFilter f, t
        }
    }
}
1 Nota che isSelect è true. Questo renderizzerà una versione compatta della tabella Author e del relativo filtro durante la selezione di un autore per il modulo del libro.
Reload
Figure 12. Aggiungere un libro con un autore dalla tabella degli autori
Reload
Figure 13. Selezionare l’autore (fai clic sul pulsante blu)

Ora disponi di un’interfaccia CRUD completamente funzionante per le tue entità Book e Author utilizzando il framework DSL Taack, senza dover scrivere manualmente viste HTML o GSP.

Ora sei pronto per esplorare le funzionalità più avanzate del Taack UI Framework.

Benvenuto!

Risoluzione dei problemi ed errori comuni

Output di errore:
ERROR: Cannot resolve symbol 'MyBooksUiService'

Hai dimenticato di ricaricare Gradle dopo aver creato il plugin. Fai clic sul pulsante di ricarica di Gradle nel tuo IDE.

Output di errore:
ERROR:  Field 'authorName_' not found

Hai rinominato il campo authorName in author (un riferimento ad Author). Assicurati di aver modificato tutti gli authorName_ in author_ sia nel modulo che nella tabella.

Output di errore:
ERROR: No matching method for action...

Assicurati di aver dichiarato il metodo corrispondente in MyBooksController e di aver utilizzato correttamente la sintassi MethodClosure: MyBooksController.&myMethod as MethodClosure.

Il modulo viene visualizzato ma non salva:

Potrebbe mancare l’annotazione @Transactional nel metodo saveBook oppure hai dimenticato di importare taackSaveService.

Errore del database dopo aver rinominato i campi:

Se hai modificato i campi nelle classi di dominio, devi eliminare i file del database: intranet/server/intranetDb.mv.db intranet/server/intranetDb.trace.db. Fai attenzione, poiché questo cancellerà tutti i dati salvati in precedenza.

Il connettore del database non esegue il flush della transazione:

Potresti utilizzare l’annotazione @Transactional sbagliata. Assicurati di utilizzare quella del package GORM e non quella di Javax.

Accedere alla console del database H2:

Potresti voler controllare l’integrità dei dati e i DDL generati dal database H2 integrato.

Per farlo devi modificare le impostazioni di Spring Security.

Naviga verso Root → /server → /grails-app → /conf

Troverai un file application.groovy.

Aggiungi

[pattern: '/h2-console/**',  access: ['permitAll']],

a questo blocco di configurazione:

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']]
]