Skip to content

rainboyan/grails-plugin-dynamic-modules

Repository files navigation

Grails Dynamic Modules Plugin

Grails Dynamic Modules Plugin (GDMP) offer new ways of creating modular and maintainable Grails applications.

A Grails plugin can implement one or more plugin modules to develop and extend Grails applications. We can use Dynamic Modules to maximize the use of Grails plugins and create an open, shared, and reusable plugin market.

I will provide more Module Types in later releases, and also welcome to contribute more types of modules, as well as plugins for these modules.

Grails Version

  • Grails 4.1.2

Usage

This plugin has been released to Maven Central.

Since this plugin is for building multiple modules, I highly recommend that you read this guide Grails Multi-Project Build first.

.
├── gradle
│   └── wrapper
├── grails-app
│   ├── assets
│   ├── conf
│   ├── controllers
│   ├── domain
│   ├── i18n
│   ├── init
│   ├── services
│   ├── taglib
│   ├── utils
│   └── views
├── plugins
│   └── menu
├── src
│   ├── integration-test
│   ├── main
│   └── test
├── build.gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── grails-wrapper.jar
├── grailsw
├── grailsw.bat
└── settings.gradle

First, you should add the dependency to your app build.gradle,

repositories {
    mavenCentral()
}

dependencies {

    // Grails 4
    compile "org.rainboyan.plugins:grails-plugin-dynamic-modules:0.1.0"

    // Grails 5
    implementation "org.rainboyan.plugins:grails-plugin-dynamic-modules:0.1.0"
}

Then in your Grails plugin project: menu, create your first Module descriptor: MenuModuleDescriptor,

@ModuleType('menu')
class MenuModuleDescriptor extends AbstractModuleDescriptor {

    String i18n
    String title
    String link
    String location
    int order

    MenuModuleDescriptor() {
    }

    @Override
    void init(GrailsPlugin plugin, Map args) throws PluginException {
        super.init(plugin, args)
        this.i18n = args.i18n
        this.title = args.title
        this.link = args.link
        this.location = args.location
    }
}

Update the MenuGrailsPlugin to extend grails.plugins.DynamicPlugin,

class MenuGrailsPlugin extends DynamicPlugin {

    // 1. add your new module types
    def providedModules = [
            MenuModuleDescriptor
    ]

    // 2. define 'menu' modules in doWithDynamicModules
    void doWithDynamicModules() {
        menu(key: 'about', i18n: 'menu.about', title: 'About US', link: '/about', location: 'topnav')
        menu(key: 'product', i18n: 'menu.product', title: 'Products', link: '/product', location: 'topnav', enabled: "${Environment.isDevelopmentMode()}") {
            description = "This menu enabled: ${Environment.isDevelopmentMode()}"
            order = 2
        }
        menu(key: 'contact', i18n: 'menu.contact', title: 'Contact', link: '/contact', location: 'topnav', enabled: false)
        menu(key: 'help', i18n: 'menu.help', title: 'Help', link: '/help', location: 'footer')
    }
}

DynamicModules plugin support more methods of GrailsPluginManager,

you can get all the ModuleDescriptor in your Grails application throug the two methods of GrailsPluginManager,

// Get all the ModuleDescriptors
Collection<ModuleDescriptor<?>> allDescriptors = pluginManager.getModuleDescriptors()

// Get all the enabled MenuModuleDescriptor
List<MenuModuleDescriptor> menuDescriptors = pluginManager.getEnabledModuleDescriptorsByClass(MenuModuleDescriptor)

I've written a demo app for you that you can clone to your local computer, run it, and go through the code to learn more.

Development

Build from source

git clone https://github.com/rainboyan/grails-plugin-dynamic-modules.git
cd grails-plugin-dynamic-modules
./gradlew publishToMavenLocal

Support Grails Version

  • Grails 4.0, 4.1
  • Grails 5.0, 5.1, 5.2, 5.3
  • Grails 6.0

Known issues

Grails has a bug that has been around since 2.0.0. I have submitted a patch for this bug, you can learn about it here, hope to fix it in the next release.

In your MyNewGrailsPlugin(which extends DynamicPlugin), when you using doWithSpring(), there will be an error reporting that A component required a bean of type 'org.rainboyan.plugins.ModuleDescriptorFactory' that could not be found., this is because the Closure doWithSpring's delegation strategy was not set, Closure.OWNER_FIRST is the default strategy.

    Closure doWithSpring() { {->
        // Grails bugs here, because doWithSpring's delegation strategy not set
           webMenuManager(DefaultWebMenuManager)
        }
    }

Although 6 months have passed and the PR#12892 for this issue has not been merged grails-core, there is still a way to solve this problem.

That is to use Java-based Container Configuration instead of Grails Plugin.doWithSpring().

@Configuration(proxyBeanMethods = false)
public class MenuModuleAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public WebMenuManager webMenuManager() {
        return new DefaultWebMenuManager();
    }

}

License

This plugin is available as open source under the terms of the APACHE LICENSE, VERSION 2.0

Links