TYPO3 Fluid complex if conditions
Asked Answered
P

11

16

I am trying to write the following if condition in fluid but it is not working as I would hope.

Condition As part of a for loop I want to check if the item is the first one or 4th, 8th etc

I would have thought the following would work but it display the code for every iteration.

<f:if condition="{logoIterator.isFirst} || {logoIterator.cycle % 4} == 0">

I have managed to get it working with a nested if but it just feels wrong having the same section of code twice and also having the cycle check use a <f:else> instead of == 0

<f:if condition="{logoIterator.isFirst}">
    <f:then>
        Do Something
    </f:then>
    <f:else>
        <f:if condition="{logoIterator.cycle} % 4">
            <f:else>
                Do Something
            </f:else>
        </f:if>
    </f:else>
</f:if>
Poetry answered 1/11, 2013 at 16:36 Comment(0)
H
26

TYPO3 v8

Updated the answer for TYPO3 v8. This is quoted from Claus answer below:

Updating this information with current situation:

On TYPO3v8 and later, the following syntax is supported which fits perfectly with your use case:

<f:if condition="{logoIterator.isFirst}">
    <f:then>First</f:then>
    <f:else if="{logoIterator.cycle % 4}">n4th</f:else>
    <f:else if="{logoIterator.cycle % 8}">n8th</f:else>
    <f:else>Not first, not n4th, not n8th - fallback/normal</f:else>
</f:if>

In addition there is support for syntax like this:

<f:if condition="{logoIterator.isFirst} || {logoIterator.cycle} % 4">
    Is first or n4th
</f:if>

Which can be more appropriate for some cases (in particular when using a condition in inline syntax where you can't expand to tag mode in order to gain access to the f:else with the new if argument).

TYPO3 6.2 LTS and 7 LTS

For more complex if-Conditions (like several or/and combinations) you can add your own ViewHelper in your_extension/Classes/ViewHelpers/. You just have to extend Fluids AbstractConditionViewHelper. The simple if-ViewHelper that shipps with Fluid looks like this:

class IfViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {

    /**
     * renders <f:then> child if $condition is true, otherwise renders <f:else> child.
     *
     * @param boolean $condition View helper condition
     * @return string the rendered string
     * @api
     */
    public function render($condition) {
        if ($condition) {
            return $this->renderThenChild();
        } else {
            return $this->renderElseChild();
        }
    }
}

All you have to do in your own ViewHelper is to add more parameter than $condition, like $or, $and, $not etc. Then you just write your if-Conditions in php and render either the then or else child. For your Example, you can go with something like this:

class ExtendedIfViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {

    /**
     * renders <f:then> child if $condition or $or is true, otherwise renders <f:else> child.
     *
     * @param boolean $condition View helper condition
     * @param boolean $or View helper condition
     * @return string the rendered string
     */
    public function render($condition, $or) {
        if ($condition || $or) {
            return $this->renderThenChild();
        } else {
            return $this->renderElseChild();
        }
    }
}

The File would be in your_extension/Classes/ViewHelpers/ExtendedIfViewHelper.php Then you have to add your namespace in the Fluid-Template like this (which enables all your self-written ViewHelpers from your_extension/Classes/ViewHelpers/ in the template:

{namespace vh=Vendor\YourExtension\ViewHelpers}

and call it in your template like this:

<vh:extendedIf condition="{logoIterator.isFirst}" or="{logoIterator.cycle} % 4">
  <f:then>Do something</f:then>
  <f:else>Do something else</f:else>
</vh:extendedIf>

Edit: updated.

Howells answered 2/11, 2013 at 10:29 Comment(6)
Thanks for the help with the helpers. My example will do something on the 4th as it uses the else, the then is when there is a remainder which confused me at first. <f:if condition="{rank} % 2">Will be shown if rank % 2 != 0.</f:if>Poetry
Ah, of course you are right. My bad. So the viewHelper needs to be adjustet as well ;)Howells
I will validate the results later but I am sure your code will work well. ThanksPoetry
Awesome! This should teach me not to be afraid of own VHs. One thing though: the Code for 7LTS, with current 7.6 and PHP 7, throws an error #1289386765: Could not analyse class: "STUBR\Template\ViewHelpers\OrViewHelper" maybe not loaded or no autoloader? PHP Warning: Declaration of STUBR\Template\ViewHelpers\OrViewHelper::render($condition, $or) should be compatible with TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper::render() in /home/user/public_html/typo3conf/ext/template/Classes/ViewHelpers/OrViewHelper.php line 34 when 'exceptionalErrors' => 28674, is set in LocalconfigClarkia
I found on forge.typo3.org/issues/76146 that this has got to do with log_level E_STRICT and can be turned off by tuning that off. But still: should the VH be modified somehow to be compatible?Clarkia
If you need an or-condition where you want to compare a single variable with two values use the following round bracked. <f:if condition="({ip}==92.255.255.55 || {ip}==92.255.255.55)"></f:if>. TYPO3 Version > 8.Lossa
S
15

Updating this information with current situation:

On TYPO3v8 and later, the following syntax is supported which fits perfectly with your use case:

<f:if condition="{logoIterator.isFirst}">
    <f:then>First</f:then>
    <f:else if="{logoIterator.cycle % 4}">n4th</f:else>
    <f:else if="{logoIterator.cycle % 8}">n8th</f:else>
    <f:else>Not first, not n4th, not n8th - fallback/normal</f:else>
</f:if>

In addition there is support for syntax like this:

<f:if condition="{logoIterator.isFirst} || {logoIterator.cycle} % 4">
    Is first or n4th
</f:if>

Which can be more appropriate for some cases (in particular when using a condition in inline syntax where you can't expand to tag mode in order to gain access to the f:else with the new if argument).

Sideling answered 23/5, 2016 at 13:31 Comment(3)
Hey Claus, may I copy your answer to append the one with the most votes so it appears "up there"?Howells
Hi Daniel - no problem.Sideling
docs.typo3.org/m/typo3/guide-extbasefluid/8.7/en-us/Fluid/…Ziagos
B
6

v:if.condition will be deprecated in vhs V2.0 use v:if stack instead: https://github.com/FluidTYPO3/vhs/issues/493

Bifocal answered 12/3, 2014 at 8:57 Comment(0)
G
4

You could also use the If Condition Extend ViewHelper provided by the VHS extension:

<v:if.condition>
    <v:if.condition.extend>
        {logoIterator.isFirst} || {logoIterator.cycle % 4} == 0
    </v:if.condition.extend>
    <f:then>Output if TRUE</f:then>
    <f:else>Output if FALSE</f:else>
</v:if.condition>

On a side note: the VHS extension provides lots of useful ViewHelpers. I feel a lot of them should be included in TYPO3 Fluid.

Graphemics answered 18/11, 2013 at 14:40 Comment(1)
Today this condition looks different, <v:if stack="{0: dateObject, 1: '>', 2: NowDateObject, 3: 'AND', 4: objecteValue, 5: '>', 6: 1}"> <f:then>IF THEN</f:then> <f:else>IF ELSE</f:else> </v:if> fluidtypo3.org/viewhelpers/vhs/master/IfViewHelper.htmlBelligerency
L
4

For many cases its enough to use an array-comparison - so you don't have to create a custom view-helper.

AND

<f:if condition="{0:user.number,1:user.zip}=={0:123,1:01234}">

OR

<f:if condition="{0:user.number,1:user.zip}!={0:false,1:false}">

Sadly this works just to check if a variable is set and not against a value. But for many cases this is enough.

PS:(with this array comparison you can also compare strings)

Ladyship answered 13/6, 2016 at 12:17 Comment(0)
D
1

In addition to Daniels' answer, I made a ViewHelper that accepts multiple conditions, with either an "and"-mode (default) or an "or"-mode:

<?php
namespace TLID\Contentelements\ViewHelpers;

class IfViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
  /**
   * Checks conditions
   *
   * @param mixed $checks
   * @param string $type
   *
   * @return boolean whether is array or not
   */
  public function render($checks, $type = "and") {
    $success = $type === "and" ? true : false;
    $doc = new \DOMDocument();
    $doc->loadHTML($this->renderChildren());
    $xpath = new \DOMXpath($doc);

    // get store values
    $storeNodes = $xpath->query("//body/store");
    $store = "";
    foreach ($storeNodes as $storeNode) {
      foreach ($storeNode->childNodes as $childNode) {
        $store .= $doc->saveHTML($childNode);
      }
    }

    // do the actual check
    foreach ($checks as $check) {
      if (
        ($type === "and" && (is_array($check) && count($check) === 0 || is_object($check) && get_object_vars($check) === 0 || empty($check))) ||
        (is_array($check) && count($check) !== 0 || is_object($check) && get_object_vars($check) !== 0 || !empty($check))
      ) {
        $success = $type === 'and' ? false : true;
        break;
      }
    }

    // render content
    $renderQueryElement = $success ? "success" : "failure";
    $renderNodes = $xpath->query("//body/" . $renderQueryElement);
    $content = "";
    foreach ($renderNodes as $renderNode) {
      foreach ($renderNode->childNodes as $childNode) {
        $content .= $doc->saveHTML($childNode);
      }
    }

    //insert contents
    $matches;
    $content = preg_replace("/<use[^>]*><\/use>/", $store, $content);

    //return rendered content
    return $content;
  }
}
?>

Though it can be written alot better, it works. Here is how i use it:

{namespace vhs=TLID\contentelements\ViewHelpers}

<vhs:if checks="{0: settings.link}">
  <f:comment><!-- store the content --></f:comment>
  <store>
    <f:if condition="{images}">
      <f:for each="{images}" as="image">
        <f:image image="{image}" alt="{image.description}" title="{image.title}" />
      </f:for>
    </f:if>

    <vhs:if checks="{0: settings.headline, 1: settings.text}" type="or">
      <success>
        <div>
          <f:if condition="{settings.headline}"><h2><f:format.nl2br><vhs:shy>{settings.headline}</vhs:shy></f:format.nl2br></h2></f:if>
          <f:if condition="{settings.text}"><p><f:format.nl2br><vhs:shy>{settings.text}</vhs:shy></f:format.nl2br></p></f:if>
        </div>        
      </success>
    </vhs:if>
  </store>

  <f:comment><!-- use the content of this container on success --></f:comment>
  <success>
    <vhs:link href="{settings.link}" target="{settings.target}" class="box">
      <use />
    </vhs:link>
  </success>

  <f:comment><!-- use the content of this container on failure --></f:comment>
  <failure>
    <div class="box">
      <use />
    </div>
  </failure>
</vhs:if>

It additionally has a store-element, because i don't like it to write the same code twice. So you can optionally save some fluid and pass it to both the success and failure containers without the need for repetition.

Declamatory answered 7/9, 2015 at 14:26 Comment(0)
E
1

It is possible to implement complex if conditions with a combination of f:if, v:variable.set and v:math. Use the math ViewHelper to do the magic and store its result in a variable. Then use if comparators to validate and act upon it.

Here is my sample code:

<f:for each="{customers}" as="customer" iteration="iterator">
    <v:variable.set name="isFirst" value="{v:math.modulo(a: iterator.cycle, b: settings.itemsperrow, fail: 0)}" />
    <f:if condition="{isFirst}==1">
        <div class="row">
    </f:if>
    <div class="col-md-{settings.colWidth}">
        <div class="clientlogo_ref">
            <f:image src="{customer.logo.originalResource.publicUrl}" />
        </div>                 
    </div>
    <f:if condition="{isFirst}==0">
        </div>
    </f:if>
</f:for>

This code begins / ends a grid row for every X item, defined by settings.itemsperrow. This is variable and can be set in the plugin's configuration. It uses modulo to calculate iterator.cycle (counter beginning with 1) mod settings.itemsperrow. If the result is 1, it is the first element of a row. 0 means it is the last, so row must be closed.

Ethylene answered 10/11, 2016 at 14:52 Comment(0)
A
0

Yes it feels wrong but this is the only way you can do it. This is a very good site for viewhelper :: https://fluidtypo3.org/viewhelpers/fluid/master/IfViewHelper.html

Apartment answered 1/11, 2013 at 17:46 Comment(2)
Thanks for the answer. I was wondering if anyone had thought about or put together a view helper to do more complex conditions?Poetry
Stop defending TYPO3 and Fluid in particular. It's a shame, it's embarrassing that you need workarounds to do boolean conditions. TYPO3 is written in PHP. Why not use it? <f:if condition="..."> is incomplete, <? if (...): ?> is not. It's complete. Mature, testest while used for years and by millions of developers. It's just stupid to accept this extra overhead while parsing the fluid syntax to display a website. The view can still be separated from logic while using the same language to preserve the MVC-concept if needed.Deerstalker
K
0

For me best way to use 'f:cycle'. If i need devide for rows each 3th elements i just do:

<v:variable.set  name="wraper" value='</div><div class="row">' />
<f:for each="{items}" as="item" iteration="itemIterator">
 ....
   <f:cycle values="{0: '', 1: '', 2: '{wraper}'}" as="cycle">
        {cycle -> f:format.raw()}
  </f:cycle>
 ...
</f:for>
Katerinekates answered 11/3, 2016 at 15:49 Comment(0)
S
0

If you have CObjects help this workaround for a Logical OR:

# Sidebar | 1 ColPos = 78
lib.sidebar1 < styles.content.get
lib.sidebar1.select.where = colPos=78
# Sidebar | 2 ColPos = 79
lib.sidebar2 < styles.content.get
lib.sidebar2.select.where = colPos=79

#LogicalOR
lib.tempLogicalOrSidebar = COA
lib.tempLogicalOrSidebar {
    10 < lib.sidebar1
    10.stdWrap.override.cObject =< lib.sidebar2
}

FLUID IF CONDITION:

<f:if condition="{f:cObject(typoscriptObjectPath: 'lib.tempLogicalOrSidebar.10')}">
Scientism answered 27/6, 2016 at 8:2 Comment(0)
A
0

Status 2017:

Here is an example of a modern VHS viewhelper condition using the stack attribute. This example also contains a NULL check and a logical or (||) to show how it is done.

<v:if stack="{0: '{itemId}', 1:'==', 2:NULL, 3: '||', 4: '{itemId}', 5: '==', 6: '{falMedia.uid}'}">
    <f:then>
    ...
    </f:then>
</v:if>

Mind that NULL is NOT quoted!

Acaleph answered 3/3, 2017 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.