How to access a string array using Sightly(HTL) - aem

How can i access a string array coming from a model class using sightly(HTL)
The TestModel is a model class that returns a string array , getResult() is the getter used to return the string array
how can I use sightly to get it??
<p>display output :</p>
<sly data-sly-use.object = "com.silversea.core.models.TestModel">
<sly data-sly-list.mylist = "${object.Result}"> //what command show we use instead of data-sly-list
<p>1st text: ${item} </p>
</sly>
</sly>

The problem you are facing here is caused by two things:
Defining an identifier on the data-sly-list statement allows you to rename the itemList and item variables. item will become variable and itemList will become variableList
More details in https://docs.adobe.com/content/help/en/experience-manager-htl/using/htl/block-statements.html
So in your example you must change ${item} into ${mylist}
<p>display output :</p>
<sly data-sly-use.object = "com.silversea.core.models.TestModel">
<sly data-sly-list.mylist = "${object.result}"> //what command show we use instead of data-sly-list
<p>1st text: ${mylist} </p>
</sly>
</sly>
The second thing is that you should also follow the java bean naming convention: So if you have a getter getResult() then in HTL you should use ${object.result} (starting from lowercase)

Related

How to get reactive validation with array group?

I have set code this way
errorGroup: any = FormGroup;
this.errorGroup = this.formBuilder.group({
errors: this.formBuilder.array([])
});
For repeat/add new data in group I have add this function which works fine.
addErrorGroup() {
return this.formBuilder.group({
error_code: ['',[Validators.required ]]
})
}
Get controls by this way. I think hear I'm missing something.
get f() { return this.errorGroup.controls.errors; }
In HTML
<select formControlName="error_code" name="error_code" (change)="errorCodeChange($event.target.value , i)">
<option *ngFor="..." value={{...}}>{{...}}</option>
</select>
<span *ngIf="f.error_code.errors.required" class="error-msg">This is required field.</span>
I got this error.
ERROR TypeError: Cannot read property 'errors' of undefined
If that error is coming from HTML, it's because your *ngIf condition is trying to read a value from an undefined object.
At the point where the view is rendered, and checked, it's entirely possible that f (incidentally, you should change that variable name to something more descriptive, but 🤷🏻‍♂️) doesn't have any errors populated yet, so will be undefined.
You can do one of two things here, either, you can wrap the whole thing in another *ngIf to ensure the error_code part of f is populate before accessing it:
<span *ngIf="f && f.error_code">
<span *ngIf="f.error_code.errors.required" class="error-msg">This is required field.</span>
</span>
Or, you can use the safe navigation operator:
<span *ngIf="f?.error_code?.errors?.required" class="error-msg">This is required field.</span>
Note the ? after each object key. This bails out when it hits the first null value, but, the app continues to work as it fails gracefully.
You can read more about it here: https://angular.io/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths
How about if you just do below?
<span *ngIf="errorGroup.get('error_code').errors.required" class="error-msg">
This is required field.
</span>
so by doing this way, you don't need the f() getter in your component file.

Binding an html form action to a controller method that takes some parameters

In my Find controller I have a method like:
public Result findLatest(String repoStr) {
............
}
Which is linked through a route:
GET /latest controllers.Find.findLatest(repo: String)
Then, I have a form in a view like:
<form action="#routes.Find.findLatest()" method="get">
....
<select name="repo">....</select>
</form>
But obviously that is failing, because it is expecting some parameters that I do not fulfill in the action. What is the correct way to do this without having to end up leaving the findLatest method taking no parameters in my controller?
You could change the routes to accept an empty string:
GET /latest/:repo controllers.Find.findLatest(repo: String = "")
Then configure your controller function to handle empty string.
That way,
<form action="#routes.Find.findLatest()" method="get">
....
<select name="repo">....</select>
will evaluate repo as an empty string at the controller level.
Edit: Support for this implementation was dropped in Play v 2.1
You may be interested in Play's Optional parameters e.g. play.libs.F.Option[String]
Example: How to handle optional query parameters in Play framework
GET /latest/:repo/:artifact controllers.Find.findLatestArtifact(repo: play.libs.F.Option[String], artifact: play.libs.F.Option[String])
This will allow you flexibility in which arguments need to be provided.
Not sure which language you're using but the link above contains an example for scala and the method declaration in java would look something like:
import play.libs.F.Option;
public static Result findLatestArtifact(Option<String> repo, Option<String> artifact){ ... }
and updated implementation 2.1
Routes with optional parameter - Play 2.1 Scala
EDIT: play 2.1+ Support : Props to #RobertUdah below
Initializing to null:
GET /latest/ controllers.Find.findLatest(repo: String = null)
GET /latest/:repo controllers.Find.findLatest(repo: String)
<form action="#routes.Find.findLatest()" method="get">
Normally all form data go in the body and you can retrieve them in your action method with bindFromRequest() (see docs).
If you really want to pass one form element as a part of the URL then you have to dynamically compose your URL in JavaScript and change your route.
Your route could look like:
GET /latest/:repo controllers.Find.findLatest(repo: String)
And the JavaScript part like (I didn't actually test the code):
<form name="myform" action="javascript:composeUrl();" method="get">
....
<select name="repo">....</select>
</form>
<script>
function submitform() {
var formElement = document.getElementsByName("myform");
var repo = formElement.options[e.selectedIndex].text;
formElement.action = "/lastest/" + repo;
formElement.submit();
}
</script>
Cavice suggested something close to what I consider the best solution for this (since F.Option are not supported anymore with the default binders in Play 2.1 ).
I ended up leaving the route like:
GET /latest controllers.Find.findLatest(repo=null)
and the view like:
<form action="#routes.Find.findLatest(null)" method="get">
<select name="repo"> .... </select>
....
</form>
and in the controller:
public Result findLatest(String repoStr) {
if(repoStr==null) {
repoStr=Form.form().bindFromRequest().get("repo");
.....
This allows me to have a second route like:
GET /latest/:repo controllers.Find.findLatest(repo: String)

Fluid array, if all elements are deleted the array is not empty, but a string

When I have an array in flux:
<flux:form.section name="links" label="Links">
<flux:form.object name="link" label="Link">
<flux:field.input name="linktext" label="Linktext"/>
</flux:form.object>
</flux:form.section>
I first check if the array is set before I render it with fluid:
<f:if condition="{links}">
<ul class="menulinks">
<f:for each="{links}" as="linkelement">
<li>{linkelement.link.linktext}</li>
</f:for>
</ul>
</f:if>
This works. But if there were items set but then deleted, {links} is not empty. It is set as a string with a whitespace " ". And that means the condition in the if-Tag returns true.
And this can lead to an error. In this case I had an error in the backend, but not on the frontend. Even I used nearly the same code in <f:section name="Preview"> and <f:section name="Main">.
My idea was to check the type of {links} and only return true if the type is array. But I am not sure if this is possible with fluid. What other options I have?
The errorreport I see in the backend:
The argument "each" was registered with type "array", but is of type "string" in view helper "TYPO3\CMS\Fluid\ViewHelpers\ForViewHelper"
My idea was to check the type of {links} and only return true if the type is array. But I am not sure if this is possible with fluid.
It is. You can always implement your own ViewHelper if you need support for something, fluid does not bring out of the box. To add the ViewHelper you need, create a php file called IfIsNonEmptyArrayViewHelper.php in some_extension/Classes/ViewHelpers/. The implementation is quite easy:
<?php
namespace Vendor\SomeExtension\ViewHelpers;
class IfIsNonEmptyArrayViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {
/**
* #param mixed $variable
* #return string
*/
public function render($variable) {
if (is_array($variable) && !empty($variable)) {
return $this->renderThenChild();
}
return $this->renderElseChild();
}
}
After that you only have to add your own fluid namespace to your template like this: {namespace ns=Vendor\SomeExtension\ViewHelpers}
Now you can write your condition like this:
<ns:ifIsNonEmptyArray variable="{links}">
<ul class="menulinks">
<f:for each="{links}" as="linkelement">
<li>{linkelement.link.linktext}</li>
</f:for>
</ul>
</ns:ifIsNonEmptyArray>
Of course ns, Vendor and SomeExtension are just placeholders for the real names.

How to access a method and pass an argument within the template?

In my template I want check whether an entity has a relation to another one. Meaning one object is in an attached Object Storage of another one.
In the controller I can simply call:
if ($product->getCategory()->offsetExists($category) {
print 'In category ' . $category->getName();
}
But I can't figure out the correct syntax in the template. I tried those without luck (both evaluate to true everytime):
<f:if condition="{product.category.offsetExists(category)}">true</f:if>
<f:if condition="{product.category.offsetExists({category})}">true</f:if>
Is this even possible within the template?
You can only access properties via Getter from Fluid with no parameters, but you can implement an own ViewHelper to check that. As parameters you can use your Product and the Category. Then you can call your ViewHelper from Fluid this way:
<vh:checkOffset product="{product}" category="{category}" />
or inline
{vh:checkOffset(product: product, category: category)}
Within your ViewHelper you can check this in the way you've done it in your Controller:
public function render($product, $category){
return ($product->getCategory()->offsetExists($category));
}
Additionally to sretuer's answer, I'll only mention that you can create VH which will display block conditionally like:
File typo3conf/ext/your_ext/ViewHelpers/CheckOffsetViewHelper.php
<?php
namespace VENDORNAME\YourExt\ViewHelpers;
class CheckOffsetViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
public function render() {
return ($product->getCategory()->offsetExists($category))
? $this->renderChildren()
: '';
}
}
?>
So you can use it in the view:
{namespace vh=VENDORNAME\YourExt\ViewHelpers}
<vh:checkOffset product="{product}" category="{category}" >
Display this only if product is in category
</vh:checkOffset>
Of course you need to fix VENDORNAME and YourExt according to your extension, can be found at the beginning of every controller, model, repository etc.
You may consider https://fluidtypo3.org/viewhelpers/vhs/master/Condition/Iterator/ContainsViewHelper.html which is designed for creating conditions in Fluid that check if an array or Iterator contains another object and works exactly like f:if regarding then and else arguments and f:then and f:else child nodes.

TYPO3: How can I access property of objects in a partial or section?

I have an object defined in TypoScript
page.10 {
variables {
myObject = COA
myObject{
1 = TEXT
1.value = yome Text
2 = TEXT
2.value = 42
}
}
}
and I need the data of the myObject in a partial
<f:render partial="myPartial" arguments="{content:myObject}" />
that looks like
<section id="myPartial">
<h2>{content.1}</h2>
<p>{content.2}</p>
</section>
Although the content is there ( because {content} will display all the properties) I cannot access it and h2 and p will be empty...
What should I do to fill h2 and p with the content of myObject?
That is not possible. TypoScript only returns text strings at the moment, not arrays. Thus the variable myObject contains the whole concatenated string of the COA, thus yome Text42.
Note that COA means Content Object Array, but the whole COA is one single object that is returned as one string.
Alternative: use the VHS extension's v:var.typoscript ViewHelper:
{namespace v=Tx_Vhs_ViewHelpers}
{v:var.typoscript(path: 'page.10.variables.myObject') -> v:var.set(name: 'myObject')}
After which you can access {myObject.1} etc. in your template. Note that the so-called "chained" usage of v:var.set is optional, but will make it easier to access your variables using an intermediate template variable instead of more expensive calls to retrieve the value completely in multiple locations. The other way:
{v:var.typoscript(path: 'page.10.variables.myObject.1')}
{v:var.typoscript(path: 'page.10.variables.myObject.2')}
etc.
VHS extension on TER: http://typo3.org/extensions/repository/view/vhs