Skip to content

Commit

Permalink
Added ng-generate-all script
Browse files Browse the repository at this point in the history
Fixes #3
  • Loading branch information
robfletcher committed Jun 25, 2012
1 parent eca77bd commit 0fc582b
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 137 deletions.
24 changes: 23 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,31 @@ This is a Grails plugin that allows you to use [Angular.js](http://angularjs.org
After installing the plugin run:

grails ng-install-templates
grails generate-controller _domain class name_

This will install the Angular JS scaffolding templates into your project under `src/templates/scaffolding`. It will also copy some common HTML template files that will be shared by all scaffolded views into `web-app/ng-templates`.

### Static scaffolding

To generate the controller and views for a domain class run:

grails ng-generate-all _domain class name_

### Dynamic scaffolding

Dynamic scaffolding is only supported for the controller. Currently you will need to generate the views for each domain class.

To generate only the views and use a dynamically scaffolded controller run:

grails ng-generate-views _domain class name_

## How it works

Instead of the Grails controller rendering a view for each page using a GSP the controller's _index_ action serves up an initial framework page containing the JavaScript resources required by Angular JS. The remaining controller actions simply return _JSON_ data.

Each _'page'_ in the CRUD interface for a particular domain class is accessed using a URL fragment; `#/list`, `#/create`, etc. The page content is rendered by Angular JS using an HTML template and the data to populate the page is retrieved from the controller using an _AJAX_ call.

The HTML templates need to be generated individually for each domain class as they contain the markup needed to represent the properties of that class in a list or a form. However, the JavaScript used for the CRUD interface is the same for all domain classes.

## Limitations

This is an experimental work-in-progress. See the [issues list](https://github.com/robfletcher/grails-angular-scaffolding/issues) for outstanding features.
Expand Down
11 changes: 11 additions & 0 deletions scripts/NgGenerateAll.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
includeTargets << new File("${angularScaffoldingPluginDir}/scripts/_NgGenerate.groovy")

target(default: 'Generates a CRUD interface (controller + Angular JS views) for a domain class') {
depends checkVersion, parseArguments, packageApp

promptForName type: 'Domain Class'
generateController = true
generateForName = argsMap.params[0]

generateForOne()
}
136 changes: 1 addition & 135 deletions scripts/NgGenerateViews.groovy
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
import grails.util.GrailsNameUtils
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.springframework.core.io.FileSystemResource
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.util.Assert
import org.codehaus.groovy.grails.scaffolding.*

includeTargets << grailsScript("_GrailsCreateArtifacts")
includeTargets << grailsScript("_GrailsGenerate")
includeTargets << grailsScript("_GrailsBootstrap")

generateForName = null
generateViews = true
generateController = true
includeTargets << new File("${angularScaffoldingPluginDir}/scripts/_NgGenerate.groovy")

target(default: 'Generates HTML Angular JS view templates for a specified domain class') {
depends checkVersion, parseArguments, packageApp
Expand All @@ -22,124 +9,3 @@ target(default: 'Generates HTML Angular JS view templates for a specified domain

generateForOne()
}

target(generateForOne: 'Generates controllers and views for only one domain class.') {
depends(compile, loadApp)

def name = generateForName
name = name.indexOf('.') > 0 ? name : GrailsNameUtils.getClassNameRepresentation(name)
def domainClass = grailsApp.getDomainClass(name)

if (!domainClass) {
grailsConsole.updateStatus 'Domain class not found in grails-app/domain, trying hibernate mapped classes...'
bootstrap()
domainClass = grailsApp.getDomainClass(name)
}

if (domainClass) {
generateForDomainClass(domainClass)
event 'StatusFinal', ["Finished generation for domain class ${domainClass.fullName}"]
}
else {
event 'StatusFinal', ["No domain class found for name ${name}. Please try again and enter a valid domain class name"]
exit 1
}
}

def generateForDomainClass(domainClass) {
def templateGenerator = new AngularTemplateGenerator(classLoader)
templateGenerator.grailsApplication = grailsApp
templateGenerator.pluginManager = pluginManager
templateGenerator.event = event
if (generateViews) {
event 'StatusUpdate', ["Generating views for domain class ${domainClass.fullName}"]
templateGenerator.generateViews(domainClass, basedir)
event 'GenerateViewsEnd', [domainClass.fullName]
}

if (generateController) {
event 'StatusUpdate', ["Generating controller for domain class ${domainClass.fullName}"]
templateGenerator.generateController(domainClass, basedir)
event 'GenerateControllerEnd', [domainClass.fullName]
}
}

/**
* Can't seem to load this if it's on the plugin source path so I've inlined it here
*/
class AngularTemplateGenerator extends DefaultGrailsTemplateGenerator {

def event

AngularTemplateGenerator(ClassLoader classLoader) {
super(classLoader)
}

@Override
void generateViews(GrailsDomainClass domainClass, String destdir) {
Assert.hasText destdir, 'Argument [destdir] not specified'

for (t in getTemplateNames()) {
event 'StatusUpdate', ["Generating $t for domain class ${domainClass.fullName}"]
generateView domainClass, t, new File(destdir).absolutePath
}
}

@Override
void generateView(GrailsDomainClass domainClass, String viewName, Writer out) {
def templateText = getTemplateText(viewName)

if (templateText) {

def t = engine.createTemplate(templateText)
def multiPart = domainClass.properties.find {it.type == ([] as Byte[]).class || it.type == ([] as byte[]).class}

boolean hasHibernate = pluginManager?.hasGrailsPlugin('hibernate')
def packageName = domainClass.packageName ? "<%@ page import=\"${domainClass.fullName}\" %>" : ""
def binding = [pluginManager: pluginManager,
packageName: packageName,
domainClass: domainClass,
multiPart: multiPart,
className: domainClass.shortName,
propertyName: getPropertyName(domainClass),
renderEditor: renderEditor,
comparator: hasHibernate ? DomainClassPropertyComparator : SimpleDomainClassPropertyComparator]

t.make(binding).writeTo(out)
}
}

@Override
void generateView(GrailsDomainClass domainClass, String viewName, String destDir) {
def suffix = viewName.find(/\.\w+$/)

def viewsDir = suffix == '.html' ? new File("$destDir/web-app/ng-templates/$domainClass.propertyName") : new File("$destDir/grails-app/views/$domainClass.propertyName")
if (!viewsDir.exists()) viewsDir.mkdirs()

def destFile = new File(viewsDir, "$viewName")
destFile.withWriter { Writer writer ->
generateView domainClass, viewName, writer
}
}

@Override
def getTemplateNames() {
def resources = []
def resolver = new PathMatchingResourcePatternResolver()
def templatesDirPath = "${basedir}/src/templates/scaffolding"
def templatesDir = new FileSystemResource(templatesDirPath)
if (templatesDir.exists()) {
try {
resources.addAll(resolver.getResources("file:$templatesDirPath/*.html").filename)
resources.addAll(resolver.getResources("file:$templatesDirPath/*.gsp").filename)
} catch (e) {
event 'StatusError', ['Error while loading views from grails-app scaffolding folder', e]
}
}

resources
}

private String getPropertyName(GrailsDomainClass domainClass) { "${domainClass.propertyName}${domainSuffix}" }

}
135 changes: 135 additions & 0 deletions scripts/_NgGenerate.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import grails.util.GrailsNameUtils
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.springframework.core.io.FileSystemResource
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.util.Assert
import org.codehaus.groovy.grails.scaffolding.*

includeTargets << grailsScript("_GrailsCreateArtifacts")
includeTargets << grailsScript("_GrailsGenerate")
includeTargets << grailsScript("_GrailsBootstrap")

generateForName = null
generateViews = true
generateController = true

target(generateForOne: 'Generates controllers and views for only one domain class.') {
depends compile, loadApp

def name = generateForName
name = name.indexOf('.') > 0 ? name : GrailsNameUtils.getClassNameRepresentation(name)
def domainClass = grailsApp.getDomainClass(name)

if (!domainClass) {
grailsConsole.updateStatus 'Domain class not found in grails-app/domain, trying hibernate mapped classes...'
bootstrap()
domainClass = grailsApp.getDomainClass(name)
}

if (domainClass) {
generateForDomainClass(domainClass)
event 'StatusFinal', ["Finished generation for domain class ${domainClass.fullName}"]
}
else {
event 'StatusFinal', ["No domain class found for name ${name}. Please try again and enter a valid domain class name"]
exit 1
}
}

def generateForDomainClass(domainClass) {
def templateGenerator = new AngularTemplateGenerator(classLoader)
templateGenerator.grailsApplication = grailsApp
templateGenerator.pluginManager = pluginManager
templateGenerator.event = event
if (generateViews) {
event 'StatusUpdate', ["Generating views for domain class ${domainClass.fullName}"]
templateGenerator.generateViews(domainClass, basedir)
event 'GenerateViewsEnd', [domainClass.fullName]
}

if (generateController) {
event 'StatusUpdate', ["Generating controller for domain class ${domainClass.fullName}"]
templateGenerator.generateController(domainClass, basedir)
event 'GenerateControllerEnd', [domainClass.fullName]
}
}

/**
* Can't seem to load this if it's on the plugin source path so I've inlined it here
*/
class AngularTemplateGenerator extends DefaultGrailsTemplateGenerator {

def event

AngularTemplateGenerator(ClassLoader classLoader) {
super(classLoader)
}

@Override
void generateViews(GrailsDomainClass domainClass, String destdir) {
Assert.hasText destdir, 'Argument [destdir] not specified'

for (t in getTemplateNames()) {
event 'StatusUpdate', ["Generating $t for domain class ${domainClass.fullName}"]
generateView domainClass, t, new File(destdir).absolutePath
}
}

@Override
void generateView(GrailsDomainClass domainClass, String viewName, Writer out) {
def templateText = getTemplateText(viewName)

if (templateText) {

def t = engine.createTemplate(templateText)
def multiPart = domainClass.properties.find {it.type == ([] as Byte[]).class || it.type == ([] as byte[]).class}

boolean hasHibernate = pluginManager?.hasGrailsPlugin('hibernate')
def packageName = domainClass.packageName ? "<%@ page import=\"${domainClass.fullName}\" %>" : ""
def binding = [pluginManager: pluginManager,
packageName: packageName,
domainClass: domainClass,
multiPart: multiPart,
className: domainClass.shortName,
propertyName: getPropertyName(domainClass),
renderEditor: renderEditor,
comparator: hasHibernate ? DomainClassPropertyComparator : SimpleDomainClassPropertyComparator]

t.make(binding).writeTo(out)
}
}

@Override
void generateView(GrailsDomainClass domainClass, String viewName, String destDir) {
def suffix = viewName.find(/\.\w+$/)

def viewsDir = suffix == '.html' ? new File("$destDir/web-app/ng-templates/$domainClass.propertyName") : new File("$destDir/grails-app/views/$domainClass.propertyName")
if (!viewsDir.exists()) viewsDir.mkdirs()

def destFile = new File(viewsDir, "$viewName")
destFile.withWriter { Writer writer ->
generateView domainClass, viewName, writer
}
}

@Override
def getTemplateNames() {
def resources = []
def resolver = new PathMatchingResourcePatternResolver()
def templatesDirPath = "${basedir}/src/templates/scaffolding"
def templatesDir = new FileSystemResource(templatesDirPath)
if (templatesDir.exists()) {
try {
resources.addAll(resolver.getResources("file:$templatesDirPath/*.html").filename)
resources.addAll(resolver.getResources("file:$templatesDirPath/*.gsp").filename)
} catch (e) {
event 'StatusError', ['Error while loading views from grails-app scaffolding folder', e]
}
}

resources
}

private String getPropertyName(GrailsDomainClass domainClass) { "${domainClass.propertyName}${domainSuffix}" }

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class AlbumController {
def list() {
cache false
params.max = Math.min(params.max ? params.int('max') : 10, 100)
response.setIntHeader('X-Pagination-Total', Album.count())
response.setIntHeader('X-Pagination-Total', Album.count())
render Album.list(params) as JSON
}

Expand Down

0 comments on commit 0fc582b

Please sign in to comment.