Grails 2.4.x comes with support for HAL.
Despite some problems with embedded resources (https://jira.grails.org/browse/GRAILS-10954) i'm starting to make it works. However still i am not sure how to deal with pagination links ("prev", "next") as they are shown in the documentation.
Is there any way HalJsonRenderer can help with this point?
What i've done is extend HalJsonCollectionRenderer and overwrite this method:
protected void writeLinkForCurrentPath(RenderContext context, MimeType mimeType, JsonWriter writer) {
final href = linkGenerator.link(uri: context.resourcePath, method: HttpMethod.GET.toString(), absolute: absoluteLinks)
final resourceRef = href
final locale = context.locale
def link = new Link(RELATIONSHIP_SELF, href)
link.title = getResourceTitle(resourceRef, locale)
link.contentType = mimeType ? mimeType.name : null
writeLink(link, locale, writer)
}
Adding links for PREV and NEXT to get rendered when required.
In order to do this we need access querystring params that can be accessed as arguments in the RenderContext object (context.getArguments())
It works quite well and is not too complex.
However if ther is another way i will be happy to know.
Related
I have following problem:
We are overriding the tt_content TCA with a custom column which has an itemsProcFunc in it's config. In the function we try to retrieve the TypoScript-Settings, so we can display the items dynamically. The problem is: In the function we don't receive all the TypoScript-Settings, which are included but only some.
'itemsProcFunc' => 'Vendor\Ext\Backend\Hooks\TcaHook->addFields',
class TcaHook
{
public function addFields($config){
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
$configurationManager = $objectManager->get('TYPO3\\CMS\\Extbase\\Configuration\\ConfigurationManagerInterface');
$setup = $configurationManager->getConfiguration(
\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT
);
}
$setup is now incomplete and doesn't contain the full TypoScript, for example some of the static-included TypoScript is missing.
Used TYPO3 7 LTS (7.6.18), PHP 7.0.* in composer-mode.
Does anybody know where the problem is? Is there some alternative?
You maybe misunderstood the purpose of TypoScipt. It is a way of configuration for the Frontend. The Hook you mentioned is used in the TCA, whÃch is a Backend part of TYPO3. TypoScript usually isn't used for backend related stuff at all, because it is bound to a specific page template record. Instead in the backend, there is the TSConfig, that can be bound to a page, but also can be added globally. Another thing you are doing wrong is the use of the ObjectManager and the ConfigurationManager, which are classes of extbase, which isn't initialized in the backend. I would recommend to not use extbase in TCA, because the TCA is cached and loaded for every page request. Instead use TSConfig or give your configuration settings directly to the TCA. Do not initialize extbase and do not use extbase classes in these hooks.
Depending on what you want to configure via TypoScript, you may want to do something like this:
'config' => [
'type' => 'select',
'renderType' => 'singleSelect',
'items' => [
['EXT:my_ext/Resources/Private/Language/locallang_db.xlf:myfield.I.0', '']
],
'itemsProcFunc' => \VENDOR\MyExt\UserFunctions\FormEngine\TypeSelectProcFunc::class . '->fillSelect',
'customSetting' => 'somesetting'
]
and then access it in your class:
class TypeSelectProcFunc{
public function fillSelect(&$params){
if( $params['customSetting'] === 'somesetting' ){
$params['items'][] = ['New item',1];
}
}
}
I had a similar problem (also with itemsProcFunc and retrieving TypoScript). In my case, the current page ID of the selected backend page was not known to the ConfigurationManager. Because of this it used the page id of the root page (e.g. 1) and some TypoScript templates were not loaded.
However, before we look at the solution, Euli made some good points in his answer:
Do not use extbase configuration manager in TCA functions
Use TSconfig instead of TypoScript for backend configuration
You may like to ask another question what you are trying to do specifically and why you need TypoScript in BE context.
For completeness sake, I tested this workaround, but I wouldn't recommend it because of the mentioned reasons and because I am not sure if this is best practice. (I only used it because I was patching an extension which was already using TypoScript in the TCA and I wanted to find out why it wasn't working. I will probably rework this part entirely.)
I am posting this in the hope that it may be helpful for similar problems.
public function populateItemsProcFunc(array &$config): array
{
// workaround to set current page id for BackendConfigurationManager
$_GET['id'] = $this->getPageId((int)($config['flexParentDatabaseRow']['pid'] ?? 0));
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$configurationManager = $objectManager->get(BackendConfigurationManager::class);
$setting = $configurationManager->getTypoScriptSetup();
$templates = $setting['plugin.']['tx_rssdisplay.']['settings.']['templates.'] ?? [];
// ... some code removed
}
protected function getPageId(int $pid): int
{
if ($pid > 0) {
return $pid;
}
$row = BackendUtility::getRecord('tt_content', abs($pid), 'uid,pid');
return $row['pid'];
}
The function getPageId() was derived from ext:news which also uses this in an itemsProcFunc but it then retrieves configuration from TSconfig. You may want to also look at that for an example: ext:news GeorgRinger\News\Hooks\ItemsProcFunc::user_templateLayout
If you look at the code in the TYPO3 core, it will try to get the current page id from
(int)GeneralUtility::_GP('id');
https://github.com/TYPO3/TYPO3.CMS/blob/90fa470e37d013769648a17a266eb3072dea4f56/typo3/sysext/extbase/Classes/Configuration/BackendConfigurationManager.php#L132
This will usually be set, but in an itemsProcFunc it may not (which was the case for me in TYPO3 10.4.14).
I'm doing some custom infrastructure for auto-generating specific bundles for individual views, and have a case where I need to get the Layout value for each view while iterating them as files.
I've tried var view = new RazorView(new ControllerContext(), actionView.FullName, null, true, null); but this is taking the LayoutPath as an input, and it is indeed resulting in an empty string on the LayoutPath property of the RazorView if I give null for that parameter, so it's not parsing the file for the value.
Could there be any other way to solve this in a similar manner, or would my best/only option be to just parse the text of the raw file (and _ViewStart)?
This is only done once at application start, so the performance is currently not an issue.
Alright, after a lot of source debugging and an epic battle with the internal access modifier, I have a working solution without having to render the whole page. I don't expect anyone else ever having the need for this, but anyway:
var httpContext = new HttpContextWrapper(new HttpContext(new HttpRequest("", "http://dummyurl", ""), new HttpResponse(new StreamWriter(new MemoryStream()))));
var page = Activator.CreateInstance(BuildManager.GetCompiledType(ReverseMapPath(actionView.FullName))) as WebViewPage;
page.Context = httpContext;
page.PushContext(new WebPageContext(), new StreamWriter(new MemoryStream()));
page.Execute();
var layoutFileFullName = page.Layout;
// If page does not have a Layout defined, search for _ViewStart
if (string.IsNullOrEmpty(layoutFileFullName))
{
page.VirtualPath = ReverseMapPath(actionView.FullName);
var startpage = StartPage.GetStartPage(page, "_ViewStart", new string[] {"cshtml"});
startpage.Execute();
layoutFileFullName = startpage.Layout;
}
Tada!
Ps. ReverseMapPath is a any arbitrary function to resolve the relative path of a full file name, see for example Getting relative virtual path from physical path
I need to show four charts on a grails page in a grid layout with positions 11, 12, 21 and 22. Each chart is build with a code similar to:
<img src="${createLink(controller:'paretoChart', action:'buildParetoChart11')}"/>
The code for the chart building action is:
def buildParetoChart11 = {
def PlotService p11 = PlotService.getInstance()
def poList = paretoChartService.getParetoidPO()
def listCounter = 0
def idPO = poList[listCounter]
idPO.toString()
def String idPOvalue = idPO
def out = response.outputStream
out = p11.paretoPlot(out, idPOvalue)
response.setContentType("image/jpg")
session["idPOList11"] = poList
}
The Java p11.paretoPlot(out, idPOvalue) returns a BufferedImage of the chart inside the OutputStream, but it only works for one chart. The other three charts vary on the the order on each all pour actions are called.
PlotService was written by me, yes. In this implementation, I'm passing the OutputStream out I got from response.outputStream and the String idPOvalue to the Java method. plotPareto's implementation is as follows:
public OutputStream paretoPlot(OutputStream out, String po) throws IOException {
chart = buildParetoChart(po);// here the chart is actually built
bufferedImage = chart.createBufferedImage(350, 275);
ChartUtilities.writeBufferedImageAsJPEG(out, bufferedImage);
}
So, is there a way to make sure one action is completed before firing up the next one?
Thanks in advance!
each request to get an image is handled asynchronously by the browser. Each request runs in its own thread on the server. With img tags, the browser controls the GET requests to get the images, so I don't think you can easily guarantee the order, and nor should you have to.
Are you seeing any errors?
I would look at the firebug or equivalent output to see if the browser is getting an error. for any of the image requests.
I would also try attaching a debugger to your server.
Did you write the PlotService? You need to make sure it is thread safe.
Also, I dont see you reading any params, is there a separate action for each image?
I'm localising a site via a Change Language control in the master page. I need to render the control with the current url you're on in each of the different languages.
So if you're on http://site.com/en/Home/About and you change the language to french, I need to direct you to http://site.com/fr/Home/About.
The localisation code works on the route data language property, so I've been trying to figure out how I can:
Get access to the current action (with all original parameters)
Get the url to the current action (with all original parameters) with the route data changed.
Can anyone point me in the right direction?
I've tried passing the ViewContext from the parent into the UserControl, which gives me access to the route data but I can't figure out how to get the language routed url from that.
I ran this on the site I'm working on locally and it seemed to work. There's probably a cleaner way.
HttpRequestBase hrb = HttpContext.Request;
System.Uri url = hrb.Url;
string[] test = url.AbsoluteUri.Split('/');
int nIndex = 0, nCounter = 0;
foreach(string str in test)
{
if (str.Contains("site.com"))
{
nIndex = nCounter;
break;
}
nCounter++;
}
string strLanguage = test[nIndex + 1];
Obviously the +1 can even go in the IF statement, but I didn't think it looked good there. Hope this helps some.
I'm not 100% happy with this, I haven't got to a stage where I can fully test the impact of this but this is what I'm going for so far. Please do answer if you have a better solution.
I pass the ViewContext from the masterpage so I get the ViewContext with route data from whatever url you're currently on.
private string GetLocalisedUrl(ViewContext viewContext, string language)
{
viewContext.RouteData.DataTokens[LANGUAGE_ROUTEDATA_KEY] = language;
UrlHelper helper = new UrlHelper(viewContext.RequestContext);
return helper.Action(viewContext.RouteData.Values["action"].ToString(), viewContext.RouteData.Values["controller"].ToString(), viewContext.RouteData.DataTokens);
}
I've looked around but could not find a way of simply including or rendering *.html files in Grails. My application needs to g.render or <g:render> templates which are delivered as html files. For this, as we know, html files have to be converted to _foo.gsp files in order to get rendered. I am totally surprised as to why isn't there a direct support for html or is there one??
Thanks!
One obvious option is to simply rename your HTML files from foo.html to _foo.gsp and then use <render template="foo">. However this is so obvious that I'm sure you've already thought of it.
If you simply want to render a HTML file from within a controller you can use the text parameter of the render controller method
def htmlContent = new File('/bar/foo.html').text
render text: htmlContent, contentType:"text/html", encoding:"UTF-8"
If you want to do the same thing from within a .gsp, you could write a tag. Something like the following (untested) should work:
import org.springframework.web.context.request.RequestContextHolder
class HtmlTagLib {
static namespace = 'html'
def render = {attrs ->
def filePath = attrs.file
if (!file) {
throwTagError("'file' attribute must be provided")
}
def htmlContent = new File(filePath).text
out << htmlContent
}
}
You can call this tag from a GSP using
<html:render file="/bar/foo.html"/>
What is it you are trying to accomplish?
Render html from a controller?
In that case, all you should have to do is redirect the user to file from your control.
redirect(uri:"/html/my.html")
Use html-files instead of gsp template-files?
Thing is, Grails is a "Convention over Configuration"-platform and that means you will have to do some things "the Grails way". The files needs the _ and the .gsp but the name can be whatever you like even if it's easier when you use the same name as the controller. What you gain from doing that is the knowledge that every developer that knows grails and comes into your project will understand how things are tied together and that will help them get started quickly.
Little bit fixed the Don's example, works fine for me
import org.apache.commons.io.IOUtils
class HtmlTagLib {
static namespace = 'html'
def render = {attrs ->
def filePath = attrs.file
if (!filePath) {
throwTagError("'file' attribute must be provided")
}
IOUtils.copy(request.servletContext.getResourceAsStream(filePath), out);
}
}
I wanted to write static html/ajax pages hosted in grails app (v2.4.4), but use the controller for the url rewrite. I was able to accomplish this by moving the file to web-app/ (for ease of reference), and simply use the render() method with 'file' and 'contentType' params, such as:
// My controller action
def tmp() {
render file: 'web-app/tmp.html', contentType: 'text/html'
}
Note: I only tried this using run-app, and haven't packaged a war and deployed to tomcat, yet.
Doc: http://grails.github.io/grails-doc/2.4.4/ref/Controllers/render.html
Closure include = { attr ->
out << Holders.getServletContext().getResource(attr.file).getContent();
}
It is relative to the web-app folder of your grails main application
I was able to use #momo's approach to allow inclusion of external files for grails rendering plugin, where network paths would get botched in higher environments - mine ended up like:
def includeFile = { attr ->
URL resource = Holders.getServletContext().getResource(attr.file)
InputStream content = resource.getContent()
String text = content?.text
log.info "includeFile($attr) - resource: $resource, content.text.size(): ${text?.size()}"
out << text
}