(Quick Reference)

Grails Admin Interface - Reference Documentation

Authors: Kaleidos Open Source

Version: 0.6.4

1 Introduction

The Grails Admin Interface plugin provides and easy and secure interface to administer out-of-the-box your Grails applications. Absolute functional from scratch, gives much more power with his extensible widget system.

The information in this guide is for three developer profiles:

  • Plugin users: installation and configuration.
  • Custom Widget developers: create your own widgets.
  • Plugin developers: contribute to this plugin.

1.1 Release history and changelog

  • 23th September 2014
    • 0.6.5: Updated bootstrap and fixed static resources
  • 20th June 2014
    • 0.6.4: Changed default widget assets location
  • 18th June 2014
    • 0.6.3: Fixed problem with 404 and resources. Javascript cleanup
  • 11th May 2014
    • 0.6.2: Fixed resources bug
  • 23rd May 2014
    • 0.6.1: First ready for world testing release
  • 9th May 2014
    • 0.1.1: First internal release

1.2 Contributors

1.3 Dependencies

This plugin doesn't import any plugin but needs that your application have installed the following dependencies:
  • Spring Security Core v1.X or v2.X for authorization/authentication:

Spring Security Core 2.x

1.4 Installation

Installation

To install you should include in your BuildConfig.groovy

plugins {
    runtime ":admin:<version>.<minorVersion>"
}

It's recommended that you include a Spring Security Core plugin to secure the administration.

plugins {
    // Spring Security v1.X , example:
    compile ":spring-security-core:1.2.7.3"
}

plugins {
    // … or v2.X , example:
    compile ":spring-security-core:2.0-RC3"
}

Configuration

Is not mandatory, but to see plugin works you have to edit your app Config.groovy and include the list of domains to start working with:

grails.plugin.admin.domains = [ "conferences.Talk", "conferences.Speaker" ]

Extend Behaviour with Custom Widgets

You can also extend the default behaviour creating your own custom widgets. See Custom Widgets section for more information.

grails.plugin.admin.domain.Conference = {
    widget "coordinates", "sample.MapWidget", height:400, width:600
}

1.5 Sample

Sample Application

There is a sample application that you can checkout at github:

https://github.com/kaleidos/grails-admin-interface-sample

Also, you cant test the sample application online at:

http://sample.grailsadmininterface.kaleidos.net/

2 Screenshots

The Grails Admin plugin looks modern and nice.

Dashboard

List entities for a domain class

New entity

Edit entity

Edit relation field

New entity for a relation field

Edit entity with relation 1-n

Edit relation 1-n field

Delete dialog

3 Plugin Structure

The Grails Admin plugin integrates seamlessly with your application

Basic view

Advanced view

4 Plugin Configuration

The power of the grails admin plugin is that you can create a full-fleshed grails administration page only by configuration. In order to be flexible enough the plugin provides several configuration for tunning your application.

There are no mandatory configuration to run your application with this plugin, but to start working with it you have to set some properties.

Your Config.groovy would look like this:

grails.plugin.admin.accessRoot = "/myconfadmin"

grails.plugin.admin.domains = [ "conferences.Room", "conferendes.Attendee", "conferendes.Conference", "conferendes.Talk", "conferendes.Speaker" ]

grails.plugin.admin.domain.Conference = "conferences.ConferenceAdmin"

4.1 Integration with Spring Security

Static assets filters

Spring security by default doesn't allow the static resources from the plugin to be served.

You should add the following rules to your Config.groovy in order to allow these:

grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    '/**/libs/**':                    ['permitAll'],
    '/**/js/**':                      ['permitAll'],
    '/**/css/**':                     ['permitAll'],
    '/**/images/**':                  ['permitAll']
]

API Basic Authentication

In order to use Basic Authentication for the Grails Admin API you have to configure the following parameters for Spring Security:

  • Activate Spring security:

grails.plugin.springsecurity.active = true
  • Activate the Basi Authorization filter

grails.plugin.springsecurity.useBasicAuth = true
  • Only apply the filter for the admin API endpoints:

Be careful because "admin" could be replaced with the property "grails.plugin.admin.accessRoot"

grails.plugin.springsecurity.filterChain.chainMap = [
   "/admin/api/**": 'JOINED_FILTERS,-exceptionTranslationFilter',
   '/**': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]

4.2 Global Configuration

Domains List

Necessary

Not mandatory, but necessary to start working with admin

You have to explicitely set the domains that your application is going to manage. The property is a list of strings containing the full class name of your domains.

By default it contains an empty list.

grails.plugin.admin.domains = [
    "conferences.Speaker",
    "conferences.Talk"
]

Optionaly, you can group your domains so they are divided into logical groups.

grails.plugin.admin.domains."Conference Management" = [
    "conferences.Conference",
    "conferences.Talk"
]

grails.plugin.admin.domains."People" = [ "conferences.Attendee", "conferences.Speaker" ]

grails.plugin.admin.domains."Infrastructures" = [ "conferences.Building", "conferences.Room" ]

Admin URL Access Root

Optional

A good security practice is not to use a "standard" URI to access your administration backend.

Grails-Admin allows you to customize the endpoint so it uses a custom one.

By default the value is /admin

grails.plugin.admin.accessRoot = "/admin"

Allow Default Configuration

When a domain has a relationship with another domain (i.e. 1-1 relationship) if the related object has not been defined in the managed domains list you will not be able to interact with it by default.

You can explicitely activate default configuration on objects that are not defined in your domain list by using this property.

Optional

grails.plugin.admin.allowDefaultConfig = false

Security. Access role

Optional

The default role required for a user to access the admin is "ROLE_ADMIN" but you can customize to use any role defined with your application.

grails.plugin.admin.security.role = "ROLE_ADMIN"

Security. Forbid unsecured admin on Production environment

By default the plugin checks if your application is secured or not (currently using Spring Security 1.x and Spring Security 2.x).

If your application is not secured, by default, the plugin will throw an exception when it's boostraped on production environments.

You can override this behaviour by setting this property to "false"

Optional

grails.plugin.admin.security.forbidUnsecureProduction = true

4.3 Per Domain Configuration

You can configure the administration screens with a specific DSL that can be configure either in your Config.groovy or in classes of type "Admin" that also allows the redefinition of some methods.

Configuration on Config.groovy file

grails.plugin.admin.domain.Speaker = {
    create includes: ["name", "description", "dateCreated", "endDate"],
    widget "birthDate", "net.kaleidos.plugins.admin.widget.DateInputWidget", dateFormat: "yyyyMMdd"
}

Configuration on "Admin" entities

Edit Config.groovy to associate your admin class with the domain entity that manage.

grails.plugin.admin.domain.Speaker = "conferences.SpeakerAdmin"

and create admin class.

Place it under grails-app/admin directory.

Note: You can place your admin class wherever you want in classpath, but we recommend this to possible future artifacts control

package conferences

class SpeakerAdmin { static options = { create includes: ["name", "description", "dateCreated", "endDate"], widget "birthDate", "net.kaleidos.plugins.admin.widget.DateInputWidget", dateFormat: "yyyyMMdd" }

}

Include properties

You can select which properties the administration will display on a specific operation.

The order will be preserved in the form.

grails.plugin.admin.domain.Speaker = {
    list includes: ["name", "description", "dateCreated", "endDate"]
    create includes: ["name", "description"]
    edit includes: ["name", "description"]
}

Exclude Properties

You can customize which properties will be excluded from the administration screens.

grails.plugin.admin.domain.Speaker = {
    list excludes: ["startDate", "endDate"]
    create excludes: ["startDate", "endDate"]
    edit excludes: ["startDate", "endDate"]
}

Properties Groups

All the properties for a domain can be visually grouped so they have a logic structure.

grails.plugin.admin.domain.Talk = {
    groups {
        "Speaker" fields: [ 'speaker' ]
        "Talk Info" style:"collapse", fields: [ 'talkDate', 'talkTime', 'room' ]
    }
}

If you don't put a property inside a group it will be displayed at the beginning of the form without any group.

Passing properties to widgets

Some widgets need some internal properties. Also, there are properties that you can pass to any widget. In order to do so, you must specify them on the configuration

grails.plugin.admin.domain.Conference = {
    widget "name",  help:"This is a help text"
}

grails.plugin.admin.domain.Talk = {
    widget "talkDate", dateFormat:"dd/MM/yyyy"
}

Specify a widget

If you want to select a specific widget for a property (for example, to use the PasswordInputWidget instead of the default TextInputWidget) you can use this configuration. Further documentation and a list of all the builtin widgets on the Built In Widgets section.

grails.plugin.admin.domain.User = {
    widget "password", "PasswordInputWidget"
}

Custom widgets

For some properties the default editor/renderer may not be enough. You can customize this behaviour writing your own widget. Further documentation on the Custom Widgets section.

grails.plugin.admin.domain.Conference = {
    widget "coordinates", "sample.MapWidget", height:400, width:600
}

5 Widgets

A widget is a component of Grails Admin that represents a property from a domain object instance.

The widget handles the rendering (HTML or JSON) for that property, and also transform the values received for that property into the correct type (for example, translate from String to Date as needed).

The plugin provides a set of built in widgets that should cover the majority of situations. To render other types or to display other type of data in a different way, you can create your own custom widgets.

5.1 Configure widgets

For basic widget configuration, please see Per Domain Configuration section.

Special properties

help

If you add a 'help' string property to a widget, it will be displayed on the GUI.

grails.plugin.admin.domain.Room = {
    widget "board", help:"Has the room a blackboard?"
}

Sample image

5.2 Styling widget instances

stylingWidgets

5.3 Built in widgets

Grails Admin Plugin brings to you several built in widgets, that you can use. The complete list of Built in widgets is this:

Built in Widgets
CheckboxInputWidget
DateInputWidget
DecimalInputWidget
EmailInputWidget
EnumWidget
HiddenInputWidget
LabelWidget
LocaleInputWidget
MapWidget
NumberInputWidget
PasswordInputWidget
RelationPopupOneWidget
RelationSelectMultipleWidget
RelationSelectWidget
RelationTableWidget
SelectWidget
TextAreaWidget
TextInputWidget
UrlInputWidget

If you not specify otherwise on configuration, Grails Admin will select a built-in widget by the type of the attribute(sometimes modified by constraints or by name):

nameTypeConstraintWidget
--inListRelationSelectWidget
-Byte-NumberInputWidget
-Short-NumberInputWidget
-Integer-NumberInputWidget
-Long-NumberInputWidget
-Float-DecimalInputWidget
-Double-DecimalInputWidget
-Character-TextInputWidget
-String-TextInputWidget
--emailEmailInputWidget
--urlUrlInputWidget
-Date-DateInputWidget
-Boolean-CheckboxInputWidget
-Enum-EnumWidget
-Locale-LocaleInputWidget
-File-LabelWidget
-Collection of other Domain Class-RelationTableWidget
-Collection-LabelWidget
-<Other Domain Class>-RelationPopupOneWidget
- Unknown -LabelWidget
id--LabelWidget
dateCreated--LabelWidget
dateCreated--LabelWidget
version--LabelWidget

LabelWidget is used when we don't know how to handle a type or his value, like Currency, File or Collection types, in order to don't scramble your data

5.3.1 CheckboxInputWidget

How would the plugin select automatically this widget?

On domain class attributes of type Boolean

Boolean ok

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "ok", "net.kaleidos.plugins.admin.widget.CheckboxInputWidget"
}

Sample of html render

<input type="checkbox" name="ok" class="form-control" checked="checked" />

Sample image

5.3.2 DateInputWidget

Internal attributes
  • dateFormat: You can specify the date format for this widget. Although internally can use the format pattern of SimpleDateFormat, in order to use the html component you can only use d, dd, M, MM, yy, yyyy. By default, the format is "dd/MM/yyyy".

How would the plugin select automatically this widget?

On domain class attributes of type Date

Date birthday

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "birthday", "net.kaleidos.plugins.admin.widget.DateInputWidget", dateFormat: "dd/mm/yyyy"
}

Sample of html render

<input type="text" value="29/05/1994" data-date-format="dd/mm/yyyy" name="birthday" class="date form-control" />

Sample image

5.3.3 DecimalInputWidget

How would the plugin select automatically this widget?

On domain class attributes of types Float or Double

Float height
Double weight

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "height", "net.kaleidos.plugins.admin.widget.DecimalInputWidget"
}

Sample of html render

<input value="180.5" data-parsley-type="number" name="height" class="form-control" />

Sample image

5.3.4 EmailInputWidget

How would the plugin select automatically this widget?

On domain class attributes of type String with a constraint of email

String email
static constraints = {
    email email:true
}

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "email", "net.kaleidos.plugins.admin.widget.EmailInputWidget"
}

Sample of html render

<input type="email" value="paul@example.com" name="email" class="form-control" />

Sample image

5.3.5 EnumWidget

How would the plugin select automatically this widget?

On domain class attributes of type Enum

Gender gender

enum Gender {
    MALE, FEMALE
}

How can I configure this widget?

You should not configure this widget

Sample of html render

<select name="gender" class="form-control">
  <option selected="selected" value="MALE">MALE</option>
  <option value="FEMALE">FEMALE</option>
</select>

Sample image

5.3.6 HiddenInputWidget

How would the plugin select automatically this widget?

This widget won't be automatically selected, you will have to configure it.

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "hidden", "net.kaleidos.plugins.admin.widget.HiddenInputWidget"
}

Sample of html render

<input type="hidden" value="abc" name="hidden" class="form-control" />

Sample image

5.3.7 LabelWidget

How would the plugin select automatically this widget?

On domain class attributes of classes not knwown for the Grails Admin Plugin. Also, on "internal" properties, such as "id", "version", "dateCreated" or "lastUpdated"

File photo
Date dateCreated
Date lastUpdated

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "ok", "net.kaleidos.plugins.admin.widget.LabelWidget"
}

Sample of html render

<label class="form-control" name="photo">/tmp/image.jpg</label>

Sample image

5.3.8 LocaleInputWidget

How would the plugin select automatically this widget?

On domain class attributes of type Locale

Locale locale

How can I configure this widget?

You should not configure this widget

Sample of html render

<input type="text" value="es_ES" name="locale" class="form-control" />

Sample image

5.3.9 MapWidget

This widget shows an iframe with a google maps view of the value, and an input text to show and update that value

How would the plugin select automatically this widget?

This widget won't be automatically selected, you will have to configure it.

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "address", "net.kaleidos.plugins.admin.widget.MapWidget"
}

Sample of html render

<div view="mapwidget" class="map-widget">
    <div>
        <span class="map-container">
            <iframe width="425" height="350" frameborder="0"
                src="https://maps.google.com/maps?f=q&amp;q=Madrid, Spain&amp;output=embed"
                marginwidth="0" marginheight="0" scrolling="no">
            </iframe>
        </span>
        <input type="button" value="Refresh" class="map-widget-refresh js-map-widget-refresh">
    </div>
    <div>
        <input type="text" value="Madrid, Spain" name="address" class="form-control map-widget-text js-map-widget-text">
    </div>
</div>

Sample image

5.3.10 NumberInputWidget

How would the plugin select automatically this widget?

On domain class attributes of types Byte, Short, Integer or Long

Integer age

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "height", "net.kaleidos.plugins.admin.widget.NumberInputWidget"
}

Sample of html render

<input type="number" value="20" name="age" class="form-control" />

Sample image

5.3.11 PasswordInputWidget

How would the plugin select automatically this widget?

On domain class attributes of type String with a constraint of password

String password
static constraints = {
    password password:true
}

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "password", "net.kaleidos.plugins.admin.widget.PasswordInputWidget"
}

Sample of html render

<input type="password" value="12345"  name="password" class="form-control" />

Sample image

5.3.12 RelationPopupOneWidget

This widget is used for model a relation between the current domain object, and another domain object. This relation can be a one-to-one relation, or the week side of an one-to-many relation.

How would the plugin select automatically this widget?

On domain class attributes which type is other domain class, including the 'belongsTo' constraints

Room room

static belongsTo = [room:Room]

How can I configure this widget?

You should not configure this widget

Sample of html render

<div action="/conferences/myadmin/api/room" data-method="put" view="relationPopupOneWidgetField" class="relation-popupone-widget ">
  <input type="hidden" value="6" name="room" class="js-one-rel-value">
  <a name="room" class="js-one-rel-text" href="/conferences/myadmin/edit/room/6">Room6</a>
  <div class="btn-group">
    <a data-url="/conferences/myadmin/api/room" data-toggle="modal" class="btn btn-default js-relationpopuponewidget-list" href="#">
      <span class="glyphicon glyphicon-list"> </span> List
    </a>
    <a data-target="#new-conferences_test_room" data-toggle="modal" class="btn btn-default js-relationpopuponewidget-new" href="#">
      <span class="glyphicon glyphicon-plus"> </span> New
    </a>
    <a style="display:block;" class="btn btn-default js-relationpopuponewidget-delete" href="#">
      <span class="glyphicon glyphicon-trash"> </span> Delete
    </a>
  </div>
</div>

Sample of render after form

<div data-field="room" class="modal fade" grailsadmin-remote="enabled"
    aria-hidden="true" aria-labelledby="confirmLabel" role="dialog"
    view="relationPopupWidgetNew" tabindex="-1" id="new-conferences_test_room">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <buton class="close" aria-hidden="true" data-dismiss="modal" type="button">x</buton>
                <h4 class="modal-title" id="confirmLabel">Add room</h4>
            </div>
            <div class="modal-body">
                <form grailsadmin-remote="enabled" class="validate-form main-form" data-method="PUT" view="formView" method="post" action="/conferences/myadmin/api/room" novalidate="">
                    <div class="form-group">
                        <label for="board">Board *</label>
                        <input type="checkbox" name="board" disallowrelationships="true" class="form-control" />
                    </div>
                    <div class="form-group">
                        <label for="name">Name *</label>
                        <input type="text" value="" name="name" disallowrelationships="true" class="form-control" required="true" />
                    </div>
                    <div class="form-group">
                        <label for="photo">Photo</label>
                        <label name="photo" disallowrelationships="true" class="form-control"></label>
                    </div>
                    <div class="form-group">
                        <label for="talk">Talk</label>
                        <p>Disabled relationship due to be inside an embedded dialog</p>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" data-dismiss="modal" type="button">Close</button>
                <button class="btn btn-plus btn-success js-relation-popup-widget-new-save-action" type="button">Save</button>
            </div>
        </div>
    </div>
</div>

Sample image

5.3.13 RelationSelectWidget

This widget is used as an alternative form to model a relation between the current domain object, and another domain object. This relation can be a one-to-one relation, or the week side of an one-to-many relation.
This widget loads all the posible related domain objets to populate the select element, so it shouln't be used if there are too many of this elements

How would the plugin select automatically this widget?

This widget won't be automatically selected, you will have to configure it.

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "speaker", "net.kaleidos.plugins.admin.widget.relation.RelationSelectWidget"
}

Sample of html render

<select name="speaker" class="form-control">
    <option value="1">Speaker0</option>
    <option selected="selected" value="2">Speaker1</option>
    <option value="3">Speaker2</option>
    <option value="4">Speaker3</option>
    <option value="5">Speaker4</option>
</select>

Sample image

5.3.14 RelationTableWidget

This widget is used for model a relation between the current domain object and a collection of other domains objects. This relation can be a many-to-many relation, or the strong side of an one-to-many relation.

How would the plugin select automatically this widget?

On domain class attributes which type is a collection of other domain class

static hasMany=[talks:Talk]

How can I configure this widget?

You should not configure this widget

Sample of html render

<div view="relationTableWidget" class="relationtablewidget clearfix">
    <input type="hidden" value="12" name="talks">
    <input type="hidden" value="6" name="talks">
    <table class="table table-bordered elements-table" data-optional="true" data-property-name="talks" data-detailurl="/conferences/myadmin/edit/talk/0">
        <tbody>
            <tr>
                <td>
                    <a href="/conferences/myadmin/edit/talk/12">New talk</a>
                </td>
                <td class="list-actions">
                    <a href="#" data-value="12" class="btn btn-default btn-sm js-relationtablewidget-delete">
                    <span class="glyphicon glyphicon-trash"> </span> Delete
                    </a>
                </td>
            </tr>
            <tr>
                <td>
                    <a href="/conferences/myadmin/edit/talk/6">Talk6</a>
                </td>
                <td class="list-actions">
                    <a href="#" data-value="6" class="btn btn-default btn-sm js-relationtablewidget-delete">
                        <span class="glyphicon glyphicon-trash"> </span> Delete
                    </a>
                </td>
            </tr>
        </tbody>
    </table>
    <div>
        <a href="#" data-url="/conferences/myadmin/api/talk" class="btn btn-default js-relationtablewidget-list">
            <span class="glyphicon glyphicon-list"></span> List
        </a>
        <a data-target="#new-conferences_test_talks" data-toggle="modal" href="#" data-url="/conferences/myadmin/api/talk" class="btn btn-default js-relationtablewidget-new">
            <span class="glyphicon glyphicon-plus"></span> New
        </a>
    </div>
</div>

Sample of render after form

<div data-field="talks" class="modal fade" grailsadmin-remote="enabled" aria-hidden="true" aria-labelledby="confirmLabel" role="dialog" view="relationPopupWidgetNew" tabindex="-1" id="new-conferences_test_talks">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <buton class="close" aria-hidden="true" data-dismiss="modal" type="button">x</buton>
                <h4 class="modal-title" id="confirmLabel">Add talks</h4>
            </div>
            <div class="modal-body">
                <form grailsadmin-remote="enabled" class="validate-form main-form" data-method="PUT" view="formView" method="post" action="/conferences/myadmin/api/talk" novalidate="">
                    <div class="form-group">
                        <label for="locale">Locale *</label>
                        <input type="text" value="es" name="locale" disallowrelationships="true" class="form-control" required="true" />
                    </div>
                    <div class="form-group">
                        <label for="name">Name *</label>
                        <input type="text" value="" name="name" disallowrelationships="true" class="form-control" required="true" />
                    </div>
                    <div class="form-group">
                        <label for="room">Room</label>
                        <p>Disabled relationship due to be inside an embedded dialog</p>
                    </div>
                    <div class="form-group">
                        <label for="speaker">Speaker</label>
                        <p>Disabled relationship due to be inside an embedded dialog</p>
                    </div>
                    <div class="form-group">
                        <label for="talkDate">TalkDate *</label>
                        <input type="text" value="" data-date-format="dd/mm/yyyy" name="talkDate" disallowrelationships="true" class="date form-control" required="true" />
                    </div>
                    <div class="form-group">
                        <label for="talkTime">TalkTime *</label>
                        <input type="text" value="" name="talkTime" disallowrelationships="true" class="form-control" required="true" />
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" data-dismiss="modal" type="button">Close</button>
                <button class="btn btn-plus btn-success js-relation-popup-widget-new-save-action" type="button">Save</button>
            </div>
        </div>
    </div>
</div>

Sample image

5.3.15 RelationSelectMultipleWidget

This widget is used as an alternative form to model a relation between the current domain object and a collection of other domains objects. This relation can be a many-to-many relation, or the strong side of an one-to-many relation.
This widget loads all the posible related domain objets to populate the select element, so it shouln't be used if there are too many of this elements

How would the plugin select automatically this widget?

This widget won't be automatically selected, you will have to configure it.

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "conferences", "net.kaleidos.plugins.admin.widget.relation.RelationSelectMultipleWidget"
}

Sample of html render

<select name="conferences" class="form-control" multiple="">
    <option value="1">conferences.Conference : 1</option>
    <option selected="selected" value="2">conferences.Conference : 2</option>
    <option value="3">conferences.Conference : 3</option>
    <option selected="selected" value="4">conferences.Conference : 4</option>
</select>

Sample image

5.3.16 SelectWidget

How would the plugin select automatically this widget?

On domain class with a constraint of inList

String country
static constraints = {
    country inList: ["Canada", "Spain", "USA"]
}

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

You can define the list of valid options with a map also on configuration

grails.plugin.admin.domain.Test = {
    widget "country", "net.kaleidos.plugins.admin.widget.SelectWidget", options: ["Canada":"Canada", "Spain":"Spain", "USA":"USA"]
}

Sample of html render

<select name="country" class="form-control">
    <option value="">--</option>
    <option value="Canada">Canada</option>
    <option selected="selected" value="Spain">Spain</option>
    <option value="USA">USA</option>
</select>

Sample image

5.3.17 TextAreaWidget

How would the plugin select automatically this widget?

This widget won't be automatically selected, you will have to configure it.

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "comment", "net.kaleidos.plugins.admin.widget.TextAreaWidget"
}

Sample of html render

<textarea name="comment" class="form-control>A comment</textarea>

Sample image

5.3.18 TextInputWidget

How would the plugin select automatically this widget?

On domain class attributes of type String

String name

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "name", "net.kaleidos.plugins.admin.widget.TextInputWidget"
}

Sample of html render

<input type="text" value="Paul" name="name" class="form-control" />

Sample image

5.3.19 UrlInputWidget

How would the plugin select automatically this widget?

On domain class attributes of type String with a constraint of url

String web
static constraints = {
    web url:true
}

How can I configure this widget?

There are several ways, described on the configuration section.

One of the easies ways is on the file Config.groovy

grails.plugin.admin.domain.Test = {
    widget "email", "net.kaleidos.plugins.admin.widget.UrlInputWidget"
}

Sample of html render

<input type="url" value="http://www.grails.org" name="web" class="form-control" />

Sample image

6 Custom Widgets. Guide to Widget developers

Besides the built in widgets, you can create your own custom widgets.

Edit your app Config.groovy and add this line for a domain class and property, where you define the custom widget class and some properties to configure.

Example:

grails.plugin.admin.domain.Conference = {
    widget "coordinates", "sample.MapWidget", height:400, width:600
}

In this example we configure for domain class Test and property address a custom widget call MyTestWidget. Some properties could be specified and will we copied directly to HTML component.

Groovy side

Inherit from Widget

Your custom widget should inherit from the class net.kaleidos.plugins.admin.widget.Widget. That is an abstract class, that has only one methods that you must implement:

  • String render(): The method to render your widget as html

Also, Widget define sever methods that you can overwrite if you need them

  • String renderBeforeForm(): Render code html before the form that contains all the widgets
  • String renderAfterForm(): Render code html after the form that contains all the widgets
  • Object getValueForJson(): Returns an object (usually an string) that whould be used in order to represent the current value of the widget when the widget is represented as JSON.
  • List<Map> getAssets(): Returns the list of assets (css and js) needed for the html representation of this widget. The list should be a Map containing the same elements that the tag "g:resource" accepts http://grails.org/doc/latest/ref/Tags/resource.html.

Last, there are several properties that you can use. Note that you don't write this properties, it is GrailsAdminPlugin who assign the values. You should use this properties as read-only.

  • Object value: The current value of the widget. Its type will depend on the actual widget. For example, on a TextInputWidget will be a String, and on DateInputWidget will be a Date.
  • Map htmlAttrs: A map of properties to be rendered on the html representation, like the name, if it is required, etc.
  • Map internalAttrs: A map of properties internal for the widget. Remember that the widget represents a property of a domain object, so:
    • domainObject: This is that domain object. Note that it could be null (for example, on the create screen)
    • propertyName: This is the name of that property.
    • domainClass: This is the class of that object.
    • relatedDomainClass: If the property is a relation with another domain class, this is that domain class. It could be null.

Error handling

If you want you can handle errors (expected or unspected) thrown by your widget. You should extend the method.

String renderError(Throwable t)

This method will be called after any exception thrown by your widget. So you can render your custom information.

When the widget throws an exception on the method "updateValue" (which parsers the form input to your custom types inside the widget) any error will be displayed in the form as if a validation error was displayed.

Sample

With all this, lets make a sample Widget. We will create a custom widget that show an addres on a map. It will be used for properties of type String.

Create the Widget Class

We will create our widget on the file src/groovy/sample/MapWidget.groovy

package sample

import net.kaleidos.plugins.admin.widget.Widget

class MapWidget extends Widget {

@Override String render() { return "" } }

Create the render method

We will have the address on the value attribute of the widget. We want that out widget create an iframe showing the address on the map.

String render() {
    def html = new StringBuilder()
    html.append("<div>")
    html.append("<iframe width='425' height='350' frameborder='0' scrolling='no' marginheight='0'
        marginwidth='0'")
    html.append("src='https://maps.google.com/maps?f=q&amp;q=${value}&amp;output=embed'")
    html.append("></iframe>")
    html.append("</div>")

return html }

Upgrade the render method to allow edition

The render method is cool, and show the address on a map, but we want also to allow to update the address. In order to do so, we need to add an input field below the map. Also, it is important to render the htmlAttrs, in order to get all the properties needed. So we must add several lines to the render method

String render() {
    def html = new StringBuilder()
    html.append("<div>")
    html.append("<iframe width='425' height='350' frameborder='0' scrolling='no' marginheight='0'
        marginwidth='0'")
    html.append("src='https://maps.google.com/maps?f=q&amp;q=${value}&amp;output=embed'")
    html.append("></iframe>")
    html.append("</div>")

html.append("<input class='form-control' type='text' ") htmlAttrs.each{key, value -> html.append("$key='${value}' ") } html.append("value='${value}' />") return html }

When you write html as this example, there is a risk of an XSS attack (for example, if the value is something like "<script>alert(0)</script>). So, instead of write the value directly, you should write it as value.encodeAsHTML(). Also, you should be carefull with the null values.

So you can write rhe values as

${value?:value.encodeAsHTML():''}

Frontend side

Most widgets won't need special css nor js. But if you need them, you can use them you can specify a custom method getAssets on the widget.

Sample

We will upgrade our MapWidget with a button that refresh the map with the current address. In order to do so, we will use a js and a css file.

List<Map> getAssets() {
    return [
        [ dir: "js", file: "admin/map.js", absolute: true ],
        [ dir: "css", file: "admin/map.css", absolute: true ]
    ]
}

Also, we will modify the render method with the code to show the button, and also with some divs to contain the elements, and several css classes.

String render() {
    def html = new StringBuilder()
    html.append("<div class='map-widget'>")
    html.append("<div>")
    html.append("<span class='map-container'>")
    html.append("<iframe width='425' height='350' frameborder='0' scrolling='no' marginheight='0' marginwidth='0'")
    html.append("src='https://maps.google.com/maps?f=q&amp;q=${value}&amp;output=embed'")
    html.append("></iframe>")
    html.append("</span>")
    html.append("<input type='button' class='map-widget-refresh js-map-widget-refresh' value='Refresh' />")
    html.append("</div>")

html.append("<div>")

html.append("<input type='text' class='form-control js-map-widget-text' ") htmlAttrs.each{key, value -> html.append("$key='${value}' ") } html.append("value='${value}' />") html.append("</div>") html.append("</div>") return html }

CSS

In order to give the button some style, we created the file WEB-APP/css/admin/map.css

.map-widget-refresh {
    background-color: #4040EA;
    border: 0 none;
    color: #FFFFFF;
    margin-bottom: 10px;
    margin-left: 15px;
    padding: 5px;
}

JS

And finally, to refresh the map, we have created the following javascript code on the file WEB-APP/js/admin/map.js

$(".map-widget").on( "click", ".js-map-widget-refresh", function(event) {
    var value = $(this).closest(".map-widget").find(".js-map-widget-text").val();
    var html = "<iframe width='425' height='350' frameborder='0' scrolling='no' marginheight='0' marginwidth='0'";
    html += "src='https://maps.google.com/maps?f=q&amp;q=" + value + "&amp;output=embed'></iframe>";
    $(this).closest(".map-widget").find(".map-container").html(html);
});

NOTE: GrailsAdminPlugin uses jquery, so it is available for your custom widgets also

More Frotend features

To customize the widget resources you must override the getAssets method

@Override
List<Map> getAssets() {
    return [
        [ dir: "mywidget", file: "example1.js", absolute: true ],
        [ dir: "mywidget", file: "example2.js", absolute: true ],
        [ dir: "mywidget", file: "example1.css", absolute: true ],
        [ dir: "mywidget", file: "example2.css", absolute: true ],
        [ dir: "mywidget/templates", file: "template1.handlebars", absolute: true ],
        [ dir: "mywidget/templates", file: "template2.handlebars", absolute: true ],
    ]
}

GrailsAdmin Javascript utils

GrailsAdmin has views & services that you can use in your widget if you want

Views

//js file
//creating view
app.view('formView', ['$el'], function ($el) {
    //view code
});

//html file // call view <div view='deleteModal'></div>

The `$el` is a Jquery object with the dom element.

Services

//create
app.service('paginationService', [], function () {
    //service code

return { method: function () { //method code } }; });

//use app.view('formView', ['$el', 'paginationService'], function ($el, paginationService) { paginationService.method(); });

Javascript libraries

parsley bootstrap bootstrap-datepicker handlebars jQuery lodash select2

Handlebars

If you add handlebars in your assets grailsAdmin creates a script tag at the end of page with the content of the handlebars file. The id of the script tag is the filename without the extension

<script id="filename" type="text/x-handlebars-template">
        <h1>handlebars template</h1>

<p>{{text}}</p> </script>

You can use templateService to get the compiled html

app.view('formView', ['$el', 'templateService'], function ($el, templateService) {
     var templateVars = {title: 'exaple var'};
     var html = templateService.get('filename', templateVars);
});

7 Contributing. Guide to Plugin developers

Once cloned the repo to download all the dependencies necessary to develop, you have to execute

cd ./grails-admin

npm install

sudo npm install -g gulp

# In case of error update npm sudo npm install -g npm

sudo apt-get install rubygems

sudo apt-get install ruby1.9.1

sudo gem install sass

# to install bower sudo npm install -g bower

# after that npm update -g bower

bower install gulp

After this, enter in front directory and execute gulp:

$ cd ./front
$ gulp

front-end will be running in http://localhost:9000.

8 Roadmap

Next topics planned:
  • Filters
  • Group of Properties
  • Widget multy property
  • Batch delete
  • Batch edit
  • 1-n inline
  • 1-1 inline
  • Preview media content Widget
  • Widget for properties in list view
  • Help texts associated with input fields
  • 1-n 2 list of combos
  • 1-n checkbox
  • Navigate throw relations window by windows
  • Themes
  • Content roles
  • History of changes (Auditory)
  • Overwrite CRUD methods
  • CRUD events
  • Inexistente fields
  • 1-n-1 relations. Hide middle relationship class
  • New Button in 1-n

9 FAQ

Why should I use this instead of "scaffolding"?

The scaffolding is better as a code-generation tool that "bootstrap" a new application with a base application. The aim of the plugin is to provide a customizable backend to extend the administration.

We don't want to override scaffolding but to use this configuration-based alternative.

Also, this plugin has some other added features such as:

  • Integrated with security
  • Configurables views
  • Integrates easily as a plugin