Permalink
Browse files

Refactored Navigation API with lots of printlns in still. Changed to …

…support hierarchies, DSL, reloading. Primary/secondary appear to work
  • Loading branch information...
1 parent 50806b3 commit 5fc9fa3e4f28035c21fe26299ba9b3daa4a25540 @marcpalmer marcpalmer committed Apr 6, 2012
@@ -36,6 +36,7 @@ class PlatformCoreGrailsPlugin {
// resources that are excluded from plugin packaging
def pluginExcludes = [
"grails-app/conf/TestResources.groovy",
+ "grails-app/conf/TestNavigation.groovy",
"grails-app/i18n/test.properties",
"grails-app/domain/org/grails/plugin/platform/test/**/*.groovy",
"grails-app/controllers/org/grails/plugin/platform/test/**/*.groovy",
@@ -48,6 +49,13 @@ class PlatformCoreGrailsPlugin {
def observe = ['*'] // We observe everything so we can re-apply dynamic methods, conventions etc
+ def watchedResources = [
+ "file:./grails-app/conf/*Navigation.groovy",
+ "file:./plugins/*/grails-app/conf/*Navigation.groovy"
+ ]
+
+ def artefacts = [getNavigationArtefactHandler()]
+
def title = "Plugin Platform Core"
def author = "Marc Palmer"
def authorEmail = "marc@grailsrocks.com"
@@ -200,15 +208,37 @@ Grails Plugin Platform Core APIs
def onChange = { event ->
def ctx = event.application.mainContext
def config = event.application.config
- switch (event.source) {
- case Class:
- ctx.grailsInjection.applyTo(event.source)
- // @todo add call to update auto nav for controllers, we badly need "onreload" events for this
-
- if (application.isServiceClass(event.source)) {
- ctx.grailsEvents.reloadListener(event.source)
- }
- break
+
+ def navArtefactType = getNavigationArtefactHandler().TYPE
+ if (application.isArtefactOfType(navArtefactType, event.source)) {
+ ctx.grailsNavigation.reload(event.source)
+ } else if (application.isArtefactOfType('Controller', event.source)) {
+ ctx.grailsNavigation.reload() // conventions on controller may have changed
+ } else {
+ switch (event.source) {
+ case Class:
+ ctx.grailsInjection.applyTo(event.source)
+ // @todo add call to update auto nav for controllers, we badly need "onreload" events for this
+
+ if (application.isServiceClass(event.source)) {
+ ctx.grailsEvents.reloadListener(event.source)
+ }
+ break
+ }
+ }
+ }
+
+
+ static getNavigationArtefactHandler() {
+ softLoadClass('org.grails.plugin.platform.navigation.NavigationArtefactHandler')
+ }
+
+ static softLoadClass(String className) {
+ try {
+ getClassLoader().loadClass(className)
+ } catch (ClassNotFoundException e) {
+ println "ERROR: Could not load $className"
+ null
}
}
}
@@ -0,0 +1,10 @@
+
+navigation = {
+ app {
+ test(controller:'test', action:'action2')
+ 'platform-testing'(controller:'sample') {
+ save action:'testSave'
+ load action:'testLoad'
+ }
+ }
+}
@@ -22,8 +22,6 @@ class PlatformToolsController {
def grailsSecurity
def grailsNavigation
- static navigationScope = "platform"
-
def index = {
}
@@ -25,18 +25,41 @@ class NavigationTagLib {
static returnObjectForTags = ['activePath', 'activeNode', 'scopeForActivationPath', 'firstActiveNode']
def grailsNavigation
+ def grailsApplication
+ /**
+ * Render a primary navigation menu
+ * @attr path Optional activation path. If not specified, uses current request's activation path
+ * @attr scope Optional scope of menu to render. If not specified, uses default scope determined by activation path or "app"
+ */
def primary = { attrs ->
- def scope = grailsNavigation.getPrimaryScopeFor(attrs.path ?: grailsNavigation.getActivePath(request))
- attrs.scope = scope
+ if (!attrs.scope) {
+ attrs.scope = 'app'
+ }
+ request['plugin.platformCore.navigation.primaryScope'] = attrs.scope
out << nav.menu(attrs)
}
+ /**
+ * Render the secondary navigation menu
+ * @attr path Optional activation path. If not specified, uses current request's activation path
+ * @attr scope Optional scope of menu to render. If not specified, uses default scope determined by activation path or "app"
+ */
def secondary = { attrs ->
- def scope = grailsNavigation.getSecondaryScopeFor(attrs.path ?: grailsNavigation.getActivePath(request))
- if (scope) {
- attrs.scope = scope
- out << nav.menu(attrs)
+ def activeNodes = findActiveNodes(attrs.path)
+ println "Active nodes are: ${activeNodes?.dump()}"
+ if (activeNodes?.size() > 1) {
+ def currentScope = grailsNavigation.scopeByName(request['plugin.platformCore.navigation.primaryScope'])
+ def target = activeNodes[-1]
+ println "Active node is in currentScope ${currentScope?.name}?: ${target.inScope(currentScope)}"
+ // Only render secondary if the user is actively in a sub-menu of a primary nav option
+ if (currentScope && target.inScope(currentScope)) {
+ if (activeNodes[0].children) {
+ println "Rendering secondary for: ${target?.dump()}"
+ attrs.scope = activeNodes[0].id
+ out << nav.menu(attrs)
+ }
+ }
}
}
@@ -57,16 +80,18 @@ class NavigationTagLib {
def activeNodes = findActiveNodes(attrs.path)
- def scopeNode = grailsNavigation.scopeByName(scope)
+ def callbackContext = [grailsApplication:grailsApplication]
+
+ def scopeNode = grailsNavigation.nodeForId(scope)
if (scopeNode) {
out << "<ul ${id}class=\"${cssClass}\">"
for (n in scopeNode.children) {
- if (n.visible) {
+ if (n.isVisible(callbackContext)) {
def liClass
if (activeNodes.contains(n)) {
liClass = ' class="active"'
}
- if (!n.enabled) {
+ if (!n.isEnabled(callbackContext)) {
liClass = ' class="disabled"'
}
out << "<li${liClass ?: ''}>"
@@ -78,14 +103,40 @@ class NavigationTagLib {
out << "</ul>"
}
}
+
+ def items = { attrs, body ->
+ def scope = attrs.scope
+ def node = attrs.node
+ if (!scope && !node) {
+ scope = 'app'
+ }
+ if (scope && !(scope instanceof String)) {
+ scope = scope.name
+ }
+
+ def varName = attrs.var
+
+ out << "<ul>"
+ NavigationNode parentNode = node ?: grailsNavigation.scopeByName(scope)
+ for (n in parentNode.children) {
+ out << "<li>"
+ out << body( (varName ? [(varName):n] : n) )
+ if (n.children) {
+ out << nav.items([node:n, var:varName], body)
+ }
+ out << "</li>"
+ }
+ out << "</ul>"
+ }
def scopeForActivationPath = { attrs ->
- attrs.path ? grailsNavigation.getScopeForActivationPath(attrs.path) : grailsNavigation.getScopeForActiveNode(request)
+ out << "NOT IMPLEMENTED - DO WE NEED THIS?"
+// attrs.path ? grailsNavigation.getScopeForId(attrs.path) : grailsNavigation.getScopeForActiveNode(request)
}
def firstActiveNode = { attrs ->
- def r = attrs.path ? grailsNavigation.getFirstNodeOfActivationPath(attrs.path) : grailsNavigation.getFirstActiveNode(request)
- return r ?: [id:''] // workaround for grails 2 bug
+ def r = findActiveNodes(attrs.path)
+ return r.size() ? r[0] : [id:''] // workaround for grails 2 bug
}
def setActivePath = { attrs ->
@@ -97,11 +148,11 @@ class NavigationTagLib {
}
private List<NavigationNode> findActiveNodes(String activePath) {
- grailsNavigation.nodesForActivationPath(activePath ?: grailsNavigation.getActivePath(request))
+ grailsNavigation.nodesForPath(activePath ?: grailsNavigation.getActivePath(request))
}
private NavigationNode findActiveNode(String activePath) {
- grailsNavigation.nodeForActivationPath(activePath ?: grailsNavigation.getActivePath(request))
+ grailsNavigation.nodeForId(activePath ?: grailsNavigation.getActivePath(request))
}
def activePath = { attrs ->
@@ -6,7 +6,7 @@
</head>
<body>
<div id="nav">
- <nav:primary class="nav nav-primary"/>
+ <nav:primary class="nav nav-primary" scope="dev"/>
<hr/>
<nav:secondary class="nav nav-secondary"/>
</div>
@@ -15,7 +15,6 @@
<input type="submit"/>
</g:form>
- <p>Scope for this path is: ${nav.scopeForActivationPath(path:params.activePath)}</p>
<p>First active node for this path is: ${nav.firstActiveNode(path:params.activePath)?.id}</p>
<p>Primary navigation for this path:</p>
<nav:primary path="${params.activePath}"/>
@@ -26,20 +25,19 @@
<p>The available navigation nodes are:
<ul>
<g:each in="${navScopes}" var="scope">
- <li>${scope.name.encodeAsHTML()}</li>
- <ul>
- <g:each in="${scope.children}" var="item">
- <li>id: ${item.id.encodeAsHTML()}<br/>
- activation path: ${item.activationPath.encodeAsHTML()}
- <g:if test="${item.activationPath == params.activePath}"><strong>ACTIVE</strong></g:if>
- <br/>
- title: ${item.titleMessageCode.encodeAsHTML()} (<g:message code="${item.titleMessageCode}" encodeAs="HTML"/>)<br/>
- default title: ${item.titleDefault.encodeAsHTML()}<br/>
- visible: ${item.visibleClosure ? 'from Closure' : item.visible}<br/>
- enabled: ${item.enabledClosure ? 'from Closure' : item.enabled}<br/>
- link: ${item.linkArgs.encodeAsHTML()}</li>
- </g:each>
- </ul>
+ <li>${scope.name.encodeAsHTML()}
+ <nav:items scope="${scope}" var="item">
+ id: ${item.id.encodeAsHTML()}
+ <g:if test="${item.id == params.activePath}"><strong>ACTIVE</strong></g:if>
+ <br/>
+ name: ${item.name.encodeAsHTML()}<br/>
+ title: ${item.titleMessageCode.encodeAsHTML()} (<g:message code="${item.titleMessageCode}" encodeAs="HTML"/>)<br/>
+ default title: ${item.titleDefault.encodeAsHTML()}<br/>
+ visible: ${item.visibleClosure ? 'from Closure' : item.visible}<br/>
+ enabled: ${item.enabledClosure ? 'from Closure' : item.enabled}<br/>
+ link: ${item.linkArgs.encodeAsHTML()}<br/>
+ </nav:items>
+ </li>
</g:each>
</ul>
</p>
@@ -9,12 +9,11 @@ package org.grails.plugin.platform.conventions
* }
* -or with arguments-
*
- * something(x:y, p:q) {
+ * something(a, b) {
* other = foo
* bar a:b
* }
*/
-class DSLBlockCommand extends DSLCommand {
+class DSLBlockCommand extends DSLCallCommand {
List<DSLCommand> children
- List arguments
}
@@ -0,0 +1,13 @@
+package org.grails.plugin.platform.conventions
+
+/**
+ * Encapsulate a DSL command that represents a nested block of commands
+ *
+ * something(x:y, p:q) {
+ * other = foo
+ * bar a:b
+ * }
+ */
+class DSLNamedArgsBlockCommand extends DSLNamedArgsCallCommand {
+ List<DSLCommand> children
+}
@@ -17,7 +17,12 @@ class StandardDSLDelegate {
}
private __newBlock(String name, args, Closure body) {
- DSLCommand cmd = new DSLBlockCommand(name: name, arguments: args ?: [])
+ DSLCommand cmd
+ if ((args?.size() == 1) && args[0] instanceof Map) {
+ cmd = new DSLNamedArgsBlockCommand(name: name, arguments: args[0] ?: [])
+ } else {
+ cmd = new DSLBlockCommand(name: name, arguments: args ?: [])
+ }
List<DSLCommand> results = []
def nestedDelegate = new StandardDSLDelegate(results)
body.resolveStrategy = Closure.DELEGATE_FIRST
@@ -41,7 +46,7 @@ class StandardDSLDelegate {
}
} else {
if (args[-1] instanceof Closure) {
- command = __newBlock(name, args[0..args.length()-2], args[-1])
+ command = __newBlock(name, args[0..args.size()-2], args[-1])
} else {
command = new DSLCallCommand(name: name, arguments:args)
}
Oops, something went wrong.

0 comments on commit 5fc9fa3

Please sign in to comment.