Search code examples
symfonydrupaltwigdrupal-themingdrupal-8

Twig: how to check the inner html content of a field


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.


Solution

  • 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 %}