I'm trying to write a Grails custom tag that (among other things) triggers inclusion of a resource, so something like <myTags:view name="foo"/>
would load, say, js/views/foo.js
. And I want it loaded with disposition: 'head'
.
I could use <r:external/>
, but that wouldn't put it in the <head>
, it would just produce an inline <script/>
tag. And I could use <r.script/>
, but that doesn't let me reference a path; I'd have to have my custom tag read the file and dump it to out
.
Now, if foo.js
was its own module, I could do something like: r.require([module: 'foo'])
, but it's not; part of the point of this is that I don't want to have to declare all of these files in ApplicationResources.groovy
. But maybe I could have ApplicationResources.groovy
create the modules programmatically, by reading through the available files -- is that possible? Or is there a better way?
I ended up going in the direction of having ApplicationResources.groovy
create modules programmatically, so the custom tag can use <r:require/>
.
The idea is, for each Backbone view, under web-app/myApp/views
, there's a Backbone view in a .js
file, and a Handlebars template in a .handlebars
file (with the same name, by convention). The .handlebars
file gets declared as an ordinary module, but gets precompiled by the Handlebars-Resources plugin.
Some code in ApplicationResources.groovy
finds all the views and creates corresponding resource modules:
GrailsApplication grailsApplication = Holders.getGrailsApplication()
File viewsDir = grailsApplication.parentContext.getResource("myApp/views").file;
if (viewsDir.exists() && viewsDir.isDirectory() && viewsDir.canRead()) {
String[] viewsJS = viewsDir.list().findAll { name ->
name.endsWith("View.js")
}
String[] views = viewsJS.collect { name ->
name.substring(0, name.length() - ".js".length())
}
for (view in views) {
"${view}" {
dependsOn 'backbone', 'backbone_relational', 'handlebars'
resource url: "dpg/views/${view}.handlebars",
attrs: [type: 'js'],
disposition: 'head'
resource url: "dpg/views/${view}.js",
disposition: 'head'
}
}
}
Then the taglib:
class ViewsTagLib {
static namespace = "myApp"
def view = { attrs ->
r.require(module: "${attrs.name}View")
out << "<${attrs.tagName} id='${attrs.id}'></${attrs.tagName}>"
}
}
Invoking it in a GSP:
<myApp:view tagName="div" name="foo" id="foo1"/>
<myApp:view tagName="div" name="foo" id="foo2"/>
Produces:
<html>
<head>
...
<!--
Automagically generated modules (included only once).
Should put these in the same bundle, but handlebars-resources
gets confused.
-->
<script src="/myApp/static/bundle-bundle_fooView_handlebars.js"
type="text/javascript" ></script>
<script src="/myApp/static/bundle-bundle_fooView_head.js"
type="text/javascript" ></script>
</head>
<body>
...
<div id="foo1"></div> <!-- backbone placeholder for 1st view instance-->
<div id="foo2"></div> <!-- backbone placeholder for 2nd view instance-->
</body>
</html>
It's not pretty but the mess is mostly hidden, and it should cut down considerably on boilerplate and on opportunities to forget to add magic strings to multiple files.