Grails UI

38 votes
Dependency :
compile ":grails-ui:1.2.3"
Custom repositories :
mavenRepo "http://localhost:8081/artifactory/plugins-releases-local/"

Source Issues

Summary

Provides a standard UI tag library for ajaxy widgets using YUI.

Installation

This plugin is deprecated and no longer maintained. It will see no further development. It is recommended that you use the native API of each widget toolkit to make the most out of them whether it be jQuery-UI, Dojo etc.

GUI depends on the installation of the YUI Plugin (version 2.6.0) and the Bubbling Plugin (version 1.5.0).

For Grails 1.1:

grails install-plugin grails-ui

For Grails 1.0.X:

grails install-plugin yui
grails install-plugin bubbling
grails install-plugin grails-ui

Description

GrailsUI

GrailsUI (GUI) is a plugin that provides an extensive tag library of rich ajax components based on the Yahoo! UI (YUI) JavaScript library and a YUI extension called the Bubbling Library. It has been designed for ease-of-use as well as configurability.

Quick Start

Just want some working code to get a quick start on using GUI? There is a GUI demo project on Google Code that shows working examples of every tag in the library. To check it out:

svn checkout http://guidemo.googlecode.com/svn/trunk/ guidemo-read-only

This example project does not have the necessary plugins installed, so you'll have to follow the simple instructions in the "Installation" section below to install the GUI plugin and its dependencies.

Screencasts

A series of screencasts is under construction:

Key Concepts

Configuration Pass-Through

Most YUI widgets accept a configuration object during creation that helps to set up the widget, and most GUI tags map directly to a YUI component. Any attributes passed into a GUI tag that the tag library doesn't consume are assumed to be configuration settings for the underlying YUI widget. This allows the user to specify YUI config options directly within the tag description as attributes. For example, the YUI Dialog component accepts a config option called 'modal'. If modal="true", the dialog will not allow interaction with any other component until it is dismissed. Because of GUI's configuration pass-through, the user can specify an attribute of 'modal' when defining the <gui:dialog/> tag:

<gui:dialog
    title="Modal Dialog"
    triggers="[show:[type:'link', text:'Click for dialog', on:'click']]"
    modal="true"
>
This message will appear in a modal dialog.
</gui:dialog>

Both the 'title' and 'modal' attributes values are passed directly into the YUI Dialog config upon construction. The 'triggers' attributed is processed and consumed by the tag library, and will be explained below in the Dialog component section. All attributes specified that are not recognized by the GUI tag processing logic are assumed to be YUI config values and will be passed along to the underlying YUI component. If the YUI component is constructed with config values it doesn't understand, this will not affect the component's behavior.

Component Accessibility

Each YUI component that is created can be referenced via JavaScript in other areas of the page. This allows users to create event listeners to trigger custom actions. If an 'id' attribute is passed along to the component tag, this can be used to access the YUI component. Here is an example of attaching a custom select handler to a GUI datePicker component:

<gui:datePicker id="myDatePicker" />
<script>
    YAHOO.util.Event.onDOMReady(function() {
        function selectHandler(){
            alert('date selected on myDatePicker!');
        }
        GRAILSUI.myDatePicker.selectEvent.subscribe(selectHandler);
    });
</script>

Because an id attribute was specified on the datePicker component, the YUI Calendar object that was created by GUI is accessible through the GRAILSUI namespace.

Simple Dependency Mapping

YUI dependencies include JavaScript and CSS files, and can sometimes be complicated. YUI has a dependency configurator on their website to help developers define the proper includes in the correct order. GUI is designed to make dependency declaration as easy as possible by providing one resources tag for users to define what will be used within each GSP. In this manner, users can declare what components are active in a page in the HEAD once, and use their tags throughout the page. Any additions or changes to dependencies should only require a simple update of the resources tag in HEAD. More information is below in the resources section. The resources tag also makes it easy to include a specific CSS or JavaScript file existing within the YUI or Bubbling Library. Even when multiple components and individual files are marked for inclusion, any redundant dependencies are filtered out.

Usage

There are two steps to using a GUI component. First, users must specify what components will be used within a GSP in the Resources tag in the HEAD of the page. Users can then declare the tags anywhere else within the page.

Resources tag

Each GSP page that uses any GUI component must declare what components are used on the page within the HEAD tag. This is done with the <gui:resources/> tag. Very simply, the components uses on the page must be declared within a 'components' attribute in order for GUI to know what YUI and Bubbling Library files to include with the page. For example, for a page that will use a richEditor and a dialog:

<gui:resources components="['richEditor','dialog']"/>
or
<gui:resources components="richEditor, dialog"/>

Either array or comma-separated lists will work within the components attribute. With most YUI JavaScript source files, there are different modes of file, like 'debug' or 'minimal'. The 'minimal' mode is a compressed (unreadable) version of the source code, while 'debug' is handy for stepping through JavaScript in a debugging tool. To include the debug versions of component JavaScript source files:

<gui:resources components="richEditor, dialog" mode="debug"/>

The 'minimal' mode has the smallest footprint and is the default mode.

Component tags

Component tags provide UI widgets for users. Each tag, if not specifically given an 'id' attribute, will have a unique id generated for it. As noted in the section on Configuration Pass-Through above, any attributes not consumed by the tag library will be passed on as configuration options to the YUI component that is created. Some components, like the dataTable, also allow specific attributes that will pass on configuration to additional supporting YUI components that are created to support it (see the section below on DataTable Paginator Configuration).

Styling Components

All GUI component styles can be overridden with local CSS. For more information on skinning YUI, see here.

If you want to use the built-in skin of the components, then you need to attach the class "yui-skin-sam" to the HTML element that contains the GrailsUI components (such as a "div" container). For instance, if you want to use the skin on all components in the entire application, then you can modify the "body" element in the "grails-app/views/layouts/main.gsp" as follows:

<body class="yui-skin-sam">

List of Component tags

Dialog

Provides a dialog and an optional trigger to show the dialog. This component wraps the YUI Dialog object, and all configuration properties of the dialog can be passed through as attributes to this tag.

Attribute definitions

Dialog attribute: triggers

Allows user to define how the dialog will be triggered to show or hide. This can be used to declare existing components as triggers, or to have GUI create new triggers (currently only links or buttons). If there is already a component with id on the page, it can be specified by id as a trigger. Here is a code example of using the triggers attribute to create a new button to trigger the dialog to show:

triggers="[show:[type:'button', text:'Press for Dialog', on:'click']]"

The 'triggers' attribute is a map, with each key representing a method that will be called on the dialog, and each value representing a trigger definition. In this case, we want a button with the text 'Press for Dialog', that will be a trigger when it is clicked. Valid values for type are currently 'link' and 'button'. Valid trigger keys are any public methods of the YUI Dialog object, but mainly 'show' and 'hide'. Valid 'on' values include 'click' and 'mouseover'.

To use an existing HTML element within the page as the dialog trigger, use the triggers attribute in this manner:

triggers="[
    show:[id:'show4', on:'click'],
    hide:[id:'hide4', on:'click']
]"

Only the id of the triggering element is required, as well as the 'on' trigger. If there is no element with the specified id in the trigger, the trigger will not exist.

Dialog attribute: form

If the form attribute is true, the dialog will set itself up to contain a form. Default buttons are created with the proper event handling to remotely submit any form data that is contained within the form. If form="true", there should be enough information within the rest of the attributes to create a URL (either controller and action, or url attribute). The attributes are sent into createLink to resolve to a URL. There should also be an 'update' attribute set if there is data passed back from the asynchronous call to the form's URL.

Dialog attributes: controller, action, params, url

If form="true", there must be enough data within the remaining attributes to resolve to a URL if sent to the createLink Grails method.

Dialog attribute: update

This should be the id of an element to update with response text from the server. This element should contain HTML, as its innerHTML value is set. If any <script/> tags are contained in the response, they are executed after the innerHTML is loaded.

Dialog Usage Examples

Modal confirm dialog (local)

This script provides an event handler and passes it into the dialog tag attached to a button. This dialog does not make a server call, but transfers the confirmation back to a local script.

<script>
    var yesHandler = function(o) {
        alert('You clicked "Yes"');
        this.cancel();
    }
</script>
<gui:dialog
    title="Modal Confirm Dialog"
    draggable="false"
    modal="true"
    buttons="[
        [text:'Yes', handler: 'yesHandler', isDefault: true],
        [text:'No', handler: 'function() {this.cancel();}', isDefault: false]
    ]"
    triggers="[show:[type:'link', text:'Confirm', on:'click']]"
>
Are you sure?
</gui:dialog>

Modal confirm dialog (remote)

Here is an example of a confirm dialog that calls a URL when the 'Submit' button is clicked.

<gui:dialog
    form="true"
    controller="demo" action="confirmSomething"
    update="thingBeingUpdatedByResponse"
    triggers="[show:[type:'link', text:'Confirm', on:'click']]"
>
Are you sure?
</gui:dialog>
This dialog doesn't bother passing control back to the page, but rather creates itself as a form, with the submit button calling a URL. On cancel, the dialog dismisses itself.

Remote form dialog

Any form elements can be placed inside the dialog tag without defining a form. When form="true", the dialog creates its own form.

<gui:dialog
    title="Dialog with Form"
    form="true"
    controller="demo"
    action="acceptForm"
    update="replaceMe"
    triggers="[show:[type:'link', text:'click me for a form', on:'click']]"
>
    <input type="text" id="input1" name="input1"/>
</gui:dialog>

Other Useful Dialog Configuration Pass-Throughs

  • buttons: Allows user to create their own buttons and event handling.

Tab View

Provides a tabbed view. This component wraps the YUI TabView object. Tab content is defined within <gui:tab/> elements.

The tabView component only accepts one possible attribute, which is optional: id.

Defining tabs

Tabs can be defined either by declaring static content within a tab element, or by defining enough attributes in the tab to resolve to a URL that will load the tab. A tab has only one required attribute, a 'label' that will define what is displayed on the tab marker. Setting the 'active' attribute to true will render the tab open initially. You may also specify a CSS class attribute for tabs that will be applied to the underlying 'li' element.

TabView Usage Examples

Static Tabs

Any HTML, JavaScript, or GUI tags can be placed inside a tabView tab. Here is an example of several different components being rendered in a tabView.

<gui:tabView>
    <gui:tab label="Tab 1" active="true">
        <h1>Inside Tab 1</h1>
        <p/>You can put whatever markup you want here.
    </gui:tab>
    <gui:tab label="Tab 2">
        <h1>Inside Tab 2</h2>
        <gui:richEditor id='editor' value="You can use gui components within tabs, too!"/>
    </gui:tab>
</gui:tabView>

Dynamic Tabs

If enough data is provided within the tab attributes to create a URL, the tab will be loaded by the URL.

If the dataSrc attribute is set, it will be used explicitly to render the tab content

<gui:tab label="Loaded with dataSrc" dataSrc="/path/to/my/page.gsp" active="true"/>
Or a controller and action can be passed.
<gui:tab
    label="Loaded from controller"
    controller="demo"
    action="tabLoader"/>

TabView Resources

AutoComplete

The autoComplete component wraps the YUI autoComplete widget. This tag can be used with local or remote data, and can depend on the value of another input element in the DOM. It can also be arranged to send along extra data to the server to be used for additional filtering.

Setting up your controller

Proper controller setup is essential to use the autoComplete effectively. The autoComplete will send along a query string attached to the HTTP request that defines what the user has input into the text box.

Currently, the GUI autoComplete is only configured to use JSON data format. This means that your controller must use the Grails JSON translation to convert autoComplete data into a usable format. Following is an example of a dummy controller action that provides data for an autoComplete.

import grails.converters.JSON
def autoCompleteJSON = {
    def list = DummyDomain.list(params.query)
    def jsonList = list.collect { [ id: it.id, name: it.name ] }
    def jsonResult = [
        result: jsonList
    ]
    render jsonResult as JSON
}

The list must contain an id as well as a label value, because the GUI autoComplete renders hidden input elements that contain the id. This allows the selection label and id to be passed along with form data on submission.

Local AutoComplete

An autoComplete can be setup using only local data.

<gui:autoComplete
        id="localData"
        options="['red','blue','yellow','orange','purple']"
/>

Remote AutoComplete

Once an autoComplete is setup to use a server to provide the data, the configuration gets more complex.

<gui:autoComplete
        id="remoteData"
        resultName="TestData"
        labelField="description"
        idField="id"
        controller="demo"
        action="testFruitDataAsJSON"
/>

The 'resultName' attribute refers to the root node of the JSON data that will contain the autoComplete list to render to the view. This value defaults to 'result', so if the controller is set up like the one above, this attribute is not necessary to override.

The autoComplete expects an array of maps containing one key for an id, and one for a label. The default map keys for these are 'id' and 'name', but they can be changed as shown above by the 'labelField' and 'idField' attributes.

Filtering AutoComplete

There are some cases where the autoComplete data may need additional filtering at the server. This filtering data must be used by the controller to filter the data. Following are two examples of ways to specify additional filtering data for the server.

<gui:autoComplete
        id="restrictedControl1"
        resultName="TestData"
        labelField="description"
        idField="id"
        controller="demo"
        action="testDataAsJSON"
        filterBy="link"
        filter="1"
/>

<gui:autoComplete
        id="restrictedControl2"
        resultName="TestData"
        labelField="description"
        idField="id"
        controller="demo"
        action="testDataAsJSON"
        queryAppend="link=3"
/>

AutoComplete Dependency

There are many cases where the value from another form input element needs to be send along with the autoComplete request. In this manner, an autoComplete can be dependent on the value of another form input.

<input id="dependedOn" type="text" value="my value"/>
<gui:autoComplete
        id="dependsOn"
        controller="demo"
        action="testDataAsJSON"
        dependsOn="dependedOn"
/>

The value of the 'dependedOn' text input will be retrieved and sent along with the autoComplete request as the 'dependedValue'. The controller that populates the autoComplete can now use this data as part of its process to filter the results.

AutoComplete Common Configurations

You probably don't want your autoComplete querying the server every few milliseconds (queryDelay), or maybe you want to restrict it from querying until a certain number of keys have been typed (minQueryLength). Use these attributes to control this:

<gui:autoComplete
        minQueryLength='3'
        queryDelay='1.5'
…
</gui:autoComplete>

Accordion

The accordion widget creates markup that is used by the Bubbling Library to transform into expandable panels.

Initially the accordion will expand to take 100% width available, so it must be bound by a containing element.

Accordion attributes

  • multiple: allows multiple panels open at once
  • bounce: provides a bounce effect on open and close
  • persistent: panels stay open
  • slow: slows down the open/close animation
  • fade: provides a fade effect on open and close

Standard Accordion

<gui:accordion>
    <gui:accordionElement title="Accordion element 1">
        Accordion element 1 content
    </gui:accordionElement>
    <gui:accordionElement title="Accordion element 2">
        <h3>Markup is fine in here</h3>
    </gui:accordionElement>
</gui:accordion>

Accordion with Options

<gui:accordion bounce="true" slow="true" multiple="true">
    <gui:accordionElement title="Accordion element 1">
        Accordion element 1 content
    </gui:accordionElement>
    <gui:accordionElement title="Accordion element 2">
        <g:each var="i" in="(1..10)">Hello ${i}<br/></g:each>
    </gui:accordionElement>
</gui:accordion>

Expandable Panel

The expandablePanel component is very similar to the accordion, and uses the same Bubbling Library source files. But the expandablePanel allows the user to define one panel that can be retracted into a title bar anywhere on the page, unattached to any other components.

Expandable Panel Examples

<gui:expandablePanel title="This panel is already expanded" expanded="true">
    I am expanded.  Close me.
</gui:expandablePanel>

<br/><br/>

<gui:expandablePanel bounce="false" title="Expand me for a fun surprise!" expanded="false"> I was not expanded at first, but if you are reading this, I must be expanded now. </gui:expandablePanel>

To allow a close action and icon, specify closable="true". By default, this is false.

Data Table

The GUI dataTable is build by creating a YUI DataTable object. The YUI dataTable is a very rich component that can be configured in many different ways. There are many configuration attributes available for users to modify the YUI table upon creation.

By default, GUI currently always uses server-side pagination and sorting. There is a JIRA to provide an option for client paging and sorting.

Column Definitions

To create a dataTable, GUI needs to know what the columns are going to look like, so it needs a map of data for each column. This list of maps representing the columns to be displayed is the column definitions. You can see a table of all the possible values of column definitions here in the instantiating section. Here is example of what a columnDefs attribute on a dataTable might look like:

columnDefs="[
    [key:'id', sortable:true, resizeable: true, label:'ID'],
    [key:'name', sortable:true, resizeable: true, label:'Name'],
    [key:'birthDate', type:'date', sortable:true, resizeable: true, label: 'Birth Date'],
    [key:'age', type:'number', sortable:true, resizeable: true, label: 'Age'],
    [key:'netWorth', type:'currency', sortable:true, resizeable: true, label: 'Net Worth']
]"

NOTE: The 'type' value is currently not being used, but a JIRA issue is logged to enable type formatting for GrailsUI 1.1.

It is essential that these column definition keys match the JSON data returned by the controller supplying the DataTable with data. See the section below on setting up the controller.

Paginator Configuration

A default Paginator is created, so no paginator config is necessary, but users may specify the config object for a custom Paginator. More information on Paginator configuration attributes can be found here. Following is an example of a custom Paginator configuration:

paginatorConfig="[
    template:'{PreviousPageLink} {PageLinks} {NextPageLink} {CurrentPageReport}',
    pageReportTemplate:'{totalRecords} total records'
]"

This specifies a template the Paginator to use, and also adds total records.

Setting up the Controller

Following is an example of a controller action that prepares data for a dataTable:

import grails.converters.JSON
def dataTableDataAsJSON = {
    def list = []
    response.setHeader("Cache-Control", "no-store")
    def data = [
            totalRecords: Demo.count(),
            results: Demo.list(params) 
    ]
    render data as JSON
}

In order for the dataTable's Paginator to know how many pages there are, the totalRecords must be passed back within the JSON data for the table to consume.

Row Click Navigation

If you want your dataTable to navigate to a certain URL when a row is clicked, you need to ensure a 'dataUrl' field is included in your JSON data that defines the URL. Then you just need to add rowClickNavigation='true' to your dataTable attributes, and each row click will navigate to the dataUrl specified in the row.

Row Expansion

The dataTable provides a row expansion feature. When configured properly, each row will expand on screen to provide more data about the row. This is done by inspecting the data for the clicked row and looking for a dataUrl.

If the JSON data received by the dataTable contains a row value called 'dataUrl', it assumes that this URL should be called and rendered on row click. Row expansion is turned off by default, so the rowExpansion attribute must be set to true as well.

Following is an example of how a controller might attach this dataUrl within the JSON return value:

import grails.converters.JSON
def dataTableDataAsJSON = {
    def list = []
    def demoList = Demo.list(params) 
    response.setHeader("Cache-Control", "no-store")
    demoList.each {
        list << [
                id: it.id,
                name: it.name,
                birthDate: it.birthDate.toString(),
                age: it.age,
                netWorth: it.netWorth,
                dataUrl: g.createLink(action: 'dataDrillDown') + "/$it.id"
        ]
    }
    def data = [
            totalRecords: Demo.count(),
            results: list
    ]
    render data as JSON
}

Here, instead of transforming domain objects into JSON, each object is manually broken down into a map, including the proper URL to use for the dataUrl value.

If rowExpansion="true", and the JSON data loaded into the table provides a dataUrl for each row, every row click will expand and populate a new panel just below the clicked row. One second click, pagination, or resort, the expanded row is contracted.

DataTable Example

The following markup defines a dataTable and expects the controller to provide dataUrl values for row expansion. It also sends an additional parameter to the controller to use for filtering (maxAge=52).

<gui:dataTable
    id="dt_2"
    draggableColumns="true"
    columnDefs="[
        [key:'id', sortable:true, resizeable: true, label:'ID'],
        [key:'name', sortable:true, resizeable: true, label:'Name'],
        [key:'birthDate', type:'date', sortable:true, resizeable: true, label: 'Birth Date'],
        [key:'age', type:'number', sortable:true, resizeable: true, label: 'Age'],
        [key:'netWorth', type:'currency', sortable:true, resizeable: true, label: 'Net Worth']
    ]"
    paginatorConfig="[
        template:'{PreviousPageLink} {PageLinks} {NextPageLink} {CurrentPageReport}',
        pageReportTemplate:'{totalRecords} total records'
    ]"
    controller="demo" action="nonStandardDataTableDataAsJSON"
    params="[maxAge:52]"
    resultsList="myResults"
    rowExpansion="true"
    rowsPerPage="3"
/>

Other Useful Dialog Configuration Pass-Throughs

Other DataTable Resources

Rich Editor

The richEditor tag is one of the most useful and easy to use tags in the library. It is used very much like a textarea input component. The id it is passed will be the id of the textarea it actually uses, and is treated as such within a form.

<gui:richEditor id='myEditor' value='initial value of the editor'/>

The richEditor uses a YUI SimpleEditor, which has a wealth of config options.

Draggable List

Draggable lists have items that can be dragged within each list to reorder the list, or dragged into other draggable lists to trade items between them.

Draggable lists must be declared within a draggableListWorkArea in order to allow dragging between lists.

<gui:draggableListWorkArea formReady="true">
    <gui:draggableList id="myList1" class="list1" prepend="list_">
        <li id="a1">A:1</li>
        <li id="a2">A:2</li>
        <li id="a3">A:3</li>
        <li id="a4">A:4</li>
    </gui:draggableList>
    <gui:draggableList id="myList2" class="list2">
        <li>B:1</li>
        <li>B:2</li>
        <li>B:3</li>
        <li>B:4</li>
    </gui:draggableList>
</gui:draggableListWorkArea>

When formReady="true", hidden input elements will be rendered that represent the orders of the draggableLists, so if the lists are within a form, this data will be submitted with the form.

Tooltip

You will need the JS resources refered as "toolTip". Do this by adding inside the HEAD part of your gsp (or layout):

<gui:resources components="['toolTip']"/>

For simple tooltips, it is only necessary to wrap the content that deservers a tooltip within the <gui:toolTip/> tags, providing a 'text' attribute, like this:

<gui:toolTip text="This text shows in a tool tip.">
   <img src="/myImg.png"/>
</gui:toolTip>

This works well for tool tips that contain a small amount of plain text. For more complex, dynamic tool tips, the toolTip tag can refer to a controller to populate the tool tip text:

<gui:toolTip 
    controller="demo"
    action="toolTipLoader"
    params="[message:'worked']"
>
    <div style="width: 200px; height: 200px; background:#8EC3E2; padding: 10px; border: 1px solid black">
        Hover over this box for a server rendred tooltip.
    </div>
</gui:toolTip>

The controller can be set up to pitch HTML directly into the tool tip:

def toolTipLoader = {
    render """
        <h3>Tool tip markup from the server!</h3>
        <p/>Here are my params: ${params}.
    """
}

Date Picker

The GUI datePicker creates a YUI Calendar object along with an input element for its selection data. It is simple to create, and allows many configuration options. For a simple datePicker:

<gui:datePicker id='simpleDatePicker' />

This will allow form access to the selected date by the id passed into it. A date value can also be passed into the date picker, in the form of a Date or Calendar object:

<gui:datePicker id='withDateValue' value="${new Date()}" />
<gui:datePicker 
        id='withCalendar' 
        value="${new GregorianCalendar(2008, 9, 31)}" 
        formatString="yyyy/MM/dd HH:mm:ss"
/>

The format string takes the same format as the Java SimpleDateFormat. If time input is needed, inputs will be added to the bottom of the datePicker with the 'includeTime' attribute:

<gui:datePicker id='withCalendarAndTime' value="${new GregorianCalendar(1978,6,11,21,45,15)}" includeTime="true"/>

Contribute

Issues, bugs, and feature requests can be logged on the GUI JIRA page. If you would like to contribute to the plugin, please download the GUI source code and attach a patch to a JIRA issue.