I have a grails app that contains a whole mess of individual javascript files in a series of nested directories. I want to manage them via the resources plugin, but don't want to have to explicitly register each one.
Web Dir Structure
webapp
app
controller
controller1.js
controller2.js
...
model
model1.js
...
view
view1.js
what would be great is to just declare in my AppResources.groovy file:
resource url: 'app/**/*.js'
but that doesn't work -- throws a null pointer. I've tried:
resource url: 'app/**' but with no luck
I've thought that i would put some code in the config file that would recurse through the directory structure, but that doesn't seem to be working. Here's what I've tried:
def iterClos = {
it.eachDir( iterClos );
it.eachFile {
resource url: ${it.canonicalPath};
}
}
iterClos( new File("$grails.app.context/app") )
Unfortunately that failed as well.
Does anyone have any ideas how I could achieve this?
problem solved.
As it turns out, the idea of running code to recuse through my javascript directories works. I just had the code incorrect. Here is the code that dynamically loads my javascript files:
--AppResources.groovy
import org.codehaus.groovy.grails.web.context.ServletContextHolder as SCH
modules = {
core {
resource url: '/resources/css/app.css', disposition: 'head'
resource url: '/resources/css/myapp.css', disposition: 'head'
resource url: '/extjs/ext-all-debug.js', dispostion: 'head'
getFilesForPath('/app').each {
resource url: it
}
}
}
def getFilesForPath(path) {
def webFileCachePaths = []
def servletContext = SCH.getServletContext()
//context isn't present when testing in integration mode. -jg
if(!servletContext) return webFileCachePaths
def realPath = servletContext.getRealPath('/')
def appDir = new File("$realPath/$path")
appDir.eachFileRecurse {File file ->
if (file.isDirectory() || file.isHidden()) return
webFileCachePaths << file.path.replace(realPath, '')
}
webFileCachePaths
}
The above will cause the Resource plugin to track my javascript files. Here's what the html looks like when Resources is in debug mode:
<script src="/myapp/extjs/ext-all-debug.js?_debugResources=y&n=1336614540164" type="text/javascript" ></script>
<script src="/myapp/app/controller/LogController.js?_debugResources=y&n=1336614540164" type="text/javascript" ></script>
<script src="/myapp/app/controller/LoginController.js?_debugResources=y&n=1336614540164" type="text/javascript" ></script>
<script src="/myapp/app/controller/ProfileController.js?_debugResources=y&n=1336614540164" type="text/javascript" ></script>
...
Being new to Grails, it is a very welcome fact that one can put executable code within a config file!
Related
I use require.js in a Grails project. There are a couple of single JavaScript files containing the require.js modules defined with define.
There is also a *.gsp file which generates the require.js config and the entry point starting with require() as there is some dynamic configs to be generated. It looks somehow like this:
<%# page contentType="application/javascript;charset=UTF-8" %>
require(['a', 'b'], function(a, b){
...
var a = ${controllerPropertyA};
...
some functions
...
});
In my layout I integrate require.js like this:
<script data-main='http://example.com/exampleController/dynamicGenerateMethod?domain=xyz.com' src='http://example.com/assets/require.js'></script>
All the modules a , b, and so on are asynchronously loaded by require.js. Now I would like to bundle them into a single file - I could use the require.js optimize tool but I prefer to use the assets-pipeline. This works as far as that I get all modules bundled into a single optimized-modules.js which is available on http://example.com/assets/optimized-modules.js.
The question: I would like to have the optimized JavaScript code in the dynamically rendered GSP file. So how can I inject the optimized-modules.js file into the GSP I'm dynamically rendering? I already thought about a tag defined in the tag library so that my *.gsp would look like
<%# page contentType="application/javascript;charset=UTF-8" %>
<g:renderFile file="/assets/optimized-modules.js" />
require(['a', 'b'], function(a, b){
...
var a = ${controllerPropertyA};
...
some functions
...
});
and the tag definition somehow like that:
def g:renderFile = { attrs, body ->
def filePath = attrs.file
if (!filePath) {
throwTagError("'file' attribute must be provided")
}
//out << filePath
out << request.servletContext.getResource(filePath).file
//out << grailsResourceLocator.findResourceForURI(filePath).file.text
//out << grailsApplication.mainContext.getResource(filePath).file.text
//out << Holders.getServletContext().getResource(filePath).getContent()
//IOUtils.copy(request.servletContext.getResourceAsStream(filePath), out);
}
But I can't get the content of the minified optimized-modules.js which was done by the assets-pipeline plugin on startup. Any thoughts on this?
Ok, I finally found it out by myself:
Instead of using the grailsResourceLocator I had to use the assetResourceLocator which is the way to go if you try to access assets resources.
My tag definition now looks like:
def renderFile = { attrs, body ->
def filePath = attrs.file
if (!filePath) {
throwTagError("'file' attribute must be provided")
}
ServletContextResource bar = (ServletContextResource)assetResourceLocator.findAssetForURI(filePath)
String fileAsPlainString = bar.getFile().getText("UTF-8")
out << fileAsPlainString
}
That way I can inject a compile assets javascript file into my GSP - perfect!
What is the best way to use Gulp in MVC template files? The problem is that my layout.cshtml files contain links to JavaScript files, gulp would need to modify these files to point at the optimized minified files. The problem is that I cannot see how I can use gulp to modify these files to point at the optimized files for debug and release because the source file would need to be the destination file and the mechanism would need to work with TFS. The best idea I have had would be to use the bundling mechanism. Before investing time into this I want to see if there are simpler alternatives.
Here is my solution, please let me know if there is a better way of doing this.
I ran:
Install-Package System.Web.Optimization.HashCache
Then I modified the BundleConfig.cs to include
var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/Scripts/CombinedFile.js");
myBundle.Transforms.Add(new HashCacheTransform());
bundles.Add(myBundle);
Then I modified _Layout.cshtml to include
#{
if (HttpContext.Current.IsDebuggingEnabled)
{
<script src = "~/Scripts/App/File1.js" ></script>
<script src = "~/Scripts/App/File2.js" ></script>
}
else
{
#Scripts.Render("~/bundle_virtual_path");
}
}
In this way I use bundles to do cache busting on the output of a gulp script which can be attached to the before build event in the Task Runner Explorer. For completeness here is a extremely simple funtion that generates the CombinedFile.js in this sample
gulp.task('myTest', function () {
return gulp.src(['Scripts/App/File1.js', 'Scripts/App/File2.js'])
.pipe(concat('CombinedFile.js'))
.pipe(gulp.dest('Scripts'));
});
Gulp file listening functions should be attached to the Project Open event
I found a solution for this without bringing .net bundling into the picture.
using the gulp plugin gulp-rev and the nuget package called FileTagger
gulp.js
var gulp = require('gulp'),
rimraf = require("rimraf"),
concat = require("gulp-concat"),
cssmin = require("gulp-cssmin"),
uglify = require("gulp-uglify"),
filter = require("gulp-filter"),
rename = require("gulp-rename"),
rev = require('gulp-rev');
var paths = {};
paths.jsSource = mainBowerFiles();
paths.baseReleaseFolder = "app";
///these names get alter by rev()
paths.baseJSReleaseFile = "site.min.js";
gulp.task("min:js", function () {
var jsFilter = filter('**/*.js', { restore: true });
return gulp
.src(paths.jsSource)
.pipe(jsFilter)
.pipe(uglify())
.pipe(concat(paths.baseReleaseFolder + "/" + paths.baseJSReleaseFile))
.pipe(rev())
.pipe(gulp.dest("."))
.pipe(jsFilter.restore);
});
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>App</title>
#FileTagger.Render.Script("~/app/site-*.min.js")
</head>
<body>#RenderBody()</body>
</html>
I am using Jquery mobile and laravel4.
I have 2 views called "mobilepage1" and "mobilepage2".
On every page there is a "Next"button". A very linear app.
This my controller:
public function login(){
View::share('test','test');
return View::make('mobilepages.mobilepage1');
}
On mobilepage1 i do this:
{{$test}}
{{ Form::open(array('url' => 'mobilepage2', 'method' => 'get')) }}
{{Form:: submit("VERDER")}}
{{ Form::close() }}
That goes to mobile page2
On mobilepage2 i also do:
{{$test}}
This does not seem to work. And gives me the "Error loading page" with the error:
{"error":{"type":"ErrorException","message":"Undefined variable: test (View: C:\\Google Drive\\htdocs\\laravel4_test4\\app\\views\\mobilepages\\mobilepage2.blade.php)","file":"C:\\Google Drive\\htdocs\\laravel4_test4\\app\\storage\\views\\cdf5614a0a7f85ce1182cff09ad77222","line":17}}
If i'm right SHARE is supposed to share in all views like a cookie right? Could it be Jquery mobile is interfering?
UPDATE:
I tried turning off ajax on Omars request:
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
$(document).bind("mobileinit", function () {
$.mobile.ajaxEnabled = false;
});
</script>
<script src="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.js"></script>
The variable still is not shared mobilepage2.
$test variable will be available in all views as long as the View::share('test','test'); command is executed.
In your case, this command is executed only in the login() method.
If you place:
View::share('test','test');
in the top of routes.php file you'll see that it will work because this file is executed on every call to your app.
If you don't want to mess your routes.php file with view shares or composers, you can do the following:
Create a new file (E.g. composers.php) inside the app folder (Where routes.php and filters.php live)
Open app/start/global.php and at the end of the file add
require app_path().'/composers.php';
Now your can place View::share('test','test'); inside this file and have it available in every call.
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.
So I was using trigger.io to create a page where there's a custom menu at the bottom and each button loads an external HTML page into main container. I had to hack around to make this work so I was wondering if there's a better way of doing it.
I started using the $('.main').load('pages/test.html') and it doesn't work. Instead I had to do:
forge.file.getLocal('pages/test.html', function (file) {
forge.file.string(file, function (str) {
$('.main').html(str);
});
});
which is kinda messy.
Also if the str HTML content as a img tag, the img doesn't show since the src attribute gets messed up. So I had to do another hack:
forge.file.getLocal('pages/test.html', function (file) {
forge.file.string(file, function (str) {
var $main = $('.main');
$main.html(str);
//Hack to resolve img src
var imgPath;
$main.find('img').each(function () {
var $this = $(this);
// First 8 chars is "file:///"
imgPath = $this.prop('src').substr(8);
forge.file.getLocal(imgPath, function (file) {
$this.prop('src', file.uri);
});
});
});
});
Any better way of purely loading an external HTML page without all the hassle?
Thanks!
Testing with forge platform v1.4 (at the time of writing, v1.4.18) on Android 4.1 and iOS (both the iPhone simulator and an iPad), I seem to be able to use jQuery's load method without any extra effort. Here's the structure for my testcase:
src/
index.html
face.png
pages/
hello.html
Here's the contents of index.html
<!DOCTYPE html>
<html>
<head>
<script src="js/jquery-1.7.1.min.js"></script>
<script type="text/javascript">
$(function () {
$('.content').load('pages/hello.html');
});
</script>
</head>
<body>
<div class="content">
</div>
</body>
</html>
And pages/hello.html:
<b>hello world</b><img src="face.png">
Which resulted in this just after app launch:
One gotcha I can see with this approach is that the src attribute for the img tag had to be relative to index.html. If you're still having problems then a more specific testcase and/or details of forge platform version used as well as what devices/simulators you tested on might be useful.