How do I include external GSPs or template in the GSP file when the template to be included is not under views folder?
Yes, you can easily do that. Here you go:
import grails.gsp.PageRenderer
class MyLib {
static namespace = "foo"
static defaultEncodeAs = "raw"
PageRenderer groovyPageRenderer
def externalTemplate = { attrs, body ->
String externalFilePath = attrs.externalPath
/*
* Put content of that external template to a file inside grails-app/views folder
* with a temporary unique name appended by current timestamp
*/
String temporaryFileName = "_external-" + System.currentTimeMillis() + ".gsp"
File temporaryFile = new File("./grails-app/views/temp/$temporaryFileName")
/*
* Copy content of external file path to the temporary file in views folder.
* This is required since the groovyPageRenderer can compile any GSP located inside
* the views folder.
*/
temporaryFile.text << new File(externalFilePath).text
/*
* Now compile the content of the external GSP code and render it
*/
out << groovyPageRenderer.render([template: "/temp/$temporaryFileName", model: attrs.model])
// Delete the file finally
temporaryFile.delete()
}
}
Now in your actual GSP where you want to include the external GSP, you can write so:
<body>
<foo:externalTemplate externalPath="/home/user/anyExternalFile.gsp" model="${[status: 1}" />
</body>
I know I am late for this reply but I encountered this problem when we tried to put report views outside of the views folder.
We couldn't use the above method because we are running a jar package and we couldn't create files inside views folder.
Here is the solution on Grails 4
first inject
def groovyPagesTemplateEngine
def groovyPageLayoutFinder
then in your controller
File externalFile = new File("/path/to/file.gsp")
if(externalFile && externalFile.exists()){
GroovyPageView groovyPageView = new GroovyPageView()
LinkedHashMap model = [:]
Template template = groovyPagesTemplateEngine.createTemplate(externalFile.text, externalFileName)
groovyPageView.setServletContext(getServletContext())
groovyPageView.setTemplate(template)
groovyPageView.setApplicationContext(getApplicationContext())
groovyPageView.setTemplateEngine(groovyPagesTemplateEngine)
groovyPageView.afterPropertiesSet()
request.setAttribute GrailsLayoutDecoratorMapper.LAYOUT_ATTRIBUTE, null
GrailsLayoutView grailsLayoutView = new GrailsLayoutView(groovyPageLayoutFinder, groovyPageView)
grailsLayoutView.render model, webRequest.getCurrentRequest(), webRequest.getResponse()
webRequest.renderView = false
return
}
else {
// something that shows error
render "not found"
}
Related
I want to write a groovy script that will copy all projects from a given view to new view with new name (see code below) and also will change config - replace strings from old value to new value.
I can copy jobs to new view, but how to modify them?
Lets say I need to change every occurrence of 'MR222' to 'MR999' in job configuration.
/* Variables */
def oldViewName = 'orig Name'
def newViewName = 'new Name'
def search = 'MR222'
def replace = 'MR999'
/* If new view doesnt exist create new view */
if ( ! Hudson.instance.getView(newViewName) ) {
def newView = new ListView(newViewName)
Hudson.instance.addView(newView)
/* For each job in old view, copy it, replace workspace and add to new view */
Hudson.instance.getView(oldViewName).getItems().each { item ->
AbstractProject project = Hudson.instance.copy(item, item.getName().replace(search, replace))
if (project.getCustomWorkspace()) {
project.setCustomWorkspace(project.getCustomWorkspace().replace(search, replace))
}
newView.add(project)
}
}
else {
println "View \"${newViewName}\" already exist"
}
I want to render or download a URL that links to a PDF in a Grails controller method. I'm okay with either opening this is in a new or the same tab, or just downloading it. How is this done in grails?
So far I have:
render(url: "http://test.com/my.pdf")
However, I get errors with this and other ways I've tried, such as rendering a response with content. Any clues?
Yes you can absolutely do it easily:
First get the file from the URL (if you don't have a local file) for example:
class FooService {
File getFileFromURL(String url, String filename) {
String tempPath = "./temp" // make sure this directory exists
File file = new File(tempPath + "/" + filename)
FileOutputStream fos = new FileOutputStream(file)
fos.write(new URL(url).getBytes())
fos.close()
file.deleteOnExit()
return file
}
}
Now in your controller, do this to allow user to automatically download your PDF file:
class FooController {
def fooService
def download() {
String filename = "my.pdf"
// You can skip this if you already have that file in the same server
File file = fooService.getFileFromURL("http://test.com/my.pdf", filename)
response.setContentType("application/octet-stream")
response.setHeader("Content-disposition", "${params.contentDisposition}; filename=${filename}")
response.outputStream << file.readBytes()
return
}
}
Now as the user will hit /foo/download the file will be dowloaded automatically.
One option is
class ExampleController {
def download() {
redirect(url: "http://www.pdf995.com/samples/pdf.pdf")
}
}
Going to localhost:8080/appName/example/download will, depending on the users browser preferences, either download the file or open the file in the same tab for reading.
I works with grails 2.5.0
I would like to modify one of the plugins in my Grails project to add something in a TagLib class.
The plugin is pdf version 0.6 and I would like to add a variable that stores a path.
The TagLib is originally as follows:
def pdfLink = { attrs, body ->
//String template = attrs['template']
String pdfController = attrs['pdfController']
String pdfAction = attrs['pdfAction']
String pdfId = attrs['pdfId']
//String pdfParams = attrs['pdfParams']
String url = attrs['url']
String filename = attrs['filename']
....
}
I've added the following:
String pdfPathToSave = attrs['pdfPathToSave']
Where pdfPathToSave is defined in gsp as follows:
<g:pdfLink class="pdf" .... pdfPathToSave="${path}"...></g:pdfLink>
I've put a breakpoint into the taglib and I see that, in attrs['pdfPathToSave'] there is correctly stored the path, but the variable pdfPathToSave is not correctly created.
How can I create a new local variable into TagLib class?
In our project we're using the razorgenerator of David Ebbo. This allowed us to move some of our cshtml files to a class library.
What we would like to achieve now is the following:
MyCommonViews has a "MyView.cshtml" in its Views folder.
MyWebProject ALSO has a "MyView.cshtml" in its Views folder.
MyOtherWebProject DOES NOT have a "MyView.cshtml" in its Views folder.
When MyOtherWebProject needs to load MyView.cshtml, it will pick the one which is in the compiled MyCommonViews project. That is what we want.
BUT when MyWebProject needs to load MyView.cshtml, we would like it to pick up the "overridden" MyView.cshtml file which is in the MyWebProject itself.
Is what we want possible and how?
Manu.
I wrote up a hacky solution for our problem. It hacks into the razorgenerators viewengine and removes all appropriate entries from the (private readonly) Dictionary it has.
The code is ran on application start.
Talk is cheap, show me the code:
private static void HackRazorGeneratorToAllowViewOverriding()
{
// first we search for the PrecompiledMvcEngine
var razorGeneratorViewEngine = ViewEngines.Engines.ToList().FirstOrDefault(ve => ve.GetType().Name.Contains("PrecompiledMvcEngine"));
if (razorGeneratorViewEngine == null)
return;
// retrieve the dictionary where it keeps the mapping between a view path and the (view object) type to instantiate
var razorMappings = (IDictionary<string, Type>)ReflectionUtils.GetPrivateReadonly("_mappings", razorGeneratorViewEngine);
// retrieve a list of all our cshtml files in our 'concrete' web project
var files = Directory.GetFiles(Path.Combine(WebConfigSettings.RootPath, "Views"), "*.cshtml", SearchOption.AllDirectories);
// do some kungfu on those file paths so that they are in the same format as in the razor mapping dictionary
var concreteViewPaths = files.Select(fp => string.Format("~{0}", fp.Replace(WebConfigSettings.RootPath, "").Replace(#"\", "/"))).ToList();
// loop through each of the cshtml paths (of our 'concrete' project) and remove it from the razor mappings if it's there
concreteViewPaths.ForEach(vp =>
{
if (razorMappings.ContainsKey(vp))
razorMappings.Remove(vp);
});
}
WebConfigSettings.RootPath contains the path on HD to the root of our web application.
This is a part of our static ReflectionUtils class:
/// <summary>
/// Get a field that is 'private readonly'.
/// </summary>
public static object GetPrivateReadonly(string readonlyPropName, object instance)
{
var field = instance.GetType().GetField(readonlyPropName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
throw new NullReferenceException(string.Format("private readonly field '{0}' not found in '{1}'", readonlyPropName, instance));
return field.GetValue(instance);
}
This did the trick. We basically force the PrecompiledMvcEngine to "forget" any view which we have in our concrete project.
You can also try CompositePrecompiledMvcEngine from RazorGenerator.Mvc 2.1.0. It was designed for correct support of view overriding within multiple assemblies. Piece of code:
var engine = new CompositePrecompiledMvcEngine(
/*1*/ PrecompiledViewAssembly.OfType<MyCommonViewsSomeClass>(),
/*2*/ PrecompiledViewAssembly.OfType<MyWebProjectSomeClass>(
usePhysicalViewsIfNewer: HttpContext.Current.IsDebuggingEnabled));
ViewEngines.Engines.Insert(0, engine);
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
The first line will register all views from the MyCommonViews assembly (~/Views/MyView.cshtml), the second line will register all views from the MyWebProject or MyOtherWebProject assembly.
When it encounters the virtual path, that already has been registered (~/Views/MyView.cshtml from the MyWebProject assembly), it overrides an old mapping with a new view type mapping.
If another project doesn't has view with the same virtual path (MyOtherWebProject) it leaves source mapping unchanged.
There is flag PreemptPhysicalFiles = false which does the magic.
Full sample:
[assembly: WebActivator.PostApplicationStartMethod(typeof(Application.Web.Common.App_Start.RazorGeneratorMvcStart), "Start")]
namespace Application.Web.Common.App_Start
{
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine2(typeof (RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = true, //compile if file changed
PreemptPhysicalFiles = false //use local file if exist
};
ViewEngines.Engines.Add(engine);//Insert(0,engine) ignores local partial views
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
}
However there is maybe a small bug:
http://razorgenerator.codeplex.com/workitem/100
In a custom tag, I am receiving as a parameter the url of a file, which I need to open.
I have this
/content/data.html
which is the output from
${createLinkTo(dir:'content',file:'data.html')}:
and I need the 'server path':
C:\mygrailsapp\web-app\content\data.html
You can use the Spring application context to find resources. This works if it's under web-app folder:
class FooController {
def grailsApplication
def myAction = {
String path = params.path // '/content/data.html'
def resource = grailsApplication.mainContext.getResource(path)
String text = resource.inputStream.text
...
}
}