Sanphorii
Sanphorii

Reputation: 145

Targeting Deeply Nested Elements

I am creating a custom Tumblr theme, and I've hit a small problem with <blockquote>. I am trying to show only the first <blockquote> (which is the last blockquote here in the code) in a <div>. For example:

<div class="post-content">
    <div class="body-text">
        <p><a href="http://example.com" class="tumblr_blog">Lorem</a>:</p>
        <blockquote>
            <p><a href="http://example.com" class="tumblr_blog">Ipsum</a>:</p>
            <blockquote>
                <p><a href="http://example.com" class="tumblr_blog">Dolor</a>:</p>
                <blockquote>
                    <p><a href="http://example.com" class="tumblr_blog">Sit</a>:</p>
                    <blockquote>
                        <p><a href="http://example.com" class="tumblr_blog">Amet</a>:</p>
                        <blockquote>
                            <p><a href="http://example.com" class="tumblr_blog">Consectetur</a>:</p>
                            <blockquote>
                                <p>Phasellus scelerisque dui sed velit facilisis pulvinar. Cras congue quis lorem ac feugiat.</p>
                            </blockquote>
                            <p>Aenean maximus mi sollicitudin, rutrum mauris vel, laoreet magna. Proin vitae tristique nisl.</p>
                        </blockquote>
                        <p>Maecenas eu orci consectetur, hendrerit arcu at, imperdiet massa.</p>
                    </blockquote>
                    <p>Vestibulum in ornare massa. Duis felis arcu, bibendum quis nisl eget, tincidunt facilisis urna.</p>
                </blockquote>
                <p>Fusce lobortis laoreet rutrum. Nam in arcu quis erat laoreet consequat eget quis est.</p>
            </blockquote>
            <p>Maecenas vitae dui viverra, varius nisl id, tempus turpis. Mauris nec lobortis sem.</p>
        </blockquote>
    </div>
</div>

Targeting the very last <blockquote> (which is the first quote shown on theme) is what I'm trying to do, but I'm not sure of the approach. The fact that the last blockquote is nested in other blockquotes is what's causing the difficulty.

Does anyone know of a way to target this last blockquote element?

Note: I should have mentioned this, since this is code for a Tumblr theme, the number of blockquotes will always vary, and so it can't be targeted by the amount.

Upvotes: 2

Views: 881

Answers (3)

Lexi
Lexi

Reputation: 1730

Unfortunately as far as I am aware you cannot make the selection you want via pure CSS. However, it is certainly possible via Javascript and jQuery makes the job much easier.

For example, you could use a function such as this to return the deepest nested blockquotes:

function getDeepestQuotes() {
  var el;
  // Get the first quote on the post to iterate through
  el = $('.body-text > blockquote');
  // Make sure there are actually quotes
  if (!el.length) {
    return null;
  }
  // Keep going down the stack until we find the one we want
  while (el.length > 0) {
    el = el.children('blockquote');
  }
  // el is now empty - step back up the stack and grab the last working state
  el = el.end();
  // el now contains the lowest scoped blockquotes
  return el
}

Live Example:

function getDeepestQuotes() {
  var el;
  // Get the first quote on the post to iterate through
  el = $('.body-text > blockquote');
  // Make sure there are actually quotes
  if (!el.length) {
    return null;
  }
  // Keep going down the stack until we find the one we want
  while (el.length > 0) {
    el = el.children('blockquote');
  }
  // el is now empty - step back up the stack and grab the last working state
  el = el.end();
  // el now contains the lowest scoped blockquotes
  return el
}

var q = getDeepestQuotes();
if (q) {
  q.addClass('last-quote');
}
blockquote {
  border-left: 15px solid gray;
}

.last-quote {
  border-left-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="post-content">
  <div class="body-text">
    <p><a href="http://example.com" class="tumblr_blog">Lorem</a>:</p>
    <blockquote>
      <p><a href="http://example.com" class="tumblr_blog">Ipsum</a>:</p>
      <blockquote>
        <p><a href="http://example.com" class="tumblr_blog">Dolor</a>:</p>
        <blockquote>
          <p><a href="http://example.com" class="tumblr_blog">Sit</a>:</p>
          <blockquote>
            <p><a href="http://example.com" class="tumblr_blog">Amet</a>:</p>
            <blockquote>
              <p><a href="http://example.com" class="tumblr_blog">Consectetur</a>:</p>
              <blockquote>
                <p>Phasellus scelerisque dui sed velit facilisis pulvinar. Cras congue quis lorem ac feugiat.</p>
              </blockquote>
              <p>Aenean maximus mi sollicitudin, rutrum mauris vel, laoreet magna. Proin vitae tristique nisl.</p>
            </blockquote>
            <p>Maecenas eu orci consectetur, hendrerit arcu at, imperdiet massa.</p>
          </blockquote>
          <p>Vestibulum in ornare massa. Duis felis arcu, bibendum quis nisl eget, tincidunt facilisis urna.</p>
        </blockquote>
        <p>Fusce lobortis laoreet rutrum. Nam in arcu quis erat laoreet consequat eget quis est.</p>
      </blockquote>
      <p>Maecenas vitae dui viverra, varius nisl id, tempus turpis. Mauris nec lobortis sem.</p>
    </blockquote>
  </div>
</div>

(Note that this will return all the quotes in the first quoted post. If you only want the first quote of a split quote, replace return el with return el.first(). Equally, this will work iteratively on split quotes to retrieve the most nested quote. If this is a problem you will need to chuck a .first() after each selector modification.)

The above example demonstrates jQuery's state stack - this can be useful if you wish to iterate through all the quotes to perform some form of nested styling.

As another example, here is a snippit that will render all quotes in a monochromatic rainbow with the oldest at black and the newest in light gray, no matter how deep it goes:

var el;
// Get the first quote on the post to iterate through
el = $('.body-text > blockquote');
// Make sure there are actually quotes
if (!el.length) {
  return;
}
// Keep going down the stack until we find the one we want
var depth = 0;
while (el.length > 0) {
  el = el.children('blockquote');
  depth++;
}
// el is now empty - step back up the stack and grab the last working state
el = el.end();
// Don't cancel depth - so that the top level has at least some gray in it.
//depth--;
// el now contains the lowest scoped blockquotes
// Save the max depth for later
var maxDepth = depth;
// Step back up the jQuery stack and operate on each level of quoting
while (el.length) {
  // Generate a black and white rainbow using the stack depth for blackness
  var hsla =  'hsla(0, 0%, ' + (100 - Math.floor((depth / maxDepth) * 100)) + '%, 1)';
  el.css('border-left-color', hsla );
  // Step up
  el = el.end();
  depth--;
}

Live Example:

function bwRainbowQuotes() {
  var el;
  // Get the first quote on the post to iterate through
  el = $('.body-text > blockquote');
  // Make sure there are actually quotes
  if (!el.length) {
    return;
  }
  // Keep going down the stack until we find the one we want
  var depth = 0;
  while (el.length > 0) {
    el = el.children('blockquote');
    depth++;
  }
  // el is now empty - step back up the stack and grab the last working state
  el = el.end();
  // Don't cancel depth - so that the top level has at least some gray in it.
  //depth--;
  // el now contains the lowest scoped blockquotes
  // Save the max depth for later
  var maxDepth = depth;
  // Step back up the jQuery stack and operate on each level of quoting
  while (el.length) {
    // Generate a black and white rainbow using the stack depth for blackness
    var hsla =  'hsla(0, 0%, ' + (100 - Math.floor((depth / maxDepth) * 100)) + '%, 1)';
    el.css('border-left-color', hsla );
    // Step up
    el = el.end();
    depth--;
  }
}

bwRainbowQuotes()
blockquote {
  border-left: 15px solid gray;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="post-content">
  <div class="body-text">
    <p><a href="http://example.com" class="tumblr_blog">Lorem</a>:</p>
    <blockquote>
      <p><a href="http://example.com" class="tumblr_blog">Ipsum</a>:</p>
      <blockquote>
        <p><a href="http://example.com" class="tumblr_blog">Dolor</a>:</p>
        <blockquote>
          <p><a href="http://example.com" class="tumblr_blog">Sit</a>:</p>
          <blockquote>
            <p><a href="http://example.com" class="tumblr_blog">Amet</a>:</p>
            <blockquote>
              <p><a href="http://example.com" class="tumblr_blog">Consectetur</a>:</p>
              <blockquote>
                <p>Phasellus scelerisque dui sed velit facilisis pulvinar. Cras congue quis lorem ac feugiat.</p>
              </blockquote>
              <p>Aenean maximus mi sollicitudin, rutrum mauris vel, laoreet magna. Proin vitae tristique nisl.</p>
            </blockquote>
            <p>Maecenas eu orci consectetur, hendrerit arcu at, imperdiet massa.</p>
          </blockquote>
          <p>Vestibulum in ornare massa. Duis felis arcu, bibendum quis nisl eget, tincidunt facilisis urna.</p>
        </blockquote>
        <p>Fusce lobortis laoreet rutrum. Nam in arcu quis erat laoreet consequat eget quis est.</p>
      </blockquote>
      <p>Maecenas vitae dui viverra, varius nisl id, tempus turpis. Mauris nec lobortis sem.</p>
    </blockquote>
  </div>
</div>

This is of course all possible via normal JS & DOM queries, but jQuery uncomplicates matters.

Upvotes: 1

MaKR
MaKR

Reputation: 1892

Expanding on first answer:

If you know that the last blockquote is always the third blockquote you could do:

.entry blockquote blockquote blockquote { }
// ".entry>blockquote>blockquote>blockquote" would be more specific

I would suggest a class or shorter unique selector. CSS selectors are calculated from the inside out. It'll first find all blockquotes, then remove those who aren't children of blockquotes, then remove those who aren't children of blockquotes who are children of blockquotes, then finally remove those who aren't inside of the entry class. You're best off using a class for the 3rd level or something else of the sort in order to increase performance.

Upvotes: 0

Adam Dunn
Adam Dunn

Reputation: 132

If you know that the last blockquote is always the third blockquote you could do:

.entry blockquote blockquote blockquote { }

Upvotes: 0

Related Questions