Fun with CSS child-selectors, making a comma separated series with oxford commas

This all started with a question on Drupal Answers in which I was trying to figure out how to manipulate the render array of paragraphs module output to turn it into a comma separated list of field content.

It turned into a fun exploration of css selectors that I haven't messed with in a long time. Here is a jsfiddle for you to follow along with.

Imagine a big wall of divs like those that drupal loves to spit out, and you'll get a basic feel for what we are dealing with, but here is a short sample. See the jsfiddle for the full test case html:

<div class="paragraphs-items paragraphs-items-field-doc-editor paragraphs-items-field-doc-editor-full paragraphs-items-full">
  <div class="label-inline">
    Editor:&nbsp;
  </div>
  <div class="entity entity-paragraphs-item paragraphs-item-doc-other-contrib-pb" about="" typeof="">
    <div class="content">
      <div class="field-pb-doc-other-fullname">
        <div class="field-items">
          <div class="field-item even">
            Person 1
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Basically there is a .paragraphs-items div for each paragraphs field, containing a label div and some arbitrary number of entries like so: dev.entity-paragraphs-item > div.content > div.field_name > div.field-items > div.field-item > value

First I want to make sure all these divs stop acting like blocks that each need their own line, and make sure that each field starts on a new line:

.paragraphs-items { /* start each field on a new line*/
    clear: left;
}

.paragraphs-items div { /* eliminate the blocky results of div hell (eliminate some extra spacing in the process)*/
    float: left;
}

Next, I default to putting commas after every value …

.paragraphs-items .entity-paragraphs-item .content:after {
    content: ", ";
}

… except the last one.

.paragraphs-items .entity-paragraphs-item:last-child .content:after {
  content: "";
}

The last two items in the series should be separated by a ", and " …

.paragraphs-items .entity-paragraphs-item:nth-last-child(2) .content:after {
    content: ", and ";
}

… unless there are only two items, in which case it should be " and "

.paragraphs-items .entity-paragraphs-item:nth-last-child(2):nth-child(2) .content:after {
}

The parts that too some time to figure out were the special cases, especially the case where there are two values. The .entity-paragraphs-item:nth-last-child(2):nth-child(2) selector says if this is the second to last child of the bigger field container AND the first child element (except the label, :nth-child(2) would need to be replaced with :first-child if there was no label) override the after content to be " and ". In css you cannot select an element that has a certain number of child elements or a child that is one of a specific number of siblings, but you can triangulate in on the same result by selecting and element that is both the first element and second to last element to select the first of two elements.

It was fun to figure that out, and I hope someone finds it useful!

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.