java -fullversion openjdk full version "17.0.5+1"
Install
Requirements
First, install the following:
-
Java (JDK-17 or JDK-19)
-
git
Optionally, you can also install:
-
Solr-9.6.x (Used for intranet-wide search bar)
-
Postgresql
Java Version
Type the following command:
On Mac, you can install Jdk-17 using brew command:
brew install openjdk@17
Cloning the ready-to-dev Intranet
git clone https://github.com/Taack/intranet.git
This repository includes a stripped-down version of an intranet on top of which you can build more applications. It includes "Crew", the user management system, and its security dependency: Spring Security Plugin.
Start your intranet
Navigate inside the newly cloned intranet folder and start the server by executing this command:
./gradlew server:bootRun
If you want Gradle to watch app module to hot restart the server at each modification, launch the server adding the -Dgrails.run.active=true
option:
./gradlew -Dgrails.run.active=true server:bootRun
After a while you should see in the console:
Grails application running at http://localhost:9442
You can now access the intranet by going to that address in your browser. You can also log in with the default credentials (username: admin; password: ChangeIt). The password can be changed in server/grails-app/conf/application.yml
.
Configuring persistent Db
If you want data to be persistent, change the server/grails-app/conf/application.yml
file to use a persistent database.
Here we want H2 to persist data in a file in development mode:
environments:
development:
dataSource:
dbCreate: update (1)
url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE (2)
1 | Creation mode (see Grails GORM docs) |
2 | prodDb : root of the filename containing data |
Producing a Jar file for Tomcat
Instead of bootRun
task, use assemble
task:
./gradlew server:assemble
The server jar file is produced into server/build/libs
folder. You can import it in your Tomcat production server or test it with:
cd server/build/libs
java -jar server-[version].jar
It will be faster, assets will be compiled and merged.
Make sure you’re not already running the application |
Set up your IDE
We highly recommend using the latest version of IntelliJ Ultimate Edition for its comprehensive support of Groovy and Grails. We are also exploring support for other IDEs such as Eclipse and Visual Studio Code, as well as considering support for IntelliJ non-Ultimate Edition.
IntelliJ Ultimate Edition
We recommend installing the IntelliJ Taack Plugin that you can install from the Intellij marketplace TaackUiASTAutocomplete or directly from the source code repository.
To open the project, select intranet/settings.gradle
.
Make sure that the JDK used with Gradle is at version 17 (or 19):
Visual Studio Code
If you really want to use Visual Studio Code, we recommend the following extensions to use this framework:
Keep in mind that most of the Grails and Taack Framework features will not be recognized by VSCode but are still usable. In addition, VSCode will not help you with imports, code navigation, and many QoL features will be unavailable since groovy support is not as extensive as IntelliJ.
First app
How app works with Taack Framework
Your intranet project has two parts:
-
The app plugins, contained in the
app
folder, they are micro-projects each containing their dependencies, build, etc.Crew
for example is an app plugin. For more in-depth explanation about how plugins are declared to the server, check here. -
The main server, that uses all the app plugins that are implemented in its
build.gradle
and render them on the main intranet page.
Creating a new app plugin
To create a new app plugin (a Gradle module), be sure to pull the latest version of your intranet from our GitHub repository (see installation)
Once it is done, you can execute the following command from your intranet root folder:
./gradlew -DmodName=myToto server:generateTaackAppTask
The newly created app should appear in the app
folder.
If you want the IDE to detect the newly created app module, you have to reload gradle projects, clicking on the following icon:
Managing Future Updates
Most of the time, updating your intranet, should imply to update buildSrc
folder, gradle.properties
, and also, less often, apps build.gradle
files.
In fact, this process is common in long-term software dev, and should not scare newcomers. |
Building an app-wide menu
First, let’s focus on the navigation for your new app. The initial step is to define how the menu will be constructed. The basic boilerplate code for that is as follows:
static private UiMenuSpecifier buildMenu(String q = null) {
new UiMenuSpecifier().ui {
menu SomeController.&action as MC
menu this.&anotherAction as MC
// import org.codehaus.groovy.runtime.MethodClosure as MC
}
}
We create a UiMenuSpecifier object to define the menu structure, and then specify the desired content for that menu.
menu TurboController.&action as MC
In this case, menu
creates a simple link in the navigation bar. Action
is the displayed name of the link, and TurboController.&action
(using MethodClosure) defines the action to which the link will redirect.
You can see a full list of items you can put in your menu in the Menu DSL.
Creating a table
Now let’s display a table in our index page by incorporating the following boilerplate:
UiTableSpecifier tableSpecifier = new UiTableSpecifier()
tableSpecifier.ui {
//Add table content inside the closure here
}
Here we are defining a new Table that will list Book instances. Then we can define the table headers by adding the following in the Closure:
new UiTableSpecifier().ui {
// -- Header --
header {
column {
fieldHeader object.title_
}
}
}
Since we are defining our table in a closure, we can add conditions, loops, etc.
Let’s add a column that will only be rendered if the user has the role ROLE_ADMIN
.
// Look if the current user has the Role "ROLE_ADMIN"
User cu = springSecurityService.currentUser as User
boolean isAdmin = cu.authorities*.authority.contains("ROLE_ADMIN")
new UiTableSpecifier().ui {
header {
column {
label object.title_
}
// Column only shown to admin
if (isAdmin) {
column {
label "Delete book"
}
}
}
}
The column with the header "Delete book" will only appear if the current user is an admin.
Now we are going to populate our table, we are going to iterate Book instances in the database by using the iterate
table DSL method.
User cu = springSecurityService.currentUser as User
boolean isAdmin = cu.authorities*.authority.contains("ROLE_ADMIN")
new UiTableSpecifier().ui {
header {
column {
label object.title_
}
// Column only shown to admin
if (isAdmin) {
column {
label "Delete book"
}
}
}
iterate(taackFilterService.getBuilder(Book)
.setMaxNumberOfLine(8)
.setSortOrder(TaackFilter.Order.DESC, object.title_)
.build()) { Book book ->
rowColumn {
rowField book.title_ //The underscore is needed here
}
// If the user is an admin display a column with a button link
// to redirect towards the book deletion action
if (isAdmin) {
rowColumn {
rowAction ActionIcon.DELETE,
this.&deleteBook as MC, book.id
}
}
}
}
For each book in our list, we make a new row with the title of the book in the first column and a Delete button in the second column if the user is an admin.
(We’re redirecting to index
since we didn’t create a delete method yet).
Your table is now complete we just need to render it on the page.
To render previously built UiSpecifiers we need to use taackUiService
it should already be imported in the controller created by the create-taack-app
command.
Add the following code below your table code:
taackUiService.show(new UiBlockSpecifier().ui {
table tableSpecifier
}, buildMenu())
taackUiService.show(UiBlockSpecifier block, UiMenuSpecifier menu)
will be in charge of rendering the specification we give him.
In this case we want to display an ajaxBlock
that contains a table
named "Book table", we pass our previously created tableSpecifier
as an argument, and we set the width of the table to MAX
so it takes the entire page.
We also use our previously created static buildMenu()
method as the second arguments on show()
to render your menu with the page.
You can now start the server and navigate to your new app. The table should be functional, but currently you will only see the table headers since there are no books in your database. So let’s proceed with creating a form and saving objects into the database.
Adding buttons to a table block
We are going to add a button to your Book table that will open a modal using AJAX to create a new Book. To achieve this, we need to add a closure in the table, like so:
taackUiService.show(new UiBlockSpecifier().ui {
table 'Book table', tableSpecifier, BlockSpec.Width.MAX, {
//Added Closure here
if (isAdmin())
action ActionIcon.CREATE, this.&bookForm as MC
}
}, buildMenu())
Now an admin will see a Create button on the top-right of the table.
The action
method is composed of the following parameters:
-
The icon used by the button must be an ActionIcon enum value.
-
The action that the button will redirect to
Creating a form and saving objects
We are now going to make the form that will be used both for creating but also updating them.
To manage both case we are first going to define our bookForm
action and then initialize either a new Book or read if a Book id has been passed as request parameters.
def bookForm(Book book) {
book ?= new Book(params)
}
Now we create a FormSpecifier
defining our form and its content.
UiFormSpecifier form = new UiFormSpecifier()
form.ui book, {
//Section of fields
section "Book details", {
field book.title_
field book.author_
}
//Save button
formAction this.&saveBook as MC
}
Once your form is defined, you can display it using the taackUiService.show()
method
UiBlockSpecifier b = new UiBlockSpecifier()
b.ui {
modal {
form form, BlockSpec.Width.MAX
}
}
taackUiService.show(b)
This time we don’t specify buildMenu
in our show since we don’t want the menu to be rendered inside the modal!
Remember to create the saveBook
action:
@Secured("ROLE_ADMIN")
@Transactional
def saveBook(String redirectAction) {
taackSaveService.saveThenReloadOrRenderErrors(Book)
}
N.B.: See Close Modal and reload page for not having to use redirectAction
when saving…
Since we only want admin to be able to create a book, we add the @Secured
annotation, for more information about security annotations we recommend to check out the grails-spring-security-core documentation.
Showing an object
Now that we can create books and see a list of them in a table, let’s display them in more details in a modal.
Once again we define the specifier, and we will render it in a block through taackUiService.show()
.
def showUser(Book book) {
// Define the show displayed fields
UiShowSpecifier showSpec = new UiShowSpecifier().ui(book, {
fieldLabeled book.title_
fieldLabeled book.author_
})
taackUiService.show(new UiBlockSpecifier().ui {
modal {
show showSpec
}
})
}
We also need to display a link to this page in the table. To add a link in the table, add the following line in the same rowColumn (Below the book title field) that you want the button to appear:
rowAction
ActionIcon.SHOW * StyleModifier.SCALE_DOWN, (1)
TurboController.&showBook as MC, book.id
1 | Here, we reduce the size of the icon with the multiply operator |
This will create a small button in the table cell that will open a modal with the corresponding book information.
Note that ActionIcon
was multiplied with a IconStyle
to change it’s size in this case.
Deleting an object
Remember the Delete button we added to our table?
Let’s make it functional by updating the action name in the table to "&deleteBook
" and creating a corresponding controller action with the same name.
@Transactional
@Secured(['ROLE_ADMIN'])
def deleteBook(Book book) {
book.delete()
redirect action: 'index'
}
N.B.: In some cases, it is better to add a field enable
to mask disabled objects instead of deleting them.
That’s it!
We use Grails delete
method to delete the book from the database and then redirect back to the index
action where the book table is.
You now have a fully working CRUD for your book class without touching any HTML/GSP files!
You are now fully prepared to explore the more advanced features of the Taack Ui Framework.
Welcome!