TYPO3 - Display system categories and sub categories - typo3

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.

Related

Typo3: How to get the View exception error

I am Developing a custom extension for Typo3. now I am getting an error if the user did not include my extension from template's include section.
I want to catch this error to show a message from controller. How can I do this?
my controller action.
public function listAction()
{
$audits = $this->auditRepository->findAll();
$this->view->assign('arrDetails', $audits);
}
This could be one solution, but not the cleanest.
We first need to get the values from the field include_static_file that it is located on the sys_template table. So:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template')->createQueryBuilder();
$result = $queryBuilder
->select('include_static_file')
->from('sys_template')
->execute()
->fetch(0);
The we need to get the string and evaluate if your extension key is present. So:
$extKey = 'your_extension_key';
if (strpos($result['include_static_file'], $extKey) !== false) {
$audits = $this->auditRepository->findAll();
$this->view->assign('arrDetails', $audits);
}
else {
$this->addFlashMessage(
'You forgot to add the static template',
$messageTitle = 'Template is missing',
$severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING,
$storeInSession = TRUE
);
}
Your HTML
<f:if condition="{arrDetails}">
<f:then>
do something with your content
</:then>
<f:else>
<f:flashMessages />
</f:else>
</f:if>
Of course you can write a static function for this or you can use the LocalizationUtility in order to get the text in multiple languages. It is up to you.
Result:
Are you sure you need your static template?
I think it is important that some values you fetch from typoscript got meaningful content.
As you inspect them anyway you can output a message if one or more values are empty.
Your plugin would work in an installation if all necessary values are set in any way. Even if your static template is not included.
And your plugin would fail if your static template is included but following typoscript would erase the settings from it.
In your error message you can note the possibility to use the values from your static template.

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}

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.

Why mvc Html.HiddenFor does not render my field?

I'm trying to do this simple thing
<%= Html.HiddenFor(model => model.Id)%>
the model is
[HiddenInput(DisplayValue=true)]
public int Id { get; set; }
but i always get this rendered
<input type="hidden" value="0" name="UserInfo.Id" id="UserInfo_Id">
i've check and the id is NOT 0.. ?!
need some explanation here...
Edit
The problem seem's to be the post thing mentionned below.
This is working
<input type="hidden" value="<%= Html.AttributeEncode(Model.Id) %>" id="<%= Html.IdFor(model=>model.Id)%>" name="<%= Html.NameFor(model=>model.Id)%>" />
Thanks to Manaf
I'm not sure if this is the case with you but the Html.HiddenFor() "do not output correct values after a post if the value is changed during the post." and this is a not a bug it was designed that way.
Quick Fix :
Don't use the helper, try this instead :
<input type="hidden" value="<%= Html.AttributeEncode(model.Id) %>" id="Id" name="Id" />
Always worked for me :)
To add to Manaf's correct answer--you note correctly that the problem occurs in controller actions that handle posts. I was getting the same problem in a controller action that handles a get when I explicitly pass a model to a view:
[HttpGet]
ActionResult SearchForSomething(SearchForm searchForm)
{
searchForm.MyId = SomeValueFromSession;
return View("SearchForSomething", searchForm);
}
In the view, this line that rendered a hidden input for MyId always rendered "0":
#Html.HiddenFor(m => m.MyId);
Per Darren Oster's suggestion I changed to the following and fixed the problem:
[HttpGet]
ActionResult SearchForSomething(SearchForm searchForm)
{
searchForm.MyId = SomeValueFromSession;
ModelState.Clear();
return View("SearchForSomething", searchForm);
}
My comment is relegated to the last place (even I couldn't find it), so:
In case you don't want to clear the modelstate, as Darren Oster suggested, removing the problematic key worked for me: ModelState.Remove("HiddenKey")
I ran into this problem as well with #Html.HiddenFor.
#Html.Hidden("Id", Model.Id) also gave value 0, but a foreign key field, e.g., #Html.Hidden("Model_Category_ModelId", Model.Category.ModelId) did work, while it #Html.HiddenFor(m => m.Category.ModelId) did not.
My solution was to redirect to the get action, as described in ASP NET MVC Post Redirect Get Pattern.