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

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.

Related

TYPO3 - Display system categories and sub categories

I am a new typo3 user and actually I'm creating an extension that will allow me to display a list of categories created with the categories of the system, which will contain subcategories (categories and subcategories can have several subcategories), and finally a pdf file
I'm having a little trouble visualizing how to create this extension
Currently, I manage to retrieve and display the parent categories, but I have no idea how to display the different subcategories after clicking on a parent category
Does anyone have an idea how I could display my different subcategories?
there is some code :
Controller :
public function listAction()
{
$categories = $this->categorieRepository->findByPid(177);
$this->view->assign(
'categories' => $categories
);
}
List.html
<div>
<f:for each="{categories}" as="categorie">
<f:if condition="{categorie.parent} ">
<f:then>
</f:then>
<f:else>
<f:link.action action="list">
{categorie.title}
</f:link.action>
</f:else>
</f:if>
</f:for>
</div>
thanks In advance for your advice
Usually the listAction never get's any parameters and the pid is assigned by the storagePid that is automatically fetched by the repository. The repository also can have the setting to ignore the pid. You never mentioned it but as I see the pid in your code you could also include the option to transfer the pid by the URL as parameter.
Additionally, and that's the answer to your question, you can transfer the parent category, so the listAction would have one or two optional parameters. They have to be optional to display also something if no parameters are given:
public function listAction($parentCategeoryId = null, $pageId = null)
{
// HERE SOME LOGIC
// YOU HAD TO TRANSFER ALSO THE $pageId TO THE REPOSITORY IF USED
$categories = ...
$this->view->assign(
'categories' => $categories
);
}
The viewHelper can transfer the parameters if you add them there:
<f:link.action action="list" arguments="{parentCategeoryId: category.uid, pageId: categorie.pid}" >
{categorie.title}
</f:link.action>
This is no copy-paste answer, so you have still some work, but that's the general approach I'd follow.

How to access a string array using Sightly(HTL)

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)

TYPO3 count objects with constraint and display result in fluid template

I have build an extension with the extension builder that handles objects, an object can be an "item" or a "project".
An object has a field status which has 4 options, and is filled with an integer (3 = sold).
An object can be signed as a "project", in that case it has a boolean 1 for isproject and a field items with related objects as items.
this all works fine, I can iterate trough the projects with my fluid template and with <f:count>{object.items}</f:count> display the number of items appartaining to the project.
In the same fashion I should display the count of only the sold items ...
(<f:count where="object.items.status == 3">{object.items}</f:count> this obviously does not work, just to render the idea)
with <f:debug>{object}</f:debug> I see the field status defined for all items ...
since I have no idea how to approch this I might have left out some vital information
As indicated in the previous answer, you can use the GroupedFor ViewHelper for this purpose. But using it would be to much logic inside the fluid template thats the reason why this should be done in the controller, model or repository.
Example: Adding a Getter to the Object-Model
/**
* #return int
*/
public function getSoldItems() {
$soldCount = 0;
foreach($this->getItems() as $item) {
// 0 = sold
if($item->getStatus()===0) {
$soldCount++;
}
}
return $soldCount;
}
In fluid you can call the Getter with {object.soldItems}
A better performance solution especially with lazy loading subobjects would be counting with the repository. For this you have to create a function in the repository and call it inside the Getter-Function. To use common repository methods for creating the query, you need a "Backrelation" of items to the object. Otherwise you have to write the query with "statement()" on your own.
You can count them in the controller or use the GroupedFor ViewHelper https://fluidtypo3.org/viewhelpers/fluid/master/GroupedForViewHelper.html
Check this workaround:
<f:variable name="counter" value="0" />
<!-- Loop over the elements -->
<f:for each="{bookings}" as="booking">
<!-- Check if the criteria is met -->
<f:if condition="{booking.paid}">
<!-- Increment the counter -->
<f:variable name="counter" value="{counter + 1}" />
</f:if>
</f:for>
<!-- Display the counter-Variable -->
{counter}

PhpStorm not recognizing TYPO3 Fluid closing tags

I have a Fluid template where I break a list into groups of 4, like:
<ul>
<f:for each="{items}" as="item" iteration="i">
<li>(CONTENT HERE)</li>
<f:if condition="{i.cycle} % 4">
</ul>
<ul>
</f:if>
</f:for>
</ul>
I've added the Fluid XML namespaces into my project like this, but PhpStorm still informs me that my closing </f:if> tag 'matches nothing'.
Is there a way to configure PhpStorm to recognize this construct?
Am I violating best practices by wrapping a closing and opening tag in an <f:if>?
The very first thing that PHP Storm validates is proper nesting of XML/HTML - like tags and in your sample you break it mercilessly (from point of view the IDE, not mine as I understand the need of use the modulo break in the Fluid) and it doesn't matter if you'll add valid XML namespaces or even disable XML inspector at all.
What's more it's so clever that is able to recognize tags even in inline then notification (damn it!), this will also display IDE level error:
<ul>
<f:for each="{items}" as="item" iteration="i">
<li>Item {i.cycle}</li>
{f:if(condition: '{i.cycle} % 4', then: '</ul><ul>')}
</f:for>
</ul>
The only solution that I found till now and using it with success in many projects is custom ViewHelper in custom ext (for IDE it's important that you declare startTage before endTag!):
<?php
namespace Vendor\Myext\ViewHelpers;
/**
* {namespace myNs=Vendor\Myext\ViewHelpers}
*
* = Inline samples
*
* === Break the list:
* {myNs:breakTagByModulo(iterator: i, modulo: 2)}
* or...
* {myNs:breakTagByModulo(iterator: i, modulo: 2, startTag: '<ul class="next-level">', endTag: '</ul>')}
*
* result on valid modulo:
* </ul><ul class="next-level">
*
* === Break the Bootstrap row:
* {myNs:breakTagByModulo(iterator: i, modulo: 2, startTag: '<div class="row">', endTag: '</div>')}
*
* result on valid modulo:
* </div><div class="row">
*
* etc...
*/
class BreakTagByModuloViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* #param array $iterator Iterator from `f:for` VH
* #param integer $modulo Modulo to check
* #param boolean $skipLast If skipLast==true VH will return false even if condition is correct, needed for `not full` lists
* #param string $startTag Begin of the tag
* #param string $endTag End of the tag
*
* #return bool|string
*/
public function render($iterator, $modulo, $skipLast = true, $startTag = '<ul>', $endTag = '</ul>') {
$i = $iterator['cycle'];
$bool = ($i % $modulo == 0);
if ($skipLast && $iterator['isLast']) {
$bool = false;
}
return ($bool) ? $endTag . $startTag : null;
}
}
?>
The usage in the view is showed above, just for your sample it will be something like:
{namespace myNs=Vendor\Myext\ViewHelpers}
<ul>
<f:for each="{items}" as="item" iteration="i">
<li>(CONTENT HERE)</li>
{myNs:breakTagByModulo(iterator: i, modulo: 4)}
</f:for>
</ul>
(of course you're replacing myNs, Vendor and Myext with your own values)
I would suggest a different approach to ensure a solid html structure. Mixing template HTML with HTML coming from view helpers seems like a dangerous idea, when the opening tag comes from one and the closing tag the other.
Since splitting the list into chunks seems to be a logical necessity rather than a presentational, the logic for this should happen in a controller rather than the view. If for some reason, you can't change the controller logic (3rd party extension?), you should resort to a widget / view helper. In there you could split the list into sets of 4 using pure PHP and then use a loop to iterate the sets in your template. This way the HTML will be less breakable, easier to maintain and easier to understand (for you and for PHPSTORM).
I was inspired by LazyOne's comment, and Biesior's answer. Taking the following:
<ul>
<f:for each="{items}" as="item" iteration="i">
<li>Item {i.cycle}</li>
{f:if(condition: '{i.cycle} % 4', else: '</ul><ul>')}
</f:for>
</ul>
I tried replacing the angle brackets with HTML entities, and Unicode escape sequences, both of which where output in full. Then I thought to do character replacement. I found the v:format.replace viewhelper, and I did the following:
<v:format.replace substring="(" replacement="<"><v:format.replace substring=")" replacement=">">{f:if(condition: '{iter.cycle} % 4', else: '(/ul)(ul)')}</v:format.replace></v:format.replace>
That is, I put parentheses in my code, and switched them to angle brackets for the output.
So there's a ridiculously verbose option for anyone who feels like using it.

TYPO3 - Fluid returns the string "array"

I have this in my template:
<v:iterator.explode content="<f:format.nl2br>{artnumbers.qualitynumber.certificates}</f:format.nl2br>" glue="<br />" as="expCertificates">
if artnumbers.qualitynumber.certificates is empty, it returns teh string "array".
Is that a bug?
v:iterator.explode when used with the as argument implies the variable assigned to as is only available inside the tag content:
<v:iterator.explode content="1,2,3" as="numbers">
{numbers} is an array
</v:iterator.explode>
{numbers} is no longer defined.
This behaviour changed from VHS 1.7 to 1.8 (from memory).
Alternatively, do:
{artnumbers.qualitynumber.certificates
-> f:format.nl2br()
-> v:iterator.explode(glue: '<br />')
-> v:var.set(name: 'extractedCertificates')}
<f:for each="{extractedCertificates}" as="certificate">
{certificate}
</f:for>
Or better, but assumes your lines are ONLY separated by a single line break:
{artnumbers.qualitynumber.certificates
-> v:iterator.explode(glue: 'constant:LF')
-> v:var.set(name: 'extractedCertificates')}
<f:for each="{extractedCertificates}" as="certificate">
{certificate}
</f:for>
Which of course lets you skip the nl2br step.
Even more compacted:
<f:for each="{artnumbers.qualitynumber.certificates -> v:iterator.explode(glue: 'constant:LF')}" as="certificate">
{certificate}
</f:for>
You are using the <f:format.nl2br> ViewHelper. This ViewHelper replaces newlines in a string (!) with tags. You cannot use it for an array and that's why it returns "array".
I don't know why you need the explode viewHelper. Why don't you just iterate through your certificates?
<f:for each="{artnumbers.qualitynumber.certificates}" as="certificate">
{certificate}<br />
</f:for>
EDIT: If certificates is a string containing newlines, use the newline as glue for explode:
<v:iterator.explode content="{artnumbers.qualitynumber.certificates}" glue="\n" as="expCertificates">
You could also try to use CONSTANT:LF in glue as suggested in the documentation of the ViewHelper.