• Rendered into HTML

  • Rendered into Mails

  • Rendered into PDF

  • Rendered into CSV

Form Validation

screenshot form validation
Figure 1. Localized form

When saving an object, errors are reported, localized, with no additional cost…​

See Grails documentation on Constraints Usage to check your object validity before saving.

Code Sample

Form code sample 1.
def editUser(User user) {
    user ?= new User(enabled: true)

    UiFormSpecifier f = new UiFormSpecifier()       (1)
    f.ui user, {                                    (2)
        row {                                       (3)
            col {
                section "User", {                   (4)
                    field user.username_            (5)
                    field user.firstName_
                    field user.lastName_
                    ajaxField user.manager_,        (6)
                        this.&selectUserM2O as MC
                    ajaxField user.mainPicture_,
                        this.&selectUserMainPicture as MC
                    field user.password_
                }
            }
            col {
                section "Coords", {
                    field user.businessUnit_
                    field user.mail_
                    field user.subsidiary_
                }
            }
            col {
                section "Status", {
                    field user.enabled_
                    field user.accountExpired_
                    field user.accountLocked_
                    field user.passwordExpired_
                }
            }
        }
        formAction this.&saveUser as MC, user.id    (7)
    }
    taackUiService.show new UiBlockSpecifier().ui { (8)
        modal {
            form f
        }
    }                                               (9)
}
1 Create the form
2 Pass object to be edited and spec of the form
3 One row, three columns
4 Section called "User"
5 Basic field, terminating with an underscore, those symbols are generated by Taack AST on class with the TaackFieldEnum annotation
6 Many-To-One relation, the action selectUserM2O will open a popup to select the manager of the user
7 Specify an action where the form will be sent to. Here it is saveUser, it will save the user
8 Create the block in a modal
9 Display it …​
screenshot dsl user form2
Figure 2. Form inside a modal Window.

Without a Domain Class

DSLs do not need a GORM domain class. Any class implementing grails.validation.Validateable and annotated with @TaackFieldEnum can be used in Form, Show and Table DSLs: field symbols (username_, age_ …​) are generated the same way, and constraints are checked by validate().

This is useful for parameter objects, wizards, or data that is not persisted (computed, coming from another service …​).

Validateable class, no database involved
@TaackFieldEnum                                             (1)
@GrailsCompileStatic
class DslSampleUser implements Validateable {               (2)
    String username
    Integer age
    Date birthday
    DslSampleStatus status = DslSampleStatus.NEW

    static constraints = {                                  (3)
        username blank: false
        age nullable: true, min: 18, max: 68
        birthday nullable: true
    }
}
1 Generates the field symbols used by the DSLs
2 Validateable instead of a domain class
3 Constraints are applied when calling validate(), errors are localized like for domain classes

The form is built as usual, and the action receiving it binds and validates the object itself, since there is nothing to save:

Binding and validating a Validateable object
def saveForm() {
    DslSampleUser u = new DslSampleUser()
    taackSaveService.bindObject(u, params)                  (1)
    if (!u.validate()) {
        taackSaveService.renderErrors(u.errors)             (2)
        return
    }
    taackUiService.show(new UiBlockSpecifier().ui {
        replaceModal {                                      (3)
            show buildUserShow(u)
        }
    })
}
1 Bind request parameters to the object
2 Errors are displayed into the form, the modal stays open
3 Otherwise, replace the form by the submitted values
The Filter DSL builds database queries, so it still requires a domain class.

Asciidoc

You can add an asciidoc editor by creating an EditorOption object and pass it to fieldEditorFromMap or fieldEditor. The editor services come from the taack-ui-asciidoc artifact.

This editor can import images dropped in the text area and import ODF files (image included).

Create EditorOption object

Editor Options
EditorOption.EditorOptionBuilder editor = EditorOption.getBuilder()
editor.addSpanRegexes(TaackBaseAsciidocSpans.spans)
editor.addSpanRegexes(TaackAsciidocTable.spans)
editor.addSpanRegexes(TaackAsciidocPlantUML.spans)
editor.onDropAction(this.&dropEditor as MC, [cmsPage: cmsPage.id])
fieldEditor cmsPage.bodyContent_, editor.build()
innerFormAction this.&previewBody as MC

Create dropEditor action

@Transactional
def dropEditor() {
    CmsPage page = CmsPage.get(params.long('cmsPage'))
    params.remove('cmsPage')
    if (params.get('onpaste')) {
        render "${convertersToAsciidocService.convertFromHtml(params.get('onpaste') as String)}"
    } else {
        final List<MultipartFile> mfl = (request as MultipartHttpServletRequest).getFiles('filePath')
        final mf = mfl.first()

        if ([AttachmentContentType.SHEET_ODS.mimeType, AttachmentContentType.LO_TEXT.mimeType].contains(mf.contentType)) {
            render convertersToAsciidocService.convert(page, mf.inputStream)
        } else {
            CmsImage cmsImage = taackSaveService.save(CmsImage)
            if (cmsImage && page) {
                cmsImage.cmsPage = page
            }
            render "image::${cmsImage.originalName}[]"          (1)
        }
    }
}
1 Render asciidoc code that will be displayed in the editor

Import images via a custom service

Use taackEditorService to convert page, and save images in files dropped to your custom object.

String convert(CmsPage page, InputStream inputStream) {
    taackEditorService.convert(new TaackEditorService.ISaveImage() {
        @Override
        String saveImage(String imagePath, byte[] image) {
            return saveImage(page, imagePath, image)            (1)
        }
    }, inputStream)
}

String convertFromHtml(String html) {
    taackEditorService.convertFromHtml(html)
}
1 Images will be attached to the page object.

Layout

Rows, columns and tabs
new UiFormSpecifier().ui reminder, {
    section {
        field reminder.title_
        rowCols 3, {                                (1)
            field reminder.dueDate_
            field reminder.status_
            field reminder.priority_
        }
        row {                                       (2)
            col BlockSpec.Width.TWO_THIRD, {
                ajaxField reminder.assignee_, CrewController.&selectUserM2O as MC
            }
            col {
                readOnly reminder.dateCreated_      (3)
            }
        }
    }
    tabs {                                          (4)
        tabLabel 'Body', {
            field reminder.body_
        }
        tabLabel 'Advanced', {
            section 'Options', true, {              (5)
                field reminder.notifyAssignee_
            }
        }
    }
    formAction this.&saveReminder as MC
}
1 rowCols splits the fields into 3 columns, avoiding row, then col, col, col …​
2 row and col for finer control of the width
3 Displayed but not editable
4 Tabs inside the form
5 Section initially collapsed
screenshot dsl form rowcol
Figure 3. rowCols in Form

DSL Symbols Hierarchy

Diagram
Figure 4. Symbols hierarchy diagram for Form DSL

DSL Elements

Root

  • ui: object to edit, optionally followed by fields that cannot be modified (lockedFields)

Inputs

  • hiddenField: add an input of type hidden into the form.

  • field: add an input with a label. An optional NumberFormat or IEnumOptions can be given.

  • ajaxField: add a many-to-one or many-to-many field. The MethodClosure must point to a block showing a modal window, additional fields can be transmitted to this action

  • readOnly: display a value that cannot be modified

  • fieldFromMap, ajaxFieldFromMap: edit an entry of a Map field

  • fieldEditor, fieldEditorFromMap: Asciidoc editor (see Asciidoc)

Structure

  • section: add a form section, optionally labeled and initially collapsed

  • row, col: split a section into columns, see BlockSpec.Width

  • rowCols: split the fields into N columns

  • tabs, tabLabel: tabs inside the form

Actions

  • formAction: action called when submitting the form, optionally a label, an id, params and a ButtonStyle

  • innerFormAction: secondary button, posting the form to another action (preview, compute …​)

  • triggerUpdate: reload the form when one of the given fields is modified