tudor-malene / Easygrid

Grails plugin - simple and declarative way of defining a DataGrid
Apache License 2.0
27 stars 24 forks source link

EasyGrid

Provides a declarative way of defining Data Grids.

It works currently with jqGrid, google visualization and jQuery dataTables.

Out of the box it provides sorting, filtering, exporting and inline edit just by declaring a grid in a controller and adding a tag to your gsp.

It also provides a powerful selection widget (a direct replacement for drop-boxes)

Official plugin page on Grails portal

Installation

Add the following plugin dependencies to your BuildConfig.groovy

repositories {
...
      // required by a dependency of the export plugin 
      mavenRepo "http://repo.grails.org/grails/core"
}
...
grails.project.dependency.resolution = {
    plugins {
        ...
        // EasyGrid plugin http://grails.org/plugin/easygrid
        compile ":easygrid:1.6.6"
        // For minimum functionality you need: jquery-ui and the export plugins.
        // Export Plugin http://grails.org/plugin/export
        compile ":export:1.6"
        // jQuery UI Plugin http://grails.org/plugin/jquery-ui
        compile ":jquery-ui:1.10.3"
        // For google visualization you also need google-visualization
        // Google Visualization API Plugin http://grails.org/plugin/google-visualization
        compile ":google-visualization:0.7"
        ...
    }
}

Check latest published version on Grails plugin portal.

After installation you need to run grails easygrid-setup command. This will append the grid templates in the /templates/easygrid folder, where you can customize them. And it will also add the '/conf/EasygridConfig.groovy' file - where you can add general settings.

Overview

The issues that Easygrid tackles are:

Easygrid solves these problems by proposing a solution based on declarations & conventions.

Demo

Features:

Concepts

The entire grid logic is defined in the Controller (or in an outside file) using the provided custom builder. (some particular view aspects can be defined in the gsp, using the provided taglibs)

For each grid you can configure the following aspects:

The plugin provides a clean separation between the model and the view (datasource and rendering) Currently there are implementations for 3 datasources (GORM , LIST - represents a list of objects stored in the session (or anywhere), CUSTOM ) and 4 grid implementations (JqGrid, GoogleVisualization, Datatables, static (the one generated by scaffolding))

Usage

All grids will be defined in controllers - which must be annotated with @Easygrid .

In each controller annotated with @Easygrid you can define a static closure called grids where you define the grids which will be made available by this controller. Starting with version 1.4.1, the preferred way of defining grids is by using plain closures ending with the Grid suffix (similar to flows in Spring WebFlow)

The plugin provides a custom Builder for making the configuration very straight forward.

Example from the petclinic sample:

def ownersGrid = {
    domainClass Owner
    columns {
        id {
            type 'id'
            enableFilter false
        }
        firstName
        lastName
        address
        city
        telephone
        nrPets {
            enableFilter false
            value { owner ->
                owner.pets.size()
            }
            jqgrid {
                sortable false
            }
        }
    }
}

In the gsp, you can render this grid via the following tag:

<grid:grid name="owners" addUrl="${g.createLink(controller: 'owner', action: 'add')}">
    <grid:set caption="Owners" width="800"/>
    <grid:set col="id" formatter="f:customShowFormat" />
    <grid:set col="nrPets" width="60" />
</grid:grid>
<grid:exportButton name="owners"/>

You also have to add the resource modules for the grid implementation:

<r:require modules="easygrid-jqgrid-dev,export"/>

From this simple example, you can see how we can define almost all aspects of a grid in a couple of lines. (datasource, rendering, export properties, filtering)

Custom UI properties can be added to the Config file (as defaults or types), in the Controller, or in the gsp page.

The simple grid from this example - which can be viewed here, comes with all features - including basic default filtering.

Grid Implementations:

To create your own implementation you need to:

  1. create a service
  2. create a renderer
  3. declare the service and renderer in Config.goovy

On installation of the plugin, the renderer templates for the default implementations are copied to /grails-app/views/templates/easygrid from the plugin, to encourage the developer to customize them.

Grid Datasource Types:

  1. gorm

    • the datasource is actually a Criteria Builder created from the specified domainClass and initialCriteria. (basically it does: DomainClass.createCriteria().buildCriteria(initialCriteria) and afterwards applies the globalFilterClosure and the filterClosure from each column)
    • domainClass - the domain (mandatory)
    • initialCriteria - used for complex queries
    • globalFilterClosure - a filter that will be applied all the time (the only parameter is 'params')

    For fast mock-up , grids with this type can be defined without columns, in which case these will be generated at runtime from the domain properties (dynamic scaffolding).

    A filter closure is defined per column and will have to be a closure which will be used by a GORM CriteriaBuilder Starting from version 1.5.0 this datasource works only with Hibernate!

  2. list

    • used when you have a list of custom objects (for ex. stored in the session) that must be displayed in the grid
    • context - the developer must declare the context where to find the list (defaults to session)
    • attributeName - and the attribute name (in the specified context)
    • pagination will be handled by the framework

    The filterClosure takes 2 parameters: the Filter object and the actual row - and returns true if the row matches the filter

  3. custom

    • when the list to be displayed is dynamic (generated by a closure)
    • dataProvider - closure that returns the actual data (must implement pagination)
    • dataCount - closure that returns the number of items

If you want to create your own datasource (skip this as a beginner):

  1. Create a service - which must implement the following methods:

    • generateDynamicColumns() - optional - if you want to be able to generate columns

    • verifyGridConstraints(gridConfig) - verify if the config is setup properly for this datasource impl

    • list(Map listParams = [:], filters = null) - ( * returns the list of rows

      • by default will return all elements
      • @param listParams - ( like rowOffset maxRows sort order)
      • @param filters - array of filters )
    • countRows(filters = null)

    • in case you want to support inline editing you need to define 3 closures (updateRow, saveRow, delRow)

  2. declare this service in Config.groovy , under easygrid.dataSourceImplementations

Columns section (see ColumnConfig.groovy)

The name of each column will be the actual name of the closure. Beside the actual column name, from the name property other properties can be inferred, like:

  1. Column Label: The label can be defined , but in case it's missing it will be composed automatically using the 'labelFormat' template - defined in Config.groovy. ( see comments in Config.groovy)

  2. Column Value: For each column you have to define the value that will be displayed in the cell. There's 2 options for this: In case the type of the grid is "gorm" or "list", and you just want do display a plain property you can use "property".

Otherwise you need to use the "value" closure, whose first parameter will be the actual row, and it will return whatever you need.

(There is a possibility to define the "property" columns more compact by using the actual property as the name of the column )

  1. Javascript settings: Another important section of each column is the javascript implementation section. All the properties defined here will be available in the render template to be used in whatever way.

Filtering:

Export

Easygrid also comes integrated with the Export Plugin This plugin has different settings for each export format , which can pe declared in the config file or in the grid.

Each column has an optional export section, where you can set additional properties like width, etc.

Column types

From the example you can also notice the type property of a column. Types are defined in Config.groovy, and represent a collection of properties that will be applied to this column, to avoid duplicate settings.

Default values:

Formatters:

The format to apply to a value is chosen this way:

  1. formatter - provided at the column level
  2. formatName - a format defined in the formats section of the Config file
  3. the type is matched to one of the types from the formats section of each datasource
  4. the value as it is

Security:

If you define the property securityProvider : then it will automatically guard all calls to the grid

Easygrid comes by default with a spring security implementation. Using this default implementation you can specify which roles are allowed to view or inline edit the grid

Other grid properties:

You can customize every aspect of the grid - because everything is a property and can be overriden. You can also add any property to any section of the configuration, and access it from the customizable template (or from a custom service, for advanced use cases)

Selection widget

The Selection widget is meant to replace drop down boxes (select) on forms where users have to select something from a medium or large dataset. It is composed from a jquery autocomplete textbox ( which has a closure attached on the server side) and from a selection dialog whith a full grid (with filtering & sorting), where the user can find what he's looking for, in case he can't find it using the fast autocomplete option. It can also by constrained by other elements from the same page or by statical values. ( for ex: in the demo , if you only want to select from british authors)

You can use any grid as a selection widget by configuring an "autocomplete" section (currently works only with jqGrid and GORM)

Online demo

Like this:

autocomplete {
    idProp 'id'                             // the id property
    labelValue { val, params ->             // the label can be a property or a closure ( more advanced use cases )
        "${val.name} (${val.nationality})"
    }
    textBoxFilterClosure {         // the closure called when a user inputs a text in the autocomplete input
        ilike('name', "%${params.term}%")
    }
    constraintsFilterClosure { params ->    // the closure that will handle constraints defined in the taglib ( see example)
        if (params.nationality) {
            eq('nationality', params.nationality)
        }
    }
}

Taglib:

Easygrid provies the following tags:

Testing:

In each annotated controller, for each grid defined in "grids" , the plugin injects multiple methods:

Guide on extending the default functionality:

FAQ:

I want to implement my first grid. What are the steps?

A: First you need to annotate a controller with @Easygrid, and define the desired grid def gridNameGrid = {..} In the gsp (if it belongs to that controller), all you have to do is: 1) add <r:require modules="easygrid-jqgrid-dev,export"/> (or whatever impelementation you're using) 2) <grid:grid name="gridName"/>

I need to customize the grid template. What are the properties of the gridConfig variable from the various templates?

A: Check out GridConfig.groovy

What is the role of the Filter parameter passed to the filterClosures?

A: Check out Filter.groovy.

Why does the filterClosure of the list implementation have 2 parameters?

A: Because on this implementation you also get the current row so that you can apply the filter on it, as opposed to the gorm implementation where the filter closure is a gorm criteria.

Isn't it bad practice to put view stuff in the controller?

A: You don't have to put view stuff in the controller. You are encouraged to define column types and as many default view properties as possible in the config file. Also , you can override or set any view property in the grid tag in the gsp.

I need to pass other view attributes to the ajax grid.

A: No problem, everything is extensible, just put it in the builder, and you can access it in the template.

I don't use spring security, can I remove the default implementation?

A: Yes you can. If there is no securityProvider defined, then no security restrictions are in place.

Is it possible to reference the same grid in multimple gsp pages, but with slight differences?

A: Yes, you can override the defined grid properties from the taglib. Check out the taglib section.

I don't like the default export.

A: No problem, you can replace the export service with your own.

Are the grid configs thread safe?

A: Yes.

The labelFormat property is weird

A: The labelFormat is transformed into a SimpleTemplateEngine instance and populated at runtime with the gridConfig and the prefix.

Are there any security holes I should be aware of?

A: All public methods are guarded by the security provider you defined either in the config or in the grid. You can also apply your own security filter on top of the generated controller actions.

What is the difference between Easygrid and the JqGrid plugin?

A: The JqGrid plugin is a thin wrapper over JqGrid. It provides the resources and a nice taglib. But you still have to code yourself all the server side logic. Easygrid does much more than that.

I use the jqgrid plugin. How difficult is it to switch to easygrid?

A: If you already use jqgrid, then you probably have the grid logic split between the controller and the view. If you have inline editing enabled, then you probably have at least 2 methods in the controller. Basically, you need to strip the grid to the minimum properties ( the columns and additional properties) , translate that intro the easygrid builder and just use the simple easygrid taglib. If, after converting a couple of grids, you realize there's common patterns, you are encouraged to set default values and define column types, to minimize code duplication. After the work is done, you will realize the grid code is down to 10%.

I have one grid with very different view requirements from the rest. What should I do.

A: You can create a gsp template just for it and set it in the builder.

The value formatting is complicated

A: It's designed to be flexible.

Can I just replace a select box with a selection widget?

A: Yes, it's pretty easy, it's justa matter of replacing the select tag . Nothing needs to be changed in the controller

I want to customize the selection widget.

A: Just create a new autocomplete renderer template and use the selection jquery ui widget

I need more information on how to.. ?

I have a suggestion.

A: You can raise a github ticket, drop me an email to: tudor.malene@gmail.com, or use the grails mailing list

Version History

1.7.1

Bugs:

1.7.0

Improvements:

Bugs:

1.6.9.1

Improvements:

Bugs:

1.6.9

Bugs:

1.6.8

Bugs:

1.6.7

Bugs:

1.6.6

Bugs:

1.6.5

Improvements:

Bugs:

1.6.4

Bugs:

1.6.3

Improvements:

Bugs:

1.6.2

Improvements:

Bugs:

1.6.1

Improvements:

1.6.0

Improvements:

Bugs:

1.5.2

Bugs:

1.5.1

Improvements:

Bugs:

1.5.0

Improvements:

1.4.6

Bugs:

Improvements:

1.4.5

Improvements:

1.4.4

Improvements:

Bugs:

1.4.3

Improvements:

Bugs:

1.4.2

Improvements:

Bugs:

1.4.1

1.4.0

1.3.0

1.2.0

1.1.0

1.0.0

0.9.9

Upgrade

Upgrading to 1.6.9.1

Upgrading to 1.6.5

You can add the resources via the resources plugin like this: <r:require modules="easygrid-jqgrid-dev,export"/> , or via the assets-pipeline plugin like:

     <asset:javascript src="https://github.com/tudor-malene/Easygrid/raw/master/easygrid.jqgrid.js"/>
     <asset:stylesheet src="https://github.com/tudor-malene/Easygrid/raw/master/easygrid.jqgrid.css"/>
     <asset:stylesheet src="https://github.com/tudor-malene/Easygrid/raw/master/export.css"/>

The convention is: 'easygrid.${implementation}.[js/css]' For the selection widget you have to add: 'easygrid.selection.js' together with jqgrid

Upgrading to 1.6.4

Merge _jqGridRenderer.gsp and/or _dataTablesGridRenderer.gsp

Upgrading to 1.6.3

Merge _dataTablesGridRenderer.gsp

Upgrading to 1.6.1

Merge _jqGridRenderer.gsp and/or _dataTablesGridRenderer.gsp

Upgrading to 1.6.0

This version will break only custom inline edit closures. To upgrade you need to add a second parameter to saveRowClosure, updateRowClosure or delRowClosure. This parameter will be of type InlineResponse. In order to send messages to the UI you will need to use this object instead of the return values. Checkout the reference implementation: GormDatasourceService.updateRow

Upgrading to 1.5.0

This is a major update and it will break existing grids. Please let me know ASAP if you have problems upgrading First of all check the petclinic example: and the example repositories.

Upgrading to 1.4.2

Upgrading to 1.4.0

Upgrading to 1.3.0

Upgrading to 1.2.0

Upgrading to 1.1.0

Upgrading to 1.0.0

License

Apache v2