2

i have a single field and that field can have one or two lines of html:

<p>One line</p>

or:

<p>first line</p>
<p>Second line </p>

Using twig how can i check if the field has one or two

tags.

Example of what i want to do:

{% if item|length('<p>') = 1 %}
     <div class="one">{{ item }}</div>
 {% elseif item|length('<p>') = 2 %}
     <div class="two">{{ item }}</div>
 {% endif %}

Any ideas of how to accomplish this?

Update #1: What Honza said is true I want the parent div to have a class if there is only one line in the item and a different class if there are two lines in the item

Update #2 Here is the actual Markup in my twig file.

{%
  set classes = [
    'field',
    'field--name-' ~ field_name|clean_class,
    'field--type-' ~ field_type|clean_class,
    'field--label-' ~ label_display,
  ]
%}
{%
  set title_classes = [
    'field__label',
    label_display == 'visually_hidden' ? 'visually-hidden',
  ]
%}

  <div{{ attributes.addClass(classes) }}>

    <div{{ title_attributes.addClass(title_classes) }}>{{ label }}</div>

    {% if multiple %}  <div class="field__items"> {% endif %}

    {% for item in items %}
      <div{{ item.attributes.addClass('field__item') }}>{{ item.content }}</div>
    {% endfor %}

    {% if multiple %} </div> {% endif %}
  </div>

i want the field__item to have a class when it has one <p> tag and a different class when it has two, I know by fact it will be <p> tags but the content of the <p> tag varies i just use first line and second line as an example but the content is generated by the user after they fill the field whether the use one line or two lines.

Alvin Bunk - using your code from Edit two got close but it still would output the classes as 0 regardless and i'm not sure why because i see in your twigfiddle file it worked, maybe it is because it's Drupal 8. the fallowing was how i integrated your code into the template:

 <div{{ attributes.addClass(classes) }}>

    <div{{ title_attributes.addClass(title_classes) }}>{{ label }}</div>

    {% if multiple %} <div class="field__items"> {% endif %}

    {% set count = item|split('</p>') %}

    {% for item in items %}

      <div class="{{ count|length -1 }}">{{ item.content }}</div>

    {% endfor %} 

    {% if multiple %} </div> {% endif %}

  </div> 

I don't know what i am doing wrong but it always comes out as class="0" i have also tried with "item.content" and "item.content|raw" but nothing.

Here is the dump output:

array(1) {
  [0]=>
  array(2) {
    ["content"]=>
    array(4) {
      ["#type"]=>
      string(14) "processed_text"
      ["#text"]=>
      string(52) "<p>CGS-2181-3105-9090</p>
<p>CGS-2181-3105-9090</p>"
      ["#format"]=>
      string(15) "restricted_html"
      ["#langcode"]=>
      string(3) "und"
    }
    ["attributes"]=>
    object(Drupal\Core\Template\Attribute)#2953 (1) {
      ["storage":protected]=>
      array(0) {
      }
    }
  }
}

Update #3

Based on the dump above i can get the html value of the field using {{ item.content["#text"] }} which outputs <p>CGS-2181-3105-9090</p> <p>CGS-2181-3105-9090</p> but i dont know how to iterate through it, i tried to set a variable {% set count = '<p>' in item.content["#text"] %} and then check the length like {{ count|length }} but i always get 1 regardless.

Update #4:

Using a variation of the code from Alvin Bunk i was able to finally get it to output a number here is the markup that gets the numbers of tag correctly:

 {% for item in items %}

          {% set count = item.content["#text"]|split('</p>') %}    

      <div class="{{ count|length -1 }}">{{ item.content }}</div>

    {% endfor %}

I moved the set variable below for loop and added the object where the strings exist from the dump and now it counts properly.

wolfhowling
  • 83
  • 1
  • 7
  • Can you also show how you are passing `items` into your controller as a parameter? – Alvin Bunk Feb 10 '17 at 01:00
  • how exactly do i show that? – wolfhowling Feb 10 '17 at 01:05
  • I'm not sure how it's done with Drupal, i'm only familiar with Symfony. in Symfony it would be done like this: `return $this->render('mytwigfile.html.twig', array( 'items' => $item->getContent() ));` That's what I'd like you to do, is call the "getContent()" function on your items object so the Twig file just gets a string instead of an object (or array). – Alvin Bunk Feb 10 '17 at 01:11
  • I currently have no clue how to do that in drupal either, i have looked in the api but that is a lot of php files and functions and i have not found anything specific so far, it seems is passed via the field module but i can't make heads or tails out of all that code, i will keep looking and update you if i find anything. Thanks – wolfhowling Feb 10 '17 at 01:32
  • I added EDIT #4. Try it. I'm running out of ideas. Drupal documentation is not very helpful. – Alvin Bunk Feb 10 '17 at 04:26
  • if i changed to the Edit # 4 then it goes back to class="0" regardless. – wolfhowling Feb 10 '17 at 13:43
  • Wait you are doing it wrong, you can't have the `{% set count...` within the for loop, that's why I changed it. If you put it within the for loop, it is going to redefine the value each time. So move the `{% set count = item.content["#text"]|split('') %}` outside the for loop. I think that should solve it then. You should mark my answer as the correct one if that's the case. I also provide the twigfiddle to show you the concept should work, each of those answers deserves an up vote at least. – Alvin Bunk Feb 10 '17 at 16:21
  • I mark your answer as right because it just needed a small modification, but just to repeat only works if item is set inside of the for loop, if put outside it will not work, i don't know why maybe is a drupal thing. As for Honza, his way is probably more suited to drupal way of doing things but that code cannot be inserted into a template, it has to be done via a module probably. – wolfhowling Feb 10 '17 at 22:05

2 Answers2

2

Another option is to make a Twig extension for counting paragraphs, but it's maybe an overkill for such a task:

EDIT - updated for Drupal

namespace Drupal\twig_extension_parCount\TwigExtension;

use Drupal\Core\Template\TwigExtension;

class parCountExtension extends TwigExtension
{
    public function getFunctions() {
        return array(
            'parCount' => new \Twig_Function_Function(array('Drupal\twig_extension_parCount\TwigExtension\ParCountExtension', 'parCount')),
        );
    }

    public function getName() {
        return 'twig_extension_parCount.parCount_extension';
    }

    public static function parCount($str)
    {
        return substr_count($str, '<p>');
    }
}

And then in template itself

{%  if (parCount(item) == 1) %}
   <div class="one">
{% elseif (parCount(item) == 2) %}
   <div class="two">
{% endif %}
{{ item }}</div>
Jan Rydrych
  • 2,188
  • 2
  • 13
  • 18
  • This code looks promising as well but i dont know how to integrate the namespace function into twig in drupal. i think that to use this one i would have to do the functions via php as field pre-process on the .theme file and then call onto the function from twig but i wouldn't know where to begin on that. – wolfhowling Feb 10 '17 at 00:32
  • The extension code is updated for Drupal. If you're interrested in Twig extensions, you can find more complex example, including descriptions, here: https://api.drupal.org/api/drupal/core%21modules%21system%21tests%21modules%21twig_extension_test%21src%21TwigExtension%21TestExtension.php/8.2.x – Jan Rydrych Feb 10 '17 at 07:39
  • you are correct that the code you have there for the function uses drupal api but this code would have to be called from a module of some kind in order to be use it cannot be inserted into a twig template, it is probably a better way of doing it or at least more of the drupal way but i am not confident enough in my coding to create a module and all its parts. None the less thanks very much, i will still keep the code and see how i can use it in the future. – wolfhowling Feb 10 '17 at 22:08
1

You haven't provided enough information, however I'll presume some things:

Item is a string, like so:

<p>first line</p><p>Second line</p>

Then you can use the following Twig code:

{% set lines = item|split('</p>') %}

{% for element in lines if element != '' %}
    <div class="{{ loop.index }}">{{ element|raw }}</p></div>
{% endfor %}

In the above, I take the item string and split into an array called lines. And then I do a for loop of the lines elements, and set the class to the loop index (which is one based). I output as raw html.

You'll notice since I split on '</p>', the array lines will contain one last element which is null; so in my for I have to add if element != ''.

Here is the twigfiddle for you to see it working in action.


EDIT #2 - Based on comments.

In that case, this should work:

{% set item = '\n<p>first line</p>\n<p>Second line</p>\n' %}

{% set count = item|split('</p>') %}

<div class="{{ count|length -1 }}">{{ item|raw }}</div>

I updated my twigfiddle so you can see the change.


EDIT #3

You missed one change, you need to split items not item:

<div{{ attributes.addClass(classes) }}>

    <div{{ title_attributes.addClass(title_classes) }}>{{ label }}</div>

    {% if multiple %} <div class="field__items"> {% endif %}

    {% set count = items|split('</p>') %}

    {% for item in items %}
      <div class="{{ count|length -1 }}">{{ item.content }}</div>
    {% endfor %} 

    {% if multiple %} </div> {% endif %}

  </div>

EDIT #4

Your set for count needs to be outside the loop. Try this:

{% set count = item.content["#text"]|split('</p>') %}  
{% for item in items %}
      <div class="{{ count|length -1 }}">{{ item.content["#text"] }}</div>
{% endfor %}
Alvin Bunk
  • 7,621
  • 3
  • 29
  • 45
  • I think the OP doesn't want to divide the lines of multiline "item" and style each other differently. What I understand from his description is that he want one style if there is one line in the "item" and other style if there are two lines in the "item". What do you think? – Jan Rydrych Feb 09 '17 at 19:04
  • Your answer is definitely helpful. We'll have to see what the OP actually wants. It's definitely not clear from his post. – Alvin Bunk Feb 09 '17 at 19:35
  • What Honza said is true I want the parent div to have a class if there is only one line in the item and a different class if there are two lines in the item – wolfhowling Feb 09 '17 at 22:52
  • See my EDIT #2 changes. That should resolve it. By the way are numbers for the class ok? I haven't thought of spelling out numbers, but there might be an easy way to do that as well. If so, let me know the maximum number you would use. – Alvin Bunk Feb 09 '17 at 23:01
  • Your edit #2 got close but still not working see update # 2. – wolfhowling Feb 10 '17 at 00:27
  • See my EDIT #3. This is because if you tried to split `item` it would result in a null array. – Alvin Bunk Feb 10 '17 at 00:32
  • When i split items then i always get a class of "-1" regardless of tags present . – wolfhowling Feb 10 '17 at 00:44
  • Can you cut an paste what `items` actually contains, then I can figure it out from there. If you are in the DEV environment you can use `{{ dump(items) }}`. – Alvin Bunk Feb 10 '17 at 00:46
  • i added the dump to the top – wolfhowling Feb 10 '17 at 00:50
  • I think it's an object. Can you try this instead: `{% set count = items.content|split('') %}` – Alvin Bunk Feb 10 '17 at 00:55
  • With that new code it goes back to class = "0" regardless of quantity present. – wolfhowling Feb 10 '17 at 00:58