renderArgs is null in template in play! framework - groovy

I have a controller function like this
public static void renderCustomersList() {
System.out.println("renderArgs"+renderArgs.toString());
render(renderArgs);
}
In my groovy template
renderArgs ${renderArgs.toString()}
I have renderArgs with value printed in my console but when i pass to the template its printing as null. I wanted those values in my main.html. I printed renderArgs in Both my renderCustomersList.html and main.html the values were null on both templates. What could be the error.

You do not need to pass renderArgs as a parameter to the render function - all the variables in the renderArgs scope will be available in the template anyway. But instead of accessing the renderArgs object itself in the template, go for the independent values instead - see below.
Controller:
public static void renderCustomersList() {
... // some code to initialize yourVariable
renderArgs.put("yourVariable", yourVariable);
render(); // renderArgs not needed as a parameter
}
And in the template:
${yourVariable}

Note also that you don't have to user renderArgs if you don't want to. You can also pass some parameters to render() like this:
public static void renderCustomersList() {
...
render(customers, products, foobar);
}
and then you can get at those parameters in your template with the variable names:
#{list items:customers, as:cust}
...
Foobar: ${foobar}
The only case in which you need to use renderArgs is if you want to use different names for your parameters in the template than in the controller. This is potentially confusing though, so I try not to do it.

Related

PHP __LINE__ equivalent for twig

There is a way to get PHP __LINE__ equivalent for Twig?
It's almost impossible to search __LINE__ on Google as exact word...
The purpose is purely for debugging a long twig file containing nested twig code inside a complex Js code, to console.log it.
The version of twig I'm using is 2.12.5.
Thanks
First things first:
As seen in the PHP documentation the (magic) constant will return the current line number of the file.The "problem" here is that twig converts templates to PHP in order to render them.
This means if you were actually were to be able to use __LINE__ inside a template, it would report back the line number from either a temporary PHP file or a cached PHP file, wether you have caching enabled or not.
TLDR: Using __LINE__ inside a template is going to report back "false"/useless information.
However, you can easily extend twig and even introduce new tags which you then can use inside templates. These customs tags allow you to modify/alter the compilation of the template's PHP file.
The interesting part here is that the "compiler" is able to provide your custom tag on which specific line the tag was called.
We can even create a custom tag, register it with twig and let the tag report back the line number in the parsed template.
Step 1 - Create a TokenParser
The TokenParser is responsible for parsing the template and allows you to choose a name for your tag. The code below will be responsible to create a simple, empty tag named line
<?php
namespace MyProject\Base\Twig\TokenParser;
use \MyProject\Base\Twig\Node\Line as LineNode;
class Line extends \Twig_TokenParser {
public function parse(\Twig_Token $token)
{
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new LineNode(new \Twig_Node(), $token->getLine(), $this->getTag());
}
public function getTag()
{
return 'line';
}
}
Step 2 - Create a Node
The Node is responsible for converting the template code to actual PHP code
<?php
namespace MyProject\Base\Twig\Node;
class Line extends \Twig_Node {
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = null) {
parent::__construct(['body' => $body,], array(), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler) {
$compiler->write('echo '.$this->getLine().';')
->write(PHP_EOL);
}
}
Step 3 - Register the tag with twig
<?php
namespace MyProject\Base\Twig;
class MyProjectTwigExtension extends Twig_Extension {
public function getTokenParsers() {
return [
new \CMS4U\Base\Twig\TokenParser\Line(),
];
}
public function getName() {
return 'MyProjectTwigExtension';
}
}
<?php
$twig->addExtension(new \MyProject\Base\Twig\MyProjectTwigExtension());
If everything is good, you can now use the custom tag {% line %} wherever you like in any template
Foo
Bar
FooBar
Current line number is {% line %} {# 4 #}

.net core 3.1 - View Components - Not finding my Default.cshtml

I'm just getting to grips with ViewComponents in my Razor pages application.
I have a ViewComponents folder within my project that contains my ViewComponent .cs code:
public class RemoveFromCartViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var result = "123";
return View(result);
}
}
I then have another folder within Pages/Shared/Components called RemoveFromCart. Within this folder I have my default.cshtml
#model string
<h2>
#Model
</h2>
Simply putting the string within a h2 tag.
In my projects Layout.cshtml file I am invoking this ViewComponent:
<div>
#await Component.InvokeAsync("RemoveFromCart")
</div>
When I start my project, the error I get is:
*InvalidOperationException: The view 'Components/RemoveFromCart/123' was not found. The following locations were searched:
/Pages/Components/RemoveFromCart/123.cshtml
/Pages/Shared/Components/RemoveFromCart/123.cshtml
/Views/Shared/Components/RemoveFromCart/123.cshtml*
This is indication my view should be called 123.cshtml which doesnt seem right. What am I doing wrong here? I should simply expect to see the text 123 appear
Thanks
By returning View("123"), you are using this overload:
public ViewViewComponentResult View (string viewName)
Returns a result which will render the partial view with name viewName.
So you are passing the view name, instead of a string value as the view’s model.
You can change that by explicitly calling the View<TModel>(TModel) overload instead:
public IViewComponentResult Invoke()
{
var result = "123";
return View<string>(result);
}
In the long run, I would suggest you to create a model class instead so that you can pass an object instead of just a string. This will avoid having this particular problem and you are also able to easily expand the model contents later on.

Can an object tell Twig not to escape output when it is {{ printed }}

I've got a situation where some variables sent to my Twig templates are plain old variables, so I want them to be html-escaped (as is the default behaviour). But other variables sent to my templates are really objects with __toString() renderers... and some of these objects send out raw HTML (e.g. from a WYSIWYG editor like TinyMCE or CKEditor).
Ideally I'd like for my template designers to not have to use the |raw filter on the objects, but instead somehow have the objects tell Twig that they're already escaped.
In other words, I'm trying to mimc the behavior of a Twig function that sets is_safe, but without requiring template designers to use a function.
E.g. I could write a Twig function using the is_safe parameter in its definition and be able to have this in my templates:
{{ figure_out_what_to_do(something) }}
(where the figure_out_what_to_do knows to inspect the "something" object to ascertain whether or not it needs to be escaped). BUT to me this is no better than having to remember to put |raw after every output of "something". So instead I'd like to be able to do this:
{{ something }}
...and have Twig recognize that something is an object and hence ask it whether or not it needs to be escaped.
I'm guessing the answer is "no", but figured I'd ask in case someone who knows more about Twig internals has any pointers for me.
Thanks.
In the __toString() method you could instead of return the html output, do this : return new Twig_Markup($html, 'UTF-8'); thus marking it as a safe and not to be escaped
Instead of returning a new \Twig_Markup object in __toString() (which causes a fatal error since it must return a string) you could extend \Twig_Markup:
class Something extends \Twig_Markup {
public function __toString() {
return $this->safeValue;
}
public function count() {
return mb_strlen($this->safeValue);
}
}
Twig looks at the object to see if it is an instance of \Twig_Markup when deciding to escape the string or not. Here’s the source for \Twig_Markup:
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* Marks a content as safe.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
class Markup implements \Countable
{
protected $content;
protected $charset;
public function __construct($content, $charset)
{
$this->content = (string) $content;
$this->charset = $charset;
}
public function __toString()
{
return $this->content;
}
public function count()
{
return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content);
}
}
class_alias('Twig\Markup', 'Twig_Markup');
As you can see in the source code, \Twig_Markup implements \Countable. That’s why I’ve overridden the implementation of public function count() in my example.

How to use the same route with different parameter types?

I have an Api controller with two different actions that take different parameter types.
// GET: users/sample%40email.com
[Route("users/{emailAddress}")]
public IHttpActionResult GetUser(string emailAddress)
// GET: users/1D8F6B90-9BD9-4CDD-BABB-372242AD9960
[Route("users/{reference}")]
public IHttpActionResult GetUserByReference(Guid reference)
Problem is multiple actions are found matching when I make a request to either. Looking at other answers I thought I needed to setup routes in the WebApiConfig like so...
config.Routes.MapHttpRoute(
name: "apiEmail",
routeTemplate: "api/{controller}/{action}/{email}"
);
config.Routes.MapHttpRoute(
name: "apiReference",
routeTemplate: "api/{controller}/{action}/{reference}"
);
What do I need to do so that each action is called based on the parameter type I pass in?
I'm very new to Web.Api any additional explanation text would be appreciated.
You do like below method declaration with attribute routing enabled:
//declare method with guid 1st
// GET: users/1D8F6B90-9BD9-4CDD-BABB-372242AD9960
[Route("users/{reference:guid}")]
public IHttpActionResult GetUserByReference(Guid reference)
and declare other method like below
// GET: users/sample%40email.com
[Route("users/{emailAddress}")]
public IHttpActionResult GetUser(string emailAddress)
Please let me know, is this work for you ?

Geb / Groovy - Page content not recognized when used within other Page Object method

When I click a button I have to wait for some dynamic content to be rendered. When I put the waitFor closure in the test it works correctly. However, I wanted to put the waitFor in a method inside the Page object so I do not have to always call the waitFor after every click, but when I do that it fails stating it cannot find the property.
This does not work:
class LandingPage extends Page {
static content = {
resultsBtn(to: ResultsPage) { $("button", id: "showresults") }
}
void getResults() {
resultsBtn.click()
waitFor { ResultsPage.results.displayed }
}
}
class ResultsPage extends Page {
static content = {
results { $("div", id: "listresults") }
}
}
class ShowResults extends GebReportingTest {
#Test
public void displayResults() {
to LandingPage
getResults()
}
}
The error states something like "No such property: results for class ResultsPage".
Is it possible to put references to content from other Page Objects inside other Page Object methods?
EDIT: I feel like this is more of a Groovy specific thing rather than Geb. I'm not sure if it's even possible to access bindings within the content closure. But it also seems like creating a getVariable() function inside the Page Object doesn't help much either.
First you shouldn't assign closures in content blocks (there's unnecessary = in ResultPage) but pass them to an implicit method, you should have:
static content = {
results { $("div", id: "listresults") }
}
The other question is why do you want to model this as two pages? As far as I understand clicking the button doesn't cause a page reload but there's an ajax call to retrieve the results. I would simply put both results and resultsBtn as contents of one page and your problem would be gone.
EDIT:
It turns out that a page change is involved in your case. Assuming that you always want to wait for these results to appear you can either:
put your waitFor condition inside of static at = {} block for ResultsPage - at checks are executed implicitly whenever you use to() which means that it will wait wherever you go to that page
put a waitFor in a page change listener
access current page via the browser property on a page, in LandingPage: waitFor { browser.page.results.displayed } but this seems like a dirty solution to me - reaching from one page to another...

Resources