Dueuno Elements #2 — Let’s be CRUD(e) for once
DISCLAIMER: I LOVE BEING CRUDE. DON’T JUDGE ME. IT’S JUST A GAME.
Okay, we have an empty box now. Let’s fill it with some fruits (fruit is good for our health).
Features
An application is a set of Features. A Feature is made of a Menu Item — so that our users can access it — and a Content, a bidimensional area we can fill with buttons and stuff.
The dark area on the left is the Main Menu. The light area is where the Content is rendered.
We create features in the application initialization method.
Edit
~/demo/grails-app/init/BootStrap.groovy
package com.example
import dueuno.elements.core.ApplicationService
class BootStrap {
ApplicationService applicationService
def init = { servletContext ->
applicationService.init {
registerFeature(
controller: 'person',
icon: 'fa-user',
favourite: true,
)
}
}
def destroy = {
}
}
The object in charge of creating a feature, and other application-wide stuff, is the ApplicationService
.
To register a Feature we need to specify the name of the controller that implemets the feature. Eg. in our case, the feature will be available at the following URL:
http://localhost:8080/person
We can couple it with an optional icon, from the free set provided by Font Awesome, to decorate the Menu Item.
Last but not least, we can register the feature as a favourite one. Favourite features are immediatly available clicking the Home buttom (top-left).
Execute
./gradlew bootRun
Click on the feature to get a (404) Not Found! message. That’s fine, we still haven’t implemented anything.
Controllers and Actions
Look at the URL. It says:
We have three things here:
http://localhost:8080
This is theaddress
andport
where we can find our application.person
This is the Controller name. A Controller is just a container. It contains Actions.index
This is an Action name. An Action is a piece of code that implements some logic.
Even if a Feature is accessed from a specific URL (controller/action
pair), a Feature may be composed by more than one Controller and many, many… many Actions.
- Controllers are Groovy classes located under the folder:
~/demo/grails-app/controllers/
- Actions are methods of a Controller class.
Structure
It’s time to feed our newborn creature. We are going to implement a CRUD view, writing the PersonController
, with different actions in order to display a list of people and give the user the ability to create, edit and delete one or more persons.
We create a skeleton first, a controller with just the action firms, so we can focus on the structure.
Create a new file
~/demo/grails-app/controllers/com/example/PersonController.groovy
package com.example
import dueuno.elements.core.ElementsController
import grails.validation.Validateable
class PersonController implements ElementsController {
def index() {
// Displays a list of people
}
def create() {
// Displays a form to input person data
}
def onCreate(PersonValidator val) {
// Creates a new person
}
def edit() {
// Displays a form to edit person data
}
def onEdit(PersonValidator val) {
// Updates a person record
}
def onDelete() {
// Deletes a person
}
}
class PersonValidator implements Validateable {
// Will validate user input
}
Please focus your attention to:
- The controller class name must be suffixed by the word
Controller
. That’s why ourperson
controller is calledPersonController
(this is a convention of the Grails Framework). - The person controller implements
ElementsController
. This makes the Dueuno Elements API available to our actions (NOTE: If you use IntelliJ IDEA Ultimate with the Grails plugin you can avoid implementingElementsController
and everything will magically work as expected. Yay!). - We use a convention to name the actions. When they start with the
on
prefix, they execute some logic in the background. When they don’t, they render a user interface. We are also using a naming standard here, we may change the action names, but for now let’s not add too much complications.
It’s time to be CRUD
Edit
~/demo/grails-app/controllers/com/example/PersonController.groovy
with the code below
package com.example
import dueuno.elements.contents.*
import dueuno.elements.controls.*
import dueuno.elements.core.ElementsController
import grails.validation.Validateable
import java.time.LocalDate
class PersonController implements ElementsController {
static final List personRegistry = [
[id: 1, firstname: 'Gianluca', lastname: 'Sartori', birthdate: LocalDate.of(1979, 6, 24)],
[id: 2, firstname: 'John Luke', lastname: 'Taylor', birthdate: LocalDate.of(1921, 6, 24)],
[id: 3, firstname: 'Juan Lucas', lastname: 'Sastre', birthdate: LocalDate.of(1942, 6, 24)],
]
def index() {
def c = createContent(ContentList)
c.table.with {
columns = [
'firstname',
'lastname',
'birthdate',
]
}
c.table.body = personRegistry
c.table.paginate = personRegistry.size()
display content: c
}
private buildForm(Map obj = null) {
def c = obj
? createContent(ContentEdit)
: createContent(ContentCreate)
c.form.with {
validate = PersonValidator
addField(
class: TextField,
id: 'firstname',
)
addField(
class: TextField,
id: 'lastname',
)
addField(
class: DateField,
id: 'birthdate',
)
}
if (obj) {
c.form.values = obj
}
return c
}
def create() {
def c = buildForm()
display content: c, modal: true
}
def onCreate(PersonValidator val) {
if (val.hasErrors()) {
display errors: val
return
}
def last = personRegistry.max { it.id }
personRegistry << [
id: last ? last.id + 1 : 1,
firstname: params.firstname,
lastname: params.lastname,
birthdate: params.birthdate,
]
display action: 'index'
}
def edit() {
def obj = personRegistry.find { it.id == params.id }
def c = buildForm(obj)
display content: c, modal: true
}
def onEdit(PersonValidator val) {
if (val.hasErrors()) {
display errors: val
return
}
def obj = personRegistry.find { it.id == params.id }
obj.firstname = params.firstname
obj.lastname = params.lastname
obj.birthdate = params.birthdate
display action: 'index'
}
def onDelete() {
try {
personRegistry.removeIf { it.id == params.id }
display action: 'index'
} catch (e) {
display exception: e
}
}
}
class PersonValidator implements Validateable {
String firstname
String lastname
LocalDate birthdate
}
Execute
./gradlew bootRun
Contents
There’s a lot of stuff here. The most important things now are:
- Contents. A Content is the canvas on which we design the UI. To do it we add
Components
andControls
. You can’t see it in the example because we are using preconfigured contents for tables (ContentList
) and forms (ContentCreate
&ContentEdit
) - The
display()
method. Each action terminates its execution with thedisplay()
method. This is the way we display the UI or route from one action to the other.
ATTENTION PLEASE: For the sake of the article I’ve implemented the Business Logic within the controller class. This is not somehting we do. Don’t do it. Don’t. Ever.
Don’t.
Conclusions
In the next article we are going to see how and where to implement the Business Logic adding a database to this Supa-Dupa-Cool-And-Fool application.
In the next-next articles we are going to deepen our intimacy with Contents, Components and Controls. Subscribe and stay tuned.